diff --git a/.github/assets/social-preview.svg b/.github/assets/social-preview.svg deleted file mode 100644 index 254321d..0000000 --- a/.github/assets/social-preview.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - KSI OPENCODE HARNESS - More agents - is not a strategy. - A real development pipeline for AI coding agents. - - PLAN - → - - DESIGN - → - - BUILD - → - - VERIFY - → - - STATE - github.com/khakisketch/KSI-Opencode-Harness - diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 25f053e..ce62c67 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -2,6 +2,7 @@ name: Check on: push: + branches: [main] pull_request: permissions: @@ -9,20 +10,13 @@ permissions: jobs: check: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - node: [20, 22] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - name: Set up Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: - node-version: ${{ matrix.node }} - - name: Run source checks + node-version: 22 + - name: Check current guidance and links run: npm run check - - name: Verify packed package - run: npm run check:package diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index ecd9401..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,176 +0,0 @@ -# Native OpenCode V2 installation - -KSI is distributed here as a standalone installer for OpenCode V2 native files, not as an OpenCode plugin. First inspect the source and decide which OpenCode configuration directory owns the server you use. For OpenChamber managed mode, use its managed OpenCode server's config; in external-server mode, use the external server's config. The OpenChamber orchestration tool is managed-only and is unrelated to KSI installation. - -## Installation ownership - -Install Node.js 20+ and OpenCode V2 separately using their upstream instructions. KSI's default `--apply` -writes only `agents/developer.md` and `agents/reviewer.md` under the selected target. -It does not install OpenCode, native roles, Node.js, providers, models, credentials, the Design Specialist, -OpenDesign, Superpowers, Docker, model CLIs, or MCP connections. -Your agent may help set those up after reading their documentation, but needs explicit approval for system -installation, shared/global settings, credentials, and service changes. KSI installation alone authorizes none of them. - -### Current operation is not an installer migration - -The KSI host's approved operating configuration uses native Plan/Build/General/Explore with the -default Design Specialist plus optional CE skills. Developer, Test Runner and Reviewer are -absent from that global environment, and Superpowers and the Goal plugin were removed. -The installer below still offers the optional Developer + Reviewer bundle; previous public -release 0.5.0-beta.8 was the historical three-role bundle. Neither reproduces that host -migration or authorizes restoring removed components. In an already-migrated environment, -inspect effective settings and do not apply role installation merely to satisfy these -examples. Preserve other tools' independent installations; another server's configuration -must be verified separately. - -## Public npm beta: preview and apply - -**Published `0.5.0-beta.9` is the current two-role release (Developer, Reviewer).** -Previous public release 0.5.0-beta.8 was the legacy three-role bundle (Developer, Test Runner, -Reviewer), not the current configuration. Removed-option behavior does not apply -retroactively to that historical release. - -With Node.js 20+, run from the root of the target project. `$PWD/.opencode` is an absolute -project-local target. `npm exec` obtains the package through npm's cache; obtaining it alone -makes no OpenCode configuration changes, and public installation needs no npm login -(registry publication authentication is a separate maintainer procedure). -The first command previews; only `--apply` writes files. - -Linux/macOS: - -```sh -npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target "$PWD/.opencode" -npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target "$PWD/.opencode" --apply -``` - -Windows PowerShell (absolute `Join-Path` target): - -```powershell -npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target (Join-Path (Get-Location) ".opencode") -npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target (Join-Path (Get-Location) ".opencode") --apply -``` - -Both commands pin the same exact beta. If the registry version is unavailable, use the source commands below. -This public beta is released through `next` and the default `latest` channel; default-channel delivery does not make it a stable release. Pin the exact version to avoid later channel movement. This beta does not imply acceptance of real-product visual output. - -### Another machine: project target versus server-global config - -On another PC, prepare Node.js 20+, OpenCode V2, and the model provider connection first using -each tool's official instructions — the installer provides none of them. Then decide the target: - -- One project only: the project's `.opencode` directory (Linux/macOS `$PWD/.opencode`, - Windows `(Join-Path (Get-Location) ".opencode")`). -- Every project on that machine's server: the OpenCode server's actual global config directory - (usually `~/.config/opencode`; managed or external servers differ — verify on that machine). - -Preview and apply use the same beta.9 commands with the chosen absolute target. No actual -Windows/macOS execution is promised — paths, browsers, and tools differ per machine, so verify -locally. The installer's two role files never reinstall removed global roles, plugins, or model -choices; an already-migrated environment must not re-apply them. - -### Let your agent install it (copy → paste) - -Paste this into the OpenCode session that should perform the installation: - -> "Install the KSI harness (ksi-opencode-harness) for this project. -> 1. Verify Node.js 20+ and OpenCode V2 are ready, then ask me whether the target is the project `.opencode` or the server's global config directory. -> 2. Use public npm 0.5.0-beta.9. Run the preview only first and show me the two files that would be written: `npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target ""` (Windows: `(Join-Path (Get-Location) ".opencode")`). Do not substitute previous public release 0.5.0-beta.8: it was the historical three-role bundle. No npm login is needed for installation. -> 3. After I confirm, apply with `--apply`; if there is a conflict, stop and ask me about `--replace`. -> 4. Verify developer and reviewer in the effective agent list — file existence alone is not proof. -> 5. Change nothing else: native roles, OpenCode, Node, providers, models, credentials, the Design Specialist, CE, browser tooling, global instructions (AGENTS.md), MCP connections and service restarts require separate approval." - -## Preview and apply from source (fallback) - -When the registry version is unavailable, or when installing from a reviewed source checkout -instead of the public package, run from that checkout. Use the -same reviewed source revision for preview and apply: - -```sh -node bin/ksi-opencode.mjs install --target /absolute/path/to/opencode-config -node bin/ksi-opencode.mjs install --target /absolute/path/to/opencode-config --apply -``` - -The first command is read-only and prints the proposed file contents as JSON. The second writes two custom `agents/*.md` files only if each destination is absent or already identical. If a file differs, apply stops before writing anything. To replace after reviewing the exact preview, use `--apply --replace`; each replaced file is copied to a unique `.bak-...` sibling first. Replacement refuses an agent file with native `model`, `variant`, or `steps` frontmatter so those preferences can be migrated manually first. Replacement is recoverable through backups but is not transactional if a later filesystem write fails. The installer rejects symlinked managed destinations. A project target is usually `/.opencode`; a global target is the server's OpenCode config directory. Use an absolute target to avoid writing into the wrong project. - -## Design work: default Specialist, optional OpenDesign - -Visual frontend work belongs by default to the Design Specialist (design/frontend/render -ownership under the global operating policy); Build owns overall integration and may apply -simple mechanical UI transfers directly, while API/data/auth contracts stay unchanged. -This installer distributes no design skills, craft references, or design-system package -and does not install or connect the Design Specialist or OpenDesign. OpenDesign is an -optional, separately and explicitly selected external-workspace integration — never the -sole, default, or mandatory design path, and its absence never blocks visual work that the -default Specialist path covers. If your work explicitly selects it, install the separate -application from its upstream documentation and explicitly configure its MCP connection. -Backend-only work needs no design installation. The responsibilities, tool contract, and -handoff requirements are in [the OpenDesign integration contract](docs/integrations/opendesign.md). - -Version 0.5.0 removed the KSI Design primary agent and the `--with-design-kit` option. Supplying the retired flag now fails with an explicit message and writes nothing. - -## Retiring the removed design role - -A version before 0.5.0 may have installed `agents/design.md` and, with `--with-design-kit`, three skill directories (`skills/frontend-design`, `skills/impeccable-design-polish`, `skills/web-design-guidelines`). This installer never deletes files it wrote earlier, so those remain until you remove them deliberately. That removal is a separate change to your configuration and needs its own review: - -1. Confirm the effective config directory the server actually uses. -2. Back up `agents/design.md` and the three skill directories. -3. Remove only those paths. Do not touch built-in Build/Plan/Explore, other plugins, credentials, or unrelated skills. -4. If your `opencode.jsonc` references `design` in agent routing or a restricted-agent list, remove that reference in the same reviewed change. -5. Restart or reconnect the client only if your setup requires it, and verify the fresh role catalog. - -Removing a role can change what a pending plan or session assumed. Re-check any in-progress work that referenced it. - -## Retiring the Test Runner role - -The current source bundle no longer ships `agents/test-runner.md`, and the `--developer-test-runner` option is removed with an explicit error message. Previous public release 0.5.0-beta.8 still has the old role and option. Developer now owns the tests for its own implementation; Reviewer reviews code and test adequacy (safe read-only checks only); Build verifies the integrated revision and unverified areas without repeating already-valid checks. Where a project requires independent execution verification, use an explicitly configured verification role or authorized CI. - -The installer never deletes a previously installed Test Runner file. An environment that already has `agents/test-runner.md` keeps it as a user-owned, explicitly configured verification role; any `opencode.jsonc` routing for it also stays untouched. If you want to retire it, back up the file and treat the removal — plus any matching routing entries — as a separate reviewed change to your configuration. - -An old installed Developer prompt can also mention the removed delegation option. -Compare it with the current template and review a backed-up prompt-only merge; -do not discard model/variant/steps, permissions, or customized instructions just -to make the role file identical. Re-check active work before retiring a shared -verification role. Removed-flag errors and retirement/preservation tests remain -intentional compatibility safeguards, not legacy functionality to delete. - -## Migrating an existing KSI plugin installation - -Preview native files first. An older installation may still have KSI-provided `agents/build.md`, `agents/plan.md`, `agents/explore.md`, `agents/design.md`, `agents/research.md`, `agents/design-critic.md`, and `commands/complete.md` or `commands/review.md`; this installer deliberately does not delete them. To restore built-in prompts or retire old roles, review the exact owners and paths, back up the files, and remove only those overrides with separate approval. A shared migration also needs a reviewed diff for the old KSI plugin registration, KSI-owned JSONC agent prompts/permissions, and any blanket global deny. Preserve unrelated plugins if present, other tools' independent installations, every model/variant/valid positive step value, JSONC comments, and unrelated settings. Preservation is not a request to reinstall Superpowers or Goal after an explicitly approved removal. Back up the global config before an approved edit; request shared-service restart separately. Do not infer fresh-session behavior from the installed files alone. - -The old `ksi-opencode-harness@0.4.0-beta.0` tarball was a V1-only plugin and has been removed from the registry; npm versions cannot be reused. Prefer the pinned beta version above for reproducible installation even after a later `latest` update. Removing a registry version does not remove any V1 plugin registration, old role files, or commands previously installed on a user's machine. Review and migrate those separately; a bare `npm install ksi-opencode-harness` only downloads the resolved package and does not apply OpenCode files. - -## Full harness composition (agent-guided) - -Current operation is a composition: OpenCode's native roles + the default Design Specialist + operating guidance + optional tools and the separate installer bundle. The two-role installer is an optional package, not a required replacement for General or a migration of global settings. Installing OpenCode and choosing a model/provider stay with each user — the composition **does not force a model**. (On the KSI host only, the user explicitly requested OpenAI gpt-6-luna-fast for the Design Specialist with the max variant preserved; never force this on other machines.) Paste this into a session to have the agent inspect what the approved environment actually needs: - -> "Set up this repository's KSI harness composition in my environment. OpenCode and my model/provider choice are mine — do not change or force them. -> 1. Inspect the current state read-only: OpenCode V2 + Node 20+, installed agents, existing settings and tools. -> 2. Verify effective native Plan/Build/General/Explore and the default Design Specialist. Do not restore Developer/Test Runner/Reviewer/Superpowers/Goal in an already-migrated environment. Only if I request the separate role bundle, use public npm 0.5.0-beta.9, preview → my confirmation → `--apply`, with the target confirmed first. Previous public release 0.5.0-beta.8 was the historical three-role release, not the current operating configuration. -> 3. Operating guidance: read `examples/autonomous-development.md` and propose only the merge into my `AGENTS.md` → apply after approval. -> 4. Optional tools — relevant CE skills, a browser tool (Playwright CLI or agent-browser, pick one), OpenDesign + design MCP (only where I explicitly select that external workspace): reuse prepared capabilities first. Propose a missing capability from official documentation only when needed; get my approval for new installs, credentials and service changes. No per-project ce-setup or duplicate planning documents by default. -> 5. Finish with a per-component status table and any skipped items with reasons." - -| Component | Required | Installed by | Verify | -| --- | --- | --- | --- | -| OpenCode V2 · Node 20+ | yes | user (official docs) | `opencode --version` | -| Native roles + default Design Specialist | native base | OpenCode | effective agent list; KSI two-role bundle is a separate option | -| Operating guidance (`AGENTS.md`) | recommended | user + agent (reviewed merge) | a new session loads it | -| CE | optional | user (official repo) | actual loading of relevant skills; no mandatory full workflow | -| Browser tool (one of) | for UI verification | user | one real page snapshot | -| OpenDesign + design MCP | for design work | user (separate app) | one project read | - -Repo-only extras (for example the OpenDesign completion-notifier plugin) are not part of the npm package; they are handled from the checkout with their own scripts. - -## Runtime and verification boundaries (package scope) - -### Optional autonomous local development policy - -Review [the shared policy section](examples/autonomous-development.md) and merge it into the server's global `AGENTS.md` or the intended repository's `AGENTS.md` if you want to adopt it. Back up the destination and preserve its existing project rules; do not replace the whole file or assume a Markdown link imports instructions automatically. The installer never performs this merge. - -Adoption delegates approved local implementation, bounded helpers, integration, verification and task-owned commits without routine progress/commit questions. Explicit user/repository restrictions still win. Push/publication/deployment require target/effect authorization, which can be granted once and reused unless the target, effects or risk materially change. Native tool/identity approvals remain enforced. Plan stays non-implementing; material design goes through the default Design Specialist (or OpenDesign's local execution where explicitly selected) and direction approval, while UI-reference skills remain supplementary. This guidance does not change models, permissions, upstream skills or goal settings, guarantee perpetual execution, or authorize new backlog work. - -OpenCode supplies Primary `build` and `plan` and Subagents `general` and `explore` without KSI replacements. The optional installer adds visible Subagents `developer` and `reviewer`. Their files omit `model`, `variant`, and `steps`: existing preferences remain user-owned. The installer never edits `opencode.jsonc`, `AGENTS.md`, credentials, providers, MCPs, plugins, Codex/Claude configuration, or separately installed skills, and does not restart services. A short cross-tool [project guidance template](examples/project-AGENTS.md) is optional and is not applied automatically. Inspect other config layers if a role ID already exists there; file collisions alone do not prove effective precedence. - -Test Runner is no longer part of the default bundle, and `--developer-test-runner` is removed. Developer owns the tests for its own implementation; Reviewer independently reviews code and test adequacy (safe read-only checks only), and Build verifies the integrated revision and unverified areas without repeating already-valid checks. Where a project requires independent execution verification beyond that, an explicitly configured verification role or authorized CI provides it. A previously installed `agents/test-runner.md` is never deleted and keeps working as such a configured role. - -The installer adds no slash commands. Plugin-only automatic checkpoint/product-state injection, six `ksi_*` evidence tools, delegation/signature checks, helper tracking, and output archiving are unavailable. Normal OpenCode tools, installed skills, and project `AGENTS.md` can support the workflow without recreating those hooks. Verify the installed files and effective modes/permissions in a fresh V2 session. A no-auth HTTP 401 is not a passing shared-service check. Provider calls, OpenChamber UI login, and Human end-to-end acceptance are separate actions. diff --git a/README.md b/README.md index 4f68780..644b1de 100644 --- a/README.md +++ b/README.md @@ -1,454 +1,80 @@ # KSI OpenCode Harness -### AI에게 일을 맡기되, 방향과 완료의 기준은 사람이 정합니다. +**OpenCode의 기본 능력으로 제품을 구현하고, 실제 동작을 검증하며, 완료 기준을 충족하면 멈춥니다.** -**KSI는 OpenCode를 대화의 중심에 두고, 필요한 역할과 도구만 연결해 제품을 만듭니다.** -먼저 무엇을 바꿀지 합의하고, 실제 프로젝트에서 구현하고, 확인한 근거와 남은 일을 함께 보고합니다. +[![OpenCode V2](https://img.shields.io/badge/OpenCode-V2-blue)](https://opencode.ai/v2/) +[![MIT](https://img.shields.io/badge/license-MIT-lightgrey)](LICENSE) +[![Check](https://github.com/khakisketch/KSI-Opencode-Harness/actions/workflows/check.yml/badge.svg)](https://github.com/khakisketch/KSI-Opencode-Harness/actions/workflows/check.yml) -[시작하기](#바로-시작하기) · [작업 흐름](#ksi는-이렇게-작업합니다) · [요청 예시](#실제로-이렇게-요청하세요) · [시각 가이드](docs/walkthrough.md) · [설치 가이드](INSTALL.md) +## 사용 방법 -**OpenCode V2** · **선택형 역할 번들 2개(Developer · Reviewer)** · **공개 Beta `0.5.0-beta.9`** · [MIT](LICENSE) +기존 OpenCode에서 **원하는 결과·제약·완료 기준**을 알려주세요. 별도 KSI 설치는 필요 없습니다. +실행·권한·세션 관리는 OpenCode Native에 맡깁니다. [제품 완성 원칙](examples/project-AGENTS.md)은 +선택형 예시입니다. 필요할 때만 기존 전역 또는 프로젝트 `AGENTS.md` 한 곳에 병합하고, +같은 정책을 여러 지침 파일에 반복하지 마세요. -> **운영 방식과 설치 범위는 다릅니다.** 아래는 KSI가 일하는 방식입니다. -> 공개 npm `0.5.0-beta.9`는 Developer · Reviewer의 **두 에이전트 파일만** 쓰는 선택형 번들입니다. -> 이전 공개판 0.5.0-beta.8은 Test Runner를 포함한 이전 3역할 배포판이었습니다. -> 기존에 설치된 Test Runner 등 다른 역할 파일은 남으며, 새 설치가 자동 삭제하지 않습니다. -> 도구 설치, 디자인 연결, 프로젝트 지침, 자동 배포까지 묶어 주는 올인원 플러그인이 아닙니다. -> 이 패키지는 네이티브 역할·OpenCode·Node·provider·모델·credentials·Design Specialist·CE·브라우저·전역 지침·MCP를 설치하지 않습니다. +> **npm 설치기는 레거시입니다.** 공개 `ksi-opencode-harness` beta 배포본에는 과거 역할과 +> 운영 문서가 포함돼 있습니다. 현재 구성에 설치하거나 재적용하지 마세요. -**현재 KSI 전역 운영(2026-10-09 전환 이후)**은 네이티브 Plan · Build · General · Explore와 -기본 Design Specialist, 선택적 CE 스킬입니다. Developer · Test Runner · Reviewer와 -Superpowers · Goal은 이 전역 환경에서 제거되었습니다. **아래 역할 설치 예시는 -별도 선택형 패키지의 사용법이지, 현재 운영에 역할을 다시 추가하라는 지시가 아닙니다.** -이미 전환한 환경에서는 재설치하지 말고 실제 서버의 유효 설정을 확인하세요. +- **기능 수정:** “저장 실패 후 재시도를 고쳐줘. 기존 API를 유지하고 실제 저장·재시도 결과를 확인해줘.” +- **새 기능:** “관리자 주문 취소를 구현해줘. 권한과 결제 계약을 보존하고 실패·거부 시나리오까지 검증해줘.” +- **화면 개선:** “예약 화면을 실제 소스에서 개선해줘. 저장 동작을 유지하고 실제 렌더와 사용자 흐름을 확인해줘.” ---- +시각적 전문성이 필요한 작업에는 기존 Design Specialist를 활용합니다. 작은 기계적 변경에는 +위임을 강제하지 않으며, Plan 분석·리뷰도 고정 단계가 아닙니다. +설정이 필요할 때만 [최소 설정 예시](opencode.jsonc.example)를 병합하세요. Plan `all`은 +메인·자식 호출을 모두 허용하지만 자동 호출을 보장하지 않습니다. 기존 모델·권한은 유지합니다. +커밋·Push·배포에 KSI 별도 승인 규칙을 추가하지 않습니다. 기존 OpenCode 권한과 사용자 요청 범위를 따릅니다. +CE·OpenDesign은 명시적으로 선택하는 외부 도구이지 기본 의존성이 아닙니다. 기존 전역 설치는 임의로 삭제하지 않습니다. -## KSI는 이렇게 작업합니다 +## 실제 검증 -**책임과 완료 근거의 흐름**은 아래 텍스트 다이어그램이 기준입니다. [시각 가이드](docs/walkthrough.md)의 -과거 캡처는 별도 명시적 선택 시의 OpenDesign 실행 예시(역사적 참고용)이며, 현재 기본 흐름을 증명하지 않습니다. +에이전트 리뷰를 받았다는 사실은 제품 동작 증거가 아닙니다. 기존 프로젝트의 테스트·빌드와 +실제 사용자 흐름을 확인하고, 발견한 관련 결함을 수정한 뒤 영향받는 부분을 다시 검증합니다. +검사별 참고 범위는 [검증 안내](docs/verification.md)에 있으며 모든 검사를 매번 요구하지 않습니다. -**목표·권한 합의 → 필요한 만큼 계획 → 구현·통합·검증·로컬 커밋까지 완주 → 결과 확인·외부 반영** - -- **Plan**은 필요할 때 요청과 실제 프로젝트를 이해하고 변경 방향·범위·불변조건·완료 증거를 정리합니다. -- **Build**는 승인된 변경의 전체 실행·통합·기술적 완료 근거와 보고를 책임지며, 작업 성격에 따라 직접 구현하거나 전문 실행을 위임합니다. 단순한 기계적 UI 전달은 Build가 직접 처리할 수 있습니다. -- **General**은 맡겨진 비시각적 기능의 조사·구현·수정·자체 테스트를 수행합니다. **CE는 에이전트가 아니라 필요할 때 불러오는 스킬 묶음**이며, Build·General이 유효 권한 안에서 사용합니다. -- **Design Specialist(기본)**는 시각적 판단·실제 UI source/style 수정·렌더·디자인 리뷰·직접 개선을 맡습니다. - **OpenDesign(로컬 실행)**은 별도로 명시적으로 선택한 환경에서만 쓰는 외부 워크스페이스 통합이며, 기본·유일 경로가 아닙니다. - -**승인된 목표 안에서는 문제 발견·우선순위 판단·수정·재검증까지 에이전트가 맡습니다.** -구현 후 실제 결과를 검토하고, 결함을 고친 뒤 관련 검사를 다시 수행해 결과를 보고합니다. -개선 목록만 넘기거나 매번 “어느 것부터 고칠까요?”라고 묻지 않습니다. 사용자는 목표와 -중요한 결정을 맡고, 일상적인 수정 순서·검사·반복은 맡지 않습니다. 점검만 요청한 작업은 -점검 범위를 지키며, 새 범위·디자인 방향·권한·배포 같은 실제 결정은 그대로 구분합니다. -[기본 검토·수정·재검증 루프 →](docs/execution.md#own-the-completion-loop-not-a-list-for-the-user) - -디자인 작업은 **제품별 대표 프로젝트를 재사용**하고, 실행 전에 실제 소스 연결을 확인합니다. -화면·작업·브랜치가 달라졌다는 이유만으로 새 프로젝트를 만들지 않습니다. 실제 제품은 -기존 제품 서버 링크에서 확인하며, 디자인 파일 미리보기와 로그인·저장·새로고침 검증을 -구분합니다. 기본 Design Specialist 흐름에서는 사용자는 OpenCode 하나에서 지휘하면 됩니다. -OpenDesign을 별도로 명시적으로 선택한 경우에만 그 외부 워크스페이스가 시각 실행을 대신하고, -OpenDesign을 직접 열거나 결과를 옮길 필요가 없습니다. -[대표 프로젝트 재사용과 연결 확인 →](docs/integrations/opendesign.md#reuse-the-representative-product-project) - -```text -사용자 원하는 결과 · 문제 · 아이디어 · 제약 - │ - ▼ -필요하면 Plan 프로젝트와 기존 결정 이해 - 변경 방향 · 범위 · 불변조건 · 완료 증거 정의 - │ - ▼ -Build 전체 실행·통합 책임 — 필요한 조사·구현·검토를 조율 - │ - ├─ 비시각적 Engineering - │ ├─ Build 직접 구현 - │ └─ General: 맡겨진 엔지니어링 구현·수정·자체 테스트 - │ - ├─ Visual Frontend (기본: Design Specialist) - │ ├─ Design Specialist: 분석 → 실제 UI source/style 수정 → 렌더 - │ │ → Design Review → 결함 직접 개선·재확인 - │ └─ OpenDesign(로컬 실행): 별도 명시적 선택 시에만 쓰는 외부 워크스페이스 - │ - └─ CE 코드 리뷰(ce-code-review) · 필요 시: 독립 코드·테스트 검토 - │ - ▼ -Build 결과 통합 · 실제 제품의 기능 계약 확인 - 미검증·영향받는 영역만 검사 · 결함 재분류 → 담당 경로에서 수정·재위임 - 완료 근거 확인 · 작업 단위 로컬 커밋 · 결과 보고 - │ - ▼ -사용자 실제 제품 결과 확인·수락 · 중요한 판단·외부 반영 권한 -``` - -명확한 작업은 **Plan을 거치지 않고 Build에서 바로 시작**할 수 있습니다. Plan과 Build는 책임이 다른 기본 모드이지, 모든 요청에서 순서대로 호출해야 하는 두 단계가 아닙니다. Explore 등은 필요할 때 조사를 돕습니다. - -Build가 작업에 맞는 방법을 선택합니다. CE 전체 워크플로·A/B/C 모드 선택·작업 중 방법 혼합 금지·General 사용률 목표는 강제하지 않습니다. General은 하위 에이전트를 호출하지 못하므로 필요한 위임은 Build가 맡습니다. - -위 다이어그램은 **책임과 완료 근거의 흐름**입니다. 시각 실행의 기본 담당은 Design Specialist이며, -OpenDesign을 별도로 명시적으로 선택한 환경에서만 그 외부 워크스페이스가 시각 실행을 대신합니다. -어느 쪽도 내부 child agent나 또 다른 개발 Primary가 아니라 전문 실행 경로입니다. Build가 목적·필요한 결과·보존할 기능/API/데이터/권한/브랜드 계약을 전달하고, 디자인 쪽은 그 안에서 레이아웃·정보 계층·컴포넌트 구성·반응형 표현을 판단하고 구현할 재량을 가집니다. Build가 시각적 세부 구현을 대신 결정하는 구조가 아닙니다. API·데이터·인증 계약은 그대로 유지됩니다. - -기존 화면 재설계는 감사부터 시작하고, 위임된 시각 방향은 디자인 측이 기존 디자인 시스템과 브리프에 맞춰 정합니다. 방향 옵션은 사용자가 요청하거나 브랜드 정체성이 쟁점일 때만 제시합니다. 큰 작업은 실제 렌더를 읽는 별도 디자인 리뷰 단계가 필요하며, 새 브랜드 정체성·고객 납품물·범위 밖 변경·선검토 요청은 승인 게이트를 유지합니다. 작은 수정은 간결하게 실행합니다. [감사·방향 선택·리뷰와 작은 수정의 구분 →](docs/integrations/opendesign.md#design-quality-loop-design-owned-analysis-review-and-refinement) - -### 작업 크기보다 작업 성격으로 소유권을 나눕니다 - -| 책임 | 담당 | 범위 | -| --- | --- | --- | -| **Visual Frontend** | Design Specialist(기본) · OpenDesign(로컬 실행, 별도 명시적 선택 시) | UI/UX 분석, 레이아웃·정보 계층·컴포넌트 표현, 실제 UI source/style, 반응형·시각적 상호작용 상태, CSS·간격·overflow, 렌더 리뷰·개선 | -| **Engineering** | Build / General | API·데이터·인증·권한·업무 상태·저장·비시각적 클라이언트 로직 | -| **통합·기술적 완료 근거** | Build | 계약·제약 전달, 기능 검증 조율, 결과 통합, 결함 분류·재위임, Git·보고 | -| **통합 검증·기술적 완료 판단** | Build | 통합된 버전의 실제 동작·기능 계약 확인과 미검증 영역 검사 — 이미 유효한 자체 테스트·검증은 반복하지 않음 | -| **독립 코드·테스트 검토** | CE 코드 리뷰(ce-code-review) · 필요 시 | 검토 — 코드·동작·테스트 적절성과 누락 확인, 필요한 경우 안전한 읽기 전용 검사; 디자인 리뷰·사용자 수락을 대신하지 않음 | - -**작은 CSS 간격 수정은 디자인 쪽, 큰 DB 마이그레이션은 엔지니어링 쪽**입니다. 크기는 얼마나 무겁게 실행할지, 성격은 누가 소유할지를 결정합니다. 같은 파일에 두 영역이 섞여 있으면 순차 인계하며, 한 번에 한 작성자만 수정합니다. - -디자인 실행 경로(기본 Design Specialist, 또는 별도 명시적 선택 시의 OpenDesign)나 필요한 소스·렌더 검수 경로가 준비되지 않으면 해당 단계의 차단을 보고하고 독립적인 비시각 작업만 계속합니다. **OpenDesign 미연결을 이유로 기본 Specialist 흐름이 막히지 않으며, 반대로 OpenDesign을 선택하지 않은 환경에서 Build나 General이 시각적 구현을 대신하지 않습니다.** [접근 확인·차단 시 처리 →](docs/integrations/opendesign.md#resolve-once-verify-the-actual-capability) - -### 자체 테스트와 독립 검토·검증은 다릅니다 - -아래 운영 지침을 채택한 작업에서는 **General이 자신이 구현한 기능의 테스트까지 책임지고**, 독립 리뷰가 필요한 변경은 **CE 코드 리뷰(ce-code-review)**로 수행합니다. 마일스톤·완료 시 Build가 통합된 버전에서 미검증·영향받는 영역을 확인하고, 이미 유효한 검증 결과는 반복하지 않습니다. 별도 독립 실행 검증이 필요한 프로젝트는 명시적으로 구성한 검증 역할이나 승인된 CI로 충족할 수 있습니다. CE 리뷰와 디자인 쪽의 렌더 리뷰는 서로 다른 근거입니다. - -**디자인 작업 성공 ≠ 전체 제품 변경 완료.** 화면을 개선했어도 API 연결·권한·저장이 깨졌다면 Build가 실제 제품에서 확인하고, 비시각적 결함은 직접 또는 General에게, 시각적 결함은 디자인 쪽에 돌려 수정한 뒤 재검증합니다. 기술적 완료 근거·사용자 수락·push/deploy 등 외부 반영은 각각 구분합니다. - -### 세 가지 원칙 - -| 원칙 | 실제로는 이렇게 합니다 | -| --- | --- | -| **작게 맡기기** | 비시각적 수정은 Build가 처리하고, 작은 CSS·화면 수정은 디자인 쪽에 간결하게 맡깁니다. 전체 감사나 새 요구사항 인터뷰는 필요하지 않습니다. | -| **책임을 분리하기** | 전문 영역의 구현 재량은 담당 경로에 두고, Build가 통합·기술적 완료 근거·최종 보고를 맡습니다. | -| **확인한 것만 완료라 하기** | “테스트 통과”, “배포됨”, “사용자가 만족함”을 서로 다른 상태로 보고합니다. | - -이 흐름은 **정의된 운영 계약이지, 런타임이 강제로 보장하는 워크플로 엔진은 아닙니다.** 프롬프트와 권한으로 역할을 안내하며, 설치나 테스트 통과만으로 실제 역할 준수·제품 품질을 보장하지 않습니다. OS 샌드박스도 제공하지 않습니다. - -**리뷰(CE 코드 리뷰 포함)의 ‘읽기 전용’은 모든 쓰기 경로 차단을 뜻하지 않습니다.** 리뷰는 코드를 수정하지 않는 것이 역할 계약이지만, shell·MCP·Code Mode 등의 유효 권한은 기존 설정을 상속합니다. 다른 도구로 구현·설정·외부 데이터를 바꾸지 않는 것은 리뷰 절차의 지침이며, 검사는 안전한 읽기 전용 범위에 한정합니다. 강제 격리가 필요하다면 별도의 권한·실행 환경 설계가 필요합니다. 설치 도구가 이를 자동 적용하지는 않습니다. - -### 승인된 개발은 중간 확인 없이 완주합니다 - -[선택형 자율 개발 지침](examples/autonomous-development.md)을 실제 `AGENTS.md`에 반영하면, Build에 승인된 구현·필요한 위임·통합·검증·작업 단위 로컬 커밋을 맡길 수 있습니다. “다음 진행할까요?”나 “커밋할까요?”를 단계마다 묻지 않고, 중요한 진행 상황을 보고하면서 이어갑니다. 명시적인 커밋 금지나 저장소 제약은 우선합니다. - -사용자에게 돌아오는 경우는 **최종 결과 보고, 중요한 범위·제품·디자인 결정, 비용·위험 증가, 승인되지 않은 외부 영향, 파괴적·보안 민감 작업, 실제 차단 또는 실행 예산 소진**입니다. 해결 가능한 테스트 실패나 중간 작업 완료는 멈출 이유가 아닙니다. - -푸시·공개·배포는 대상과 영향을 승인받아야 하지만, 처음부터 승인했다면 같은 승인을 다시 묻지 않습니다. 단, 대상·영향·위험이 중대하게 바뀌면 새 승인이 필요합니다. 네이티브 도구·인증 승인은 별개입니다. 기술적 완료·사용자 수락·외부 배포를 구분하며, 수락 대기를 이유로 남은 승인 작업을 멈추거나 새로운 과업을 임의로 시작하지 않습니다. - -이 정책은 설치 도구가 자동 적용하지 않습니다. 기본 에이전트, 모델·권한·upstream 스킬은 유지합니다. 현재 KSI 전역 환경에는 Goal이나 응답 종료 뒤의 자동 계속 실행이 없으며, 지침만으로 무제한 실행을 보장하지 않습니다. - -채택한 운영 지침에서는 **브랜치·워크트리 같은 작업 공간 방식은 선택한 실행 방법(네이티브 흐름 또는 CE 워크플로)의 규칙을 따르고**, Build는 사용자 작업 보존·통합·검증·보고를 맡습니다. 사용자가 작업 공간 목록을 보며 단계마다 지시할 필요가 없습니다. UI 변경은 실제 제품 서버에서 필요한 흐름·저장 결과·키보드·화면 크기 등을 확인하되, 작은 수정마다 전체 감사나 중복 브라우저 검증을 강요하지 않습니다. [작업 공간·브라우저 검증 절차 →](docs/execution.md) - -기본 개발은 **feature 브랜치**에서 하고, 같은 작업의 기존 feature 브랜치가 있으면 새로 만들지 않고 재사용합니다. 워크트리는 다른 세션·동시 작성자·병렬 개발·기존 체크아웃 보호처럼 **실제 격리가 필요할 때만** 만듭니다. CE 워크플로는 자체 브랜치·워크스페이스 규칙을 적용합니다. 여러 화면을 기존 브랜드로 통일하는 내부 도구 작업은 대표 화면을 실제 소스에서 구현·검증한 뒤 한 번 리뷰하고, 그 결과를 승인하면 나머지는 화면별 승인 없이 단계 검증하며 전개합니다. - -### 상태 확인과 멈춤도 자연어로 - -- **“지금 어디까지 됐어?”** — 실제 Git·작업 기록·검증 근거를 대조해 작업 상태, 사용자 확인과 외부 반영을 구분합니다. -- **“이 프로젝트 바로 작업 가능한지 확인해줘.”** — 준비 상태를 읽기 전용으로 확인하며, 파일 존재와 실제 연결·권한 검증을 혼동하지 않습니다. -- **“이 작업 잠깐 멈춰.”** — 후속 작업을 멈추고 현재 세션의 완료 알림 재개 권한을 일시정지합니다. 현재 환경에는 Goal을 멈출 필요가 없으며, 외부 디자인 실행은 자동 취소되지 않습니다. 사용할 수 없는 스위치나 계속 실행 중인 외부 작업은 별도로 보고합니다. -- **“이어서 진행해줘.”** — 해당 프로젝트 기록의 범위·승인·연결 정보를 재사용하고 현재 상태를 확인합니다. 새로운 목표나 다른 세션 작업을 자동 생성하지 않습니다. -- **“세션이 멈춘 것 같아.”** — 미커밋 작업 보존 → 재시도 → 로그 진단 순서로 복구합니다. 모델·provider 오류는 환경 문제로 분류하고, 전달된 알림이 아직 처리되지 않았을 수 있으므로 대기 중 작업을 다시 확인합니다. - -이는 기존 도구와 운영 지침의 흐름이지 새 슬래시 명령이나 강제 실행 기능이 아닙니다. 소스 체크아웃에는 짧은 상태 보고와 준비 점검 도구가 있습니다([사용법·한계 →](docs/execution.md#reuse-project-context-inspect-status-and-preparation)); npm 역할 설치에 포함되지는 않습니다. - -## 계획과 결정은 어디에 남나요? - -여러 단계의 계획을 요청하면 **제안 상태로 작업 기록에 저장**하고, 승인한 범위와 -실제 진행·검증 결과를 구분합니다. 파일을 만들었다고 구현이 승인되는 것은 아닙니다. -단순 질문이나 작은 수정마다 새 기획서를 만드는 방식은 아닙니다. - -- **상세 계획·결정·검증:** `docs/superpowers/plans/`의 해당 작업 기록 한 곳 -- **제품 목표·현재 단계:** `docs/superpowers/product-state.md`의 요약과 기록 링크 -- **다음 세션 인계:** `.opencode/working-state.md`의 짧은 상태와 기록 링크 - -**Plan과 Build는 같은 승인된 작업 계획을 이어서 사용합니다.** Plan이 작성했다는 -이유만으로 실행이 승인되지는 않습니다. 목표·범위/제외·수용 기준·의존성/순서·검증 방법과 -실제 승인 상태를 작업 크기에 맞게 남기고, CE 스킬을 사용해도 별도 경쟁 계획을 만들지 않습니다. 기존 `docs/superpowers/`와 SDD 기록은 보존하는 경로이지 제거된 플러그인의 의존성이 아닙니다. - -Plan의 기본 작성 위치가 `~/.opencode/plan/`이라 저장소 기록을 직접 쓰지 못한다면, -실제 파일 경로·버전/구간·승인 상태·프로젝트 기록 대상을 인계합니다. Build는 원본을 읽고 -승인 내용을 보존해 프로젝트 기록으로 옮깁니다. 미승인 초안은 옮겨도 미승인이며, -옮긴 뒤에는 프로젝트 기록을 실행 기준으로 삼아 두 계획을 따로 갱신하지 않습니다. -Build는 그 기준과 실제 Git·구현·검증을 대조해 진척을 갱신하고, Plan은 재평가·수정안을 -제안합니다. 작은 수정에 Plan을 강제하거나 기본 프롬프트·권한을 확대하는 방식은 아닙니다. -[Plan→Build 계획·저장·실행 기준 →](docs/execution.md#one-approved-plan-across-plan-and-build) - -Build는 작업자에게 필요한 문서 경로·범위·완료 기준을 전달하고, 작업자는 실제로 -읽은 기준과 결과·차단 사항을 반환합니다. Build가 공통 기록을 정리합니다. -**문서 공유는 대화 전체의 자동 공유가 아닙니다.** 임시 시범 결과도 결론·검사 근거·한계를 -저장소에 요약하며, 원본 로그·인증 정보는 무조건 복사하지 않습니다. -이것은 채택한 운영 지침이고 설치기가 문서를 자동 작성하는 기능은 아닙니다. -[기록 시점·승인 상태·문서 인계 절차 →](docs/execution.md#record-plans-and-bind-handoffs-to-documents) - -## 누가 무엇을 맡나요 - -**주 대화는 OpenCode 기본 Build / Plan에서 이어갑니다.** KSI는 이 역할들을 교체하지 않습니다. - -| 역할 | 담당 | 경계 | -| --- | --- | --- | -| **사용자** | 목표, 범위, 권한, 최종 승인 | 테스트가 사용자 승인을 대신하지 않습니다. | -| **Plan** · 기본 | 프로젝트·기존 결정 이해, 변경 방향·범위·불변조건·완료 증거 정리 | 일반 제품 파일 구현은 Build로 넘기며, 모든 수정의 필수 단계는 아닙니다. | -| **Build** · 기본 | 전체 실행·통합·기술적 완료 근거·검증 조율·로컬 커밋·보고 | 비시각적 구현은 직접 또는 위임하고 단순한 기계적 UI 전달도 직접 처리할 수 있으며, 시각적 구현은 디자인 쪽에 맡깁니다. 사용자 수락·외부 반영 권한을 대신 결정하지 않습니다. API·데이터·인증 계약은 그대로 유지됩니다. | -| **Explore** · 기본 | 기존 코드와 구조 조사 | 구현 담당이 아닙니다. | -| **General** · 기본 | 맡겨진 비시각적 엔지니어링 조사·구현·수정·자체 테스트 | 하위 에이전트를 호출하지 않으며, 시각적 구현·제품 수락을 대신하지 않습니다. | -| **Design Specialist** · 기본 | 시각적 판단·실제 UI source/style 수정·렌더·디자인 리뷰·직접 개선 | 내부 child agent나 별도 개발 Primary가 아닙니다. 제품 전체의 통합 책임이나 사용자 승인을 대신하지 않습니다. | -| **CE 스킬** · 선택 | 현재 실행 에이전트에 필요한 전문 절차 제공 | 별도 실행 에이전트나 위임 대상이 아니며, 범위·권한을 확대하지 않습니다. | -| **OpenDesign(로컬 실행)** · 별도 명시적 선택 시 | 외부 워크스페이스에서 시각 실행 대행 | 기본·유일 디자인 경로가 아니며, 미선택이 시각 작업의 차단 사유가 되지 않습니다. 제품 전체의 통합 책임이나 사용자 승인을 대신하지 않습니다. | - -위 표는 현재 KSI 전역 운영입니다. 선택형 설치 번들의 Developer · Reviewer 구성은 아래 설치 절차를 따릅니다. -이전 공개판 0.5.0-beta.8의 3역할 구성과는 구분됩니다. -구현 위임·조사·코드 검토는 필요에 맞게 선택하되, 채택한 완료 계약의 독립 검토·통합 검증을 자체 테스트로 대체하지 않습니다. 모델·provider·reasoning variant·step budget은 사용자가 결정합니다. KSI 호스트에서만 Design Specialist 선호 모델은 사용자가 명시적으로 요청한 OpenAI gpt-6-luna-fast(max variant 유지)이며, 다른 PC·소비자에게 강제하지 않습니다. - -**General은 명확한 비시각적 구현 과업의 조사·구현·자체 테스트에 적극 활용합니다.** Build는 목적·계약·완료 기준을 간결하게 전달하고, 반환된 실제 diff를 확인해 통합·복구를 책임집니다. 맥락이 강하게 얽힌 판단이나 명백한 통합 수정은 직접 처리할 수 있습니다. 강제 위임·사용률 목표·위임을 위한 작업 쪼개기는 하지 않습니다([과업 전달·반환·복구 기준 →](docs/execution.md#use-general-effectively-without-mandatory-delegation)). - -## 실제로 이렇게 요청하세요 - -명령어를 외울 필요 없이 **결과와 제약을 자연어로 전달**하면 됩니다. 아래는 KSI 전용 슬래시 명령이 아닙니다. - -### 01 · 작은 수정 — 담당을 나눠 간결하게 처리 - -> “저장 실패 후 재시도 로직을 고쳐줘. 화면은 그대로 두고 저장·재시도 검사 결과를 알려줘.” - -비시각적 로직은 Build가 직접 또는 General에게 맡겨 구현하고, 통합·검증을 책임집니다. 버튼 배치·스타일·반응형 같은 시각적 수정은 작더라도 디자인 쪽이 실제 소스에서 수정·렌더 확인하고, Build가 기능을 검증합니다. 작은 시각 수정마다 새 인테이크나 전체 화면 감사를 하지는 않습니다. - -### 02 · 큰 기능 — 합의하고 나눠서 구현 - -> “관리자가 주문을 취소하는 기능이 필요해. 먼저 기존 구조와 예외 상황을 조사해서 계획을 제안해줘. 승인한 뒤에는 필요한 위임·구현·통합·검증·로컬 커밋까지 중간 진행 확인 없이 완주해줘. 푸시·배포는 별도 승인 전에는 하지 마.” - -Plan에서 방향을 합의하고 Build에서 실행합니다. 역할을 나눠도 통합 책임은 Build에 남습니다. - -### 03 · 새 디자인 — 실제 제품 소스에서 - -> “이 프로젝트의 예약 화면을 새로 디자인하고 싶어. 실제 제품 소스를 확인하고, Design Specialist가 제품 소스에서 직접 작업하게 해줘. 방향이 크게 바뀌면 먼저 보여주고, 구현·검증까지 진행해줘.” - -기본 흐름은 Design Specialist가 맡습니다. OpenDesign을 별도로 명시적으로 선택한 환경에서만 그 외부 워크스페이스가 시각 실행을 대신합니다. 제품 폴더가 연결되어 있으면 시안을 따로 만들어 옮기는 대신 **실제 소스에서 직접** 작업하고, 연결이 없으면 지원되는 산출물 형식과 필요한 변환을 먼저 안내합니다(시안은 운영 코드와 자동 동기화되지 않습니다). -원격 환경에서는 접속 PC가 아니라 **실제 연결된 서버·프로젝트**에서 실행될 수 있습니다. - -### 04 · 마무리 — 완료 범위를 분명하게 - -> “승인한 범위의 구현·검증·작업 단위 로컬 커밋까지 완주하고, 바뀐 내용과 실제 검사·미확인 사항을 보고해줘. 이번에는 푸시·배포하지 마.” - -외부 반영까지 맡기려면 처음에 저장소·브랜치·배포 환경과 영향을 함께 승인하세요. 같은 범위의 승인은 반복 요청하지 않습니다. - -보고는 **지금 어느 수준으로 쓸 수 있는지 → 무엇이 막혔고 누가 풀 수 있는지(에이전트 작업/대표님 입력/결정) → 다음에 무엇을 할지** 순서로 받으면 판단이 쉽습니다. 이미 승인된 개발 범위에서는 "진행해달라"를 반복하지 않아도 다음 작업을 선언하고 계속합니다. 세션 종료 뒤의 자동 실행을 뜻하지는 않습니다. - -### 05 · 장기 개발 — 기록으로 재개 - -> "이 프로젝트의 승인된 목표를 이어서 진행해. 기존 제품 정본·작업 기록과 현재 Git을 대조하고, 완료된 일을 반복하지 말고 남은 범위의 구현·검증까지 맡아줘. 푸시·배포는 별도 승인 전에는 하지 마." - -여러 세션에 걸친 작업은 기존 제품 정본·작업 기록·체크포인트·Git으로 이어갑니다. 현재 KSI 환경에는 Goal 플러그인과 자동 계속 실행이 없습니다. 과거 `active` 기록은 보존된 이력이며, 재개·pause·정리를 위해 플러그인을 다시 설치하지 않습니다. 야간·상시 실행 기능을 뜻하지 않으며, 새 마일스톤·새 범위·외부 반영은 별도 승인입니다. - -## 바로 시작하기 - -### 1. 먼저 준비하세요 - -[OpenCode V2](https://opencode.ai/v2/docs/)와 **Node.js 20+**가 필요합니다. -모델 provider 연결과 로그인은 별도로 준비합니다. KSI는 도구나 인증 정보를 설치·제공하지 않습니다. - -### 2. 변경 내용을 미리 보고 적용하세요 - -아래는 **선택형 역할 번들을 설치하기로 한 환경**의 절차입니다. 이미 전환한 환경에 Developer를 다시 설치하지 않습니다. - -**공개 npm `0.5.0-beta.9`가 현재 두 역할(Developer · Reviewer) 번들입니다.** -이전 공개판 0.5.0-beta.8은 Test Runner를 포함한 이전 3역할 배포판이었으며, 현재 구성과 다릅니다. -제거된 옵션의 현재 동작도 이 과거 공개판에 소급 적용되지 않습니다. -첫 명령은 **미리보기만**, 두 번째 명령만 파일을 씁니다. 두 명령은 같은 beta.9를 고정합니다. -공개 설치에 npm 로그인은 필요 없습니다(배포자의 publish 인증은 별개 절차입니다). - -Linux/macOS — 대상 프로젝트 루트에서 실행하세요. `$PWD/.opencode`는 프로젝트 로컬 대상입니다. - -```sh -# 미리보기 — OpenCode 설정 파일을 변경하지 않습니다 -npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target "$PWD/.opencode" - -# 검토 후 적용 — 선택한 프로젝트에 두 역할을 추가합니다 -npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target "$PWD/.opencode" --apply -``` - -Windows PowerShell — 대상은 절대 경로의 `Join-Path` 결과입니다. - -```powershell -# 미리보기 — OpenCode 설정 파일을 변경하지 않습니다 -npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target (Join-Path (Get-Location) ".opencode") - -# 검토 후 적용 — 선택한 프로젝트에 두 역할을 추가합니다 -npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target (Join-Path (Get-Location) ".opencode") --apply -``` - -대상 경로는 절대 경로여야 합니다. 다른 프로젝트는 해당 프로젝트의 절대 경로로 바꾸세요. -전체 프로젝트가 아니라 서버 전체에 적용하려면 프로젝트 `.opencode`가 아니라 실제 사용하는 -**OpenCode 서버의 설정 디렉터리**(보통 `~/.config/opencode`, 관리형·외부 서버 환경에서는 다를 수 있음)를 -대상으로 하세요. 같은 파일은 반복 적용해도 변경하지 않으며, 다른 파일은 임의로 덮어쓰지 않습니다. -명시적인 교체에는 백업이 생성됩니다. [충돌·업그레이드 규칙 →](INSTALL.md) - -다른 PC에서는 Node.js 20+·OpenCode V2·모델 provider 연결을 각자 공식 절차로 먼저 준비하세요. -이 설치기는 그 어떤 것도 설치·제공하지 않습니다. Windows·macOS에서의 실제 실행 결과는 보장하지 않습니다 — -경로·브라우저·도구 차이는 각 환경에서 직접 확인하세요. - -
-검토한 소스 체크아웃에서 설치하려면 - -공개판 대신 검토한 소스로 설치할 때만 아래를 씁니다. 소스 루트에서 실행하며, -미리보기와 적용 모두 같은 검토된 소스 버전을 사용하세요. +이 저장소는 실행 프레임워크가 아닌 원칙·설정 예시·정적 데모입니다. 저장소 변경 확인: ```sh -# 미리보기 — OpenCode 설정 파일을 변경하지 않습니다 -node bin/ksi-opencode.mjs install --target "$PWD/.opencode" - -# 검토 후 적용 — 선택한 프로젝트에 두 역할을 추가합니다 -node bin/ksi-opencode.mjs install --target "$PWD/.opencode" --apply -``` - -
- -### 3. 에이전트에게 맡기기 (복사 → 붙여넣기) - -아래를 사용하는 OpenCode 세션에 그대로 붙여넣으면, 에이전트가 **미리보기 → 확인 → 적용 → 검증** 순서로 진행합니다. - -> “이 프로젝트에 KSI 하네스(ksi-opencode-harness)를 설치해줘. -> ① Node.js 20+·OpenCode V2가 준비됐는지 확인하고, 설치 대상(프로젝트 `.opencode` vs 서버 전역 설정 디렉터리)을 나에게 먼저 물어봐. -> ② 공개 npm 0.5.0-beta.9로 먼저 어떤 2개 파일이 쓰일지 미리보기만 보여줘: `npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.9 -- ksi-opencode install --target "<절대경로>"`. 이전 공개판 0.5.0-beta.8은 이전 3역할이므로 두 역할 설치용으로 대신 쓰지 마. 공개 설치에 npm 로그인은 필요 없어. -> ③ 내가 확인하면 `--apply`로 적용해줘. 충돌이 있으면 멈추고 `--replace` 여부를 다시 물어봐. -> ④ 적용 후 유효 에이전트 목록에서 developer·reviewer를 확인해줘 — 파일 존재만으로 판단하지 마. -> ⑤ 그 외에는 아무것도 바꾸지 마: 도구 설치·credentials·전역 지침(AGENTS.md)·서비스 재시작은 별도 승인 없이는 하지 마.” - -### 4. 실제 역할을 확인하세요 - -OpenCode의 유효 에이전트 목록에서 `developer`, `reviewer`를 확인하세요. -파일이 존재하는 것만으로 다른 설정 계층의 덮어쓰기까지 확인된 것은 아닙니다. - -전체 프로젝트에 적용하려면 실제 사용하는 **OpenCode 서버의 설정 디렉터리**를 선택하세요. -보통 `~/.config/opencode`이지만, 관리형·외부 서버 환경에서는 다를 수 있습니다. - -## 설치되는 것과 별도로 준비할 것 - -**공개 beta.9의 기본 적용 범위는 아래 두 파일입니다.** 이전 공개판 0.5.0-beta.8의 3역할 구성과 -구분하세요. npm 패키지를 내려받는 것만으로 OpenCode 설정은 바뀌지 않습니다. - -```text -선택한 설정 디렉터리/ -└── agents/ - ├── developer.md - └── reviewer.md +npm run check ``` -**이 설치기는 에이전트 2개만 설치합니다.** 네이티브 역할·OpenCode·Node·provider·모델·credentials·Design Specialist·CE·브라우저·전역·프로젝트 지침(`AGENTS.md`)·`opencode.jsonc`·MCP 연결은 설치되지 않습니다 — 기존에 설치된 Test Runner 등 다른 역할 파일은 남으며, 새 설치가 자동 삭제하지 않습니다. 지침은 [예시](examples/autonomous-development.md)를 검토해 병합하고, 나머지는 각자 준비합니다. - -| 구성 요소 | 설치·관리 주체 | 언제 필요한가요? | -| --- | --- | --- | -| OpenCode V2 · Node.js · 모델 연결 | 사용자, 각 도구의 공식 절차 | 기본 작업 환경 | -| KSI 두 역할 | 이 설치 도구, 검토 후 `--apply` | 구현·검토를 나눠 맡길 때 | -| Design Specialist | OpenCode 기본(전역 운영 기준) — 패키지가 설치하지 않음 | 시각적 프론트엔드 작업의 기본 실행 | -| Compound Engineering (CE) | 선택적 별도 준비, 사용자 승인 | 네이티브 실행에 특정 전문 스킬이 도움이 될 때; 전체 흐름이나 프로젝트별 설치는 기본이 아닙니다 | -| Playwright CLI · agent-browser · 공식 브라우저 스킬 | 실행 서버/사용자 환경에 별도 준비, 사용자 승인 | 실제 브라우저 조작·검증이 필요할 때; 선택한 도구 하나를 사용하며 패키지가 자동 설치하지 않습니다. [준비·검증 범위 →](docs/execution.md#prepared-global-browser-environment) | -| OpenDesign(로컬 실행) · 디자인 MCP 연결 | 별도 명시적 선택 시 별도 준비, 사용자 승인 | 외부 워크스페이스에 시각 실행을 맡기기로 명시적으로 선택했을 때 — 기본·유일 경로가 아님 | -| 운영 지침(`AGENTS.md`) · 연속성 기록 | 사용자 + 에이전트, 설치기가 쓰지 않음 | 장기 작업의 맥락과 의사결정을 이어갈 때 | - -### 이 하네스 전체 구성을 갖추기 (에이전트 주도) - -현재 운영은 **OpenCode 네이티브 역할 + 기본 Design Specialist + 운영 지침 + 선택 도구들**입니다. 이 저장소의 Developer · Reviewer 설치 번들은 별도 선택 사항이며, 이미 전환한 환경의 필수 구성은 아닙니다. OpenCode 설치와 모델 선택은 각자 몫이고 **특정 모델을 강제하지 않습니다.** 아래를 세션에 붙여넣으면 에이전트가 승인된 운영에 필요한 구성만 확인합니다. - -> “내 환경에 이 저장소의 KSI 하네스 구성을 갖춰줘. OpenCode 설치와 모델·provider 선택은 내 것이니 바꾸지 말고 강제하지도 마. -> ① 먼저 읽기 전용으로 현재 상태를 점검해줘: OpenCode V2·Node 20+, 설치된 에이전트, 기존 설정·도구. -> ② 네이티브 Plan·Build·General·Explore와 기본 Design Specialist의 유효 구성을 확인해. 이미 전환한 환경의 Developer·Test Runner·Reviewer·Superpowers·Goal을 복원하지 마. 별도 역할 번들을 내가 요청한 경우에만 공개 npm 0.5.0-beta.9의 미리보기 → 내 확인 → `--apply`로 진행해. 이전 공개판 0.5.0-beta.8은 이전 3역할이므로 현재 운영을 대신하지 않아. -> ③ 운영 지침: `examples/autonomous-development.md`를 읽고 내 `AGENTS.md`에 병합할 변경만 제안 → 승인 후 적용. -> ④ 선택 도구 — 필요한 CE 스킬, 브라우저 도구(Playwright CLI 또는 agent-browser 중 하나), OpenDesign+디자인 MCP(외부 워크스페이스에 맡기기로 명시적으로 선택한 경우에만): 먼저 이미 준비된 것을 확인해. 부족한 기능이 있을 때만 공식 문서로 제안하고, 새 설치·credentials·서비스 변경은 내 승인 후 진행해. 프로젝트별 ce-setup이나 중복 문서 생성은 하지 마. -> ⑤ 마무리: 구성 요소별 설치·확인 상태와 건너뛴 항목·이유를 표로 보고해줘.” - -| 구성 요소 | 필수 | 설치 주체 | 확인 | -| --- | --- | --- | --- | -| OpenCode V2 · Node 20+ | 필수 | 사용자 (공식 절차) | `opencode --version` | -| 네이티브 역할 + 기본 Design Specialist | 기본 | OpenCode | 유효 에이전트 목록; 독립 리뷰는 CE 코드 리뷰(ce-code-review) | -| 운영 지침(`AGENTS.md`) | 권장 | 사용자 + 에이전트 (검토 병합) | 새 세션에서 지침 로드 | -| CE | 선택 | 사용자 (공식 저장소) | 필요한 스킬의 실제 로드; 전체 흐름 강제 없음 | -| 브라우저 도구(택 1) | UI 검증 시 | 사용자 | 실제 페이지 스냅샷 1회 | -| OpenDesign + 디자인 MCP | 별도 명시적 선택 시 | 사용자 (별도 앱) | 프로젝트 읽기 1회 | - -이 저장소에서만 쓰는 선택 부품(예: OpenDesign 완료 알림 플러그인)은 npm 패키지에 포함되지 않습니다 — 체크아웃에서 자체 스크립트로 다룹니다. - -### 디자인과 제품 소스 +현재 문서 링크·최소 설정·비공개 패키지 경계만 검사합니다. 별도 KSI 검사 래퍼·설치기는 없습니다. +이 결과는 실제 제품 검증이 아닙니다. -시각 실행의 기본 담당은 **Design Specialist**입니다(전역 운영 기준). **OpenDesign(로컬 실행)**은 -별도로 명시적으로 선택한 환경에서만 쓰는 외부 워크스페이스 통합이며, 기본·유일 경로가 아닙니다. -OpenDesign을 부를 때 사용자에게는 **OpenDesign(로컬 실행)**이라고 합니다 — 'Local Codex'는 OpenDesign 제품 표기이며 별도 도구·에이전트가 아닙니다(에이전트가 별도 프로그램처럼 소개하면 잘못된 표현입니다). 실행 에이전트·모델은 실행마다 선택할 수 있습니다. **OpenDesign Cloud**는 명시적으로 요청한 경우에만 검토합니다. -둘 모두 이 npm 패키지가 설치하거나 로그인·연결해 주는 기능이 아닙니다. +## 현장 안전점검 UI 데모 -**OpenCode 작업 계약 → 디자인 분석·실제 UI 소스 수정 → 디자인 렌더 리뷰·개선 → OpenCode 기능 검증·Git → 보고** 순서로 사용합니다. 디자인 실행의 기본 담당은 Design Specialist이며, OpenDesign을 별도로 명시적으로 선택한 환경에서만 그 외부 워크스페이스가 대신합니다. 어느 쪽도 OpenCode 내부에 새 디자인 에이전트를 설치하는 것은 아닙니다. -제품 폴더가 연결되어 있으면 시안 복사본 없이 그 소스에서 직접 작업하고, 연결이 없을 때만 지원되는 산출물 형식으로 진행합니다. 새 시각 방향처럼 방향 승인이 필요한 경우에만 미리보기로 먼저 확인합니다. 자동 시나리오가 소스 전용 실행을 `blocked`로 표시해도 실패가 아닙니다 — 실제 diff·렌더로 다음 단계를 이어가고, 필요하면 명시적 시나리오 플러그인을 사용합니다. +현장 담당자가 브리핑 전에 조치 대상을 찾는 **정적 화면 시안**입니다. 가상 데이터이며 +운영·납품 실적이나 연결된 제품이 아닙니다. 실제 조치·저장·로그인·API 연결은 없습니다. -시각적 프론트엔드 작업은 **분석·설계·UI 코드·스타일·반응형·상호작용 표현·렌더 리뷰·개선까지 디자인 쪽이 맡습니다.** 작은 CSS 수정도 같은 원칙이며, 작업 크기에 맞게 간결한 개선 실행을 사용합니다. OpenCode는 실제 소스·상태·레퍼런스·기능 계약을 제공하고, 디자인 리뷰의 결함→수정 버전→렌더 증거와 실제 제품 동작을 확인합니다. UI를 자체 취향으로 다시 고치지 않습니다. -기능·데이터·권한·브랜드 보존은 기존 레이아웃을 고정한다는 뜻이 아닙니다. 승인된 개선 목적 안에서 화면 구조·계층·컴포넌트는 재설계할 수 있습니다. 과감함은 큰 숫자나 강한 색이 아니라 사용자 업무의 실제 문제를 해소하는 변화로 판단합니다. -수용 기준은 관찰 가능하게 씁니다(첫 화면에 보여야 할 것·주요 행동·모바일 스크롤 깊이·실패 정의). 실행 전 레퍼런스·자산·소스 입력 접근을 확인하고, 읽을 수 없으면 실행하지 않고 차단 보고합니다. -기존 화면 재설계나 막연한 불만에는 디자인 측이 실제 화면에서 문제를 발견·우선순위화해 재설계의 문제 정의로 삼습니다(읽기 전용 감사는 지원되는 방법 중 하나이며, 문제를 일일이 열거하지 않아도 됩니다). 위임된 재설계의 시각 방향은 디자인 측이 결정해 바로 진행하고, **방향 옵션 2~3개**는 사용자가 요청했거나 브랜드 정체성 자체가 쟁점일 때만 만듭니다(승인된 방향이면 생략). -큰 작업의 **리뷰는 필수 단계**입니다. 생성과 다른 리뷰 스킬을 사용하고, 실제 렌더 상태(대상 뷰포트·빈/오류/로딩·키보드/포커스·접근성·모션)를 입력으로 전달하며 리뷰가 그 입력을 실제로 읽었는지 확인합니다. 리뷰 결과는 좋다/나쁘다가 아니라 **결함 목록**(무엇이/어느 화면·상태/사용자 영향/원하는 결과)으로 받아 해결/미해결을 라운드별로 추적하고, 단계마다 선택한 스킬을 기록합니다. -제품별 `product-context.md`를 OD 프로젝트에 두면 매 세션 브리핑을 반복하지 않습니다. 결과는 제품 링크(로컬이면 tailnet·임시 표기)·전/후 스크린샷·디자인 프리뷰 링크의 묶음으로 받습니다. -라운드 상한은 두지 않되, 한 라운드에서 측정 가능한 개선이 없으면 **품질 미달과 남은 결함**을 보고합니다. 산출물 생성·디자인 리뷰 증거·기능 검증·사용자 수용은 구분하며, 테스트 통과나 실행 성공만으로 디자인 완료를 주장하지 않습니다. 백엔드·API·권한·저장 로직은 Build 담당입니다. +![현장 안전점검 UI 시연 화면 — 가상 데이터](docs/assets/demo/safety-field-inspection.png) -합의한 요구사항을 브리프에 재사용해 같은 질문을 반복하지 않습니다. 실제 UI 소스는 디자인 실행이, 업무 규칙·API·데이터·인증과 기능 검증·Git은 Build가 맡습니다. 같은 파일에 둘 다 작업해야 하면 순차 인계합니다. 설치된 UI 참고 스킬이 있다면 보조 자료일 뿐 별도 디자인 생성 경로가 아닙니다. 디자인 접근·렌더가 불가능하면 부족한 항목을 알리고 독립적인 비시각 작업만 계속합니다. +[라이브 데모](https://khakisketch.github.io/KSI-Opencode-Harness/demo-safety/) · +[화면 소스](docs/demo-safety/index.html) -새 화면·큰 재설계·미해결 UI 패턴에는 OpenCode가 필요할 때 **Mobbin 사례를 조사**하고, 실제로 본 패턴·원본 링크·접근 가능한 이미지를 같은 브리프로 전달합니다. 승인된 화면 구현이나 작은 수정에는 검색을 반복하지 않습니다. OpenCode에 연결된 MCP가 OpenDesign 실행 내부에도 자동 연결된다고 가정하지 않습니다. +## 레거시와 기록 -작업 기록에는 **대상 화면·변경 범위·상태/반응형 규칙·기존 컴포넌트·미해결 결정**을 작게 남깁니다. 실행 성공이나 바뀔 수 있는 미리보기 링크만으로 승인된 버전이라고 판단하지 않습니다. 시안을 제품 코드에 통째로 덮어쓰거나 두 작성자가 같은 제품 파일을 동시에 수정하지 않습니다. +과거 역할 설치기, 운영 매뉴얼, 계획/상태 정본, 외부 디자인 통합과 전용 스크립트는 현재 +트리에서 제거했습니다. 과거 내용은 Git 기록으로 확인하며 운영 경로로 다시 적용하지 않습니다. +미커밋 원본은 제거 전에 저장소 밖에 백업했습니다. -사용자에게는 **방향 확인용 미리보기**와 **실제 제품 확인**을 구분해 제공합니다. 미리보기는 방향/프로토타입 확인용, 제품 개발 서버는 실제 로그인·API·저장·권한·라우팅 검증용입니다. 개발 서버를 디자인 공간으로 옮기지 않고, 구현 캡처·차이·제약을 디자인 검토에 피드백합니다. 자동 역호출·재귀 생성·별도 대형 오케스트레이터는 추가하지 않습니다. [레퍼런스·인계·검증 절차 →](docs/integrations/opendesign.md#reference-research-and-brief-transfer) +현재 원본은 설치기 배포본이 아니며 `package.json`의 `private: true`로 발행을 막았습니다. +기존 발행 버전·사용자 설치 파일은 변경하지 않았습니다. 이전 CLI가 필요한 기존 사용자는 +이미 사용하던 정확한 배포 버전 또는 해당 Git 리비전을 사용해야 하며, 현재 원본에는 CLI가 없습니다. +과거 설치기로 현재 사용자 설정·Design Specialist를 덮어쓰지 마세요. -연결 확인은 도구 목록 확인에서 끝내지 않고 **대상 프로젝트의 실제 읽기**까지 확인합니다. -유효한 같은 대상 확인은 재사용하되, 대상 변경·오류·재시작·활성 컨텍스트 만료 시 다시 확인합니다. -[디자인 연결·인계·복구 가이드 →](docs/integrations/opendesign.md) +작업 진행은 Native 세션·Git·실제 검사 결과로 확인합니다. 제품 요구사항·승인 결정은 해당 +제품의 기존 문서에 유지하고, 지속 기록을 요청받았거나 실제로 필요할 때만 갱신합니다. +KSI 전용 원장·상태·체크포인트나 고정 계획 경로는 요구하지 않습니다. Native Plan 저장물을 +프로젝트 문서로 매번 복제할 필요도 없습니다. 과거 기록은 Git 이력에 보존돼 있습니다. -> 저장소의 [환경별 배포 자료](https://github.com/khakisketch/KSI-Opencode-Harness/tree/main/integrations/opendesign)는 -> npm에 포함되거나 자동 적용되지 않습니다. 모든 환경에 그대로 적용하는 설치법도 아닙니다. +## 만드는 곳 -## 자동으로 하지 않는 일 +**주식회사 케이에스아이** · 043-288-4860 -- OpenCode, 디자인 도구, CE 등 스킬·플러그인, Docker, 모델 CLI 설치 -- `opencode.jsonc`, `AGENTS.md`, provider, 모델 선택, MCP, credentials 변경 -- 서비스 재시작, 기존 설치 삭제, 승인되지 않은 푸시·공개·배포 -- 자율 개발 지침의 자동 적용, 명시적 제약을 무시한 커밋, 관련 없는 과업 확장 -- 강제 팀 구성, 자동 디자인 동기화, 사용자 승인 대행 +AI 개발 도구와 현장 업무 화면을 실제로 사용할 수 있는 기능과 인터페이스로 구체화합니다. +도입·커스터마이징 문의는 전화로 연락 주세요. -런타임 훅, 자동 checkpoint 주입, `ksi_*` 근거 도구, 출력 보관소, `/complete`·`/review` 명령도 설치하지 않습니다. -프로젝트 규칙과 별도 스킬로 작업을 지원할 수 있지만 패키지 내장 기능으로 설명하지 않습니다. - -
-이전 KSI 버전에서 이동한다면 - -0.5.0부터 Design primary와 `--with-design-kit`은 제거되었습니다. 예전 역할·스킬·플러그인 등록은 -새 설치가 자동 삭제하지 않습니다. 먼저 소유 경로와 실제 설정을 확인하고 백업 후 별도로 정리하세요. -기존 모델 설정, 다른 도구의 독립 설치, 플러그인과 credentials는 보존해야 합니다. 별도 승인한 전역 전환과 일반 패키지 설치를 혼동해 제거된 Superpowers·Goal을 복원하지 않습니다. [전체 이전 절차 →](INSTALL.md) - -
- -
-이 저장소를 읽는 에이전트에게 - -1. README와 INSTALL, 프로젝트 지침, 실제 서버·설정 위치를 확인하세요. -2. 이미 준비된 도구를 확인하고, 부족한 도구는 최신 공식 문서로 조사하세요. -3. 시스템 설치·공유 설정·credentials 접근이나 공유·서비스 변경·삭제는 별도 승인을 받으세요. -4. 승인된 대상에서 미리보기 후 적용하고, 사용자 모델·권한 선택을 보존하세요. -5. 별도 스킬·디자인 연결은 필요하고 승인된 경우만 준비하세요. 문서에 등장한다고 설치된 것은 아닙니다. -6. 역할의 실제 적용과 디자인 프로젝트 읽기를 확인하세요. 설정됨·연결됨·검증됨을 구분해 보고하세요. - -선택적 CE 스킬은 [공식 저장소](https://github.com/EveryInc/compound-engineering-plugin)를 참고하세요. 문서에 있다는 이유로 설치하거나 전체 흐름을 강제하지 않습니다. - -
- -## 더 알아보기 - -| 목적 | 문서 | -| --- | --- | -| 설치·충돌·업그레이드 | [INSTALL](INSTALL.md) | -| 개발 흐름(그림·실제 화면) | [Walkthrough](docs/walkthrough.md) | -| 역할과 설치 구조 | [Architecture](docs/architecture.md) | -| 작업·위임·완료 보고 | [Execution](docs/execution.md) | -| 디자인 연결과 제품 구현 인계 | [Integration](docs/integrations/opendesign.md) | -| 선택형 프로젝트 지침 | [AGENTS.md 예시](examples/project-AGENTS.md) | -| 문제 해결 | [Troubleshooting](docs/troubleshooting.md) | -| 검증 범위와 한계 | [Verification](docs/verification.md) | - -### 개발·검증 - -```sh -npm run check -npm run check:package -``` +## 라이선스 -패키지 검사는 격리된 환경에서 실제 tarball의 미리보기, 두 파일 적용, 반복 적용, 제거된 옵션(`--with-design-kit`·`--developer-test-runner`)과 -사용자 설정 덮어쓰기 거부를 확인합니다. 패키지 검사 통과는 실제 제품 디자인 승인이나 모든 외부 연동의 성공을 뜻하지 않습니다. +[MIT](LICENSE) — Copyright (c) 2026 KSI diff --git a/bin/ksi-opencode.mjs b/bin/ksi-opencode.mjs deleted file mode 100644 index bca7e70..0000000 --- a/bin/ksi-opencode.mjs +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env node -import { copyFile, lstat, mkdir, readFile, writeFile } from "node:fs/promises" -import { randomUUID } from "node:crypto" -import { isAbsolute, join } from "node:path" -import { buildNativeBundle } from "../src/native-bundle.mjs" - -const usage = "Usage: ksi-opencode install --target [--apply] [--replace]" -const designKitRemoved = "The --with-design-kit option was removed in 0.5.0. The vendored design kit is no longer distributed; use the OpenDesign workspace for design work (see docs/integrations/opendesign.md)." -const testRunnerRemoved = "The --developer-test-runner option was removed: Test Runner is no longer part of the default bundle. Developer owns the tests for its own implementation and Reviewer independently reviews code and test adequacy; an existing installed Test Runner role file is left untouched (see INSTALL.md)." - -function options(args) { - if (args[0] !== "install") throw new Error(usage) - const result = { target: null, apply: false, replace: false } - for (let index = 1; index < args.length; index++) { - const arg = args[index] - if (arg === "--target" && !result.target && args[index + 1]) result.target = args[++index] - else if (arg === "--apply") result.apply = true - else if (arg === "--replace") result.replace = true - else if (arg === "--developer-test-runner") throw new Error(testRunnerRemoved) - else if (arg === "--with-design-kit") throw new Error(designKitRemoved) - else throw new Error(usage) - } - if (!result.target || !isAbsolute(result.target)) throw new Error(usage) - return result -} - -async function kind(path) { - try { - const stat = await lstat(path) - if (stat.isSymbolicLink()) throw new Error(`Refusing symlink: ${path}`) - return stat.isDirectory() ? "directory" : stat.isFile() ? "file" : "other" - } catch (error) { - if (error?.code === "ENOENT") return "absent" - throw error - } -} - -async function inspect(target, bundle) { - const rootKind = await kind(target) - if (rootKind !== "absent" && rootKind !== "directory") throw new Error(`Target must be a directory: ${target}`) - const directories = new Set() - for (const path of bundle.keys()) { - const parts = path.split("/") - for (let index = 1; index < parts.length; index++) directories.add(parts.slice(0, index).join("/")) - } - for (const dir of directories) { - const status = await kind(join(target, dir)) - if (status !== "absent" && status !== "directory") throw new Error(`Managed path must be a directory: ${join(target, dir)}`) - } - const changes = [] - for (const [relativePath, after] of bundle) { - const path = join(target, relativePath) - const status = await kind(path) - if (status !== "absent" && status !== "file") throw new Error(`Managed path must be a file: ${path}`) - const before = status === "file" ? await readFile(path, "utf8") : null - if (before === after) continue - changes.push({ path: relativePath, status: before === null ? "create" : "conflict", before, after }) - } - return changes -} - -function hasUserRouting(frontmatterFile) { - const frontmatter = frontmatterFile.match(/^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1] - // Conservative: YAML permits indentation, quoted keys, and flow mappings. - // A false-positive refusal is safer than dropping user routing on replace. - return frontmatter !== undefined && /\b(?:model|variant|steps)\b["']?[ \t]*:/.test(frontmatter) -} - -async function apply(target, changes) { - await mkdir(target, { recursive: true }) - const directories = new Set() - for (const { path } of changes) { - const parts = path.split("/") - for (let index = 1; index < parts.length; index++) directories.add(parts.slice(0, index).join("/")) - } - for (const directory of directories) { - await mkdir(join(target, directory), { recursive: true }) - } - for (const change of changes) { - const path = join(target, change.path) - if (change.status === "create") { - await writeFile(path, change.after, { flag: "wx" }) - continue - } - if (await kind(path) !== "file" || await readFile(path, "utf8") !== change.before) { - throw new Error(`Conflict changed during install: ${path}`) - } - const backup = `${path}.bak-${Date.now()}-${randomUUID().slice(0, 8)}` - await copyFile(path, backup, 1) - await writeFile(path, change.after) - change.backup = backup - } -} - -async function main() { - const parsed = options(process.argv.slice(2)) - const bundle = await buildNativeBundle() - const changes = await inspect(parsed.target, bundle) - if (parsed.apply && !parsed.replace && changes.some((change) => change.status === "conflict")) { - throw new Error("Existing native file conflict; review the preview, then pass --apply --replace to back up and replace it.") - } - if (parsed.apply && parsed.replace && changes.some((change) => change.status === "conflict" && change.path.startsWith("agents/") && hasUserRouting(change.before))) { - throw new Error("Existing native role has model, variant, or steps frontmatter; migrate those user-owned fields manually before replacement.") - } - if (parsed.apply && changes.length) await apply(parsed.target, changes) - process.stdout.write(`${JSON.stringify({ target: parsed.target, applied: parsed.apply, changes }, null, 2)}\n`) -} - -main().catch((error) => { - console.error(error instanceof Error ? error.message : String(error)) - process.exitCode = 1 -}) diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index f02b8aa..0000000 --- a/docs/architecture.md +++ /dev/null @@ -1,63 +0,0 @@ -# Native OpenCode V2 architecture - -The published KSI package has two parts: two custom role templates and a standalone installer. The repository also carries optional, not-installed records and helpers — the design-notifier plugin, the OpenDesign deployment assets under `integrations/`, and the read-only `scripts/` diagnostics. The installer renders ordinary V2 `agents/*.md` files into a selected configuration directory. OpenCode reads those files natively; there is no distributed KSI `setup(ctx)`, `agent.transform`, or KSI entry in `plugins`. [OpenCode agents](https://opencode.ai/v2/docs/agents) and [configuration](https://opencode.ai/v2/docs/config) define the runtime semantics. - -OpenCode's built-in Build and Plan are the Primaries; native General and Explore are Subagents. Build/Plan/General/Explore retain their built-in system prompts. The optional installer adds Developer and Reviewer, but those package contents are not the active global configuration. The KSI host's approved 2026-10-09 operation uses General for implementation/self-tests, the default Design Specialist for visual frontend ownership (design/frontend/render), and CE skills (review runs through ce-code-review); Developer/Test Runner/Reviewer are no longer present in the global configuration, and Superpowers/Goal are removed. Other servers need their own effective-config verification. Build owns overall execution/integration and may apply simple mechanical UI transfers directly; it may work directly or delegate as appropriate. API/data/auth contracts stay unchanged. General and the default Developer cannot delegate. There is no KSI design primary role. - -CE is a collection of procedures loaded by an executing agent, not another -agent or a delegation target. Build selects useful methods; no full CE pipeline, -user-selected A/B/C mode or no-mixing rule is mandatory. Skills neither grant -permissions nor replace Build's integration/completion responsibility. The installer bundle -remains Developer + Reviewer (public npm 0.5.0-beta.9), and previous public release -0.5.0-beta.8 was the historical three-role release; documenting current operation -does not change either bundle. - -Plan is optional, not a required entry stage: it helps understand the project and prior decisions and define direction, scope, invariants and completion evidence. Clear tasks can start directly in Build. Build owns execution, integration, technical-completion evidence and reporting, not human acceptance or unapproved external delivery. Its relationship to the connected design workspace is a responsibility boundary, not a claim of superior visual expertise: Build supplies purpose, desired outcomes and functional/API/data/auth/brand invariants; design chooses and implements layout, hierarchy, composition and responsive presentation within them. - -Plan/Build/installed planning skills use one approved workstream plan, not competing -authorities. Latest user decisions define the goal; the approved plan defines scoped -execution; Git/code/checks define implementation state. Plan prepares/reassesses -proposals; Build owns execution progress and evidence. If Plan can only write native -`~/.opencode/plan/` documents, Build records the actual handed-off source in the -canonical project ledger while preserving its content and approval state. The native -source does not remain a second evolving execution plan. This [handoff contract](execution.md#one-approved-plan-across-plan-and-build) -does not replace native prompts, expand permissions, auto-import files or enforce compliance. - -Within an authorized outcome, Build owns discovery, prioritization and the -implementation → actual-result review → repair → affected verification loop, not -just an improvement list handed back to the user. The visual owner — the default -Design Specialist, or the explicitly selected external workspace where chosen — -remains the visual writer/reviewer, and independent verification — CE review and -the integration checks the revision needs, or an explicitly configured -verification role — remains distinct from author self-review. This [completion default](execution.md#own-the-completion-loop-not-a-list-for-the-user) -does not turn audit/Plan-only requests into product-write authority, add a runtime -or automatically resume paused work/goals; material scope/security/delivery gates stay. - -Native-first: keep tool behavior, provider/model prompting and native mode boundaries upstream; put KSI operating policy and project decision criteria in the appropriate `AGENTS.md`. A non-empty agent `system` (including a Markdown agent body) replaces the provider base prompt, while project instructions remain additional sources. An AGENTS.md supplement can still alter behavior, and OpenCode does not resolve conflicts automatically. Diagnose repeated failures against actual goals, state, loaded guidance, permissions and model/step constraints before considering a separately authorized prompt-replacement experiment. Compare the same tasks under matched conditions and preserve rollback; the installer must not turn an unverified experiment into a default built-in override. - -Design is a separate responsibility, not a new native OpenCode role. By default the -Design Specialist owns all visual frontend analysis, UI source/styles, -responsive/interaction presentation, rendered design review and refinement, including -small visual fixes. [OpenDesign](integrations/opendesign.md) is an optional, separately -and explicitly selected external-workspace integration that takes that seat only where -chosen — never the sole, default, or mandatory design path, and its absence never blocks -visual work on the default path. Build owns contracts, coordination, nonvisual -business/API/data/auth work, functional integration/verification and Git. Mechanical artifact transfer is not permission to redesign. This installer distributes no design skills, craft references or design-system package; project brand and authorized outcome remain authoritative. No orchestrator role, KSI preview runtime, mandatory design stage for nonvisual work or model override is added. - -The workflow is asymmetric: OpenCode supplies a compact task contract; the design workspace analyzes, edits actual UI sources, renders, reviews and refines; Build checks review evidence and real product functionality, then reports. Presentation defects return to design; nonvisual functional defects stay engineering-owned. No automatic engineering-session callbacks, recursive generation or simultaneous same-file writers are added. Keep design review evidence separate from functional/product-server evidence; this is the existing MCP/source-or-artifact handoff, not enforced quality scoring. - -Route by work nature, not size: a small spacing/overflow repair stays design-owned; a large DB migration stays engineering-owned. Size determines how much process and verification the work needs. Under the adopted operating policy, General owns the tests for its own implementation, the CE review skill independently reviews code and test adequacy when warranted and may run safe read-only checks, and Build verifies the integrated revision and unverified areas without repeating already-valid checks. Where a milestone requires independent execution verification beyond that, an explicitly configured verification role or authorized CI provides it. That review does not provide design approval. Recheck affected behaviour after later edits. Design success is not whole-product completion: Build resolves integration/functional defects, returns visual defects to design, and distinguishes technical evidence, human acceptance and external delivery. This contract is not an enforced runtime workflow and the installer does not automatically apply it. - -For existing frontends, use the [product-aware design contract](integrations/opendesign.md#product-aware-frontend-improvement): actual relevant source/tokens and integration constraints feed supported UI output, which Build integrates faithfully and compares with the real product. The design workspace is not limited conceptually to unrelated mockups, but framework/source-edit capability is verified per target, not inferred from HTML support. Verified source-direct work in the actual product repository is the preferred path (one writer at a time; worktree optional), with separate output plus Build integration remaining the fallback when direct access is unavailable; guidance adoption alone still does not enable new mounts or security changes, which keep their own scoped authorization. - -Each installed custom role owns its description, mode, system prompt, and targeted native permission rules, not an exhaustive allowlist. The bundle has no skill whitelist and omits model/variant/steps so existing user settings remain in their native configuration layer. The package installs no native roles or prompts, OpenCode, Node.js, providers, models, credentials, the Design Specialist, CE, browser tooling, global policy, or MCP connections. (On the KSI host only, the user explicitly requested OpenAI gpt-6-luna-fast for the Design Specialist with the max variant preserved; this preference is host-local and is never forced on other machines.) The installer does not parse or rewrite JSONC and refuses differing file collisions without explicit replacement and backup. A collision in a *different* config layer still requires effective-config inspection; file checks alone do not settle OpenCode precedence. OpenCode's last matching permission rule wins, and its permissions are not an OS sandbox. - -Reviewer denies native `edit` and `subagent`; other actions inherit effective defaults/configuration. Under V2's base allow, shell, MCP mutations and Code Mode can remain available. Code Mode's nested tools still enforce their own permissions, but an `edit` denial does not govern every other mutation action. Read-only review is a behavioral contract: the reviewer may not use another tool to modify implementation/configuration/services/external data, and runs only safe read-only checks; artifact-producing checks belong to Build/CI or an explicitly configured verification role. Verify effective rules per target; stricter shell/MCP policies or OS isolation require separate design/adoption, not automatic installer changes. See [current V2 permissions](https://opencode.ai/v2/docs/permissions). - -No slash command is installed. The former six `ksi_*` evidence tools, output archive, runtime delegation/signature checks, helper lifecycle guard, and continuity hooks are not part of the installer distribution. Agents can use normal authorized OpenCode tools and optional shared `AGENTS.md` guidance, but this is guidance, not the same enforcement or telemetry. The approved product milestone still needs Human end-to-end acceptance; package tests alone do not establish it. - -The optional [autonomous development policy](../examples/autonomous-development.md) broadens delegated local execution through verified task-owned commits, not product scope or native permissions. It separates technical completion, user product acceptance and external delivery; already-approved actions do not need repeated approval. The installer ships this reference but does not merge it into `AGENTS.md`. Current KSI operation has no Goal plugin or automatic goal continuation. Long-term state lives in the existing product records and Git; preserved goal data does not execute. A separately authorized continuation capability in another environment is not an installer feature. - -The installer never removes a previously installed role. An earlier global installation may still contain `agents/design.md`, the retired design skills, or a Test Runner file from earlier bundles; retiring them is a separate, reviewed migration step in the target configuration directory, not a side effect of this package. See [installation and migration](../INSTALL.md). - -OpenChamber is a client: managed mode uses its managed OpenCode server's configuration, external-server mode uses the external server's configuration. OpenChamber's orchestration tool is managed-only. Neither UI mode has been accepted from the local package checks. diff --git a/docs/assets/demo/safety-field-inspection.png b/docs/assets/demo/safety-field-inspection.png new file mode 100644 index 0000000..856f6bb Binary files /dev/null and b/docs/assets/demo/safety-field-inspection.png differ diff --git a/docs/assets/walkthrough/00-flow.png b/docs/assets/walkthrough/00-flow.png deleted file mode 100644 index 6e8aafe..0000000 Binary files a/docs/assets/walkthrough/00-flow.png and /dev/null differ diff --git a/docs/assets/walkthrough/00-flow.svg b/docs/assets/walkthrough/00-flow.svg deleted file mode 100644 index b3f2105..0000000 --- a/docs/assets/walkthrough/00-flow.svg +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - 대표님 (사용자) - 요청 · 제약 · 완료 기준 · 최종 수용 - - - - 요청 - - - - OpenCode (Build) - 작업 계약 · 조율 · 기능 검증 · Git · 보고 - - - - 작업 계약 (MCP) - - - - 기능 검증 - - - - OpenDesign (로컬 실행) - 분석 · 감사 → 실제 UI 소스 수정 - → 렌더 리뷰(필수) → 결함 목록 → 개선 반복 - 스킬 선택 · 실제 렌더 확인 · 라운드 상한 없음 - - - - 실제 제품 (소스) - 빌드 · 라우팅 · 저장 · 권한 - 기능 검증은 실제 제품에서 - 스크린샷이 아니라 동작으로 확인 - - - - 실제 UI 소스 수정이 제품으로 - - - - 리뷰 묶음 보고 - 제품 링크 · 전/후 스크린샷 - · 디자인 프리뷰 - diff --git a/docs/assets/walkthrough/01-opendesign-workspace.png b/docs/assets/walkthrough/01-opendesign-workspace.png deleted file mode 100644 index aae58ff..0000000 Binary files a/docs/assets/walkthrough/01-opendesign-workspace.png and /dev/null differ diff --git a/docs/assets/walkthrough/02-product-dashboard.png b/docs/assets/walkthrough/02-product-dashboard.png deleted file mode 100644 index d8ae60f..0000000 Binary files a/docs/assets/walkthrough/02-product-dashboard.png and /dev/null differ diff --git a/docs/demo-safety/index.html b/docs/demo-safety/index.html new file mode 100644 index 0000000..8bcbdd3 --- /dev/null +++ b/docs/demo-safety/index.html @@ -0,0 +1,664 @@ + + + + + + +KSI 안전점검 — 오늘의 현장 (시연용 데모) + + + +
+ + +
+ 시연용 데모 화면입니다 · 가상 데이터 · 실제 현장 정보가 아닙니다 · 주식회사 케이에스아이 043-288-4860 + GitHub에서 보기 +
+ + +
+
+ KSI 안전점검 + + 오늘의 현장 +
+
+ 2026년 10월 13일 화요일 — 오전 07:42 +
안
+
+
+ + +
+ + + + + 2곳이 오전 9시 브리핑 전에 조치가 필요합니다 — 한강교량 4번 교각은 차단 상태이고 서초타워 12층은 검토 대기입니다. + +
+ + +
+ 오늘의 점검 + 총 6건 · 07:42 동기화 (가상 데이터) +
+ + +
+
+ 현장 + 점검 유형 + 상태 + 점검자 + 시간 + 조치 +
+ + +
+ + +
+
+
한강교량 — 4번 교각
+
토목 · 구조물 건전성 점검
+
+
구조 점검
+
+ + + 차단 + +
+
점검자 A
+
오전 06:15
+
+
+ + +
+
+
서초타워 — 12층
+
추락방호 · 안전대·앵커 점검
+
+
안전 순찰
+
+ + + 검토 필요 + +
+
점검자 B
+
오전 07:05
+
+
+ + +
+
+
강남역 — 3번 출구
+
전기 · 가설전선 점검
+
+
전기 안전
+
+ + + 진행 중 + +
+
점검자 C
+
오전 07:30
+
+
+ + +
+
+
여의도공원 — 북문
+
소방 · 소화기·비상구 점검
+
+
소방 안전
+
+ + + 예정 + +
+
점검자 D
+
오전 09:00
+
+
+ + +
+
+
마포창고 — 2번 구역
+
장비 · 크레인·지게차 점검
+
+
장비 점검
+
+ + + 완료 + +
+
점검자 E
+
오전 05:45
+
+
+ + +
+
+
종로플라자 — 옥상
+
일반 · 정리정돈·보호구 점검
+
+
일반 안전
+
+ + + 완료 + +
+
점검자 F
+
오전 06:30
+
+
+ +
+
+ + + + +
+ + + + diff --git a/docs/execution.md b/docs/execution.md deleted file mode 100644 index cf2d526..0000000 --- a/docs/execution.md +++ /dev/null @@ -1,570 +0,0 @@ -# KSI native execution workflow - -## Native roles, delegated execution - -Plan supports understanding the project, comparing alternatives, analyzing risks and preparing consequential decisions without implementing ordinary product files. It is not a required stage for every edit or a substitute for an independent reviewer. Build can investigate and plan as part of execution, implement directly, delegate bounded work when authorized and useful, and reconcile the final result. Preserve both native system prompts. - -### Current KSI operation versus installer scope - -The KSI host's approved operating configuration changed on 2026-10-09: native -Plan/Build coordinate, native General implements and self-tests, Explore -investigates, the default Design Specialist owns visual frontend work -(design/frontend/render), and review runs through the CE review skill. Compound Engineering (CE) supplies -optional skills, not another agent or a mandatory pipeline. Developer, Test Runner and Reviewer are -removed from that global environment; Superpowers and the Goal plugin -are no longer registered there. These are dated host-specific facts, not proof -that another server or project has the same effective configuration. - -The installer bundle still offers Developer + Reviewer (public npm 0.5.0-beta.9); -previous public release 0.5.0-beta.8 was the historical Developer + Test Runner + Reviewer -release. Neither applies the global migration. Do not reinstall retired roles or plugins merely to match an -installer example. Preserve unrelated installations and inspect the actual -serving configuration when adopting or troubleshooting an environment. - -Below, "the design workspace" means the default Design Specialist unless OpenDesign was -separately and explicitly selected for that environment, in which case the external -OpenDesign workspace takes that seat. OpenDesign is never the sole, default, or mandatory -design path, and its absence never blocks visual work on the default path. Build owns -overall integration and may apply simple mechanical UI transfers directly; API/data/auth -contracts stay unchanged. - -Build chooses native execution or relevant CE skills as the work needs, without -asking the user to choose an A/B/C mode or imposing a no-mixing rule. Build and -General may load skills their effective permissions allow. General cannot -launch further subagents; its implementation assignment must not require that. -Explore's current skill denial is not lifted by a CE instruction. Build owns any -needed child dispatch and final integration; a skill grants no new authority. - -The optional [autonomous local execution policy](../examples/autonomous-development.md) delegates the agreed outcome through implementation, bounded helpers, integration, verification and task-owned local commits. Merge it into the appropriate global/project `AGENTS.md` only after adopting it; package installation does not apply it. This is operating guidance, not permission enforcement or a new runtime. - -### Project judgment, not a replacement Plan role - -Latest approved requirements and user decisions define intended behavior; code, tests, Git and observed checks establish actual implementation state. Report discrepancies rather than letting existing implementation or a passing check redefine the product requirement. A checkpoint loses to fresh repository evidence about state, not to implementation about what the product should do. - -When asked for next development tasks or priorities, compare the current approved milestone and acceptance criteria with relevant implementation and verification evidence. Recommend by contribution to that goal, dependencies and actual blockers, not by unfinished-plan order alone. If a missing/conflicting goal materially affects the choice, ask for that decision. Keep clearly scoped requests focused unless a material conflict requires escalation; neither a recommendation nor technical completion authorizes new scope or interrupts remaining authorized work. Apply this criterion to Plan and Build without making Plan a mandatory gate or requiring full-product audits for small changes. - -Requests are inputs, not orders. Before accepting scope, judge it against the product goal: the problem it solves, the user value, and the smallest viable path to verify it. If a better alternative exists — or the work looks unnecessary for the approved goal — say so with evidence and propose the alternative; the product or business decision stays with the user. At slice boundaries, re-check whether the goal is already met or the value unclear, and recommend stopping or cutting scope when the remaining work no longer serves the goal. This judgment is expected of Plan and Build alike and never authorizes silently changing approved scope. Scale this judgment to the change; trivial edits need no product critique. - -Do not ask "should I continue?" between authorized tasks. Report meaningful progress and continue. Resolve reversible implementation details inside the agreed contracts; record significant assumptions. Reopen a decision only when material scope, target, effects or risk change, not because a file, test cycle or plan task ended. - -### One approved plan across Plan and Build - -Plan prepares decisions; it does not become the authority over the user's goal or -the execution Primary. Build can also plan when the task is clear. Use the same -workstream record whichever agent or installed skill helped write it. Optional -CE skills provide applicable methods, not a second competing plan or extra -product authority; keep required specs linked rather than duplicating them. Skills -are tools, not mandatory stages: choose the lightest effective path, and do not let -process ceremony substitute for the model's own judgment. The checks the chosen path requires still apply. - -For substantive planning, keep the handoff compact and task-proportional: - -- Goal and latest requirement/decision basis; approval state and exactly what is approved. -- Scope/exclusions, preserved contracts and observable acceptance/failure criteria. -- Relevant repository/worktree/base, existing spec/record paths and unresolved decisions. -- Dependencies, execution order and verification/evidence needed for completion. - -This is content to capture in the existing document, not a mandatory form or a new -spec for every small edit. A Plan draft remains a proposal until the user approves -the relevant scope; selecting Build, saving a file or checking a box is not approval. - -#### Native Plan storage to project record - -Respect the actual native write policy. V2 normally permits Plan documents under -`~/.opencode/plan/`, not ordinary repository edits. When a required repository record -cannot be written in Plan, save the plan in its permitted native location when -asked and return its exact path, relevant section/version, intended repository/ledger -target, approval state and open decisions. Disclose that repository persistence is -pending; do not use shell, MCP or an implementing helper to bypass the restriction. - -After switching to Build with the relevant authorization, Build reads that actual -source and the existing project record before implementing or delegating substantive -work. If inaccessible, report the missing input instead of reconstructing it from -memory. Preserve the reviewed content and the user's actual approval when recording -it in `docs/plans/` (and linking any required spec). Importing a proposal -does not approve it. An unchanged approved plan does not need approval again merely -because it was transferred; material content changes retain the decision gate. - -Record the native source path/version and the canonical project record path in the -handoff. The project record becomes the execution authority for that approved scope; -the native file is the retained source, not another independently evolving execution -plan. Preserve prior files; do not overwrite an existing record or delete a native -draft blindly. Use sequential ownership, not automatic synchronization or another -session launch. No additional Plan permission or built-in system override is required -for this path. Other hosts/configurations must be checked, not assumed identical. - -#### Execute and reassess without inventing completion - -At start/resume, Build compares the approved record with current branch/HEAD/status -and relevant code/check evidence. Inspect differences only as needed. A stale plan -does not authorize replaying completed work; code/tests establish actual state but -do not rewrite the approved goal. Resolve routine reversible execution details and -record significant changes without reopening settled decisions. Material scope, -contract, design, security or external-effect changes return to the user. - -During execution, Build records implemented work, verification, blockers and next -actions against that scope. Plan may reassess evidence and propose a revised plan, -but cannot declare work implemented/accepted from the plan alone or silently change -approved requirements. Build owns execution progress/common record updates; Plan -proposal edits and Build evidence updates are handed off sequentially. Keep user -acceptance and external delivery distinct. Native prompts/modes/permissions, design -ownership, independent verification and external-effect boundaries remain unchanged; -this guidance is not enforced plan loading, monitoring or guaranteed compliance. - -### Own the completion loop, not a list for the user - -An authorized outcome includes agent-owned discovery, prioritization, repair and -reverification of relevant defects. Default to: acceptance criteria → implementation -→ review of actual results → concrete defect correction → affected checks → outcome -report. Prioritize by contribution to acceptance, safety and regression risk; choose -routine tools, sequencing and reversible details yourself. Do not stop after a -findings/candidate list, ask the user to select routine repairs, or make them repeat -known requirements. Keep defect → revision → check → fixed/open evidence in the same -task ledger, and continue all remaining authorized work without per-round approval. - -Self-review does not replace independent evidence. Engineering uses actual code and -changed-flow results; the design workspace owns actual rendered review/refinement -and all visual repairs. Preserve the CE review skill for changes that warrant review and the integration -checks the change needs; General tests are author evidence and Build reuses -already-valid checks instead of rerunning them. Do not force a helper per edit or -treat a generator's self-rating as design approval. Rerun affected checks after a -repair. A passing subset or an agent's “done” is not the agreed result. - -Match depth to risk. For recurring/no-progress failures, preserve partial work, -diagnose the cause and adjust a reversible approach within scope instead of blind -replay or endless handoffs. If the agreed quality cannot be reached, report the -concrete remaining defect/blocker truthfully; do not call it complete. Required -access, material direction/scope/security decisions, substantial cost/risk increases -and unapproved external effects still return to the user with a recommended default. -Continue independent authorized work where safe. No arbitrary round cap, new runtime -loop, automatic goal or permission expansion is implied. - -The request still controls authority: an audit-only request includes review/correction -of its findings and evidence, not product implementation; Plan cannot execute product -repairs. An improvement/implementation request delegates routine corrections serving -that approved outcome, not every backlog item or another product. Respect explicit -pauses and native modes/permissions/budgets. Report the usable result and actual -verification briefly, plus only unresolved material decisions/blockers; do not hand -ordinary prioritization or a routine “which one next?” menu back to the user. - -### Verdict from verified results, not repeated re-review - -Build's default verdict scope is: the change matches the approved requirement and -observable acceptance; the returned scope matches the assignment without unrelated -or unexpected changes; the evidence names the revision/workspace it covers, -including relevant uncommitted changes, and no later affected edit invalidated it; -interfaces and integration are consistent; and unresolved items with their risk are -known. A “PASS” without revision, scope and environment is not acceptance evidence, -and a helper's “done” is not approval. - -Match depth to risk, not file count: routine text or small approved-pattern edits -need the owner's self-check plus any required final check; ordinary features add the -integration checks the change needs; complex or interface/API-changing work runs the -CE review skill (ce-code-review); auth, permissions, payment or data changes get -that review plus strengthened independent checks. There is no separate KSI -Reviewer step to run as well. Milestone/closeout verification stays real: Build verifies the -integrated revision and unverified areas and records the actual commands/results, -and where a project requires independent execution verification beyond that, an -explicitly configured verification role or authorized CI provides it. Visual changes -remain design-owned regardless of size. - -Do not re-read the whole implementation by default; that duplicates the -review step and spends the context Build needs for the remaining work. -Expand to specific diffs, logs or renders only when evidence is missing, stale or -contradictory; a contract or interface conflict is plausible; unexpected or unrelated -changes appear; or a high-risk boundary is touched (auth, permissions, data, external -effects, irreversibility). Route each finding to its owning seat — implementation -defect → General or design, review gap → CE review (ce-code-review), evidence gap → Build (or an -explicitly configured verification role), contract/scope → Build, then the user when -material — with the concrete defect, and recheck affected behavior after the repair. -Tag each recorded defect with its origin seat (self, CE review, design review, -independent verification) so agent contribution can be judged across tasks over -time; this is tracking, not a quota or a forced-helper mandate. - -Keep helper returns compressed — result, scope, verification, unresolved items and -evidence locations — with full logs, diffs and code in the ledger or evidence files, -pulled only when a verdict or repair needs them. Resume from the saved state and -actual Git, not from conversation history. - -## Technical completion and human acceptance - -Technical completion requires the agreed implementation/integration and actual verification evidence, plus scoped local commits when allowed by the adopted policy. Report failing/unrun checks honestly. Human product acceptance and external delivery are distinct states: tests do not establish user satisfaction, and pending acceptance does not block remaining authorized engineering work. Technical completion does not authorize a new milestone or unrelated backlog work. - -All visual frontend work, including small CSS/spacing/overflow fixes, belongs to the design workspace through [the design integration contract](integrations/opendesign.md): analysis, UI source/styles, rendered design review and refinement. Build owns task contracts, coordination, nonvisual logic/API/data/auth, functional integration/verification and Git, not substitute visual repairs. Default to finish-and-report within the authorized outcome/brand; preview-and-wait for unapproved new direction, client delivery, scope change or review-first request. Continue independent engineering while waiting; small visual repairs use compact refinement, not a new intake/full audit. UI references are supplementary, not another design path. - -For frontend work, follow the [product-aware flow](integrations/opendesign.md#product-aware-frontend-improvement): send actual source/tokens, sanitized states, inspected references and functional contracts; separate behavior/brand invariants from redesign freedom. Prefer verified direct editing of actual UI source (worktree optional); one writer at a time, sequential handoff for shared files. Design review uses actual renders/source and produces criterion/defect → revision/render → fixed/open evidence. Build verifies that evidence and product behavior, and only mechanically adapts a separate artifact when direct access is unavailable; visual adaptations go back to design. The design workspace owns its internal implementation → review → refinement loop; OpenCode does not repeat that visual review — its review seats validate the returned revision/evidence (defects tracked fixed/open) and cover engineering concerns: functional contract, security, regression, integration. Home/credentials/unrelated projects, security changes, external delivery and API/data/auth/dependency changes retain their boundaries. - -Before completion, distinguish generated output, design-reviewed output with criterion-linked render evidence, functionally verified product and human acceptance. A canonical-entry-invalid source edit still needs design review; a report-only/intermediate stage is not UI output. Tests/run success do not establish design quality. A full refinement round without measurable improvement is quality unmet, not “only user acceptance remains”; report open defects and next options. No arbitrary round cap or new automatic continuation loop is added. - -Classify new-artifact intake versus existing-artifact refinement before opening a brief card. Reuse -agreed change requirements and verified project/storage/brand bindings. Interpret physical run status -with required-output validation and agent explanation: produced output, genuine question, environment -blocker, verified no-change and incomplete are different outcomes. The [run-result and Plan-permission -procedure](integrations/opendesign.md#interpret-run-results-before-reporting-completion) retains native -status, deliberate recovery and explicitly adopted read-only design-MCP access in Plan, without a new -orchestrator. Recheck effective deployment identity/file-access evidence after relevant changes. - -OpenCode performs conditional Mobbin research before new/material or unresolved design and passes inspected pattern intent, canonical links and accessible images in the same agreed brief; no inner-MCP/auth inheritance is assumed. Record the actual required-file snapshot and verification in the existing task record; bind human direction approval to the snapshot before production integration only when gated. Keep design previews for direction and actual product-server checks for functionality, with clearly labeled reachable links. Feedback can return as findings/questions, not automatic reverse-session execution or two writers in product files. Follow the [reference and snapshot handoff procedure](integrations/opendesign.md#reference-research-and-brief-transfer). - -## Local commits and external delivery - -After adoption, coherent verified task-owned local commits are routine delegated work unless the user or repository forbids them. Inspect the index/diff and preserve unrelated user work, secrets and checkpoints. This does not permit history rewriting, force pushes or destructive cleanup. A helper's role alone does not grant Git or publication authority; Build owns completion. - -Push, publish and deploy require authorization for the target and relevant effects, including deployment triggered by a push. Explicitly invoking a shipping workflow (lfg, ce-commit-push-pr) authorizes its documented repository effects — pushing the task branch and opening its PR; deployment, registry publication and new credential access stay separate, and merge stays the user's decision unless granted. Authorization may be granted at task start and reused through delivery. Do not ask again unless the target, effects or risk materially change. Native tool approvals, authentication and browser approvals are still required when presented; policy is not a bypass. - -## When to return to the user - -- The agreed technical outcome is achieved: report changes, checks, commits, delivery and remaining acceptance. -- A material product/data/architecture/design or scope decision is missing, or agreed cost/risk substantially increases. -- A new unapproved external effect or a destructive/security-sensitive action is required. -- A genuine access/information/environment blocker cannot be resolved within scope, or execution budget is exhausted: preserve resumable state and report partial work. - -Recoverable failures and intermediate task completion are not stop conditions. Continue independent authorized work if another part is blocked and doing so is safe. - -## Delegation and continuity - -### Record plans and bind handoffs to documents - -Shared files are reusable context, not shared conversation memory. A fresh child -does not automatically inherit the parent's chat, and a link does not prove its -target was loaded. Use existing project records, not a new memory service or -mandatory task form. - -#### Record at a meaningful boundary - -When the user requests a substantive multi-step plan, or a consequential scope/design -decision must survive this session, persist it before delegation, pause or closeout. -Reuse the active ledger under `docs/plans/`; create a focused record -only for a separate workstream. Applicable skills still determine whether a written -spec or implementation plan is needed. Ordinary questions/trivial changes do not -require a new spec, ledger or approval ceremony. Respect native Plan write limits; -if the intended record cannot be written, report that limit and the actual record -location, not a false claim that the repository was updated. - -Label a newly proposed plan **proposed** until approval covers its scope. Record -the user's decision, what it authorizes and excludes, and actual progress separately. -Saving a file, approving a probe, or approving one part does not approve the rest. -Once authorized, continue that work without another routine approval gate. Distinguish -implemented, independently verified, human-accepted and externally delivered outcomes; -none is implied by a checkbox or an agent's "done". - -| Record | Owns | Do not use it for | -| --- | --- | --- | -| `AGENTS.md` / adopted operating guidance | Stable routing and boundaries | Changing task progress or new task approval | -| Existing spec, when required | Requirements and design | A duplicate execution log | -| One active task ledger, or the method's own record (a CE plan/run/review artifact) | Approval/scope, decisions, progress, inputs and verification evidence; links to any spec | Silent execution authority for a proposal | -| `product-state.md` | Product goal/slice/acceptance and backlog summary, linked to the ledger | Copying the whole plan/history | -| `.opencode/working-state.md` | Short worktree resume state and next actions, linked to the ledger | Product documentation or a committed approval record | -| Skill-internal progress or a preserved legacy SDD ledger | The skill's own execution record | A second KSI state or mirrored progress; link it from the task ledger instead | - -One canonical record per concern: a CE workflow's own artifacts are its record — -link them rather than mirroring their content — and the KSI task ledger exists -for workstreams that have no method record. A native session's conversation is -not a record. - -#### Hand off the relevant basis, not the entire history - -Primary selects relevant document paths/sections and carries approved scope, -actual workspace/base, invariants, ownership/exclusions and observable checks in the -compact assignment. Reuse context already read and unchanged; do not make every -worker reread the whole repository. Helpers read relevant inputs and briefly -identify their actual basis in the return, along with changed files/checks/tested -state, conflicts, missing access and unverified work. Do not invent an unread -requirement or resolve a material discrepancy by copying current implementation. - -For example: "Read the approved-scope section of the task ledger and the API -contract in the existing spec; own this retry path, preserve response semantics, -and report files, checks and conflicting requirements." No user-written form, -extra team or delegation quota is required. - -Primary reconciles actual diffs/results and updates common ledger/product-state/ -checkpoint at stable boundaries. Helpers return findings rather than concurrently -rewriting shared records. Build runs the integration checks the integrated revision -needs, reusing already-valid evidence for the same revision; a helper's report does -not replace evidence or grant approval. - -#### Preserve conclusions beyond temporary evidence - -Before reporting a non-trivial trial complete, keep a sanitized durable summary in -its relevant task record: purpose/authority, source revision or SHA, actual target -and commands, observed results, important failures/corrections, helper/check identity, -limitations and next decision. For a throwaway spike, that record may be a concise -findings section rather than a new design spec. Temporary paths can locate optional -raw evidence, but understanding the outcome must not depend on `/tmp` surviving. -Do not copy credential/config backups or sensitive/customer logs into Git. If -evidence was not inspected or is unavailable, label it accordingly; do not recreate -proof or infer cost savings from test durations. Keep detailed history in the -ledger, with only summary/links in product-state/checkpoint. - -These are adopted operating instructions, not automatic injection/synchronization, -runtime enforcement, guaranteed agent compliance or an OS sandbox. The npm installer -still installs only two role files; it does not create records or merge policy. - - - -### Use General effectively, without mandatory delegation - -Keep the current structure: Build may implement nonvisual work directly or assign it to -General. Actively consider General for a coherent implementation/repair with identifiable -scope, stable contracts and observable completion criteria; it is not only overflow help -when Build is busy. Build can work directly when judgment is tightly coupled to its current -context, a clear integration correction is needed, or handoff overhead outweighs the benefit. -Choose by quality, rework and end-to-end efficiency, not task size or delegation count. -Do not split work just to delegate, impose a usage quota, or investigate/implement everything -first and then give General a ceremonial task. Build still does enough investigation to -identify the right outcome, constraints and risks; General owns implementation investigation -and reversible technical choices within that contract. - -In an environment deliberately retaining Developer, the same bounded contract -applies to that configured helper. This does not authorize re-enabling it in the -current KSI global environment. The legacy heading anchor above preserves older -documentation links, not the retired routing preference. - -#### Compact assignment and return - -Build selects the necessary context; do not require a user-authored task form, a full conversation -copy or a second product plan. An ordinary assignment can contain these items in a short message: - -| Assignment from Build | Return from General | -| --- | --- | -| Desired outcome, current problem and observable success/failure criteria | Changed files and implementation summary | -| Actual working directory/base revision, relevant paths and approved decisions | Actual self-test commands/results, the tested revision (workspace + HEAD + relevant uncommitted changes) and where raw evidence lives | -| Behavior/data/permissions/API invariants, owned files and excluded work | Incomplete/unverified work, blockers or contract discrepancies | -| Trusted checks and any access/tool limits relevant to the task | Significant assumptions or follow-up needed for integration | - -The same evidence identification applies to every seat: the CE review names the -diff range and revision reviewed plus what it did not inspect; an independent -verification run names the exact commands, tested revision and skipped/not-run -checks; design the changed screens and files on the actual source revision, the -rendered-review evidence with defects tracked fixed/open, any functional-contract -impact and unresolved items. A return without its revision and scope is not -usable for a verdict, and no seat claims a check it did not run. - -For example: repair an API retry path, preserve response/data semantics and the existing UI, -own the named nonvisual files, add a regression for the observed failure, and report the actual -checks. Give General room to find the implementation; do not prescribe every line. A contract -discrepancy or material new scope/architecture/data decision returns to Build and, when needed, -the user. An assignment grants no additional permissions or install/external-delivery authority. - -#### Integrate evidence and recover partial work - -Build inspects the actual diff and current workspace state against the contract, reconciles -interfaces and verifies the integrated flow. A helper's “done”, test subset or report is not -completion evidence by itself. General tests its own implementation; Build independently -executes the integration/closeout checks the integrated revision needs (or an explicitly -configured verification role does), records commands, results and relevant pending changes -in the existing ledger, and reuses already-valid checks for the same revision instead of -rerunning them mechanically. Later affected edits require rechecking. Independent -review runs through the CE review skill (ce-code-review) — read-only, and not a -substitute for Build's integration checks or design-owned rendered review. - -If a result is wrong or incomplete, return a concrete defect (location/state, expected versus -observed behavior and missing evidence), not a vague demand to try harder. For a worker stop, -preserve partial work, inspect what actually changed and distinguish an implementation defect -from missing context, access/permission, provider or step-limit failure. Resume with corrected -context only when that addresses the cause; do not blindly respawn or replay the task. Repeated -mismatch calls for a narrower contract or another suitable execution path, including direct -Build work within its nonvisual remit, rather than endless handoffs. Do not silently change -models/budgets, install tools or bypass permissions to recover. Report a genuine unresolved -blocker and continue independent authorized work where safe. - -Shared-file work is sequential, one writer at a time; parallel work follows the ownership and -workspace rules below. All visual source/style changes, including tiny integration-related -spacing fixes, remain design-owned. An unavailable design workspace is not permission for a -General or Build visual fallback. These are operating guidelines, not runtime enforcement -or proof of improved real-development quality. - -Parallel scheduling is the working method's own concern — a CE workflow follows its execution strategy, and native runs use OpenCode's subagent capability when separable work exists. Build integrates the results and owns the final claim; no separate KSI coordinator or parallel-development procedure is added. - -OpenCode V2 keeps its own Build, Plan, General, and Explore prompts. The installer bundle's two `agents/*.md` files define only its additional roles (Developer, Reviewer), not the current global operating configuration. KSI's installer does not set models, variants, or steps. Existing positive step budgets stay in JSONC or other user-owned configuration; absent values use OpenCode defaults. Native General and the default Developer cannot delegate. Test Runner is no longer part of the source bundle and `--developer-test-runner` is removed; an already installed Test Runner file remains user-owned, not automatically retired or a required verification seat. Treat helper output as author feedback, not independent acceptance. - -There are no KSI slash commands: ask Build to reconcile or complete work and use the CE review skill (ce-code-review) when a change needs independent review. For continuity, read the project's `AGENTS.md`, `.opencode/working-state.md`, product-state, ledger, and current Git state when relevant; the installer no longer injects these automatically. Use ordinary authorized tools for repository evidence. Six former `ksi_*` evidence tools, automatic archival, and runtime task-contract guards are unavailable. Do not claim they ran. Authority comes from the user/adopted operating policy, not a role or passing test. - -Reviewer's native `edit` and `subagent` denials are targeted protections, -not an all-write sandbox: shell, MCP/API and Code Mode side effects have separate -effective permissions. The reviewer may not repair implementation or change config, -services or external data through another tool, and runs only safe read-only checks; -artifact-producing checks belong to Build/CI or an explicitly configured verification -role. Before a check, confirm its expected effects. Unknown or out-of-scope effects -are reported before execution. Inspect the actual effective policy when isolation -matters; do not silently tighten global permissions or claim the role file enforces -complete read-only access. - -Local autonomy is not perpetual background execution. The current KSI host has no Goal plugin or goal auto-continuation. Preserved goal records, including an `active` entry, are historical data and do not authorize execution or require pausing/clearing. Do not reinstall the plugin to resume ordinary work; use the approved task record and actual Git state. Another environment with an explicitly adopted continuation mechanism must verify its own permissions, pauses and budgets separately. Tool approval, step limits, context and connectivity can still stop execution; documentation and package tests cannot guarantee long-running model behavior. - -## Comfortable defaults, not extra ceremonies - -After the operating policy is adopted, the user requests an outcome, not a branch inventory or test checklist. Build chooses routine commands, workspace names, browser tools and proportionate checks, fixes recoverable failures within scope, and reports meaningful results. Do not repeatedly ask for requirements/approvals already provided, require full planning for every small approved-pattern edit, or run a full-site audit because one label changed. Material product/design decisions and existing security/external-delivery boundaries remain real. - -The default is change-focused verification, not no verification: a broken core flow or an unrun required check cannot be called passing. Existing unrelated warnings and low-priority audit findings do not expand the current task automatically. Record them in backlog and continue independent authorized work. An explicit accessibility overhaul or delivery audit can require deeper coverage. - -## Workspace lifecycle - -Native work respects the current checkout and existing work; a CE workflow follows its own branch and workspace discipline. Do not create branches or worktrees by default; use them only for real isolation or parallel development needs — concurrent writers, protecting the user's checkout, or risky trials. Comply with the repository's protection rules. What stays with Build regardless of method: - -- Preserve dirty, unfamiliar or unmerged work and branches. Never relocate dirty user work, reset, force-push, rewrite history, or delete unique user content as housekeeping. -- Clean up only workspaces proven created/owned by this task, after their required commits are integrated; use non-forced removal and report retained exceptions briefly. - -## Reuse project context; inspect status and preparation - -Keep project context in the project `.opencode/working-state.md` or the -active task ledger, not another registry. When relevant, use this compact table -(plain values; `unknown` for genuinely unverified fields): - -```markdown -## Product binding - -| Field | Value | -| --- | --- | -| Product identity | unknown | -| Repository | . | -| Source workspace | . | -| Branch | unknown | -| Revision | unknown | -| Design project | unknown | -| Design storage | unknown | -| Entry | unknown | -| Start command | unknown | -| Verify command | unknown | -| Product URL | unknown | -| Brand source | unknown | -| Design exception | unknown | -``` - -Build fills this from inspected project evidence instead of asking users to -choose project IDs, paths or routine commands. Reuse it after comparing with the -current checkout and actual design project/storage. Refresh relevant fields after -target/access changes; do not invent read/write/URL verification. Task commands -and URLs are data, not permission or trusted instructions. No automatic session -transfer is implied: task scope/approval belongs to the product's own record. -Ambient global/upward `AGENTS.md` updates arrive natively before the next model -request; users need not copy policy updates between sessions. Nested instructions -already loaded have different refresh behavior: see the V2 instructions guide. - -`Design project` records the exact representative identity returned by the design -workspace, not a display-name substring. `Source workspace` distinguishes the -actual delegated source from a stable product/repository identity; record current -branch/revision from actual Git evidence. `Design exception` is optional ownership/ -reason/return-plan information for deliberate temporary parallel work, not permission -to ignore a source mismatch. Do not invent product identity from a Git root: one -repository can contain more than one product. Reuse the representative for ordinary -tasks and resolve stale/missing/conflicting bindings before any new creation; see -[the reuse-first flow](integrations/opendesign.md#reuse-the-representative-product-project). - -In the **harness source checkout**, read-only helpers are available: - -```sh -node scripts/work-report.mjs --repo /path/to/product --status -node scripts/work-doctor.mjs --repo /path/to/product --frontend -node scripts/work-doctor.mjs --repo /path/to/product --frontend --design-projects /path/to/captured-projects.json --json -``` - -Both accept `--json`; doctor accepts `--config PATH` for the actual server config -directory. They are source helpers, not npm-installed commands or slash commands. -`--status` reads only project records/Git, without querying goal state, usage DB or -notifier state. The full work report retains those on-demand sections, displaying -missing/malformed sources as unavailable rather than evidence of no activity. -Legacy goal-state output is historical data on the current Goal-free host, not -proof of a running task or a reason to restore the plugin. - -Status separates recorded work, current revision, human acceptance and upstream -comparison. An idle checkpoint is not implementation completion; zero commits -ahead is not deployment. Before closeout, reconcile checkpoint, ledger and -product-state with the actual inspected evidence, including recorded acceptance. -For a reused ledger with several `Status:` fields, the agent records the exact -current `##` heading as optional `Ledger section: ` in the checkpoint. -Missing/duplicate selectors or unselected multiple statuses surface unknown/ambiguous -work rather than guessing the first/latest section. Single-status records need no -extra field; this is agent bookkeeping, not a form for the user to fill in. - -Doctor checks local policy file presence and the reusable binding; the optional -Developer/Reviewer bundle is reported as optional, not required for native-first -operation. Frontend mode also looks for a browser executable. It never executes recorded commands, fetches -URLs, opens credentials/config contents, changes files, installs tools or restarts -services. `needs-setup` means local prerequisites are absent/mismatched; -`unverified` means effective native catalogs, actual design read/write and product -flow still need verification. Even all-present metadata does not prove readiness. -Optional notifier absence is not a blocker for ordinary local engineering. - -The optional `--design-projects FILE` consumes a caller-supplied current MCP list -(`{"projects":[...]}`) or array snapshot and compares exact recorded identity and -source directories. The agent obtains/sanitizes the snapshot; users need not copy -it between tools or administer project IDs. Missing, malformed, ambiguous or -contradictory explicit snapshots do not become a match. A `matched` result is only -captured metadata correspondence: confirm the actual current project read/source -access before a run. Doctor neither contacts MCP, reads credentials, checks current -branch/revision, enforces uniqueness across clients nor creates/rebinds a project. -It remains a source helper, not an installed command or runtime guard. - -## Pause/resume in ordinary language - -For an explicit “pause this task/session” request, Build uses the existing native -tools to pause **this session's** notifier wake permission and stop starting -further task work. The current KSI host has no goal-continuation switch to pause. -Preserved goal state is not an active execution mechanism; never reinstall a -plugin to pause it, apply this to unrelated sessions, or edit state JSON directly. - -Report separately: no installed goal auto-continuation, notification wake permission, -and any external design execution still running. A pause is not an implicit -cancel; report an unavailable notifier switch or an already-running external -execution rather than claiming “everything stopped.” - -On explicit resume, reconcile the authorized task record, actual Git state and -native mode, then resume requested notifier wake permission. Plan may reassess -but cannot resume implementation. Do not revive preserved goals or create new -scope. In another environment with a separately authorized continuation plugin, -inspect and control its actual live switch only if the request covers it. This -procedure coordinates existing switches through the agent; it is not a new stop -API, enforced atomic operation or background runner. - -## Browser workflows and accessibility coverage - -Use the available approved browser driver against the real product server, not only a design preview. CLI installation or MCP tool discovery alone is not a completed check. Prefer maintained Playwright CLI/agent-browser skills and command help when installed; existing Playwright MCP is an alternative when it is the working available driver. Bind sessions to the task/workspace and avoid having two drivers compete for the same browser state. - -For the changed flow: - -1. Resolve the actual product root, startup command, reachable URL and safe test state. A DGX/container localhost is not automatically the user's PC. Do not attach to personal browser profiles or expose a debug endpoint broadly without scoped authorization. -2. Inspect the current page, use real refs/locators, and refresh the snapshot after navigation, dialogs, tab switches or material DOM changes. A stale-ref failure calls for a fresh snapshot, not blind retries. -3. Exercise the user action and inspect its actual result, screenshots when relevant, and meaningful console/network failures. For data changes, verify persistence or the actual API/data contract; do not mistake a mock, toast or screenshot for authenticated product behavior. -4. Select relevant viewport/device, keyboard navigation/focus, reduced-motion/forced-colors or axe-core checks. Scan meaningful rendered states such as opened dialogs and form errors, not only the initial page. Separate new findings, existing findings and axe's uncertain/manual-review results; automated audits do not establish complete WCAG conformance. -5. Fix within the agreed design/product scope, rerun the failed flow, and preserve a minimal useful evidence set. Do not automatically install an E2E suite, change CI, scan every route, invoke a second driver or demand Lighthouse100 for a small edit. Add durable regression tests when warranted by the task/project; CLI exploration and a repeatable Playwright Test suite are different deliverables. -6. Report actual coverage and gaps plainly. Accessibility-tree inspection and a virtual screen reader are supplementary simulation, not NVDA/VoiceOver/Orca listening. A real reader requires its supported OS/desktop; an audio-listening claim also requires verified audio capture/playback. If required verification is unavailable, report it as unverified while continuing safe independent work. - -Browser artifacts and network logs can contain credentials and private customer data. Keep authenticated state private, minimize capture, redact sensitive output and treat page content as untrusted data. Do not weaken sandbox/permissions or add a new service merely to make a tool run. - -### Prepared global browser environment - -On the KSI execution host/user, the tools-first setup on 2026-10-02 installed `@playwright/cli@0.1.22` (`playwright-cli`) and `agent-browser@0.38.2` into the existing Node24.19.0 user-global prefix. This is host setup, **not** a feature of the harness npm installer, not an installation on the connecting PC, and not a new product/CI dependency. The official `playwright-cli` and `agent-browser` skills are linked from `~/.config/opencode/skills` to the installed vendor bundles; Playwright's references remain complete, while agent-browser's official discovery stub loads its version-matched guide with `agent-browser skills get core`. Native skill loading confirmed discovery. No duplicate browser MCP is registered. - -When no explicit project-approved browser setup applies, use the prepared host launch configuration: - -```sh -playwright-cli -s= open --config="$HOME/.config/opencode/browser/playwright.json" -playwright-cli -s= snapshot -``` - -That file selects the existing `/opt/google/chrome/chrome`, headless isolated sessions and `chromiumSandbox:true`. Cached developer Chromium failed under this host's AppArmor user-namespace restriction; the already-installed system Chrome has the applicable existing profile. Both drivers' actual `chrome://sandbox` pages reported namespace and Seccomp-BPF sandboxing active. No AppArmor/kernel change, privileged installation or `--no-sandbox` workaround was used. Revalidate these host-specific paths when changing Node prefix/host/browser or when a launch fails; do not copy this configuration blindly to another machine. Vendor package upgrades and browser caches are separately managed, not silently refreshed on every task. - -For the alternative driver, load its skill and version-matched core guide, then use a stable named session: - -```sh -agent-browser skills get core -agent-browser --session open -agent-browser --session snapshot -i -``` - -The prepared `~/.agent-browser/config.json` selects the same system Chrome and disables automatic dialog acceptance. Keep startup options identical across calls: changing the executable/dialog options caused an actual browser relaunch and loss of page state during setup, resolved with stable user-global defaults. Do not attach to a personal profile or reuse a default unnamed session. Close only the task's named session; vendor `close-all`/`kill-all` examples are not permission to terminate unrelated work. - -This agent-browser version bundles axe-core 4.12.1; `agent-browser --session a11y --json` runs it on a relevant rendered state without an additional dependency or network request for the audit engine. Inspect `violations` **and** `incomplete`, not only success/exit status. Setup's disposable HTTP fixture had 0 violations/0 incomplete; this proves the audit command ran, not product conformance or screen-reader listening. Standalone Lighthouse/virtual-reader tooling and real reader/audio setup remain separate. - -Both CLI capability checks exercised fresh snapshots, keyboard focus/submission, actual fixture POST/GET and backing-file persistence after reload, 390px viewport, screenshots, and console/network inspection; Playwright also exercised reduced-motion emulation. Four screenshots were inspected. These were independent **tool capability** sessions, not a requirement to run two drivers for every product change and not verification of a customer's authentication/API/routing. Apply the earlier change-focused real-product checks on each relevant development task. - -For completion evidence, record the actual revision/relevant pending changes, target, command and result in the existing ledger; later changes require rechecking affected behavior. Run the CE review skill (ce-code-review) when a change warrants review, and reuse already-valid checks instead of rerunning them; there is no mandatory extra verifier after every edit. Resume from the approved task record, not the preserved Goal state. A future V2 evidence helper requires a demonstrated gap and its own approved design; it must not become a second orchestrator or a session-idle auto-fixer. - -## Portable skills versus host-specific plugins - -OpenCode V2 discovers portable skills from its native and documented compatibility locations. That is not a Claude Code plugin loader. A Claude Code plugin can bundle a manifest, skills, agents, hooks and MCP setup; OpenCode V2 plugins have their own API and lifecycle. The [OpenAI Playwright skill](https://github.com/openai/skills/tree/main/skills/.curated/playwright), [Playwright CLI skills](https://github.com/microsoft/playwright-cli) and [agent-browser skills](https://github.com/vercel-labs/agent-browser) are workflow candidates, not proof that their paths/dependencies are installed here. - -Reuse maintained skills after checking host assumptions, paths, commands and conflicts. Do not copy or rewrite upstream skills into competing KSI skills, and preserve historical Superpowers/SDD records without treating their paths as a runtime dependency. Install or adapt a runtime plugin only for a demonstrated missing capability with scoped permission; do not add a second orchestrator or hooks that reopen completed decisions/run a full audit after every edit. Guidance is agent operating policy, not new runtime enforcement. - -Sources: [V2 skills](https://opencode.ai/v2/docs/skills), [V2 plugins](https://opencode.ai/v2/docs/plugins), [V2 worktree APIs](https://opencode.ai/v2/docs/build/sdk), [Claude Code plugin components](https://code.claude.com/docs/en/plugins), [axe-core](https://github.com/dequelabs/axe-core), [Guidepup actual readers](https://github.com/guidepup/guidepup), [virtual-reader limitations](https://github.com/guidepup/virtual-screen-reader). diff --git a/docs/integrations/opendesign.md b/docs/integrations/opendesign.md deleted file mode 100644 index aafe104..0000000 --- a/docs/integrations/opendesign.md +++ /dev/null @@ -1,1102 +0,0 @@ -# OpenDesign integration - -> 범위: 이 문서는 **별도로 명시적으로 선택한 환경에서만 쓰는 외부 워크스페이스 통합**의 상세 -> 절차서입니다. 시각 실행의 기본 담당은 Design Specialist이며, OpenDesign은 기본·유일·의무 -> 경로가 아닙니다. OpenDesign을 선택하지 않은 환경에서는 이 문서의 절차를 적용하지 않으며, -> 그 미선택이 시각 작업의 차단 사유가 되지 않습니다. 아래의 상세 절차는 해당 선택이 이루어진 -> 경우에만 조건부로 적용됩니다. - -The design workspace is a separately deployed application, not a role in this -installer and not a vendored skill kit. The installer ships no design skills, design system, or craft -references; this document defines how the harness expects OpenCode to use the OpenDesign capability. - -This is an **optional, user-managed integration**. Installing KSI does not install the application, -Docker, model CLIs, credentials, or its MCP server. Follow [upstream installation instructions](https://github.com/nexu-io/open-design) -for your platform; approve any installation, shared configuration, credential sharing, and service changes separately. -The source repository's `integrations/opendesign/` assets record one Ubuntu arm64 deployment, not a portable -automatic installer. - -## Division of responsibility - -DGX user-level operating guidance is installed separately in `~/.config/opencode/AGENTS.md`, under -`Design-to-engineering workflow`. It applies across that user's OpenCode projects; it is not installed -by the KSI package. Where OpenDesign was separately and explicitly selected, the design -path is **OpenDesign's local execution** (the execution agent/model is -chosen per run — not the standalone Codex CLI) in the connected server's execution environment, -not the browser client's PC. **User-facing copy: call it "OpenDesign (local execution)".** -"Local Codex" is the vendor's mode label (the OpenDesign MCP instructions list it as a product name); -do not present it as a separate product, agent or CLI — if the label must be quoted, state immediately -that it is the same local OpenDesign execution. This environment's explicit user naming preference -overrides the vendor default. **OpenDesign Cloud** requires an explicit user request. Agents must verify -the actual connection, project and storage root before describing execution location or modifying files; -they must not assume a code repository is already a registered design project. Design-to-product handoff -is explicit, not automatic synchronization. Small visual fixes stay design-owned; -purely nonvisual repairs and mechanical transfers stay engineering-owned. - -The V2 global instruction file is refreshed before the next model request; policy-only changes need -no service restart. A repository name does not prove registration or folder association: resolve the -actual target before work, rather than creating a replacement project by default. - -**Design-side global principles vs project context (two layers, applied 2026-10-10).** -The design workspace's own global settings hold the design-side responsibility principles: -understand the product before changing it, exercise professional design judgment across UX, -IA, interaction, visual quality and consistency, use skills/systems/references/critique -methods adaptively rather than mechanically, own end-to-end frontend implementation within -the assigned scope, critically evaluate the actual rendered outcome rather than the process, -resolve routine design problems autonomously instead of waiting for the user to enumerate -them, and distinguish verified results from unverified assumptions. They deliberately do -not force a specific skill, a fixed iteration/render count, a layout style, or OpenCode-side -orchestration rules. Project-specific context — intended users, primary workflows, design -system, existing UI structure, product constraints — belongs to the product's own repository -documents (for example the resident `product-context.md`), not to the global layer and not -to a new KSI design framework. The applied text is user-owned configuration and is not -duplicated here. Principles are not capability: they do not by themselves add browser access -or multimodal verification; the execution environment must support that, and their effect is -judged on real delegated work — how many user-facing problems a delegated improvement finds -and resolves without extra user direction. - -| Owner | Responsibility | -| --- | --- | -| OpenDesign | UI/UX quality owner for the delegated scope: user/task understanding, autonomous problem discovery and prioritization, visual analysis and decisions, layout/hierarchy/component presentation, frontend implementation in actual UI source/styles (including small CSS/spacing/overflow fixes), responsive/visual interaction states, rendered self-review, verification of the affected result, autonomous refinement and completion evidence | -| OpenCode (Build) | Scope definition and delegation (target, purpose, preservation constraints — not a presolved defect list or fix direction); overall execution/integration and technical-completion evidence; functional/API/data/auth/brand contracts and production constraints; nonvisual engineering directly or through Developer; regression/integration verification, Git and reporting | -| CE code review | Independent code/behaviour review of the change when warranted (ce-code-review; safe read-only checks only); does not substitute for rendered design review or human acceptance | -| Human | Material direction/product decisions, final acceptance and external-delivery authorization; existing authorization is reused within its approved target/effects | - -This is a responsibility boundary, not an aesthetic command hierarchy. Build delegates purpose, -outcomes and invariants; OpenDesign has discretion over layout, information hierarchy, component -composition and responsive presentation within the authorized scope. With verified source access, -the preferred path is direct product-source implementation, not a mockup that Developer must recreate. -Plan can help define the contract but is not mandatory for clear tasks. - -**Delegating design work (Build side).** Delegate UI/UX quality for the scope, not a presolved -design. A delegation states the target screen/flow, the purpose to achieve and the constraints to -preserve (business rules, API/data/auth/permission contracts, brand/tokens, one writer, unrelated -functionality); user complaints pass through as reported symptoms, not as the definitive defect -list. Problem discovery, prioritization, direction, implementation, rendered verification and -iterative refinement belong to the design workspace — so a short delegation is the norm, because -the design-side global principles already carry that responsibility and Build need not restate the -workflow each time. If the design side concludes that business rules or product direction must -change, it reports the proposed change and it goes to the user for a decision — never adopted -silently. When Build's functional verification finds remaining visual/UX defects, they return to -the design workspace with evidence; Build neither patches nor re-critiques the visual result. Build -keeps verifying that existing functionality, contracts, permissions and integration still hold. - -Build can detect design mismatches, responsive/accessibility regressions and production-contract -conflicts, then provide evidence to design; it does not substitute its own visual repair or approval. -Design success is not whole-product completion. If an API connection breaks, Build fixes or delegates -the nonvisual integration defect; visual defects return to design, with shared files handed off -sequentially. Developer's author tests do not replace independent review or the integration checks -the integrated revision needs: the CE review skill reviews code and test adequacy when warranted, Build verifies the integrated -revision and unverified areas (reusing already-valid checks), and later relevant edits require -affected checks again. These responsibilities -do not create a native design child agent or another development Primary, or runtime enforcement. - -## Prerequisite - -An OpenDesign daemon must be reachable and its MCP server registered in the OpenCode configuration the -session uses. Verify on initial use, when the target changes, or after connection errors/restarts: - -- the MCP server appears in the active tool catalog, and -- a read tool such as `list_projects` returns the expected workspace. - -Reuse a valid check for the same connection and explicit target; do not repeat setup discovery for every -design edit. Active context may change or expire, so resolve it when the user's request depends on the -currently open project/file. A lost connection requires a new read, not automatic write/generation replay. - -### Resolve once, verify the actual capability - -- With an explicit project, read that project directly; do not first resolve an unrelated active tab. - Resolve active context only when the user means the currently open design. Once resolved, pin the - project/entry for subsequent calls; do not use an expiring active context as a durable task identity. -- Record the product repository/screen, design project, resolved storage, entry and brand/token source - in the existing task ledger. A managed design folder is valid and may intentionally be separate from - production code; mark an unverified product linkage as pending, not inferred from a matching name. - A null registered design-system id does not negate an existing file-based brand specification. -- Discover the few needed tools by server namespace/name, then reuse their verified signatures. - Page only when the result's next page is relevant. Re-discover after a connection/catalog change; - a temporarily empty catalog after a workspace move is not proof the integration was removed. -- Web health, a connected MCP and an installed agent CLI prove different things; none alone proves - the inner agent can read required sources or write task-owned output. Reuse valid same-environment - file-access evidence, but invalidate it after image/CLI/security/mount/storage changes or access errors. - Any new model-backed smoke run still needs its own authorization; do not silently launch one on every edit. -- After deployment/recreation, compare the actual image digest, effective Compose file list, storage - mounts and enforced security profiles with the approved deployment. Check effective capabilities, - no-new-privileges and seccomp as appropriate. Preserve credentials without reading/logging their values. - Drift is a blocker for a dependent run, not permission to restart or disable a sandbox. Report the - smallest separately authorized recovery action, active-run protection, interruption and rollback scope. - -Check `opencode mcp list` from the **actual project directory**. A healthy web server or a successful -standalone MCP probe is not proof that the current OpenCode session is connected. If it says -`Connection closed`, use OpenCode's `/mcps` to disconnect and reconnect this server in that project, -then recheck tool discovery and a real project read. Container restarts can terminate a live stdio connection; -the earlier container-local transport cannot recover automatically. A new chat alone is not a proven recovery mechanism. -The DGX deployment now uses the host-side proxy described below, verified to survive container restart. - -For manual V2 configuration use `mcp.servers`, `disabled`, and timeout objects as documented in -[the V2 MCP guide](https://opencode.ai/v2/docs/mcp-servers). Do not blindly copy older `mcp`/`enabled` examples. -Use the command supplied by your actual upstream installation; Docker deployment commands are not universal. - -If the capability is absent, say so and continue with the engineering work that does not depend on it. Do -not invent design artifacts or silently substitute a locally-invented design system. - -## When to use OpenDesign (only where explicitly selected) - -Where OpenDesign was separately and explicitly selected, the design path is OpenDesign's local execution. Reuse requirements and constraints already agreed in Plan/Build in the design brief instead of interviewing the user again. New artifacts still use the brief/confirmation flow; reuse does not fabricate answers or skip a missing material decision. Default to finish-and-report within the existing approved design language and authorized scope. Preview-and-wait only for a new brand identity, client-facing deliverable, out-of-scope change or explicit review-first request; a delegated redesign's visual direction is design-owned and does not add a routine approval step. Gate approvals are distinct from routine progress confirmation. Continue non-dependent engineering while waiting and, after resolving a gate, continue integration and verification without per-task permission prompts. - -### Ownership: design work and engineering work - -OpenCode coordinates the task; the design workspace owns **all visual frontend -work**: UI/UX analysis, composition, component presentation, responsive and -interaction states, actual UI source/style edits, rendered design review and -refinement. This includes small CSS/spacing/overflow repairs. Do not route by -file count or call a visual fix an engineering exception because a pattern exists. -This is a connected specialist workflow through MCP, not a new native OpenCode -agent, automatic synchronization or another development Primary. - -OpenCode owns purpose/scope, source and reference access, business/API/data/auth -contracts, functional wiring/verification, integration, Git and reporting. It can -detect visual defects and provide screenshots/reproduction steps, but sends the -visual repair back instead of inventing a competing fix. Pure nonvisual logic, -permissions and persistence defects remain engineering work. For shared files, -handoff sequentially; one writer at a time. Mechanical integration of a verified -artifact is allowed without redesign; any visual adaptation returns to design. - -For a redesign, separate **invariants** (functionality, data, permissions, route/API -contracts and agreed brand) from **redesign freedom** (layout, hierarchy, density, -component composition and responsive presentation within the authorized outcome). -Preserving behavior does not mean preserving a rejected layout. A request to -improve substantially requires a diagnosis of the user's task friction and the -structural change that addresses it, not merely a larger KPI or stronger color. -Unapproved new brand identity, client delivery, scope changes and review-first -requests keep their decision gates; a delegated redesign's visual direction is -design-owned, and authorized structural improvement alone does not require -repeated approval of routine iterations. - -### Intake versus refinement - -Classify the action before opening a brief card: - -- **New artifact:** collect and confirm the brief unless the user explicitly waived questions. Prefill - supported known answers from the user's actual requirements; do not invent answers or confirm for them. -- **Existing artifact refinement:** when the target and material direction are already agreed, use a - compact change brief rather than open and discard a new-artifact card. Include the current entry, - brand/token sources, requested delta, affected screens/states, expected output files and exclusions. - Inspect the relevant artifact bundle when its contents are needed; metadata alone does not prove - knowledge of the existing composition or CSS. Ask only for a missing material decision. -- **Small visual repair:** use a compact design refinement with the exact source, - defect, reproduction viewport/state and expected fix; no fresh intake interview, - mandatory Plan stage, full-site audit or per-fix approval. Scale review to the change. -- **Nonvisual engineering:** remain in Build; no design run for a pure backend, - API/permission/persistence repair or an exact artifact transfer with no visual changes. - -For example, a visual-theme refinement may preserve copy, navigation and information architecture, -use the existing brand specification, and identify the actual affected screens and required responsive -states. Touch-target and overflow requirements are constraints to verify in rendered output, not a -claim established by merely including them in the prompt. - -Read the current tool's locale support. The inspected brief-card implementation supports English, -Simplified/Traditional Chinese and Japanese, with English fallback; Korean product UI does not imply -a Korean brief card. Preserve the user's Korean requirements in the readable change brief and disclose -card fallback if a new-artifact card is needed. Do not alter upstream localization or fabricate a -translated native card. Recheck this limitation after a relevant upstream upgrade. - -Installed UI-reference skills (if any) are supplementary reference material for briefs, accessibility notes, responsive risks and implementation constraints — not a review path and not an independent design-generation path. They must not replace an approved artifact or existing tokens/components with a competing palette, layout or system. If OpenDesign's local execution is unavailable, report that blocker and continue non-dependent engineering; do not silently substitute a skill-generated design. OpenDesign Cloud remains explicit-request only. - -- Any visual UI source/style change, from an existing-component repair to a new screen. -- Layout, hierarchy, navigation presentation, responsive and interaction-state work. -- Rendered design analysis, critique, polish and concrete refinement of those outputs. - -## When not to use it - -- Backend/API/data/auth and other purely nonvisual business-logic work. -- Functional verification, Git and mechanical integration that changes no visual design. -- A literal typo correction changing no layout or UI composition; if it changes the - presentation, use the compact visual-refinement path instead. - -## Interface contract - -Read and inspect: - -- `get_artifact` — the entry file plus referenced siblings in one pull. Prefer this over repeated single-file reads. - Pass a known project/entry directly. If default-entry lookup fails or the target is unknown, retrieve metadata - and pass the explicit project/entry; do not add a metadata lookup to every successful read. -- `get_file` / `search_files` / `list_files` — a single known file, a literal search, or metadata with change polling. -- `get_active_context` — the project and file the user currently has open, when that is the right target. - -Commission or extend design work: - -- `create_project` — a new project when none fits. -- `start_run` — ask OpenDesign to generate or refine a design. Generate one canonical request id per confirmed - user action and reuse it verbatim if a response is lost. -- `get_run` — poll until terminal. OpenDesign runs can legitimately take a long time; report progress to the - user instead of cancelling, and only cancel on an explicit instruction. -- File writes through the MCP surface are available, but they are not a substitute for a design run when the - task is actually a design task. - -### Interpret run results before reporting completion - -Physical terminal status describes execution, not satisfaction of the requested task. Use terminal -status together with `deliverableValid`/`deliverableValidation`, required output evidence and the inner -agent's explanation. Do not rewrite native status or add a second continuation controller. - -| Observation | User-facing interpretation and next action | -| --- | --- | -| Queued/running, including unchanged file mtimes | Still working; poll at the supported interval. Do not cancel/replay. | -| Failed/canceled, or explicit access/security/environment failure | Blocked/ended without completing the requested output. Report the concrete reason and preserve the prior artifact. Do not infer a missing user requirement or regenerate blindly. | -| Clean terminal turn asking a genuine material question | User input needed; relay the question. A tool failure is not a question merely because no artifact was produced. | -| Successful execution, valid deliverable and all required output verified | Design output produced. Bind the preview and required-file snapshot now, then run the design-quality loop (below): evaluate the rendered result against the agreed references, brand and quality bar and commission concrete refinements until it is met — no fixed round cap. On the default path (existing approved design language, authorized scope) continue to implementation only after the bar is met and report the result, and preview-and-wait only for a new visual direction, a client deliverable, out-of-scope changes or an explicit review-first request. Implementation and human acceptance stay separate from the design verdict. | -| Explicit verification-only task, no tool failure, inspected existing output and reasoned no-change result | Review completed without changes. Identify the existing artifact; do not call it newly generated. | -| Terminal success but no valid required output, no justified question/no-change outcome | Output unverified/incomplete. Inspect the explanation and relevant diagnostic evidence; do not call it complete or attach an old project preview as this run's output. | - -If output exists but the run reports a failure, treat it as partial/unapproved until verified; output -does not override an execution error. A required theme/CSS file missing from an otherwise valid HTML -deliverable is still incomplete. An artifact manifest marked complete or an old preview returned by -project metadata cannot override an invalid run-scoped deliverable verdict. - -Project hygiene before commissioning: confirm the project has a meaningful declared entry -(`get_project` → `entryFile`). Without one, validation falls back to file inference, and a project -with ambiguous root HTML (for example two root HTML files) reports `entry_missing` for every run -regardless of the produced output — a project-config gap, not an output verdict. Declare the -canonical entry (`PATCH /api/projects/:id` metadata `entryFile`, preserving existing metadata keys) -or create the artifact entry through the design workspace. - -Recovery depends on the cause: clarify a real question, diagnose an environment blocker, or inspect -missing output. A lost start response uses the original request id/payload after querying the existing -run; a terminal environment failure must not be blindly replayed. After an authorized repair, determine -the supported continuation/retry action and authorization without inventing a resume mechanism. Concrete quality refinement — a new run carrying the prior artifact and a specific defect list — is the normal design loop, distinct from replaying a failed run. - -Normal polling should retain only run state, deliverable verdict, relevant error/question and usable -links. Pull full diagnostics only for investigation and the source bundle when context, approval -snapshot or implementation needs it. Keep internal identifiers in the task record, not product copy; -do not print raw event streams, credentials or provider telemetry in progress messages. - -### Design-quality loop (design-owned analysis, review and refinement) - -The simple flow is **scope/purpose delegation → design-owned discovery and -prioritization → design analysis/implementation → design-rendered review/refinement → -OpenCode functional verification → report**. -Design review is a design responsibility, not an optional OpenCode aesthetic -judgment or a user defect-finding exercise. Scale it to the task; no fixed round cap. -The bar is successful **representative user tasks**: information priority, -cognitive burden, mobile/desktop operation, cross-screen consistency and preserved -functionality. Tool use, skill names, a generic score or denser layouts are not -the outcome. Routine defects belong in the design-owned correction loop, not in -a repeated request for the user to discover or direct them. For the delegated -scope this loop is design-owned end to end: Build sets scope and constraints, -verifies function and integration, and routes remaining design defects back to -the design workspace rather than fixing or re-critiquing them itself. - -1. **Diagnosis belongs to the delegated scope.** For an existing screen or a vague - dissatisfaction report, the design workspace derives its own prioritized problem - statement from the actual current screens at the relevant viewports/states — a - dedicated read-only design audit stage is a supported way to do this, but Build - neither authors the defect list nor prescribes the fixes; the user should not - have to enumerate the problems. For a user-delegated redesign, the design - workspace resolves the visual direction from the design system, brief and - references and proceeds without a user direction pick; commission a - lightweight direction options run (2-3 distinct direction boards with a - rendered sample of the critical screen area) only when the user asks for - options or the brand identity itself is in question, and continue - non-dependent work while any choice is pending. -2. **Compact task contract.** Carry the agreed scope and purpose, the constraints - to preserve versus redesign freedom, actual source/components/tokens, - inspected references and accessible images, sanitized states/viewports and - expected targets. Define success at the **observable purpose level** — what the - intended user must be able to do or know, and what counts as failure — instead - of vague adjectives; leave the concrete problem list, layout solutions and - verification detail to the design workspace, and do not invent a universal - above-the-fold rule or arbitrary score. **Verify input access before - commissioning**: the execution environment must be able to read the references, - assets and actual source; if an input is unreadable, do not commission — block - and report the gap instead of running blind. - Verify the **render-review path** too: host browser tools are not automatically - available inside the design execution environment. Use either a verified inner - renderer or actual product captures handed to the design reviewer, with an - **actual image-consumption receipt** and source revision/viewports/states. - File access, PNG hashes, screenshot existence and reviewer prose are not enough: - verify the image-view invocation and model image input (or equivalent observable - rendered-input evidence). A source-only - audit does not satisfy rendered review. If neither path is available, block the - dependent visual stage and continue independent nonvisual work; do not install - browsers, restart containers, or substitute Build-side visual fixes implicitly. - - **Host capture handoff (when the design environment has no renderer).** Build - produces the captures mechanically — an evidence step, not Build-side design - review — and hands them to the design side: - - Serve the isolated checkout with the product's own fixture/test server and - verify the app's entry/login path actually renders before capturing — a - hidden or non-responsive control is often a fixture env/feature-flag gap - rather than a UI defect, so check the fixture flags and page state before - concluding; keep the fixture read-mostly and stop it afterwards. - - Capture with the product's prepared browser tooling at the task's viewports; - if the browser session reports "not open", reopen it before capturing, and - after a fixture restart verify the page re-initialized (the 2026-10-12 - validation needed a cache-buster query on the entry URL). - Write each shot into the project's evidence directory with pass and viewport - in the name (`validation-after-390.png`, `validation-r2-*`, …) so every round - stays distinguishable. - - Record a receipt per file: SHA256, viewport/state, and the source revision - (changed-file hashes); a reused "before" set keeps its original hashes and is - never presented as new evidence. - - Give the review run the expected hashes and require it to stop and report on - mismatch; keep the captures inside the project folder so the design side can - read them. - - For a quick local sanity check, view downscaled copies (e.g. ~680 px JPEG) - instead of loading full-size PNGs into a token-limited session; the design - reviewer always judges the originals. - - Re-capture after each refinement as a NEW file set — never overwrite the - already-reviewed captures; hash-bound evidence must stay addressable. -3. **Design execution.** Select the agent and task-appropriate skill from the live - `list_agents`/`list_skills`; record the selection per stage. The design execution - analyzes the product problem and edits actual UI source where access is verified. - Suitable installed choices may include `frontend-design`, `impeccable-design-polish`, - `design-review`, `plan-design-review` or `web-design-guidelines`; select by actual - content and task, not name alone. A small repair can render/review/refine in one - compact run; a substantive change needs the separate review stage below. -4. **Required rendered review, independent for substantive changes.** After the - chain's output is verified, commission substantive review in a **fresh evaluation - context**, not the generator's own approval. Verify a new, unseeded native - conversation/thread, without inherited implementation history or a fork. A - different run id or review skill name does not prove independence; use supported - conversation selection and verify the underlying thread. A small repair may - use a compact same-run observation/refinement loop, labeled as such. - Check **requested versus effective review instructions** too: project plugin - bindings can replace the primary skill body, even when a review skill was - explicitly selected. Verify the delivered full-body review instructions; do - not count a generator recipe or upstream-install stub as a reviewer. - Record the review receipt in the task record (the schema validated 2026-10-12): - fresh thread identity without a fork, the delivered-instruction markers and - composition you verified, the consumed-image SHA256s, the source hashes read, - the verdict, and the fixed/open defect list with its unverified items. - Feed it the real source snapshot, the **actual - rendered states** at the target viewports plus empty/error/loading states, - keyboard/focus, accessibility (axe where available) and motion/interaction - states, references and criteria; confirm the review actually read and visually - consumed those inputs. Inspect native transcripts when normalized daemon events - omit image calls; missing normalized events alone do not prove non-consumption - (the run's archived rollout — observed at - `/archived_sessions/rollout-*-.jsonl` — is the fallback - source for the actual tool calls and image inputs). This - applies equally to HTML artifacts and successful source-direct edits with an - invalid canonical-entry verdict. A report-only/plan-only stage is not UI output. - Missing files or render access remain unverified; do not claim a pass from a - manifest, run success, screenshot existence or a generic review score. OpenCode - checks the evidence and actual integration, not substitutes its own visual fix - or taste verdict. -5. **Concrete refinement and evidence.** Require the review output as a concrete - **defect list** — what is wrong, which screen/state, the user impact and the - desired result — tracked fixed/open across rounds, not a good/bad verdict. - Return findings with the prior source/render to design, then have design recheck - the changed states. In the existing ledger, keep a small table: - - | Criterion / defect | Design finding and requested change | Revision + viewport/state render evidence | Fixed / open | Functional check | - | --- | --- | --- | --- | --- | - - Measurable improvement means a criteria-linked defect demonstrably closed, - not lines changed, larger type or stronger color. Continue until the bar is met; - a full round with no measurable improvement stops as **quality unmet**, with - remaining defects and next options, not “complete, only user acceptance left”. - A genuine access/execution blocker or material decision is also a truthful stop. - Disclose render failures, unconsumed inputs and instruction conflicts. If active - rules limit rendering or forbid required post-write checks, the bar is unmet; - user custom instructions cannot override those rules. Do not silently change - the runtime, project binding, tools, model or permissions as a workaround. -6. **Engineering verification and report.** Direct UI source edits can occur - before quality pass but remain provisional. OpenCode verifies functional - integration/build/routing/permissions/persistence on the actual product and - returns presentation issues to design rather than editing them; nonvisual - bugs stay engineering-owned. OpenCode does not re-critique the visual result - or substitute its own design; remaining UI/UX defects return to the design - workspace with evidence. - Match the review evidence to the actual files/revision after later changes. - Distinguish generated, design-reviewed with evidence, functionally verified - and human-accepted; none implies the next. Keep existing direction/client/ - scope/review-first and representative rollout gates, and reachable preview links. - -This is an execution contract, not runtime enforcement. Notifier/unit/package -checks prove their own behavior, not aesthetic quality or agent obedience. Real-use -acceptance requires a product session's review/refinement and changed-flow evidence. -The notification's `renderInputAccess` and `renderReview` stay `unverified`: they -are reminders to inspect task evidence, not a detector that certifies a rendered -review from a daemon status or the inner agent's prose. - -**Resident product context (per project).** Keep one `product-context.md` at the -OD project root: brand tokens from the actual token files, component inventory, -screen↔file map, invariants (API/data/auth/permission contracts, one writer, no -unapproved brand change) and the review criteria pointers. Write it once when -registering the source project and refresh it when tokens/components/screens -change. Briefs and review runs read it first instead of re-explaining the product -each session; it supplements actual source inspection and never replaces it or -becomes an approval. Include a short recurring-defects/forbidden-patterns list -fed from review findings (for example, "decoration must not push the primary task -below the fold"), so later briefs do not repeat known failures. - -**First-cycle evidence (real-use validation).** When a product runs its first -design cycle under this flow, record in the task ledger: the review run id and -requested/effective instructions, native review-context identity and consumed-image -receipt; the defect list with closed/open counts; how a `blocked` outcome was -handled (deliberate continuation vs explicit scenario); rounds and elapsed time; -the delivered review bundle; and the functional checks. This is the flow's -real-use evidence — it does not replace human acceptance or prove design quality -by itself. - -**Delegation evaluation (standing frame).** Judge a delegated pass on: what it -discovered on its own; what it actually fixed and verified (revision-bound); what -remained open from the known problem set; how many user interventions it required -(routine decisions target zero); and whether it introduced functional regressions -(tests, routes, permissions, data). At the harness level the standing criteria -stay: (1) Plan/Build make the product, priority and completion judgments; (2) -roles and skills add real value without duplicate implementation — CE review -surfaces real defects, and the design workspace runs its own quality loop; (3) -long-term state stays in one canonical record with no competing source of truth; -(4) approved work finishes without user intervention, with the user judging -outcomes and product decisions only. - -**Run mechanics (observed).** A single request may expand into a plan→execute -run chain: stage 0 produces the direction and working material but no product -files; the strategy maps the next run (a plan stage with no files is not a task -result). The notifier follows mapped successor stages for the same session; -verify the chain mapping after a lost binding and judge the final output by the -actual source diff plus a real render check, not the intermediate completion. -Strategy outcomes may read `blocked` (`od_next_canonical_deliverable_invalid`) -for entry-untouched source-direct work; that verdict alone does not mean the -task failed. - -**Project bindings and the blocked outcome (observed 2026-10-06).** A folder -import is auto-bound to the `example-web-prototype` scenario and the od-next -strategy profile `prototype` (`strategyBinding.taskProfile`). The od-next -strategy supports only `prototype`, `ppt`, `marketing` and `hyperframes` — -there is no code profile, so do not plan to "rebind od-next to code". Under the -prototype profile, source-only edits with an untouched canonical entry are -flagged `od_next_canonical_deliverable_invalid` and the strategy outcome becomes -`blocked`; that outcome can stop automatic continuation, so continue the next -stage deliberately from the actual source diff/render evidence instead of -reading `blocked` as task failure or completion. Supported alternatives: start -the run with an explicit scenario plugin — `od-code-migration` (patch-edit ↔ -build-test devloop, diff-review handoff; inputs `repoPath`, `targetStack`, -optional build/test commands) or `od-design-refine` (direction picker → -patch-edit → critique loop → handoff) — which stamps an `explicit_user` -scenario binding for the project; `POST /api/projects/:id/scenario/restore-automatic` -reverts to the automatic scenario. Whether the strategy evaluation changes under -an explicit scenario is not yet verified in this environment — verify it in the -actual run and record the result. - -**Instruction/context caveats (verified 2026-10-10).** A bound plugin can replace -the primary review skill body: the project's own explicit `od-design-refine` -binding is what supplies the replacing body, so re-selecting that same scenario -is not a remedy. Additional composed skill blocks are appended by the audited -composition branch after the plugin body; whether a requested extra skill is -actually delivered must be verified in the run's effective prompt, not assumed -from the request. The MCP commissioning path reuses the project's earliest -conversation, so a new run is not a fresh reviewer; prior implementation and -review executions were observed to share one native conversation. The supported -conversation endpoint can create an unseeded conversation for a scoped read-only -review — pass its identity explicitly and verify a new native thread without a -fork parent. Ask/chat mode has demonstrated local image viewing on a captured -file; it does not by itself provide an independent production verification loop, -and other run paths' write/verification policies were not exercised here. No -automatic rebinding, runtime patching or browser install follows from these facts. - -**Writeback degradation (verify with the actual file change, not a status field).** -A source-direct run must show its claimed edits as real changes in the connected -product checkout (file paths in its output, actual diff/build/render evidence). -Do not use `workspace.storage.kind` alone as the regression signal: on 2026-10-06 -the `od-owned` field correlated with a degraded state, but on 2026-10-11/12 runs -that provably edited host product files (mtimes inside the run window; the -validation diff itself) also reported `od-owned` with `baseDir: null`, so the -field alone currently distinguishes nothing for MCP-started runs. Treat a run as -degraded when its output claims edits that are absent from the folder, when it -says it cannot write outside the OD storage, or when a small write probe fails — -then record the deviation (run id + observed behavior) in the task record, report -it, and resolve it or get a user decision — do not silently switch to -transcribe-and-copy. Transcribing stays a legitimate fallback only when direct -access is confirmed unavailable, and the adopted changes must map to their OD -source. - -**Skill selection (observed).** The catalogue advertises more skills than have -full bodies — some entries are stubs pointing at upstream bundles. For real -product UI prefer full-body skills (e.g. `frontend-design` covers dashboards -and application screens); `design-taste-frontend` / `gpt-taste` are explicitly -landing-page/portfolio skills ("not dashboards, not data tables"). Many design -skills declare `designSystemRequired`; without an attached design system, -proceed with the repository's own tokens as the brand contract and record the -choice. Verify the skill exists in the installed list before passing it. - -Observed working selections (verify against the live list before use): product -dashboard/app UI → `frontend-design`; polish pass → `impeccable-design-polish`; -design review/critique → `design-review` / `plan-design-review`; motion after the -interface exists → `emilkowalski-motion`; compliance/guidelines → -`web-design-guidelines`; landing/portfolio taste → `design-taste-frontend` / -`gpt-taste` (unsuitable for dashboards). Record the per-stage selection in the -task ledger so working choices accumulate across sessions. - -**Design system attachment (candidate, verify first).** The daemon supports a -project design system id; attaching one built from the product's tokens may -improve `designSystemRequired` skill coverage and cross-screen consistency. -Treat it as an experiment: verify with a real run, record the outcome, and only -then standardize. - -**Pipeline artifacts.** Strategy runs may leave working material in the -project (e.g. `.od-frames/`). Treat it as untracked pipeline output: keep it -out of product commits and clean it up after the pass unless the design tool -still references it. - -### Completion notification (host-side notifier) - -The optional `plugins/design-notifier/` component closes the loop from the other direction: when a -design run reaches a terminal state, the originating session receives one advisory notification -instead of the user having to ask again. Verification, interpretation and any implementation stay -with the receiving agent and the user; the notifier only observes and notifies. - -- Binding is recorded when a session calls `start_run` (request id + session), before the response - can be lost; the returned run id is attached afterwards and the record becomes - `run:@`. The tool-name prefix is configurable (`toolNamePrefix`, default - `opendesign`); `design_runs list` exposes `observedTools` for diagnosis. -- The notifier polls the local daemon (`GET /api/runs/:id`, default 60 s) through an **elected - poller**: every location instance captures its own sessions' bindings, but one instance holds - the state-lock leader lease and does the polling/delivery, so the daemon is not queried per - location. Overlapping ticks during a slow-tick lease handover are possible and are fenced by the - delivery claim. Delivery admits one message per run/session through the native session synthetic - inbox (deterministic `msg_...` id); a state-lock claim serializes concurrent instances and a - re-send of the same id is idempotent at the session. -- **Wake permission is per session.** An unpaused session receives the completion with auto wake - (`resume:true`, `delivery:queue`). A paused session is not contacted at all: the terminal result is - stored as `held` in notifier state, and `resume` makes it eligible for auto wake on a later poll tick, subject to existing delivery retry delays and any remaining pause switches. -- The message requires verification with `get_run` (preview URL and `agentMessage`), never claims a - deliverable when `deliverableValid` is false, forbids blind regeneration, and defaults to finishing - the job (implementation plus a result report with its preview) when the change follows the existing - approved design language and authorized scope; it previews-and-waits only for an unapproved new - direction, client delivery, scope change or review-first request. Any verified UI output, - including canonical-entry-invalid source edits, requires design-owned rendered review/refinement; - `designQuality: "not_assessed"` never declares a quality pass. Intermediate stages are non-final - and mapped successors stay watched; report-only stages do not authorize new design work. - Source notifications distinguish the daemon's artifact verdict from **required product verification**: - `entry_not_touched` on a succeeded source-direct run is not automatically product failure or success; - inspect the actual diff/build/changed-flow checks. Read-only reports are not design deliverables. - A representative visual-system result requires the single user review before wider rollout, even when - separate plan pre-approval was unnecessary. Failed/canceled executions are never upgraded by this rule. - Existing pinned runtimes must be explicitly updated after verification to receive these source changes. -- Policy: idle session → `queue` + auto wake; busy session → `queue` (no interruption); paused - session → `held` (no session contact until resume); deleted target session → `orphaned`, never - delivered elsewhere and never replaced by a new agent; daemon-missing run → `missing` (kept for - diagnosis, no delivery); lost start response → reconciled by `clientRequestId` for up to 24 h - without starting a replacement run. -- State is durable under `~/.local/state/opencode-design-notifier/` (atomic JSON bindings, - `paused.json` per-session/global switches, `leader.json` lease, bounded `events.log`); restart - recovery re-reads it and does not resend delivered runs. - -Install the pinned runtime copy with `node scripts/install-design-notifier.mjs` (writes -`~/.config/opencode/plugins/design-notifier/` with a SHA256 manifest; `--verify` detects drift) and -remove any `plugins` config entry that points at the development tree so only the pinned copy loads. -The `design_runs` tool lists tracked/held runs (with leadership and pause state), re-watches an -existing run as a recovery path, and pauses/resumes wake permission for the calling session -(`scope:"all"` for every session). Limits: same-host daemon reachability, up to one poll interval of -latency after a finish or resume (subject to existing delivery retry delays), leader takeover within the lease TTL after a crash, overlapping -ticks during handover are fenced by the delivery claim, a pause racing an in-flight delivery takes -effect on the next tick, and no knowledge of goal pause state other than the explicit switch. This -component is not part of the npm package and does not install anything by itself. - -### Plan-only design MCP permissions - -Native Plan's file-edit restriction is not a deny rule for separately named MCP calls. The optional -`agents.plan.permissions` block in [the configuration example](../../opencode.jsonc.example) first denies -`opendesign_*`, then allows the explicitly known read tools. Future unknown tools stay denied. This -blocks generation, file/project changes, cancellation and interactive brief mutations in Plan while -retaining source/status discovery. Build's permissions and native system prompts are unchanged. - -Adopt the block explicitly and merge it with existing Plan rules; do not replace unrelated settings. -Use the actual normalized MCP server prefix, and inspect effective configuration in the actual project -because later project/agent rules may override it. Code Mode checks nested tool permissions too. -Before adoption, run `opencode mcp list` from the actual project and compare the server name with -the normalized action names in its tool catalog. After a server rename, update the rule prefix and -evaluate reads/mutations/unknown actions for that **actual** prefix again. The isolated sample verifier -checks `opendesign_*` only; passing it does not prove a differently named live connection is protected. -The installer does **not** apply this policy or modify JSONC. This is a direct design-MCP boundary, -not an OS sandbox, a blanket shell/subagent restriction or a guarantee about other read-only roles. - -`node scripts/verify-native-v2-isolated.mjs` evaluates native Plan read/mutation/unknown-tool decisions, -Build generation permission and native Plan edit denial in an isolated server without design tools or -provider calls. Test live adoption with native catalogs/evaluation, not by attempting a real deletion -or design run. Startup without credentials/MCP and unmonitored child egress remain disclosed limits. - -## Reference research and brief transfer - -OpenCode owns the existing reference-research integration; OpenDesign's local execution owns design synthesis. Use focused Mobbin screen/flow/section research before a new screen, material redesign or unresolved pattern when it can inform the task. Do not repeat research for implementation of an already-approved screen, component reuse, small copy/spacing changes or backend work. Respect explicit user requests, supplied references and project constraints. Routine searches/selection inside the authorized design brief do not need a separate user approval for every query. - -Select references based on actually inspected images, not app names or metadata alone. Retain canonical Mobbin links and record the specific hierarchy, interaction or state pattern to borrow; do not copy brand assets or product copy. When an image must be embedded, saved or passed into design, use the permitted high-resolution image source rather than the low-resolution inline preview. Image URLs expire; keep permitted selected files in a task-owned reference location when needed. References supplement existing tokens/components and human-approved direction, not replace them. - -Send the same agreed requirements/constraints plus this compact reference input in the design brief: - -- target screen/flow and the user task; -- selected canonical links and actually accessible image files (or supported attachments); -- the useful pattern in each reference and what must not be copied; -- existing product tokens/components, required empty/loading/error/mobile states and out-of-scope changes. - -Verify that the design execution environment can read those files and interpret the image input. A host or client-PC path, external URL, or screenshot filename alone does not prove access or vision support. Use the target project's supported attachment/file mechanism or verified shared reference location; do not expose the home directory or copy authentication stores. An OpenCode MCP connection is not automatically inherited by the design run. Configure an additional inner connection only as a separate authorized change, not as a prerequisite to this reference-transfer workflow. - -If Mobbin is absent, unauthorized or unhelpful, report the limitation and use provided references/established patterns where sufficient; do not fabricate results or block every design task by default. If essential design direction remains unresolved, return that decision to the user. Missing local design-execution capability is a separate blocker and does not authorize silent substitution with a UI-reference skill or OpenDesign Cloud. - -## Handoff requirements - -### Product-aware frontend improvement - -OpenDesign's local execution can improve an existing product's UI; a separate design folder is not -proof of product-source access, and a design artifact is not an automatic product -integration. Build supplies the actual implementation context so design work does -not become an unrelated mockup that engineering has to redesign. When verified write -access to the actual product working directory exists, prefer the direct -product-source path below over copying sources. - -Use this existing-task flow: - -1. Identify the user task, observed problem, affected screen/states and approved - scope. Inspect the relevant product source and current rendered evidence; - distinguish observed defects from assumptions. Reuse existing requirements. -2. Assemble the minimum necessary product context below. Use already authorized - file/attachment mechanisms; verify the design environment can actually read - the inputs. Do not infer accessibility from host paths or project metadata. -3. Confirm the output format supported for this target. Request product-compatible - UI components/styles where supported, preserving existing interfaces and tokens. - A prompt asking for React/TSX or any framework is not evidence of that capability. - If only a standalone HTML/JSX/CSS artifact is supported, state the adaptation - needed before the run and constrain it with the same product contracts. No - promised ready-to-merge component without actual file/execution evidence. -4. Commission the existing refinement or new-artifact path, preserve canonical - entry/output requirements, validate the real files and rendered states, and - retrieve the complete required bundle for integration. A prototype fixture - is not a functioning product API/auth/save flow. -5. Build integrates the output faithfully without visual redesign, connects real data/routes/events and - verifies the actual changed product flow. Compare design and product at relevant - viewports/states; return visual deviations to design for review/refinement, including small repairs. - Keep the default finish-and-report path and existing direction/review-first gates. - -#### Minimum product context - -| Input | What Build supplies | -| --- | --- | -| Identity and revision | Actual product repository/worktree/revision, target route/component paths, design project/storage/entry; pending changes that affect the input | -| Product purpose | User task, observed problem, accepted outcome and exclusions; do not invent objectives from screenshots | -| Relevant source | Screen/components and necessary imports/styles; for multi-screen work, a screen/file map; existing reusable patterns, tokens, fonts and permitted assets | -| Framework constraints | Actual framework/conventions, component boundaries and available trusted render/check commands; preserve dependencies unless separately approved | -| Integration contracts | Props/types, data shape, route destinations, event/callback semantics, permission/disabled behavior; credentials and live private data excluded | -| State evidence | Relevant wide/narrow captures and sanitized empty/loading/error/stale/permission fixtures; distinguish observed and proposed states | -| Output ownership | Required files/format, canonical renderable entry and permitted output paths; product integration owner and immutable input/output snapshot | - -Use selective verified read-only access or minimal task-owned source copies under -existing authorization, not a full-repository/home-directory export. Remove secrets -and private customer data before transfer. If required sources cannot be accessed, -report the specific gap and continue unrelated work; do not silently claim the design -run used the product or reconfigure mounts/credentials/security to make it work. - -#### Compact refinement brief - -Populate this in the existing task record/run request from observed facts; it is a -prompt convention, not a new MCP schema or automatically enforced manifest. Reuse -known values instead of interviewing the user again. New-artifact intake still follows -the existing brief/confirmation rules. - -```text -Target: product worktree/revision + route/component; design project/storage/entry. -Purpose: user task, observed problem and agreed acceptance criteria. -Input: relevant source/tokens/assets/captures/fixtures with verified accessible paths. -Preserve: functionality, component contracts, data/event/route/permission semantics and agreed brand. -Redesign freedom: scoped hierarchy/layout/component/interaction presentation and states/viewports; - do not freeze a rejected layout or change business rules. -Produce: supported UI files/styles, renderable canonical entry, required siblings/assets, - component-to-product mapping, integration instructions and known limitations. -Ownership: design owns delegated UI source/styles, rendered review and visual refinement; - Build owns nonvisual integration/contracts, functional verification and Git. - One writer per file, sequential handoff if UI and domain changes share a file. -Verify: identify inputs actually read, changed output paths, rendered states/checks performed, - criterion/defect -> revision/render -> fixed/open evidence and access/output gaps. - A success status alone is insufficient; report an unmet bar honestly. -Exclude: product API/data/auth/Git/deployment changes, new dependencies or unrelated screens. -``` - -Output must describe reuse/adaptation of existing product components, required -imports/assets and synthetic fixtures, and what remains for real data/event wiring. -For an artifact task, preserve the canonical entry expected by the output contract; -component siblings alone do not prove a valid artifact. For source-direct work, -verify actual UI source and product renders; do not fabricate entry edits merely -to change an entry-centric verdict. If the supported output cannot -satisfy a required state or implementation contract, surface that gap rather than -silently lowering the contract or launching another generation blindly. - -#### Faithful integration and feedback - -Build may make nonvisual/mechanical production adaptations within the agreed design contract, -but does not invent a different layout, action hierarchy or visual system to make -integration easier. Preserve accessible semantics, tokens, responsive ordering and -empty/loading/error/permission behavior. Record artifact-to-component mapping, -actual required-file hashes and necessary differences in the existing handoff. - -Compare the design preview and the real product at corresponding relevant states -and viewports. Verify actual navigation, callbacks, keyboard/focus and persistence -where changed. Visual deviations and repairs return to design with the exact -source snapshot, product capture and constraint; nonvisual/mechanical adapters -do not require another design run. Recheck after integration fixes. This is a scoped feedback loop, -not automatic synchronization, recursive generation or mandatory pixel-perfect scoring. - -#### Direct product-source work: verified, simplified preferred path - -Source-direct frontend work is verified in this deployment: a probe run imported an -existing host folder (`metadata.baseDir`), edited the actual React TSX/CSS in the real -working directory, built it and produced a renderable canonical entry; Build then -independently verified the diff, build output and rendered interaction -(`docs/superpowers/plans/2026-10-05-source-direct-feasibility.md`). Prefer this path -for real product frontend work over mockup-then-reimplement. - -- Point the run at the actual product repository — the real working directory, a - dedicated branch, or the user's normal checkout. A dedicated branch keeps a - review/rollback point and is the default suggestion; the user's checkout is - acceptable when they prefer it (record the base revision before substantive - writes). A separate worktree is optional convenience, not a requirement: use one - when the user's working tree must stay untouched or concurrent writers would - otherwise collide. -- Keep one writer at a time on the delegated UI paths. OpenDesign's local execution owns the UI source - edits; Build owns requirements/contracts, actual verification, integration and Git. - Sequential handoff is the default; concurrent writers need disjoint files and - separate workspaces (a worktree keeps the checkouts separate). -- Real boundaries remain: home/credential roots and unrelated projects stay out of - agent access (the gateway refuses them as selectable new projects; connected-root - scope is still prompt-scoped, not an OS allowlist — see the last bullet); new root - connections keep their own scoped approval, active-run protection and rollback - scope; API/data/auth, dependencies, Git history and push/deploy changes are not - delegated implicitly. -- For source-direct runs, completion evidence is the actual source diff plus a real - build/render check of the changed flow. The deliverable validator is entry-centric: - when the canonical HTML entry is intentionally untouched, `entry_not_touched`/ - invalid can be the correct physical outcome — do not fabricate an entry regeneration, - and do not treat that verdict alone as task failure. Strategy pipelines may additionally - mark the task `blocked` (`od_next_canonical_deliverable_invalid`) for the same reason; - the same source-diff + render evidence governs. Build's verification of the real - product remains the authority for "done". -- Run scope is prompt-scoped, not an OS sandbox: the inner agent's tools span its - connected root. Keep sensitive non-product material out of connected roots. - -#### Reuse the representative product project - -Default to **one representative OpenDesign project per product**, not one per -screen, task, session or Git worktree. Keep its exact project identity, actual -source workspace, design storage/entry, brand and reachable product URL in the -existing Product binding record. A repository may contain several products; a -folder name, title substring or Git root alone does not establish identity. - -Before commissioning, read the recorded representative and compare an actual -project read with the intended source directory/revision. Reuse valid same-target -evidence, and refresh it after source/binding/access changes. Missing, stale, -multiple or unreadable identities are unresolved bindings, **not authority to -create another project**. Normal tasks use files/artifacts and conversations inside -the representative project. Do not make the user administer IDs or folder choices. - -A worktree change does not by itself create a new product. Rebind the existing -project only when the installed capability is confirmed, no active writer/run -depends on the old binding, and the intended source change is authorized; re-read -the result. Rebinding does not migrate older folder files/artifact paths or make -past snapshots current. Genuine concurrent work may use a temporary project with -an explicit owner, source, reason and return/retention plan in the same record. -Preserve historical projects; naming, cleanup, merging or deletion are not automatic. - -The optional source-checkout doctor snapshot comparison is described in -[execution guidance](../execution.md#reuse-project-context-inspect-status-and-preparation). -It is metadata validation, not daemon-wide uniqueness enforcement or live access -proof. A new project is appropriate for a genuinely new product, or after a -successful inventory establishes no existing representative and current task -authority permits creation; a failed lookup never establishes absence. - -#### Commissioning preconditions and the design quality loop - -Before commissioning, confirm and record the three project-level conditions in the -existing task record; never assume them from a project name, a screenshot or an -earlier session: - -1. **Real-source write access** — the run can edit the actual project source path; - recheck inner file-access evidence after relevant changes or failures. -2. **Completion handoff** — Build can detect completion (notifier or an explicit - polling fallback) and take the result back in the originating session. -3. **Continue from the actual source** — integration and verification use the - modified revision (diff/build/changed-flow), not the preview alone. - -A missing or unverified condition is a blocker report, not a silent fallback; -independent nonvisual work continues meanwhile. - -OpenDesign owns its internal analyze → implement → render-review → refine loop and -returns its fixed/open defect evidence. OpenCode does not re-run that visual review: -review seats validate that it happened on the actual revision and review the -engineering concerns (functional contract, security, regression, integration). -Send the goal, change scope, preserved functional/API/data/permission contracts and -completion criteria; OpenDesign makes the internal design/implementation decisions -and returns changed screens/files, render evidence (fixed/open), functional-contract -impacts, unresolved items and evidence locations. - -#### Registering a source project (this deployment) - -First apply the representative reuse/source checks above. Only when registration -or an authorized source rebind is genuinely needed, use the remote-workspace flow; -the deployed daemon accepts a path inside a **connected project root** — a -root's canonical subdirectories (its worktrees included) are covered: - -1. **Connect** the project folder — a genuinely new root recreates the design - container once, with automatic rollback; a path already covered by a - connected root returns `ready` without recreation: - `POST /api/remote-workspace/connections` - with `{ "path": "", "requestId": "" }` through the - local gateway (or the in-container CLI `od project open-server - --gateway-url --json`), then poll the operation until `ready`. - The connector refuses while any design run is non-terminal. Any existing - directory under home is selectable except the home root and the credential - directories (`.ssh`, `.gnupg`, `.codex`, `.claude`, `.gemini`, `.config`, - `.local`). -2. **Reuse/rebind before import.** If an existing representative already points - at that exact source, keep it. For an authorized idle-source change, use the - installed supported rebind path and verify the same identity/new directory. - If the capability or ownership is unresolved, report the binding issue rather - than silently importing a duplicate. For a genuinely new product only, **import**: - `POST /api/import/folder` with - `{ "baseDir": "", "name": "" }` (or - `od project import-folder`). The daemon verifies the connected path - server-side — a browser-reported "connected" flag is never trusted — and - sets `metadata.baseDir` plus the detected `entryFile`. - -Agents perform needed connection/registration steps, not a fresh import per task; -this is a standing capability for eligible projects under home, not a per-project -user decision. Reuse does not bypass exact-source verification or active writers. -Never mount a broader parent directory as a shortcut: the boundary is the -project root itself (its canonical subdirectories are covered, not the reverse), -and this deployment deliberately avoids -Desktop/home-wide mounts. Keep one writer at a time. After import, check the -project's `metadata.scenarioBinding` / `strategyBinding` and record them in the -task record; a folder import normally carries the automatic prototype binding -(see Run mechanics for the source-direct blocked outcome and the explicit -scenario alternatives). For source-direct work, record the chosen scenario path -(automatic prototype + deliberate continuation, or an explicit scenario plugin) -and create or refresh the resident `product-context.md`. - -#### External OD export intake (archive from another machine) - -When the user hands over an OpenDesign export (a project/artifact zip produced on -another PC): - -1. Extract it to a task-owned location **outside the product repository**; keep the - original archive out of commits and never leave it in the repo root. -2. Inventory what it contains (artifacts, version history, plans, references) and - record an **adoption map** in the task record: which version/artifact will be - implemented, what is reference-only, and what is rejected. -3. If OD runs must reference the export, place a copy **inside the connected project - root** or import it as a project — runs cannot read paths outside their connected root. -4. Confirm the intended direction with the user only when the adoption choice is - materially ambiguous; otherwise proceed and report the map with the result. - -#### Multi-screen visual-system pass - -When several screens show accumulated drift (patch-built layouts, an -exception-heavy theme, missing shared components), fix the system before the -screens instead of continuing per-screen patches: - -1. Have the design workspace audit the screens and map each to its files; inventory existing tokens and - components. -2. Have design define tokens plus shared components (card, panel, button, stats, empty - state) with spacing and type scales. -3. Have design implement and render-review one representative screen in the real - source, refining criteria-linked defects; OpenCode verifies evidence, build, - tests and actual changed-flow behavior. Direct edits remain provisional until verified. -4. Present the definition, the scope and the representative screen's real - result as one review. For internal tools that keep the existing brand, this - single review replaces a separate plan approval; obtain pre-approval before - step 3 only for a new visual direction, a client-facing deliverable, an - out-of-scope change or an explicit review-first request. Where a gated - approval is used, its snapshot binds the system definition, not a full mockup - set. -5. After the user approves the reviewed representative result, roll out screen by screen with per-stage checks, without - per-screen user approvals, and report once at completion; finish with a full - build/test/render pass on the whole app. - -Do not big-bang rewrite every screen at once. Keep functionality, routing and -data unchanged, and keep one writer at a time on the UI files. This is the -preferred recovery when prior work left the product without a unified visual -language. - -When asking OpenDesign for a change, state: - -- the target project and entry file, -- the user task and the screen or flow it affects, -- the states that matter (empty, loading, error, stale, mobile), plus an edge-state class checklist in the first brief (dialog/modal interplay, deep-scroll positioning, missing header/anchor states) so one iteration covers defect classes instead of per-defect round trips, -- existing product tokens, components, and constraints that must be preserved, -- what is explicitly out of scope. - -When bringing a design back into production code, report: - -- which artifact and version was implemented, -- known differences between the artifact and the production implementation, -- what was verified (build, tests, rendered check) and what was not, -- that the artifact is not user acceptance. - -### Minimal snapshot handoff - -Keep a small record in the existing task ledger/design decision, not a new central orchestration service. The record below is an operating convention, **not** a native MCP response schema or an enforced API. Do not add duplicate authoritative state files for every handoff. - -| Field | Record | -| --- | --- | -| Target | Product repository, intended screen/flow and verified design project/storage mapping | -| Source | Project/artifact identity, relative entry and required files; source preview as a locator | -| Snapshot | Actual SHA256 of each required file's retrieved bytes; identify any referenced file/asset not included | -| Direction approval, when gated | The user's actual approval evidence tied to that snapshot; missing required approval remains pending. On the default path, record the verified snapshot with the result report instead of adding a gate | -| Design contract | Required states, responsive rules, existing tokens/components and allowed differences | -| Open decisions | Material questions still unanswered; absence must not be invented as approval | -| Implementation evidence | Product revision, actual checks/rendered states, differences and unverified items | - -Request the source bundle once when implementation needs it; use explicit project/entry if default selection is absent or ambiguous. Check truncation/skipped files and follow up only for missing required source/assets. Preserve retrieved source bytes when hashing; if only decoded text is available, record its encoding/reconstruction and do not describe the hash as a raw binary-file hash. Binary references, CDN URLs and external dependencies may need separate retrieval/access checks. Never infer that every referenced asset is present just because a bundle request succeeded. - -User direction approval may happen in the design workspace or conversation. On the default path (existing approved design language, authorized scope) no separate pre-implementation approval is required: record the verification snapshot with the result report. When the change was gated (new visual direction, client deliverable, out-of-scope or explicit review-first request), bind the agreed artifact to the captured snapshot at approval; if files changed or cannot be matched to what was approved, mark that uncertainty and obtain a decision rather than invent provenance. Material subsequent changes require updated direction approval; routine production adaptations within the approved contract can be documented without per-file confirmation. A manifest's schema version/status, execution success, mutable preview URL or old screenshot does not identify an immutable approved revision. Fetching source for implementation does not itself approve it. - -### Product verification and feedback - -Before reporting overall technical completion, reconcile required design work, -validated output/snapshot, product integration, changed-flow verification and -unresolved differences with the agreed acceptance criteria. Missing required design -or checks remain engineering work/blockers, not “only user confirmation remains.” -Tests/review findings cover their inspected scope, not design quality. A design-run -failure or unsupported entry requires cause-specific recovery, not substituting -Build's own design to save time. Never regenerate solely to tick a process box; -a verified no-change result can preserve the implementation with its evidence. - -The design preview is for direction, composition and prototype states. The actual product server is for authentication, API integration, saving, permissions, routing, error handling and the production component/responsive behavior. Keep both responsibilities: a working prototype button or screenshot does not prove real functionality. No API/backend change is authorized by a design artifact. - -Give the user two clearly labeled, actually reachable links when applicable: **Design preview** and **Implemented product**. Preserve the product server; do not migrate or duplicate its runtime inside the design workspace merely to consolidate viewing. Resolve URLs for the actual client/network; daemon-returned localhost URLs are not automatically browser-client-PC URLs. Report inaccessible/uninspected states rather than claiming verification. Standard review bundle: the reachable implemented-product link (a temporary tailnet-bound static server is acceptable for a static product — label it tailnet-only and temporary), before/after screenshots of the changed screens at the target viewports, and the design preview link. Screenshots inform the review; the product link remains the functional-verification surface. - -For an existing application, the practical default is opening its verified product -URL in a normal browser tab, retaining the product's own origin/session/API behavior. -The entry may itself be called `index.html`; serving it from an artifact/file panel -does not reproduce the product server or authentication. A static preview cannot -substitute for login, authorization, saving or reload-persistence checks. If framing -is blocked by browser/auth/mixed-content restrictions, use the top-level product -link; do not weaken the artifact sandbox, add a credential-forwarding/unrestricted -proxy, start a duplicate runtime or switch silently to demo. Unreachable product -links stay explicitly unavailable. Opening/testing/changing data still requires -the current task's authority and safe test state; storing a URL grants none. - -Build can return task-scoped product captures, observed differences and constraints for design feedback, excluding credentials/private data. Feedback may propose design revisions or ask for product information; Build adjudicates and implements in its authorized scope. An important redesign goes through the existing direction-approval boundary again. This loop is explicit feedback, not automatic synchronization or execution delegation back into the engineering session. - -## Boundaries - -- An approved OpenDesign artifact is the design source of truth for that screen. It does not authorize - backend, data, or scope changes. -- Do not create a parallel design system inside OpenCode. If a project has tokens or a design system, those - sources remain authoritative. -- A generated artifact is not design approval, and a passing test is not user acceptance. The Human owns both. -- Do not claim a design run, file read, or rendered inspection that did not actually occur. - -- OpenCode owns the development conversation, production changes, verification and Git. OpenDesign's local execution is the design specialist. An internal coding runtime used for design is not authorization to launch/resume the outer engineering session, recursively commission more design runs, or change product API/data/Git/deployment. -- Keep task-owned design/reference outputs and product writes under explicit ownership; never let concurrent design and Build writers modify the same product files. With verified source access, prefer direct edits in the actual product repository (one writer at a time); without it, keep separate design-output locations with existing product sources read-only where practical. An imported writable project root may technically permit such writes: operating guidance is not a filesystem sandbox. Do not silently move/copy the user's project or reconfigure mounts. - -## Optional DGX remote-folder gateway (source-only) - -`integrations/opendesign/remote-workspace/` is separately approved host infrastructure, not part of -the KSI installer. It browses the host home directory, connects explicitly selected canonical -project roots as same-path writable binds (a connected root covers its canonical -subdirectories), then uses existing folder import/working-directory APIs. -Home and credential/configuration roots are not selectable projects. This is not an OS sandbox: -connected roots share the same container and account. - -Deployment requires a revision-matched daemon/web overlay, a private configuration using -`config.example.json`, and the user service example. Pin the installed immutable base image when -building `Dockerfile.overlay.example`; keep the original image and all effective Compose inputs for rollback. -The overlay must run with `OD_REMOTE_WORKSPACE_GATEWAY` and the private `OD_REMOTE_DAEMON_TOKEN`; -omitting the gateway variable intentionally restores native local behavior and removes remote connected-root checks. -Keep tokens in a private mode-0600 environment file, not in JSON, logs or source control. - -Validate source checks before installation: - -```sh -npm test -# Explicit opt-in: isolated temporary Compose container, never the production service. -OD_REMOTE_DOCKER_SMOKE=1 node --test integrations/opendesign/remote-workspace/tests/connector-docker-smoke.test.mjs -``` - -CLI parity in the patched application: - -```sh -od project browse-server --gateway-url https://YOUR-TAILNET-HOST:7456 --json -od project open-server /home/YOU/projects/PROJECT --gateway-url https://YOUR-TAILNET-HOST:7456 --json -``` - -The gateway listens on loopback; expose it only through the approved tailnet-only Serve mapping. -Tailnet membership is the remote trust boundary, not knowledge of a Host header. Only the exact -read-only Labs GET has remote-to-loopback authority translation; normal privileged routes retain their guards. - -A new root can recreate the shared container. During connection, generation is blocked container-wide; -recreate/health waits can each take 120 seconds, and rollback can extend the outage. Existing active or -unverifiable runs prevent recreation. Polling remains on the stable gateway; duplicates join the operation. -Root paths are checked before and after recreation. This detects changes and rolls back, but cannot provide -an atomic filesystem/Docker bind transaction against a hostile same-user process racing path replacement. - -On failure, inspect the operation's rollback result. For operator rollback, restore the backed-up image -override together with the prior project override, recreate only the fixed service, verify health, then -restore the previous gateway configuration/Serve target if needed. Preserve daemon volumes, provider config, -agent mounts and unrelated Serve/Funnel routes. Never use `down -v` or broad cleanup as recovery. -Only container-local stdio (for example, `docker exec`) terminates with container recreation. If that -connection closes, reconnect through `/mcps` and verify a real read. The gateway does not manage MCP; -the host-side proxy below survives container recreation and does not need reconnection merely because -the daemon restarts. Reconnect only if the actual client transport has closed. - -### Host-side MCP continuity - -Use the existing upstream `runMcpStdio` HTTP proxy **on the host**, not under `docker exec`. -`mcp-host-entry.ts.example` is a small deployment shim, not a new protocol implementation. -Build it with the exact daemon source revision and installed dependency closure, bundle for Node, -and place the generated bundle in a persistent private deployment directory (not a temporary worktree). -Pin the loopback daemon URL and keep the approved idle-exit setting at zero. - -On DGX the host bundle is `~/.local/share/od-mcp-host/proxy.cjs`; the existing MCP command now uses host -Node instead of Docker. Only that command/comment changed; unrelated configuration and credentials -were preserved, with a private backup. Container restart no longer terminates its stdio connection. -Calls during downtime return an error; after health recovery the next call works on the same connection. -**No write, upload, or generation request is automatically replayed.** Interrupted actions still need a -conscious retry, using the original request ID for generation when appropriate. - -A host proxy crash or host reboot still requires normal client reconnection; this is not universal -process supervision. Restart/idle test results and limits belong in the -[technical closeout ledger](https://github.com/khakisketch/KSI-Opencode-Harness/blob/main/docs/superpowers/plans/2026-10-01-remote-workspace-closeout.md). - -### Attachment behavior - -The chat attachment input uses `/api/projects/:id/upload`; a request to the separate `files` endpoint -alone does not exercise that UI. Verify saved bytes in the selected project, visible upload errors and -conscious retries; do not automatically replay uploads. Reference-file access is separate from model -interpretation accuracy or support for every format. Check actual outputs rather than terminal status; -technical JSON reports are not rendered design artifacts. Fixture results are in the closeout ledger. - -### Local execution nested sandbox on Docker - -The local design execution agent uses bubblewrap. Docker's default seccomp denies its user-namespace creation; -the default AppArmor mount denial also blocks its nested read-only filesystem setup. A healthy -daemon or terminal run status alone does not prove shell tools can execute or save files. - -The optional `ksi-codex-userns.apparmor` and `seccomp-policy.mjs` record the separately approved -Ubuntu arm64 repair. They are **not automatically installed** and require host security review. -Start from a reviewed, pinned Moby default-deny seccomp profile; the generator preserves its rules: - -```sh -node integrations/opendesign/remote-workspace/seccomp-policy.mjs reviewed-default.json new-codex.seccomp.json -``` - -Use only on the fixed application container, with **all outer capabilities dropped**, no-new-privileges -retained, and AppArmor enforcement enabled. The added calls permit nested namespaces and bubblewrap -mount operations; they do not grant host CAP_SYS_ADMIN. Preserve proc/sys restrictions and remaining -default-deny syscalls. Do not use privileged mode, unconfined profiles, host sysctl changes, or -`danger-full-access`/legacy sandbox fallbacks to hide failures. - -Prove the policy before deployment in a disposable, network-disabled container with no credentials: -The local execution sandbox succeeds; workspace-write can create a file in the selected root; -a separate writable Docker bind is read-only inside the sandbox; direct outer-container mount fails. -Then commission one real local-execution fixture run and check the resulting bytes on the host. -These checks prove the tested boundaries, not complete sandbox security or design quality. - -The reviewed DGX deployment used `/etc/apparmor.d/ksi-codex-userns`; its seccomp and Compose override -are private host runtime files. This is a deployment record, not proof the current container still -uses it. Inspect actual image/Compose/security identity after every recreation and compare it with the -approved connector configuration; health alone does not establish local-execution readiness. Back up configuration before adding the security override to the -connector's pinned Compose file list, so subsequent project connections retain it. Verify actual -`CapEff=0`, `NoNewPrivs=1`, seccomp filtering and enforced profile after every rollout. Restore the -previous pinned Compose list and recreate only the application service for rollback; remove the -optional host profile only when no running container references it. diff --git a/docs/releasing.md b/docs/releasing.md deleted file mode 100644 index 4c853de..0000000 --- a/docs/releasing.md +++ /dev/null @@ -1,37 +0,0 @@ -# Releasing the native installer (maintainers) - -This repository publishes the standalone `ksi-opencode-harness` native OpenCode V2 installer as a public **beta**, not a runtime plugin or a claim of accepted real-product visual quality. This guide is repository-only and is not included in the user-facing npm tarball. The V1 plugin version `0.4.0-beta.0` was removed after public V2 install verification; npm versions remain immutable. Do not reuse any published or unpublished version. - -## Prepare a new version - -The two-role bundle is the 0.5.0-beta.9 candidate. `package.json`, the exact-version checks and the README/INSTALL pins all carry beta.9; `npm run release:check` verifies the match on a clean tree. Do not republish previous releases. Until Primary publishes beta.9, its published/shipped status must still be verified after the authorized release — update the candidate/legacy notices as well -as the version pins then; matching pins alone does not prove published behavior. - -Choose an unused version and update the manifest and exact-version checks in `test/native-package.test.mjs` and `scripts/check-package.mjs`. Pin the same version in README/INSTALL preview and apply commands. Compare registry versions/tags with the candidate, review the source commit and tarball inventory, then run `npm run check`, `npm run check:package`, `node scripts/verify-native-v2-isolated.mjs`, `npm pack --dry-run --json`, and `git diff --check`. Run `npm run release:check` for the pinned preflight — it verifies the README/INSTALL version pins against `package.json`, a clean working tree, and both source checks; it never publishes, tags or changes a channel. The tarball contains the CLI, two role sources, public docs and licenses, but no retired design kit, V1 plugin entrypoint, tests, internal plans/state, credentials, or this release guide. Test the packed binary in a disposable target, including a read-only preview and two-role apply. File creation alone is not live OpenCode acceptance. - -## Publish only with explicit authorization - -After the reviewed commit is integrated and pushed, confirm that explicit authorization covers npm registry writes and the target channel. Authorization granted at task start is valid; do not request it again unless target/effects/risk change. For the approved beta channel, use `npm publish --tag next --registry=https://registry.npmjs.org/` with the exact verified tarball (npm may require an interactive browser/OTP flow). Preserve `latest` unless changing it is separately authorized. A successful CLI message may precede registry availability; verify `npm view @ version bin --json`, then install that exact version from the public registry into a disposable HOME/prefix and run preview/apply/repeat smoke checks. Verify both dist-tags and installed bytes against the reviewed tarball. A failure after publication does not roll back npm when Git is reverted; preserve the published version and report the partial state. - -Never unpublish an entire package name. Retiring an older version is a **separate, explicitly approved, version-qualified** action only after replacement verification, subject to registry policy; if denied, deprecate only that version and report the result. Do not modify user-owned OpenCode, Superpowers, or goal configuration, restart shared services, or infer visual/product acceptance from registry checks. - -## Tag the verified release - -After registry verification, tag the release commit so the published version maps to a revision: - -```sh -git tag v -git push origin v -``` - -Tags are markers only — they do not authorize or trigger npm writes. The repository keeps a tag for every published version (`v0.5.0-beta.1` … `v0.5.0-beta.8`). If a tag is missing for an already published version, add it at the commit whose `package.json` carries that version (verify with `git show :package.json`), not at a later commit. - -## Release checklist - -- [ ] `npm run release:check` passes on the release commit (pins, clean tree, source checks) -- [ ] `node scripts/verify-native-v2-isolated.mjs` passes -- [ ] `npm pack --dry-run --json` inventory reviewed (no tests, plans, state or credentials) -- [ ] explicit authorization covers the npm write and the target channel -- [ ] publish the exact verified tarball; verify `npm view @ version bin --json` and both dist-tags -- [ ] public install smoke test (preview → apply → repeat) from the registry -- [ ] tag `v` at the release commit and push the tag diff --git a/docs/superpowers/plans/2026-09-15-long-running-orchestration.md b/docs/superpowers/plans/2026-09-15-long-running-orchestration.md deleted file mode 100644 index 28fbb1a..0000000 --- a/docs/superpowers/plans/2026-09-15-long-running-orchestration.md +++ /dev/null @@ -1,73 +0,0 @@ -# Long-running Orchestration Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Enforce coordinator-only Build, restore bounded evidence after compaction/restart, and connect Research and delegated Design without expanding uncontrolled recursion. - -**Architecture:** Fixed repository-evidence service first; role/lifecycle integration second; portable documentation and local activation last. One writer at a time in the existing clean checkout on improve/long-running-orchestration, explicitly selected by the user. No commits are authorized; review task-scoped worktree snapshots including untracked files. - -**Tech Stack:** Node >=20 ESM, native OpenCode1.18.31 plugin hooks/tools, node:test, existing dependency-free package conventions. - -**Spec:** docs/superpowers/specs/2026-09-15-long-running-orchestration-design.md - -## Global Constraints - -- Build never implements product code; Primary writes this plan/checkpoint and execution bookkeeping only. Worker owns implementation/configuration. -- One writer per worktree; no automatic commits/push/install/release. Existing user model/variant choices and shared Codex/Claude/Superpowers installations remain intact. -- Native permissions are not an OS sandbox. Do not introduce a language/keyword classifier for visual sufficiency or arbitrary shell effects. -- Conversation is transient; one chosen progress ledger owns execution, checkpoint is only a short recovery pointer. Native task registry is not a second plan. -- Preserve existing optional Developer Test Runner guards. No arbitrary compaction percentage or undocumented config field. - -## Execution ledger - -This section is this plan's sole task-progress ledger; the checkpoint points here. This explicit existing-plan ledger replaces a duplicate SDD progress file for this execution. Test results and scoped review snapshots provide evidence because commits are not authorized. - -| Task | State | Ownership and dependency | -|---|---|---| -| 1 Repository evidence | complete | Primary fix round after cloud-Worker opt-in block; 12/12 targeted, 77/77 full at the time; traversal + clean-verify smoke PASS; independent reviewer unavailable, Primary inspected diff | -| 2 Roles and continuity | complete | Primary (cloud Worker dispatch blocked by provider opt-in); strict Build, research/design-task roles, continuity registry/reconcile/archive, contracts; 7/7 continuity tests | -| 3 Documentation and activation | complete | Primary; policy/docs/example/package updated, mobbin 5-10 rule scaled, native debug smoke PASS, no live provider/MCP changes, no restart yet | - -Preflight: Task1 and2 share the repository service interface, not a writer; Task2 and3 share package/runtime behavior and run sequentially. Task1 must support uncommitted work because the global constraints forbid commits. Task2 must preserve helper lifecycle tests. Task3 must reconcile independently changed global routing instead of restoring old models. - -### Task 1: Bounded repository evidence - -**Files:** Create src/repository.mjs and test/repository.test.mjs. No changes to shared index/agents/package yet. - -**Interfaces:** Export createRepositoryService({ worktree }) with async status(), snapshot({ paths }), verify({ snapshot }), and readCheckpoint(). Snapshot returns a generated identifier, immutable manifestPath/diffPath, fingerprint and coverage metadata; verify returns valid plus changed/unknown evidence. Paths are explicit worktree-relative task paths. No caller-controlled commands, revisions, output roots or arbitrary executable settings. - -- [ ] Write temp-Git integration tests for staged+unstaged+untracked inclusion, later content drift, pre-existing dirty baseline, filenames with spaces, path traversal/symlink rejection, sensitive-file exclusion, output-size bounds and a Git root not equal to cwd. -- [ ] Run `node --test test/repository.test.mjs` before implementation and record the meaningful failure. -- [ ] Implement fixed Git observation using execFile without a shell, disabled optional locks/fsmonitor/external diff/textconv, concrete internal artifact paths, content fingerprints and explicit excluded/truncated coverage. readCheckpoint is bounded and does not grant paths mentioned inside it. -- [ ] Run targeted tests. Return interface details, changed files and short test evidence. Primary/reviewer inspect an actual worktree snapshot before accepting. - -### Task 2: Strict roles, task identity and compaction recovery - -**Files:** Modify index.mjs, src/agents.mjs, src/contracts.mjs, agents/design.md, agents/explore.md, agents/developer*.md, agents/reviewer.md and relevant tests. Create src/continuity.mjs, agents/build.md, agents/research.md, and tests/fixtures needed. Share the Design contract with a generated hidden design-task entrypoint rather than duplicating its design rules. - -**Interfaces:** Consume Task1 createRepositoryService. Expose native ksi_repository for bounded status/snapshot/verify and ksi_reconcile for recovery. createContinuity receives native worktree/client and repository service, and supplies native hook helpers for task before/after, events and compaction context. Validate runtime args explicitly and verify schema compatibility with actual native plugin loading. - -- [ ] Add failing tests: Build refuses product edits/bash/unknown execution while bookkeeping remains usable; role graph adds Research and Build→design-task, keeps Design and worker recursion denied; native model/variant preservation; no child checkpoint writes. -- [ ] Add recovery tests: first dispatch requires reconcile, compaction/restart requires renewed reconcile, stale/wrong-parent/wrong-role task_id rejected, known child repair accepted, unknown/unfinished task identity surfaced rather than silently restarted, existing helper behavior preserved. -- [ ] Add output tests: large final task output archived under a scoped generated artifact path with session/call provenance; Parent receives bounded preview+pointer+unknown/truncation notice; write/archive failure does not masquerade as successful complete output. -- [ ] Implement effective strict Build permissions and a narrow before-tool check. Add the fixed repository/reconcile tools without exposing unrestricted shell or arbitrary output locations. Retain scoped Design prototype permissions and an explicitly narrower hidden entrypoint. -- [ ] Add bounded compaction recovery pointers and native lifecycle/task identity records where required, with explicit limits on cross-process enforcement. Do not implement a speculative distributed scheduler or automatic product approval. -- [ ] Run targeted tests then `npm run check`. Return actual diff/snapshot pointers and escalate missing native API/lifecycle evidence instead of inventing it. - -### Task 3: Portable policy, diagnostics and local activation - -**Files:** instructions/harness.md, README.md, INSTALL.md, docs/{architecture,execution,design,verification,troubleshooting}.md, examples/design-handoff.md, examples/design.project.jsonc, opencode.jsonc.example, package.json and package-check tests/scripts as necessary. Approved local scope: /home/ksi/.config/opencode/{AGENTS.md,opencode.jsonc,mobbin-design.md} plus a narrowly scoped OpenCode launcher only if necessary and separately justified; no shared shell/Claude/Codex changes. - -**Interfaces:** Document actual Task1/2 tools and contracts. Live config must preserve all current providers/models/MCPs/plugin pins, remove misleading Build broad execution grants, align Research/Design tooling, and retain automatic compaction settings. Add a selected-field capability diagnostic if necessary; never print credential-bearing full effective config. - -- [ ] Update package allowlist/checks for shipped new runtime modules and prompts. Test renamed/added roles and installed package behavior rather than retaining obsolete six-role assumptions. -- [ ] Align Superpowers mapping: fresh child per task, same-task verified resume, one ledger, dirty-state review packages, bounded reports, no automatic commit or model substitution. Explain Primary bookkeeping vs product source writes. -- [ ] Document strict Build flow, Explore/Research output fields, Design missing-decision/approval handoff, compaction/restart reconciliation and worker failure handling. Remove duplicate test command requirements and unconditional5–10-reference search rule. -- [ ] Re-read live global configuration before patching only relevant settings. Activate websearch by a scoped supported mechanism if existing process/tool registration requires it; native settings alone must not be claimed to enable a missing tool. Do not install unrelated LSP/MCP packages. -- [ ] Run `npm run check`, `npm run check:package`, `git diff --check`, native isolated config/tool discovery, and targeted no-product-write recovery/permission smoke on OpenCode1.18.31. No full raw config or external model calls without need. -- [ ] Independent reviewer checks final actual diff against spec and test evidence. Primary records remaining runtime/model boundaries, idle checkpoint and restart instructions. - -## Verification baseline - -- Before edits: `npm run check` passed65/65 on2026-09-15. -- Native Test Runner dispatch failed before execution due loaded unsupported legacy model; no silent replacement. Primary can execute trusted tests, reviewer independently inspects artifacts. diff --git a/docs/superpowers/plans/2026-09-23-human-centered-workflow.md b/docs/superpowers/plans/2026-09-23-human-centered-workflow.md deleted file mode 100644 index 1d14066..0000000 --- a/docs/superpowers/plans/2026-09-23-human-centered-workflow.md +++ /dev/null @@ -1,86 +0,0 @@ -# Human-Centered Workflow Slice — Plan Ledger (public sanitized summary) - -Status: in_progress (in-progress user-approved work, no completion claimed) -Date: 2026-09-23 to 2026-09-24 -Branch: `improve/human-centered-workflow-20260923` from `da8ed9550ea7f13986fbdaa378fb443d25abe4e0` -Product state: `docs/superpowers/product-state.md` -Private full ledger: archived locally only (not in public source); this file is the public summary. - -> m02 Operational evidence consistency was accepted by the user on 2026-09-24 at the authorized slice-close boundary and is recorded done; this ledger does not merge, release, or accept m03. - -## Objective - -Implement outcome-first Build communication plus Design Primary live-preview collaboration with narrow authorization, in an isolated worktree that preserves original main's uncommitted docs. Role and permission runtime behavior changed as documented in prompts, permissions, packaging gates, and tests. - -## Human-visible acceptance (end-to-end, pending user check — not claimed) - -- Build reports headline what a user can do/see, what is incomplete, and the next product result, with only genuinely necessary decisions and tests/review as supporting evidence; failed checks stay headline blockers. -- Design collaborates via a human-openable localhost live prototype/story URL showing baseline vs revision, with interactive inspection plus desktop/mobile PNGs where authorized; server/tool consent stays separate from visual approval and production acceptance. -- Plan, Design, and Build read as peer user-selected Primaries with bounded workers; no approval is inferred from source. - -## Scope (public summary) - -- Outcome-first Build wording plus Plan-use wording; failed Test Runner/Reviewer checks as headline blockers. -- Design live-preview collaboration wording with deliberate approval and inspected-evidence guards retained. -- Peer Plan/Design/Build diagram and role wording in README and architecture docs. -- Design Primary foundation: small ambiguity-aware brief, project design-system entrypoint template, live preview plus iteration plus accessibility/interaction/visual critique, honest handoff. No new subagents beyond the approved hidden critic. -- Narrow Design delegation: Design Primary may dispatch exactly two local read-only children — explore and new hidden design-critic — with single-line artifact/version plus desktop/mobile PNG evidence. No Research/external child, no developer, no recursion, no Design as target. -- Build design-task role removed; post-approval integrated fidelity is checked by Reviewer visual-fidelity mode with no code writes. -- Permission fail-closed hardening for Design and critic paths (scoped preview, read/skill, secret-boundary, malformed-value handling) as conservative validators; no OS sandbox or secret-isolation claim. -- Public tarball boundary: `package.json files` ships exactly eight public docs; `docs/superpowers/**` (product-state, ledgers, specs) is excluded from the npm tarball and stays source-checkout only. -- Product-state records m01 done, m02 done (accepted 2026-09-24), and m03 in_progress. No m03 slice close claimed. - -## Actual decisions preserved - -- Isolated worktree/branch used; original main dirty work preserved and never deleted manually. -- Prior README routing/proof, architecture sample, troubleshooting classes, verification dated entries, and m02 ledger integrated additively; no silent overwrite of m02/m03 history. -- Portable model-routing sample keeps five explicit entries with no extra entries and no forced mapping claim; hidden design-critic stays unpinned and inherits native selection unless the user pins it. -- Critic evidence is single-line with artifact/version plus two workspace-relative PNG claims (desktop plus mobile, each with a directory prefix); placeholders, absolute paths, traversal, single-PNG, and duplicate evidence lines are rejected with specific errors. Accepted strings are claims, never proof of existence, inspection, or approval. -- Hook backstop denies critic writer/shell/task and external/network/MCP use by child caller identity; native read-only permissions stay the primary guard with timing uncertainty stated, not as an unconditional dual-enforcement claim. -- A user-authorized reversible local experiment briefly pointed global sessions at the uncommitted candidate worktree (paths redacted); revert is one config line plus a full process restart. Local experiment only — not release or slice acceptance. -- New upstream revisions are pinned only after source review and compatibility checks; KSI contains no fork or copy of official upstream Superpowers. - -## Executed checks (dated, commands plus counts; writer-targeted unless noted) - -- 2026-09-23 foundation: `node --test test/design.test.mjs` 36 pass / 0 fail; `npm run check:package` pass; `git diff --check` clean. No live browser/server run; no preview URL or PNG inspection claimed. -- 2026-09-23 hardening: independent Test Runner on pre-repair revision `npm run check` 236/236, check:package pass, pack dry-run contains both new Design docs, diff-check clean; original main paths unchanged. -- 2026-09-23 pre-bookkeeping: independent Test Runner `npm run check` 236/236, check:package pass, pack dry-run 41 files including both new docs, diff-check pass; original main paths unchanged. No exact-final verification claimed after the bookkeeping edit. -- 2026-09-23 delegation: `node --test test/design.test.mjs test/index.test.mjs test/package.test.mjs` 112 pass / 0 fail; `node --test test/design.test.mjs test/contracts.test.mjs test/package.test.mjs` 68 pass / 0 fail; `node --test test/index.test.mjs` 61 pass / 0 fail; `git diff --check` clean. -- 2026-09-23 audit/contract repair: `node --test test/audit-routing.test.mjs test/contracts.test.mjs test/design.test.mjs test/index.test.mjs` 131 pass / 0 fail; `git diff --check` clean; routing-sample diff empty; original main 4 modified plus 2 untracked unchanged. -- 2026-09-23 isolated offline metadata smoke on actual OpenCode 1.18.31 with task-owned temporary home/config pointing only at the candidate file URL (paths redacted): `debug config`, `debug agent design-critic`, and `debug agent design` each exit 0. Config metadata only — not child dispatch, hook lifecycle, PNG inspection, preview, or user acceptance. -- 2026-09-23 post-switch: independent Test Runner on fresh global config `debug config/agent` each exit 0 (metadata only); independent Reviewer severity High notes all global sessions depend on uncommitted code — consciously accepted as reversible local experiment with rollback documented. -- 2026-09-23 design-task removal: `node --test test/design.test.mjs test/index.test.mjs test/contracts.test.mjs test/audit-routing.test.mjs test/package.test.mjs test/goal-federation.test.mjs` 147 pass / 0 fail; `git diff --check` clean; original main 4 modified plus 2 untracked unchanged. -- 2026-09-23 reviewer Mode fail-closed: `node --test test/contracts.test.mjs test/index.test.mjs` 87 pass / 0 fail; `git diff --check` clean. -- 2026-09-23 native pair fail-closed: `node --test test/design.test.mjs` 43 pass / 0 fail; `git diff --check` clean. -- 2026-09-23 tarball boundary: `node --test test/package.test.mjs` 10 pass / 0 fail; `npm run check:package` pass; `npm pack --dry-run` path/size inspection only 37 files including exactly eight public docs under `docs/`, zero internal-state hits; `git diff --check` clean. -- 2026-09-23 permission-scope repair: `node --test test/design.test.mjs` 46 pass / 0 fail; `node --test test/package.test.mjs` 10 pass / 0 fail; `git diff --check` clean. -- 2026-09-24 Design permission hardening: `node --test test/design.test.mjs` 49 pass / 0 fail, then 53 pass / 0 fail after read/skill merge fix, then 55 pass / 0 fail after secret-boundary overlap fix; each with `git diff --check` clean and original main unchanged. -- 2026-09-24 prerelease docs/package gate: `node --test test/package.test.mjs` 11 pass / 0 fail; `npm run check:package` pass; `git diff --check` clean. -- 2026-09-24 manifest plus visual-duplicate repair: `node --test test/package.test.mjs test/contracts.test.mjs test/index.test.mjs` 100 pass / 0 fail; `npm run check:package` pass; `git diff --check` clean; original main 4 modified plus 2 untracked unchanged. -- 2026-09-24 candidate `0.4.0-beta.0` prerelease metadata/docs (provisional, no publication): version bump plus exact version checks, two-independent-plugin install wording, `next`-tag publish gate wording, historical evidence preserved with candidate scope separated; targeted checks only, final full suite/pack/review owned by Primary. At that snapshot neither slice was marked complete (m02 completed later the same day — see Reconciliation below); no Design visual/user acceptance and no published link claimed. -- m02 reference (same workstream family): `npm run check` 222/222 on committed HEAD with docs/state-only dirty worktree; `npm run check:package` pass; `npm pack --dry-run` 38 files with no tarball; `git diff --check` pass. - -## Boundaries (not verified here; Primary owns independent tests/review) - -- No full suite author run after the final sanitization edit; no commit, push, merge, tag, npm publish, network, live model, browser, or preview/server run in this sanitization turn. -- Live Design visual pilot, PNG inspection, child-dispatch lifecycle, stable commit/pin, source-public disclosure rights/brand approval, and m03 user end-to-end acceptance remain pending human decisions (m02 acceptance recorded 2026-09-24 — see Reconciliation below). -- Tarball-gate pass is not source-public approval; a public Git release still needs human data/classification review before push. No secret-free or legal claim is made. -- Compatibility baseline is OpenCode 1.18.29 and Node >= 20; other OS/versions are unverified until actually tested. CI badge (when present) reflects the published main commit only, not uncommitted changes. -- m02 done (accepted 2026-09-24 — see Reconciliation below); m03 stays in_progress with no slice close, no live/visual approval, and no publication action claimed. -- Candidate `0.4.0-beta.0` is a provisional prerelease preparation only, not a completion or release-success claim; no commit, push, tag, npm publish, or network action in this turn. - -## Reconciliation — m02 slice close (2026-09-24) - -Scope: valid slice-close boundary explicitly authorized by the user on 2026-09-24 for m02 only; single writer in the candidate, no commit/push and no original-main edits in this turn. -Status: m02 Operational evidence consistency is done (user acceptance 2026-09-24, evidence in the m02 public ledger); m03 human-centered live Design visual flow remains in_progress unaccepted — no m03 acceptance claimed. -Preflight: candidate branch `improve/human-centered-workflow-20260923` at committed HEAD `da8ed9550ea7f13986fbdaa378fb443d25abe4e0`; original main keeps its prior 4-modified-plus-2-untracked docs/state work, unchanged by this turn. -Checks in this turn (writer-targeted, product-state/ledger scope): `node --test test/index.test.mjs` and `git diff --check`; pre-edit snapshot 67/67 pass with diff-check clean; post-edit 66/67 pass with diff-check clean — the single failure pins the pre-close m02 `in_progress` state (`test/index.test.mjs:1260`) and needs a source/test update outside this turn's allowed paths, so verification stays pending for Primary; no full-suite rerun invented here. -Boundaries: public prerelease `0.4.0-beta.0` stays intended only, not published; historical dated entries above are preserved as snapshots and not rewritten as though completed earlier; live Design visual pilot, PNG inspection, and approved artifact remain pending human decisions. - -## Public release note — scope-note-only correction (2026-09-24, user-approved exceptional narrow scope) (historical, not current) - -User-approved factual metadata-only correction: docs scope text plus product-state scope-note line only; Goal, m02/m03 status, Current-slice acceptance, Next, and Backlog unchanged. m03 remains in_progress with no live Design visual/Human acceptance; public beta intent only with npm auth ENEEDAUTH at last preflight; no commit, push, tag, publish, live preview/test success, or release claimed. - -## Public release truth correction (2026-09-25, docs-only; no registry publish/tag action in this turn) - -Registry install evidence category, 2026-09-25, `ksi-opencode-harness@0.4.0-beta.0`: first-ever npm publish verified (registry exact version/integrity; exact temp-install ESM default async plugin function; 37 files, no internal docs; KSI plus official Superpowers `b36e0829c6d0140e93cfef2ca599b1b07d4a7797` as two independent plugins). Observed `next` and `latest` both point to this beta (first-version auto-`latest` behavior plus official metadata invariant — not a silent tag fix); both tags left untouched, beta never called stable, bare/unversioned spec warns to use the exact pin; next accepted stable release moves `latest` then (no invented version/tag). Immutable `0.4.0-beta.0` tarball README still carries pre-publish wording — disclosed as a publish-time snapshot limitation. m02 done (accepted 2026-09-24); m03 stays in_progress with no slice close, no live/visual approval, and no legal/sandbox/visual completion claimed. No commit, push, tag, publish, network, live model, browser, credential, or raw-log action in this turn. diff --git a/docs/superpowers/plans/2026-09-23-operational-evidence.md b/docs/superpowers/plans/2026-09-23-operational-evidence.md deleted file mode 100644 index 9013f70..0000000 --- a/docs/superpowers/plans/2026-09-23-operational-evidence.md +++ /dev/null @@ -1,86 +0,0 @@ -# Operational evidence consistency — plan ledger - -Updated: 2026-09-23. Scope approved by Build; single-writer Developer execution, no helpers/subagents. - -## Objective - -Improve operational continuity and make routing/verification/failure claims understandable and auditable, docs/state only. - -## Allowed write paths - -- `docs/superpowers/plans/2026-09-23-operational-evidence.md` (this ledger) -- `docs/superpowers/product-state.md` -- `README.md` -- `docs/architecture.md` -- `docs/troubleshooting.md` -- `docs/verification.md` - -## Forbidden shared files - -All other paths — especially machine OpenCode config (path redacted), `examples/model-routing.json`, plugin source/runtime, tests, `instructions/harness.md`, and `.opencode/working-state.md` (Build-owned checkpoint; do not touch). - -## Baseline (verified 2026-09-23, fresh — not reused) - -- Branch: `main`, HEAD: `da8ed9550ea7f13986fbdaa378fb443d25abe4e0`, `git status --short`: clean. -- Prior `main @ da8ed955` observation treated as stale until re-verified above. - -## Changes - -1. **Ledger (this file):** records approved scope, baseline, and per-file intent. -2. **Product state:** new harness `docs/superpowers/product-state.md`; current slice `Operational evidence consistency` left `in_progress`, linked to this ledger. No user acceptance claimed, slice not marked complete. -3. **README:** qualify the undated `200+ tests` claim with dated/scoped pointer to `docs/verification.md`; clarify `examples/model-routing.json` is an optional, non-enforcing portable sample that may differ from machine-local routing. No model values changed; no machine choices promoted to portable defaults. -4. **Architecture:** tighten the existing routing-sample paragraph (already non-enforcing) so the sample-vs-machine-local distinction is explicit. No model values changed. -5. **Troubleshooting:** add privacy-preserving diagnostics separating (a) plugin/startup or role-load, (b) task permission/contract rejection, (c) child startup/execution, (d) provider/model request failures — each with minimal redacted evidence and fresh-process guidance. No fix claimed for the unresolved generic server error. -6. **Verification:** `docs/verification.md` was an allowed path but intentionally left unchanged in the first implementation pass (history preserved with dates/limits; no new pass claimed). Updated in the 2026-09-23 evidence follow-up below with the dated Test Runner results. - -## Acceptance criteria - -- Ledger exists at the allowed path for the approved scope. -- Product-state exists with the current slice in progress and ledger link. -- README/architecture state the sample's optional non-enforcing nature without changing model values or porting machine choices. -- README's undated `200+ tests` claim removed or qualified in favor of dated/scoped evidence. -- Troubleshooting distinguishes the four failure classes with redacted evidence and fresh-process guidance. -- Historical verification keeps dates/limits; no user acceptance claimed; slice not complete. -- No runtime/model/provider/MCP/config change, commit, push, install, release, or live/billable call. - -## Targeted verification - -- `git diff --check` clean. -- New Markdown links manually validated; product-state schema checked against the harness contract. -- Initial pass left independent Test Runner/Reviewer dispatch to Primary; both have since completed — see Final evidence below (this supersedes that deferral for test execution). - -## Escalate if - -Pre-existing dirty edits overlap a target, the checkout is not the harness repo, or acceptance would require changing model routing/runtime behavior. - -## Review repair (2026-09-23, same task) - -Failure: independent review (ID redacted) reported three low-severity clarity findings — L1 duplicate failure taxonomies without a mapping (`docs/troubleshooting.md:35-44,57-66`); L2 current slice duplicated in Next slices (`docs/superpowers/product-state.md:18-24`); L3 OpenCode versions 1.18.29 and 1.18.31 co-occurring without distinct-context explanation (`docs/troubleshooting.md:9`, `docs/verification.md:17`, `INSTALL.md:11`). -Evidence: the review report findings above; verified against current worktree files before editing (no dates invented; `INSTALL.md` was read-only and outside repair paths, untouched; `docs/verification.md` was allowed but intentionally unchanged in that pass — see evidence follow-up below). -Changes: troubleshooting gains a triage→evidence mapping paragraph (broad 3 categories route to actionable 4 classes) and a version-context note (1.18.29 = this investigation + compatibility baseline; 1.18.31 in `verification.md` = distinct evidence-tool check; neither proves same-run compatibility); product-state Next slices now `(none)` with no invented candidate while the current slice stays `in_progress`. Slice not marked complete; no acceptance claimed. - -## Evidence follow-up (2026-09-23, same task) - -Failure: final review (ID redacted) Low-1 — this ledger misstated `docs/verification.md` as outside repair paths (it was allowed; corrected above); Low-2 — README points to `verification.md`, which had no current 2026-09-23 / 222-test entry. -Evidence: independent Test Runner report (ID redacted) on committed HEAD `da8ed9550ea7f13986fbdaa378fb443d25abe4e0` with the 5-path docs/state-only dirty worktree (results quoted verbatim from that report; no full suite rerun here — exact-final-revision rerun left to Primary). -Changes: ledger wording corrected (this file); `docs/verification.md` gains a dated/scoped 2026-09-23 entry recording the 222/222 results with committed-HEAD-vs-dirty-worktree distinction and explicit boundaries (no live/quality/cost/acceptance). Historic counts and compatibility scope preserved. - -## Final evidence (2026-09-23, same task) - -Failure: final review (ID redacted) L-2 — this ledger still deferred independent Test Runner/Reviewer to Primary; L-4 — README mentioned `npm test` while dated evidence is for `npm run check` (which subsumes it). Both traceability/clarity only, not acceptance blockers. -Evidence (superseded by Evidence reconciliation below; kept for audit trail): independent Test Runner (ID redacted) on committed HEAD `da8ed9550ea7f13986fbdaa378fb443d25abe4e0` with the six-path docs/state-only dirty worktree (`README.md`, `docs/architecture.md`, `docs/troubleshooting.md`, `docs/verification.md`, this ledger, product-state) — results quoted verbatim from that report, no full suite rerun here: `npm run check` 222/222 pass; `npm run check:package`, `npm pack --dry-run` (38 files, 87.2 kB package / 274.3 kB unpacked, no tarball), and `git diff --check` all exit 0; no forbidden paths/live calls. Final review verdict: technically ready for user acceptance review, no High/Medium findings, prior L1/L2/L3 addressed. Known low follow-ups not taken into files here: the `INSTALL.md:66` "권장 mapping" versus optional/no-endorsement wording (outside allowed paths). Note: the `docs/verification.md` entry records the earlier five-path run (86.4 kB / 272.1 kB); the final six-path numbers above live in this ledger because `docs/verification.md` is outside this turn's allowed paths. -Changes: README Proof now names `npm run check` (including its `node --test` suite) as the source of dated/scoped evidence with no separate `npm test` invocation claimed (this file's turn); ledger deferral superseded by the recorded runs above. Acceptance gate: the slice stays `in_progress` per product-state and user end-to-end acceptance remains pending — nothing marked complete. No-live/quality/cost/release boundaries preserved; no committed/clean tree claimed. - -## Evidence reconciliation (2026-09-23, same task) - -Failure: final review (ID redacted) M-1/L-1 — latest run not durably recorded; L-2 — this ledger still cited the prior report; L-6 — nonblocking `INSTALL.md:66` terminology issue noted in ledger but absent from product-state backlog. No High findings; slice ready for user acceptance viewing once record gaps are addressed. -Evidence: latest independent Test Runner (ID redacted) on committed HEAD `da8ed9550ea7f13986fbdaa378fb443d25abe4e0` with the six-path docs/state-only dirty worktree at its snapshot — quoted verbatim, no suite rerun here: `npm run check` 222/222; `npm run check:package` pass; `npm pack --dry-run` 38 files / 88.0 kB / 276.4 kB / shasum `405ccebbe6e6f7f4400ec7022bf21e3bd17726a6`, no tarball; `git diff --check` pass; no forbidden paths/live calls. This runner preceded this follow-up record patch, so it is the measured pre-record snapshot — NOT bitwise exact for the resulting tree, and no slice acceptance is claimed. Run-size differences across runs (86.4 → 87.2 → 88.0 kB) reflect docs changing after each run. A Primary-dispatched test reruns after this writer stops; its outcome will be reported in chat/ledger only if available, not pretended to predate itself. -Changes: `docs/verification.md` gains the dated follow-up entry above; this ledger's latest-run reference replaced (prior report kept marked superseded for audit trail); product-state Backlog gains the nonblocking `INSTALL.md:66` item. Current slice/milestone unchanged; Next slices stay none; all historical dated evidence preserved. No live/model/quality/cost/user-acceptance claim. - -## Human acceptance — slice close (2026-09-24, same workstream) - -Scope: valid slice-close boundary explicitly authorized by the user on 2026-09-24 for m02 only; m03 remains in_progress unaccepted and no m03 acceptance is claimed here. -Acceptance: user explicitly accepted m02 Operational evidence consistency in the current conversation on 2026-09-24; no acceptance screen is invented and no visual artifact is claimed. -Preflight: candidate branch `improve/human-centered-workflow-20260923` at committed HEAD `da8ed9550ea7f13986fbdaa378fb443d25abe4e0`; original main keeps its prior 4-modified-plus-2-untracked docs/state work, unchanged by this turn. -Checks in this turn (writer-targeted, product-state/ledger scope): `node --test test/index.test.mjs` pre-edit 67/67 pass, post-edit 66/67 pass, with `git diff --check` clean in both snapshots; the single post-edit failure is the pre-close m02 `in_progress` assertion (`test/index.test.mjs:1260`) requiring a source/test update outside this turn's allowed paths — reported as failure with verification status pending per the slice-close contract; no independent full-suite rerun invented here. -Boundaries: public prerelease `0.4.0-beta.0` stays intended only, not published; no commit, push, merge, tag, npm publish, network, live, or browser action; historical dated entries above are preserved as snapshots and not rewritten as though completed earlier. diff --git a/docs/superpowers/plans/2026-09-26-opencode-v2-plugins.md b/docs/superpowers/plans/2026-09-26-opencode-v2-plugins.md deleted file mode 100644 index fabc597..0000000 --- a/docs/superpowers/plans/2026-09-26-opencode-v2-plugins.md +++ /dev/null @@ -1,439 +0,0 @@ -# OpenCode V2 Native KSI Role Ownership — Follow-up Implementation Plan - -## Design-centered portable kit — KSI-20260928-DESIGN-KIT-CRAFT - -**Approved direction (2026-09-28):** Design is the normal Primary for material product/visual design; Build may handle small changes within approved patterns and owns production integration. Adapt a small pinned OpenDesign skill/craft subset to native OpenCode, not the whole OpenDesign app, its daemon, 163 catalog entries, or 114 templates. Keep skills discoverable rather than imposing per-agent allowlists, and retain Design Critic as optional independent review. Existing project tokens and Human-approved direction outrank generic craft. No automatic global replacement, service restart, provider call, commit, push, or release in this source phase. - -**Observed gap:** The existing optional kit ships two original SKILL.md files. OpenDesign injects `od.craft.requires` and active design-system context in its own daemon; OpenCode V2 loads SKILL.md and only lists supporting files, so the existing metadata does not load craft. `design-review` is a third-party catalog stub, while `brand-extract` and OpenDesign's `agent-browser` expect app-specific context. Do not advertise these as runnable KSI capabilities. - -| id | deliverable | dependency | validation | -| --- | --- | --- | --- | -| KC-1 | Pin OpenDesign's complete `impeccable-design-polish` skill and six relevant craft references with provenance; install selected craft as local references beside the two producer skills | existing pinned upstream | upstream hash comparison, package inventory | -| KC-2 | Add small OpenCode-facing guidance to installed producer skills so they explicitly read relevant craft and existing project design sources; keep upstream vendored bodies unchanged | KC-1 | RED→GREEN bundle and installer tests; isolated skill catalog | -| KC-3 | Make role and public guidance Design-centered for material design while retaining Build for small established-pattern UI work; describe Critic as optional independent lens | KC-2 | bundle tests, documentation review, full package/check/diff checks | - -**Review focus:** Do not overwrite a project's `DESIGN.md`, install an unrelated default design system, turn craft's numerical examples into hard KSI restrictions, mutate built-in Build/Plan prompts, add a plugin/runtime, or claim visual-quality acceptance from a skill catalog test. Global apply/restart and provider-backed product comparison remain separate decisions. - -**Source implementation and checks (2026-09-28):** Reused the existing dirty task checkout; pre-change `npm run check` passed 25/25. KC-1 vendored the pinned `impeccable-design-polish/SKILL.md` plus six OpenDesign craft files; SHA256 comparison of each local file against its pinned raw upstream URL matched. KC-2 installs four craft files beside `frontend-design`, six beside `impeccable-design-polish`, and adds OpenCode V2 reading/context notes to installed skills and craft while preserving vendored originals. Default six-agent install is unchanged, design kit now has three skills and 23 total managed files, including the polish directory license. KC-3 routes material design to Design while Build retains small established-pattern UI changes and production integration; Design Critic remains optional. Bundle/installer/catalog RED: three expected failures (11 vs 22 files, absent craft/polish, absent catalog expectation), then GREEN 19/19. Design-role routing RED 4/5 then GREEN 5/5. Initial fresh `npm run check` 26/26, `npm run check:package` PASS, `node scripts/verify-native-v2-isolated.mjs` PASS on isolated OpenCode 2.0.18 (nine role modes, three skill IDs from isolated config, no verifier-issued provider request; child egress unmonitored), `git diff --check` PASS. Independent review then found two Important issues: original craft mandates could be read as KSI hard gates, and the pinned web-review skill invited live mutable upstream guidance. RED→GREEN regression tests added; installed craft now has a prominent contextual preface, web review defaults to the pinned local guide and only fetches latest on explicit Human update request, and installed polish includes an Apache license and warning that suggested companion skills are not installed. No actual skill invocation, visual artifact, or Human quality acceptance has yet been measured. No global config, shared service, provider, or KSI Safety source changed. - -**Final source review and promotion boundary:** After remediation, full `npm run check` 26/26, `npm run check:package`, `git diff --check`, and private-server V2.0.18 catalog checks all PASSed again. The same independent reviewer rechecked the installed artifacts and returned PASS with no Critical/Important/Minor findings; Git status was unchanged by review. Read-only global installer preview (not apply) now shows five existing-file conflicts (`agents/design.md`, `agents/research.md`, `agents/design-critic.md`, `skills/frontend-design/SKILL.md`, `skills/web-design-guidelines/SKILL.md`) plus twelve new skill/license/craft reference files. It proposes no JSONC change. This is a preview, not permission to replace those files, restart the shared service, invoke a model, or claim improved visual quality. Prior three-file promotion hashes are superseded by this new source candidate; re-freeze exact existing/candidate file hashes and show the five-file diff before any separately approved global apply. - -**Approved global adoption and restart (2026-09-28):** The Human separately approved the 17-file global adoption and `openchamber.service` restart after the exact five-file diff and twelve-create inventory were shown. Preflight confirmed exactly five expected replacements, twelve absent create targets, and unchanged JSONC SHA256 `d1916c10f2c35cb2e59e986eeac4d8b84f71332a889600a49dc6b7b1c87d986e`. Recoverable copies of those five existing files and unchanged JSONC are in `/home/ksi/.local/state/ksi-harness-backups/design-centered-kit-20260928.FYeigK/`; each copy was byte-compared to its source before application. The collision-safe installer also placed five `.bak-*` siblings at the replaced paths. Post-apply, all 23 managed files match the generated bundle, JSONC is byte-unchanged, and a repeat preview reports zero changes. The service restarted successfully: pre-PID `1595231`, post-PID `2662586`, active/running; OpenChamber reports one password-protected runtime. Authenticated read-only shared catalogs returned `/api/agent`, `/api/skill`, `/api/command` HTTP 200, with nine expected role modes, Design→Research allow, Design edit allow, Research shell deny, default Developer subagent deny, visible Critic, and three installed design skills at global paths. The user's actual Developer routing remains `opencode-go/muse-spark-1.3-contributor#high`, `steps: 80`; the isolated fixture's hard-coded `deepseek-v4.1-flash`/47 assertion is not a shared-service expectation. Commands listed were `init`, `review`, `goal`, `pause_goal`, `resume_goal`; no KSI `/complete` command was installed. `/api/tool` returned 404, so tool registration was not established through that route. Fresh `npm run check` 26/26, `npm run check:package`, and `git diff --check` passed. No model/provider execution, live Design skill use, visual-quality acceptance, commit, push, or release occurred. m03 remains in progress pending a separately authorized real-product quality pilot and Human acceptance. - -**Research role retirement (2026-09-28, later user decision):** The Human chose to rely on built-in Explore for bounded local or authorized external investigation and to remove the optional Research role; the earlier six-role decision and catalog are historical snapshots, not the current on-disk contract. Source now ships five custom roles; Design delegates to Explore and Design Critic, and asks Explore for dated sources when current external claims matter. The installer deliberately does not delete an older Research file in someone else's target. TDD evidence: bundle tests failed 2/5 before source change then passed 5/5; verifier tests failed 3/7 before correction then passed 7/7; package gate failed for the retired template before correction, then passed. Final `npm run check` 26/26, `npm run check:package`, isolated OpenCode 2.0.18 agent/skill catalogs PASS (five custom roles, built-ins untouched, no verifier-issued provider request; child egress not monitored), and `git diff --check` clean. The installed Design file was compared byte-for-byte to the new bundle. Private pre-change copies of global Design, Research, JSONC and the untracked source template are in `/home/ksi/.local/state/ksi-harness-backups/research-retirement-20260928/`. On disk, global `agents/research.md` and the JSONC `agents.research` preference were removed; only the Research grant and prompt sentence changed in Design. The old `.bak-*` sibling is inert history. No shared-service restart or authenticated post-change catalog was performed, so effective live removal is not claimed. No source commit/push or m03 acceptance; the Human deferred further visual-quality discussion. - -**Reviewer consolidation (2026-09-28, subsequent user approval; supersedes older role-count statements):** The Human chose four native custom roles (`design`, `developer`, `test-runner`, `reviewer`) and no new MCP, plugins, or external skills. Source and installed global Design now delegate to Explore and Reviewer, not retired Research/Design Critic. Reviewer distinguishes code-change review from explicitly requested rendered design review and must disclose missing image input or pixels. Global Research was already absent; Design Critic role file and JSONC steps preference were removed after recoverable backup alongside Design, Reviewer, and JSONC in `/home/ksi/.local/state/ksi-harness-backups/reviewer-unification-20260928/`. Installer intentionally does not prune retired files in other targets. Bundle tests RED 3/5 then GREEN 5/5; verifier RED 3/7 then GREEN 7/7; package assertion RED then package PASS. Final `npm run check` 26/26, `npm run check:package`, isolated V2.0.18 catalog PASS, `git diff --check` PASS; four on-disk global role files byte-match the generated bundle and retired roles/routing are absent. No model execution, shared-service restart/reload, authenticated post-change catalog, visual-quality acceptance, commit, push, or release. The older 'Current contract' section below records historical migration planning, not this latest on-disk contract. Describe the pending product acceptance to the Human plainly as '실제 제품 화면을 사람이 확인하고 디자인 결과를 승인하는 단계는 아직 보류 중', not by internal milestone code alone. - -**Review correction (2026-09-29):** Read-only independent review identified current-guide count drift in `docs/execution.md`, `docs/releasing.md`, and `opencode.jsonc.example`, plus `README.md`'s old Critic label and the need to mark `docs/verification.md` catalogs as historical. Updated those current guides; kept `docs/design-critique.md` filename for link stability with an explicit Reviewer note. Global AGENTS.md gained one backed-up plain-language user-update rule; no new skill or runtime feature. Earlier dated historical evidence remains unchanged. Review did not inspect image pixels or restart the shared service. - -**Approved shared-service activation (2026-09-29):** Human explicitly requested restart. `systemctl --user restart openchamber.service` exited 0; unit became active/running with PID `4111608` (prior `2662586`). `opencode service status` returned `http://127.0.0.1:49374`. Authenticated CLI `opencode api get /api/agent` in `/home/ksi` returned 11 records: visible `build`, `plan`, `design` (primary), `general`, `explore`, `developer`, `test-runner`, `reviewer` (subagent), plus hidden `compaction`, `title`, `summary`; no Research or Design Critic. Effective Design subagent grants are Explore and Reviewer, while Developer and Reviewer deny child delegation. Model/steps from the same live catalog: Explore `opencode-go/deepseek-v4.1-flash`, 20; Developer `opencode-go/muse-spark-1.3-contributor#high`, 80; Test Runner `opencode-go/deepseek-v4.1-flash`, 24; Reviewer `opencode-go/muse-spark-1.3-contributor#xhigh`, 32; Design has no fixed model, 60 steps; built-in Build/Plan/General no fixed model or step cap in agent metadata. This is live metadata only, not a model call, image capability test, or Human design acceptance. - -**Go routing adjustment (2026-09-29, subsequent user request; supersedes routing above):** Human prioritized subscription usage allowance over nominal token price and conditionally preferred Muse Spark 1.2 if its quota were higher. Official OpenCode V2 Go docs report equal Muse Spark 1.2/1.3 Contributor limits: Go $60/month, Go Plus $120/month, estimated Go 45,300/5h, 113,300/week, 226,600/month. Both active models support minimal/low/medium/high/xhigh in the live catalog; no 1.2 quota advantage was found. After backing up only global JSONC to `/home/ksi/.local/state/ksi-harness-backups/go-routing-20260929/opencode.jsonc.before`, changed Explore to `opencode-go/muse-spark-1.3-contributor#low` (20 steps), General to the same `#medium` (no explicit steps), and Test Runner to the same `#low` (24 steps); Developer, Reviewer, Design, plugins, MCP, and all other JSONC entries remain unchanged. Authenticated `opencode api get /api/agent` immediately reported exactly those three effective model/variant/steps entries; service remained active with same PID `4111608`, so no restart was needed or performed. This verifies routing metadata, not task quality, actual provider invocation, or quota remaining on this account. - ---- - -## Design Studio quality loop — KSI-20260928-DESIGN-STUDIO - -**Approved direction:** Design remains an optional peer Primary, not a required Build stage. The Human rejected the earlier six-screen result as below the desired design standard. Improve the native Design method rather than adding a preview service, a forced helper hierarchy, or a second project design system. This is a source/package follow-up; global file replacement, shared-service restart, and new provider-backed quality acceptance remain separate approval/evidence boundaries. - -**Goal:** Make Design capable of using actual product evidence, external examples, explicit visual directions, rendered critique, and Human preference feedback to produce and retain better design decisions. Do not promise Claude Design-equivalent quality from prompts alone. - -**Architecture:** Keep the existing six native roles and two optional OpenDesign-derived skills. Design owns visual direction and artifacts; Explore inspects local product sources, Research gathers bounded external examples, and Design Critic independently assesses actual captures and task clarity. Project `DESIGN.md` remains an optional entrypoint to approved sources and preference evidence, not a duplicate token set. Build remains the normal UI implementer and production integrator. - -**Constraints:** Preserve all existing WIP and native Build/Plan/Explore prompts; no new runtime plugin, skill whitelist, custom preview tool, fixed screenshot quota, automatic design-system substitution, model override, provider call, global configuration write, service restart, commit, push, or release in this source phase. - -**Workspace ruling:** Reuse the current `fix/opencode-v2-plugin-migration-20260926` task checkout because its uncommitted native installer and templates are the implementation baseline; a new worktree from HEAD would omit them. One source writer only, with independent read-only review after focused validation. - -| id | deliverable | dependency | focused validation | -| --- | --- | --- | --- | -| DS-1 | Native Design may explicitly delegate bounded reference research; improve Design and Critic instructions around real visual evidence, distinct directions, task-based critique, and honest limitations | existing native role/bundle | RED→GREEN native-bundle test; full package tests | -| DS-2 | Update existing design guidance, optional project context entrypoint, and handoff example for approved/rejected references, semantic patterns, and feedback provenance | DS-1 language | review source/package docs against the approved responsibility boundary | -| DS-3 | Verify source bundle, package contents, and credential-isolated V2 catalog; freeze exact diff and prepare a separate global preview/quality-pilot proposal | DS-1, DS-2 | `npm run check`, `npm run check:package`, `git diff --check`, isolated V2 verifier | - -**Review focus:** A read-only Critic cannot honestly claim visual inspection when the selected model lacks image input or no PNG is supplied; Research must not change Design's final authority; differing design directions must change the product/interaction idea rather than just colors; an unapproved preference must not enter project design truth; installed global files and service remain unchanged until separately approved. Existing six-screen pilot captures provide a quality baseline, not a passing result. - -**Acceptance boundary:** DS-1–3 can prove the native configuration and documented method. Only a provider-backed, real-product pilot with user judgment can test whether output quality improved; that is not covered by credential-isolated catalog tests or a reviewer PASS. - -**Source-phase evidence (2026-09-28):** Baseline `npm run check` 22/22. DS-1's new native-bundle test failed 3/4 as expected because Design lacked the explicit `research` grant, then passed 4/4. Independent review found that Research still had shell access despite its read-only prompt. A new assertion failed 3/4, then passed 4/4 after adding Research `shell: deny`. The isolated catalog checker gained Design→Research and Research shell-denial assertions, with focused RED 5/6 then GREEN 6/6. The private-server startup parser was RED 6/7 then GREEN 7/7. Refreshed full `npm run check` 25/25, `npm run check:package` PASS, and `git diff --check` PASS. Source changes include Design/Research/Critic native role metadata and prompts, focused tests/verifier, and existing design/install guidance; no UI, provider, or global files were modified. The renewed read-only installer preview against `/home/ksi/.config/opencode` reports precisely three conflicts: `agents/design.md` (Research grant and method), `agents/research.md` (shell denial), `agents/design-critic.md` (critique method). Installed `opencode.jsonc` remained SHA256 `d1916c10f2c35cb2e59e986eeac4d8b84f71332a889600a49dc6b7b1c87d986e`. - -**Current runtime evidence and limit:** The old verifier called `opencode debug agents`, which on V2 follows the default shared-service path; its previous “isolated” PASS may have observed the shared catalog and is not reliable as proof of isolation. The same command timed out twice in a fresh temporary environment. A replacement verifier starts its own loopback `opencode serve --port 0` with the seven-key environment allowlist, uses only its ephemeral generated HTTP Basic password, queries only `/api/agent` and `/api/skill`, and terminates that exact child. This OpenCode 2.0.18 run used no shared/provider credentials (ephemeral local Basic auth only) and PASSed file checks, effective nine-role modes/permissions, and both skill catalog paths (agent attempts 3; skills 1); verifier-issued provider requests 0, child-initiated egress unmonitored. The shared service was not interrupted. A catalog PASS still does not prove prompt adherence, Critic image perception, output quality, or Human acceptance; a real-product provider-backed pilot remains needed. - -**Independent review:** First pass CHANGES_REQUIRED for the Research shell escape and missing effective catalog assertion. Both were corrected with RED→GREEN tests; the verifier was also made private-server-based after discovering the old shared-service path. Re-review PASS, no Critical/Important findings; its sole Minor was ambiguous “no-auth” wording, corrected to distinguish absent provider credentials from ephemeral local HTTP Basic auth. This review is source/runtime-contract evidence, not a design-quality acceptance. - -**Promotion freeze (read-only):** Branch `fix/opencode-v2-plugin-migration-20260926`, HEAD `38073f0759037e5efe250822895708ecb21b5b6d`, existing dirty native-installer WIP retained. Generated candidates → installed-before SHA256: `agents/design.md` `9f4bc99438d65543d1e040ae9de2e966566bd8ea0dec986462615293fbfaae3e` → `46a5178a0839ca17847deded1153222417dfd5fb02d1edba548039b7b3cb0341`; `agents/research.md` `6da11cb2c552da546add86ba248c5600044d2005a714e49e688ea24fae966556` → `f4aa2116e11e512d2e6e944b85f45feb6d01b98e32898859c287290932c9a96a`; `agents/design-critic.md` `ad7c1b144bd3d80141b85d04dc354646ca037f5239ffa5d397e8f43facf24b98` → `86f05020c5de9f812e6419c1687b4677bdbe29e47628d245c5eef25504f2b1ba`. `opencode.jsonc` stays `d1916c10f2c35cb2e59e986eeac4d8b84f71332a889600a49dc6b7b1c87d986e`. Before any separately approved apply, recheck these exact target/candidate hashes, retain recoverable private backups, and review the three-file diff; subsequent edits invalidate the affected freeze. - ---- - -## OpenDesign component kit — KSI-20260928-OPEN-DESIGN-KIT - -The Human approved a Build-first, optional-Design direction and clarified that “use OpenDesign” means bringing useful components into the KSI package, not running its app/daemon or connecting an always-on MCP. This section supersedes the earlier no-OpenDesign-copy scope for the exact selected kit only; it does not authorize provider calls, new global writes, shared-service restart, commit, push, or release. - -- **OD-1 — Pinned audit:** Read `nexu-io/open-design` at `1b47e60bd46641469fcd8b69c496c4e3a548bc28`. Selected self-contained `frontend-design` (Apache-2.0) and `web-design-guidelines` (MIT, with pinned reference); excluded catalog-only third-party install pointers, fixed dashboard template, and mandatory nine-heading reference contract. Copied the full Neutral Modern package as a reference, never as the project design source. All vendored files were independently compared with upstream Git blobs; provenance and licenses are included. -- **OD-2 — Distribution:** `--with-design-kit` adds exactly the two complete OpenCode `skills/` directories to the six-role native installer. Default installation remains six roles. Existing conflict preview/explicit replacement/backups apply to skills; nested pre-existing symlinks are refused. The complete neutral system is packaged but not installed. RED→GREEN tests cover opt-in contents, idempotence, nested symlink, conflict/backup, provenance, and package installation. -- **OD-3 — Roles and guidance:** Design Critic is no longer hidden in source. Public docs make Build the normal UI entry point, Design an optional peer Primary, and design skills/critique optional. Built-in Build/Plan/Explore remain unmodified. No Codex, global OpenCode, or shared-service configuration was changed by this kit. -- **OD-4 — Deferred pilot:** The approved copy-based Build versus Design comparison using `design-previews/index.html` needs live model/provider calls. The prior no-provider authorization remains; the original artifact was not edited and no quality, cost, or Human acceptance conclusion is claimed. - -Verification after source integration (2026-09-28): `npm run check` 21/21 PASS; `npm run check:package` PASS with offline tarball + opt-in install; `node scripts/verify-native-v2-isolated.mjs` PASS on OpenCode 2.0.18 with visible Critic, both skill IDs loaded from the isolated config, built-in roles unchanged, and zero verifier-issued provider requests (child egress unmonitored); `git diff --check` PASS. Independent read-only whole-kit review returned PASS with no Critical/Important findings; two Minor follow-ups were recorded: preflight symlink checks do not atomically eliminate local TOCTOU, and future vendored-file drift could benefit from a locked full-inventory/hash check. The reviewer confirmed current vendored hashes and package completeness. At this source-only milestone, global Critic was still hidden and shared service was not yet restarted or verified. m03 Human acceptance remained pending. - -**Approved global follow-up, 2026-09-28:** The Human then authorized global adoption and restart. Exact preview was one conflict (`agents/design-critic.md`, only `hidden: true` removed) plus five new skill files; all other roles and JSONC were unchanged. Private pre-change copies are in `/home/ksi/.local/state/ksi-harness-backups/open-design-kit-20260928.gNsEkA/`; the installer also created `/home/ksi/.config/opencode/agents/design-critic.md.bak-1790574158987-0a949771`. After matching preflight hashes, `--with-design-kit --apply --replace` changed exactly those six files, and repeated preview found zero changes. `openchamber.service` was restarted and observed active. Direct unauthenticated OpenCode endpoints returned 401; a fresh authenticated OpenChamber browser session then obtained HTTP 200 agent/skill catalogs from the restarted service. The expected nine modes were present, Critic was visible, both new skills pointed to global paths, Developer model/variant/steps and default nondelegation survived, and Design/Critic permissions matched intent. The session was closed without saving auth state. No provider request, live delegation, Build edit, visual comparison, commit, push, or release occurred. m03 Human acceptance is still pending. - -**Approved provider-backed pilot, 2026-09-28:** The Human then authorized the copy-based comparison. Standalone Build and Design used the same brief and `opencode-go/muse-spark-1.3-contributor#high`, each editing only an independent copy of `design-previews/index.html`; original SHA256 remained `f107b3c1bfd6877bebbc7a53fe580cd3de651899cf2329a25303360efc2d79b6`. Build loaded both installed skills and completed. Design's first non-interactive run and a TTY retry both stopped when its explicit `external_directory: ask` rule required approval to read the global `web-design-guidelines/references/guidelines.md`; a later `--auto` run completed, so the comparison has a permission-mode caveat. No global permission change was made. Browser captures at desktop 1440×900 and mobile 390×844 had no page errors or horizontal overflow; this one comparison favored Build's less sequential, shorter mobile explanation, while Design retained an overrestrictive legacy sentence. HTML and six PNGs are preserved in `/home/ksi/.local/state/ksi-harness-pilots/2026-09-28-build-design/`; neither was promoted to the original preview. A separate standalone Build→Developer live probe succeeded and independently verified `status=delegated`, resolving the original subagent-runtime symptom in that environment, not proving shared-service live delegation. See `docs/verification.md` for limitations. Global Design skill-read friction remains to be addressed through a newly reviewed exact diff/backed-up global update and separate restart approval. m03 Human acceptance remains pending; no commit/push/release. - -**Design managed-skill permission source correction, 2026-09-28:** The pilot finding was traced to the effective rule order: OpenCode V2 allows its managed global config directory, but the KSI Design role appended `external_directory: * → ask` after that allow. A project-local role overlay did not remove the existing global rule. RED/GREEN coverage was added to the actual isolated agent-catalog verifier for the installed guidelines reference; the source Design role now omits only that redundant ask, leaving native external-directory defaults and unrelated web/MCP asks intact. `npm run check` passed 22/22; package and diff checks passed; isolated OpenCode 2.0.18 catalog passed after three transient service-start timeouts correlated with a separate background service from this investigation (causality not proven; the managed OpenChamber service stayed active). Independent review found one Important documentation-state mismatch, corrected in `docs/verification.md`, plus one deferred Minor: the verifier's wildcard matcher covers current emitted patterns but not every V2 wildcard/action form. Read-only global preview showed exactly one conflict: delete the 62-byte `{"action":"external_directory","resource":"*","effect":"ask"},` from `agents/design.md`; pre-change global Design backup is `/home/ksi/.local/state/ksi-harness-backups/design-skill-permission-20260928.axtX9s/design.md.before` (SHA256 `f3301fac571ccce6cec95c923d906064df65eccdf27473fe8f0269858737c378`). This source-only state was followed by the separately approved global correction below. - -**Approved global Design correction and restart, 2026-09-28:** The Human authorized the exact one-file change and shared-service restart. Preflight live/backup hashes matched `f3301fac571ccce6cec95c923d906064df65eccdf27473fe8f0269858737c378`; installer `--with-design-kit --apply --replace` deleted only the Design external-directory catch-all ask and created `agents/design.md.bak-1790583427430-be3c6107`. Installed Design now hashes `46a5178a0839ca17847deded1153222417dfd5fb02d1edba548039b7b3cb0341`; repeat preview has zero changes; global JSONC stayed at `d1916c10f2c35cb2e59e986eeac4d8b84f71332a889600a49dc6b7b1c87d986e`. `openchamber.service` restarted active/running with a new PID. A no-`--auto` standalone Design provider run loaded `web-design-guidelines`, read its global reference, and edited only a disposable probe to `status=read`. Authenticated catalogs from the restarted OpenChamber service returned HTTP 200, Design Primary, both global skills, and an effective managed-global-directory `allow` rule after the base `ask`. No shared-service model prompt or Human m03 end-to-end acceptance is claimed; no commit, push, or release. - ---- - -## Source cleanup and approved global configuration — KSI-20260928-NATIVE-CLEANUP - -The Human requested the next improvements, legacy-file cleanup, and complete global configuration. This supersedes the immediately following paragraph's **no-global-edit** limit for this exact migration; it does not authorize provider/credential use, commit/push/release, or an ambiguous shared-service restart. The source remains the lightweight six-role installer, not an OpenCode plugin. A separate question about restarting `openchamber.service` and the intended OpenDesign upstream is pending, so neither action was taken. - -- **Recoverability:** Before deletion, the potential legacy-source graph was archived privately at `/home/ksi/.local/state/ksi-harness-backups/pre-native-cleanup-20260928-38073f0.tar.gz` (SHA256 `96e2c8270d5a601ba1500e57a29b774855e87ea0325c511519497d760b1168c8`); the separately tracked old continuity CLI was copied to the same backup directory. Neither the other dirty worktree nor untracked `design-previews/` was modified. -- **Source change:** Removed the retired plugin entrypoint, `agents/` and `instructions/` prompts, plugin-only source and tests, generator, audit and V2-plugin verifier, legacy JSON examples (including stale model routing), and continuity hook CLI. The native installer, six role templates, four focused test files, package check, and no-auth native V2 verifier remain. Added RED/GREEN cleanup regressions and removed the obsolete `npm audit` script. Design, critique, design-system entrypoint, handoff, and parallel guidance are now proportional and optional; no parallel-specific agent was added. The optional Playwright example uses V2 `mcp.servers`, is disabled by default, and discloses possible `npx` download/execution if enabled. Historical specs/plans and user artifacts were preserved. -- **Global configuration:** Privately backed up pre-change `opencode.jsonc` (SHA256 `3b4ee72d5477536832d78cb8c98825a81fd9569188aae01a8c02ff6aedd160e3`) and `AGENTS.md` (SHA256 `8301689d6950301b7665c052d0a05d1fc84011b388cf5b8a8d9314df8c3d417b`). Exact diff artifacts are `/home/ksi/.local/state/ksi-harness-backups/opencode.jsonc.light-native.diff` and `opencode-AGENTS.md.light-native.diff`. After confirming live-file hashes matched the backups, applied those exact candidates and installed six Markdown roles in `/home/ksi/.config/opencode/agents/` with the existing collision-safe installer. Post-change hashes equal the candidates: JSONC `d1916c10f2c35cb2e59e986eeac4d8b84f71332a889600a49dc6b7b1c87d986e`, AGENTS `ddf3dc3409202c19c43bca57cb8cdf21c1ac8ba2692c8cdbc57ff5b02e5f6849`. The KSI plugin registration, blanket deny, stale instruction pointer, and KSI-owned role descriptions/prompts/permissions were removed; user model/positive steps, unrelated plugins, provider/MCP config, and comments were preserved. No native command files were added. -- **Current checks:** `npm run check` PASS 16/16 after review corrections; `npm run check:package` PASS offline; `git diff --check` PASS; `node scripts/verify-native-v2-isolated.mjs` PASS on OpenCode 2.0.18 with expected nine modes, built-ins untouched, hidden Critic, preserved Developer model/steps, no commands and zero verifier-issued provider requests (child egress unmonitored). `openchamber.service` was observed active but not restarted. No credential, provider, authenticated catalog, UI, live edit/delegation, Human m03 acceptance, commit, push, or release check was performed. -- **Independent review:** First combined-diff review found two Important issues (auto-enabled `npx` MCP example and stale shipped model routing sample). Focused RED/GREEN fixes were made. Scoped re-review returned PASS with no Critical, Important, or Minor findings; it independently ran the 5 focused package tests, offline package check, and `git diff --check`, with Git status unchanged. - -**Next:** Obtain an explicit shared-service restart decision, then if approved restart and inspect the actual shared catalog without provider calls or credentials; a 401 remains unavailable evidence. Resolve which OpenDesign project the Human intended before any import. Keep m03 `in_progress` until Human end-to-end acceptance. - ---- - -## Lightweight native distribution supersession — KSI-20260928-LIGHT-HARNESS - -The Human subsequently chose a smaller, non-punitive installation contract. This is the current source target; the nine-role/two-command installer section and plugin sections below are historical, not current distribution requirements. Reuse this dirty branch/worktree and preserve its WIP. No global edit, shared-service restart, provider/credential use, commit, push, or release is authorized by this follow-up. - -**Current contract:** Ship six optional native Markdown agents (`design`, `developer`, `test-runner`, `reviewer`, `research`, `design-critic`). Leave OpenCode's built-in Build, Plan, and Explore prompts and model choices alone; do not install `/complete` or `/review`. Keep role prompts short, no KSI skill whitelist or global deny-all, and only narrow evidence-role action limits. `developerTestRunner` stays disabled by default. Offer a project `AGENTS.md` template without writing it automatically. Keep the installer preview/collision/backup behavior and preserve existing models, variants, positive steps, and unrelated config. Superpowers and the user's goal plugin remain external components, not KSI copies. - -**Source implementation:** `src/native-roles.mjs` and `templates/agents/*.md` now own the six role definitions; `src/native-bundle.mjs` renders only those files. `bin/ksi-opencode.mjs` applies only directories represented in the bundle and leaves legacy agent/command files untouched. `package.json` includes only the CLI, native role source/templates, optional project guidance template, and public docs/examples; no KSI plugin entrypoint or command file is packed. Older plugin code/tests are preserved as unshipped WIP. Current tests/package gate cover the six-file contract, legacy-file preservation, and no-auth isolated catalog results. - -**Verification at this milestone:** RED/GREEN focused tests preceded source changes. Independent review found three Important issues: replacement could discard native frontmatter routing, Design guide overstated enforcement, and Critic guide claimed an unset step budget. The first correction's YAML-key coverage failed re-review; expanded indented/quoted-key cases were RED, then GREEN. `--apply --replace` now refuses any conflicting file with protected routing frontmatter before writing; docs distinguish prompt guidance from native enforcement and disclose non-transactional partial-apply risk. After correction, full `npm run check` passed 210/210, `npm run check:package` passed offline tarball install, `npm pack --dry-run --json` listed 26 files without plugin/command entries, and `git diff --check` passed. Disposable OpenCode 2.0.18 verification passed: six installed files, no built-in override or installed commands, intended nine resulting role modes, hidden Critic, retained Developer model/steps, and no Developer delegation by default. Verifier-issued provider requests: zero; child egress not monitored. No live model delegation/edit or shared-service check was performed. - -**Independent review:** Scoped re-review after the second correction returned PASS with no Critical, Important, or Minor findings. It independently ran installer tests 6/6 and `git diff --check`; Lead ran the full checks above. Git status was unchanged by review. - -**Separate global migration proposal, not applied:** Current `/home/ksi/.config/opencode/opencode.jsonc` SHA256 is `3b4ee72d5477536832d78cb8c98825a81fd9569188aae01a8c02ff6aedd160e3`. A future reviewable candidate should (1) remove only the KSI repository entry from `plugins`; (2) remove the top-level `permissions: [{action:"*",resource:"*",effect:"deny"}]` and stale `instructions: ["mobbin-design.md"]` after confirming they have no intended unrelated use; (3) remove `agents.build` and `agents.plan` overrides, retaining native built-ins; (4) reduce `agents.explore` and the six custom `agents` objects to user-owned `model` and positive `steps` (plus any unrelated fields), allowing installed Markdown to own mode, description, prompt, permissions, and hidden state; and (5) install the six Markdown files after exact collision checks. The global OpenCode `AGENTS.md` also currently says Plan may edit only the checkpoint; a separate exact diff should remove that built-in-role constraint while retaining shared project guidance. Codex's independent global policy needs no automatic rewrite. Before any global write, re-read and hash each target, create private backups, show a byte-exact diff, and obtain fresh approval naming the global files and service restart. The earlier approval recorded below applied only to its earlier exact candidate, not this new migration. Shared OpenChamber catalogs and Human end-to-end acceptance remain pending; m03 is not done. - ---- - -## Historical installer-native supersession — KSI-20260928-INSTALLER - -The Human approved removal of the KSI runtime plugin in favor of a portable installer for native V2 roles and commands. This was the executable direction at that point, superseded by the lightweight distribution above. All plugin-runtime tasks and success claims below remain dated history, not requirements for the current replacement. Work in this same dirty task checkout; do not discard prior WIP, change shared/global configuration, restart a service, access credentials/providers, commit, push, or release. - -**Acceptance:** The source package is a standalone CLI, not a loadable OpenCode plugin. A no-auth disposable install creates native `agents/*.md` and `commands/*.md`; Build/Plan/Design are Primary, six KSI workers are Subagent; `/complete` and `/review` select Build; models, variants, and existing positive steps are not written or replaced. Preview is the default; collisions require explicit replacement and backups. Existing plugin registration is removed only after separate global-config approval. Runtime-only tools/hooks are honestly de-scoped. - -**File map:** `src/native-bundle.mjs` renders role/command Markdown from existing prompt/policy sources; `bin/ksi-opencode.mjs` performs safe preview/install; `package.json` exposes the CLI and no plugin entrypoint; `scripts/check-package.mjs` verifies the tarball; `test/native-bundle.test.mjs`, `test/installer.test.mjs`, `test/native-package.test.mjs`, and `test/verify-native-v2-isolated.test.mjs` cover behavior; `README.md`, `INSTALL.md`, `opencode.jsonc.example`, and current `docs/*` describe native-only scope. Existing plugin source/test history is retained in this dirty checkout but excluded from the distributable tarball until cleanup can be separately verified. - -- [x] **N1 — Native bundle:** RED/GREEN role and command tests; nine native agents, two commands, no model/steps or removed `ksi_*` grants; Developer opt-in remains off by default. -- [x] **N2 — Safe installer:** RED/GREEN preview, clean install, idempotence, collision refusal, explicit replacement/backup, JSONC preservation, and symlink tests. -- [x] **N3 — Distribution/docs:** RED/GREEN package tests; CLI-only private source manifest and offline local-tarball install; current docs explain native and removed runtime behavior. Former plugin source/tests remain unshipped WIP in this checkout, not an active package entrypoint. -- [x] **N4 — Isolated evidence:** Disposable no-auth V2.0.18 CLI/debug catalog verifies roles, permissions, retained model/steps, and command registration after bounded config loading. Command agent switch and actual edit/delegation were not exercised. Shared service remains separate. - -### Installer-native evidence (2026-09-28) - -- Native bundle tests: RED missing module, then GREEN 3/3; removed-tool grant regression RED 2/3 then GREEN 3/3. -- Installer tests: RED missing CLI 0/4, then GREEN 4/4. Native-package tests: RED 0/2, then GREEN 2/2 after manifest/docs changes. Isolated verifier tests: RED missing script, then GREEN 3/3 after contract refinement. -- `npm run check` passed 209/209, `npm run check:package` passed an offline installed CLI invocation, `npm pack --dry-run --json` listed 28 public files without `index.mjs` or `src/plugin-v2.mjs`, and `git diff --check` passed. These are source/package checks, not remote CI. -- `node scripts/verify-native-v2-isolated.mjs` exited 0: OpenCode 2.0.18, nine expected modes, Build/Design effective wildcard edit permissions, Build shell recovery, default Developer nondelegation, hidden Critic, retained Developer model/steps, `/complete` and `/review` catalog names. The first cold catalog can be empty before native layers load; the verifier retries at most eight times and inspects the same isolated background service. V2 command catalog omits `agent`, so `agent: build` is source-file evidence only. No model request/edit was issued; child egress unmonitored. -- Global directory path preflight found no existing same-name Markdown agent/command files. The current global JSONC still has a KSI plugin entry. No global write, service restart, provider call, credential read, commit, push, or release occurred in this superseding work. The old V1 npm tarball is unchanged; source manifest is private. - ---- - -> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Repair KSI loading on OpenCode V2 by moving KSI custom roles to native configuration while keeping Build and Design capable of normal, recoverable work. - -**Architecture:** The plugin registers transforms only for built-in `build` and `plan`; all seven KSI role definitions are generated as native V2 configuration snippets. A delegation hook reads the post-materialization agent catalog and rejects absent or non-KSI role signatures at use, which prevents native Explore from silently substituting for the configured KSI Explore role without making startup brittle. - -**Tech Stack:** Node.js ESM (`>=20`), `node:test`, OpenCode V2.0.18 plugin APIs and JSONC/JSON examples; no new dependencies. - -**Spec:** `docs/superpowers/specs/2026-09-27-v2-native-agent-ownership-design.md` - -## Status and supersession - -Current executable follow-up: 2026-09-28. It supersedes only the conflicting strict-role material in the historical 2026-09-26 plan below: plugin-time custom-role transforms, missing-role startup failure, Build coordinator-only edit/shell denial, and `design-previews/**` permission enforcement. Historical tests and dated evidence remain evidence of what was run, not evidence that those contracts remain current. - -Subsequent Human approval on 2026-09-28 authorized the exact reviewed global-config merge, pre-change backup, and `openchamber.service` restart recorded in **Approved shared-service application** below. That approval supersedes the earlier no-global-edit/no-restart constraint only for those actions; it does not authorize credentials, provider calls, publication, or further config changes. - -Branch/baseline: `fix/opencode-v2-plugin-migration-20260926` at `38073f0`, with existing uncommitted migration work preserved. Product state remains `m03` in progress and unaccepted. - -## Global Constraints - -- Keep the exact role map: Primary `plan`, `design`, `build`; Subagent `explore`, `developer`, `test-runner`, `reviewer`, `research`, `design-critic`. -- Native config owns custom-role mode, description, system prompt, minimal permissions, hidden state, and default steps; plugin transforms only built-in `build` and `plan`. -- Build normally delegates but can directly read, edit, run shell, and use LSP to complete or recover approved work. Design has normal workspace capability; `design-previews/` is a convention only. -- Do not use `**/design-previews/**`, a root-derived preview rule, a path gate, or `*: deny` lockdown for productive Build, Design, or Developer roles. Keep evidence-role independence through explicit narrow action denies. -- Preserve existing config-shaped `model`/`variant`, valid positive integer `steps`, `request`, `disabled`, and unrelated fields. Do not replace whole user agent objects. `developerTestRunner` remains `false` by default. -- Missing native snippets must not disable plugin tools, commands, hooks, events, or cleanup. At delegation, absent KSI roles and built-in Explore without the KSI signature must fail clearly and read-only; never synthesize/upsert an agent. -- Do not edit `/home/ksi/.config/opencode/opencode.jsonc`, any OpenChamber-managed config, credentials, providers, services, or `design-previews/`. Leave `.opencode/` project state unchanged except the required shared checkpoint update at the Task 4 evidence milestone. No shared-service restart, provider call, commit, push, release, publish, or milestone acceptance. -- Work in this checkout because its dirty migration is the task baseline. Do not install dependencies or perform a network install in the source checkout; the existing disposable, offline local-tarball install inside `npm run check:package` remains permitted. Use only Node built-ins and disposable temporary directories. - -## File map - -| Path | Responsibility | -| --- | --- | -| `src/agents.mjs` | KSI role metadata, soft V2 permission builders, strict pure config merge, generated role signature, and Build/Plan-only transform. | -| `src/plugin-v2.mjs` | Plugin setup and delegation-time read-only catalog/signature check; remove Build/Design path and coordinator-only hook guards. | -| `agents/build.md`, `agents/design.md`, `instructions/harness.md` | Make delegation the normal workflow, not a hard implementation prohibition; document preview as a convention. | -| `scripts/generate-agent-config.mjs` | Dependency-free deterministic renderer for both role snippets. | -| `examples/ksi-agents.default.json`, `examples/ksi-agents.developer-test-runner.json` | Generated, copyable native `agents` objects for default and explicit opt-in. | -| `opencode.jsonc.example`, `INSTALL.md`, `README.md`, `docs/architecture.md`, `docs/design.md`, `docs/design-critique.md`, `docs/execution.md`, `docs/releasing.md`, `docs/troubleshooting.md`, `docs/verification.md`, `examples/design.project.jsonc` | Non-destructive merge/install guidance and current truthful behavior; dated historical evidence stays dated. | -| `test/support/opencode-v2-context.mjs` | Fixture with independent transform-time and final catalog agent collections plus `ctx.agent.list()`. | -| `test/ksi-agent-config.test.mjs`, `test/plugin-v2.test.mjs`, `test/design.test.mjs` | RED/GREEN coverage for ownership, signatures, role modes, soft Build/Design behavior, and no path gate. | -| `test/package.test.mjs`, `scripts/check-package.mjs`, `package.json` | Generated-file byte parity, package manifest, disposable tarball install, and package-facing docs/examples. | -| `scripts/verify-v2-isolated.mjs`, `test/verify-v2-isolated.test.mjs` | Disposable OpenCode 2.0.18 runtime verifier and its isolation tests; it supplies no provider/auth/MCP configuration, issues catalog-only verifier requests, and never targets the shared service. Child-initiated egress is unobserved. | - -## Interfaces and dependencies - -| Producer → consumer | Interface and contract | -| --- | --- | -| `src/agents.mjs` → generator/tests/plugin | `BASIC_READ`, `KSI_EVIDENCE`, `GOAL_READ`, `GOAL_WRITE`, `EXTERNAL_NETWORK`, `DESIGN_EXTERNAL`, the named skill/child sets below, and `buildKsiAgentSnippet({ prompts, designPrompt, developerTestRunner }) -> { agents }`; the snippet contains exactly `KSI_OWNED_ROLES`, no model field, exact mode map, generated prompts/permissions, and opt-in-only Developer → Test Runner grant. | -| `src/agents.mjs` → config installer/tests | `mergeKsiAgentConfig(existing, ksi) -> object`; validates plain objects/rule arrays and positive supplied `steps`, preserves opaque `model` (including `{ providerID, model, variant }`), `request`, `disabled`, and unrelated fields, and appends KSI permission rules after existing rules. | -| `src/agents.mjs` → plugin hook | `assertKsiRoleSignature(agent, role, expected) -> void`; accepts the generated mode/description/system/hidden state and terminal generated permission suffix while allowing user model/request/unrelated fields and a valid positive user step override; otherwise throws a role-specific native-config remediation error. | -| `src/plugin-v2.mjs` → V2 context | `ctx.agent.transform(editor)` updates only `build` and `plan`; `ctx.agent.list()` is called only when a KSI delegation target is requested, then passed through existing response normalization and `assertKsiRoleSignature`. | -| generator → package gate | `node scripts/generate-agent-config.mjs --output-dir ` writes exactly the two named JSON files. `check-package` byte-compares them with `examples/` before packing. | -| isolated verifier → runtime evidence | `node scripts/verify-v2-isolated.mjs --plugin-dir --opencode-bin ` prints a bounded JSON result for `with-snippet` and `without-snippet`, then removes only its own `mkdtemp` directory. | - -## Per-role native permission contract - -Except for the named skill/child expansions below, every listed rule has `resource: "*"`. `allow(A)` means `{ action: A, resource: "*", effect: "allow" }`; `ask(A)` and `deny(A)` use the same resource with that effect. `allow(_SKILLS)` expands to `{ action: "skill", resource: , effect: "allow" }`; `allow(_CHILDREN)` expands to `{ action: "subagent", resource: , effect: "allow" }`. The implementation defines these exact named action arrays and uses only their entries; it does not emit `{ action: "*", resource: "*", effect: "deny" }`. - -| Constant | Exact actions | -| --- | --- | -| `BASIC_READ` | `read`, `glob`, `grep`, `list` | -| `KSI_EVIDENCE` | `ksi_repo_status`, `ksi_repo_diffstat`, `ksi_checkpoint_read`, `ksi_reconcile`, `ksi_env_probe`, `ksi_audit_summary` | -| `GOAL_READ` | `get_goal`, `get_goal_history`, `list_all_goals` | -| `GOAL_WRITE` | `create_goal`, `set_goal`, `update_goal_objective`, `update_goal`, `update_goal_status`, `clear_goal` | -| `EXTERNAL_NETWORK` | `external_directory`, `webfetch`, `websearch` | -| `DESIGN_EXTERNAL` | `playwright_*`, `mobbin_*`, `gpt_imagegen`, `context7_*` | -| `UNSAFE_BROWSER` | `playwright_browser_run_code_unsafe` | -| `PLAN_SKILLS` / `PLAN_CHILDREN` | `brainstorming`, `writing-plans`, `systematic-debugging`, `ui-ux-pro-max`, `customize-opencode` / `explore`, `reviewer`, `research` | -| `BUILD_SKILLS` / `BUILD_CHILDREN` | `subagent-driven-development`, `executing-plans`, `verification-before-completion`, `systematic-debugging` / `explore`, `developer`, `test-runner`, `reviewer`, `research` | -| `DESIGN_SKILLS` / `DESIGN_CHILDREN` | `brainstorming`, `ui-ux-pro-max`, `mobbin-design`, `systematic-debugging`, `verification-before-completion` / `explore`, `design-critic` | -| `DEVELOPER_SKILLS` / `DEVELOPER_CHILDREN` | `test-driven-development`, `systematic-debugging`, `verification-before-completion` / `test-runner` only when `developerTestRunner === true` | - -Rules are emitted in the order shown in the next table. Named normal grants use `allow`; a role's `{ action: "subagent", resource: "*", effect: "deny" }` appears before each named child allow, and `{ action: "skill", resource: "*", effect: "deny" }` appears before each named skill allow. Therefore the final matching named child/skill allow wins while unlisted subagents/skills stay denied. No other catch-all rule is emitted. - -| Role | Exact ordered rules | -| --- | --- | -| Plan | `allow(BASIC_READ)`, `allow(lsp)`, `allow(question)`, `allow(todowrite)`, `allow(plan_exit)`, `allow(webfetch)`, `allow(websearch)`, `allow(KSI_EVIDENCE)`, `allow(GOAL_READ)`, `deny(edit)`, `deny(shell)`, `deny(skill)`, then `allow(PLAN_SKILLS)`, `deny(subagent)`, then `allow(PLAN_CHILDREN)`. | -| Build | `allow(BASIC_READ)`, `allow(lsp)`, `allow(edit)`, `allow(shell)`, `allow(question)`, `allow(todowrite)`, `allow(webfetch)`, `allow(websearch)`, `allow(KSI_EVIDENCE)`, `allow(GOAL_READ)`, `allow(GOAL_WRITE)`, `deny(skill)`, then `allow(BUILD_SKILLS)`, `deny(subagent)`, then `allow(BUILD_CHILDREN)`. | -| Design | `allow(BASIC_READ)`, `allow(lsp)`, `allow(edit)`, `allow(shell)`, `allow(question)`, `allow(todowrite)`, `ask(EXTERNAL_NETWORK)`, `ask(DESIGN_EXTERNAL)`, `deny(skill)`, then `allow(DESIGN_SKILLS)`, `deny(subagent)`, then `allow(DESIGN_CHILDREN)`, then final `deny(UNSAFE_BROWSER)`. No preview-path rule. | -| Developer | `allow(BASIC_READ)`, `allow(lsp)`, `allow(edit)`, `allow(shell)`, `allow(question)`, `allow(todowrite)`, `ask(external_directory)`, `ask(webfetch)`, `ask(websearch)`, `ask(context7_*)`, `deny(skill)`, then `allow(DEVELOPER_SKILLS)`, `deny(subagent)`, then only when opted in `allow(test-runner)`. | -| Explore | `allow(BASIC_READ)`, `allow(lsp)`, then `deny(edit)`, `deny(shell)`, `deny(subagent)`, `deny(external_directory)`, `deny(webfetch)`, `deny(websearch)`. | -| Test Runner | `allow(read)`, `allow(shell)`, then `deny(edit)`, `deny(subagent)`, `deny(external_directory)`, `deny(webfetch)`, `deny(websearch)`. | -| Reviewer | `allow(read)`, `allow(lsp)`, `allow(KSI_EVIDENCE)`, then `deny(edit)`, `deny(shell)`, `deny(subagent)`, `deny(external_directory)`, `deny(webfetch)`, `deny(websearch)`. | -| Research | `allow(read)`, `allow(webfetch)`, `allow(websearch)`, `allow(context7_*)`, then `deny(edit)`, `deny(shell)`, `deny(subagent)`, `deny(external_directory)`. | -| Design Critic | `allow(read)`, then `deny(edit)`, `deny(shell)`, `deny(subagent)`, `deny(lsp)`, `deny(skill)`, `deny(external_directory)`, `deny(webfetch)`, `deny(websearch)`, `deny(mobbin_*)`, `deny(gpt_imagegen)`, `deny(playwright_*)`, `deny(context7_*)`. These denials follow the read grant and retain Critic read-only/visual-inspection boundaries. | - -## Review Focus - -- Transform editor containing only `build`/`plan` must still register all plugin surfaces; own with Task 1 lifecycle tests and Task 4 isolated `without-snippet` case. -- A native built-in Explore must not satisfy KSI Explore; own with Task 1's signature-mismatch delegation assertion. -- Config-shaped model/variant, positive user steps, `request`, `disabled`, and unknown fields must survive merge exactly; own with Task 2 merge tests. -- Build must be able to use direct edit/shell/LSP and Design must edit a normal workspace path; neither may retain a preview-path rejection; own with Task 1 policy/hook tests. -- Generated artifacts must be byte-identical to policy/prompt source and package them without internal ledgers; own with Task 3 parity and tarball tests. - -## Ownership and review boundaries - -| Task | Writer-owned paths | Independent reviewer checks | -| --- | --- | --- | -| 1 | `src/agents.mjs`, `src/plugin-v2.mjs`, `agents/build.md`, `agents/design.md`, `instructions/harness.md`, `test/support/opencode-v2-context.mjs`, `test/agent-policy-v2.test.mjs`, `test/bookkeeping-policy.test.mjs`, `test/design.test.mjs`, `test/evidence-tools.test.mjs`, `test/goal-federation.test.mjs`, `test/index.test.mjs`, `test/ksi-agent-config.test.mjs`, `test/plugin-v2.test.mjs` | No custom ID is read/updated in transform; direct recovery did not widen evidence roles; late validation is read-only and covers Explore. | -| 2 | generator, two generated snippets, merge/snippet tests | Generated data is sourced once, merge is non-destructive, opt-in is off by default, no model is emitted. | -| 3 | package gate, manifest, package tests, configuration/design examples and current docs | Snippets ship and match bytes; guidance never instructs automatic global edits; old strict claims are not presented as current. | -| 4 | isolated verifier and verification evidence | Temp isolation supplies no provider/auth/MCP configuration, verifier requests are catalog-only, and conclusions distinguish isolated source/package evidence from shared service; child egress is unobserved. | - -Each task finishes with its focused test evidence, `git diff --check`, and an independent reviewer gate on the exact dirty baseline. The reviewer is read-only and reports findings; no task authorizes a commit. - -## Tasks - -### Task 1: Soft native lifecycle and delegation behavior - -**Files:** Modify `src/agents.mjs`, `src/plugin-v2.mjs`, `agents/build.md`, `agents/design.md`, `instructions/harness.md`, `test/support/opencode-v2-context.mjs`, `test/agent-policy-v2.test.mjs`, `test/bookkeeping-policy.test.mjs`, `test/design.test.mjs`, `test/evidence-tools.test.mjs`, `test/goal-federation.test.mjs`, `test/index.test.mjs`, `test/ksi-agent-config.test.mjs`, `test/plugin-v2.test.mjs`. - -**Depends on:** Current approved spec only. **Produces:** Build/Plan-only transforms and a tested late role-signature boundary for Tasks 2–4. - -- [x] Add RED fixture support for separate `transformAgents` and final `catalogAgents`, and expose `ctx.agent.list()` without giving `AgentEditor` an add/upsert method. -- [x] Add RED tests named `built-ins-only transform registers surfaces without KSI snippets`, `delegation rejects missing Developer with native snippet remediation`, `delegation rejects built-in Explore without KSI signature`, `Build can directly edit shell and use LSP for recovery`, and `Design has no preview-path edit gate`; assert the named catalog/tool/command/rule outcomes in the preceding step. Add table-driven assertions that compare every role's complete ordered `{ action, resource: "*", effect }` sequence to **Per-role native permission contract**, including every named constant expansion, no universal catch-all, final named subagent/skill ordering, the Developer opt-in exception, and every evidence-role denial. -- [x] Run `node --test test/ksi-agent-config.test.mjs test/plugin-v2.test.mjs test/design.test.mjs`; observe the expected current-contract failures before implementation. -- [x] Refactor `applyKsiBuiltinsPolicy(editor, prompts, options)` to require/update only `build` and `plan`; delete `applyKsiAgentPolicy`, preview-root helpers, Design override validator, and their call sites. Keep existing model and valid positive Build steps. -- [x] Implement soft productive-role rules: no root catch-all deny for Build, Design, or Developer; Build grants direct read/edit/shell/LSP recovery; Design has workspace edit capability without path matching; Developer stays an implementation-capable Subagent. Keep only explicit edit/shell/subagent/network restrictions needed for Explore, Test Runner, Reviewer, Research, and Design Critic independence. -- [x] Implement `assertKsiRoleSignature` and call `ctx.agent.list()` only immediately before a KSI `subagent` dispatch. Validate final catalog data read-only, reject absent/mismatched roles with role and snippet-remediation text, and never call `editor.update` for a custom role. -- [x] Rewrite Build/Design/harness wording so direct work is permitted for approved recovery, normal delegation remains guidance, and `design-previews/` is a default artifact convention. -- [x] Run the focused command again, run `git diff --check`, and obtain the Task 1 independent review. - -#### Task 1 evidence (2026-09-28) - -- RED: 30/6, plus missing `BASIC_READ`; stale-contract suite 24/8. -- GREEN: focused 40/40; stale-contract suite 32/32; full `npm run check` 197/197; `git diff --check` clean. -- Independent review: frozen review diff `a8c06878...`; Task 1 reviewer **Approved** with no findings. No commits were made. - -### Task 2: Strict native config builder and deterministic snippets - -**Files:** Modify `src/agents.mjs`, `test/ksi-agent-config.test.mjs`; create `scripts/generate-agent-config.mjs`, `examples/ksi-agents.default.json`, `examples/ksi-agents.developer-test-runner.json`. - -**Depends on:** Task 1 exports. **Produces:** The one source of truth for Task 3 package/docs work. - -- [x] Add RED tests for exact seven-role ID set and modes, hidden Design Critic, model omission, default opt-in off, opt-in permission/prompt on, config-shaped model/variant preservation, valid positive user-step preservation, invalid step/rule rejection, and preservation of `request`, `disabled`, and unknown fields. -- [x] Add RED generator tests that require one `--output-dir ` argument, reject missing, duplicate, and unknown arguments, assert the output directory receives exactly the default and opt-in filenames, and byte-compare both outputs to the checked-in examples. -- [x] Run `node --test test/ksi-agent-config.test.mjs`; observe the expected failures from the lenient merge, stale model fixture shape, and absent generator/artifacts. -- [x] Make `mergeKsiAgentConfig(existing, ksi)` a strict pure merge: reject invalid supplied objects, steps, and permission rule arrays rather than dropping/replacing them; preserve user-owned opaque model/variant, valid step, request, disabled, and other keys; apply KSI-owned fields and append valid KSI rules in V2 order. -- [x] Keep `buildKsiAgentSnippet` driven by current role metadata and prompt files. Create the dependency-free generator that requires exactly one `--output-dir ` argument, rejects missing/duplicate/unknown arguments, and always writes both variants: default (`developerTestRunner: false`) and opt-in (`true`). It writes stable `JSON.stringify(value, null, 2) + "\\n"` bytes to the two fixed filenames in one process; it has no variant-selection flag. -- [x] Run `node --test test/ksi-agent-config.test.mjs` and the generator parity test, run `git diff --check`, and obtain the Task 2 independent review. - -#### Task 2 evidence (2026-09-28) - -- Initial RED: 4/6. Initial GREEN: 6/6 and full `npm run check` 199/199. -- Review found a stale KSI-owned hidden-state issue; correction RED: 6/7, GREEN: 7/7 and full `npm run check` 200/200; `git diff --check` clean. -- Re-review: **PASS**. No commits were made. - -### Task 3: Package parity and installation guidance - -**Files:** Modify `package.json`, `scripts/check-package.mjs`, `test/package.test.mjs`, `opencode.jsonc.example`, `INSTALL.md`, `README.md`, `docs/architecture.md`, `docs/design.md`, `docs/design-critique.md`, `docs/execution.md`, `docs/releasing.md`, `docs/troubleshooting.md`, `docs/verification.md`, `examples/design.project.jsonc`. - -**Depends on:** Task 2 generated artifacts. **Produces:** Shippable source/package guidance without an automatic config mutation. - -- [x] Add RED package/doc tests requiring both generated examples in `package.json.files` and `REQUIRED_PACKAGE_FILES`, source-to-example byte parity, V2 `agents` snippet merge instructions, the precise role map, default-off `developerTestRunner`, plugin survival without snippets, Build recovery, and Design convention-not-gate language. Cover `docs/design.md`, `docs/design-critique.md`, `docs/releasing.md`, and `examples/design.project.jsonc` explicitly so no shipped current-behavior text retains V1 syntax, a strict preview path, or coordinator-only Build wording; retain Design Critic's read-only/visual-inspection boundary while replacing its stale preview-permission claim with convention-not-gate wording. -- [x] Run `node --test test/package.test.mjs`; observe failures for missing artifacts/package parity and old “IDs before plugin setup” / strict preview claims. -- [x] Extend `check-package` to generate both snippets into its own temporary directory, byte-compare them before `npm pack`, and assert the packed tarball includes both public JSON examples while still excluding `docs/superpowers/`, tests, and secrets. Do not package the check script merely to run the parity check. -- [x] Replace current, conflicting claims in the listed public docs and `opencode.jsonc.example`: users copy/merge a chosen snippet into each server config; merging preserves their model/variant/request/valid steps/unrelated keys; the plugin never edits their config; a missing snippet leaves plugin surfaces active but makes KSI delegation fail clearly. Preserve dated historical verification entries, and do not claim shared-service success. -- [x] Run `node --test test/package.test.mjs`, `npm run check:package`, `npm pack --dry-run`, and `git diff --check`; inspect the manifest for both snippets and no internal state. Obtain the Task 3 independent review. - -#### Task 3 evidence (2026-09-28) - -- Initial RED: 14/4. GREEN: package tests 18/18, `npm run check:package` passed, `npm pack --dry-run` listed 40 files, and full `npm run check` passed 203/203. -- Fix round corrected stale Build/native-ownership documentation and masking tests. Re-review: **PASS**. -- Deferred for final integration review: package success text may overstate plural built-ins/all plugin surfaces; `docs/verification.md` uses singular permission wording. - -### Task 4: Disposable OpenCode V2.0.18 validation and evidence - -**Files:** Create `scripts/verify-v2-isolated.mjs`, `test/verify-v2-isolated.test.mjs`; modify `docs/verification.md`, this ledger, `.opencode/working-state.md` only at this semantic milestone. - -**Depends on:** Tasks 1–3 and the already installed local OpenCode `2.0.18`. **Produces:** Reproducible isolated evidence, not shared-service acceptance. - -- [x] Add mandatory RED tests in `test/verify-v2-isolated.test.mjs` for verifier argument validation and child environment construction. They reject relative plugin/bin paths and observe that hostile `OPENCODE_*`, provider, credential/auth, and SSH-agent names are absent from a spawned child environment. -- [x] Implement the verifier with `mkdtemp`, separate HOME/XDG config/data/cache/state, loopback-only server, a minimal config that loads the supplied local plugin, no provider/auth/MCP configuration, bounded polling, and `finally` shutdown/removal. Its child environment is the exact `PATH`, `HOME`, `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_CACHE_HOME`, `XDG_STATE_HOME`, and `TMPDIR` allowlist; it never spreads `process.env`. Verifier-issued requests are only loopback catalog GETs and reject redirects; it does not issue model-session or provider endpoints. Child-initiated egress is unobserved. -- [x] Implement `with-snippet` checks for active plugin status, exact role modes, `/complete` and `/review`, six evidence tools, Build direct `read`, `glob`, `grep`, `list`, `edit`, `shell`, and `lsp` recovery permissions, and no Design preview-only rule. Implement `without-snippet` checks for active plugin surfaces, absent custom roles, and non-KSI native Explore; late delegation remains unit-level coverage. These assertions were not reached in the isolated 2.0.18 run because its first catalog request returned 401. -- [x] Run `node scripts/verify-v2-isolated.mjs --plugin-dir "$(pwd)" --opencode-bin "$(command -v opencode)"`. It printed bounded JSON for both cases with exact unavailable evidence: `/api/agent (HTTP 401)`; each temporary root was removed. No server ticket, global/shared configuration, restart, or provider/auth/MCP configuration was supplied. This is not two passing runtime cases; child egress was not observed. -- [x] Run `npm run check` (205/205), `npm run check:package` (passed), and `git diff --check` (clean); record actual outputs and remaining shared-service conditions in `docs/verification.md` and the checkpoint. Keep m03 unaccepted. Fix Round 1 requires the refreshed final packet below. -- [x] Obtain the Task 4 independent review on the final dirty baseline. - -#### Task 4 evidence (2026-09-28) - -- RED: new focused verifier test 0/2, failing only because the module did not exist. GREEN: focused test 2/2 and script syntax check clean. -- Local isolated runtime: OpenCode `2.0.18` started on unique `127.0.0.1` ports under disposable roots with the exact seven-key environment allowlist. Both `with-snippet` and `without-snippet` received `/api/agent (HTTP 401)`. The verifier records this as unavailable, exits nonzero, confirms removal (`cleanup.removed: true`), and claims no plugin/catalog behavior passed. The verifier sent only catalog requests; no provider/auth/MCP configuration was supplied, and child egress was not observed. -- Fix Round 1 addressed redirect handling, shutdown/cleanup, overclaim prevention, and exact Build read-action assertions. RED was 2/6 for absent exports, GREEN 6/6 plus syntax; a final non-ENOENT `removed()` regression was RED 6/7 and GREEN 7/7. The redirect target received zero requests; real-child exit, rejected unconfirmed SIGKILL, ENOENT-only removal, and failed-result cleanup are covered. The authorized isolated runtime was not rerun; its 401 evidence remains unchanged. -- Refreshed final packet: focused verifier 7/7 plus syntax check, `npm run check` 210/210, `npm run check:package` passed, and `git diff --check` clean. Re-review: **Approved**. Deferred for final integration review: a minor port-reservation bind race. No runtime acceptance or shared-service validation is claimed. -- Shared-service/OpenChamber validation, a global config edit/restart, publication, commit, and milestone acceptance remain unperformed and unauthorized. Child-initiated provider activity is not monitored by this verifier. - -## Final integration gate - -- [x] The Lead freezes the exact dirty-file baseline after Task 4, re-runs the narrow affected tests plus `npm run check`, `npm run check:package`, and `git diff --check`, and sends the combined diff to an independent integration reviewer. -- [x] Report isolated verification separately from shared-service verification. The latter remains unperformed unless the user separately approves a global config change and service restart; provider calls remain out of scope. -- [x] Do not commit, push, publish, release, or mark m03 accepted. A Human end-to-end acceptance remains required after the evidence gates. - -### Final integration evidence (2026-09-28) - -- Lead pre-review checks passed: affected tests 68/68, full `npm run check` 210/210, `npm run check:package`, 40-file dry-run package inspection, and `git diff --check`. -- Integration review found one Important defect: role signatures compared permission objects by serialization and rejected harmless object-key reordering. The single correction round now compares exact `action`/`resource`/`effect` values while retaining rule-array order, narrows package claims, corrects V2 `permissions` wording, and retries confirmed `EADDRINUSE` startup collisions at most three times. -- Correction evidence: RED 10 passed/4 failed, then GREEN 14/14; lifecycle RED 10/11, then GREEN 11/11; focused combined 60/60; full `npm run check` 215/215; `npm run check:package` passed; `git diff --check` clean. Independent scoped re-review: **PASS**, with no Critical or Important findings. -- Residual minor: closing the reservation socket before OpenCode binds leaves a small TOCTOU window; bounded confirmed-collision retries mitigate it but do not establish server identity. The prior isolated OpenCode runs remain unavailable at HTTP 401, and the shared service/global configuration remain unchanged and unverified. - -### Approved shared-service application (2026-09-28) - -- The Human approved the exact proposal diff after review. Before writing, the global config SHA256 was `a43959d6545c019314c13fbadedfdc7b0f7c1adb04823bcaf0f88ce6982800dd`; the private 0600 backup `/home/ksi/.config/opencode/opencode.jsonc.pre-ksi-native-20260928` matches it. The applied config SHA256 is `3b4ee72d5477536832d78cb8c98825a81fd9569188aae01a8c02ff6aedd160e3`, matching the reviewed candidate. The diff SHA256 is `08a904f33f2fbcb5d660452ac32cc0bce07d2e8625b623397b18d36fa1964483`. -- The merge populated the seven KSI native roles, preserved existing role models and unrelated config, and replaced only Design/Design Critic's stale per-role catch-all denies. The existing top-level catch-all deny and Build/Plan config entries were not changed; effective permission behavior still needs runtime confirmation. -- `systemctl --user restart openchamber.service` exited 0. The unit is active/running with a new main PID and new managed OpenCode child; `openchamber status` reports one password-protected foreground runtime. Read-only, unauthenticated GETs to the new OpenCode server's `/api/plugin`, `/api/agent`, `/api/command`, and `/api/tool` each returned HTTP 401. No plugin-active, role-mode, command, tool, effective-permission, Design edit, or user end-to-end success is claimed. No password, credential, provider call, or OpenChamber UI authentication was used. -- The authorized configuration/restart work is complete. Further runtime catalog or UI acceptance requires an authorized authentication path; do not bypass the 401 boundary or mark m03 accepted from process liveness alone. - -## Self-review - -- Spec coverage: Tasks 1–4 cover native ownership, transform ordering, soft roles, late failure, merge preservation, generated snippets, installation guidance, package parity, and isolated V2 validation. Shared service remains deliberately excluded. -- Interface consistency: Task 1 produces the signature/build-transform functions consumed by Task 2 and the plugin; Task 2 produces exact files consumed by Task 3; Task 4 consumes the packaged/source configuration without changing it. -- Proportion: code bodies are deliberately absent; every implementation decision that affects behavior is tied to a named interface, test, or command. -- Deviations from the historical plan are explicit above. No unresolved design choice is delegated to an implementer. - ---- - -# Historical 2026-09-26 V2-only and OpenChamber Compatibility Plan - -The dated completion and diagnostic evidence below is retained for history. The old executable plan required all KSI custom IDs in the transform editor, failed plugin setup when they were absent, confined Build to coordination bookkeeping, and enforced a project-root preview path for Design. Those are historical design choices, superseded by the 2026-09-28 native ownership and soft-role contract above; this section must not be used as the current execution contract. - - -## Baseline evidence (before this V2-only refactor) - -- Branch `fix/opencode-v2-plugin-migration-20260926`, HEAD `38073f0`; pre-existing untracked `.opencode/` and `design-previews/` are preserved. -- Previous dual-entrypoint source passed `npm run check` (279 tests), `npm run check:package`, and `git diff --check`. Fresh pre-Task-1 `npm run check` again passed 279/279 on 2026-09-26; these are baseline results, not verification of the planned V2-only result. -- Official V2 plugin, migration, agent, command, tool, and permission docs were checked. OpenChamber package README and installed compatibility code were inspected read-only; no OpenChamber/OpenCode service was queried or restarted. -- Independent reviewer dispatch was unavailable under the current permission policy; no reviewer result is claimed. - -## Task 1 completion evidence - -- `node --test test/agent-policy-v2.test.mjs`: 12 passed, 0 failed. New tests cover atomic missing-agent/invalid-step/unsafe-permission failures; preserved model/system/step selections; role budgets and per-role V2 permission boundaries; exact Plan/Build grants; nested bookkeeping scope; and secret/wildcard action ordering. -- `npm run check`: 291 passed, 0 failed. `git diff --check`: clean. -- The V2 builders are additive at this milestone; the active V1 runtime and behavior tests remain intact for the planned Task 3 cutover. - -## Task 2 completion evidence - -- `node --test test/evidence-tools-v2.test.mjs`: 5 passed, 0 failed. Covers all six V2 definition shapes, JSON Schema property bounds/defaults/enums, direct-call validation (including unknown fields), bounded structured output, and V2 session context for reconcile. -- `npm run check`: 296 passed, 0 failed. `git diff --check`: clean. -- The V2 factory temporarily delegates execution to the preserved V1-shaped helper; Task 3 will remove that helper and move validation/operations into the native V2 factory before deleting the bridge. - -## Task 3 completion evidence - -- `index.mjs` now exports only `{ id, setup }`; `src/plugin-v1.mjs` and legacy V1 permission/tool builders are removed. V2 hooks use native prompt/context/compaction, subagent/shell, command, event, and cleanup APIs. -- Native tests cover role transforms, permissions, task contracts/routing, continuity, output archive/title handling, helper ancestry/lifecycle/cleanup, and scoped bookkeeping. A regression test verifies an external plugin instance directory does not become the active project's bookkeeping root. -- The final `npm run check` passed 203/203 tests after the root-selection, resume-identity, compaction-fallback, and Design-step checks. No OpenCode runtime check was run. - -## Task 4 completion evidence - -- Package/documentation tests first failed on the old `server` export/package gate and missing OpenChamber mode guidance, then passed after the V2-only update. -- `npm run check:package` passed its disposable local tarball install and V2 `setup(ctx)` registration assertions. `node --test test/package.test.mjs` passed 15/15 before the final full-suite run. -- Current docs distinguish the unpublished V2-only source from the immutable published V1-only `0.4.0-beta.0`, describe server-side KSI for OpenChamber managed/external modes, and preserve dated historical verification evidence. - -## Task 5 completion evidence - -- Final offline gates: `npm run check` 203/203; `npm run check:package` passed; `npm pack --dry-run` reported 38 files; `git diff --check` clean. -- OpenChamber `2.0.1` compatibility was statically checked against OpenCode `2.x >=2.0.15` (local CLI `2.0.18`). OpenCode plugin loading and OpenChamber managed/external UI verification remain pending exposed-key rotation and separate user authorization; no provider call, commit, push, or publish occurred. -- Milestone m03 remains in progress and unaccepted. Independent review could not be completed because the review dispatch returned no findings/report. - -## Blockers and explicit deferrals - -- OpenCode runtime/plugin-load smoke and both OpenChamber mode UI checks await confirmation that exposed credentials have been rotated and separate user authorization. Provider calls require separate approval. -- No service restart, OpenChamber installation/update, global config migration beyond the already-approved V2 configuration, remote source verification, commit/push, publish, or release. - -## Follow-up offline role-boundary audit (2026-09-26; no runtime calls) - -- User asked to proceed with the improvement priorities. A disposable V2 context was loaded locally, and only its `execute.before` hook was called; no edit or shell command executed. Build's request to edit `.opencode/opencode.jsonc` was accepted because `src/agents.mjs` grants `.opencode/**` and `src/plugin-v2.mjs` classifies every `.opencode/` path as bookkeeping. This is broader than the stated checkpoint/artifact/report intent; configuration and plugin paths under `.opencode/` are not bookkeeping. -- The same offline hook accepted a Test Runner shell request with an unassigned non-test command. `src/agents.mjs` grants `shell: * allow`; `agents/test-runner.md` limits behavior in instructions, but the hook does not check the assigned `Commands:` field against the requested command. This is a policy gap, not evidence of an actual execution or breach; subprocesses are not OS-sandboxed. -- Reviewer dispatch for the previous migration returned only a status-command preamble (twice), not a substantive review; no independent approval is claimed. Agent role additions or blanket step increases have no evidence-based justification. Optional `developerTestRunner` remains off by default. -- Ruling: Do not silently tighten Build/Test Runner permissions inside the already verified V2 migration — changing permission scope may break legitimate bookkeeping or test commands, and the user's follow-up requested an audit before a choice. Cost if wrong: these broader permissions remain until a narrow, tested follow-up is approved. -- Runtime/plugin-load and OpenChamber managed/external UI checks remain blocked until exposed-key rotation is confirmed and the user separately authorizes them. No provider calls or service restart; no commit, push, or publish. - -## Follow-up: Build write boundary and runtime discovery (2026-09-26) - -- User confirmed exposed-key rotation and authorized provider-free OpenCode/OpenChamber runtime/UI checks, and chose to narrow Build first. Test Runner policy remains unchanged pending a separate command-scope design. No service restart, provider call, credential read, config edit, commit, or publish was authorized or performed. -- RED: new tests showed Build's `execute.before` accepted `.opencode/opencode.jsonc` and `.opencode/artifacts/continuity/registry.json`; native permission rules included `.opencode/**`. GREEN: narrow the shared bookkeeping classification to `.opencode/working-state.md` and `.opencode/artifacts/execution-reports/**`, retaining plan ledger and product-state paths, including nested project prefixes. `node --test test/bookkeeping-policy.test.mjs test/agent-policy-v2.test.mjs` passed 19/19; full `npm run check` passed 205/205 and `npm run check:package` passed. Work remains uncommitted. -- Runtime GET-only checks reached OpenCode `2.0.18` shared service. Its default API location was `/home/ksi`, not this repository. KSI was listed in `/api/plugin` with `state.status: failed` and a sanitized error `Plugin disabled after agent.transform failed. Check server logs for details.` KSI roles were not transformed (developer/test-runner/reviewer/research/design-critic presented as Primary), `/complete` absent. This is **not** successful runtime acceptance. -- A disposable offline V2 context using only ID/mode/permission fields from the service's agent catalog rejected the Design permissions with a `KSI: Design wildcard action allow/ask is not allowed`-class error. The service catalog showed `*:allow` for Design. This is a plausible cause of the server transform failure, not a confirmed server-log root cause; KSI correctly fails closed rather than silently retaining a broad Design grant. -- `openchamber status` showed one password-protected foreground runtime on port 7666. No password was read, and no authenticated OpenChamber UI/API or managed/external mode acceptance was observed. `opencode api --standalone` selected this repository but returned empty plugin/agent/command catalogs in separate ephemeral invocations; do not treat that as a successful isolation test. -- Next gate: ask before changing any global agent configuration; if approved, resolve the unsafe Design permission override and confirm the actual OpenCode location, then retry read-only catalog checks. Recheck OpenChamber managed/external modes only after KSI loads; provider-free policy/command visibility is the limit of this authorization. - -### Diagnosis correction and source-only Design fix - -- Global agent-only configuration inspection was explicitly approved. The redacted OpenCode config diagnostic showed `agents.design.permissions` contains a root deny only; **no global agent configuration was changed**. The server's merged Design `Agent.Info.permissions`, however, included native wildcard allows before terminal root denies. Thus the earlier suggestion that the global Design configuration itself contained an unsafe `*:allow` was incorrect. -- New regression reproduced the merged native prefix rejection before the fix. Under V2 last-match-wins semantics, rules before the last `*:deny` are inert; the Design validator now checks that deny and only its effective suffix while preserving valid later scoped grants. A later wildcard allow still fails closed. Focused policy/Design/plugin tests 49/49; final `npm run check` 206/206, `npm run check:package` passed, `npm pack --dry-run` 38 files. -- User authorized one OpenCode location reload. `POST /api/location/reload` exited successfully, but subsequent repository-location GETs (`location[directory]` as documented deep-object query) still showed KSI `failed`, worker roles untransformed, and `/complete` absent. Reload success is not plugin-load success. A service restart was not authorized, and doing one from the current session risks interrupting that session; do not assume one is safe. OpenChamber UI remains password-protected/unverified. No provider call, authenticated UI access, or release action occurred. - -### Approved service restart follow-up (2026-09-27) - -- User explicitly approved a shared OpenCode service restart, acknowledging session interruption. A detached `opencode service restart` returned the local service URL; the session was interrupted and then resumed. `/api/info` reports OpenCode `2.0.18` with an active server PID. No second restart is planned without another reason. -- Provider-free repository-scoped GETs still show KSI `state.status: failed`, `/complete` missing, and custom worker agents untransformed. Server WARN now identifies the actual transform failure: `applyKsiAgentPolicy` cannot find `design, developer, test-runner, reviewer, research, design-critic` in its editor at transform invocation. Thus the prior merged-Design-permission hypothesis was not the current live error; its offline regression remains valid but does not explain this failure. -- Read-only, redacted configuration inspection shows all required KSI agent IDs declared in the global JSONC. Both home- and repository-scoped `/api/agent` catalogs contain those six IDs **after** the plugin is disabled. This suggests a transform/agent-config materialization ordering mismatch; the exact ordering mechanism is not yet demonstrated. Do not remove the fail-closed missing-ID guard or claim runtime success on the basis of the later catalog. No global config, credentials, provider, OpenChamber login, commit, push, or release was touched. - -### Isolated V2 transform-order reproduction (2026-09-27) - -- Reproduced with OpenCode `2.0.18` on loopback using separate temporary `HOME`, `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, cache, state, DB, and minimal agent/plugin configuration. No provider/auth config was present; the probe made no provider calls and did not read credentials. -- A temporary diagnostic plugin logged both `editor.list()` inside `ctx.agent.transform` and `await ctx.agent.list()` before/after registering the transform. All three exposed only built-in IDs (`build`, `explore`, `plan`, etc.); configured custom IDs (`design`, `developer`, `test-runner`, `reviewer`, `research`, `design-critic`) were absent. After setup, the same location's `/api/agent` returned those config-defined IDs. The KSI plugin in this same isolated setup fails with the same missing-ID error. This confirms the live behavior is a V2 transform/config materialization-order incompatibility on 2.0.18, rather than undeclared agents, wrong location, a shared-service cache, or the Design permission validator. -- A second throwaway probe called `editor.update("developer", ...)` when `editor.get("developer")` was `undefined`; the call did not error and the later `/api/agent` entry reflected the marker. This is not acceptable as a validation substitute: without a reliable pre-transform configured-agent source, blindly updating could implicitly create/override an undeclared role and violate the fail-closed contract. -- No tracked source, global configuration, credentials, or OpenChamber state changed. No provider calls or additional shared-service restarts; all three isolated diagnostic servers were stopped. `git diff --check` is clean. Safe KSI-only remediation remains unproven; preserve the missing-ID guard pending a supported post-config transform/validation surface or an upstream OpenCode fix. -- User requested improvement and approved the strict-contract/upstream path. Refreshed diagnostics (OpenCode v2.0.18 latest; Linux arm64; repo KSI plugin failed, Superpowers/Goal/OpenChamber agent plugins active). Published https://github.com/anomalyco/opencode/issues/51598 after title/body approval; `gh issue view` verified state OPEN. No KSI source or global configuration changes. diff --git a/docs/superpowers/plans/2026-09-29-v2-npm-distribution.md b/docs/superpowers/plans/2026-09-29-v2-npm-distribution.md deleted file mode 100644 index b1353ea..0000000 --- a/docs/superpowers/plans/2026-09-29-v2-npm-distribution.md +++ /dev/null @@ -1,78 +0,0 @@ -# Native V2 npm Distribution Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Publish the existing native OpenCode V2 installer as the default public npm beta under `ksi-opencode-harness`, then retire only the V1 version after independent V2 installation verification. - -**Architecture:** Keep the existing standalone CLI and narrow npm `files` allowlist. Change the release manifest and public installation copy; strengthen packed-tarball tests to prove preview-first behavior without touching user configuration. Perform registry publication, tag repair, and V1-version removal as separately gated, ordered release operations rather than adding a runtime plugin or a publication automation subsystem. - -**Tech Stack:** Node.js >=20, npm registry/CLI, `node:test`, OpenCode V2 native agents and skills. - -**Spec:** `docs/superpowers/specs/2026-09-29-v2-npm-distribution-design.md` - -## Global Constraints - -- Keep the unscoped package name `ksi-opencode-harness`; use new version `0.4.0-beta.1`; never reuse V1 `0.4.0-beta.0`. -- Publish the beta explicitly under `latest`; do not leave `next` pointing to V1. Say **beta**, not stable or real-product visual acceptance. -- Installing the package by itself does not change OpenCode configuration; CLI `install --target ` previews and `--apply` writes under existing collision, backup, and user-routing protection. -- Pack only the CLI, four custom roles, optional vetted design-kit/vendor sources, licenses, and public docs. No V1 runtime plugin, internal plans/specs, tests, secrets, live config, preview files, or session state. -- Preserve global OpenCode/Codex/Superpowers config and the separate dirty human-centered worktree. Do not publish/unpublish, edit npm tags, or request credential values before explicit release-candidate review and authorization. -- Remove only V1 *version* after V2 registry install passes; if removal is denied, deprecate only V1 and report it. Never unpublish the entire package. - -## Review Focus - -1. A package install with no CLI invocation must not write to OpenCode config (Task 2 disposable HOME assertion). -2. A packed CLI preview of a missing target must leave it absent (Task 2 preview assertion). -3. A packed CLI invoked with a relative target or conflicting user-owned role must fail without changes (Task 2 negative-case assertions). -4. A registry whose `latest`/`next` changed since planning must not silently serve V1 after release (Task 3 pre/post tag queries and explicit correction). -5. A partial release (publish succeeds, verification fails or V1 unpublish fails) must preserve V2 and disclose the exact state rather than broad-delete or overwrite an npm version (Task 3 failure branches). - ---- - -### Task 1: Declare the native CLI beta and public installation path - -**Files:** -- Modify: `package.json` (`version`, `private`) -- Modify: `README.md`, `INSTALL.md`, `docs/releasing.md` -- Test: `test/native-package.test.mjs` - -**Interfaces:** -- Consumes: existing `bin` name `ksi-opencode`, `install --target` preview-first CLI, and package `files` allowlist. -- Produces: public `ksi-opencode-harness@0.4.0-beta.1` manifest and documented invocation `npm exec --yes --package=ksi-opencode-harness@0.4.0-beta.1 -- ksi-opencode install --target /absolute/path/to/opencode-config` (add `--apply` only after preview). - -- [ ] **Step 1: Write failing tests.** In `test/native-package.test.mjs`, assert `packageJson.version === "0.4.0-beta.1"`, `packageJson.private !== true`, the three public docs name the new version and `ksi-opencode` invocation, identify it as beta/native installer (not a plugin), and do not recommend OpenCode `plugins` registration. Keep existing source-checkout command coverage as an alternate path. -- [ ] **Step 2: Run targeted test.** `node --test test/native-package.test.mjs`; expect assertion failures for old version/private manifest or missing npm invocation. -- [ ] **Step 3: Update manifest and copy.** Change only `package.json` version and `private` guard; retain the current bin/files list and Node floor. Add the exact versioned npm command to README/INSTALL, retain source-checkout usage, and update release guidance to require explicit `latest`, `next` correction, and V2 verification *before* version-only V1 removal. State that packaged installation alone is inert and the design kit remains opt-in. -- [ ] **Step 4: Verify and commit.** `node --test test/native-package.test.mjs` then `npm run check`; expect zero failures. Commit only these five files with a release-preparation message. - -### Task 2: Prove the published bytes are safe and usable - -**Files:** -- Modify: `scripts/check-package.mjs` -- Test: `test/native-package.test.mjs` (only if the packaged-file assertions need a focused unit-level gate) - -**Interfaces:** -- Consumes: Task 1 manifest and the existing `npm pack --json` / offline `npm install --prefix` harness in `scripts/check-package.mjs`. -- Produces: executable tarball smoke gate invoked by `npm run check:package`; no active/global target write. - -- [ ] **Step 1: Add packed-CLI assertions.** Extend `scripts/check-package.mjs` after the offline install to check the installed manifest version and `private !== true`, verify disposable HOME has no `.config/opencode` after package install, run the installed `.bin/ksi-opencode install --target /opencode-config` *without* `--apply`, assert `applied: false` and no target directory, then perform the existing `--apply` flow. Include a rejected relative-target command, a user-routing conflict fixture that remains byte-identical, and a repeat apply with empty `changes`. Ensure the script's own temp directory cleanup still runs on assertions. -- [ ] **Step 2: Run the package gate.** `npm run check:package`; existing CLI behavior may already satisfy these new safety assertions. If it fails, capture the real failure and write a focused regression before changing CLI behavior; do not manufacture a failing test for behavior that already works. -- [ ] **Step 3: Make the minimal package/test adjustments.** Prefer existing CLI behavior and test-fixture corrections; change `bin/ksi-opencode.mjs` only if a real packed-CLI discrepancy is observed and add a focused regression in `test/installer.test.mjs` for any such bug. Do not weaken collision or symlink protection. -- [ ] **Step 4: Verify and commit.** Run `npm run check`, `npm run check:package`, `node scripts/verify-native-v2-isolated.mjs`, `npm pack --dry-run --json` and inspect the tarball allowlist, then `git diff --check`. Require zero test failures, no forbidden packaged paths, and no verifier-issued provider calls. Commit only tested changes. - -### Task 3: Registry preflight, publication, and retiring V1 - -**Files:** -- Modify: `docs/releasing.md` only if release-time evidence reveals a documentation correction; do not add secrets or generated registry output to source. -- Record outcome in the active release ledger/checkpoint without exposing credentials or customer data. - -**Interfaces:** -- Consumes: Task 2's exact tarball/source commit and approved release candidate; npm owner rights and 2FA when required. -- Produces: public V2 tarball, `latest` and `next` both resolving to V2, and either removed V1 version or explicit per-version deprecation/blocker. - -- [ ] **Step 1: Read-only preflight.** Compare `git status --short`, HEAD, npm `view ksi-opencode-harness versions dist-tags --json`, and registry owner/dependency/unpublish-policy evidence. Check authentication/ownership locally without printing secrets. If V2 version already exists, tags unexpectedly changed, V1 has dependents, or publish rights are missing, stop and report before any registry write. -- [ ] **Step 2: Integrate reviewed candidate.** Fast-forward/merge release branch to `main` following repository policy, rerun `npm run check`, `npm run check:package` and isolated V2 check on that exact source, push `main`, and verify remote commit. If merge tests fail or push is rejected, stop before npm publication. -- [ ] **Step 3: Present a release-candidate gate.** Show exact remote `main` HEAD, tarball inventory/check results, chosen public command, intended `latest`/`next` changes, and refreshed registry/unpublish eligibility to the Human. Obtain explicit approval for the registry write phase; do not infer it from plan approval. -- [ ] **Step 4: Publish and verify V2.** From the verified commit, run `npm publish --tag latest` with authorized npm credentials/2FA; query `npm view ksi-opencode-harness@0.4.0-beta.1 version bin --json` and `npm view ksi-opencode-harness dist-tags --json`. In a disposable HOME and prefix, install that exact version from the public registry and exercise the installed binary's preview/apply; require roles and optional kit results and no write before explicit apply. On failure, do not remove V1 or assume a Git rollback undoes npm publish. -- [ ] **Step 5: Repair `next` and retire only V1.** Once the V2 registry smoke is green, set `next` to `ksi-opencode-harness@0.4.0-beta.1` if it still resolves to V1. Confirm V1 version-only unpublish eligibility and invoke `npm unpublish ksi-opencode-harness@0.4.0-beta.0` without `--force`; if denied, use `npm deprecate ksi-opencode-harness@0.4.0-beta.0 "V1 plugin retired; use 0.4.0-beta.1 native OpenCode V2 installer"` when permitted, disclose the failure, and never unpublish the whole name. -- [ ] **Step 6: Verify and report.** Query registry versions/tags again, verify an unqualified install resolves to V2 in a disposable environment, and report the actual new version, source commit, tags, V1 deletion/deprecation outcome, and install command. Preserve the separate human-centered dirty worktree and untracked preview/session artifacts; remove only the clean, integrated release worktree/branch after confirming there is no unique unsaved work. diff --git a/docs/superpowers/plans/2026-09-30-design-workspace-separation.md b/docs/superpowers/plans/2026-09-30-design-workspace-separation.md deleted file mode 100644 index 63972ac..0000000 --- a/docs/superpowers/plans/2026-09-30-design-workspace-separation.md +++ /dev/null @@ -1,300 +0,0 @@ -# Design workspace separation — OpenDesign adoption and OpenCode Design retirement - -Date: 2026-09-30 -Status: source change and global migration complete; release/documentation closure in progress; Human design acceptance remains separate - -**Approved direction (2026-09-30):** OpenDesign is the design workspace for KSI work. The OpenCode harness -retires its Design primary and the vendored OpenDesign skill kit, and documents an on-demand integration -contract instead. Design is a capability, not an OpenCode stage. Reviewer is reduced to code and behaviour -review; the Human still owns direction and acceptance. - -## Why - -The Design primary was rarely selected in real use, and switching primaries interrupted long Build work. -The vendored kit could not reproduce OpenDesign's own craft/design-system composition, so it could not reach -OpenDesign-quality output. Keeping both meant duplicated ownership and an ambiguous decision about when to -leave Build. - -## Scope of this change - -Source only, in this repository. No global OpenCode configuration was edited, no installed role or skill was -deleted, no provider call was made, and nothing was published. - -Removed: `templates/agents/design.md`, `vendor/open-design/`, `docs/design.md`, `docs/design-critique.md`, -`docs/design-system-template.md`, `examples/design.project.jsonc`, `examples/design-handoff.md`, and the -`--with-design-kit` code path in `src/native-bundle.mjs` and `bin/ksi-opencode.mjs`. - -Changed: `src/native-roles.mjs` now exports three roles (Developer, Test Runner, Reviewer); Reviewer's -description and prompt cover code and behaviour review only. `README.md`, `INSTALL.md`, `docs/architecture.md`, -`docs/execution.md`, and `docs/verification.md` were updated, and `docs/integrations/opendesign.md` defines the -integration contract. `package.json` moved to `0.5.0-beta.1` and drops the vendored files. - -Behaviour on upgrade: supplying `--with-design-kit` now exits non-zero with an explicit message and writes -nothing. A previously installed `agents/design.md` and the three skill directories are left untouched; retiring -them is a separate reviewed migration in the target configuration directory (see INSTALL.md). - -## OpenDesign deployment (separate system, verified) - -OpenDesign was installed from the official Docker path outside this repository and verified: - -- `ghcr.io/nexu-io/od:0.24.1`, local `http://127.0.0.1:7456` and tailnet-only - `https://dgx-spark-1.tailda9552.ts.net:7456` both return 200 for `/api/health`. -- `deploy/docker-compose.linux.yml` was arm64-adapted (aarch64 glibc paths and dynamic linker; OpenCode state - in its own volumes; host config mounted file-by-file read-only; container OpenCode service pinned to port - 49380 to avoid the host service on 49374). -- `~/.opencode/bin/opencode-cli` translates the adapter's `--dir ` to the OpenCode 2.x trailing - positional directory. OpenDesign's agent list reports OpenCode `available` with v2.0.18. -- End-to-end: project `ksi-deploy-smoke` generated `index.html` with `agent=opencode`; the run succeeded and - the preview served over both local and tailnet URLs. -- Reverse direction: the `opendesign` MCP server is registered in the global OpenCode configuration and its - tools (read, write, `create_project`, `start_run`, `get_run`) were exercised from an OpenCode session. - -The OpenDesign Cloud sign-in path is separate and needs the `vela` binary, which is not installed. Local CLI -agents (OpenCode) need no account. Nothing here was changed to enable Cloud. - -## KSI design guidance in OpenDesign (done) - -KSI design intent is installed as a design-system package in the daemon's user data directory -(`/app/.od/design-systems/ksi/DESIGN.md`, host volume `open-design_open_design_data`). It is prose-only: the -manifest contract would require a compiled `tokens.css`, and KSI has no real brand token set to compile yet, so -inventing one was rejected. The daemon supports `DESIGN.md`-only folders for user-installed systems. - -Verified: `GET /api/design-systems` lists it as `user:ksi` / "KSI Design System" / "Enterprise / Field -Operations" / `source: user`, and the MCP resource `od://design-systems/user%3Aksi/DESIGN.md` returns the full -7573-character text. The document covers the KSI principles (no KPI-card scatter, task-flow-first, status to -next action, maps only when spatial, explicit site/session relationships, hierarchy over density, restrained -visual language) plus layout, status, map, data-display, typography, colour, component, motion, and -anti-pattern sections. - -Follow-up when real KSI brand tokens exist: add `tokens.css` and `manifest.json` to make it a full package. - -## Adapter compatibility fixes (2026-09-30, after first live use) - -OpenDesign's OpenCode adapter targets the older `opencode-cli` argv shape, so `~/.opencode/bin/opencode-cli` -is a Node shim that translates before delegating to the real binary: - -- `run --dir ` → the workspace directory as a trailing positional argument (2.x syntax). -- `run --pure` → dropped. The daemon adds `--pure` only to its connection smoke test; 2.x rejects it, which - made the Local Agent "Test" button fail with `Unrecognized flag: --pure`. Verified after the fix: - `OpenCode replied in 5971 ms — 'ok'`. -- `models --verbose` → `--verbose` dropped. 2.x rejects it and the daemon then falls back to a hardcoded - seven-model list; the plain list is the daemon's documented fallback and now yields the real catalog - (18 entries after a daemon restart; the catalog is cached in-process). - -Codex was listed as unavailable because `~/.local/bin/codex` is a symlink into `~/.codex`, which was not -mounted, so the link dangled inside the container. `~/.codex` is now mounted read-write at its absolute host -path (Codex keeps sessions, history, logs, and credential state there and fails against a read-only home); -`codex-cli 0.159.2` is detected. - -Still unsupported by OpenCode 2.x and therefore unavailable through this adapter: `export --sanitize --pure` -(child-session evidence) and `run --variant`. The variant flag is only sent when a reasoning variant was -discovered from live models, which currently cannot happen. - -### Container identity, credentials, and configuration (2026-09-30) - -Root cause 1 — **host file ownership.** The container ran as uid 1001 while host files are owned by uid 1000 -and mode 600, so OpenCode could read neither `auth.json` nor the host `opencode.jsonc`. Resolved by running -the container as `1000:1000` and moving the three OpenDesign volumes to the same ownership. The uid is exposed -as `OPEN_DESIGN_UID` / `OPEN_DESIGN_GID` in `docker-compose.linux.yml`; that file is deliberately the place -these changes live because upstream's `install.sh` and `update.sh` auto-detect `docker-compose.linux.yml` and -would silently drop a separate KSI-named compose file. - -Root cause 2 — **HOME.** uid 1000 is named `node` in the image, so the image resolves `HOME` to `/home/node`, -which does not exist on the read-only root filesystem. Every daemon-spawned agent CLI failed with -`EROFS: read-only file system, mkdir '/home/node/.config'`, which is why OpenCode reported no version and the -model list fell back to the hardcoded seven entries. Fixed by pinning `HOME=/home/open-design`. - -Root cause 3 — **credentials are not in `auth.json`.** OpenCode 2.x stores credentials in its SQLite database -(`credential` table); `auth.json` is legacy and is not imported (verified by deleting the container database -and watching the table stay at zero). The container therefore had its own empty credential store. Resolved -with `~/opendesign/sync-opencode-credentials.sh`, which copies only the `API key` rows from the host database -into the container database. OAuth rows are excluded on purpose: a rotating refresh token shared between two -stores would invalidate one of them. - -Root cause 4 — **the host engineering config does not belong in the container.** Loading the host config made -the container's OpenCode start MCP servers it cannot run (`docker exec`, `npx`, host-absolute commands), which -slowed startup and polluted its catalog. The container now has its own minimal config at -`~/opendesign/opencode-config-container/opencode.jsonc` with no roles, skills, or MCP servers; OpenDesign -injects per-run context itself. - -Result after the four fixes: `opencode auth list` shows OpenCode Go, DeepSeek, Google, and Nvidia; the daemon -reports `opencode v2.0.18` with 144 models; the Local Agent connection test returns `OpenCode → ok in 5.4s`; -and design runs succeed, including one explicitly pinned to `deepseek/deepseek-v4-pro`. - -### Local agent connection status (2026-09-30, after all fixes) - -| Agent | Result | Note | -| --- | --- | --- | -| OpenCode | ok ~4.5s | 144 models from the shared host credentials | -| BYOK OpenCode | ok ~4.1s | only the `default` model: no OpenDesign BYOK provider is configured | -| Codex CLI | ok ~8.7s | needs `CODEX_HOME` pinned to the mounted host store | -| Antigravity | ok ~9.2s | needs the host's `~/.gemini` sign-in mounted at `~/.gemini` | -| Claude Code | fails | `Your organization has disabled Claude subscription access for Claude Code` — an account-level restriction, not a configuration problem | - -Codex resolves its home from `CODEX_HOME` rather than from the mount target, so the environment pins it to -`/home/ksi/.codex`. Antigravity stores an OAuth token under `~/.gemini/antigravity-cli` and refuses to start -unauthenticated, so the host's signed-in `~/.gemini` is mounted read-write at the container home. - -## Verification evidence (2026-09-30, source branch) - -- `npm run check` — 26/26 pass. -- `npm run check:package` — PASS: installer-only tarball, offline three-role install, and explicit rejection - of the removed flag; the packed file list contains no `vendor/`, `docs/design*`, or `examples/design*` paths. -- `node scripts/verify-native-v2-isolated.mjs` on OpenCode 2.0.18 — PASS with `installed: true`, - `modelAndStepsOmitted: true`, `builtinsUntouched: true`, `noSkillsInstalled: true`, `commandsNotInstalled: true`, - agent catalog `ok`, and retired design skills absent. Zero provider requests issued by the verifier; child - egress not monitored. -- No model/visual-quality claim, no shared-service catalog check, and no npm publication in this record. - -## Global migration (done 2026-09-30) - -With explicit approval, the retired Design surface was removed from the global OpenCode configuration after a -full timestamped backup: - -- `~/.config/opencode/agents/design.md` removed (older `.bak-*` siblings kept). -- `~/.config/opencode/skills/{frontend-design,impeccable-design-polish,web-design-guidelines}` archived to - `skills-removed.tar.gz` and removed. `mobbin-design` was left in place. -- `~/.config/opencode/opencode.jsonc`: `restricted_agents` is now `["plan"]`, and the dead - `"design": { "steps": 60 }` routing entry was removed. The file was re-parsed successfully and the diff is - exactly those two changes; the `opendesign` MCP entry is untouched. -- Verified against the running service: `/api/agent` returns - `build, compaction, developer, explore, general, plan, reviewer, summary, test-runner, title` — no `design`. - -Backups live in `~/.local/state/ksi-harness-backups/design-retire-/` (config tree, compose file, -deploy `.env`, systemd unit, removed skills archive, and a copy of the daemon data volume). - -## Reproduction assets (2026-09-30) - -The whole deployment is reproducible from the source repository: `integrations/opendesign/` carries the -upstream compose patch (verified to apply cleanly to `nexu-io/open-design` at `5b19dfa`), the `opencode-cli` -argv shim, the container OpenCode config, the credential-sync script, the KSI design-system `DESIGN.md` plus -its publishing `metadata.json`, and a README covering prerequisites, steps, operations, and known -limitations. Nothing there is part of the published npm package. - -## First real design run and the memory ceiling (2026-09-30) - -A realistic KSI screen was generated end to end with the `user:ksi` design system active (project -`ksi-safety-field-inspection`, entry `field-inspection.html`). The result follows the KSI principles: one -decision-driving summary line instead of a KPI wall, a single primary object (the inspection list), an -explicit status vocabulary shown with a label plus shape rather than colour alone, and a next action on every -actionable row. Screenshot: `design-previews/artifacts/ksi-safety-field-inspection.png` in this repository. - -The first attempt failed with `Transport: The socket connection was closed unexpectedly`. -`docker inspect` showed `OOMKilled=true`: the upstream default container cap of 384 MB with a 192 MB Node heap -is smaller than a real design run. Raised to 2 GB / 1024 MB heap in `deploy/.env`; the retry succeeded. A -user design system also defaults to `status: "draft"`, and project creation is rejected with -`DESIGN_SYSTEM_NOT_PUBLISHED` until `metadata.json` sets `"status": "published"`. Both requirements are -recorded in the reproduction README. - -This is a working design run, not Human acceptance: the screen has not been reviewed or approved by the user. - -## Pending - -- Human review of the source diff and of real use of OpenDesign as the design workspace. -- Optional later: a compiled `tokens.css` + `manifest.json` for the KSI design system once real KSI brand - tokens exist. OpenDesign Cloud and `vela` are explicitly out of scope (Local AI only). - -## Release and documentation closure (approved 2026-09-30) - -User approved README/workflow clarification and GitHub/npm beta deployment, then clarified that OpenCode, -OpenDesign, and Superpowers must be separately installed by the user or an authorized agent reading upstream -documentation. KSI's default installation remains exactly three native agent files. No automatic dependency -installation, credential sharing, MCP registration, or system configuration is added. - -- [x] Reorganize README around purpose, roles, workflow, exact install scope, separate optional tools, and agent onboarding. -- [x] Document integration reconnect and explicit artifact-entry requirements; replace deployment example with V2 MCP syntax. -- [x] Flag deployment assets as workstation-specific, including writable credential mounts and internal DB-sync risks. -- [x] Verify source/package and independent branch review; commit release documentation (`79d5a97`). -- [x] Publish approved GitHub source/tag/release and npm beta; verify public package installation. - -Fresh live audit: standalone MCP read succeeded while the project's OpenCode connection was failed with -`Connection closed`. Disconnect/connect explicitly at the worktree Location restored 22 tools without -a whole-service restart. Native live-tool project read, explicit-entry artifact bundle (17,069 bytes), -successful prior-run status, agent discovery, and KSI resource read succeeded. Prior-run evidence records -project-selected KSI guidance and exit 0. No fresh generation/write run or automatic restart recovery was -performed in this audit. The default-entry bundle failed despite metadata having an entryFile. - -Claude account restrictions are expected; Codex intermittent connection-test failures are follow-up, -not release blockers per user. `npm whoami` currently returns E401, so npm publication requires reauthentication. -Human saw the preview but has not approved its design; do not claim full production implementation acceptance. - -Independent review identified a stale four-role statement in the shipped config example and failure-path -secret persistence in the source-only credential-sync aid. Corrected the example to three. Removed container -SQL files entirely: API-key SQL now streams over stdin; the private host mktemp file retains EXIT cleanup. -Two offline boundary tests (DB apply success and failure) failed first on container file creation, then -passed after the fix; full suite 28/28 and package check pass. Tests use fixture-only Docker/sqlite stand-ins, -not real credentials or database migration. Added package exclusion assertion for `integrations/`. -Human prose count does not get a brittle exact-word regression test; three-role installation is already -covered by real CLI/package tests. The running machine's older sync script was not changed automatically. - -User completed npm web login; `npm whoami` now returns the expected maintainer identity. Release will use -the `next` dist-tag without silently changing `latest`. GitHub main can fast-forward from `de7f823`. - -### Public release evidence - -- GitHub main fast-forwarded to `79d5a97`; annotated `v0.5.0-beta.1` and GitHub prerelease published with an - installable tarball. A clean consumer downloaded that public asset and installed exactly three agent files. -- User completed npm's separate interactive publishing approval from this repository (not the home directory). - Anonymous public metadata returned `0.5.0-beta.1` and the preserved CLI bin mapping. Published integrity - matched the reviewed/local release tarball. `next` selects this beta; `latest` was deliberately left unchanged. -- Fresh-home/cache `npm exec --package=ksi-opencode-harness@0.5.0-beta.1` preview wrote nothing; apply created - only three agents, retained existing config unchanged, and repeat apply was idempotent. No global config - or third-party tools were installed. Evidence: `/tmp/opencode/ksi-public-beta-azm41b9g` and - `/tmp/opencode/ksi-github-beta-re05u5t4` (local resumability evidence, not shipped assets). -- npm emitted a misleading bin-normalization warning: source inspection showed it normalizes the leading - `./` then assigns the bin target. Public metadata and actual npm-exec installation verified that the CLI - was not removed. No manifest/behaviour change was needed. - -### MCP idle-exit root cause and approved host adjustment - -The live MCP disconnected again without a container restart. Installed upstream `mcp.js` declares a -30-minute default idle-exit timer; `_resolveMcpStdioIdleExitMs` accepts zero to disable it. Real wrapper -processes reproduced closure with a 200 ms idle setting; zero stayed connected and listed all 22 tools -after the same idle period. User explicitly approved adding `OD_MCP_STDIO_IDLE_EXIT_MS=0` to this server's -global docker-exec command. Private backup made; exact diff verified as that single argument only. -Project-scoped disconnect/connect restored tools; live project read and process environment confirmed -the setting. No credentials, unrelated servers, or whole service were changed. Full 30-minute soak and -automatic recovery from an actual container restart remain unverified. - -This operator guidance is a source-only documentation follow-up to the immutable tagged/npm beta; no -published tarball or release tag is overwritten. Remaining product acceptance is human review of the -README/workflow and separately of a production design implementation, not a registry availability blocker. - -Deferred review minors: unified-diff blank context can trigger whole-range whitespace checks in the stored -compose patch; a historical ledger contains the maintainer's concrete private-tailnet hostname. Neither is -a package secret or npm-installed setting. Main-source links, explicit `KSI_ASSETS`, dist-tag clarity, and -integration-asset package exclusion are now addressed. - -## Remote DGX project workspace follow-up — design stage - -User reported folder-picker HTTP 500 and clarified work/output must happen in an existing DGX project, -not the connecting device or a reference-only folder. Confirmed actual response: native picker cannot find -zenity in the headless container; Desktop/project directories are not mounted. Other console errors have -separate Cloud/missing-file/workspace-context causes. - -Initial Desktop/KSI-CCTV-only written spec was approved; implementation planning was interrupted before -a plan was saved. User then rejected per-project restrictions, clarified home-wide directory browsing -versus selected-project work, and requested a more proactive approach. Revised spec replaces the fixed -allowlist with home navigation and selection-driven host connections; KSI-CCTV is only the first smoke target. -Host connection service manages fixed-container project mounts, preserves existing deployment settings, -guards active runs and recovers failed reconnects. No home-wide writable mount or agent Docker socket. -Written spec: `docs/superpowers/specs/2026-09-30-dgx-remote-project-workspace-design.md`. -Status: revised-spec self-review performed; user revised-spec review and implementation plan/execution approval -still required. No runtime/mount/CCTV-source changes, no new publish/commit approval inferred. - -## Labs setting read diagnosis - -User asked whether enabling Design Harness would improve generation and supplied the remote settings page. -Read-only HTTP comparison: loopback health and rollout status both 200; rollout reports requested/effective -active with default source. Via the same tailnet HTTPS origin, health remains 200 but rollout status is 403 -with "request host must be a loopback daemon address". Source route requires local-daemon request checks; -LabsSection maps all read failures to unreadable/off. Thus this is a remote settings-read restriction, not -evidence the daemon is stopped or the experiment is disabled. Local Codex is in source default eligible -agents, but task/runtime capability checks still apply; actual per-run use/quality gain not verified. -No configuration, proxy, security guard, or generation run changed. Preserve this access restriction until -an authenticated remote-settings design is approved; do not remove all local-daemon guards to silence UI. - -User subsequently approved the revised workspace spec and requested server deployment plus the Labs fix. -Implementation/deployment plan: `docs/superpowers/plans/2026-09-30-dgx-remote-workspace.md`. -Actual HTTPS 7456 route is tailnet-only Tailscale Serve to loopback daemon, not the separate nginx container. -Plan review/execution-method selection pending; no runtime changes made during planning. diff --git a/docs/superpowers/plans/2026-09-30-dgx-remote-workspace.md b/docs/superpowers/plans/2026-09-30-dgx-remote-workspace.md deleted file mode 100644 index 5483270..0000000 --- a/docs/superpowers/plans/2026-09-30-dgx-remote-workspace.md +++ /dev/null @@ -1,194 +0,0 @@ -# DGX Remote Workspace Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans for native execution or superpowers:subagent-driven-development if the user selects delegation. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Browse DGX home directories from the remote browser, connect selected writable projects without manual approval lists, and make the Labs status readable remotely. - -**Architecture:** A stable host-side loopback gateway handles directory listing and project connection while forwarding normal traffic to the existing daemon. Only the exact read-only Labs status request gets validated remote-to-local header translation; upstream local-daemon guards remain intact. A restricted host connector manages selected-project compose overrides, container recreation and rollback; existing folder import remains the project registration authority. - -**Tech Stack:** Node.js built-in HTTP/filesystem/test modules, TypeScript/React, existing Express daemon, Vitest, Docker Compose, Tailscale Serve. - -**Spec:** `docs/superpowers/specs/2026-09-30-dgx-remote-project-workspace-design.md` (revised version approved by user). - -## Global Constraints - -- Browse `/home/ksi`; project work uses selected canonical descendants, not a home-wide writable mount. -- No fixed project allowlist; opening a folder authorizes connecting that project. -- Home itself and credential-store roots are not project workspaces; preserve existing credential mounts and config. -- No Docker socket in the agent container; host connector accepts no client command, image, container or compose path. -- Preserve existing workspace permissions, canonical baseDir, native desktop picker and reference-only linkedDirs semantics. -- KSI-CCTV is the first smoke target; only new files in `design-previews/remote-workspace-smoke/` may be written there. -- Do not publish npm, change existing tags, install Cloud tooling, alter unrelated services, or commit without separate permission. -- User approved this plan and native execution/deployment with "진행"; preserve backups and reversible rollout. - -## Review Focus - -- A second click during container recreation must join the existing operation, not create another restart. -- A symlink changing between validation and connection must not mount an unchecked outside-home target. -- A failing compose update must preserve prior mounts, image, config and existing project accessibility. -- A browser disconnect during connection must leave a queryable result and permit idempotent retry. -- The Labs exception must not translate arbitrary URLs, methods, hosts or cross-site requests into local authority. - -## Evidence and deployment routing - -- Harness branch `refactor/retire-design-opendesign`, HEAD `cc77f29`; ledger/spec edits are existing session work. -- Upstream checkout `/home/ksi/opendesign/open-design` has user-modified `deploy/docker-compose.linux.yml`; preserve it. -- Container image `ghcr.io/nexu-io/od:0.24.1` reports daemon `0.23.1`; prove runtime/source parity before a custom build. -- Tailscale Serve on HTTPS port 7456 is tailnet-only and forwards to loopback port 7456. Other Serve/Funnel routes are unrelated. -- `od-https-proxy` is not the proxy serving this URL; do not modify that container or its stale host mount. -- Local Labs status is 200/active; remote status is 403 due to Host validation, not missing activation. -- Host gateway uses a free loopback port selected and persisted during deployment (preferred 17456; fail rather than steal a port). -- Only the existing HTTPS 7456 Serve target changes to the gateway. The daemon remains loopback-bound at 7456. -- Gateway process is managed by a user systemd service. Host connector runs under the existing trusted user with Docker access. - -## Task 1: Stable gateway and exact Labs read exception - -**Files (harness):** -- Create `integrations/opendesign/remote-workspace/gateway.mjs` -- Create `integrations/opendesign/remote-workspace/request-policy.mjs` -- Test `integrations/opendesign/remote-workspace/tests/gateway.test.mjs` -- Test `integrations/opendesign/remote-workspace/tests/request-policy.test.mjs` - -**Interfaces:** `validateRemoteRequest({peer,host,origin,fetchSite}, config) -> {ok:boolean,reason?:string}`; `createGateway({config,workspaceService,upstream}) -> http.Server`. Configuration pins the exact HTTPS origin supplied by the user, loopback upstream and home root. Transport trust is the tailnet-only Serve listener plus loopback peer; Host alone is not authentication. - -- [ ] Write tests: approved remote GET status returns upstream JSON; wrong Host/Origin, non-loopback peer and `Sec-Fetch-Site: cross-site` return 403 before forwarding. Absent Origin remains valid for same-origin GET/navigation. -- [ ] Run `node --test integrations/opendesign/remote-workspace/tests/request-policy.test.mjs integrations/opendesign/remote-workspace/tests/gateway.test.mjs`; confirm RED because modules do not exist. -- [ ] Implement request validation and streaming proxy including SSE/websocket forwarding and abort cleanup. Only exact GET `/api/strategies/od-next/rollout` without query receives `Host: 127.0.0.1:7456` and removal of the already-validated remote Origin; all other routes preserve existing behavior. Never rewrite authentication or disable daemon guards globally. -- [ ] Add tests that POST, alternate paths, query variants and config mutations receive no local-authority translation; unavailable upstream returns a useful 502. Normal streaming traffic stays streamed, not buffered. Incoming forwarding headers are not an authority source. -- [ ] Run the same command and confirm GREEN. Record evidence in this plan; do not change rollout mode (already active). - -## Task 2: Home directory browsing and restricted connection manager - -**Files (harness):** -- Create `integrations/opendesign/remote-workspace/path-policy.mjs` -- Create `integrations/opendesign/remote-workspace/connector.mjs` -- Create `integrations/opendesign/remote-workspace/workspace-service.mjs` -- Test `integrations/opendesign/remote-workspace/tests/workspace-service.test.mjs` -- Test `integrations/opendesign/remote-workspace/tests/connector.test.mjs` - -**Interfaces:** `resolveWorkspacePath(input, {home,forSelection}) -> Promise`; `listDirectories({path,showHidden}) -> Promise<{path,entries}>`; `connectProject({path,requestId}) -> Promise<{operationId,status,path}>`; `getOperation(operationId) -> operation`. Request IDs are UUIDs, operation status is `pending|connecting|ready|failed|busy`. - -- [ ] Write fixture tests for home browsing, Desktop shortcut, absolute/relative inputs, hidden folders, internal symlinks, missing directories and outside-home links. Home itself may be listed but not selected; exact credential roots `.ssh`, `.gnupg`, `.codex`, `.claude`, `.gemini`, `.config` and `.local` and their descendants are not selectable projects. Do not read their file contents. -- [ ] Run `node --test integrations/opendesign/remote-workspace/tests/workspace-service.test.mjs integrations/opendesign/remote-workspace/tests/connector.test.mjs`; confirm RED. -- [ ] Implement GET `/api/remote-workspace/directories`, POST `/api/remote-workspace/connections` and GET `/api/remote-workspace/operations/:id` in gateway-owned routing. Use Task 1 policy, JSON limits and canonical validation; do not send directory file contents to the model. Unknown input fields return 400. -- [ ] Implement connector with fixed server-side compose files/service and `spawn` argv (no shell interpolation). Serialize operations, deduplicate request IDs, recheck canonical targets before updating mounts, preserve preexisting settings, and create an atomic JSON-as-YAML compose override containing only same-path selected bind mounts. Never accept Docker arguments from the browser. -- [ ] Before restart, inspect `/api/runs` using its existing response contract; only positively verified idle permits connection. Unknown/unreachable run state fails closed. Track existing connections in private state, wait for health up to 120 seconds, then mark ready; failure restores prior override and container configuration and records whether rollback health recovered. -- [ ] Add tests for duplicate/concurrent requests, changed symlink, active-run refusal, disconnect/retry persistence, simulated health timeout/rollback, and injected container/image/command fields. Use fake Docker/upstream in all unit tests. -- [ ] Run the same test command and confirm GREEN. Show clear errors for host permissions and reconnect failure; do not grant chmod broadly. - -## Task 3: Browser folder picker and existing import integration - -**Files (isolated upstream worktree):** -- Create `apps/web/src/components/RemoteProjectFolderPicker.tsx` -- Create `apps/web/src/state/remote-workspace.ts` -- Modify `apps/web/src/components/useOpenFolderImport.ts` -- Modify `apps/web/src/components/NewProjectPanel.tsx` -- Test `apps/web/tests/components/RemoteProjectFolderPicker.test.tsx` -- Test `apps/web/tests/components/useOpenFolderImport.test.tsx` -- Modify `apps/daemon/src/import-export-routes.ts` -- Test `apps/daemon/tests/remote-workspace-import.test.ts` - -**Interfaces:** `listRemoteDirectories(path,showHidden)`, `connectRemoteProject(path,requestId)`, `readRemoteOperation(id)` use Task 2 contracts; picker returns a ready canonical path to the existing `onImportFolder` callback. A read-only gateway capability response advertises remote mode; absent capability retains native behavior. - -- [ ] Create an isolated upstream worktree using using-git-worktrees; read applicable upstream root/apps/daemon/packages rules. Preserve existing compose edits and prove installed runtime revision parity; stop/revise plan if the custom build cannot be matched safely. -- [ ] Write failing component tests: home navigation, breadcrumbs, Desktop shortcut, direct input, hidden toggle, unavailable entries with reason, cancellation, single-click open, connecting/failed/busy states and return of canonical selected path after ready. Native host import remains unchanged. -- [ ] Run `pnpm --filter @open-design/web test tests/components/RemoteProjectFolderPicker.test.tsx tests/components/useOpenFolderImport.test.tsx`; confirm RED. -- [ ] Implement picker using existing modal/list/input styles, not a redesign. Host capability takes precedence for native desktop; remote browser uses gateway picker and awaits readiness before existing folder import. Keep reference-only picker semantics unchanged. -- [ ] Add daemon tests proving remote-connected canonical paths are accepted through existing folder import with existing workspace permission checks; disconnected/home/outside/credential roots cannot be imported or assigned as external baseDir in remote mode. Managed internal projects remain supported. -- [ ] Implement the remote-mode connection-status check using a server-pinned gateway endpoint and existing privileged import/replace-baseDir paths; never trust a browser `connected` flag. Do not allow generic project create/patch to set baseDir. -- [ ] Run web tests above and `pnpm --filter @open-design/daemon test tests/remote-workspace-import.test.ts`; confirm GREEN. Run web/daemon typecheck and builds. Preserve already-existing failure evidence separately from regressions. - -## Task 4: Deployment, recovery and server configuration - -**Files (harness):** -- Create `integrations/opendesign/remote-workspace/service.mjs` -- Create `integrations/opendesign/remote-workspace/od-remote-workspace.service.example` -- Create `integrations/opendesign/remote-workspace/config.example.json` -- Modify `docs/integrations/opendesign.md` -- Private runtime files under `/home/ksi/.local/state/od-remote-workspace/`; install service under user systemd directory. - -**Interfaces:** `service.mjs --config ` starts Task 1 gateway with Task 2 service; config contains only pinned paths/origin/service/ports, not arbitrary client-controlled commands. Tokens, if needed by the existing upstream auth, come from private environment files and never source/JSON examples. - -- [ ] Write a service smoke test using fixture upstream/connector, proving startup health, bad config rejection and recoverable operation state; run it RED then implement startup and shutdown handling and run GREEN. -- [ ] Back up the effective compose inputs, selected image digest, user service state and only the 7456 Serve mapping privately. Check active runs and existing target port. Do not print credentials or full effective compose environment. -- [ ] Build a revision-pinned local image from the isolated upstream source; preserve uid/gid, agent mounts, daemon data, provider config, loopback binding and MCP idle setting. Do not replace upstream user's source changes. -- [ ] Install gateway/service, validate health and local tests before replacing the 7456 Serve proxy target. Keep its tailnet-only scope; do not change other routes or enable Funnel. -- [ ] Deploy custom image during verified idle; verify container health and MCP read/reconnect, then exercise new project connection. Failures restore previous image/override/Serve target; do not delete Docker volumes or unrelated files. -- [ ] Update source-only integration docs with exact commands, trust boundaries, disconnection/restart behavior and rollback. No public package or release changes. - -## Task 5: End-to-end evidence and acceptance - -**Files:** Create upstream `e2e/ui/remote-project-workspace.test.ts`; update this ledger and ignored harness checkpoint only. - -- [ ] Write E2E fixture tests: home navigation, two independently chosen fixture projects, automatic connection without allowlist, file bytes visible on host, host edits visible in UI, reconnect persistence, canonical cwd and retained native picker behavior. -- [ ] Run `pnpm --filter @open-design/e2e exec playwright test -c playwright.config.ts ui/remote-project-workspace.test.ts`; record RED before implementation and GREEN after deployment-compatible fixtures are ready. -- [ ] Compare local and remote Labs GET status: both 200 and active; wrong origin/host/cross-site variants remain 403. Confirm no changes to rollout mode and no exception for other privileged routes. -- [ ] Recheck KSI-CCTV Git baseline; create a unique smoke artifact only in the specified preview subdirectory, record matching host/UI bytes, read back a host edit, and verify no preexisting product/config files changed. Do not use a model generation run as a substitute for checking actual file paths. -- [ ] Verify Local Codex execution cwd in a separate fixture without touching existing products. Record whether newer design strategy actually applies independently from the enabled setting. -- [ ] Request user remote-browser acceptance of folder selection/save/reopen and Labs display. Automated passing does not close m04/m05 human acceptance. Record remaining gaps and rollback evidence, then collapse checkpoint to the verified baseline. - -## Self-review and handoff - -- Spec requirements map to Tasks 1–5; all five Review Focus cases have explicit test steps. -- Stable gateway is necessary because the daemon disappears briefly when new project mounts are connected; polling it alone cannot report reconnect state. -- The new host connector is privileged infrastructure, not a cosmetic picker; no deployment is justified by unit tests alone. -- Recommendation: native execution in this session, with independent final review, to keep deployment integration under one owner. User must review this plan and select native or delegated execution before implementation. - -## Execution evidence - -- User approved native execution with "진행"; no commits/publication authorized. -- Task 1 RED absent modules → 19/19 GREEN; full harness now 61/61 including connector/service tests. -- Task 2 focused canonical path/listing/connector tests GREEN; upstream/private status integration pending. -- Ruling: stage Labs-only gateway before custom image rollout (workspaceEnabled=false) — resolves independent 403 without exposing unfinished connector — cost if wrong: restore only 7456 Serve target; no daemon restart. -- Labs-only gateway deployed as user systemd service on loopback 17456. Remote status/health/settings 200; - active mode unchanged; cross-site status request 403. Parsed unrelated Serve/Funnel configuration unchanged. -- Private backup: ~/.local/state/ksi-harness-backups/remote-workspace-20260930-194510/. -- Exact installed-image source SHA fetched: 89e64d813bb1c7a11519b3f668f011f7017637d7; isolated - worktree /tmp/opencode/od-remote-workspace on ksi/remote-workspace. Original upstream compose untouched. -- Pristine web/daemon full suite attempts timed out at 120s (not passed); rerun with longer budgets required. -- Longer daemon baseline completed BEFORE any daemon source changes: 12048 passed, 76 failed, 16 skipped. - Raw names/details: `/tmp/opencode/od-daemon-baseline-long.log`. Failure inventory: - - `tests/chat-artifacts-run-cover.test.ts`: 7 - - `tests/chat-artifacts-video-cover.test.ts`: 4 - - `tests/chat-route.test.ts`: 33 - - `tests/codex-rollout-usage.test.ts`: 1 - - `tests/connection-test.test.ts`: 7 - - `tests/late-media-produced-file-association.test.ts`: 4 - - `tests/media-failure-reaches-run-terminal.test.ts`: 5 - - `tests/opend-2765-next-step-locale-carriage.test.ts`: 2 - - `tests/plugins-headless-run.test.ts`: 1 - - `tests/run-terminal-produced-files-association.test.ts`: 2 - - `tests/media/policy-routes.test.ts`: 7 - - `tests/runtimes/codex-child-evidence.test.ts`: 3 -- Full web background run picked up two new RED hook tests while running: 12762 passed, 2 failed, - 1 expected fail, 11 skipped. Those new tests are now GREEN; this is not a pristine/full post-change verdict. -- Current focused picker/hook/i18n tests 28/28 GREEN; web typecheck GREEN. Shared DTO, existing-modal picker, - native precedence, cancellation and ready-path import hook implemented in exact-revision isolated worktree. - Daemon connection checks, CLI parity, custom image, connector activation and E2E are still pending. -- User chose the recommended first option: preserve original 76 failures as baseline and proceed with feature-scoped checks/deployment; no need to fix unrelated failures in this work. -- Independent deployed Labs-only review (not final whole-branch): no Critical; 22 reviewed tests GREEN. - Ruling: preserve forwarding headers in this scoped fix — current local-daemon guard uses socket/Host/Origin, - not X-Forwarded authority; grep found no enabled trust-proxy setting. Blind stripping could break normal - proxy behavior — cost if wrong: future daemon header trust needs review. Labs handler verified read-only - in exact-image source `routes/strategy-rollout.ts`; response contains status, no credentials. - Ruling: tailnet membership is the remote authentication boundary, loopback/Host checks are not user identity - — consistent with approved transport; cost if wrong: wider tailnet membership broadens status readership. - Internal status remains disabled/token-gated; add stronger provenance checks before connector activation. - Deferred review minors: body-bearing GET, token-length timing, hop-by-hop normalization, exact-origin - formatting, example Node path portability and defensive req.url checks. No sandbox claim. -- Latest deployment 2026-09-30 23:25 KST: workspaceEnabled=true; reviewed local overlay image - `ksi-open-design:remote-workspace-20260930-reviewed`; original Compose edits/data/agent mounts preserved. - HomeView primary picker included (NewProjectPanel alone was insufficient); browser response/draft race - reproduced and fixed RED→GREEN. Real remote browser E2E 1/1 pass (21.3s): two arbitrary new host folders, - browser save→host bytes, host edit→browser read, reload/reconnect. Evidence `/tmp/opencode/od-remote-e2e-reviewed.log`. -- KSI-CCTV unique preview smoke bytes match host and remote API/MCP read; tracked Git status remains clean. - Actual Local Codex generation cwd/new strategy not yet verified (container cwd alone is not that witness). -- Fresh harness tests pass; latest focused web 11/11 and daemon 7/7 pass; builds pass. Real isolated Compose - rollback/merge smoke 9/9: prior sentinel mount retained, controlled health failure rolls back added root. - Full web before final draft fix: 12771 pass/1 expected fail/11 skipped; final rerun in progress. - Full post-change daemon background in progress; baseline failures not silently declared fixed. -- Independent whole-change reviewer completed; private upstream-token leakage fixed and late root swaps - detected with rollback. Atomic protection against a hostile same-user Docker/path race is not claimed. - Generation outage affects whole container; absent gateway env restores native mode by design, now documented. -- Automatic MCP recovery does not occur after container recreation. Explicit project reconnect restored catalog - and smoke file read; user-visible browser acceptance remains pending. No commit/push/publication performed. diff --git a/docs/superpowers/plans/2026-10-01-autonomous-execution.md b/docs/superpowers/plans/2026-10-01-autonomous-execution.md deleted file mode 100644 index fe95cda..0000000 --- a/docs/superpowers/plans/2026-10-01-autonomous-execution.md +++ /dev/null @@ -1,60 +0,0 @@ -# Autonomous local execution and beta.3 delivery - -> **For agentic workers:** Use executing-plans for the approved inline execution. User approved the bounded in-chat design and requested implementation, application, commit, push and deployment. This delivery is scoped to global AGENTS.md policy application, this repository's main integration/push and the verified 0.5.0-beta.3 npm tarball on next; preserve latest and all services. Do not reopen routine approvals or infer authority for other targets/effects. - -**Goal:** Keep native Build/Plan while delegating scoped local development through verification and commits, and reserving material decisions/external effects for the user. -**Architecture:** Optional shared operating guidance, not a new runtime or permission override. Local Codex owns material design; Build owns production integration. Package install still writes exactly three role files. -**Tech Stack:** Markdown guidance, Node.js package checks, native OpenCode V2, npm next. -**Spec:** Approved in-chat bounded design (2026-10-01); this ledger records its constraints, not a separate unreviewed architectural spec. - -## Global Constraints -- Preserve native Build/Plan/Explore, upstream skills, models, permissions, MCPs, goal configuration, credentials and services. -- Build completes only the authorized scope; no routine next-task/commit approval prompts. Local scoped commits are delegated by adopting this policy; explicit user/repository restrictions still win. -- Prior scoped authorization covers push/deploy; ask only for new authorization, material decisions, destructive/security-sensitive changes or genuine blockers. Do not override native tool approvals/budgets. -- Technical completion, human product acceptance and external delivery are separate states. Acceptance pending alone must not stall remaining authorized work. -- Local Codex remains primary for material design; ui-ux-pro-max is supplementary, never a competing design system. Reuse the same requirements in the design brief. -- Apply the exact reviewed shared policy to global AGENTS.md with backup; installer does not apply it automatically. Publish unused 0.5.0-beta.3 to next only, preserving latest. - -## Review Focus -- Existing user restrictions and user-owned Git changes must remain authoritative. -- Already-approved push/publication should not be re-requested, but implicit external authority must not be invented. -- Delegation is optional and bounded; helpers do not acquire Git/publication authority merely from their role. -- Material visual changes still need an approved design; missing design capability is not silent permission to substitute a skill. -- Documentation/packaging checks do not prove reliable long-running model behavior or user acceptance. - -## Tasks -### Task 1: Operating guidance and local application -- [x] Inspect current tree, policy, package ownership and beta tags; create ignored isolated worktree. -- [x] Run baseline check (log: /tmp/opencode/ksi-autonomy-baseline.log). -- [x] Require the optional operating policy in the real packed-consumer check and observe failure before shipping it. -- [x] Add reusable policy, reconcile project guidance/README/execution/design integration and optional installation instructions. -- [x] Back up and apply the reviewed policy globally without changing user configuration or upstream skills. -- [x] Run full checks, packed consumer and isolated effective-agent verification; review whole change. -- [x] Commit scoped changes, integrate main and push without force. - -### Task 2: Beta release and evidence -- [x] Publish exact verified beta.3 tarball to next (the user already authorized delivery); use normal browser approval if npm requires it. -- [x] Verify public registry metadata/tags, tarball integrity/bytes, fresh consumer preview/apply/repeat and no policy auto-application. -- [x] Record delivery evidence/product state; push this evidence-only closeout under the existing main delivery authorization and verify remote HEAD in the checkpoint. - -## Decisions and evidence -- Ruling: Treat this as an approved bounded policy change, not a new subsystem; inline completion is requested, so no additional plan/method approval gate. Cost if wrong: user can correct the recorded policy scope; no runtime permissions are widened. -- Ruling: Deployment means live global guidance plus the existing public npm beta channel; no service restart or design daemon redeployment is needed for guidance-only changes. Cost if wrong: a different deployment target would remain undelivered, not silently modified. -- Registry preflight: next=0.5.0-beta.2, latest=0.4.0-beta.2; beta.3 unused. -- Baseline/final check64 pass/0fail/1 opt-in skip; package RED missing optional policy then GREEN; logs /tmp/opencode/ksi-autonomy-{baseline,check,package-red,package-green}.log. -- Isolated V2 metadata verification passed (native prompts/files retained, roles/models/steps and retired skill checks); /tmp/opencode/ksi-autonomy-isolated.log. No provider requests issued by verifier; behavior/egress not established. -- Exact shared policy applied once to global AGENTS.md; harness reported refreshed instructions. Original global guidance preserved byte-for-byte outside that section; private backup /home/ksi/.local/state/ksi-harness-backups/autonomy-20261001.Bz3uqr. Config, three live role files and four upstream skill hashes unchanged. -- Ruling: Live Reviewer has pre-existing visual-review customization and conflicts with package default; preserve it, do not run replacement. This release changes no role templates. Cost if wrong: live Reviewer remains different from the default package, deliberately documented rather than silently overwritten. -- Independent whole-change review found no Critical issue and three Important authorization-summary qualifiers; corrected README/INSTALL target/effects/risk change conditions and scoped the ledger's delivery authority. Eight textual policy scenarios covered continue/failure/no-commit/unrelated-index/prior-push/new-deploy/missing-design/Plan-no-goal boundaries; this is interpretation review, not live behavior testing. -- Final: minor (deferred): Redundant manifest includes assertion for the optional policy; real packed-consumer missing-policy RED/GREEN already covers packaging. Future README/INSTALL pin automation remains manual release validation, not a new text-detector test in this docs-only change. -- Ruling: Reviewer did not independently rerun checks/registry/global backup comparisons; Primary verifies those with actual commands. Model-continuation reliability, rendered design and human acceptance are outside this guidance-only delivery and remain unclaimed. Cost if wrong: manual evidence might miss a behavior regression; report the verification boundary instead of declaring a runtime guarantee. -- Human real-development/end-to-end acceptance remains separate from this technical delivery. -- Post-review final check64/0fail/1skip and packed consumer passed; relative documentation links and paired beta.3 pins verified; whitespace clean. Final logs /tmp/opencode/ksi-autonomy-final-{check,package}.log. -- Source commit0682659 integrated to main fast-forward, merged-tree check64/0fail/1skip and packed consumer passed; pushed without force and remote exact HEAD verified. Reviewed18-file tarball /tmp/opencode/ksi-opencode-harness-0.5.0-beta.3.tgz. -- First non-TTY publish returned EOTP, not publication success. Normal npm PTY/browser approval flow started for the same verified tarball; auth is a required identity approval, not a repeated scope decision. Do not republish blindly if success arrives; verify registry state. - -## Published delivery evidence (2026-10-02 closeout) -- User completed normal browser identity approval; npm PTY command reported +ksi-opencode-harness@0.5.0-beta.3. Initial public lookup404 was registry processing, not permission to publish again. -- Public registry next=0.5.0-beta.3, latest=0.4.0-beta.2 unchanged; beta.2 retained. Reviewed/public tarballs and SHA512 integrity match; all18 source/package/installed files match. Fresh isolated public install verifies read-only preview, exactly3 roles, repeat idempotence and no automatic AGENTS.md policy merge. -- Public GitHub README at source0682659 matches the reviewed tarball/source bytes. Evidence /tmp/opencode/ksi-autonomy-public-consumer.log; fresh closeout rerun /tmp/opencode/ksi-beta3-closeout-public.log with /tmp/opencode/verify-ksi-autonomy-public.py before any follow-up changes. -- Policy/global application and technical delivery complete. No service restart, new design generation, model permission change, user configuration replacement or human real-development acceptance is claimed. The Plan-mode interruption postponed only this evidence record/push, not publication. diff --git a/docs/superpowers/plans/2026-10-01-readme-beta2-release.md b/docs/superpowers/plans/2026-10-01-readme-beta2-release.md deleted file mode 100644 index f6adc44..0000000 --- a/docs/superpowers/plans/2026-10-01-readme-beta2-release.md +++ /dev/null @@ -1,43 +0,0 @@ -# README renewal and beta.2 delivery - -User approved the bounded in-chat design and delivery scope: Korean workflow-first README, -GitHub main push, npm 0.5.0-beta.2 on next; preserve latest and existing DGX services. - -## Checklist -- [x] Inspect README, installer scope, release guide, registry versions and official V2 agent docs. -- [x] Present short design and obtain user approval. -- [x] Renew README: orientation, workflow, responsibilities, requests, installation, boundaries, deeper docs. -- [x] Update manifest/exact-version checks and pinned installation commands; correct stale release instructions. -- [x] Run harness, packed consumer, isolated verification, tarball inventory and README link checks. -- [x] Commit and push main without force (ab95c5e; remote exact HEAD verified). -- [x] Publish verified tarball to next only — browser login and separate publish approval completed; npm confirmed beta.2 publication. -- [x] Verify new registry version and public consumer bytes after publication. - -No new runtime functionality or permission changes. Technical publication does not establish -human acceptance of the rendered README or end-to-end product design. - -## Pre-publication evidence -- Harness64 pass/0fail/1 opt-in skip; isolated V2 agent catalog/permissions and retired role/skill checks pass, no provider requests issued by verifier. -- Packed offline consumer preview/apply/repeat and user-owned model preservation pass. -- Tarball17 files; only CLI, three roles, approved public docs/example/license. No integration deployment assets or private state. -- README local links/fences and matching beta.2 installation pins pass; whitespace clean. -- English-only removal wording assertion failed after translation; now verifies the explicit Korean Design/flag removal notice without dropping the check. -- Registry before delivery: next0.5.0-beta.1, latest0.4.0-beta.2; candidate0.5.0-beta.2 unused. - -## Earlier partial delivery / blocker (resolved below) -- Exact tarball publish returned E404; npm whoami returned E401 Unauthorized. Public package lookup works, so no successful authenticated publication is established. -- Registry afterward still next0.5.0-beta.1/latest0.4.0-beta.2; beta.2 absent. No tag mutation or version publication confirmed. -- Public README/INSTALL commands restored to available beta.1; beta.2 manifest/tests remain prepared. Repack after restoring beta.2 pins when publication credentials are ready; do not publish the earlier staged tarball blindly. -- User must sign in to npm on this server with a package-authorized account. Never request tokens or passwords in chat. - -## Authentication recovery -- User completed agent-started browser login; npm whoami succeeds. Candidate beta.2 still unused; next/latest unchanged before retry. -- Restore beta.2 install pins, reverify current source and repack; original failed publish did not create a registry version. - -## Published delivery evidence -- Restored beta.2 pins and reverified harness64 pass/0fail/1 opt-in skip plus packed consumer. Source200d86e pushed to main; current source repacked and published. -- Non-TTY publish returned EOTP. npm's documented web OTP path requires TTY stdin/stdout; PTY publish displayed browser approval and waited. User approved; CLI reported +ksi-opencode-harness@0.5.0-beta.2. No passwords/OTP/tokens passed in chat. -- Registry processing completed: next0.5.0-beta.2, latest unchanged0.4.0-beta.2. Existing beta.1 retained. -- Public tarball exactly matches reviewed local tarball/integrity. Fresh registry install in disposable HOME/cache:17 installed files match source, preview writes nothing, apply creates exactly3 roles, repeat has no changes. No real global config written. -- Public GitHub README bytes match source and npm README. Evidence: /tmp/opencode/ksi-beta2-public-consumer.log; reproduction script /tmp/opencode/verify-ksi-beta2-public.py. -- No DGX service or separate upstream source changes. Browser-rendered GitHub review remains unavailable (client block); human README/design acceptance still separate. diff --git a/docs/superpowers/plans/2026-10-01-remote-workspace-closeout.md b/docs/superpowers/plans/2026-10-01-remote-workspace-closeout.md deleted file mode 100644 index 36e2362..0000000 --- a/docs/superpowers/plans/2026-10-01-remote-workspace-closeout.md +++ /dev/null @@ -1,77 +0,0 @@ -# Remote Workspace Closeout Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Do not delegate without explicit authorization. - -**Goal:** Resolve remaining reproducible daemon failures without weakening security or assertions, verify supported remote attachment/recovery flows, and accurately close the technical work. - -**Architecture:** Retain the approved remote workspace and host MCP architecture. Repair test fixture isolation where tests select real host executables or create unsafe archive permissions; investigate any remaining failures before changing production code. - -**Tech Stack:** Existing Node/TypeScript, Vitest, Playwright, host MCP HTTP proxy and restricted container deployment. - -**Spec:** `docs/superpowers/specs/2026-09-30-dgx-remote-project-workspace-design.md`; user request to finish remaining work on 2026-10-01. - -## Global Constraints -- Preserve all existing uncommitted work, products, credentials and connected fixture roots. -- No sandbox disabling, privileged/unconfined mode, host sysctl changes, weaker assertions or security checks. -- Local Codex only; no OpenDesign Cloud tooling, subscription or fabricated brand assets. -- Do not reboot the host, commit, push or publish without explicit authorization. -- Technical acceptance does not substitute for human design satisfaction or README acceptance. - -## Review Focus -- Real primary executable installed beside fake fallback: test must run only its fixture (Task 1). -- Inherited executable override: preserve/restore it and prevent real agent launches (Task 1). -- Group-writable default umask: fixtures must remain private without relaxing production checks (Task 2). -- Outage/idle/relaunch: reads recover, mutations and generation never replay (Task 3). -- Supported attachments: exact saved bytes, clear unsupported/oversize errors, no product overwrite (Task 3). - -## Investigation evidence -- Baseline 76 failures in 12 daemon test files; full changed suite 80 in same files. -- Runtime `opencodeAgentDef.bin` is `opencode-cli`, fallback `opencode`. Many failed tests create only fallback; host has `/home/ksi/.opencode/bin/opencode-cli`. Baseline logs show actual host AI sessions instead of fixture output. Do not rerun those tests until isolated. -- Archive tests reproduce 4 failures/25 passes: `/tmp/opencode/od-remaining-archive-red.log`. -- Host umask is 0002; archive readers reject group-writable directories/files by design. Same 29 tests with process umask 0077 pass: `/tmp/opencode/od-remaining-archive-private.log`. This is fixture-permission evidence, not a production defect. - -### Task 1: Isolate fake-agent execution -**Files:** upstream `apps/daemon/tests/{chat-route,connection-test,chat-artifacts-run-cover,chat-artifacts-video-cover,late-media-produced-file-association,media-failure-reaches-run-terminal,opend-2765-next-step-locale-carriage,plugins-headless-run,run-terminal-produced-files-association}.test.ts` and `tests/media/policy-routes.test.ts`; existing runtime resolution test utilities as needed. -**Interfaces:** Keep current `withFakeAgent(..., run)` interfaces; fixture execution must select the prepared binary, not the host primary/fallback or inherited override. -- [x] Add a deterministic fixture-isolation regression with a competing executable that only writes a sentinel; assert fixture output and absence of competing sentinel. No real AI invocation. -- [x] Run regression RED, confirming the primary/fallback mismatch rather than provider failure. -- [x] Repair fixture binary names/explicit scoped overrides with proper environment restoration and child-process propagation; preserve all original product assertions. -- [x] Run all ten affected files together; record named residual failures and investigate them rather than retrying blindly. Stable12-file run290/290 pass, /tmp/opencode/od-closeout-fixture-final.log. - -### Task 2: Make archive fixtures private -**Files:** upstream `apps/daemon/tests/codex-rollout-usage.test.ts` and `tests/runtimes/codex-child-evidence.test.ts`; existing archive-index tests if available. -**Interfaces:** Existing archive readers and safety checks remain unchanged; test archive roots/index/files must be owner-private. -- [x] Preserve the reproduced RED log; inspect each failing fixture's modes to confirm the exact rejected object. -- [x] Set explicit private fixture permissions at creation, without changing global production umask or read safety logic. -- [x] Run both files under the original 0002 umask: expected 29/29 pass. Retain rejection tests for unsafe paths, ambiguous copies and unrelated threads. - -### Task 3: Verify remaining technical acceptance -**Files:** existing upstream E2E and MCP smoke tests, `docs/integrations/opendesign.md`, this ledger and rolling checkpoint. -**Interfaces:** Existing upload APIs, selected fixture project, Local Codex run request identity, host MCP stdio; no new recovery subsystem. -- [x] Verify supported attachment formats through actual existing UI selection/upload and saved bytes; test actionable rejection/retry where supported. Verify one attachment-aware Local Codex result without overwriting product files; do not claim every format is interpretable. Text+PNG UI bytes,503/manual retry/no-replay and final local report independently checked; first incorrect model report preserved. -- [x] Run same-client MCP idle read after more than 30 minutes; controlled host-proxy termination/relaunch verifies normal client reconnection, not automatic process supervision. Host reboot remains a separately authorized operation, not simulated as a real reboot. PASS after1860110ms, /tmp/opencode/od-closeout-idle.log. -- [x] Run complete daemon suite using repaired fixtures and normal security; investigate any remaining named failures before reporting completion. Run harness suite, affected web tests, typechecks/builds and `git diff --check`. Full daemon12134 pass/0fail/16 skipped; affected290/290, archive29/29, web44/44, harness64 pass/1 opt-in skip; builds/typechecks and whitespace checks pass. -- [x] Record exact results, deployment needs and unresolved human acceptance. Review changed diff locally; request independent review only if explicitly authorized. Keep all changes uncommitted unless separately approved. Test-only repairs require no production redeployment; human design/README acceptance remains separate. - -## Execution status -- User approved inline execution. Test-only fixture isolation/private permissions implemented;29 archive tests pass. Actual UI text/PNG storage+visible error+manual retry and final Local Codex reference report independently verified. -- Technical closeout verified: full daemon12134 pass/0fail/16 skipped, affected290/290, archive29/29,31min idle/relaunch, UI attachment and final local reference report pass. Human acceptance and unrelated integrations are separate; no commit/push/publication/reboot. See ignored ledger for detailed evidence. - -## Deployment verification notes (2026-10-01) - -- Host MCP: same client read, outage error, actual container restart/health recovery and read success; a browser-uploaded file was read afterward without reconnecting. A separate31min idle test then controlled proxy termination/new-client relaunch passed. No automatic host supervision or real host reboot was verified. -- Actual attachment UI: unique text/PNG fixtures saved byte-for-byte into the selected DGX folder. A controlled503 was visible; exactly one request occurred before conscious re-selection and successful retry. -- Local Codex: first report read text correctly but misread PNG dimensions; evidence was preserved. A Python follow-up failed because Python was absent; no dependency was installed. Existing Node readUInt32BE produced a new exclusive report independently verified for cwd, text first line and PNG1x1. This demonstrates file access and tested computation, not guaranteed model interpretation or design acceptance. -- Global policy check: live instruction update observed; EVENTOUCH directory design MCP connected. No same-name design project was found, so repository name alone cannot identify the target. No replacement project was created. Separate code MCP connection failure remains outside this closeout. - -## Commit and delivery authorization (2026-10-01) - -The user subsequently authorized commit/deployment closeout; the original no-commit constraints above -describe the earlier execution phase, not this later permission. KSI source/docs commit:e72e78f; -daemon/web/contracts source commit:1bd12b5eb on the isolated ksi/remote-workspace branch. The latter -was not pushed to the original upstream repository; unrelated untracked work was preserved. -Fresh KSI check and packed consumer verification pass; deployed health/Labs and runtime source parity -rechecked. Global policy/runtime deployment is already active. Existing npm beta/tag/dist-tags unchanged; -The user selected local main merge: main pulled fast-forward-only and then fast-forwarded to f3e4c18. -Merged-tree npm check:64 pass/0fail/1 opt-in skip; packed offline consumer verification passed. -No push or new npm publication; separate upstream worktree and connected fixtures preserved. diff --git a/docs/superpowers/plans/2026-10-02-browser-toolkit-beta5-release.md b/docs/superpowers/plans/2026-10-02-browser-toolkit-beta5-release.md deleted file mode 100644 index 64d8bcc..0000000 --- a/docs/superpowers/plans/2026-10-02-browser-toolkit-beta5-release.md +++ /dev/null @@ -1,43 +0,0 @@ -# Browser-toolkit beta.5 delivery - -## Authorization and target -User explicitly requested "이제 적용하고 배포해줘" after the completed global toolkit/ergonomic setup and the stated remaining GitHub/npm delivery. Deliver the four local improvement/evidence commits through existing GitHub main and an unused public npm0.5.0-beta.5, first next and then latest/default after consumer verification. Keep all previous versions, beta designation, current lightweight installer boundary and host setup. This authorizes the established external delivery, not another product deployment/service restart/new runtime/tool installation or replacement of the customized live Reviewer. - -## Workspace and baseline -- Primary works in the clean canonical checkout /home/ksi/Desktop/KSI-Projects/KSI-Opencode-Harness, main44e501a; docs/version-only release preparation, one writer. Local target main, remote origin/main atd52482f, four task-owned improvement commits ahead. Preserve four unrelated worktrees. -- Registry next/latest bothbeta.4; beta.5 unused. Previous immutable versions remain. Standard npm whoami succeeds; no credential reads or new login needed at preflight. Native browser/identity approval may still be required for registry writes. -- Completed global setup remains applied; verify exact canonical policy/config/official skills/tool versions and preservation, don't rerun --replace against user-customized roles. - -## Checklist -- [x] Reconcile main/remote/registry/auth and read prior release procedure; baseline check exit0. -- [x] Advance existing exact source/packed-version guards and observe expected failure against beta.4. -- [x] Bump manifest and paired README/INSTALL pins to beta.5; align channel wording and point public onboarding at the already-agreed workspace/browser workflow. No installer/runtime behavior change. -- [x] Verify source/packed consumer/isolated native metadata/tarball inventory/paired pins/live global application; independent bounded release review. -- [x] Commit candidate, push existing main without force, verify exact remote HEAD. -- [x] Publish exact reviewed tarball once to public next using normal identity approval; verify public bytes/integrity/fresh exact consumer. -- [x] Promote latest/default using normal approval; verify next/latest, all prior versions, fresh default consumer and GitHub public bytes. -- [x] Record delivery evidence for the authorized final main push; final remote HEAD/clean tree and idle checkpoint are verified by Primary after pushing this record. No user product/audio/long-running acceptance claim. - -## Evidence boundaries -- Baseline /tmp/opencode/ksi-beta5-baseline-check.log. Previous toolkit actual capability evidence remains in docs/superpowers/plans/2026-10-02-global-browser-toolkit.md; release tests do not repeat or replace customer-product acceptance. -- Publishing package docs does not automatically install browser tools, official skills or global operating guidance for downstream consumers. Only three optional role files are written by the installer after explicit --apply. -- Do not persist ephemeral authentication/approval URLs, codes, cookies or credentials in Git/checkpoint. If a write fails, inspect public state before any retry; never republish an already published version or bypass identity approval. -- Expected source/packed beta.5 guards failed against manifestbeta.4: /tmp/opencode/ksi-beta5-{version,package}-red.log, each exit1/actualbeta.4/expectedbeta.5. Then manifest/pins updated; no runtime code/role template changes. -- Live global verification reconfirmed canonical policy exactly once,368 prior config/role/skill hashes, official links, CLI versions and sandbox-preserving configs; custom Reviewer remains untouched. Actual toolkit smoke evidence remains applicable to unchanged tool setup; no fresh product/UI claim. -- Candidate checks64pass/0fail/1opt-in skip and packed consumer passed; isolated nativeV2 catalog/models/steps/default permissions/retired skill checks passed (0verifier provider requests, child egress not monitored): /tmp/opencode/ksi-beta5-{check,package,isolated}.log. -- Reviewed tarball /tmp/opencode/ksi-opencode-harness-0.5.0-beta.5.tgz: SHA512 matches pack record, all18 allowed files match source byte-for-byte, paired README/INSTALL preview/apply pinsbeta.5 verified. No runtime/role files changed; new docs make already-adopted workspaces/browser workflow discoverable and default-beta wording explicit. /tmp/opencode/ksi-beta5-pack.json. -- Independent read-only release review found no implementation/verifier defect. Review observations: next/latest wording becomes accurate only after planned promotion (mandatory post-write verification); keep internal product-state/ledger separate from the public release-content commit; registry consumers use disposable targets, never live config. All are covered by the release procedure. Reviewer relied on existing test logs and did not repeat live global/hash checks; Primary reran source64/0/1 and packed consumer before commit. No new acceptance claim. -- Release-contentc341faa and separate preflight3a5eedf pushed main; remote exact3a5eedf verified. Four previous local improvement commits are now public. Dry pack and fresh isolated metadata checks passed. -- Initial non-TTY publish exited1/EOTP without writing beta.5 (HTTP404/tagsbeta.4 verified). Installed npm auth.js requires stdin+stdout TTY to enter standard browser approval; used existing script pseudo-terminal with --browser=false, no CLI/auth-policy modification or bypass. -- Standard browser-approved publication exited0; exact beta.5 metadata/tarball now available and matches reviewed bytes/integrity. First full verifier stopped at next tag assertion because aggregate registry still returned beta.4 immediately after write. Do read-only propagation recheck, never duplicate immutable publication; latest promotion still pending. -- Aggregate next tag propagated on read-only recheck; full before-promotion verifier passed: nextbeta.5/latestbeta.4, exact reviewed tarball/SHA512/all18source+installed files, fresh exact public install, preview read-only, exactly3roles, repeat idempotence/no automatic policy application, all prior versions retained, exact remote HEAD/public README. /tmp/opencode/ksi-beta5-public-before-promotion.log at3a5eedf. Default install not yet claimed. -- Started one standard authorized latest promotion via existing npm pseudo-terminal/browser approval path. No package republish; await completion/required user approval before verifying default channel. -- Latest-specific attempt exited1 after303s with approval done-endpoint PUT404. Fresh whoami succeeds and registry remains nextbeta.5/latestbeta.4; immutable beta.5 is public. This matches uncompleted browser-approval expiration, not a package/version defect. Actual browser outcome is not reported. No automatic replacement link, new login, npm mutation/auth bypass or package republish; wait for user readiness to finish one fresh latest-specific approval promptly. Default install/latest delivery remains unverified. -- User explicitly requested replacement approval link; fresh auth/tags/clean main0015596 verified before one standard latest-only reissue. Reissue also exited1 after301s with done-endpoint PUT404; fresh auth succeeds and nextbeta.5/latestbeta.4 remain. Browser result is still unknown; ask whether approval was completed, an error appeared, or the link was not used before any further write. No automatic third latest request, login or republish; public beta.5 remains intact. - -## Final delivery evidence -- User explicitly requested another latest-specific approval link. Fresh auth/tags rechecked, issued one standard latest-only request; user browser approval completed and npm exited0. Public next/latest both0.5.0-beta.5; beta.4/all previous versions retained. No immutable package republish, credential collection, CLI mutation or approval bypass. -- Full public/default verifier passed at0015596 (release-contentc341faa): exact reviewed tarball/SHA512, all18source+installed bytes, fresh exact-version and unversioned default installs, read-only preview, exactly3role apply, repeat idempotence, no automatic policy/config application, all prior versions, remote exact HEAD/public GitHub README. /tmp/opencode/ksi-beta5-public-latest.log. -- Fresh final source64pass/0fail/1opt-in skip, packed consumer and isolated nativeV2 metadata/default permissions/models/steps/retired-skill checks pass: /tmp/opencode/ksi-beta5-final-{check,package,isolated}.log. Verifier issued0provider requests, child egress unmonitored. Final canonical global policy exact once/368priorfile hashes/custom Reviewer/tool versions/official skill links/sandbox-preserving configs reverified; no live reinstall or browser launch needed for unchanged setup. -- Earlier independent candidate/verifier review remains valid; no production/package content changed since reviewed publication. Closeout changes only these internal records, not public tarball, policy or role/runtime code. Auth-expiration notes above describe prior attempts, not a remaining blocker. -- Agreed local application and external delivery are technically complete. No service restart, new runtime/plugin/forced continuation, project dependency/CI change, product auth/API/WCAG/audio/human design or real-development acceptance claim. Follow-up engineering is not inferred from pending human acceptance; no automatic new milestone/backlog work. diff --git a/docs/superpowers/plans/2026-10-02-design-handoff-beta4-release.md b/docs/superpowers/plans/2026-10-02-design-handoff-beta4-release.md deleted file mode 100644 index b49e33d..0000000 --- a/docs/superpowers/plans/2026-10-02-design-handoff-beta4-release.md +++ /dev/null @@ -1,50 +0,0 @@ -# Design-handoff beta.4 delivery - -## Authorization and scope -User requested the previously deferred remote update/release on2026-10-02 ("해줘"). Deliver the already-applied/reviewed guidance through this repository's main push and unused npm0.5.0-beta.4 on next. Preserve latest0.4.0-beta.2 and all previous versions. No new design generation, runtime/config/permission change, service restart or product acceptance is included. - -### Expanded channel authorization -The user subsequently requested public delivery of the current version as the main/default release ("배포도 해주고 현재 최신을 메인으로 배포해줘 공개"). This explicitly authorizes changing npm latest to0.5.0-beta.4 after exact public consumer verification, in addition to next. Keep the beta version designation; no invented stable release or removal of older versions. GitHub main already contains sourcee2263b5. The earlier latest-preservation boundary describes the previous authorization stage, now superseded for this tag only. - -## Checklist -- [x] Reconcile clean local mainc195ff4, two unpushed guidance commits and remotebbfeae4. Registry nextbeta.3/latest0.4.0-beta.2; beta.4 unused. -- [x] Run baseline source check; log /tmp/opencode/ksi-beta4-baseline.log, exit0. -- [x] Update exact release guards first and observe mismatch before preparing beta.4. -- [x] Bump manifest and paired README/INSTALL pins; run source/packed-consumer/isolated native metadata, inventory and whitespace checks. -- [x] Commit release candidate, push main without force and verify remote exact HEAD. -- [x] Publish exact reviewed tarball to next; complete normal browser identity approval if required. -- [x] Verify public metadata/tags/integrity/source bytes and fresh consumer preview/apply/repeat/no automatic policy merge. -- [x] Promote verified beta.4 to latest under expanded authorization; verify next/latest and fresh unversioned default installation. -- [x] Record/push evidence and retain idle checkpoint; do not republish immutable versions. - -## Verification boundaries -Guidance was independently reviewed in the prior slice with no Critical/Important findings and eight textual scenarios. This release changes only version/pins and delivery records, not the policy, native prompts, runtime or role templates. Source/package/native metadata checks do not prove live Mobbin usefulness, vision support, design quality, product-server behavior or human end-to-end acceptance. - -## Evidence -- Preflight remote mainbbfeae44382aa7776c3e0d3f5a2c0bf23544e59e. Current local68a0b07/c195ff4 guidance commits remain scoped and task-owned. -- Exact version guards failed against beta.3 as intended, then full check64/0fail/1opt-in skip and packed consumer passed for beta.4. Logs /tmp/opencode/ksi-beta4-{version-red,package-red,check,package}.log. -- Isolated native V2 catalog/permissions/models/steps and retired skill checks passed; verifier issued no provider requests, child egress not monitored. /tmp/opencode/ksi-beta4-isolated.log. -- Reviewed tarball /tmp/opencode/ksi-opencode-harness-0.5.0-beta.4.tgz:18 allowed files match source exactly; paired README/INSTALL beta.4 pins and whitespace pass. Policy/role/runtime source is unchanged from its prior independent review; only release version/pins changed. -- Release sourcee2263b5 plus previously local68a0b07/c195ff4 pushed to main without force; remote exact HEAD verified. -- First reviewed-tarball publication exited1 with PUT E404; independent whoami returned E401. Public registry still has no beta.4 and unchanged nextbeta.3/latest0.4.0-beta.2. Authentication is invalid, not a tarball/version defect; no blind publication retry. Logs /tmp/opencode/ksi-beta4-{publish-tty,auth-check}.log and /tmp/opencode/ksi-beta4-after-failed-publish.json. -- Standard npm web login started once; requires user browser approval. Resume exact reviewed-tarball publication only after successful login and confirming beta.4 remains unused. Do not record credentials or authentication URLs in durable records. -- Expanded-authorization preflight: source remote still exacte2263b5, beta.4 absent, nextbeta.3/latest0.4.0-beta.2. Fresh whoami stillE401; existing web-login process remains active awaiting browser approval, not duplicated. Prepared public verifier now checks next/latestbeta.4 and both exact-version and unversioned default installs after promotion. -- Fresh expanded-scope local checks:64pass/0fail/1opt-in skip, packed consumer pass, unchanged reviewed-tarball SHA512 and all18 source bytes match, verifier syntax and git whitespace valid. Logs /tmp/opencode/ksi-beta4-latest-{check,package}.log. These do not establish public publication/default-channel delivery, which remain blocked on browser authentication. -- Browser reported old approval URL404. Login diagnostic confirms initial web POST200, then approval polling GET404 and fallback to legacy username input; fresh whoamiE401. Stopped only the obsolete task-owned login with SIGINT(exit130), started one fresh standard web login. No credentials collected, auth bypass, publication or registry mutation. Fresh request awaits user browser approval; do not persist authentication links. -- Replacement standard web login exited0; fresh whoami succeeded. Public beta.4 still absent, nextbeta.3/latest0.4.0-beta.2 unchanged; reviewed tarball SHA512/all18 source bytes reverified. Started one post-auth publication with explicit public access to next; wait for completion, do not duplicate. No public-success/default-channel claim yet. -- Post-auth publication exited1 with approval-poll GET404, not package PUT failure. Fresh whoami still succeeds; beta.4 absent and tags unchanged. Issued one new standard browser-gated publication of the exact tarball after confirming no active prior attempt; no bypass or version reuse after publication. Public delivery/latest remains pending. -- Second post-auth publication also ended approval-poll GET404. Both attempts show94 pending HTTP202 polls and expire at approximately5minutes(302s/303s); no approval-success200 or second package PUT. Fresh whoami succeeds and beta.4 remains absent with nextbeta.3/latest0.4.0-beta.2. This is an uncompleted/expired browser publishing approval, not broken login or tarball evidence. No third automatic link regeneration; wait until user is ready to complete a fresh approval promptly, then revalidate version/auth and retry once. No active publication remains. -- User explicitly confirmed readiness("준비됐어"). Fresh whoami succeeds, beta.4 still absent/tags unchanged, reviewed tarball integrity/all18 source bytes unchanged and tracked tree clean. Started one new standard browser-gated publication to next; approval must finish within observed5minute window. No success or latest promotion claimed yet. -- User-ready standard publication exited0 after browser approval; public beta.4 appears and nextbeta.4/latest0.4.0-beta.2 verified. Initial aggregate registry tag lag resolved on read-only recheck; no duplicate publication. Full exact public consumer verification passes: reviewed tarball/integrity/all18 source+installed bytes, read-only preview, exactly3role apply, repeat idempotence/no automatic policy application, public GitHub README. /tmp/opencode/ksi-beta4-public-before-promotion.log. Default install is intentionally not claimed until latest promotion. -- Started one authorized standard npm latest promotion; await completion or normal browser identity approval. Do not republish immutable beta.4. -- Latest promotion exited1 after approximately303s pending202 approval responses, then done-endpoint404. npm11.17.0 dist-tag passes PUT/body through otplease to npm-profile polling; server nevertheless accepted pending202, so PUT alone is not evidence of a CLI defect. No approval-success200 or completed tag write was observed. No installed npm mutation/custom auth workaround or automatic retry. Fresh whoami and exact public consumer recheck pass; nextbeta.4/latest0.4.0-beta.2 remain. /tmp/opencode/ksi-beta4-public-after-tag-expiry.log. User readiness to finish one new latest-specific browser approval is the remaining blocker; never republish beta.4. -- User confirmed readiness for latest-specific approval. Fresh whoami succeeds and tags remain nextbeta.4/latest0.4.0-beta.2. Started one new standard dist-tag promotion only; no republish and no duplicate request. Await approval/completion, then exact/default public verification. -- User-ready latest-specific attempt also expired after303s with done-endpointPUT404. Fresh public tags still nextbeta.4/latest0.4.0-beta.2. Browser outcome has not been reported; pending202 proves approval was not observed by CLI, not why it was unobserved. No third automatic latest request; ask for actual browser result before choosing next recovery. Public beta.4 unchanged; no active write. -- User answered browser-outcome question: latest approval was not yet performed("아직 승인하지 않음"). This explains pending approval expiration; no CLI repair/auth bypass is warranted. Await actual readiness to open a newly issued latest-specific link and finish approval within5min. Authorization to promote remains, but cannot substitute for user's required identity approval. No new request issued automatically. - -## Final delivery evidence -- User explicitly requested new latest-specific approval links after prior requests expired. Final standard dist-tag request completed successfully(exit0); public next/latest both0.5.0-beta.4. No immutable package republish or older-version removal; beta designation retained. -- Full public verifier passes after promotion: exact reviewed tarball/SHA512, all18 source+installed bytes, exact-version and unversioned default public installs, read-only preview, exactly3role apply, repeat idempotence, no automatic policy/config application and public GitHub README bytes. /tmp/opencode/ksi-beta4-public-latest.log; source verification at82216fd, release contente2263b5. -- Fresh final source check64pass/0fail/1opt-in skip, packed consumer and isolated nativeV2 metadata/retired-skill checks pass. /tmp/opencode/ksi-beta4-final-{check,package,isolated}.log. Verifier issued0provider requests; child egress not monitored. This does not claim live design/product acceptance. -- Independent bounded release/evidence review: no Critical/Important findings; synchronized version pins, exact guards,18file inventory and public/default-install evidence verified. Two minor future-only improvements deferred: packaged channel wording is conservative after explicit promotion, and README/INSTALL exact-pin tests could be hardened. Do not republish immutable beta.4 for prose polish. -- Review boundaries reconciled: Primary owns real public/default verification; live visual/long-running acceptance remains unclaimed; ephemeral approval material is not persisted; prior guidance's independent review is reused. Closeout uses the already-authorized main push, not a new release or integration decision. Only repository records change; final remote exact HEAD/clean tracked-tree evidence is retained in the private checkpoint after push. diff --git a/docs/superpowers/plans/2026-10-02-design-reference-handoff.md b/docs/superpowers/plans/2026-10-02-design-reference-handoff.md deleted file mode 100644 index a010036..0000000 --- a/docs/superpowers/plans/2026-10-02-design-reference-handoff.md +++ /dev/null @@ -1,42 +0,0 @@ -# Design-reference handoff guidance - -## Approved scope -User approved the in-chat bounded recommendation on 2026-10-02: conditional Mobbin research in OpenCode, a small snapshot-based design-to-Build handoff, and separate design-preview/product-server verification. Keep the existing structure; no new orchestrator, reverse execution, runtime code, automatic sync, design generation, credential/config changes or new npm publication. - -## Work checklist -- [x] Reconcile Git/checkpoint and prior beta.3 delivery; fresh public verification passed and evidence commit bbfeae4 pushed with exact remote HEAD confirmed. -- [x] Read existing design integration, shared policy and deployment record; existing runtime/MCP/artifact inspection from the prior review is reused, not treated as a fresh model run. -- [x] Reconcile global/shared guidance, README, integration/execution docs and stale deployment architecture explanation. -- [x] Specify brief reference inputs and a compact handoff record using existing source/ledger records, not a new enforced API/schema. -- [x] Back up and apply the shared policy globally; preserve all other guidance, native prompts, user configuration and upstream skills. -- [x] Run full checks/packed consumer/link and policy comparisons; independent read-only review. -- [x] Commit and integrate locally; no new push/npm release is inferred from this approval. Previous beta.3 evidence push is separately authorized and already completed. -- [x] Record verified application and idle checkpoint; human workflow acceptance remains separate. - -## Acceptance and boundaries -- New/material or unresolved visual patterns trigger focused reference research before design; approved-pattern implementation and small fixes do not trigger repeated searches. User-requested/reference-provided exceptions are respected. -- OpenCode selects actual-viewed patterns, canonical links and durable accessible images where permitted. Local Codex receives the same agreed brief plus reference intent; no assumed MCP/auth/context inheritance or copied brand assets. -- Existing tokens, components and human-approved direction override references. Missing reference capability is reported honestly but is not automatically a design blocker; missing required design capability must not be silently replaced by a UI-reference skill. -- Handoff ties human direction approval to the actual required-file content snapshot, not only a success status, mutable URL or manifest schema version. Missing/unread/truncated required assets are disclosed/resolved before implementation. -- Build owns product implementation/API/data/Git/server verification. Design feedback may return as questions/findings; automatic outer-session launches, recursive generation and two writers in product files are not authorized. -- Design preview verifies direction/prototype states, not real authentication/API/routing/permissions. Product server verifies implementation. Give reachable labeled links and report actual inspected states; do not imply localhost refers to the user's PC. -- Installer still writes only three roles. This is guidance/explicit handoff, not automatic enforcement or proof of sandbox isolation, token/cost savings, visual quality or end-to-end behavior. - -## Decisions -- Existing docs and shared operating guidance are the implementation; no upstream skill or application source needs modification. -- Apply the canonical operating section with backup; instructions for Local Codex travel in the design brief, not as a silently installed inner-agent MCP/configuration. -- Keep beta.3 immutable and registry unchanged. Follow-up source is locally integrated only; previous delivery evidence is already public. -- Because this is prose guidance with no new executable path, use source/packaging verification and independent scenario interpretation; do not invent text-detector tests or claim a live design run. - -## Verification -- Baseline/final check 64 pass/0 fail/1 opt-in skip; packed consumer passed. Logs /tmp/opencode/ksi-design-handoff-{baseline,check,package}.log. These checks verify source/packaging, not live reference/design/product behavior. -- Prior public beta.3 closeout /tmp/opencode/ksi-beta3-closeout-public.log;18-file/source/integrity/fresh-consumer checks passed before follow-up edits. -- Isolated V2 effective agent/permissions/models/steps and retired skill checks passed, no provider requests issued by verifier; /tmp/opencode/ksi-design-handoff-isolated.log. Child egress not monitored. -- Exact canonical policy appears once in live AGENTS.md; original guidance retained except the intended Build/Plan wording correction. Harness confirmed instruction refresh. Private backup /home/ksi/.local/state/ksi-harness-backups/design-handoff-20261002.gqM0MS; config/roles/Mobbin/UI/upstream skill hashes unchanged. -- Local links and handoff anchors checked; tarball inventory remains18 allowed files, no internal state/deployment assets. Registry next=beta.3/latest=0.4.0-beta.2 unchanged; no new release or inner configuration changes. -- Independent read-only review: no Critical/Important findings, eight textual boundary scenarios covered. Minor WIP spacing/status notes reconciled with actual checks; no rendered/image/live-generation evidence or human workflow acceptance claimed. -- Source68a0b07 committed with an explicit guidance/source-packaging-only verification boundary, then main fast-forwarded locally. Merged-tree check64/0fail/1skip and packed consumer passed; /tmp/opencode/ksi-design-handoff-merged-{check,package}.log. On-demand global guidance now resolves to the locally integrated current document. -- Follow-up source is not pushed/published; public beta.3 remains the already-verified earlier source0682659 with closeoutbbfeae4. Never republish immutable beta.3 from this modified tree. A future release/remote update needs its own scoped authorization. - -## Later delivery authorization -The user subsequently requested the deferred remote update/public release on2026-10-02. The earlier local-only boundary records that stage, not a prohibition on the newly approved delivery. Beta.3 remains immutable; the new scoped main/beta.4-next delivery and evidence live in [the beta.4 ledger](2026-10-02-design-handoff-beta4-release.md). No live design generation is added. diff --git a/docs/superpowers/plans/2026-10-02-ergonomic-workflow.md b/docs/superpowers/plans/2026-10-02-ergonomic-workflow.md deleted file mode 100644 index 0b1fcb4..0000000 --- a/docs/superpowers/plans/2026-10-02-ergonomic-workflow.md +++ /dev/null @@ -1,37 +0,0 @@ -# Ergonomic global development workflow - -## Authorization and scope -User explicitly requested improving global operating guidance after explaining that development should be comfortable, available across projects, and not require manual branch/worktree administration. Reuse the agreed change-focused browser/audit direction; add agent-owned workspace lifecycle to existing guidance. This is a bounded policy/documentation change, not a new executable orchestration subsystem. No new CLI/skill/plugin installation, permission/config change, service restart, old-worktree cleanup, remote push or npm publication is authorized by this slice. The previous beta.4 delivery is complete and immutable. - -## Checklist -- [x] Reconcile clean canonical main d52482f, completed beta.4 delivery and current global policy. Read official skills/plugin/workspace references. -- [x] Create this task's ignored Git fallback worktree and bind the current session; no native creation tool is exposed. Record ownership below; preserve all existing worktrees. -- [x] Run baseline source checks; back up live global instructions and preservation hashes privately. -- [x] Extend canonical policy/execution guide for comfortable defaults, agent-managed workspaces, change-focused actual browser/a11y checks and portable skill/plugin boundaries. -- [x] Apply the exact canonical sections globally while preserving every unrelated instruction/config/role/upstream skill; verify source/packed consumer and boundaries. -- [x] Independent read-only scenario review; address significant findings without adding scope. -- [x] Commit, integrate locally into recorded main target, reverify, safely retire only the task-owned worktree and return to canonical checkout. No remote delivery; idle checkpoint records closeout. - -## Workspace binding -- Owner: this OpenCode Primary/task, ergonomic-workflow. -- Workspace: /home/ksi/Desktop/KSI-Projects/KSI-Opencode-Harness/.worktrees/ergonomic-workflow. -- Branch: feat/ergonomic-workflow; base/integration target main at d52482f. -- Canonical checkout: /home/ksi/Desktop/KSI-Projects/KSI-Opencode-Harness. -- Other worktrees are existing unrelated ownership; do not remove or sweep them. - -## Evidence and boundaries -- Baseline npm run check exited0; /tmp/opencode/ksi-ergonomic-baseline-check.log. No dependency install needed for this built-in Node test suite. -- Private instruction backup /home/ksi/.local/state/ksi-harness-backups/ergonomic-workflow-20261002.h4526vf2;86 config/role/installed/upstream-skill hashes recorded, no credential values stored/output. -- Immediately after session_move, catalog briefly advertised only management tools; it subsequently repopulated Context7/browser/design tools without config mutation. This is actual transient discovery evidence, not proof of permanent config loss or working browser/product-server checks. -- Guidance validation uses exact source/live preservation and scenario review; do not invent runtime text-detector tests or claim a live screen-reader/audit/browser workflow. Tool installation remains separate. -- Applied canonical sections exactly once in live AGENTS.md; all other content preserved except declared Local Codex terminology clarifier, already-authorized skill-menu rule and on-demand execution-guide pointer. Harness confirms live instruction refresh;86 preservation hashes unchanged. /tmp/opencode/ksi-ergonomic-application.log. -- Source check64pass/0fail/1opt-in skip and packed consumer pass; /tmp/opencode/ksi-ergonomic-{check,package}.log. No new runtime tests: these are source/packaging checks, not proof of every future agent decision or live browser/a11y behavior. -- Independent read-only review found no Critical/Important issues;12 boundary scenarios pass as interpretation, not runtime proof. Primary owns the exact intended-edit and86-hash verification; reviewer independently inspected source/diffs/logs and spot-checked global sections. -- Review minor M1: canonical absolute guide pointer is intentionally authoritative across unrelated product repositories; after local main integration it resolves to the new guide. Do not prefer an arbitrary product's identically named docs/execution.md. M2/M3 optional explicit base/detection wording deferred: actual selected branch/HEAD/base inspection and installed isolation skill already cover them without new approval ceremonies. M4 clarified working-MCP alternative in execution guide. No further runtime/tool scope added. - -## Local technical outcome -- Source7a2aab4 committed with only four task-owned documentation files; canonical main rechecked clean atd52482f and fast-forwarded to that source. No unrelated edits, dependency installation or checkpoint in commit. -- Fresh pre-integration and integrated checks each64pass/0fail/1opt-in skip; packed consumer each passed. /tmp/opencode/ksi-ergonomic-{final,merged}-{check,package}.log. -- Actual owned workspace inspection found only tracked files, Git linkage and disposable ignored checkpoint; necessary facts already in this ledger. Session returned to canonical main, canonical checkpoint refreshed, then non-forced Git worktree removal and merged branch deletion succeeded. Existing four unrelated worktrees retained at unchanged revisions; no broad prune/force/delete. -- Live global policy remains the exact canonical sections plus declared clarifiers;86 preserved config/role/upstream files unchanged. No new CLI/skill/plugin, model/permission/config change, service restart, remote push or npm publication. Published beta.4 remains unchanged; current guidance supplement is local-only. -- Technical guidance task has no remaining engineering work. Human real-development comfort and end-to-end acceptance remain pending; no real browser/product/audio/a11y/long-running behavior is established by these checks. Future global toolkit installation needs its own concrete scope, not a mandatory single-project pilot. diff --git a/docs/superpowers/plans/2026-10-02-global-browser-toolkit.md b/docs/superpowers/plans/2026-10-02-global-browser-toolkit.md deleted file mode 100644 index 481cc8f..0000000 --- a/docs/superpowers/plans/2026-10-02-global-browser-toolkit.md +++ /dev/null @@ -1,52 +0,0 @@ -# Global browser toolkit implementation plan - -> **For agentic workers:** Use executing-plans for inline execution of this approved environment setup; no parallel writers needed. - -**Goal:** Make maintained browser CLI workflows available globally and prove they can actually drive a browser without adding another harness. - -**Architecture:** Preserve native Plan/Build/Explore, Superpowers, Context7, existing Playwright MCP and design-workspace ownership. Add pinned vendor CLI installations and complete version-matched official skill bundles in OpenCode's global discovery path. No KSI runtime plugin, automatic continuation gate, new product dependency or CI change. - -**Tech Stack:** Node24.19.0/npm11.17.0, ARM64 Linux, @playwright/cli0.1.22, agent-browser0.38.2, existing browser binaries/cache, official vendor skills, native global AGENTS.md. - -**Spec:** Approved in-chat recommendation and user's "그렇다면 개선해줘 그렇게 적합한 방향으로~"; tools first, runtime evidence helper only after demonstrated gaps and separate design. - -## Constraints and workspace -- Global means this host/user's execution environment across projects, not automatic installation on the connecting PC or other servers. -- Only browser CLI/official-skill preparation and bounded operating guidance are adopted here. No Oh My OpenCode, retired V1 worktree/review plugins, pretend V2 LSP enablement, Sentry, memory, standalone audit suite or real screen-reader setup. -- No personal browser profile/credential copying, broad debug-port exposure, sandbox weakening, system package installation, service restart, remote push or publication. -- Work in the clean canonical checkout main atf79fce2: small documentation-only Git changes, one writer. Global installations are outside Git; a separate product worktree would not isolate them. Preserve four existing unrelated worktrees. -- Base/local integration target: mainf79fce2, canonical /home/ksi/Desktop/KSI-Projects/KSI-Opencode-Harness. No native goal. - -## Review focus -1. Existing CLI/skill name collision: detect before installation; never overwrite user-owned definitions. -2. ARM64/runtime/browser compatibility: inspect installed help, actual launch, and architecture rather than trusting discovery. -3. Session/profile isolation: fresh named sessions, loopback smoke target, only task-owned browser/server cleanup. -4. Partial/stale evidence: capture real actions/reload, console/network and viewport/keyboard checks; don't call a fixture authenticated product verification. -5. Cross-project/global discovery: verify source/skill bundle and effective discovery, preserve unrelated config/roles/upstream skills; no automatic audits or endless loops. - -## Tasks -- [x] Inspect clean main, current checkpoint/product state, PATH/global npm prefix, official current docs and registry versions; baseline source check. -- [x] Back up global instructions and preservation hashes; install the two pinned global CLIs without product dependency changes. -- [x] Inspect version-matched skill bundles and collision-check standard global discovery roots; install/link complete official bundles into global OpenCode discovery and load them. -- [x] Run independent fresh CLI sessions on a disposable loopback HTTP fixture: snapshot, keyboard interaction, saved value/reload, mobile viewport, screenshot, console/network inspection. Inspect actual images; close only owned sessions/server. -- [x] Update docs/execution.md and canonical/global policy routing with actual capability/limitations, not guaranteed automation or popularity claims. Preserve native prompts/config/permissions/roles/upstream skills. -- [x] Verify source/packed consumer, live policy equality/preservation, tool versions and effective skill discovery; independent read-only review. -- [x] Commit only task-owned documentation on canonical main; no remote delivery or user end-to-end acceptance claim. Idle checkpoint records the final local closeout. - -## Evidence -- Baseline source check: /tmp/opencode/ksi-browser-baseline-check.log. -- Registry metadata pins @playwright/cli0.1.22 (Node>=18; Playwright1.64 alpha dependency) and agent-browser0.38.2 (Node>=24). Host satisfies Node requirement; native/browser ARM64 launch remains to be checked. -- No browser CLIs or colliding vendor skills found in current PATH/standard global skill roots before setup. Existing Chrome/Chromium/Firefox binaries and Playwright browser cache detected; not proof of working launch. -- Context7 currently connected and used for vendor docs; no duplicate MCP registration needed. -- Private backup: /home/ksi/.local/state/ksi-harness-backups/browser-toolkit-20261002.q2i295f1;368 preserved configuration/role/skill files hashed. No secrets output. npm installation log /tmp/opencode/ksi-browser-install.log:4packages added; npm11 left agent-browser postinstall unapproved/unrun. Packaged native ARM64 binary already works; no lifecycle-policy bypass or npm config change needed. -- Both pinned versions verified by commands/npm list; official complete11-file Playwright skill bundle linked and agent-browser official discovery stub linked to installed package; native skill tool successfully loaded both and harness advertised them. Core/reference content stays in vendor package, not copied into a competing KSI skill. -- Ruling: use existing system Chrome rather than cached developer Chromium. Reproduction showed AppArmor userns restriction1 and cached executable lacking applicable profile; system Chrome already has root-owned userns profile/helper. Changing only the executable fixed sandboxed launch. Both actual chrome://sandbox pages confirmed Namespace/PID/network/Seccomp-BPF active. No security-policy modification or disabling sandbox. Added new collision-free host browser configs only. -- Ruling: agent-browser startup flags must stay stable across commands. Omitting first-call executable/dialog flags caused relaunched blank browser; stable user-global config restored consistent session. Existing unrelated config files weren't rewritten; this is browser CLI setup, not an OpenCode orchestrator. -- Actual fixture checks: two independently named sessions, snapshots/keyboard Tab focus/Enter save, real POST/GET200 plus disk-state/reload value matches,390px viewport/no horizontal overflow, four inspected screenshots, no console/page errors; Playwright reduced-motion true. agent-browser bundled axe4.12.1 ran locally:0violations/0incomplete/25passes/64inapplicable. Scratch /tmp/opencode/ksi-browser-toolkit-20261002 includes fixture/state/server logs/images/audit result. Not product auth/API acceptance, WCAG conformance, real-device or screen-reader listening. -- Prepared global Playwright config also launched successfully from an independent non-project cwd and read the previously persisted server value. Task-owned three browser sessions closed individually; no close-all/kill-all, personal profile, cloud provider or debug-port exposure. /tmp/opencode/ksi-browser-toolkit-20261002/smoke-summary.json and sandbox evidence files. -- Final source64pass/0fail/1opt-in skip and packed consumer pass: /tmp/opencode/ksi-browser-final-{check,package}.log. Exact live policy edits verified against private backup;368 prior config/role/skill hashes and all previously installed global npm top-level versions preserved. /tmp/opencode/ksi-browser-preservation.log. Only the two vendor skill symlinks and new browser configs added outside Git; core OpenCode config/permissions/models/native agents/upstream files untouched. -- Fixture server stopped with SIGTERM only after matching recorded PID, exact cwd and fixture command; expected shell143 notification is task cleanup, not a failed capability test. Scratch evidence retained, no broad deletion. No actual customer-product interaction/authentication, screen-reader audio or human comfort acceptance claimed. -- Independent read-only final review found no Critical implementation defect. Its Important items were pending commit/process and limits of its own review, not contrary evidence: Primary already directly loaded skills, recomputed368hashes/compared actual npm baseline, inspected all four images and closed the three named sessions; reviewer inspected files/raw reports/logs and confirmed no fixture listener/process remained. Keep those evidence roles distinct; do not call review itself live browser testing or human acceptance. Pending commit/closeout completed under the adopted local policy, not extra authorization. -- Review minor spacing corrected in execution guide. Host path portability is explicitly scoped/revalidation required; `-s=` syntax directly exercised by Primary with actual names in all Playwright tests. No missing-code fix, additional plugin or new installation scope. -- Source974d97f committed locally on main with exactly four task-owned documentation files; installation/config/skill artifacts remain host-managed outside Git. Final pre-commit source64pass/0fail/1opt-in skip and packed consumer pass: /tmp/opencode/ksi-browser-closeout-{check,package}.log. Main was clean immediately after source commit; no checkpoint, secret, temporary fixture or product dependency in the commit. -- Technical toolkit setup is complete; no required engineering task remains in the approved scope. Human real-development comfort/actual product acceptance is pending. No remote push/npm publication/service restart, standalone audit installation, real screen-reader setup or KSI plugin generation; four unrelated worktrees retained. diff --git a/docs/superpowers/plans/2026-10-03-design-completion-notifier.md b/docs/superpowers/plans/2026-10-03-design-completion-notifier.md deleted file mode 100644 index 37a6e2e..0000000 --- a/docs/superpowers/plans/2026-10-03-design-completion-notifier.md +++ /dev/null @@ -1,202 +0,0 @@ -# Design completion notifier — workstream ledger - -Updated: 2026-10-03 -Owner: OpenCode Build (this session) -Status: technically complete; user real-run acceptance pending - -## Objective -Close the OpenDesign completion loop for the KSI workflow: when an OpenDesign -run finishes, the *originating OpenCode session* is notified automatically -instead of relying on the user to ask again. Delivery is an advisory -notification; verification and implementation decisions stay with the OpenCode -agent and the user. - -## Approved scope (user request 2026-10-03: "아무튼 너가 말한 대로 개선을 해줘") -- Durable run↔session binding recorded before the start response can be lost. -- Completion detection with a polling fallback (no new generation, no retry). -- Delivery into the original session via the native session synthetic inbox - (`delivery: queue`; `resume: false` while paused). -- Restart/compaction recovery from durable state, duplicate suppression by - deterministic message id and per-(run, session) binding key. -- Policy: paused → hold without auto-execution; Plan mode → report only; - deleted session → orphaned (never deliver elsewhere, never spawn an agent). -- No new orchestrator: a single local OpenCode V2 plugin, no second primary, - no automatic design generation, no npm publication, no Cloud. - -## Non-goals -- Regenerating or retrying any design run automatically. -- Cross-machine delivery; the daemon and OpenCode server must be reachable on - this host (default `http://127.0.0.1:7456`). -- Replacing native background subagent notifications or goal state. -- Packaging in the npm tarball (repo-only component for now; revisit on a - distribution decision). - -## Design summary -Component: `plugins/design-notifier/` (V2 plugin, pure JS ESM, no deps). -- `setup(ctx)` registers `ctx.tool.hook("execute.before"/"execute.after")`, - a `design_runs` tool (list/watch/pause/resume), and a poller. -- Binding capture: `execute.before` records `{requestId, sessionID, ...}` for - `opendesign_start_run`; `execute.after` parses the run id and re-keys the - binding to `run:@`. -- Lost response: bindings without a run id are reconciled via - `GET /api/runs?projectId=` matching `clientRequestId`; give up to - `unresolved` after 24h (no blind retry). -- Polling: default 60s (configurable), per-run status via - `GET /api/runs/:id`; terminal = succeeded|failed|canceled. -- Delivery: `ctx.session.synthetic` with a policy message built from the run's - `deliverableValid/deliverableValidation/failure*` fields; deterministic - `msg_...` id; one delivery per (run, session). -- State: `~/.local/state/opencode-design-notifier/` (`bindings.json`, - `paused`, `events.log`); env override `KSI_DESIGN_NOTIFIER_STATE_DIR`. - -## Verification plan -1. Unit/integration tests under `test/` with fake store/daemon/deliver: - binding capture, lost-response recovery, dedup, restart recovery, pause, - session-deleted orphan, parse failures (RED→GREEN where practical). -2. `npm run check` and `node scripts/check-package.mjs` unchanged and green. -3. Live activation (after private config backup): plugin loaded, hook observed - for a read-only opendesign tool call, E2E delivery to a scratch session - using an existing terminal run, dedup re-check. No new generation run. -4. Independent reviewer pass on plugin code before closeout. - -## Evidence log -- Implementation: `plugins/design-notifier/` (index + state/daemon/messages/notifier - libs, package.json, README), tests `test/design-notifier-{state,messages,notifier,plugin}.test.mjs`, - doc section in `docs/integrations/opendesign.md`, example entry in `opencode.jsonc.example`. - V2 2.0.22 tool hooks carry `sessionID`/`result` (verified in `@opencode/plugin` types); - delivery uses `ctx.session.synthetic`; daemon reads use `GET /api/runs/:id` and - `/api/runs?projectId=` (host gateway, read-only). -- Tests: new suite 23 pass / 0 fail; full `npm run check` 91 pass / 0 fail / 1 opt-in skip - (`/tmp/opencode/ksi-design-notifier-check.log`). -- Live activation (private backup - `/home/ksi/.local/state/ksi-harness-backups/design-notifier-20261003.121883`): global - `plugins` entry added; plugin loaded (`events.log` setup records). Seven instances exist - because OpenCode loads the global plugin **per active location** (7 project directories); - they share the state dir and delivered exactly once in testing. `location` is now recorded - in the setup log line for diagnosis. -- Hook capture verified live: a read-only `opendesign_list_projects` call appeared in - `observedTools` (`execute.before` fires for MCP tools, including Code Mode nested calls); - raw tool names use the `opendesign_start_run` underscore form, which `isStartRunTool` - matches. Real `start_run` capture remains to be observed on the user's next design run - (extractRunId matches the verified `ok()` wrapping: - `content:[{type:"text", text: JSON.stringify(payload)}]`, `payload.id` = run id). -- E2E delivery (scratch session `ses_efeac34dfffeiorYtVo7ruF0n3`, model - `muse-spark-1.3-contributor#low`; existing terminal runs only, **no new generation**): - 1. paused + run `78da352e…` → admitted with `resume:false`: inbox item present, context - empty, session cost 0 (no execution). - 2. resumed + run `f64f2576…` → one delivery event with `resume:true`; the idle session - executed a turn without user input (cost 0.00213, in/out 21079/79) and reported the - `entry_not_touched`/invalid verdict correctly with no regeneration. - 3. Dedup: exactly two synthetic messages in context, one `delivered` event per run, - inbox empty afterwards. Evidence: `/tmp/opencode/ksi-design-notifier-e2e.json`. -- Guard preserved: `test/native-package.test.mjs` ("never instructs registering KSI in - plugins") rejected an example-config mention of the harness plugin path; the example change - was reverted instead of relaxing the guard, and activation guidance lives only in - `docs/integrations/opendesign.md` + the plugin README. -- Independent review (`reviewer`, read-only, session `ses_efea9685affeFhcLf5rSzOcnBP`): ran - 23/23 tests; one Critical (multi-instance check-then-deliver race; "exactly one" doc overclaim), - Important items (watch/re-notify doc contradiction; `orphaned` conflated a daemon-missing run; - prefix matching undocumented), and minor robustness notes (structured MCP output, corrupt - non-object JSON, tmp-file cleanup, tick fatal handling, options docs). One-scope fix pass: - - Delivery claim under the state lock (`store.claimDelivery`, 120 s TTL) → concurrent instances - cannot both deliver; `store.mutate` now skips the write when nothing changed; tick failures are - logged (`tick-fatal`); corrupt non-object JSON is preserved; stale temp files are reaped; attach - without any session id keeps the request record instead of keying `@unknown`. - - New `missing` state (run absent on the daemon) distinct from `orphaned` (deleted session); - reap/cap treat both as final. - - `extractRunId` also reads `structuredContent`/`structuredOutput`; `toolNamePrefix` option/env - for renamed MCP servers (default `opendesign`), documented with `observedTools` diagnosis. - - Docs corrected: claim + deterministic id semantics, no "re-notify" claim for `watch`, states and - options/env lists, prefix limitation. -- Server dedup verified empirically (probe `msg_454415d7…`): posting the same synthetic `id` twice - returned the original admission and the inbox held exactly one item — the deterministic id makes - a re-send a session-level no-op even across the claim TTL. -- Post-review suite: notifier tests 26/26; full `npm run check` 94 pass / 0 fail / 1 opt-in skip - (`/tmp/opencode/ksi-design-notifier-check3.log`). - -## Hardening pass 2 (2026-10-03, user approved "그렇게 하자") - -Approved direction: keep the thin connector and improve the *safety* of the -structure — (1) session-level wake permission, (2) no lock-less writes and -separated held/delivered states, (3) one poller instead of per-location polling, -(4) a pinned runtime copy instead of point the config at the dev tree, (5) defer -event streams and keep polling. - -- Semantic change: **held completions live in notifier state, not in the - session inbox.** The plugin context exposes no inbox list/cancel API, so a - paused session is not contacted at all; `resume` delivers held completions - with auto wake. (Replaces the pass-1 `resume:false` inbox admission.) -- Implementation: `be421d9` (plugin 0.2.0) — `paused.json` per-session/global - wake permission, `held` state (not TTL-reaped), state-lock leader lease - (`leader.json`, 60 s TTL, ≤20 s renew, released on unload) with per-instance - capture and single-poller delivery, `withLock` now fails the write after a - 5 s timeout instead of proceeding unlocked, `scripts/install-design-notifier.mjs` - (SHA256 manifest, `--verify`, `--uninstall`, refuses non-plugin targets). -- Tests: notifier suite 36/36 (held/resume, per-session isolation, global - switch, leader gating/takeover/release, lock-timeout failure, legacy pause - migration, installer); full `npm run check` 104 pass / 0 fail / 1 opt-in skip - (`/tmp/opencode/ksi-design-notifier-check4.log`). -- Pinned install verified live: manifest commit `be421d9`, 7 files, `--verify` - ok; global config entry pointing at the repo removed (private backup - `design-notifier-pin-20261003.821d07`); `opencode plugin list` shows only - `/home/ksi/.config/opencode/plugins/design-notifier/index.js`; auto-discovery - of the config-dir plugin copy confirmed. -- Live E2E on the pinned runtime (new scratch session - `ses_efe919192ffe4GKJ8ivvdT8GFR`, low model; existing terminal run - `78da352e…`; no new generation): paused → `held` with **cost 0 and inbox 0** - (inbox observed via `GET /api/session/{id}/inbox`; session untouched) → resume → delivered - `resume:true wasHeld:true` within one - leader tick → session executed the turn automatically (cost 0.0021329, - in/out 21103/82) and reported the invalid verdict correctly; delivered-event - count stayed 1 across a further tick (dedup). Evidence - `/tmp/opencode/ksi-design-notifier-hardening.json`. One `leader-claimed` per - reload batch confirms single-poller election with 7 locations. -- Transition artifact: 7 `tick-fatal: store.isPaused is not a function` entries - during a mid-edit reload; no errors after the final reload batches. -- Deferred by design: daemon event/SSE subscription (item 5) — polling remains - the single mechanism until a demonstrated need. -- Not included: approval-hash binding (direction approval stays conversational). -- Known limits: resume latency ≤ one poll cycle; leader takeover ≤ lease TTL - after a crash; goal-pause state not observed (explicit `paused` only). - -## Policy alignment — finish-and-report default (2026-10-03, user-directed) - -User approved the clarified flow ("이렇게 개선해봐"): one instruction → brief → -design → implementation → tests → result report without intermediate "shall I?" -gates; the design is shown together with the implemented result; stop only for -external delivery, irreversible/costly actions, and new brand/identity-level -direction (plus client-facing deliverables and explicit "review first" -requests); keep non-dependent work moving while a run or check is pending. - -- Global `~/.config/opencode/AGENTS.md` updated after private backup - (`design-autonomy-20261003.0445ff`, original sha256 `50659393…`): the design - bullets now default to finishing and reporting with the preview, gate only - the listed cases, keep non-dependent engineering moving while waiting, and - state that visual design quality comes from OpenDesign's harness (Build does - not substitute its own design). "Local Codex" phrasing clarified as - OpenDesign's local execution mode. -- Notifier message step 3 (`lib/messages.js`) and its test now instruct the - receiving agent to default to finishing within authorized scope and to stop - only for new direction / client deliverable / out-of-scope (or an explicit - user request). Docs aligned: `docs/integrations/opendesign.md` - (result-interpretation row, handoff approval paragraph, notifier section) and - plugin README. -- Verification: notifier suite 39/39; full `npm run check` 107 pass / 0 fail / - 1 opt-in skip; pinned copy reinstalled and `--verify` green. -- Bonus verification the same day: the first **real** (non-test) `start_run` capture - occurred — EVENTOUCH session `ses_efe9d3ab…` ("디자인 이어서 개선하기"): request - `a3f1c2d4…` recorded 11:38:20Z, run `9443ee43…` attached via the hook, terminal - 11:50:23Z, delivered `resume:true` 11:50:32Z (≈9 s, within one poll cycle). The - session was active, so the queued notification awaited its next input processing - (no interruption, as designed); the run's `entry_missing` verdict was classified - invalid so the message could not be misread as a successful deliverable. -- Post-pass independent review (reviewer session `ses_efe8db196ffeoNuUKM6expruqE`, - no Critical; tests matched claimed numbers). One-scope fixes: pause re-check - after the delivery claim (narrows the pause-vs-delivery window; the residual - window is documented), hook/tool lock-error surfacing (`hook-error`, - `pause failed: …`), corrupt `paused.json` preserved aside and logged, installer - staged directory swap and argument guards, docs wording corrected ("elected - poller", overlap/pause limits). Tests added: concurrent leadership claims - (exactly one winner), held→tracking regression on a re-activated run, corrupt - `paused.json`. Suite 39/39; full `npm run check` 107 pass / 0 fail / 1 opt-in - skip (`/tmp/opencode/ksi-design-notifier-check5.log`). diff --git a/docs/superpowers/plans/2026-10-03-design-flow-hardening.md b/docs/superpowers/plans/2026-10-03-design-flow-hardening.md deleted file mode 100644 index 55475e7..0000000 --- a/docs/superpowers/plans/2026-10-03-design-flow-hardening.md +++ /dev/null @@ -1,95 +0,0 @@ -# Design-flow Hardening Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans inline. Track steps here; do not create a second authoritative ledger. - -**Goal:** Harden the existing design-to-engineering workflow and enforce read-only design access in Plan without adding an orchestrator. - -**Architecture:** Retain the existing MCP/host-proxy/design-run boundaries. Improve operating guidance and an explicitly adopted Plan-only native permission allowlist; verify it with the existing isolated V2 verifier, not live design mutations. - -**Tech Stack:** Existing Markdown/JSONC, Node test runner and OpenCode V2 native permission API. - -**Spec:** `docs/superpowers/specs/2026-10-03-design-flow-hardening.md` - -## Global Constraints -- No upstream application edits, new dependencies, native prompt replacements, cloud fallback, credential reading/sharing, container restart, security-profile change, product implementation, push, npm publication or automatic generation. -- Keep installer behavior at exactly three optional roles; policy/config adoption remains separate. -- Preserve unrelated global configuration, models, native prompts, upstream dirty files and unfamiliar worktrees. -- Use existing ledger/checkpoints; guidance is not runtime enforcement or end-to-end acceptance. - -## Review Focus -- Physical success with zero output and sandbox errors must not become a design-completion claim. -- Questions and verified no-change must not be mislabeled generation failures or trigger blind retries. -- A future unknown MCP mutation must remain denied in Plan; required reads must work. -- Build's design permissions and native Plan file-edit restrictions must remain unchanged. -- Effective deployment/storage identity must not be inferred from health, project name or historical deployment notes. - -## Workspace -- Primary: this session, inline execution per adopted local autonomy; no implementer team. -- Workspace: `.worktrees/design-flow-hardening`, branch `improve/design-flow-hardening-20261003`. -- Base/local integration target: canonical `main` at `c35a57a`; only non-destructive fast-forward integration after verification. -- User approval: "그렇다면, 개선해줘" following the six-item review. Security-sensitive deployment/restart is a separate decision. -- Baseline: `npm run check` — 64 pass, 0 fail, 1 opt-in Docker smoke skipped. - -### Task 1: Native Plan design-permission boundary -**Files:** `scripts/verify-native-v2-isolated.mjs`, `test/verify-native-v2-isolated.test.mjs`, `opencode.jsonc.example`. -**Interfaces:** The verifier consumes the sample's optional `agents.plan.permissions` in an isolated config, calls the native permission-evaluation API with explicit Plan/Build agents, and produces `designPermissions` evidence. No MCP tool or provider is invoked. -- [x] Add native evaluation checks and unit coverage for returned permission decisions. -- [x] Run the isolated verifier before adding the rules; observe Plan mutation/unknown-tool checks fail because the native decisions are allow. -- [x] Add deny-by-default Plan design-namespace rules with explicit known read exceptions; preserve all other sample content. -- [x] Rerun isolated verification, full harness and packed consumer checks. - -### Task 2: Workflow guidance and current-user adoption -**Files:** `docs/integrations/opendesign.md`, `docs/execution.md`, `docs/troubleshooting.md`, `examples/autonomous-development.md`; separately managed live `~/.config/opencode/AGENTS.md` and `opencode.jsonc`. -**Interfaces:** Use Task 1's tested rule list exactly; do not copy native prompts or install a plugin. Guidance covers intake, storage/brand binding, result classification, deliberate recovery and narrow discovery. -- [x] Update the existing guide, shared policy and pointers; self-review blocker/question/no-change/new/refinement/deployment-drift scenarios. -- [x] Back up only the two user configuration files privately; apply the approved policy additions and tested Plan-only rules with unrelated bytes/settings preserved. -- [x] Query live native Plan/Build catalogs and evaluate safe permission requests; no design mutation, provider call or shared-service restart. -- [x] Verify policy consistency and configuration preservation; record results. - -### Task 3: Infrastructure diagnosis and verified local integration -**Files:** this ledger, `docs/superpowers/product-state.md`, private checkpoints. -**Interfaces:** Read-only comparison of connector configuration and current container; no automatic deployment repair or generation retry. -- [x] Verify current approved-vs-effective deployment drift and describe a separately authorized recovery action, including interruption/rollback scope. -- [x] Run fresh full checks, packed consumer and whitespace checks; independently review the whole task diff. -- [x] Commit only task-owned changes, fast-forward canonical main after rechecking its status/HEAD, and rerun affected checks. -- [x] Update durable evidence and checkpoints; report applied improvements, remaining deployment blocker and human acceptance separately. - -## Decisions and evidence -- Pre-flight: Task 2 consumes Task 1's Plan-only rules; both use native normalized `opendesign_` actions with resource `*`. No new runtime interface. -- Ruling: native inline execution and local integration follow the already-adopted operating policy; no additional execution-method menu or approval ceremony. Cost if wrong: workflow preferences require correction, not product-data recovery. -- Ruling: strengthen Plan's design-MCP permissions only, not blanket shell/subagent restrictions or all custom read-only roles. This fixes the reviewed boundary without inventing a general host sandbox. Other-agent/direct-shell bypass prevention remains outside this slice. -- Task 1 evidence: native V2.0.21 evaluator RED denied expectations failed for ten Plan mutations/unknown actions; GREEN passed all23 decisions (11 reads,10 mutations/unknown,Build generation,Plan edit). No design tool/provider invocation. Logs `/tmp/opencode/ksi-design-flow-permissions-{red,green}.{json,err}`. Full suite66pass/0fail/1opt-in skip; packed consumer passes. - -## Additional recovery authorization -The user explicitly approved "복구·검증 승인 (권장)": inspect image/data compatibility and active work first; restore the existing reviewed deployment only if safe, preserving volumes/credentials/security; one small Local Codex file-access/output check is authorized. EVENTOUCH regeneration is not authorized. This overrides the earlier no-recreation constraint only for that bounded recovery, not general deployment/security changes. - -### Task 4: Restore existing reviewed deployment, if safe -- [x] Confirm the reviewed image shares the installed image's exact base layers/version/revision, approved profile exists, retained project roots are valid and no run/connection is active. -- [x] Preserve private rollback configuration and task-owned integrity evidence; recreate only the fixed design service using the existing approved Compose/image/security/project inputs. Never remove volumes, expose credentials or grant Docker to an inner agent. -- [x] Verify health, actual image/security/mount identity and same-session MCP read recovery without manual reconnect; run the single authorized task-owned file-access/output fixture and inspect bytes, not terminal status alone. -- [x] Record actual evidence and rollback/blocker outcome. No EVENTOUCH retry, new cloud service, package publication or host reboot. - -## Verified implementation / review evidence (before local integration) -- Task 2: live native permission evaluation23/23 passed, Build catalog byte-equivalent before/after, direct design tools0/provider requests issued0. Only a task-owned transient evaluation session was created/deleted. `/tmp/opencode/ksi-design-flow-live-permissions.json`. -- Exact global-config unrelated bytes preserved; shared operating bullets match; only approved global guidance edits applied. Private original config/policy backups retained owner-private. `/tmp/opencode/ksi-design-flow-preservation.json`. No shared OpenCode service restart. -- Task 4 compatibility: reviewed image retained all17 installed base layers plus two reviewed overlay layers, same version0.24.1 and base revision89e64d813bb1c7a11519b3f668f011f7017637d7. Approved image digest `sha256:a9b79052fe2e6e839a8fb854cc2f0af2804c88496f066ae536f70d26124a5005`; prior base image digest `sha256:70624117e8ac09892a2b3d3baef9125d0a5027d0dc591b25b249c6e3715beadb`. -- Initial preflight:12 terminal runs/no active runs, zero active connector operations and three retained canonical roots valid. Attempt1 applied the reviewed deployment, then a nested verification-command escaping error caused safe rollback to the original image. `/tmp/opencode/ksi-design-flow-recovery-attempt1.err` retained. Corrected command syntax checked before attempt2; no new profile/security workaround. -- Final attempt2: restored reviewed image with all five approved Compose inputs, enforced `ksi-codex-userns`, CapEff0, NoNewPrivs1 and Seccomp2; managed volume, three selected-root mounts, original EVENTOUCH file hashes and deployment input bytes preserved. Existing deployment/config backups owner-private. `/tmp/opencode/ksi-design-flow-recovery.json`. Runtime run-list count reset across daemon recreation; this was not used to claim historical run-record deletion or perpetual supervision. Project/conversation metadata and original files remain accessible. -- Same session's design MCP read recovered after recreation without `/mcps` reconnect. Actual EVENTOUCH project/storage read and existing technical fixture project read verified; no EVENTOUCH generation requested by this task. -- Single authorized fixture: existing `ksi-deploy-smoke`, request `8e49bbfd-6830-4c04-8122-71ac4d62cc3f`, run `78da352e-587d-4119-9686-fdf3cf02e885`. New exclusive input/output only; one successful Bash tool/no tool errors. Source bundle retrieval untruncated/no skipped files and independent filesystem evidence verified input text, cwd and SHA256 against actual bytes. Output `flow-hardening-20261003-b7d8.html`,318bytes, raw SHA256 `6da54a3f3e9681d936f0957891a78e656ea5d46e5f6a09172b4f2110ee6ead55`; input SHA256 `165b58be698f85526cb7eeb29712175056bf6ec2999fec3c37df0c01e226d133`. `/tmp/opencode/ksi-design-flow-fixture-evidence.json`. -- Fixture physical success had `deliverableValid:false`, `entry_not_touched`: it intentionally preserved the existing canonical entry. This proves file-read/hash/exclusive-output capability, **not** canonical design delivery, visual quality, production integration or human approval. The fixture is retained as technical evidence; no claim of browser-rendered inspection. -- Independent whole-task reviewer found no Critical issue and two Important improvements: ensure full decision coverage and check actual renamed MCP prefix. Coverage/drop/duplicate/tampered-expectation tests reproduced the gap RED2fail then GREEN4pass; checker now requires all23 unique contract cases with independent expected verdicts. Prefix-adoption/rename procedure clarified. Final source suite68pass/0fail/1opt-in skip, packed consumer passes, native isolated23/23 pass, whitespace clean. Logs `/tmp/opencode/ksi-design-flow-review-{red,green}.log` and `/tmp/opencode/ksi-design-flow-final-{check,package,isolated}.{log,json,err}` as applicable. -- Final minor (deferred): verifier parses the maintained example's documented full-line-comment subset, not arbitrary JSONC. An unsupported inline comment fails closed; no parser dependency was added. -- Final review scope ruling: independent reviewer did not verify live fixture bytes, deployment identity, private backup contents, visual acceptance or final integration. Primary verified allowed fixture/deployment/packed-consumer evidence; backups remain private, other-project precedence and human visual acceptance remain unclaimed. Cost if wrong: overstated coverage; therefore each boundary is explicitly reported. - -## Local integration / technical outcome -- Source commit `3fbf64b` contains only the10task-owned source/spec/ledger files; no private checkpoint, backup, credential, operator scratch script or runtime data staged. Target canonical main was clean and remained atc35a57a before fast-forward; integrated non-destructively and rebound this session to canonical root. -- Fresh integrated checks at3fbf64b: `npm run check`68pass/0fail/1opt-in skip; `npm run check:package`passes; `node scripts/verify-native-v2-isolated.mjs`all23native decisions and role/skill preservation pass; whitespace clean. `/tmp/opencode/ksi-design-flow-merged-{check,package}.log` and `/tmp/opencode/ksi-design-flow-merged-isolated.{json,err}`. Only subsequent closeout documentation/checkpoint edits follow; engineering/configuration bytes are unchanged. -- Technical scope complete locally: current-user policy/native guard applied and reviewed deployment/file-access capability restored. Human design/end-to-end comfort acceptance remains pending; do not close m04 or launch a new milestone. No push/new release/npm publication, EVENTOUCH regeneration, upstream application change, provider/model reconfiguration or host reboot. -- Preserve private backups and task-owned technical fixture evidence. Only this task's clean integrated worktree/branch may be retired after checking untracked/ignored files; four unrelated worktrees remain untouched. - -## Completion-notification follow-up (read-only, user asked after restart) -- Not resolved by this slice: design-MCP commissioning still returns immediately and requires the caller to poll; it does not attach an external design run to OpenCode's native background-subagent completion delivery. Actual restored runtime `/app/apps/daemon/dist/mcp.js:2145-2147,2235` and source `apps/daemon/src/mcp.ts:2610-2612,2699-2717` confirm this contract. -- The design daemon exposes a run event stream (`apps/daemon/src/routes/runs.ts:3518-3529`) to connected subscribers; the current host proxy/gateway does not implement a terminal-event-to-original-OpenCode-session wake/inbox bridge. The configured plugins were not extended. MCP session correlation metadata is not a completion subscription or authorization. -- After the OpenCode server restart, explicit reads of both the original EVENTOUCH failed-output run and the technical fixture run still succeed; source files/project metadata remain available. This proves lookup recovery for these runs, not automatic notification, terminal-event replay or agent continuation after closing/restarting a session. Merely keeping a chat window open is not a scheduler. -- Needed future design: durable originating-session/run binding, trusted native completion delivery, startup reconciliation/deduplication and pause/mode/approval preservation; prefer a thin notification adapter over a second engineering orchestrator. No such adapter, goal or extra generation was installed/started under this follow-up. Human scope approval is required before implementing the additional integration. diff --git a/docs/superpowers/plans/2026-10-04-autonomy-gaps.md b/docs/superpowers/plans/2026-10-04-autonomy-gaps.md deleted file mode 100644 index b143868..0000000 --- a/docs/superpowers/plans/2026-10-04-autonomy-gaps.md +++ /dev/null @@ -1,58 +0,0 @@ -# Autonomy gaps — workstream ledger - -Updated: 2026-10-04 -Owner: OpenCode Build -Status: complete (technical) - -## Objective (user-directed "나머지 모두 개선해줘") -Close the three remaining pieces from the autonomy synthesis: -1. Cost guard — goals must carry limits; a usage/status summary exists. -2. Completion visibility — goal stops are reported with a fixed contract; one - consolidated report shows goals, design-run deliveries and usage. -3. Next-task proposal — milestone/closeout reports end with top backlog - candidates instead of waiting for the user to ask. - -## Actions -A. Global `~/.config/opencode/AGENTS.md` (backup `autonomy-gaps-20261004.6b88cd`, - prior sha256 `03407356…`): - - Session goals: "Never create an unbounded goal: every goal carries limits, - and its objective states the stop-report contract (final status, evidence, - why it stopped, next options)." - - Autonomous final report: "End every milestone/closeout report with the top - 1–3 next candidates from the product-state backlog (one-line rationale - each); starting them still requires user authorization." -B. `scripts/work-report.mjs` (new, read-only): consolidated report — goals - (status/tokens/turns from goals.json), design-run deliveries (notifier - bindings + pause state, pending first), usage by project and top sessions - (`session_v2` via read-only sqlite), worktrees + product-state Next slices for - a given repo. Options: `--repo`, `--days`, `--json`. Unit tests - `test/work-report.test.mjs` (4) cover section parsing, goal normalization - (second→millisecond timestamps), binding ordering and token formatting. -C. Deliberately not built: a separate goal-completion watcher/notifier. Goal - completion happens in-session (the session reports as it finishes) and there - is no better delivery channel than the session/TUI; the actionable gap was - silent limit-stops, addressed by limits + the stop-report contract. Revisit - if a real silent-stop incident appears. - -## Verification -- `node --test test/work-report.test.mjs` → 4/4. -- Live report run shows real state: 2 active goals (EVENTOUCH 14-view redesign - `ses_efe9d3abfffe`; SIP external-runtime bundle `ses_f0e6f0de9ffe`) and one - design run in flight (`31c01c6a` → EVENTOUCH session), plus usage by project - and worktree inventory. -- test-runner independent closeout: session `ses_efa73e693ffeK3fg27wvs2a0Q6` ran - `npm run check` → exit 0, tests 112 / pass 111 / fail 0 / skipped 1 (log - `/tmp/opencode/ksi-autonomy-testrunner-check.log`). -- AGENTS.md diff reviewed; private backup kept. - -## Notes -- The mandatory-limit wording in Objective/Actions/Verification above records the - original decision, not current policy. Later the same day the user explicitly - chose generous safety rails, no-progress auto-pause as the primary brake, and - user-adjustable/removable limits for product-critical work. The stop-report - contract remains required. No global cap or existing-goal change is implied. - Reconciled in `2026-10-04-guidance-alignment.md`; retain earlier evidence as history. -- Usage costs only cover metered providers; subscription sessions show tokens - with cost 0 (tokens remain the primary signal). -- The report is on-demand; the durable "no reminder needed" pieces are the - closeout next-candidates rule (A) and the goal stop-report contract. diff --git a/docs/superpowers/plans/2026-10-04-guidance-alignment.md b/docs/superpowers/plans/2026-10-04-guidance-alignment.md deleted file mode 100644 index e3342f1..0000000 --- a/docs/superpowers/plans/2026-10-04-guidance-alignment.md +++ /dev/null @@ -1,85 +0,0 @@ -# Guidance alignment — workstream ledger - -Updated: 2026-10-04 -Status: technically complete locally; real-work acceptance pending -Owner: OpenCode Build - -## Approved scope - -User approved the preceding bounded review with “개선해줘”: retain native -Build/Plan/Explore prompts, reconcile current policy with shipped guidance and -state, distinguish approved requirements from implementation evidence, and add -conditional next-task judgment without mandatory product-wide audits. -No model, permission, custom-role, plugin, goal, publication or deployment changes. - -## Workspace - -- Owned workspace: `.worktrees/guidance-alignment` -- Branch: `fix/guidance-alignment-20261004` -- Base and agreed local integration target: `main` at `407155365715fd57c174d516c27b14ab026479e6` -- Global AGENTS.md private backup: `guidance-alignment-20261004.f9ve7d7q` -- Configuration/custom-role SHA256 preservation snapshot: six files in that backup. - -## Checklist - -- [x] Read actual global policy, main source, checkpoint, product state and active ledger. -- [x] Confirm bounded scope and user's approval; create owned isolated workspace. -- [x] Baseline `npm run check`: 112 tests / 111 pass / 0 fail / 1 opt-in skip. -- [x] Align guidance and current state; annotate superseded historical decisions. -- [x] Independent read-only review of scope, contradictions and judgment scenarios. -- [x] Independent final source/package verification and configuration preservation. -- [x] Verified local commit, integration, affected checks and owned-workspace cleanup. - -## Verification contract - -Existing suite/package checks cover installer behavior and packaging, not agent -judgment quality. Prose-only changes do not get exact-wording tests. Read-only -review must consider: a small bug fix, next-task prioritization with an obsolete -backlog item, implementation contradicting approved requirements, and default -design integration versus a new-direction gate. Review is interpretation evidence, -not a live product acceptance or proof of improved average model behavior. - -## Evidence - -- Official V2 agent `system` replaces the provider base; AGENTS.md remains an - additional source, and instruction conflicts are not automatically resolved: - https://opencode.ai/v2/docs/agents and https://opencode.ai/v2/docs/instructions. -- No native prompt replacement needed or authorized in this change. -- Actual global AGENTS.md updated (not only an optional example): requirements vs - implementation state; conditional priorities; scoped requests; goal-relevant - closeout candidates without invented counts; explicit review-first handoff gate. -- Reviewer `ses_ef9ab5b19ffeUtbxtQQvhx5mU7`: four interpretation scenarios reviewed; - two gate-list omissions and one ambiguous supersession phrase identified and - repaired. Focused recheck reports all three resolved, no outstanding findings. - The notifier's actual message already includes review-first; no plugin edit. -- Independent test-runner `ses_ef9ab18deffeAxPByM7NN13gGc` initial run: - `npm run check` exit 0, 112 tests / 111 pass / 0 fail / 1 opt-in skip; - `npm run check:package` exit 0 (installer-only tarball/offline three-role install); - `git diff --check` exit 0. Logs: `/tmp/opencode/ksi-guidance-alignment-{check,package,diff}.log`. - Config/custom-role preservation: six SHA256 matches, zero mismatches/missing. - Post-repair run by the same independent test-runner on staged documentation at - source HEAD `4071553`: `npm run check` exit 0 (112/111/0/1), - `npm run check:package` exit 0 and `git diff --cached --check` exit 0 including - the added ledger. Logs: `/tmp/opencode/ksi-guidance-alignment-final-{check,package,diff}.log`. - Six config/custom-role hashes still match. Global AGENTS.md is an intentional, - separately reviewed edit, not one of those preserved paths. - No improved model-quality claim. -- Commit `84bbbc50b163ed1c7ad38a7b5870663c1639ce94` fast-forwarded into canonical - `main`; same independent test-runner verified that exact integrated HEAD: - `npm run check` exit 0 (112 tests / 111 pass / 0 fail / 1 opt-in skip), - `npm run check:package` exit 0, `git diff --check` exit 0; six preservation - hashes match. Logs: `/tmp/opencode/ksi-guidance-alignment-merged-{check,package,diff}.log`. -- Session returned to canonical main. Owned task workspace was inspected including - ignored files; its only unique ignored checkpoint was copied to the private - backup before removal. Non-forced worktree removal and merged-branch deletion - succeeded. Four pre-existing unrelated worktrees retained. -- No push, npm publication, native prompt/model/permission/role/plugin change, - service restart, goal mutation or provider/behavioral probe. Global AGENTS.md - changes were observed as native instruction updates in this session. -- Next acceptance candidate: observe scoped fixes and next-task decisions during - real authorized work (m06), plus existing design-flow human acceptance (m04). -- Final evidence-only delta independently checked by the same test-runner: - `git diff --cached --check` exit 0; only this ledger and product-state changed - after the integrated checks, both excluded from package.files; six config/role - hashes remain matched. Log: `/tmp/opencode/ksi-guidance-alignment-closeout-diff.log`. - Test-runner session rebound to canonical main after owned-workspace cleanup. diff --git a/docs/superpowers/plans/2026-10-04-verification-and-project-hygiene.md b/docs/superpowers/plans/2026-10-04-verification-and-project-hygiene.md deleted file mode 100644 index a214166..0000000 --- a/docs/superpowers/plans/2026-10-04-verification-and-project-hygiene.md +++ /dev/null @@ -1,46 +0,0 @@ -# Verification & project hygiene — workstream ledger - -Updated: 2026-10-04 -Owner: OpenCode Build -Status: complete (technical) - -## Objective (user-directed: "모델 라우팅 가이드 빼고 모두 개선해줘") -Close the gaps found in the 2026-10-04 usage audit: -1. test-runner independent verification had lapsed (0 uses since 2026-09-25); final checks were - run by the Primary. -2. EVENTOUCH design runs always validated `entry_missing` — root cause: no declared - `metadata.entryFile` plus two root HTML files, so entry inference always failed (project-config - gap, not a prompt/agent bug). -3. Goal mode was never proposed for multi-hour unattended work. -Excluded by the user: model routing guide (subscription vs opencode-go). - -## Actions & evidence -A. Verification roles restored — global `~/.config/opencode/AGENTS.md` (private backup - `workflow-hygiene-20261004.5c56d4`, prior sha256 `a8016a9d…`): - - Skills: "Final verification of a milestone or closeout is executed by `test-runner` - (independent run), not the Primary; record the test-runner session id, command and result in - the ledger. `reviewer` reviews, `developer` implements bounded tasks; use these roles by - default instead of letting all three collapse into the Primary." - - Session goals: "For multi-hour or unattended work … propose goal mode with explicit - turn/token/duration limits …; create or resume the goal only after the user agrees." -B. EVENTOUCH project entry declared: `PATCH /api/projects/eventouch-a82e` - `metadata.entryFile = "eventouch-operations.html"` (HTTP 200; existing `skipDiscoveryBrief` - preserved; verified via daemon GET and MCP `get_project`). Future runs validate against the - canonical entry (`valid` / `entry_not_touched`) instead of always `entry_missing`. - `docs/integrations/opendesign.md` gained a pre-commissioning entryFile check. -C. Goal proposal rule encoded (see A). -D. Model routing guide — excluded by the user. -E. Connection-test sessions: 26 total, last created 2026-10-01 01:37 KST, none in the last 3 days; - cleanup remains backlog, no deletion performed. - -## Verification (rule A applied on day one) -- First independent run: test-runner session `ses_efa8404ddffe6eHgEw2aoAMO3D` executed - `npm run check` → exit 0, tests 108 / pass 107 / fail 0 / skipped 1; log - `/tmp/opencode/ksi-hygiene-testrunner-check.log`. -- EVENTOUCH entry confirmed on both the daemon HTTP API and MCP `get_project`. -- AGENTS.md diff reviewed; private backup kept. - -## Limits / follow-ups -- The EVENTOUCH entry change takes full effect on the next real design run; no new run was started - for verification (generation needs separate approval). -- Connection-test session cleanup remains backlog (E2). diff --git a/docs/superpowers/plans/2026-10-05-beta6-release.md b/docs/superpowers/plans/2026-10-05-beta6-release.md deleted file mode 100644 index 8eb6e83..0000000 --- a/docs/superpowers/plans/2026-10-05-beta6-release.md +++ /dev/null @@ -1,77 +0,0 @@ -# Beta.6 delivery (source-direct flow release) - -## Authorization and target - -User approved ("그렇게 해줘") the proposed three-step release: update version pins to -0.5.0-beta.6, push GitHub main, and publish npm with tags next + latest. This delivers -the accumulated local improvements — design completion notifier, finish-and-report -default, work-report, guidance alignment, product-aware design flow, source-direct -frontend flow and the README alignment — through the existing GitHub main and public -npm. Keep all previous versions, the beta designation, the lightweight three-role -installer boundary and the host setup. Not another product deployment, service -restart, runtime/tool installation or replacement of the user-customized roles. - -## Workspace and baseline - -- Primary works in the clean canonical checkout - /home/ksi/Desktop/KSI-Projects/KSI-Opencode-Harness; base HEAD 13ad872, one writer. - Remote origin/main at c35a57a (beta.5 era), 25 local commits ahead at start. -- Registry preflight: dist-tags latest/next = 0.5.0-beta.5; 0.5.0-beta.6 unused (E404). - The npm CLI session needs fresh standard browser approval (whoami E401); use the - established pty + `--browser=false` path with user approval; no credential - collection, CLI/auth-policy mutation or approval bypass. -- Four unrelated worktrees preserved; no product/mount/security change in this task. - -## Checklist - -- [x] Reconcile main/remote/registry/auth; confirm beta.6 unused. -- [x] Advance version guards and observe the expected RED against beta.5. -- [x] Bump manifest + paired README/INSTALL pins to beta.6; no runtime/role/installer - behavior change. -- [x] Source/packed/isolated checks pass; tarball reviewed (18 files, integrity, - byte-identical README/INSTALL/docs, pinned beta.6). -- [x] Commit release content; commit records; push main without force; verify remote - HEAD. -- [x] Publish the reviewed tarball to npm next with standard browser approval; verify - public bytes/integrity. -- [x] Promote latest with standard browser approval; verify next/latest and a fresh - consumer. -- [x] Record delivery evidence; final verification. - -## Evidence - -- RED: guards advanced to beta.6 first — `npm run check` exit 1 (native installer - version test) and `npm run check:package` exit 1 (actual beta.5 / expected beta.6). - Logs /tmp/opencode/ksi-beta6-{version,package}-red.log. -- GREEN: `npm run check` exit 0 (112 tests / 111 pass / 0 fail / 1 opt-in skip), - `npm run check:package` exit 0, `node scripts/verify-native-v2-isolated.mjs` exit 0. - Logs /tmp/opencode/ksi-beta6-{check,package,isolated}.log. -- Tarball /tmp/opencode/ksi-opencode-harness-0.5.0-beta.6.tgz: 18 files; integrity - `sha512-+IRDbChjCa9B/NyWgqFvo7rt48dkiTxBNyABfapCBJM+Fu7JDzaiIlfHiugjkATaA4O0MuuYYt7RV04b72chZA==`, - shasum `44964fcd9f60fd18fb9545ec0c3e851b7b0f6b2c`, sha256 - `50c0e65198aa1b22d5905c25cfdb1e65ff925c25a63738554878ae0cb352b4d2`. README/INSTALL/ - opendesign.md/execution.md byte-identical to source; pins at beta.6. Pack record - /tmp/opencode/ksi-beta6-pack.json. - -## Delivery evidence - -- Push: release content `7db2c46` + preparation record `a600480`; GitHub main - advanced `c35a57a..a600480` and the verified remote HEAD matches local. No force - push; four unrelated worktrees untouched. -- Registry: login completed via standard browser approval (identity `ksi-corp`); - `0.5.0-beta.6` published with tag `next` (exact reviewed tarball), then promoted to - `latest` via the same standard approval path. Public state: `latest` and `next` - both `0.5.0-beta.6`; all previous versions retained; published integrity - `sha512-+IRDbChj…t7RV04b72chZA==` and shasum `44964fcd…` match the reviewed tarball. - No republish, no auth bypass, no credential collection. -- Fresh public consumer checks at beta.6: exact-version preview wrote nothing → - apply created exactly the three role files → repeat returned `changes: []`; - unversioned default install followed the same flow and wrote only `agents/`. - Logs under /tmp/opencode/ksi-beta6-consumer.* and ksi-beta6-default.* dirs. -- Note: initial `npm exec` verification attempts returned 127 because the local - packument cache predated the publish; `npm view --prefer-online` revalidation - resolved it (a local cache effect, not a package defect — plain install and the - revalidated exec both succeed). One long verification command was interrupted - mid-run earlier; it was re-run bounded (`timeout`) and completed. -- This release carries documentation, guidance and installer content only; it is not - evidence of live product design quality or user design acceptance. diff --git a/docs/superpowers/plans/2026-10-05-product-aware-design.md b/docs/superpowers/plans/2026-10-05-product-aware-design.md deleted file mode 100644 index 9e94d7e..0000000 --- a/docs/superpowers/plans/2026-10-05-product-aware-design.md +++ /dev/null @@ -1,102 +0,0 @@ -# Product-aware design workflow — workstream ledger - -Updated: 2026-10-05 -Status: technically complete locally; live product/design acceptance pending -Owner: OpenCode Build - -## Approved outcome - -User approved the preceding bounded proposal with “그렇게 개선해줘”: use Local -Codex as a design specialist working from actual product context, not an isolated -mockup generator. Strengthen the existing context/brief → UI artifact → faithful -product integration → product verification workflow. Direct product-source editing -is a capability-dependent, separately scoped option, not assumed or enabled here. - -## Scope and workspace - -- Existing global AGENTS.md plus shipped integration/execution guides and optional examples. -- Retain native Build/Plan/Explore prompts, model/permission/custom-role/plugin settings. -- No EVENTOUCH frontend edit, design generation, mount change, installation or deployment. -- Owned workspace `.worktrees/product-aware-design`, branch `fix/product-aware-design-20261005`. -- Base/integration target: local main `de1924f`. -- Global policy backup: `product-aware-design-20261005.d2mk9el0`; six config/role SHA256 baselines. - -## Checklist - -- [x] Confirm approved scope, inspect current guidance and actual prior project metadata. -- [x] Create owned workspace; baseline check: 112 tests / 111 pass / 0 fail / 1 opt-in skip. -- [x] Define product-context inputs, supported output contract and faithful integration. -- [x] Tighten approved-pattern exception and completion evidence without new approval gates. -- [x] Independent scenario review and source/package/preservation checks. -- [x] Local commit/integration, integrated checks and safe owned-workspace cleanup. - -## Verification limits - -Prose-only guidance receives scenario interpretation review, not exact-wording -tests. Existing installer/package tests check regressions and delivery contents, -not design quality or live agent compliance. No provider-backed design/probe is -authorized by this harness-workflow task. Required live product/design acceptance -remains pending until a separately authorized product task uses the flow. - -## Relevant evidence - -- Previous read-only EVENTOUCH metadata: design entry `eventouch-operations.html` - in managed design storage `/app/.od/projects/eventouch-a82e`; metadata did not - establish product source linkage or framework execution. Not re-observed here. -- Existing integration guide already separates design authority from product - integration and forbids concurrent writers; this change fills context/output - and route-selection gaps rather than adding another agent or orchestrator. -- Actual global AGENTS.md receives four focused operating rules. The canonical - integration guide contains minimum-context fields, a reusable compact brief, - supported output/fallback rules, faithful adaptation and scoped feedback, - source-direct capability prerequisites and completion evidence. Shipped examples - and execution/architecture docs point to that flow; no helper/API/schema added. -- Independent reviewer `ses_ef5f63181ffecqEVTT5jXGSiBz` reviewed the six scoped - scenarios: existing pattern versus new hierarchy, managed HTML versus source - access, HTML-only adaptation, routine versus material integration differences, - missing-design completion and source-direct ownership. No Medium/High/Critical - findings; one nonblocking phrasing variation, same meaning. Interpretation only, - not a model-behavior or visual-quality claim. -- Independent test-runner `ses_ef5f57912ffefy0eGr0HUfRuJT` at base `de1924f` plus - pending docs: `npm run check` exit 0 (112 tests / 111 pass / 0 fail / 1 opt-in skip), - `npm run check:package` exit 0 (offline installer/package verification), - `git diff --check` exit 0. Six config/custom-role hashes match; actual global - AGENTS.md is intentionally changed and separately backed up/reviewed. - Logs: `/tmp/opencode/ksi-product-aware-design-{check,package,diff}.log`. -- Initial background reviewer/test-runner were cancelled without findings or - verification; bounded foreground replacements supplied the evidence above. - No result from cancelled helpers is treated as verification. -- Implementation `39835f1d280b1b3a00697c9745f9b352c6e38fdf` fast-forwarded into - canonical main. Same independent test-runner (rebound to main) ran fresh - `npm run check` exit 0 (112/111/0/1), `npm run check:package` exit 0, - `git diff --check` exit 0; six config/role hashes still match. - Logs: `/tmp/opencode/ksi-product-aware-design-merged-{check,package,diff}.log`. -- Session returned to canonical main; owned task workspace inspected including - ignored files. Only unique ignored checkpoint copied to the private backup, - then removed; non-forced worktree removal and merged-branch deletion succeeded. - Four unrelated pre-existing worktrees retained. -- Global AGENTS.md native instruction update observed in this session. Installer - remains opt-in for policy; shipped examples do not automatically apply it. - No design generation, actual product component/capture transfer, framework - capability test, mount/security change, product write, service restart, push or - publication occurred. This is a completed operating-policy/handoff improvement, - not a claim of an enforced runtime or better visual results already measured. -- Final evidence-only ledger/product-state delta independently checked by the - same test-runner: cached diff exit 0, both records excluded from package.files, - no code/packaged inputs changed and six preservation hashes match. Log: - `/tmp/opencode/ksi-product-aware-design-closeout-diff.log`. - -## Next acceptance candidates (not execution authorization) - -User-requested final closeout recheck: independent test-runner -`ses_ef527c73bffefHke78G4z40Hpi` at main `f482f5d` found clean tracked state; -`npm run check` exit 0 (112/111/0/1), `npm run check:package` exit 0 and -`git diff --check` exit 0. Six config/role hashes match; actual global rules -present, owned workspace absent and four unrelated worktrees preserved. Logs: -`/tmp/opencode/ksi-product-aware-design-closeout-final-{check,package,diff}.log`. -This added record is unshipped evidence only; no operating or packaged inputs changed. - -- m04: apply this flow to one authorized existing frontend improvement, verifying - actual product inputs, supported output and design-to-product fidelity. -- Source-direct UI capability: only investigate in a separately authorized scoped - isolated target if needed; not prerequisite to the default artifact/integration path. diff --git a/docs/superpowers/plans/2026-10-05-review-once-flow.md b/docs/superpowers/plans/2026-10-05-review-once-flow.md deleted file mode 100644 index 73138fe..0000000 --- a/docs/superpowers/plans/2026-10-05-review-once-flow.md +++ /dev/null @@ -1,67 +0,0 @@ -# Review-once flow — less ceremony for internal design passes - -Updated: 2026-10-06 -Status: technically complete locally; human real-use acceptance separate -Owner: OpenCode Build - -## Approved outcome - -User approved ("그렇게 해보자") three relaxations of the design flow, aimed at -less round-trip ceremony without weakening real boundaries: - -1. Review-once rule: for internal tools that keep the existing brand, a - visual-system pass no longer waits for a separate plan approval — it goes to - one representative screen (verified build/tests/render) and presents the - definition, scope and real result as a single review; pre-approval remains for - a new visual direction, a client-facing deliverable, an out-of-scope change or - an explicit review-first request. -2. Decision batching: unavoidable decision batches come once with a recommended - default per item, so a single approval settles the batch. -3. Rollout autonomy: after the single review, screens roll out with per-stage - checks and without per-screen user approvals, reported once at completion. - -Deferred by agreement: a wider "guidance diet" (④) waits until after the first -real pass shows which rules actually chafe. - -## Scope and workspace - -- Global AGENTS.md + docs/integrations/opendesign.md + both examples. -- No product/mount/container/security change; no design generation; no push/publication. -- Owned workspace `.worktrees/review-once-flow`, branch - `fix/review-once-flow-20261005`; base/target main `056b29c`. -- Global policy backup `review-once-flow-20261005.hrsvt822`; six config/role - SHA256 baselines. - -## Checklist - -- [x] Confirm scope; create owned workspace. -- [x] Apply the three approved edits (global ×2, guide ×1, examples ×2). -- [x] Baseline/final checks + independent review. -- [x] Commit/integration, integrated check, cleanup and checkpoint. - -## Verification - -- Reviewer `ses_ef385cc9cffeD4ZuUSaXx6IniU` (read-only interpretation): all six - checks passed — the three relaxations are implemented; the four gates, - one-writer, functionality/routing/data freeze and staged per-stage checks stay - intact; three Low/Info wording findings (example qualifier, rollout - explicitness, batching sentence in the autonomy example) fixed by aligning both - examples with the guide. -- Test-runner `ses_ef3841fe8ffe1Wn822liQtn2bI`: `npm run check` exit 0 - (112/111/0/1), `npm run check:package` exit 0, `git diff --check` exit 0; six - preserved config/role hashes match; global AGENTS.md changed by exactly the two - intended bullet edits; repo paths confined to docs/examples plus the new - ledger. Logs: `/tmp/opencode/ksi-review-once-{check,package,diff}.log`. -- Closeout reconciled 2026-10-06: Git contains `ea8b514` on main (implementation - commit). The previous independent test-runner session above also ran fresh - integrated checks at that revision: 112 tests/111 pass/0 fail/1 optional skip, - package/diff exit 0 and six config/role hashes matched; reported logs - `/tmp/opencode/ksi-review-once-merged-{check,package,diff}.log`. Owned worktree - and merged branch were removed; four unrelated worktrees remain. This fixes a - stale in-progress/checklist record, not a new verification or human acceptance. - -## Verification limits - -Prose guidance: interpretation review, not runtime enforcement. The relaxed flow -still requires the representative screen's real verification evidence; the first -real pass (EVENTOUCH) is the live test. No product quality or acceptance claim. diff --git a/docs/superpowers/plans/2026-10-05-source-direct-feasibility.md b/docs/superpowers/plans/2026-10-05-source-direct-feasibility.md deleted file mode 100644 index b2bfde5..0000000 --- a/docs/superpowers/plans/2026-10-05-source-direct-feasibility.md +++ /dev/null @@ -1,148 +0,0 @@ -# Source-direct frontend feasibility — technical spike - -Updated: 2026-10-05 -Status: feasibility spike complete locally; product activation not performed - -## Approved question and boundaries - -User: research existing cases before proceeding, and try if feasible. Determine -whether Local Codex can edit actual frontend files rather than requiring a separate -mockup and reimplementation. This is a research/throwaway capability spike, not -authorization to change another product, mounts/security, dependencies, deployment -or the global default workflow. No main-product writer is enabled by this record. - -## Checklist - -- [x] Explore current integration and deployment without changing it. -- [x] State question/probe; user authorized research and feasible trial. -- [x] Research primary sources and comparable workflows. -- [x] Run a minimal synthetic source-edit/build probe within an already connected fixture root. -- [x] Verify actual host bytes, rendered React interaction and preserved prior files independently. -- [x] Record source-edit capability separately from product runtime/visual acceptance and recommended next scope. - -## Observed sources - -- Upstream folder-import PR #624, merged 2026-05-07: - https://github.com/nexu-io/open-design/pull/624 — explicitly no copy/shadow tree, - metadata.baseDir determines file APIs and agent working directory; security - fixes are documented in review. This is implementation evidence, not a guarantee - for every product framework or installed version. -- https://github.com/nexu-io/open-design/blob/main/README.md — existing-repo refresh - advertised, but migration plugins also appear in roadmap as alpha/planned. -- https://github.com/nexu-io/open-design/blob/main/plugins/_official/scenarios/od-code-migration/open-design.json - and atoms/patch-edit/SKILL.md — pipeline/patch/build-test definitions exist; - not treated as an already exercised end-to-end migration. -- https://github.com/nexu-io/open-design/blob/main/docs/rfc-drafts/dev-server-auto-detect.md - remains Draft: native automatic framework dev-server preview is not proven. -- https://kombai.com/features/code/ — vendor documents repo/component reuse, - direct coding, browser verification and agent delegation; not personally tested. -- https://github.com/onlook-dev/onlook — code container + source indexing + visual - editing/agent writes, Next.js/Tailwind-focused; not installed/tested here. -- Context7 /nexu-io/open-design corroborated folder import and migration definitions; - installed runtime evidence takes precedence over unversioned documentation. - -## Current environment and probe design - -- Harness main/base 9086e72, clean before evidence notes; unrelated worktrees preserved. -- Current design runtime version0.24.1/base89e64d8; reviewed image SHA256a9b79052…; - actual five approved Compose inputs, capDropALL/CapEff0/NoNewPrivs1/Seccomp2, - enforced ksi-codex-userns; no recreation/security change. -- EVENTOUCH remains managed artifact storage, not a source-linked product workspace. -- Two old fixture roots and KSI-CCTV already mounted; no new root connection needed. -- Synthetic React component under a uniquely task-owned subfolder in fixture `two`; - original witness preserved. Reuse already-installed React19.2.4/react-dom19.2.4/ - scheduler0.27.0 read-only and runtime esbuild0.25.12, no installs or product edits. -- Exact frozen small copy/target-size change, not a new visual direction. Local Codex - writes only the synthetic component/styles and fixture build outputs; no API/auth/ - route/private-data/Git/deployment changes. Verify working directory, source hashes, - actual bundle/render/button behavior. No mockup reconstruction by Build. -- Existing root bindings are broader than the task-owned child directory; prompt - scope is not an OS allowlist. Preserve prior-file hashes and disclose that limit. - -## Results - -- Task-owned synthetic repo baseline9f69495 under fixture two/source-direct-20261005-a7c2; - build from outer container passed, no installations. Baseline browser exercised - the actual React state0→1, button label Increase count, CSS min-height32px (actual - rendered height44px due padding), no wide viewport overflow. This is a functionality - probe, not a visual-quality comparison. -- New task-owned project baacbc86-0f65-4568-9654-94e810ab3ae3 imports the existing - exact connected root. MCP get_project confirmed resolvedDir matches host root; - prior projects untouched. Canonical entry set only on the new probe project. -- Requeste5850fe5-05d3-4d9c-a217-d2b82071ac96/runf61361e0-6e06-40a0-8a2b-2521f43d7f07: - Local Codex requested exactly two source edits plus existing build and receipts, - single writer in task child. No skill/plugin explicitly requested, but daemon - automatically applied example-web-prototype snapshot8e38ba99…; this is a possible - source-direct routing mismatch, not evidence of a code-edit workflow being enabled. -- Run physically succeeded, exit0, declared deliverable valid, syntax check passed. - Actual workspace reports folder-backed/user-local/writeback=in-place. Local Codex - changed the two existing host source files, ran the trusted build and wrote a - technical receipt; Build did not reproduce/transfer the UI. About89s for this - tiny test is one observation, not a latency/cost estimate for real design work. -- Primary inspected exact Git diff, receipt, preserved originals, actual browser - label/count behavior, 390px computed CSS and screenshot. Not a visual-quality - evaluation: the frozen label/min-height change needs no new design direction. -- Independent test-runner `ses_ef50782acffekyjjxI5qXfr4v0` verified fixture baseline - remains9f69495 with exactly App.tsx/style tracked changes; witness, build and - package preserved. Fresh trusted build exit0, deterministic public outputs; - all three HTTP responses200 and byte-identical to host. Browser mouse count0→1, - keyboard Enter1→2, min-height44px, no390px overflow; source change survives reload - while local count intentionally resets0. Page errors/console empty; relevant - document/JS/CSS requests200 (reload cache304). Same browser session handed over, - no duplicate driver; Primary closed only the owned session afterward. -- Independent preservation: six config/role hashes match, global policy unchanged, - same reviewed image/security retained. Harness diff check exit0; no source/runtime/ - package/config changes, so unchanged harness suite/package checks were not rerun. -- Receipt caveat: agent-reported input CSS hash is NOT the actual baseline-file - hash. Read-only diagnosis matched it exactly to baseline bytes with final newline - removed; raw baseline snapshot and Git baseline match. Output hashes and final - diff are correct. The receipt is retained as produced, not silently fixed or - trusted as raw-byte provenance. Independent raw-byte evidence is authoritative. - This limits receipt reliability, not demonstrated source-edit/build behavior. -- Runtime staged `.od-skills/web-prototype-0bfaa88545/` at the imported root. - Original witness preserved; the broader cwd sandbox includes the parent fixture - root, not a file-extension/child-directory hard allowlist. Do not generalize this - probe's prompt-scoped ownership into a security guarantee. -- Evidence: `/tmp/opencode/ksi-source-direct-final-evidence.json` and matching - `-390.png`/`-http-*` files; baseline `/tmp/opencode/ksi-source-direct-baseline.json`, - compact run summary `/tmp/opencode/ksi-source-direct-run.json`. Required public - hashes: index.html c41498bb6afe237952ee8bb8475792e612dffeac7a5b1a9d7e7d4619b201d9d4; - main.js 5336e6091d195c4ddded4edb0aa12eb8f9253daefff210a55c08558e7036f2f3; - main.css a42df5d82833f5ac3c302b45793759e0d36a61877df1a15a465d3f1cd111f414. -- Probe project and uniquely owned synthetic child retained, clearly named as a - capability fixture. No active browser/server created by this task remains. - Do not delete the imported project/root as generic cleanup: it shares an old - fixture root with a witness and other registrations. Four unrelated worktrees - and all prior projects preserved; no installs/restart/mount/security/config/ - real-product changes, external publication/deployment or goal operations. -- Same independent test-runner checked the final two-record staged delta: cached - diff exit0, no packaged/runtime/config changes, source/preview/security limits - not overstated, and newline-stripped input-CSS hash comparison reproduced exactly. - `/tmp/opencode/ksi-source-direct-closeout-evidence.log`. Browser not reopened. - -## Recommendation and unverified scope - -User approved the simplified flow the same day (direct repo edits, worktree optional, -one writer) and the guidance was updated in the follow-up record -`docs/superpowers/plans/2026-10-05-source-direct-flow.md`. - -Direct source editing is feasible in the inspected environment; a separate mockup -and manual reimplementation are not intrinsic requirements. Prefer per-target -isolated source-direct UI ownership when real source access, trusted build/runtime, -privacy and delivery verification are established. Build need not redesign UI; it -coordinates functional contracts and verifies/integrates the resulting product diff. - -The tested fixture is a static esbuild bundle from actual React TSX, not a Next/Vite -development server, SSR/HMR, product auth/API or native visual collaboration. The -runtime selected a prototype plugin and used a touched canonical HTML entry; -source-only edits with unchanged entry still need correct run-output verification. -Latest docs/RFC are not proof those missing capabilities shipped in this installed -runtime. EVENTOUCH remains a separate managed artifact with no source linkage. - -Next candidate (requires its own product/infrastructure scope): one authorized -product-screen pilot in an isolated task workspace. Verify actual files, existing -dependencies/commands, runtime accessibility and source-compatible output before -any new root binding; a new connection may recreate the shared container and needs -active-run protection and scoped approval. Do not switch global routing or connect -main/home/credentials based solely on this synthetic result. A new frontend-preview -adapter or file-level policy would be implementation work, not part of this spike. diff --git a/docs/superpowers/plans/2026-10-05-source-direct-flow.md b/docs/superpowers/plans/2026-10-05-source-direct-flow.md deleted file mode 100644 index db79051..0000000 --- a/docs/superpowers/plans/2026-10-05-source-direct-flow.md +++ /dev/null @@ -1,108 +0,0 @@ -# Source-direct frontend flow — simplified operating path - -Updated: 2026-10-05 -Status: in progress -Owner: OpenCode Build - -## Approved outcome - -User approved ("그래 그렇다면 그렇게 개선해줘 나는 편한게 좋아") simplifying the -verified source-direct frontend flow: Local Codex edits the actual product -repository directly — dedicated branch or the user's normal checkout; a worktree -is optional convenience, not a requirement — with one writer at a time. Build owns -requirements/contracts, real verification, integration and Git. Real boundaries -stay: home/credential roots and unrelated projects out of agent reach; new mounts -and security changes keep their own scoped approval; push/deploy and API/data/auth/ -dependency changes are never delegated implicitly. - -Foundation: docs/superpowers/plans/2026-10-05-source-direct-feasibility.md -(independently verified synthetic source-edit/build/browser probe). - -## Scope and workspace - -- Global AGENTS.md operating rules, plus the shipped integration/execution/ - architecture docs and examples. One new ledger, a product-state entry and a - forward link from the spike ledger. -- No product/mount/container/security change; no design generation; no push or - npm publication. -- Owned workspace `.worktrees/source-direct-flow`, branch - `fix/source-direct-flow-20261005`; base and local integration target main `0b159c9`. -- Global policy backup `source-direct-flow-20261005.km3540i7`; six config/custom-role - SHA256 baselines. - -## Checklist - -- [x] Confirm scope; create owned workspace; baseline check (112 tests / 111 pass / - 0 fail / 1 opt-in skip). -- [x] Update global AGENTS.md source-direct rules (real repo preferred, worktree - optional, boundaries, completion evidence) — three surgical edits. -- [x] Rewrite the integration guide's direct-source section; align boundaries, - execution, architecture and examples. -- [x] Record product-state entry and spike-ledger forward link. -- [x] Independent review and source/package/preservation checks. -- [x] Local commit/integration, integrated check, owned-workspace cleanup and - checkpoint. - -## Verification - -- Reviewer `ses_ef4f85384ffezpE0AD1xVBDRcG` (read-only interpretation): six scenario - checks passed; one Medium (record base/rollback before substantive writes — - applied without re-imposing a main prohibition the user rejected) and four Low - findings (gateway enforcement wording, `entry_not_touched` provenance, example - boundary list, concurrent-writer phrasing). All five repaired and re-verified as - resolved; no new contradictions. -- Test-runner `ses_ef4f6a153ffePJIO2ciymIangg`: `npm run check` exit 0 (112 tests / - 111 pass / 0 fail / 1 opt-in skip), `npm run check:package` exit 0, `git diff - --check` exit 0; six preserved config/role hashes match; global AGENTS.md has - exactly the three intentional bullets and no other drift; all repo changes - confined to docs/examples. Logs: - `/tmp/opencode/ksi-source-direct-flow-{check,package,diff}.log`. -- Implementation `4c702399bf00bd4e071f6d1f90b8237c10d1c7b1` fast-forwarded into - canonical main; the same test-runner (rebound to main) re-ran fresh checks: - check 112/111/0/1, package pass, diff exit 0, six hashes match, committed change - set docs/examples only, global bullets present. Logs: - `/tmp/opencode/ksi-source-direct-flow-merged-{check,package,diff}.log`. -- Session returned to canonical main; owned workspace inspected including ignored - files, only the unique checkpoint copied to the private backup, then non-forced - worktree removal and merged-branch deletion succeeded. Four unrelated pre-existing - worktrees retained. -- This completes operating-policy and documentation alignment. It does not connect - a real product repository, run a generation, or change mounts/container/security; - real-product source-direct use remains a separate scoped step. (Follow-up the same - day: EVENTOUCH was connected and imported as source project `EVENTOUCH source` - `a819d3a4-a7b9-4254-93af-d5267b5e28d1`; see the canonical checkpoint.) -- User-requested session closeout recheck: independent test-runner - `ses_ef4e8824cffeXWcpOWvDgvo99E` at main `7621b07` verified a clean tracked tree, - `npm run check` exit 0 (112/111/0/1), `npm run check:package` exit 0, - `git diff --check` exit 0, exactly the three global bullets with no other policy - drift, six preserved config/role hashes matching, both EVENTOUCH projects present, - container healthy with the approved image/security/mount, only pre-existing - worktrees, and 23 local commits ahead of origin with nothing pushed. Logs: - `/tmp/opencode/ksi-session-closeout-{check,package,diff}.log`. This added record is - unshipped evidence only. - -## Applied changes - -- Global AGENTS.md: product-context bullet prefers the real repository over a source - copy; the integration bullet now states the verified preferred path (one writer, - worktree optional, retained boundaries, source-diff + build/render completion - evidence, `entry_not_touched` clarification); the Korean handoff bullet points at - the real repository/branch with a single writer. -- Integration guide: the direct-source section becomes "verified, simplified preferred - path"; the product-aware flow intro prefers direct access; the boundaries bullet - prefers direct edits with verified access (single writer) and keeps fallback/limits. -- Execution, architecture and both examples carry the same simplified rule. -- No product/mount/container/security/config/model/permission change; no generation. - -## Verification limits - -Prose-only guidance receives interpretation review, not exact-wording tests. This -task does not connect a real product or run another generation. The completion -rule ("an intentionally untouched canonical entry may report `entry_not_touched`; -that verdict alone does not invalidate the source-direct result") is inferred from -the entry-centric validator design: the spike's probe run touched its canonical -entry, so the `entry_not_touched` outcome was not itself exercised and must be -re-confirmed per target. Connecting -a real product repository remains a separate scoped step; a new root connection can -recreate the shared container and needs its own approval, active-run protection and -rollback scope. diff --git a/docs/superpowers/plans/2026-10-05-system-pass-guidance.md b/docs/superpowers/plans/2026-10-05-system-pass-guidance.md deleted file mode 100644 index 4eb0bd1..0000000 --- a/docs/superpowers/plans/2026-10-05-system-pass-guidance.md +++ /dev/null @@ -1,66 +0,0 @@ -# System-pass guidance — multi-screen visual-system recovery - -Updated: 2026-10-05 -Status: in progress -Owner: OpenCode Build - -## Approved outcome - -User approved ("진행해줘 그렇게 반영해줘") three targeted guidance improvements -following the EVENTOUCH root-cause review (patch-built screens, exception-heavy -theme, mockup/product divergence): - -1. A system-pass priority rule: accumulated visual drift prefers audit → - token/component scales → direction approval → one representative screen in the - real source → staged rollout, not more per-screen patches or a big-bang rewrite. -2. A screen/file map requirement in the minimum product context for multi-screen work. -3. The same one-line principle in both project-guidance examples. - -Deliberately not added now: further rules before the first real large-scale source -pass is observed; the beta.6 npm docs terminology lag stays for a possible future -release. - -## Scope and workspace - -- Global AGENTS.md + docs/integrations/opendesign.md + both examples. -- No product/mount/container/security change; no design generation; no push/publication. -- Owned workspace `.worktrees/system-pass-guidance`, branch - `fix/system-pass-guidance-20261005`; base/target main `65d9e74`. -- Global policy backup `system-pass-guidance-20261005.qiq3tohc`; six config/role - SHA256 baselines. - -## Checklist - -- [x] Confirm scope; create owned workspace; baseline check (112/111/0/1). -- [x] Apply the three approved edits. -- [x] Independent review and source/package/preservation checks. -- [x] Commit/integration, integrated check, cleanup and checkpoint. - -## Verification - -- Reviewer `ses_ef3b834f5ffebmaJeSl2OwSUaA` (read-only interpretation): six checks - passed — drift routes to the system pass; no big-bang and staged per-stage - checks; direction approval binds the system definition without weakening - existing gates; functionality/routing/data freeze and one-writer preserved; - screen/file map consistent with minimum-context privacy; global/guide/examples - agree. One Low wording finding (per-stage checks missing in one example) fixed. -- Test-runner `ses_ef3b722efffeGHRWHtJQShmCJv`: `npm run check` exit 0 (112 tests / - 111 pass / 0 fail / 1 opt-in skip), `npm run check:package` exit 0, `git diff - --check` exit 0; six preserved config/role hashes match; global AGENTS.md differs - by exactly the one inserted bullet; repo changes confined to docs/examples plus - the ledger. Logs: `/tmp/opencode/ksi-system-pass-{check,package,diff}.log`. -- Implementation `d4b084fa8f45c4fada74dd13c7154e9fa0e92f56` fast-forwarded into - canonical main; the same test-runner (rebound to main) re-ran fresh checks: - check 112/111/0/1, package pass, diff exit 0, six hashes match, committed change - set docs/examples only, global diff exactly the one bullet. Logs: - `/tmp/opencode/ksi-system-pass-merged-{check,package,diff}.log`. -- Owned workspace inspected including ignored files; the unique checkpoint was - copied to the private backup before non-forced removal; merged branch deleted. - Four unrelated worktrees retained. Change is local only — not pushed to GitHub, - no npm release; the global rule is live immediately. - -## Verification limits - -Prose guidance: interpretation review, not runtime enforcement. The rule's -effectiveness depends on the first real multi-screen pass in EVENTOUCH; that -observation remains pending. No product design quality or acceptance claim. diff --git a/docs/superpowers/plans/2026-10-06-design-quality-loop.md b/docs/superpowers/plans/2026-10-06-design-quality-loop.md deleted file mode 100644 index 1e40937..0000000 --- a/docs/superpowers/plans/2026-10-06-design-quality-loop.md +++ /dev/null @@ -1,142 +0,0 @@ -# Design-quality loop — designer-like OpenDesign usage - -Updated: 2026-10-06 -Status: in progress -Owner: OpenCode Build - -## Latest disposition (2026-10-06) - -User rejected the bold pilot result ("디자인 … 문제 많은데요"). This is not -representative-screen approval, and no wider rollout is authorized. Source writing -and rendering worked; the recorded runs do not demonstrate an autonomous, -criteria-linked design review/refinement loop. The later approved contract routes -all visual analysis/UI edits/rendered review/refinement to the design workspace, -with OpenCode coordinating and checking functional/evidence obligations. See -`2026-10-06-visual-ownership.md`; product refinement stays in the product session. - -## Approved outcome (user 2026-10-06) - -"나머지 모두 개선해줘" — implement the design-pipeline improvements discussed -after the user reported that direct OpenDesign use produces much better quality -than the OpenCode-driven one-shot flow: - -1. Skill chain: each design run selects the task-appropriate skill from the - actually installed OpenDesign skill list (generation → polish → review). -2. Evaluation: OpenCode evaluates the rendered result itself (screenshots, - references, brand/quality bar) and produces a concrete defect list. -3. Refinement loop: concrete refinement runs until the bar is met — **no fixed - round cap** (user explicitly rejected an arbitrary cap); stop when met or a - full round shows no measurable improvement, then report remaining gaps. -4. Engine: keep `codex` temporarily (no Claude subscription yet); revisit later. - -## Scope and workspace - -- Global AGENTS.md (design bullet + Korean design section + retry wording), - docs/integrations/opendesign.md, examples ×2, README.md, notifier message - source + tests, and the previously installed pinned notifier runtime copy - (local update, verified; no service restart). -- No product repositories, design generation, push/npm, dependencies, mounts, - credentials, model/permission changes. One writer, current checkout, main, - base 5d763f4. -- Global policy backup `design-loop-20261006.ou3d1yte`; six config/role hashes. -- Previously installed runtime: version 0.2.0, commit 61984160… - -## Checklist - -- [x] Notifier message + tests (RED→GREEN). -- [x] Global/guide/examples/README guidance. -- [x] Full checks + independent review/test-runner. -- [x] Commit; pinned notifier runtime update + verify; closeout. - -## Closeout - -- Implementation committed `aefd698`; pinned notifier runtime updated to the same - commit and `--verify` returned `ok:true` (7 files; changed/missing/extra empty). - No service restart; the running service may keep serving the old copy until - plugins reload. No push/npm. - -## Evidence - -- RED/GREEN logs: `/tmp/opencode/ksi-design-loop-notify-{red,green}.log`. -- Full pre-review: check exit0 (129/128 pass/0 fail/1 optional skip), package exit0, - diff exit0 — `/tmp/opencode/ksi-design-loop-{check,package}.log`. -- Skill names cited in the guide were verified against the daemon's actual - `list_skills` output on 2026-10-06 (163 skills); the guide instructs adapting - from the live list if names change. -- Reviewer `ses_ef30b88a3ffeabYpsOqPCQIhMK` (read-only): no Critical/Important; - four relaxations consistent across EN/KO, retry-vs-refinement contradiction - resolved, gates/one-writer untouched, notifier stays advisory, six hashes - match, runtime still old. Minor fixes: "measurable" defined in the guide - (done); engine pin kept in global policy only, not in the shipped generic - guide (ruling: environment-specific value belongs in global policy/ledger). -- Test-runner `ses_ef30a9d57ffe39gWzcqWm7P6OM`: check exit0 (129/128/0/1), - package exit0, diff exit0, targeted notify 10/10; six hashes match; global - diff = exactly the four intended semantic edits; installed runtime confirmed - old (`0.2.0`, commit 61984160…). Logs - `/tmp/opencode/ksi-design-loop-final-{check,package,diff,notify}.log`. - -## First real pilot — agency-home (2026-10-06) - -- Pipeline: od-next strategy v2.0.4 (plan → execute; runs `cafd8f8a` → `549c4884`), - agent codex, skill `frontend-design`. Plan stage produced no files; execute - stage produced the real source edits. -- Result: `features/agency-ui.js` + `styles/theme.css` (+201/−19) — stats as one - value-first summary band with dividers, section heads, divider-based dense - lists (card chrome removed), heading rule, button hover/active/touch states, - 900/600px responsive incl. 2×2 mobile stats. Commit `5f7b92f` on - feat/eventouch-visual-system; data/routing/copy/brand tokens preserved. -- Verification: rendered at 1440×900 and 390×844 (before/after captures at - `/tmp/opencode/evdesign-{before-desktop,before-mobile,agency-desktop,agency-mobile}.png`); - console clean except the static-server `/api/health` 404 (absent server API, - not a regression); the 준비 view still renders. -- Interpretation: `no_artifact` + strategy outcome "blocked - (od_next_canonical_deliverable_invalid)" are expected here — the canonical - HTML entry is intentionally untouched for source-direct edits; evidence is the - actual source diff plus the render check. -- Status: representative-screen review pending with the user (single review per - the review-once flow). Rollout to remaining screens only after approval. -- Leftover: pipeline materials `.od-frames/` (untracked) remain in the repo; - clean up on request. - -## Pilot 2 — bold editorial direction (2026-10-06, user: "더 과감 혹은 새로") - -- Runs `04ed48ea` (plan) → `40a41c17` (execute), skill `frontend-design`. -- Result: warm full-width hero band (`--action` fill, white type, h1 clamp - 34–48px), the live KPI moved out of the summary band into the hero (mono, - clamp 48–68px, tabular numerals), 3px brand rules above section headings, - stats band 4→3 columns, inverted primary button inside the band. Copy, data - and behavior unchanged; mobile stacks the band. -- Mid-run: a full-block CSS patch failed on context; the agent re-read and - applied smaller patches (no partial corruption) — confirmed by the final diff. -- Verification: rendered at 1440×900 and 390×844; console clean except the - static `/api/health` 404; captures `evbold-after-{desktop,mobile}.png` vs the - previous `evdesign-agency-{desktop,mobile}.png`. -- Status: user rejected this result; neither direction is accepted by this record. Committed with - "style: bold editorial redesign of agency-home". - -## Harness learnings (pilot 1, 2026-10-06) - -- The notifier delivers only the stage bound to the session; od-next expands one - request into plan→execute runs, so the follow-up stage must be tracked through - the run's strategy mapping (a background watcher was used). **Fixed the same - day:** the notifier now words intermediate stages as non-final and watches the - mapped follow-up run for the same session until the chain ends (10-minute - visibility grace); commit `feat: follow strategy chains in design completion - notifications`; pinned runtime updated. -- Strategy outcome `blocked` (`od_next_canonical_deliverable_invalid`) and - `no_artifact` are expected for entry-untouched source-direct work; the source - diff + render check is the evidence (now documented in the guide). -- Skill catalogue vs installed bodies: `design-taste-frontend` / `gpt-taste` are - landing-page skills ("not dashboards"); the full-body `frontend-design` fits - product UI. Design-system-required skills ran fine with the repository tokens. -- Pipeline artifacts (`.od-frames/`) land untracked in the project; keep them - out of product commits (now documented in the guide). - -## Verification limits - -- Harness guidance/message/interpretation tests and actual source-write/render - observations are recorded above; they do not prove a design-quality pass. The - EVENTOUCH source project was re-registered (`aabf3621`), both pilots ran and the - bold result was rejected. No criteria-linked autonomous design-review/refinement - loop, product acceptance or deployment is claimed. Pipeline staging directories - noted as leftovers above were subsequently cleaned; those notes are historical. diff --git a/docs/superpowers/plans/2026-10-06-source-registration-flow.md b/docs/superpowers/plans/2026-10-06-source-registration-flow.md deleted file mode 100644 index 99506ee..0000000 --- a/docs/superpowers/plans/2026-10-06-source-registration-flow.md +++ /dev/null @@ -1,48 +0,0 @@ -# Source-project registration flow (deployment) - -Updated: 2026-10-06 -Status: technically complete; first real design loop separate - -## What happened (user-directed 2026-10-06: "전역 설정 하네스를 개선") - -- Goal: the writable design capability must be a **global, hands-off harness - capability**, not a series of per-project approvals. -- Checked the deployed daemon's registration rule: - `remoteWorkspaceRootBlockReason()` accepts a folder-backed project only for an - **exact connected path** (`status.path === canonicalPath && connected`), and - the 2026-09-30 deployment design excludes Desktop/home-wide mounts. -- Interim experiment: connected `~/Desktop` as a parent bind - (operation `5dd59db4-b16e-4372-9b71-c97e1e2e2d41` → ready) to test the - "one broad mount" idea. Verified the daemon still requires exact per-project - connections (children were not connected) and that the parent bind violated - the documented boundary → reverted through the standard compose-override - path. Backups: `connections.json.before-desktop-revert`, - `projects.compose.json.before-desktop-revert` (private backup dir). -- Registered `EVENTOUCH source` via `POST /api/import/folder` - (`baseDir=/home/ksi/Desktop/SI-Projects/EVENTOUCH`, name `EVENTOUCH source`) — - the missing piece behind the earlier "outside allowed write paths" failures. -- Documented the exact flow in the integration guide + global policy: - connect → import, agent-executed, standing scope = every project under home - except the home root and credential directories; one writer at a time. - -## Evidence - -- Connect op `5dd59db4` (Desktop, interim) → ready; revert verified: - `connections.json` paths = 4 project binds, compose exit 0, daemon healthy, - Desktop mount absent (`docker inspect open-design`). -- Import response: project `aabf3621-654f-45ba-b908-6daee94d95fa` - (update 2026-10-06: the product session later re-created EVENTOUCH as - `9db026a4-670d-4a16-bc9f-f79bf0942142` with the same baseDir; a stored id is - environment state — verify with `list_projects` before use. See the binding - review in `2026-10-06-visual-ownership.md`.) - (`EVENTOUCH source`), conversation `bb1132f8-…`, `entryFile=index.html`; - re-read via the design MCP confirms `baseDir`/`resolvedDir` = - `/home/ksi/Desktop/SI-Projects/EVENTOUCH`. -- No active design runs before/after the operations. - -## Limits - -- This covers reachability + project registration only. Real product writes and - design quality still need the first real design loop (agency-home) with the - design-quality-loop guidance. A new project path still costs one container - recreation on first connect (by design; automatic rollback). diff --git a/docs/superpowers/plans/2026-10-06-usability-alignment.md b/docs/superpowers/plans/2026-10-06-usability-alignment.md deleted file mode 100644 index 44ab35c..0000000 --- a/docs/superpowers/plans/2026-10-06-usability-alignment.md +++ /dev/null @@ -1,87 +0,0 @@ -# Usability alignment Implementation Plan - -Updated: 2026-10-06 -Status: technically complete locally; runtime adoption and human real-use acceptance separate - -> **For agentic workers:** Use executing-plans inline; one whole-change reviewer and independent final test-runner. Checklist below is the workstream ledger. - -**Goal:** Reduce repeated instructions, misleading completion reports and unnecessary workspace changes through the six improvements approved by the user (“개선해줘”). - -**Architecture:** Keep the native session/tool mechanisms and existing notifier. Separate execution/HTML verdict from product verification; extend read-only reporting with current Git/record consistency and local readiness. Reuse existing product-state/ledger for project bindings and native tools for pause/resume. - -**Tech Stack:** Node.js ESM, node:test, Git, Markdown guidance; no dependencies. - -**Spec:** Approved six-item proposal in this conversation, summarized in Scope below. - -## Global Constraints - -- No new orchestrator, automatic install, generation, credentials access, model/permission changes, product changes, push or publication. -- No blanket entry-verdict success exception. Source-direct output always requires actual diff/build/changed-flow verification by Build. -- Preserve native prompts and model selections. Existing installed notifier is not automatically replaced; runtime update is separately reported. -- One location, one writer: current clean main checkout; base/integration target `ea8b514`. No parallel source writers or isolation need, so no new branch/worktree. -- Global policy backup `usability-20261006.xkbczbo8`; six config/role hashes protected. - -## Scope - -1. Resolve current-checkout versus blanket-isolation contradiction in global and shipped guidance. -2. Notifier: unverified entry verdict is not product failure; retain daemon verdict and describe source verification and representative-review boundary. -3. Read-only report: show recorded work, record/Git inconsistencies, pending human/external decisions; unavailable sources must not look empty. Unlimited goals are informational, not an arbitrary failure warning. -4. Reuse a compact Product binding table in existing product-state/ledger; no user copy-paste between sessions required for common policy updates. -5. Explicit pause maps to goal + notifier switches in the calling session through native tools, never canceling a running design task implicitly. Resume preserves mode restrictions and never invents a goal. -6. Read-only local preparation inspection: role/policy/binding/runtime presence, no credentials/config values/network/command execution; file presence is not effective/live readiness. - -## Review Focus - -- Failed/canceled runs stay failures, even with an untouched entry. -- Unknown/invalid outputs must never be called successful product work or trigger regeneration automatically. -- Missing/malformed state or non-Git folders yield unknown/warnings, not fabricated completion. -- Stored binding commands/URLs are data only, never executed/fetched by readiness checks. -- Pauses/resumes remain explicit, session-scoped, native-mode-aware and distinct from external run cancellation. - -## Task 1: Notification semantics - -Files: `plugins/design-notifier/lib/messages.js`, `test/design-notifier-messages.test.mjs`, notifier README and integration guide. -Interface: keep `classifyRun()` daemon-artifact classifications for compatibility; `buildDelivery()` adds advisory product-verification semantics, no new states or retries. -- [x] Write tests: nonvalid successful run is verification-required, failed stays failed, representative review and pause checks included in receiving-agent contract. -- [x] Run `node --test test/design-notifier-messages.test.mjs` and observe intended failures. -- [x] Implement truthful notifications without changing transport, IDs or daemon classifications; rerun tests. - -## Task 2: Status reporting - -Files: new `scripts/work-status.mjs`, `scripts/work-report.mjs`, `test/work-report.test.mjs`, new `test/work-status.test.mjs`. -Interface: `inspectWorkStatus(repo)` returns recorded status, branch/HEAD, ledger reference, consistency warnings and next actions; `work-report --status` skips usage/goal state reads and prints compact local status. -- [x] Write tests for stale HEAD/unfinished ledger, missing/non-Git input, no invented acceptance and unlimited informational limits. -- [x] Run `node --test test/work-report.test.mjs test/work-status.test.mjs` RED. -- [x] Implement read-only status and unavailable-source warnings; run targeted checks GREEN. - -## Task 3: Local preparation and reusable binding - -Files: new `scripts/work-doctor.mjs`, new `test/work-doctor.test.mjs`, execution guide, README. -Interface: `inspectReadiness({repo,configDir,frontend})` returns local check states and needs-setup/unverified summary; source-only CLI `node scripts/work-doctor.mjs --repo PATH [--config PATH] [--frontend] [--json]`. No network or config contents. -- [x] Write fixture tests for absent roles/policy, complete local setup still not live-verified, malformed/mismatched binding, read-only behavior and CLI flags. -- [x] Run `node --test test/work-doctor.test.mjs` RED. -- [x] Implement narrow local checks; document Product binding reuse and native pause/resume mapping. - -## Task 4: Guidance, records and closeout - -Files: global `AGENTS.md`, examples, execution/integration guides, README, current product-state/checkpoint, previous review-once ledger. -- [x] Resolve isolation wording; add compact reusable-binding and native pause/resume guidance. Do not modify upstream skills. -- [x] Reconcile prior review-once integration/verification evidence and current active work; retain dated history as superseded, not current policy. -- [x] Whole-change reviewer; repair actionable findings with RED→GREEN where code changes. -- [x] Independent test-runner: `npm run check`, `npm run check:package`, `git diff --check`, controlled CLI fixtures, six hash preservation. -- [x] Task-owned local commits only; refresh idle checkpoint. No push/npm/runtime installation. - -## Evidence / Progress - -- Planning: previous investigation reproduced entry_not_touched classification and unlimited warning; confirmed live notifier matches source. Rule contradiction and stale closeout read from actual files. -- Pre-flight: report consumes status from Task 2; doctor reads only existing product binding/record metadata, never writes it. No other shared implementation interfaces. -- Ruling: implement all six within existing mechanisms; pause and cross-session continuity are operating guidance, not a new orchestration API. Local readiness cannot prove live effectiveness; report unverified even when all files are present. -- RED→GREEN logs: `/tmp/opencode/ksi-usability-{notification,status,doctor,report-extra,input}-{red,green}.log`. Targeted regressions cover untouched-entry execution, failed execution, review/pause contract, stale/missing/escaped records, malformed state shape, repository mismatch/absence and CLI invalid input. -- Full pre-review suite at ea8b514 + pending task changes: `npm run check` exit 0,129 tests/128 pass/0 fail/1 optional Docker skip; `/tmp/opencode/ksi-usability-pre-review.log`. Actual source CLI --status and doctor ran; no readiness claim. -- Independent reviewer `ses_ef32c965affeBEChx1X4LgjN3f`: no Critical/Important findings; exactly three intended global policy hunks and six protected hashes verified read-only. No product/runtime/visual acceptance inferred. -- Final: minor (deferred): M1 notifier's terminal-only productVerification two-value contract differs from report's unknown state for nonterminal input; actual delivery is terminal-gated and no completion is inferred. -- Final: minor (deferred): M2 additional binding placeholders (N/A/none/null) are not normalized as absent; all-present checks still remain live-unverified. Extend on demonstrated use, not an invented readiness claim. -- Final: Ruling: review's unexercised live runtime/catalog/product/browser/visual behavior is not verified by this task — those surfaces are unchanged or outside local source checks; runtime adoption/real-use acceptance stay explicit follow-ups — cost if wrong: deployment behavior may require further validation before delivery. -- Independent test-runner `ses_ef32a4baeffe4tsUBw0A2JSqG0` on main ea8b514 +17 task paths: `npm run check` exit0 (129/128 pass/0 fail/1 optional skip), `npm run check:package` exit0, `git diff --check` exit0; all fixture categories included. Six config/role hashes match; global diff exactly three intended hunks. Installed notifier messages remain byte-identical to ea8b514 baseline (no runtime update). -- Source CLI status/doctor JSON exit0; actual HEAD/recorded-active matched, pending acceptance/local-ahead delivery separated, all-present doctor still unverified. Independent strace doctor check: no child exec/network calls. Evidence `/tmp/opencode/ksi-usability-final-{check,package,diff}.log`, `ksi-usability-final-{status,doctor}.json`, `ksi-usability-final-doctor-strace.log`. -- Local implementation committed as `f57f211` on main. No workspace created/moved/merged or removed; four unrelated worktrees preserved. Closeout updates only this ledger/product-state; checkpoint remains private/uncommitted. Current global policy is applied, notifier source changes are not installed, GitHub/npm unchanged. Native pause orchestration is guidance, not a newly enforced/atomic API; no actual task/goal pause was performed. diff --git a/docs/superpowers/plans/2026-10-06-visual-ownership.md b/docs/superpowers/plans/2026-10-06-visual-ownership.md deleted file mode 100644 index c666fcb..0000000 --- a/docs/superpowers/plans/2026-10-06-visual-ownership.md +++ /dev/null @@ -1,506 +0,0 @@ -# Visual ownership simplification Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Route all visual frontend work, including rendered design review and refinement, to the design workspace; keep OpenCode responsible for contracts, coordination, functional verification and Git. - -**Architecture:** Simplify the existing policy and notifier advice, not add an orchestrator. The notifier preserves daemon verdicts and delivery semantics; UI-quality obligations depend on the verified task/output, not the canonical-entry verdict. Direct product-source UI editing remains preferred, with one writer and a supported artifact handoff when direct access is unavailable. - -**Tech Stack:** Existing Markdown guidance, JavaScript notifier, Node.js built-in tests; no new dependencies. - -**Spec:** Approved in this conversation: “시각적 작업은 다 … 넘기는” and “그렇게 단순화하면서 개선해줘”. This bounded change implements that in-chat contract; earlier OpenCode-led aesthetic evaluation and small-visual-edit exceptions are superseded. - -**Status:** Implemented and verified locally; actual design-quality behavior remains a product-session validation obligation. - -## Global Constraints - -- Harness-only work. No EVENTOUCH source writes, design runs, product-session mutations, push, deployment or publication. -- Preserve functionality/data/permissions/API contracts; layout, hierarchy, responsive presentation and components may be redesigned within the authorized outcome and brand boundaries. -- Design workspace owns UI source/styles, interaction presentation, rendered analysis/review and concrete refinement, including small CSS/visual fixes. OpenCode does not substitute its own visual fixes. -- OpenCode owns business rules/API/data/auth, functional integration tests, ownership handoff and Git; same-file work is sequential. -- Keep native modes/prompts/models/permissions, goal/pause rules and conditional human direction/representative approval gates intact. -- No new automatic loop, runtime quality classifier or claimed aesthetic score. A stalled round means quality unmet, not success; no fixed refinement-round cap. -- Update existing global policy and pinned notifier copy with backups and verification; no service restart. Guidance is not enforcement or proof of live design quality. - -## Review Focus - -- An entry-invalid successful source edit must still receive design-review guidance after actual output verification. -- Valid artifact, report-only output and intermediate plan stage must not be mistaken for quality pass or authority to spawn a new run. -- Failed/cancelled/missing-output work must not be upgraded to success or blindly replayed. -- Small visual repair stays design-owned; a nonvisual API/permission/persistence defect stays engineering-owned; shared UI files have one writer. -- User rejection invalidates acceptance, while historical commits/tests remain implementation evidence. Harness completion does not claim repaired product visuals. - -## Workspace and evidence - -- Primary: current checkout `/home/ksi/Desktop/KSI-Projects/KSI-Opencode-Harness`, branch/target `main`, base `8aa350eecdc69e041fe4e45fb36276aefa5f6901`, initially clean. No isolation needed: one writer; existing unfamiliar worktrees retained. -- Root cause evidence: integration guide previously exempted approved-pattern visual fixes and made coordinating-agent aesthetic review primary; notifier quality advice was gated by artifact-valid verdict (`messages.js` step 3). Pilot ledger recorded generated/rendered changes but no criteria-linked design review/refinement evidence. User rejected the bold result; no acceptance claimed. -- Pre-flight: notifier advice, shipped guide/examples and live global policy must state the same ownership contract. State records must distinguish policy/code verification from real design outcomes. - -### Task 1: Notifier output-contract regression - -**Files:** `test/design-notifier-messages.test.mjs`, `plugins/design-notifier/lib/messages.js`, `plugins/design-notifier/README.md`. -**Interfaces:** `buildDelivery({run, sessionID, ...})` remains unchanged; advisory metadata adds `designQuality: "not_assessed"` without changing classification, message IDs or wake/delivery behavior. - -- [x] Add table-driven tests for valid/entry_not_touched/no_artifact successful runs, report-only and intermediate caution, failed/cancelled runs and design-owned review/repair; assert actual emitted advice/metadata, not documentation source text. -- [x] Run `node --test test/design-notifier-messages.test.mjs` and observe intended missing-advice/metadata failures. -- [x] Replace artifact-valid-only quality advice with output-contract-aware design-owned review/refinement instructions; preserve original verdict and gates. -- [x] Run targeted notifier tests and `npm run check`; all tests must pass (optional Compose skip reported separately). - -### Task 2: One ownership contract, small evidence handoff - -**Files:** `docs/integrations/opendesign.md`, `docs/{architecture,execution}.md`, `examples/{project-AGENTS,autonomous-development}.md`, `README.md`, `/home/ksi/.config/opencode/AGENTS.md`; reconcile `docs/superpowers/product-state.md`, pilot ledger and `.opencode/working-state.md`. -**Interfaces:** Compact brief = purpose/problem, invariants vs redesign freedom, actual source/states/references, expected files and acceptance. Existing task record = criterion/defect → design review finding → revision/render evidence → fixed/open + functional check; no new registry. - -- [x] Back up global policy/pinned runtime and record native config/role hashes. -- [x] Replace competing routing/self-evaluation rules; design analyzes/implements/renders/reviews/refines, OpenCode checks evidence and actual functional flow. Small visual fixes use compact refinement, not fresh intake/full audit. -- [x] Keep new direction/client/scope/review-first and representative rollout gates; UI direct edits can precede quality pass but are provisional and not completion evidence alone. -- [x] Reconcile stale pilot/state claims: user rejected bold result; product refinement belongs in the product session; autonomous quality loop remains unverified. -- [x] Fresh reviewer tests consuming guidance against the five Review Focus classes; record interpretation limits, not visual-quality success. -- [x] Independent test-runner runs `npm run check`, `npm run check:package`, targeted tests and diff checks; verifies native hashes, global-policy scope and no product writes. -- [x] Commit task-owned files, update the existing pinned notifier copy, independently verify installed hashes/metadata and final repo state; reconcile ledger/checkpoint and report live-pickup/real-use limits. No push/restart. - -## OD-usage loop refinement (follow-on, same slice) - -Approved in conversation (2026-10-06 "그렇게 개선해줘") after the user asked how to -use OD better: make the design-review run a required stage, feed it actual rendered -states, standardize its output as a fix list, write observable acceptance criteria, -verify input access before commissioning, and record skill selection per stage. - -- RED: two new notification-contract tests failed (`/tmp/opencode/ksi-od-usage/red.log`) — review-run requirement and observable-criteria/input-access advice absent. -- GREEN: messages + notifier suites 39/39 pass (`green.log`). Global policy bullet 43 + KO section, shipped guide §Design-quality loop, notifier README, examples ×2 and README aligned to the same contract. -- Backup/preservation: `/tmp/opencode/ksi-od-usage/` (policy backup, notifier copy, 9 config/role hashes). -- Implementation commit `e40ebbb`; independent test-runner `ses_ef0b3b84affeU0M9vGdvX9BIjB`: check142/pass141/fail0/skip1, targeted39/39, whitespace/status clean, 9/9 hashes preserved, global policy = exactly the two intended bullets, pinned payload 7/7 equal, 96 installed-message contract assertions pass; two nuances flagged (R5 skill-recording parity in 3 docs; missing literal assertions for "distinct review skill"/"scroll depth"). -- Nuances closed in `d6f7c83`: parity text added to README/project-AGENTS/notifier README; two assertions added and mutation-checked (phrase removal → both tests RED, restore → 39/39 GREEN; log `mutation.log`). Full check re-run 142/141/0/1 (`check2.log`); pinned runtime re-installed at `d6f7c83` (verify ok:true). -- Limits: live plugin pickup unverified; emitted contracts/docs consistency ≠ visual quality or live agent obedience. Real validation remains the product session's rendered review/refinement evidence. - -## Direction options + audit-first + user-state reporting (follow-on 2, same slice) - -Approved 2026-10-06 ("그렇게 해줘") from the ranked list: (1) lightweight direction -options before full implementation when the direction is new/ambiguous, (2) a -read-only design audit of the actual current screens before redesigning, using its -defect list as the redesign's problem statement. Additionally folded in from the -user's same-day feedback about unreadable progress reports and approval-waiting: -(3) user-state reporting (usable level now → blockers and who unblocks them → -what you will do next) and no naked next-candidate endings — continue within an -active goal/authorized mandate with recommended defaults, only genuine -material/irreversible decisions or external inputs return to the user. - -- RED: two new notification-contract tests failed (`/tmp/opencode/ksi-od-direction-audit/red.log`); one wording hyphen gap fixed RED→GREEN (`green.log`, 41/41). -- GREEN: full `npm run check` 144 total / 143 pass / 0 fail / 1 optional Compose skip (`check.log`); 9/9 config/role hashes preserved. -- Surfaces: notifier advice step 4 (renumbered 5–8), guide loop step 1 + renumber, global policy EN/KO design bullets + closeout-report bullet, examples ×2, README, notifier README. -- Backup: `/tmp/opencode/ksi-od-direction-audit` (policy, notifier copy, 9 hashes). -- Commit `1e64c2a`; pinned notifier copy updated to the same commit (verify ok:true). Ledger commit `8d9448e`. -- Independent test-runner `ses_ef08d5b49ffejcK2yMoCZVTPfe` (at `8d9448e`): check 144/143/0/1, targeted 41/41, whitespace/status clean, 9/9 hashes preserved, global policy = exactly 3 approved bullet replacements, runtime `1e64c2a` payload 7/7 identical, installed-message 105/105 assertions (step 4 + renumbered 5–8 + prior contracts), A/B/C doc citations verified with no stale shipped contradiction. Residuals (low, deferred): historical quote in `2026-10-04-autonomy-gaps.md:23` is a dated record already annotated as historical in product-state; notifier README restates A/B but not C (C lives in runtime step 8 and the parent README). Live pickup/real behavior intentionally unverified. -- Limits: emitted contracts/docs only; live pickup and real design/report behavior unverified. No product edits/runs/restart/push. - -## Project-binding review (follow-on 3, same slice — user request 2026-10-06) - -User asked to review the OD runtime/ports/projects and whether the flow has a -problem after the product session reported a stopped chain ("프로젝트에 묶인 작업 -유형이 프로토타입이라 소스 수정 산출을 지원하지 않는다"). - -- Runtime verified read-only: daemon 127.0.0.1:7456 (health ok, v0.23.1) and - tailnet 100.116.163.1:7456; remote-workspace gateway 127.0.0.1:17456 (403 on /); - `open-design` container running; tailscale serve active. Projects: EVENTOUCH - `9db026a4` (baseDir the product checkout), `ksi-deploy-smoke`, KSI-CCTV - `cf18904b`. EVENTOUCH was re-created by the product session; `aabf3621` is gone. -- Root cause confirmed: folder imports carry `scenarioBinding` - (`example-web-prototype`, provenance `automatic_default`) and the od-next - `strategyBinding.taskProfile` `prototype`. od-next supports only - prototype/ppt/marketing/hyperframes — **no code profile**, so "rebind od-next - to code" is not a supported path. Source-only edits with untouched canonical - entry → `od_next_canonical_deliverable_invalid` → outcome `blocked`, which can - stop automatic continuation. -- Supported alternative found: an explicit scenario plugin in `start_run` - (`od-code-migration` patch-edit/build-test/diff-review; `od-design-refine` - direction→patch→critique→handoff) stamps an `explicit_user` scenario binding; - `POST /api/projects/:id/scenario/restore-automatic` reverts. Whether the - strategy evaluation changes under an explicit scenario is unverified — must be - tested in the product session's actual run. -- Guide "Run mechanics"/"Registering" updated with the binding facts; global - policy EN/KO design bullets gained the blocked-continuation clause; product-state - and the source-registration ledger reconciled the EVENTOUCH id drift. -- Limits: all checks were read-only probes (daemon API, container files, plugin - metadata); no design run, no binding mutation, no restart. The recommended - alternatives are supported mechanisms, not verified outcomes. - -## Resident context + review bundle (follow-on 4, same slice) - -Approved 2026-10-06 ("이 부분까지 모두 개선"): complete OD-usage items 3–6 — -resident product context, review-delivery bundle, skill observations table, and -accessibility/motion review inputs — plus the registration scenario-path record. - -- RED: three new notification-contract tests failed (`/tmp/opencode/ksi-od-context-bundle/red.log`); GREEN 44/44 after the message changes. -- Surfaces: notifier step 3 (keyboard/focus + accessibility + motion states; resident `product-context.md`), report sentence (review bundle), guide (resident-context subsection, bundle paragraph, skill observations table, review-input list, registration line), global policy EN/KO, README, examples ×2. -- Commit `c43fbea`; pinned notifier copy updated to the same commit (verify ok:true). Full check 146/146 pass/0 fail/1 optional skip; 9/9 config hashes preserved. Independent test-runner verification recorded below. -- Limits: emitted contracts/docs only; live pickup, real review runs and product-session context-file creation unverified. - -## Goal usage + first-cycle protocol (follow-on 5, same slice) - -Approved 2026-10-06 ("goal 을 언제 에이전트가 쓰는지 … 나머지도 모두 개선해줘"): -goal-mode proposal signals, user-facing goal recipe, first-cycle evidence -protocol, recurring-defect patterns in the resident context, and the design-system -attachment candidate. - -- Surfaces: global policy Session-goals bullet (when to propose / when not / proposal contents), examples/autonomous-development.md goal bullet, README §05 goal recipe (how to start/pause/resume, Plan/scope limits), guide (recurring-defects line in resident context, first-cycle evidence subsection, design-system candidate note). -- Docs/policy only — no notifier or code change; pinned runtime stays `c43fbea`. -- Checks/commit recorded below. Limits: guidance only; goal behavior and the first-cycle protocol are validated in real sessions. - -## Naming rule — "Local Codex" (follow-on 6, same slice) - -User asked why agents keep presenting the locally installed OpenDesign as "Local Codex" -(structural OD problem or our guidance?). Root cause: (1) OpenDesign's own MCP -instructions list "Local Codex" as the user-facing product name for local execution, -so agents comply; (2) our guidance mirrored that vendor label without a user-facing -naming rule. Fix: user-facing copy says **"OpenDesign (로컬 실행)"**; "Local Codex" is -a vendor mode label only — never introduced as a separate product/agent/CLI; the -environment's explicit user preference overrides the vendor default. Applied to -global policy KO, guide EN, README. Docs/policy only — no code/runtime change. -Historical records keep their original wording (not rewritten). - -## Writeback + external-intake rules (follow-on 7, same slice) - -From the 2026-10-06 EVENTOUCH audit (read-only): the session's latest OD run -reported `od-owned` workspace while the checkpoint claimed source-direct was -blocked and Build transcribes — a silent degradation; and the user's -cross-machine OD export (24MB zip) sat in the repo root with no recorded -adoption map. - -- Guide: "Writeback degradation" paragraph (od-owned storage = capability - regression to re-verify/record/report, not a silent transcribe switch) and - "External OD export intake" subsection (extract outside the product repo, - adoption map, place inside the connected root for runs, report the map). -- Global policy KO design bullet: the two matching clauses. -- Docs/policy only; no code/runtime change. Checks/commit below. - -## Environment fix — codex MCP crash-loop (follow-on 8, same slice) - -User asked whether MCP instability should be fixed rather than disabling Code -Mode. Log evidence: `mcp connect failed server=codex` dozens of times (all codex, -none other) — `/home/ksi/.local/bin/codex mcp-server` spawns without a TTY -("stdin is not a terminal"), exits, retries; each retry churns the tool catalog -(the "tool list appears/disappears" symptom). Sessions also manually -disconnect/reconnect the opendesign MCP (recovery attempts, extra churn); -opendesign "resource templates — Method not found" warnings are harmless noise. - -- Fix: `~/.config/opencode/opencode.jsonc` codex server `enabled: false` with a - comment; backup `/tmp/opencode/ksi-mcp-fix/opencode.jsonc.before-1628`. -- Verified: config parses (codex false, others true); no new WARN failures after - the edit (3-min observation); runtime reports codex not registered (disconnect - 404). Revert = restore backup or `enabled: true`. -- Conclusion recorded: Code Mode was not the cause; the EVENTOUCH `codemode:false` - diagnostic is unnecessary unless flapping recurs after this fix. - -## Environment fix 2 — opendesign MCP proxy EPIPE handling (follow-on 9) - -The only observed opendesign MCP crash was `uncaught exception: Error: write -EPIPE` (proxy exiting code 1 when the client closed the pipe — normal disconnect -treated as a crash, causing reconnect churn). The deployed bundle -`~/.local/share/od-mcp-host/proxy.cjs` had no broken-pipe handling. - -- Patch: `isBrokenPipe` + `exitQuietly` — EPIPE / ERR_STREAM_DESTROYED on - uncaught exception or unhandled rejection now exits 0 quietly instead of - reporting a crash. Localized (lines ~26451-26478), `node --check` OK. -- Backup `/tmp/opencode/ksi-mcp-fix/proxy.cjs.before`; diff - `/tmp/opencode/ksi-mcp-fix/proxy-epipe.patch` (34 lines). The repo's - `mcp-host-entry.ts.example` does not contain this handler, so the fix lives - only in the deployed bundle — reapply from the patch after any redeploy. -- Running proxies keep the old code until their sessions end; new spawns use the - patched bundle. Live EPIPE quiet-exit will be visible in the log on the next - client disconnect (no more "[od mcp] uncaught exception ... EPIPE"). -- Remaining noise: opendesign "failed to list MCP resource templates — Method not - found" warnings are client-side calls to an unsupported method (harmless). - Codex retries continue only in sessions started before the config change - (this harness session included); new sessions no longer load codex. - -## Environment fix 3 — codex MCP fully removed (follow-on 10) - -User decided the codex MCP will not be used (delegation/media generation move to -OpenDesign). The `enabled:false` entry was removed from -`~/.config/opencode/opencode.jsonc` and replaced with a tombstone comment (why it -was stale, where delegation lives now, how to re-add). Backup -`/tmp/opencode/ksi-mcp-fix/opencode.jsonc.before-removal`; parse verified — mcp -servers now opendesign/mobbin/playwright/context7 only. - -EVENTOUCH observation (same check): the newest runs are **folder-backed** with -`baseDir` = the product checkout (source-direct writeback working again; the -older od-owned runs were the pre-fix ones), and two runs were active at check -time. The earlier audit concern about silent transcribe degradation is resolved -in the current state; checkpoint header/body consistency and the post-full-swap -test-suite rewrite remain open on the product side. - -## Push + EVENTOUCH remaining-items status (follow-on 11) - -User: "남은 것 모두 진행해줘" (2026-10-06). - -- Harness push: `origin/main` (github.com/khakisketch/KSI-Opencode-Harness) — see the - push record below. -- EVENTOUCH adoption map (observed, recorded here since the product repo was - active): the cross-machine OD export (`493d5d55-….zip`, extracted at - `/tmp/opencode/od-batch`) was consumed as a **full-swap rebuild**: `1876efa` - vendored the OD bundle as the new product shell, `2f3fbcd` dropped OD - scaffolding, then OD-authored iterations `45ec662`→`07b2cbd` (creation - flow/overview/sidebar) landed as product commits. The product session should - mirror this map in its own record and archive/remove the repo-root zip. -- Product-side remaining items are **in-flight in the active EVENTOUCH session** - (2 design runs running at check time; source-direct folder-backed writeback - working): checkpoint header/body consistency, post-full-swap test-suite - rewrite, zip archiving. Not edited from here — one writer; the product session - owns that workspace. - -## Stop-recovery guidance (follow-on 12, same slice) - -From today's incident: the EVENTOUCH session (`ses_efe9d3ab…`) stopped at 09:11/09:16 -UTC with `Failed to drain Session` / `AI.Error: The request contains invalid -parameters` (provider-layer rejection; same AI.Error class as today's `Model is -unavailable` and `Referenced reasoning item` errors in other sessions), leaving -uncommitted work in the `field-operation` worktree that the checkpoint did not -mention. - -- Global policy: checkpoint Current State now names active worktrees and any - uncommitted work; new "recover from unexpected stops" bullet (preserve work - first → retry → compact/fresh session from checkpoint → provider/model check; - delivered ≠ processed). -- examples/autonomous-development.md: same recovery bullet (parity). -- docs/troubleshooting.md: diagnosis bullet (`grep "Failed to drain"`, AI.Error - classes, never delete the worktree or replay runs). -- guides/working-state-template.md: Current State line mentions worktrees/uncommitted. -- Classification recorded: the stop was provider/environment, not harness. - -README polish (same day): flow diagram now shows the current design loop -(audit-first / direction options → source implementation → required review run → -defect list → refinement); "세션이 멈춘 것 같아" recovery line added to the -natural-language section; one blocked line added to the design section. Version -pins verified current (beta.6 = latest/next). Commit `b9523e4`, pushed; check -147/146/0 and package check passed. - -## Release/deployment improvements (follow-on 13, same slice) - -From the user's "배포쪽도 개선해줘": release tooling was verified (docs/releasing.md -existed; only tag v0.5.0-beta.1 existed; pin guards were an open backlog item). - -- `scripts/release-check.mjs` + `test/release-check.test.mjs` (TDD: 4 tests RED → - GREEN): extracts README/INSTALL pins (`package=ksi-opencode-harness@…` and the - Beta badge — deliberately not bare `@version` mentions so the historical - `0.4.0-beta.0` note is not a false positive), checks pins vs package.json, - clean tree, and runs `npm run check` + `check:package`. `npm run release:check` - added; never publishes or tags. -- `docs/releasing.md`: preflight mention, "Tag the verified release" section and a - release checklist. -- Tags: `v0.5.0-beta.2`…`v0.5.0-beta.6` created at commits whose `package.json` - carries that version (`ab95c5e`, `0682659`, `e2263b5`, `c341faa`, `7db2c46`) and - pushed; remote now has all six tags. -- Verification: full check 151/150/0; `npm run release:check` dogfood pass - (pins 7, clean tree, both checks); commit `373b62f` pushed. -- GitHub deployment completion (same day): releases created for `v0.5.0-beta.2`…`v0.5.0-beta.6` (pre-release, npm-linked notes; beta.1 already existed); CI (`check.yml`, 3 OS × 2 Node) now also enforces the version-pin guard — a real-file test in `test/release-check.test.mjs` (mutation-checked: broken pin → RED, restored → GREEN) runs inside `npm run check`. Full check 152/151/0; commit `c31a712` pushed. -- npm status: `latest`/`next` remain `0.5.0-beta.6`; the ~40 commits of improvements since the beta.6 release (install guides, composition guide, docs) are **not yet published** — a beta.7 requires explicit publish authorization; do not bump pins before that authorization (README pins must not point at an unpublished version). -- **beta.7 released (2026-10-06, user "승인")**: version bump `447e6df` (checks 152/151/0, isolated verify pass, pack 18 files / 70.7 kB, pins 7); `npm publish --tag next` after browser approvals (login + publish); registry verified (`bin` normalized as with beta.6); public smoke test preview → apply → repeat (`changes: []`); `latest` promoted after two expired auth sessions (third completed); tag `v0.5.0-beta.7` pushed; GitHub release created. Final dist-tags: **`next` = `latest` = `0.5.0-beta.7`**. - -## Visual walkthrough (follow-on 14, same slice) - -User feedback: docs are text-heavy; captures/screens of the real development flow -(especially OpenDesign) would help understanding. - -- `docs/walkthrough.md` (Korean): flow diagram + two real captures — OpenDesign - project workspace (conversation/files/render) and the actual product dashboard - (new EVENTOUCH shell) — with per-image captions and a who-does-what table. -- `docs/assets/walkthrough/`: `00-flow.svg` (source) + `00-flow.png` (rendered via - Playwright at 900×540), `01-opendesign-workspace.png`, - `02-product-dashboard.png` (both captured live via the Playwright MCP). -- README: header link, inline flow image under "KSI는 이렇게 작업합니다", and a - "더 알아보기" row. `package.json` files now include `docs/walkthrough.md` and - `docs/assets` (tarball 23 files / 448 kB). -- Verification: check 152/151/0, package check pass; commit recorded below. - -## Responsibility-flow clarification (2026-10-07, approved README follow-on) - -- User approved the reviewed five corrections with "그래 개선해줘": optional Plan, routing - by work nature rather than size, design as actual visual implementer with discretion, - independent final checks on integrated code, and technical completion distinct from - human acceptance/external delivery. This is documentation clarification, not a new runtime. -- Baseline/integration target: existing checkout `main` at `84fd8b4`, clean and ahead3 of origin. - One writer; four unfamiliar pre-existing worktrees retained. No new goal or workspace. -- Scope: README responsibility diagram/core definitions/ownership and role table; align the - architecture and integration responsibility summaries only. No implementation, agent/config/ - policy/runtime/asset changes, design generation, installs, push or publication. -- Ruling: keep the current checkout and existing ledger; an extra skill scratch ledger/worktree - would duplicate continuity without an isolation need. Preserve the existing overview PNG/SVG: - it already depicts Build coordination → actual UI source → product verification, not a mandatory - Plan or a mockup-only handoff. Text clarifies the additional roles/order without a visual redesign. -- Acceptance: a reader can distinguish optional planning, engineering vs all visual work, - design professional discretion vs Build integration responsibility, self-tests vs independent - final checks on the integrated revision (recheck after changes), and acceptance vs delivery. - Preserve capabilities/authorization prerequisites and installer-only3-role/no-enforcement limits. -- Verification depth: prose-only changes use an independent interpretation review and existing - full/package/version-pin checks; no synthetic string test presented as workflow enforcement, - no product/provider/browser run required. Final test-runner receipts belong here. -- [x] Rewrite README diagram, role definitions and compact ownership rules. -- [x] Align architecture/integration summaries; initial prose/diff inspection complete, automated checks next. -- [x] Independent reviewer and test-runner; record findings/results. -- [x] Local closeout: only the verified5 doc/state files enter the commit with this record; - product state reconciled, exact committed revision/final check receipt retained in checkpoint. - External delivery is not included; no human end-to-end acceptance inferred. - -- Reviewer `ses_eeb3024e1ffetVkHIJ0TiD2Sd1` covered7 interpretation cases: no Plan for clear - API repair; small visual vs large DB routing; design-success/API-failure repair; later edit - recheck; blocked design no engineering visual fallback; prior delivery approval reuse. - Entry-path visibility findings addressed with compact links to existing audit/direction/ - review gates and blocked-path guidance; Test Runner/Reviewer ownership rows split. - The gates/no-fallback already existed later in README and the integration guide; this makes - them visible to an entry-only reader rather than introducing a new permission/workflow rule. -- Non-blocking review coverage limits: reviewer did not verify origin/worktree inventory or - image pixels; Primary's initial Git inventory and direct PNG/SVG read confirm the recorded - baseline and compatible preserved overview. No new asset/render quality claim. Size/process - pointer shares the compact review-gates link. No deferred defect requires new scope. -- The first independent test run may overlap these prose refinements; use the post-refinement - test-runner receipt for completion, not stale output from the first pending tree. -- Post-refinement independent test-runner `ses_eeb2ff873ffeqJ8uaHUaTGTi4z`: exact - `TMPDIR=/tmp/opencode KSI_TOOL_CHOICE_INTEGRATION=1 npm run check`166 total/165 pass/0 fail/ - 1 optional real-Compose skip; `npm run check:package` and `git diff --check` pass. README's - new2 fragment links resolve; all referenced local files exist. Diff remains5 prose/state - files only; no source, role, config, version, asset or runtime payload changes. Logs: - `/tmp/opencode/ksi-responsibility-docs-20261007/refined-{check,package}.log`. -- No deferred review defect/new scope. Local technical documentation checks complete; final - commit/revision receipt goes in checkpoint. Human readability/product acceptance and public - GitHub/npm delivery remain separate. No push, release, installer run or global adoption. - -## Verification / closeout - -### Audit remediation (2026-10-07, user-approved bounded follow-on) - -- Contract: investigate recurrent request failures without changing models or disabling Code Mode; - repair confirmed common-harness gaps around rendered-review readiness and truthful state/closeout. - User approved the in-chat design with "진행을 부탁드립니다." Product UI/test rewrites remain in - their owning sessions. No installation, shared-service restart, push, release or deployment approval. -- Baseline/target: existing checkout `main` at `d2020f4`, initially clean and in sync with origin. - No concurrent writer; four pre-existing worktrees retained. Checkpoint is uncommitted resumability - data, not intended behavior or proof of completion. -- Audit evidence: actual ERROR at `~/.local/share/opencode/log/opencode.log:76215` - (2026-10-07T03:08:43Z, analyst `ses_eebabd37bffeDrTtXqouM4Ot3j`) rejects non-auto tool_choice. - Do not infer from that response that the provider alone is at fault: request construction remains - an open boundary. CLI reports 2.0.24; actual service API reports 2.0.22; causality unestablished. -- Design evidence: the recent EVENTOUCH field-operation run's terminal explanation says browser - verification was not performed and 390/320 rendering remains unverified. Source changes/tests - exist at product `86b532d`; execution success and source diff do not close rendered review. - No product repair or generation is authorized in this harness follow-on. -- Acceptance: confirmed defects get behavioral regression tests (RED → GREEN), full source/package - and independent test-runner checks; any unavailable provider/render capability remains explicitly - unverified/blocked, without claiming product or environmental stability. New readiness/reporting - advice is advisory, not an enforced quality gate or a second continuation controller. -- Root-cause evidence: failed analyst `ses_eebabd37bffeDrTtXqouM4Ot3j` enumerated via native - paginated API: exactly32 assistant messages, final message empty, configured reviewer cap32. - Serving `/proc/494960/exe` (2.0.22, old/deleted binary) and installed CLI2.0.24 both contain - the native step-limit branch selecting `toolChoice:... ? "none" : undefined`. No explicit - tool-choice config existed before the repair. This supersedes the earlier provider-only - classification at line268; other generic invalid-parameter errors remain undiagnosed. -- Local characterization: a credential-isolated native service and fake loopback auto-only - endpoint reproduced empty final output with `none`; a model-body `auto` overlay produced - `bounded-summary`; an unsolicited final read was rejected by the unchanged native - "Tools are disabled after the maximum agent steps" guard, exactly2 requests/cap2 throughout. - The original throwaway probe is `/tmp/opencode/ksi-request-probe/probe.mjs` (not delivered). - Retained characterization: `scripts/verify-tool-choice-isolated.mjs`, opt-in integration test. - Real provider calls/shared-session changes/credential inheritance:0; other child egress unmonitored. -- Current-user compatibility: backed up existing config privately, added only - `providers.opencode-go.models.muse-spark-1.3-contributor.body.tool_choice="auto"`. - Native config reads show ollama/nvidia preserved; all7 effective roles' model/variant/mode/ - steps/permissions equal their baseline. No model replacement, step-limit removal or restart. - This is a scoped workaround, not an upstream native-code fix or proof of real long-run stability. -- Render capability: existing container has Node but no agent-browser/playwright-cli/browser - binary at the tested paths. The already completed preparation audit has an explicit receipt - for seven host captures at1280/390 and source files, and explicitly lacks320/loading/success - captures. Readable-host-capture review is a supported alternative, not a completed review of - the different field-operation revision/states. No new product screenshot/run/mutation. -- Implementation: notifier preserves daemon classification/scheduling/IDs/pauses, adds explicit - unverified render-input/review fields plus inner-renderer/readable-capture prerequisite; - guide/global policy mirror this. Walkthrough captions now identify historical illustrative - screens rather than implying current execution/verification. Checkpoint contradictions removed. -- TDD: three new notification contracts RED on missing fields → GREEN29/29; local integration - test1/1 and standalone native characterization `ok:true` before minor review refinements. - Reviewer `ses_eeb8f7be0ffe6xaOo2JlUjQM7i`: no Critical/Important, four interpretation cases - consistent. Minor missing status-case metadata coverage and omitted-choice ambiguity fixed; - bounded opt-in timeout retained (no observed flake). No native/visual-quality pass inferred. -- Independent final verification: test-runner `ses_eeb8d1ef4ffecycg7N5rOeHp19` at baseline - `d2020f4` + the nine task files ran `TMPDIR=/tmp/opencode KSI_TOOL_CHOICE_INTEGRATION=1 - npm run check`:159 total/158 pass/0 fail/1 optional real-Compose skip. `npm run check:package` - passed including the walkthrough/assets package content; native isolated verifier passed - (CLI2.0.24, native roles, no provider requests/retired skills, all22 Plan design permission - decisions). `git diff --check` passed. All7 effective role baselines identical; private - in-memory config comparison and policy comparison confirmed exactly the intended additions, - with unrelated bytes preserved. Logs: `test-runner-{check,check-package,verify-native-v2, - role-baseline}.log` under the private evidence directory below. These checks cover the current - walkthrough/package revision, not visual acceptance of its historical product examples. -- Additional serving-version evidence: the actual2.0.22 executable ran the same isolated - characterization with `ok:true`; baseline none rejected, scoped auto summary returned, - terminal read rejected, cap2/two requests unchanged. Host/container SHA256 for the existing - registration390px capture matched (`211d5fbb...50e29`); prior reviewer explicitly read7 - supplied captures. This does not validate the separate recent field-operation rendered states. -- Delivery boundary: existing pinned notifier at `c43fbea` passes its manifest check,7/7 files - unchanged. Its advice differs from this new source. **Do not refresh it implicitly**: current - remediation excludes deployment; earlier "refresh after commit" checkpoint intent is superseded - by this boundary. Global render-path policy and narrowly scoped model-body workaround are applied; - notifier advice is source-ready only, not installed or confirmed picked up. -- Status: approved source/config remediation technically verified locally; long-run real-provider - summary behavior and product design-quality acceptance remain unclaimed, not replaced by test - counts. No push, npm release, product run/write or shared-service restart. Private backups and non-secret role baseline under - `/tmp/opencode/ksi-audit-remediation-20261007` (directory700, backup files600). - -#### Committed rerun failure and contained helper side effect (2026-10-07) - -- Source committed locally as `ab01b51` (9 files); no push. The same independent test-runner's - committed rerun **failed**:159 total/157 pass/1 fail/1 skip, isolated summary expectation. - Package/diff passed. This supersedes any implication that the first green run closed the task. -- Root cause of the intermittent characterization failure, not a change in provider behavior: - second HTTP request can arrive before the second assistant is visible in the native message - API. Old `requests.length>=2 && latest.completed` accepted the first completed assistant. - A diagnostic repeat captured2 requests but only1 assistant, with the first tool's permission - error misreported as a terminal-step failure (`probe-race-evidence.json` in evidence directory). - Pure result selection now requires the second completed assistant in ascending order, not a - longer sleep or a retry-until-green wrapper. Deterministic regression RED→GREEN;12 repeated - native characterization runs then passed all three cases (`probe-race-fixed-repeats.json`). -- Separate tool side effect: the verification helper invoked - `node scripts/install-design-notifier.mjs --help` while discovering usage. This was not an - assigned command/approved install. Old CLI ignored unknown flags, so it installed `ab01b51` - at03:46:27Z. The helper's "no install" report was incorrect; native message/tool history and - installed manifest establish the actual action. No evidence shows a deliberate install or - product write, but transient live plugin pickup is unknown and not claimed absent. -- Containment: Primary preserved that accidental payload privately, extracted only the proven - prior `c43fbea` tracked plugin files, and restored that7-file payload at03:49:03Z. Manifest - now records rollback time/source path; it is not byte-identical historical install metadata. - Read-only installed verifier passes. No shared-service restart or design run performed. - New notifier advice remains source-only in the final intended state, **not** never transiently - installed. This paragraph supersedes earlier "unchanged throughout" delivery implications. -- CLI safety repair: `--help`/`-h` return usage without writing; unknown flags fail before writes, - missing path values and conflicting verify/uninstall fail closed. Three real CLI regressions - were watched RED against the old installer in disposable config directories, then GREEN; - no live config used for those tests. Existing no-argument install behavior retained. -- Follow-on reviewer `ses_eeb8309b3ffe6yR5Z6WZt2ve4a`: no blocking findings in4 code/test - files; selector is deliberately fixed to this2-step characterization, help wins without - writes, unsupported `--target=...` fails rather than silently installing the default target. -- Follow-on test-runner `ses_eeb824753ffeGrTGHI2YM2d9iY` at `ab01b51`+6 pending task files: - `TMPDIR=/tmp/opencode KSI_TOOL_CHOICE_INTEGRATION=1 npm run check`166 total/165 pass/0 fail/ - 1 optional Compose skip; `npm run check:package`, actual serving2.0.22 isolated native - characterization, diff checks and installed manifest verifier pass. All7 installed plugin - files' SHA256 equal `git show c43fbea:...`. Logs: `followon-{check,package,v22,installed}.log`. - Helper did not resolve the supplied private role baseline and compared README rather than - runtime advice; those two conclusions were **not** treated as verified. Primary's exact-path - recheck confirms all7 roles still match `/tmp/opencode/ksi-audit-remediation-20261007/ - role-baseline.json` and installed `lib/messages.js` equals c43fbea but differs from new source; - independent exact-path follow-up requested. First independent role/config preservation run - remains valid for the unchanged global settings, not a new finding from the helper's search. -- Final code revision `f015b23` (after `ab01b51`) independently rechecked by test-runner - `ses_eeb824753ffeGrTGHI2YM2d9iY`: `TMPDIR=/tmp/opencode KSI_TOOL_CHOICE_INTEGRATION=1 - npm run check`166/165/0/1, package/diff pass, clean main ahead2. Exact-path baseline7/7 roles - match; installed `lib/messages.js` equals c43fbea and differs from new source. This corrects - the helper's prior wrong-location search and README comparison. Logs: `closeout-{check, - package}.log` in the existing private evidence directory. Following commit records these - receipts only; no code/runtime change or push. Technical repair locally complete; slice - end-to-end human acceptance remains pending. Transient plugin pickup, real-provider long-run - reliability and rendered product quality remain unclaimed. Failed run/side effect retained. - -- RED: seven new notification-contract tests failed on missing advice/metadata (`/tmp/opencode/ksi-visual-ownership/red.log`); 12 existing tests passed. One compatibility assertion then caught the missing explicit blind-regeneration warning; restored it without changing the test. -- GREEN: messages + notifier suites 37/37 pass; full `npm run check` 140 total / 139 pass / 0 fail / 1 optional Compose skip. Logs `green.log`, `check.log` in the same directory. Nine config/native-role hashes unchanged. -- Reviewer `ses_ef2751567ffeYft7eI70xSrP29`: no code/policy conflicts found; the two flagged pending items were the not-yet-committed plan and pinned-copy adoption, not implementation defects. Seven scenario groups consumed the new guidance: small visual repair → design compact refinement; nonvisual shared-file bug → Build with sequential ownership; entry-invalid verified UI → design review; intermediate/report-only → no fabricated UI/refinement authority; stalled/rejected → quality unmet; authorized same-brand redesign → structural freedom with invariants; missing render/Plan/pause → respect blockers/native boundaries. This is constrained interpretation, not observed live agent execution or visual-quality validation. -- Source commit `24702e11e195fd3ece95aad568ef48eeb6f0f457` on main includes the plan; the existing pinned notifier copy was updated to that exact commit (7 files). Both reviewer pending items are resolved. No new workspace or native agent; unfamiliar worktrees retained. -- Independent test-runner `ses_ef272f946ffeAQrNnE4Bc7WCm3` at `24702e1`: `npm run check` exit0 (140 total / 139 pass / 0 fail / 1 optional Compose skip), `npm run check:package` exit0, messages/notifier targeted37/37 exit0, diff and committed whitespace checks exit0, clean Git. Logs `final-{check,package,targeted}.log` under `/tmp/opencode/ksi-visual-ownership`. -- Same test-runner: 9/9 native config/role hashes preserved; global policy differs from backup in exactly six single-bullet replacements (105 lines unchanged). `node scripts/install-design-notifier.mjs --verify` exit0/ok:true/commit24702e1/changed+missing+extra empty; independent source/installed `hashTree` comparison 7/7 equal. Actual installed `buildDelivery` imports: 55 assertions over valid/entry-invalid/failed/intermediate/paused cases, no failures. Logs `final-{runtime,hashcompare,runtime-contract}.log`. -- Limits: no product edits/design runs/other-session writes, no service restart/push/publication. Live notifier pickup is unverified; policy refreshed through the harness instruction channel. Unit/runtime-contract and scenario-interpretation checks do not prove agent obedience or visual quality. Real design improvement must be demonstrated by the product session's rendered review/refinement and changed-flow evidence. Human product acceptance remains rejected/unproven, not silently closed. diff --git a/docs/superpowers/plans/2026-10-07-beta8-release.md b/docs/superpowers/plans/2026-10-07-beta8-release.md deleted file mode 100644 index ab34dd2..0000000 --- a/docs/superpowers/plans/2026-10-07-beta8-release.md +++ /dev/null @@ -1,289 +0,0 @@ -# beta.8 release — authorized delivery record - -## Authority, binding and boundaries - -- User requested "푸시배포해봅시다" and explicitly chose `next + latest` after disclosure - that GitHub main includes all five previously local commits, not only Developer guidance. -- Target: existing checkout/main, baseline `4ecebb0`, clean/ahead5; origin is - `https://github.com/khakisketch/KSI-Opencode-Harness.git`. Fetch confirmed0 behind/5 ahead. -- Registry: public `ksi-opencode-harness`, new immutable `0.5.0-beta.8`; registry lists - versions through beta.7 and both channels beta.7. Never reuse or unpublish a version. -- Follow `docs/releasing.md`: exact-version pins, independent checks/isolated metadata, - reviewed tarball, main push, publish exact tarball to next, public install verification, - promote latest, verify both channels, release tag/GitHub release. -- One writer/current checkout; retain four unrelated worktrees. Build handles mechanical - release pins/delivery rather than inventing an implementation helper task. No new product - logic/visual work, model/permissions/native prompts, global roles, notifier installation, - shared service/container restart, goal or new orchestration. -- npm whoami returned401 (expired login); standard web approval required, not an auth bypass. - Activation details/auth output stay out of tracked records. Private evidence root: - `/tmp/opencode/ksi-beta8-release-20261007`. - -## Release steps - -- [x] Confirm source target, unused version and explicit next/latest authority. -- [x] Update manifest/package checks and README/INSTALL pins; release prepared as `c5ee02a`. -- [x] Independent full/package/preflight, isolated native metadata and inventory verification. -- [x] Push authorized main; verify remote/CI separately from publication. -- [x] Publish exact verified tarball, public preview/apply/repeat and byte/integrity verification. -- [x] Tag release commit and publish GitHub release after registry verification. -- [x] Promote latest and verify default beta.8 consumer — completed after the explicit - user-requested resumption and successful standard approval; historical blocker below. -- [x] Record actual achieved/blocked delivery evidence; reconcile product state and report. - -## Evidence boundary - -beta.8 distributes the updated installer/public guidance and walkthrough/assets. Manual -notifier scripts/plugin and private global operating policy remain outside the npm package; -source push does not install notifier advice or update other users' policies automatically. -No claim of human acceptance, improved real-development quality or live-provider reliability. -Verification receipts will be added after observed execution; npm/browser identity approvals -must remain standard user approvals. Publication failures are partial delivery, not rollback. - -## Preparation evidence - -- Independent Test Runner `ses_eeb122a3fffeCA2YiTJzrk4BTw` checked baseline4ecebb0 - plus exactly five mechanical version-pin files and this ledger. Full check exit0: - 166 total/165 pass/0 fail/1 pre-existing opt-in Compose skip; package check exit0. -- Existing isolated native V2 fixture exit0 on2.0.24: effective agents/model/steps/native - builtins preserved, no retired skills or commands installed,23 permission decisions pass, - zero provider requests issued by verifier. Child egress was not monitored. -- Dry inventory:23 files,453521 packed bytes/612112 unpacked bytes; public walkthrough - and four assets included. No private plans/state/tests/root deployment assets/notifier/ - credentials. Public docs/integrations/opendesign.md intentionally included. -- Seven README/INSTALL pins match manifest beta.8. `git diff --check` passes; - 11 protected config/role/source hashes preserved, package.json differs only by version. -- Logs under private evidence root: `pre-{check,package,isolated}.log`, `inventory.json`. - Clean-tree preflight and exact tarball/public-consumer checks remain subsequent gates. - -## Release execution and recovered blockers - -- Test Runner same session verified clean committed `c5ee02a` via release:check (7pins, - full/package pass) and retained exact23-file tarball453521bytes. SHA256: - `526392348f318d31e16eaabd50385b73ece5a2b5dd5ac5790519ac4485664a39`. - Pack SRI/SHA1/per-file SHA256 in private `pack.json` and `pack-hashes.json`. -- Authorized main push succeeded `d2020f4..c5ee02a`, remote SHA confirmed. Standard npm - login completed; first publish failed EOTP without writing beta.8 (registry404 confirmed). - Use the same verified tarball after CI recovery with standard interactive approval; - never log or request an OTP/token in tracked records or bypass approval. -- Remote CI37579001582 failed all6jobs:22 common fixture failures from hardcoded - `/tmp/opencode` absent on GitHub Ubuntu/macOS/Windows; Windows25 failures included - two POSIX-only expected-path assertions and URL.pathname used as executable argv. - Local host had that temp directory, masking this portability issue. Original failure - log retained privately; no passing-CI claim and npm publication held for repair. -- Developer `ses_eeb0d6e88ffepHCYVGzhelKeY0` owned8test-fixture files sequentially in - current checkout; Primary did not concurrently edit them. Minimal os.tmpdir/path.join - and fileURLToPath repairs, native strict expectations retained. Disposable absent-parent - RED reproduced ENOENT; self-check standard/fresh TMPDIR166/165/0/1 and package pass. - Remaining two Windows assertion files were explicitly added to scope by Primary; - no production/security/runtime/model/auth changes or installation. -- Test Runner same release session independently verified actual8-file diff, both TMPDIR - modes166/165/0/1 and package pass; original pack paths,size,SHA1/SRI byte-identical, - retainedtgz SHA256 unchanged.11protected sources/configs unchanged, manifest version-only. -- Reviewer `ses_eeb093b8cffeA808dAHld8uiQZ` confirmed fixture-only changes and retained - strict XDG/home/credential/security checks. Missing remote matrix is a pending gate, - not a code defect or satisfied by Linux. Its portable symlink-target concern was fixed - by Developer: actual fixture-owned existing state directory outside home replaces `/tmp`, - exact no-side-effect/rollback/refusal checks unchanged. Targeted8/8 and full165/0/1 pass. -- Deferred nonblocking review notes: mixed os import style and synthetic POSIX-looking - inputs in composition tests do not establish UNC/real-Windows-drive coverage. No broad - platform audit or security-check skipping added. Actual6job matrix still required. -- Post-symlink independent verification and repaired-source remote CI are next; no publish - until observed CI success. Tests/ledgers are excluded from package, so source recovery - may map the same verified immutable tarball to a later release commit if parity holds. -- Final post-symlink Test Runner same session:166 total/165 pass/0 fail/1 original - Compose skip, package/diff pass, actual escape-target/security assertion diff inspected; - dry pack remains exact23files/453521bytes/same SRI/SHA1. Logs - `final-portability-{check,package}.log`. Commit/push this tested source for remote matrix. - -### Canonical-path and Windows containment recovery - -- Test-only repair committed/pushed `2889d97`; same Test Runner independently checked - exact committed clean release:check, isolated metadata23decisions and166/165/0/1. -- CI37579994057: Ubuntu20/22 pass; macOS5fail/job, Windows6fail/job. Five failures are - fixture expectations using temp-path aliases instead of canonical realpath (/var versus - /private/var and RUNNER~1 versus runneradmin). The sixth Windows failure is a real - source-only status-report leak: POSIX-only `../` check failed to reject `..\outside`. - Never change the expected private-content refusal to bless that leak. -- Primary extended Developer ownership narrowly to `scripts/work-status.mjs` and its - regression, plus the two existing fixture bases. Canonicalize fixture realpath before - deriving expectations; segment-aware native-separator outside-root guard preserves - traversal/cross-drive rejection and existing actual symlink secrecy assertion. -- Developer reproduced5alias failures on a symlinked TMPDIR and Windows relative escape - against the old guard. New actual helper test (no source-string/mock test) covers slash - versus backslash splitting; actual drive/UNC behavior still requires Windows CI. -- Independent reviewer same session: no blocking code/security defect; synthetic-separator - test does not prove Windows isAbsolute semantics. Early worker test-wiring failure was - fixed by a direct helper import, not by widening report exports. Stale early log retained, - do not cite it as green. Optional extra empty/..foo/drive cases deferred, no broad audit. -- Independent Test Runner same session: security-check167 total/166 pass/0 fail/1 original - Compose skip, package/diff pass; own symlink-TMPDIR targeted18/18pass,0skip. Packet dry - inventory and original tgz/SRI remain byte-identical (scripts/tests/ledger not packaged). - Fresh evidence: `security-{check,package,alias}.log`. Next: push tested source and verify - actual6job matrix before npm publishing. No auth/install/runtime or design changes. - -### Windows package-verifier execution recovery - -- Canonical/security repair committed/pushed `f711e20`; exact clean release preflight - passes and all23packaged source bytes match retainedtarball hashes. -- CI37580579130: all6sourcechecks pass, including real Windows external-ledger refusal - and macOS canonical-path tests. Ubuntu/macOS packagechecks pass; Windows2packagejobs - fail only `spawn npm ENOENT`. This is a separate source-only verifier execution defect. -- Developer same session changed only check-package.mjs/new check-package.test.mjs: - invoke absolute validated npm_execpath through Node without shell/PATH lookup; Windows - check .cmd-shim and actualbinJS presence then run JS through Node, not claim .cmd execution. - POSIX standalone/executable check retained. Four real argv helper tests, no mocks/skips. -- Primary diff inspection caught a dropped package-install-no-global-config assertion in - the initial body move; Developer restored it and retracted the earlier byte-identical - flow claim. Whole-body audit/reviewer/Test Runner confirm all34assert statements retained. - No script was published or a user's configuration touched by the provisional verifier. -- Independent reviewer same session: no code/security blocker, full strict offline/minimal - env/no-config/preview/three-role/idempotence/conflict checks intact; remote Windows package - execution remains a real pending gate, not a Linux inference. -- Test Runner same session:171 total/170pass/0fail/1 originalCompose skip, package/diff pass, - safe23file drypack/SRI and retainedtgz byte-identical. Logs `npm-portability-{check,package}.log`. -- Source scripts/tests excluded from published installer. Direct verifier execution without - npm_execpath now fails descriptively before fixture creation; documented npmrun entry - supplies it. Do not install tools/use shell:true or weaken platform gates to recover. - -## Final source gate and publication state - -- Final verified source/release target `178b2e9f6c91250bab43f2d3a23da1a50c76ae9c` - pushed and clean. Same independent Test Runner: clean release:check pass,7pins, - exact suite171/170/0/1, package pass, all23packaged source bytes equal retainedTGZ. - Receipts `publish-{preflight,check}.log`. No tarball regeneration. -- Actual CI37581287013 completed success: Ubuntu/macOS/Windows × Node20/22, - all6jobs including source and packed-package verification success. Earlier failed CI - receipts preserved above; final real Windows security/package evidence is now available. -- The original noninteractive publish returned EOTP and beta.8 registry404. A subsequent - standard interactive publish of the exact original tarball is awaiting user approval; - do not replay it while pending. Separate login success does not mean publish success. - No publication/channel/tag/consumer-success claim before registry receipts. - -## Public delivery receipts - -- Standard interactive publish succeeded for immutable `0.5.0-beta.8` after user browser - approval. Immediate registry404 was processing latency, not a reason to republish. - Read-only bounded readiness check observed version on attempt3 (~60seconds) at06:30:08Z. -- Test Runner same session independently executed reviewed public-consumer verification: - 18/18checks pass, registry/SRI and downloadedtarball match original, all23installedfiles - byte-identical, Linux standalone preview no-write → own3-roleapply → repeatchanges[]. - Minimal credential-free disposable HOME/cache/prefix; target contains only agents, - no operating policy/model/runtime config. Only owned disposable consumer removed. - Receipts `readiness.log`, `readiness-metadata.json`, `public-consumer.log`; - no Windows .cmd or productquality claim. -- At public check, next=beta.8/latest=beta.7. Authorized latest promotion is currently - waiting for a separate standard browser approval; do not replay while pending. -- Tag `v0.5.0-beta.8` created/pushed at source `178b2e9`; GitHub prerelease created: - https://github.com/khakisketch/KSI-Opencode-Harness/releases/tag/v0.5.0-beta.8. - Final channel/defaultconsumer verification and evidence-only closeout push remain. -- First latest-promotion approval failed404 at npm's authentication-completion endpoint, - not the package endpoint. Fresh public queries still return beta.8/version and - next=beta.8/latest=beta.7. No package disappearance, unpublish or republish inferred. - Original command is terminal; a cause-specific new standard dist-tag approval attempt - was launched (not a replay of an in-flight command). Private receipts - `promote-latest.log` and `promote-latest-retry.log`; final channel state still pending. -- Second standard latest attempt failed identically; stopped issuing identical retries. - Source diagnosis on installed npm11.17 confirms `dist-tag.add` passes PUT/body opts - into `otplease`, then `npm-profile.webAuthOpener` forwards them unchanged to the done - fetch. Authentication completion should use GET; inherited PUT is a request-method - correctness defect. Source inspection alone does not prove it caused the terminal404; - later HTTP-state evidence below corrects the causal attribution. Public version exists. -- User explicitly approved a single-command in-memory compatibility shim, no installation/ - config/2FA change. It guards the existing HTTPS npm-registry done endpoint, removes - only method/body from cloned poll opts and forces GET. Original browser opener and - standard OTP return/retry/write remain native; original dist-tag PUT/body unchanged. -- Actual installed npm-profile loopback HTTP reproduction RED: PUT+versionbody observed - instead of GET/no-body. Independent Test Runner same session GREEN2/2, original write - object unchanged and endpoint guard enforced; installed npm4/4source hashes unchanged, - retainedtgz unchanged. Independent reviewer same session: no blocking scoped security - finding. Full wrapper composition was inspected, not a mocked end-to-end auth claim. -- Test Runner disclosed a supplementary mock attempt that did not intercept registry-fetch - and issued one unauthenticated read-only GET to the standard registry done endpoint404. - No token, challenge, registry write or credential/config change; stray owned scratch - removed, not repeated. Preserve this limitation rather than claim a fully isolated - supplementary test. Actual live standard approval is still required. -- At that stage, the authorized corrected latest command ran with a private preload in one - Node process; no NODE_OPTIONS/global hook/vendor writes. Its completion and registry - channel state were pending (subsequent terminal results below). Private artifacts auth-get-{preload,test}.cjs, red/green logs, - npm-unchanged-hashes.json and promote-latest-corrected.log remain outside the package. -- Corrected command terminated with GET404 too. Targeted debug-log analysis emitted ONLY - HTTP method/status/path/count (no queries, auth identifiers, headers/bodies/tokens): - prior standard attempt PUT20295times→PUT404; corrected attempt GET20295times→GET404. - Thus both were accepted as pending polls and ended without a successful approval200. - Do not call method correction a proven complete fix for404 or blame package absence. -- User-facing causal explanation corrected. New single-process corrected request started - only after prior was terminal; fresh link immediately supplied with instructions to - finish browser approval, not merely open the link. Do not issue another request while - this one is pending. Installednpm/config/2FA still unchanged; latest verification pending. - -## Historical partial-delivery closeout (superseded by successful resumption below) - -- Fresh corrected approval also terminated GET404. All launched promotion commands are - terminal; stop creating approval links. No successful tag write and no downgrade/ - unpublish/republish to disguise the blocked latest stage. -- Independent final Test Runner `ses_eeb122a3fffeCA2YiTJzrk4BTw`: registry version/bin/ - originalSRI confirmed, next=beta.8/latest=beta.7, exact publicconsumer18/18pass with - all23installedbytes/preview-no-write/own3rolesapply/idempotence/no-policy writes. - Full171total/170pass/0fail/1originalCompose skip, package/diff pass;11protected hashes - unchanged and manifestversiononly, installednpm4/4sources unchanged, tgz unchanged. - Logs `closeout-{registry.json,public.log,check.log,package.log}` in private evidence root. -- Read-only remote confirmation: source/tag178b2e9, publicGitHubprerelease exists, - CI37581287013 all6jobs pass. Windows CI checks .cmd shim presence and Node-driven - binJS behavior, not actual native .cmd execution; do not overstate the runner's wording. -- Achieved: source push, immutable npm beta.8 to next, exact public install/bytes, tag and - GitHub release. NOT achieved: latest promotion/default-beta.8 install. Plain latest - still resolves beta.7; use `@next` or `@0.5.0-beta.8` for the delivered version. -- Blocker owner: a working standard npm account/browser second-factor completion is - required for the authorized tag change. Manual completion in a working authenticated - user terminal or npm-side resolution can unblock it; never request credentials/OTP in - chat, disable2FA, install another CLI, infer a successful approval or keep retrying. -- Current source/records technically verified, but authorized external delivery remains - partial. No live notifier/global role installation or shared restart. Human workflow/ - real-development/design quality acceptance remains separate. Receipt-only closeout - commit/push and checkpoint reconciliation do not alter the published immutable payload. -- Final receipt-only records independently checked by the same Test Runner: - `final-records-{check,package}.log`,171/170/0/1, package/diff pass; actual registry - channels/tag/release match partial-status wording, published tag range1–8 verified. - No new auth requests or defaultbeta8claim. Optional tag-annotation uniformity is - nonblocking; beta.8's lightweight tag names the verified revision and was not rewritten. - -## User-requested latest resumption - -- User explicitly requested "진행해보자" after partial closeout. This reopens only the - remaining already-authorized latest stage, not a standing automatic retry loop or - new release/version/product scope. -- Rechecked clean main `6c776cf`, npm11.17/login identity valid, registry next=beta.8/ - latest=beta.7 and installednpm4/4source hashes unchanged. Reuse the reviewed explicit - single-process GET-only poll correction; no installednpm/config/2FA or payload writes. -- Launched one fresh standard approval request; activation link immediately sent to - user with instructions to complete npm approval. Do not replay while pending. Log: - private `promote-latest-user-resume.log`. Completion/channel/default-consumer evidence - was pending at launch; the actual successful completion is recorded below. - -## Completed channel delivery - -- Resumed promotion command returned `+latest: ksi-opencode-harness@0.5.0-beta.8`. - Read-only registry confirmation: BOTH next/latest=beta.8. No republish/unpublish, - changed package bytes or permanent npm/auth/config/2FA modifications. -- Independent Test Runner `ses_eeb122a3fffeCA2YiTJzrk4BTw` verified unversioned/default - `npm view`/`pack`/`install` in a fresh credential-free disposable HOME/cache/prefix: - 19/19checks pass, default version beta.8, originalSRI/shasum, downloaded payload - and all23installedfiles byte-identical, preview no-write→exactly3rolesapply→idempotent - repeatchanges[], no AGENTS/model/runtime config writes or live target overwrite. -- Registry `final-channels.json` and `default-consumer.log` retained privately. Tag - v0.5.0-beta.8 remains source178b2e9; GitHub prerelease persists. Installednpm4/4sources - unchanged, protected11configs/roles unchanged, manifest version-only; retainedtgz - SHA256 unchanged. The single-process auth shim ended and was not used for consumer - verification, installed as a persistent hook or claimed to fix npm globally. -- Full authorized external channel delivery is now achieved. Earlier partial/failure - evidence remains historical, not a current blocker. Source/render/human-quality - acceptance and actual Windows native .cmd execution still are not inferred from delivery. -- Independent final record check by the same Test Runner: `TMPDIR=/tmp/opencode - KSI_TOOL_CHOICE_INTEGRATION=1 npm run check`171total/170pass/0fail/1optionalCompose - skip; `TMPDIR=/tmp/opencode npm run check:package` and `git diff --check` pass. - Logs `completed-records-{check,package}.log`; actual channels/tag/release/default19/19 - receipt match the completed-delivery wording. No source or packaged file changed. -- Commit/push these verified receipt-only records without altering the release tag or - immutable23-file package; final commit/remote-CI receipt stays in the checkpoint/private - logs to avoid a recursive evidence-only commit loop. No new product/design/runtime scope. diff --git a/docs/superpowers/plans/2026-10-07-effective-developer-delegation.md b/docs/superpowers/plans/2026-10-07-effective-developer-delegation.md deleted file mode 100644 index c521f5c..0000000 --- a/docs/superpowers/plans/2026-10-07-effective-developer-delegation.md +++ /dev/null @@ -1,119 +0,0 @@ -# Effective Developer delegation — approved execution record - -## Objective and authority - -Improve practical Developer use without mandatory delegation, a Developer-first rule, -usage quotas, a new orchestrator or a Build edit ban. Quality and end-to-end completion -efficiency are the objective, not helper count. User approved the four-step in-chat plan -with "그래 개선해줘" after rejecting forced delegation. - -Build retains direct implementation, task judgment, integration, recovery, verification -coordination, Git and reporting. Visual work remains design-owned, even when small. -Developer can investigate and implement within a compact contract and run author tests; -those tests do not replace independent final checks on the integrated revision. - -## Binding and boundaries - -- Repository/target: current checkout `main`, baseline `ec2bef0`, initially clean/ahead4. -- One Primary writer; four pre-existing worktrees retained. No new goal or worktree. -- Scope: README summary/link; execution guide; autonomous/project guidance examples; - narrowly aligned current-user `~/.config/opencode/AGENTS.md`; this ledger/product state. -- No role definitions, native prompts, models/variants/steps/permissions, installer/runtime, - assets, product source, dependency installation, restart, push or npm release changes. -- Private backup and12 protected config/role/source hashes: - `/tmp/opencode/ksi-effective-delegation-20261007` (directory700, backup/manifest600). -- Ruling: execute this prose/policy task inline in the existing checkout. Separate scratch - ledgers/worktrees or a worker per paragraph would add the exact handoff overhead being - avoided. Follow the approved in-chat plan, native boundaries and repository continuity. -- Verification is interpretation review plus existing trusted source/package checks and - exact policy/preservation comparison, not a synthetic string test or live product pilot. - -## Steps and expected evidence - -- [x] Clarify selection: use Developer for useful bounded nonvisual implementation, - not just overflow assistance; keep direct work for tightly coupled/context-heavy or - clear integration repairs; do not split merely to delegate or pre-implement the task. -- [x] Define compact handoff/return: goal, paths/current base/decisions/invariants, - scope/exclusions and observable acceptance; changed files, actual commands/results, - open/unverified work. Worker chooses reversible implementation details within scope. -- [x] Retain Build integration/recovery: inspect actual diff/state/contracts, route defects, - preserve partial work, diagnose cause before continuation, recheck changed integrated code. -- [x] Align examples/README/current-user policy without settings or role changes. -- [x] Independent reviewer checks the cases below; test-runner executes full/package checks, - links,12 preservation hashes and exact three global-policy bullet additions. -- [x] Reconcile product record and prepare task-owned local closeout; commit identity is - recorded by Git, committed-state recheck receipts by the same Test Runner session below - and private `committed-{check,package}.log`/checkpoint, not a self-referential ledger SHA. - -## Interpretation review focus - -1. Bounded API/download repair: Developer can own investigation/implementation/self-tests; - no mandatory Plan, busy-Build prerequisite or user-authored task form. -2. Tightly coupled judgment or trivial integration: Build may work directly; no quota or - forced task splitting, and "integration" is not permission to bypass visual ownership. -3. Worker success/partial result: inspect actual code and independent integrated checks, - not prose/toast/test subset as completion; later edits need affected verification. -4. Worker interruption/repeated mismatch: preserve work, concrete diagnosis/feedback and - adapt scope/path; no blind respawn/replay, implicit installs/model changes or endless loop. -5. Shared files/parallel work: sequential handoff by default; concurrent writers require - separate workspaces and disjoint ownership, not a second simultaneous Primary. -6. Small visual repair or unavailable design: remains design-owned; no Developer/Build - visual fallback, new direction, external delivery or implicit goal authorization. - -## Evidence and status - -Guidance implemented and independently reviewed/checked locally. Prose -checks can prove consistency and package safety, not developer quality or long-run reliability. -Observe real outcomes only inside later authorized product work; no new experiment here. - -- Pre-flight: selection → handoff → integration/recovery feed one operating contract; - README/project summary and autonomous/global bullets agree with execution detail. No - conflicting shared interfaces identified; existing Developer role already supports - implementation investigation and author tests, so no role change is needed. -- Ruling: no artificial RED→GREEN prose-string test — this is approved documentation/ - operating-guidance work, not production behavior code. Interpretation review, exact - policy diff and trusted suite/package checks test the relevant evidence; cost if wrong: - real-development friction must be discovered during authorized use, not hidden by a - synthetic “quality” test. -- Scope implementation: four guidance files; global policy adds exactly three bullets - matching the autonomous example. Existing role/config/source bytes are verification - targets, not edit targets. The V2 instructions guide plus Context7 confirms global/ - upward ambient guidance refreshes before the next model request; this session actually - received the three-bullet update. No service restart or manual per-session copying. -- Primary preliminary `git diff --check` passed. - -### Independent review and verification receipts - -- Reviewer `ses_eeb1ce11effetFczCcrIjnVl7y`: all six interpretation cases covered; - no Critical finding or concrete implementation defect. Its Important item was the - pending independent integrated check, now satisfied by the Test Runner receipt below. - Exact committed-state verification follows the local commit; review advice is not approval. -- Test Runner `ses_eeb1ce11dffeEvcdowPiiBuYOi`: baseline `ec2bef0` plus five modified - doc/state files and this untracked ledger. `TMPDIR=/tmp/opencode - KSI_TOOL_CHOICE_INTEGRATION=1 npm run check` exit0,166 total/165 pass/0 fail/1 skip; - `TMPDIR=/tmp/opencode npm run check:package` exit0; `git diff --check` exit0. - Logs: `/tmp/opencode/ksi-effective-delegation-20261007/final-{check,package}.log`. -- Optional Compose test skipped because `OD_REMOTE_DOCKER_SMOKE` was not enabled; - requires Docker Compose and is outside the approved prose task, not a failed check. -- Link verification:50 total links/34 local file links in four guidance files,0 broken; - new execution heading fragment resolves from README and project example. -- Preservation:12/12 config/installed roles/role sources/installer/package hashes match. - Global policy reconstruction confirms exactly three added bullets,0 removals, all other - bytes unchanged and each addition byte-identical to autonomous example lines8–10. -- Primary inspected actual guidance diff and log tails. Runner's English “behind4” label - was a translation error; `git rev-list --left-right --count origin/main...HEAD` is0/4: - main was ahead4 before this task, not behind. No fetch/push or branch alteration. -- Final: minor (deferred): explicitly repeat optional Plan and goal opt-in inside the new - section. Both are already preserved in their authoritative sections; duplication is - optional polish, not a missing permission gate or a blocking defect. -- Final: minor (deferred): repeat concurrent-worktree requirements in the new shared-file - sentence; the immediately following paragraph already specifies disjoint ownership and - separate worktrees. Product-state's spacing typo is corrected during receipt update. -- Reviewer set aside real-development efficiency/reliability, human readability acceptance, - visual quality, refresh internals, goal/worktree state, publication and synthetic RED→GREEN - prose tests. These are the approved evidence boundaries, not dropped requirements: - no new pilot/product/design/goal/delivery work authorized; actual ambient update and - worktree inventory were observed by Primary, not inferred from the review. No live goal - was created/resumed. Future real-use claims still require their own evidence. -- Finish: current checkout/branch is already the approved local target; no merge/menu, - new workspace, forced cleanup or push. Four pre-existing worktrees retained. diff --git a/docs/superpowers/plans/2026-10-07-playwright-agents-adoption.md b/docs/superpowers/plans/2026-10-07-playwright-agents-adoption.md deleted file mode 100644 index 7c78da5..0000000 --- a/docs/superpowers/plans/2026-10-07-playwright-agents-adoption.md +++ /dev/null @@ -1,123 +0,0 @@ -# Playwright Agents — proposed improvement and historical pilot - -## Authority and current status - -Status: proposed, not approved for implementation or product/global adoption. - -User requested a harness improvement plan after one approved throwaway Agents -trial. The later "그렇게 개선해줘봐" approves recording/handoff hygiene, not -all items below. Saving this proposal does not approve configuration, dependency -installation, a product trial, CI changes or release. - -## Proposed outcome and boundaries - -Keep native Build/Plan and three optional KSI roles. Add project-local optional -E2E assistance only where durable tests are useful. Ordinary browser checks keep -the current CLI/agent-browser/MCP path; no mandatory planner → generator → healer -chain, delegation quota or second orchestrator. - -Build owns task judgment, integration/Git; helpers plan/write/repair tests; -Test Runner independently verifies integrated results. Product defects return -to Build/Developer or design by ownership. Healer cannot redefine approved -behavior or substitute visual UI repairs. - -## Proposed sequence (requires scope/design approval before implementation) - -1. **Usage/ownership guidance:** distinguish routine interaction from durable E2E - planning/generation/repair. Skip unnecessary stages, not assertions. -2. **Project-local adapters:** vendor source/version recorded, official originals - preserved; existing fixtures/config reused, model/variant/steps/Code Mode kept. - Avoid blanket generic Playwright MCP denial; forbid automatic skip/fixme, - weaker expectations and product/config/credential edits by test helpers. - Default installer still writes only Developer/Test Runner/Reviewer. -3. **Readiness/access:** actual root, stable CLI resolution, seed, safe test state, - URL, launch/sandbox, native permissions and MCP capabilities verified. No - automatic installs, customer data or sandbox bypass. Internal native read/edit - paths are Location-relative; consider diagnostic reads explicitly. MCP writes - and code execution need separate capability verification: read-only labels and - native edit rules do not prove isolation/safe execution. -4. **Regressions:** wrong locators heal narrowly; real save/API defects stay failing - and are reported; access/auth/server issues classified without bypass; scope - violations checked against actual boundaries. Original assertions, independent - final checks and default-install/package preservation verified. -5. **Separately authorized product trial:** evaluate maintainability, rework, - end-to-end time and available usage evidence. No cost-saving claims when usage - is unavailable, implicit EVENTOUCH work or every-project rollout. - -Suggested first implementation slice: items 1–4 after approval. Product selection, -trial, current-user global adoption and GitHub/npm/external delivery are separate -decisions. This is a scope proposal, not an approved written implementation plan -or replacement for applicable design/plan gates. - -## Historical capability trial — 2026-10-07 - -User approved one synthetic trial with "한 번 해볼래?". Product/global Agents -settings remained untouched. Throwaway workspace used Playwright Test 1.63.0, -system Chrome, headless isolated contexts, `chromiumSandbox:true`, one worker, -zero retries and a real local HTTP form with disk-backed persistence. Vendor -prompts had stage/project scope and no-skip/no-assertion-weakening supplements; -this was not an unmodified stock trial. - -Required value: `agents-pilot-20261007`. Fill "Probe value", click "Save", see -`Saved: agents-pilot-20261007`, reload and retain exact status/input value. - -| Stage | Actual observation | Child session | -| --- | --- | --- | -| First planner | Live seed loaded, required reads denied; stopped before plan | `ses_ee9b747e3ffeZUbg79GhWurcIs` | -| Corrected planner | Live save/reload inspected; one plan saved | `ses_ee9b63cfaffeiD8Vthu6k8vot0` | -| Generator | Live steps, generator log and one fixture-based test | `ses_ee9b4ee4dffeQQQ3i63xkpJAS7` | -| Baseline/fault/final verification | Independent stable CLI execution | `ses_ee9b2a11dffexnnl10EVBuO21A` | -| Healer | test_run reproduced; test_debug/live snapshot/locator; one-line repair | `ses_ee9b0ab98ffeBfqBScEpTzenw6` | - -Initial read failure: absolute allow patterns did not match internal relative -resources. Exact local reads and stage-relative edits fixed it without global -permission changes. Healer's optional `test-results/.last-run.json` read remained -denied; MCP debugging and independent final checks still worked. - -### Independent commands and revision-bound results - -Workspace: `/tmp/opencode/ksi-pw-agents-pilot-20261007-R57NPy` (not Git). -Stable CLI: `/tmp/opencode/ksi-browser-latest-20261007/probe/node_modules/@playwright/test/cli.js`. -These are historical evidence locations, not portable product setup or authority -to execute again. Owned fixture server stopped, session returned to harness, -no test page remained active and unrelated browsers were untouched. - -```text -node test -c playwright.config.ts tests/persistence.spec.ts --reporter=list - baseline: exit 0, 1 passed, 0 failed/skipped - controlled wrong locator: exit 1, 1 failed, 0 skipped - repaired revision: exit 0, 1 passed, 0 failed/skipped -node test -c playwright.config.ts --reporter=list - final seed + persistence suite: exit 0, 2 passed, 0 failed/skipped -``` - -Fault: exact button name `Save` → `Save pilot value` at test line 13. Failure: -missing button at `expect(save).toBeVisible()` line 17, 5-second assertion timeout; -actual page still showed "Save". Healer restored only that locator. Final file was -byte-identical to green baseline, SHA256: -`bf4a766e1021cf7d07b0e29260c98c76904d46fabe542a0f60af0949060f554f`. -Faulty SHA256: `5240de5f550e1645006e4034dc78130cc62178bccded02c0b8ae156f18e00e07`. -Exact save/value/reload assertions remained; skip/fixme/only/test.fail absent. -Disk state and `GET /state` retained the required value after persistence. -Eight protected fixture/seed/config/prompt files matched their recorded hashes. - -Optional temporary raw evidence: `PILOT.md`, -`evidence/{baseline,fault,final,final-suite}.log`, -`evidence/{generated-baseline.spec.txt,faulty.spec.txt,final-summary.json}` under -that workspace. No sensitive logs/config backups are copied into Git. - -### Evidence limits - -This proves one real planner/generator/healer workflow on synthetic persistence -with selected native model/budgets. Four specialist launches (one blocked, three -successful) plus one Test Runner child continued across checks. Total tokens, -cost and elapsed time were not measured; test durations do not prove efficiency. - -Not proven: complex auth/API/data flows, security isolation, visual quality, -every-host portability, real-development savings, human acceptance or adoption. -Native edit allowlists/task prompts do not constrain every MCP filesystem/code -execution effect and are not an OS sandbox. - -Earlier browser-tools update was separate: configured generic MCP 0.0.80 → 0.0.83; -CLI 0.1.22 and agent-browser 0.38.2 already latest at that check. These are dated -observations, not a standing guarantee or automatic-update policy. diff --git a/docs/superpowers/plans/2026-10-07-recorded-handoffs.md b/docs/superpowers/plans/2026-10-07-recorded-handoffs.md deleted file mode 100644 index 0e0f43f..0000000 --- a/docs/superpowers/plans/2026-10-07-recorded-handoffs.md +++ /dev/null @@ -1,1579 +0,0 @@ -# Recorded plans and document-bound handoffs — approved work record - -## Authority and status - -Status: approved guidance implemented, independently verified and committed locally. -Human workflow acceptance remains pending; no external delivery authorized. - -User approved the four-part in-chat change with "그렇게 개선해줘봐": record -substantive plans with explicit approval state, hand off relevant documents, -retain durable summaries of temporary evidence, and avoid conflicting copies. -This approves recording/handoff hygiene, not implementation of the separate -Playwright Agents adoption proposal. - -## Binding and scope - -- Repository/target: existing canonical checkout, `main`, base - `f6df31a7c85be7a25b0749daea2b016e3643e704`; initially clean/synced. -- Primary writes guidance/records inline; no new workspace/concurrent writer. - Four pre-existing unrelated worktrees remain untouched. -- Changes: README, execution procedure, optional project/autonomous guidance, - this record, Playwright proposal/pilot summary, product-state, ignored checkpoint, - and two matching current-user global operating bullets. -- No native role/prompt/model/variant/step/permission/Code Mode, installer, - upstream skill, dependency, product, service, goal or notifier change. - No push/publication/deployment; beta.8 delivery authority is not reused. -- Private global-policy backup and 14 preservation hashes: - `/tmp/opencode/ksi-recorded-handoffs-20261007-hpyilbo8`. - -## Acceptance and checklist - -- [x] Requested substantive multi-step plans are saved as proposals before - handoff/pause/closeout; simple questions/trivial edits need no full ceremony. -- [x] Proposal, approved scope, progress and verified outcome are distinguished; - recording a plan or passing a probe grants no execution/delivery authority. -- [x] Helpers receive relevant document paths/sections plus workspace/base, - scope/invariants and observable checks; return actual inputs read, conflicts, - changed files/results and limitations. Primary reconciles common records. -- [x] One detailed task record owns decisions/evidence; product-state and checkpoint - link to it. Sanitized commands/results/source identity/limitations survive loss - of temporary logs; raw sensitive output stays private. -- [x] Persist existing Playwright pilot conclusions and the unapproved adoption - proposal without installing or exercising agents again. -- [x] Independent interpretation review and Test Runner source/package, local - links, global-policy reconstruction and protected-file checks. -- [x] Commit verified task-owned files locally; reconcile checkpoint/product - state/ledger, keeping human acceptance and delivery separate. - -## Verification approach - -Bounded prose/operating-policy change, not production behavior code. No synthetic -string test is treated as proof of agent compliance. Review these cases against -actual guidance, then run existing trusted source/package checks: - -1. Multi-step plan is recorded as proposed; implementation stays unapproved. -2. Record/handoff approval does not approve the Playwright adoption proposal. -3. Small changes reuse an existing ledger; no extra spec, team, quota or approval gate. -4. Fresh helpers read supplied relevant sections; unavailable/conflicting inputs - are reported, not invented or silently overridden by code/tests. -5. Primary reconciles actual diffs/checks and common status; helpers do not race - on product-state/checkpoint/ledger. -6. Durable summaries remain useful without temporary logs, private/customer data - or unsupported cost/quality/security claims. -7. Resume follows linked records/current Git, not a stale release checkpoint or - the existence of a plan as execution authority. - -Checks: `TMPDIR=/tmp/opencode npm run check`, -`TMPDIR=/tmp/opencode npm run check:package`, `git diff --check`, new local -file/fragment links, 14 preservation hashes, global policy identical to backup -except the two approved bullets. No model/real-product/browser trial needed. - -## Decisions - -- Existing continuity documents, not a new memory service, hook or orchestrator. -- Stock roles/skills/config and default three-role installer stay unchanged. -- Document-bound handoffs are proportionate guidance, not automatic loading, - shared conversation memory, guaranteed compliance or an OS sandbox. -- Playwright adoption remains proposed; historical pilot is capability evidence - only. See [the proposal](2026-10-07-playwright-agents-adoption.md). - -## Results - -- Primary applied guidance to five repository documents and exactly two current-user - global operating bullets, matching the autonomous example. This session received - the ambient policy diff without restart; no automatic shared-chat claim is made. -- Historical pilot commands/results/source SHAs/limits are in the separate proposed - adoption record. No trial/config/Agents installation was replayed. -- Reviewer `ses_ee9900a2affe6bAQPPfGY3q5oT` read the actual diff/new records and - returned its input basis. All seven interpretation cases passed; no Critical or - Important finding. Compared retained pilot receipt SHAs/results with the summary. -- Minor trigger wording mismatch in project template verified and aligned with - consequential scope/design decisions. Stale pending checkpoint refreshed; private - preservation backup consumed by Test Runner, not copied into Git. -- Test Runner `ses_ee98d4085ffeVHucmopHi0o09z` read the approved ledger, actual - five-file diff/two new records/checkpoint, private preservation inputs and retained - historical pilot receipts. Checked base `f6df31a` plus seven pending task-owned - files; no code/config/role/installer changes. -- `TMPDIR=/tmp/opencode npm run check`: exit 0, 171 total, 169 passed, - 0 failed, 2 optional skips (Compose and opt-in tool-choice final-summary integration). - Neither was run or claimed passed. `TMPDIR=/tmp/opencode npm run check:package`: - exit 0, installer-only tarball/offline three-role install/retired-kit rejection pass. - `git diff --check .`: exit 0. Logs `final-{check,package,diff}.log` in the private - evidence directory above. -- 41/41 local file/fragment links resolve; 14/14 protected config/role/source/skill - hashes match. Global policy: exactly two added bullets, zero removed; stripping - them reconstructs the backup byte-identically, and both match the autonomous - example. Common records point to this active ledger and keep adoption proposed. -- Retained pilot raw logs/receipt confirm the summarized RED→GREEN/SHA/results; - no new browser/agent/product/provider trial. Primary inspected actual diff and - machine receipt/log counts. Detailed conclusions here survive temporary-file loss. -- Guidance compliance, real-development effectiveness and human workflow acceptance - remain unproven. Only current-user global guidance was adopted; source checkout - changes are not npm/GitHub delivery and do not update other users' installations. -- Source/guidance and records committed locally as `02b5f96` on `main`; seven - task-owned files only, index/whitespace diff inspected, checkpoint excluded. - No merge/workspace cleanup needed; four unrelated worktrees retained. Final - receipt-only closeout is identified by Git; final committed-state checks use the - same Test Runner and private `committed-*` receipts rather than a self-referential SHA. - -## Approved audit follow-up (2026-10-08) - -Status: technically verified and locally committed; human acceptance and real-use effectiveness pending - -### Authority, binding and decisions - -- User requested a read-only assessment, then approved the recommended bounded - repairs with "그래 진행해봐": fix two status-report defects with regressions, - align role boundary guidance/verification, then observe during authorized work. -- Same canonical `main`, base `23cbd882db52862fafbb1fd46aa7587df095cd89`, initially - clean/ahead 2. Primary implements inline; one writer, no new workspace. Four - unrelated worktrees remain untouched. Reuse this handoff record, not a new spec. -- Preserve APIs, displayed full status text, three-action limit, local-only delivery - semantics, native roles/permissions/models/variants/budgets and installer behavior. -- Read-only review is a behavioral contract, not a blanket permission/OS sandbox. - Keep existing permissions rather than silently imposing a restrictive shell/MCP - policy. Explicitly forbid implementation changes through alternate tools in role - guidance; Test Runner may create only expected task-owned check artifacts. -- Add native policy evaluation for Reviewer/Test Runner `edit`/`subagent` denials; - record shell/Code Mode/MCP decisions separately as observations, not safety proof. -- No global installation/config/policy edits, services/restarts, notifier delivery, - product work, goal, dependency, Agents adoption, push/publication or deployment. - Temporary isolated installer/native-server checks are verification, not adoption. - -### Basis and acceptance - -Audit at base: `scripts/work-status.mjs:77,88` dropped numbered `Next` items -(real checkpoint 2 → reported 0) and missed unfinished-ledger warnings for -`idle — explanation`. Independent reproduction: Test Runner -`ses_ee608c4e2ffeQDPuuCZRxkQuT4`; Reviewer -`ses_ee60cad59ffe6Ku1yFqGaXxV37` confirmed. Existing check passed -171 total / 169 pass / 0 fail / 2 optional skips despite both defects. -Live rule lists allowed shell/execute/design writes for both helpers; no writing -tool was invoked. Installed notifier matched its pinned manifest 7/7; its source -lag is intentional, not unauthorized drift. Raw audit evidence: -`/tmp/opencode/ksi-harness-audit-20261008-b1ceba48`. - -Acceptance: supported Markdown bullets/numbered lists retain order and at most -three next actions in JSON/text; descriptive idle statuses warn for genuinely -unfinished ledgers, not completed/active/unknown statuses. Guidance explains -what is denied versus inherited; native checks evaluate actual helper denials -without executing tools/providers. No claim of all-write isolation or effectiveness. - -### Work checklist - -- [x] Reconcile Git/checkpoint/approved scope; read actual parser, tests and role - guidance; current V2 permissions docs and Context7 corroborate ordered inheritance. -- [x] Write parser regressions and witness both failures before repair. -- [x] Repair list/status recognition, preserve existing read-only/report contracts; - pass focused and full source checks. -- [x] Clarify role boundary guidance; add/evaluate native helper permission evidence - with failing receipt-validation regressions before verifier changes. -- [x] Fresh independent review and integrated Test Runner source/package/native - checks; record revision, outputs and optional checks not run. -- [x] Locally commit task-owned changes and reconcile records/checkpoint; - human acceptance and external delivery remain separate. - -### Verification results - -- Primary RED: `TMPDIR=/tmp/opencode node --test test/work-status.test.mjs - test/verify-native-v2-isolated.test.mjs` exited 1: 21 total, 16 pass, 5 fail. - Numbered Next returned `[]`, mixed markers returned only the fourth item, - descriptive idle suppressed the warning; helper receipt checker was absent. -- Primary GREEN after minimal repairs/verifier receipt checks: same focused command - exited 0, 21/21. Full `TMPDIR=/tmp/opencode npm run check` exited 0: - 176 total / 174 pass / 0 fail / 2 optional skips. This is author feedback, not - final independent verification. No assertion weakening or skip added. -- Native helper evaluation samples one implementation edit path and one `general` - delegation target per role, each with a separate decision. Do not infer all - resources/tools are isolated or combine multiple resources into an all-denied claim. -- Private evidence root `/tmp/opencode/ksi-status-repairs-20261008-8tklxmzi`: - `author-check.log`, pre-check 17 protected hashes (current-user guidance/config/ - roles/pinned notifier plus repository permission rules/installer/example). -- Final planned checks: `TMPDIR=/tmp/opencode npm run check`, - `TMPDIR=/tmp/opencode npm run check:package`, `TMPDIR=/tmp/opencode node - scripts/verify-native-v2-isolated.mjs`, `git diff --check`, CLI/JSON real checkpoint - Next output and protected hash reconciliation. Source/role changes stay local; - current-user config/role bytes unchanged. Final ownership: independent Test Runner. - -- Fresh Reviewer `ses_ee5c2f4faffemwJZx5slrapC07` read the actual 12-file pending - diff against the approved section; no Critical/Important findings. One Info - wording ambiguity allowed Reviewer check artifacts: verified against the contract - and narrowed to refer artifact-producing checks to Primary/Test Runner. This is - instruction clarification, not new native permission enforcement. -- Pre-existing ledger-vocabulary edge cases (`inactive` matching `active`, non-dash - checkboxes) remain outside these two observed repairs; no regression introduced. - Follow up only if actual records use them. No new adoption/isolation runtime is - implied. Review exclusions (private data, full-suite duplication, all-resource/OS - isolation and unrelated history) do not hide required scoped checks: Test Runner - independently ran source/package/native and preservation checks. -- Test Runner `ses_ee5c28bb5ffeqIDVs7DRub44s9` independently checked base23cbd88 - plus 12 pending tracked files and ignored checkpoint, with all SHA256 unchanged - during checks. Actual results: focused21/21; check176/174/0/2skips; package/diff - exit0; native OpenCode2.0.24 file/catalog/retired-skill checks pass, existing23Plan - cases pass and all4sampled helper cases deny. Six shell/execute/design-write - observations allow; **not** all-write isolation. Tools/provider requests issued by - verifier0; child egress not monitored. Owned temporary server/config cleaned up, - shared services untouched. -- Optional checks not run: `real isolated Compose preserves base mounts and recovers - failed project connection`; `auto-only final-summary overlay preserves the native - step limit`. No new skips or weakened assertions. These are not reported passed. -- Real `node scripts/work-report.mjs --status --json` and `--status` exit0: - explanatory status preserved, all3numbered Next items returned in order, warnings - empty. Early-return status route does not read session DB/full report. Protected - current-user/permission/installer inputs17/17 match. Primary inspected - `final-summary.json` and `final-native-verify.json`, not just the helper report. -- Same Test Runner independently reran the final narrowed Reviewer wording and - records: `TMPDIR=/tmp/opencode npm run check` exit0,176/174/0/2same optional - skips; `npm run check:package` exit0; native verifier exit0 on2.0.24, - 23Plan+4helper cases pass and6observations allow; `git diff --check` exit0; - real JSON/text status returns3ordered actions and preserved full status. - Protected17/17 still match. Only Reviewer+ledger/product-state/checkpoint changed - from the earlier receipt, as expected; all checked hashes stable during rerun. - Primary inspected `closeout-summary.json`; `closeout-*` logs retained privately. -- Required-file SHA256 snapshot (final verifier/tested source; excludes mutable - bookkeeping): parser `99bc1ec7e89f09c082dc6a481cf813f8645bd11fe7794b4a8ebc47dda2343dea`; - verifier `9b7962f5badda4f84a6e8fdd5d8a445425c145dc893024f5952d5c78155a0e0c`; - parser tests `2e6c214b0acf48677f95216ee9166aa477ae0ede0bb6ac2e96e73cab7f79262c`; - verifier tests `8c0995a88d0c62d29986d0fc058caa6c7b1657270fcf5b65a3d0ced2c4676cee`. - Role/package source bytes are captured in the independent private receipt and - forthcoming local Git revision; no global/public adoption follows from them. -- Human acceptance/guidance effectiveness remain pending, not an engineering - blocker. No external delivery. Real-use observation continues only as part of - already-authorized development; no new product/pilot selected here. -- Source/role guidance and verified records committed locally as - `fde313407b36ea65e20f7b13eea506dc295eef44` on canonical `main`. All12task-owned - paths staged explicitly, index/diff checked; checkpoint excluded, no unrelated - user changes. Primary reconciled10non-bookkeeping source/doc SHA256 with the - independent closeout snapshot and rechecked17protected hashes before commit. - Main clean/ahead3 immediately afterward. This receipt reconciliation changes - only ledger/product-state; final receipt commit is identified by Git history, - without a self-referential SHA. No new workspace, cleanup, push or publication. - -## Approved Plan→Build contract follow-up (2026-10-08) - -Status: guidance implemented, independently reviewed/verified and locally committed; real-use acceptance pending - -User approved the in-chat recommendation with "개선해줘": strengthen a common -planning/handoff basis, connect native Plan storage to the project record, and -consider only necessary minimal document permissions. This adopts the matching -current-user operating supplement, not a built-in prompt/permission replacement. - -- Workspace/target: canonical `main`, base `6d0922e18a6c0971c274fcd46ed8a9688001d317`, - initially clean/ahead4. Primary inline, one writer; four unrelated worktrees kept. -- Scope: execution/README/architecture, optional project/autonomy examples, two - matching current-user `AGENTS.md` bullets, this record/product-state/checkpoint. -- Preserve native prompts/modes/config/permissions/models/budgets, three installed - roles/installer, source code/tests, upstream skills, notifier, design ownership, - independent final verification and explicit goal opt-in. No install, service, - product task, runtime/hook, automatic transfer, publication/push/deploy or new pilot. -- Decision: no extra Plan edit permission is needed. Current V2 docs + Context7 - and read-only live catalog showed native plan-path edit allow, other edits deny, - and the adopted design namespace deny/read-allow. Use native storage + explicit - Build recording, not shell/MCP/helper bypass. Single-agent lookup returned404; - supported list catalog supplied actual Plan rules. No write probe/config mutation. -- Shared basis: latest approved requirements govern intent; approved plan governs - scope/order; Git/code/checks govern implementation state. Skill output uses the - same plan with required specs linked. No mandatory Plan stage or new form. -- Native proposal handoff includes actual source path/version or section, target, - approval/open decisions. Build reads real inputs, preserves content/approval and - records canonically before substantive execution; proposals remain unapproved, - unchanged approval is not reopened for transfer. Preserve prior files; sequential - writer, no two independently evolving plans or automatic session launch. -- Build owns execution progress/evidence and reversible adjustments; Plan may - reassess/propose, not invent implementation/acceptance or silently expand scope. - -### Checklist and observable review cases - -- [x] Reconcile existing state and approval; reuse current V2/native capability evidence. -- [x] Add common plan/storage/execution contract and aligned current-user supplement. -- [x] Independent review: approved vs proposal transfer; missing source; existing - ledger collision; stale HEAD/completed work; routine vs material adjustment; - Plan-only writes; same skill plan/linked spec; small direct-Build task; ownership - and completion evidence. No live role-compliance or effectiveness claim. -- [x] Independent source/package/diff/link checks, exact two-bullet policy diff and - 25 protected hashes; actual Plan edit/design rules unchanged, no paid tool probe. -- [x] Locally commit verified task-owned changes and reconcile records; - current-user guidance adoption separately from package/global-role delivery. - -### Evidence and limits - -Private root `/tmp/opencode/ksi-plan-build-handoff-20261008-n4l6pjml` contains -policy backup and25protected before hashes. Prose-only change: review interpretations, -not synthetic string tests as proof of model compliance. Planned checks: -`TMPDIR=/tmp/opencode npm run check`, `TMPDIR=/tmp/opencode npm run check:package`, -`git diff --check`, local links, policy reconstruction and protected/live rule diff. -No extra native-server run is needed if protected verifier/config/role bytes and -effective rules remain unchanged. Guidance/loading/effectiveness and human acceptance -remain distinct; a file path does not prove another session loaded the document. - -- Reviewer `ses_ee5768ee5ffeB14Qds2pNtcJKy` read the actual seven-document diff - and approved section; all nine interpretation cases pass, no blocking findings. - Low/deferred: more explicit path-collision wording; existing reuse/no-blind-overwrite - and sequential ownership already cover it, so no new rule/format introduced. - This is document interpretation, not a real model flow/compliance test. -- Actual Test Runner dispatch `ses_ee5761db6ffen7zULzPTnwemd6` independently ran - `TMPDIR=/tmp/opencode npm run check`: exit0,176total/174pass/0fail/2optional skips; - `npm run check:package`: exit0, offline three-role package check; - `git diff --check`: exit0. Local file/fragment links45/45 resolve; protected25/25 - unchanged; current-user guide exactly2added bullets/0removed, both byte-identical - to the autonomy example; removing them reconstructs backup byte-identically. -- Optional Compose and auto-only final-summary integration checks remain unrun, - not passed. No assertion/test change or skip added. Native catalog read confirms - Plan primary, edit-all deny/native-plan-path allow, design namespace deny plus - 11reads allow. No tool-write probe, extra native run, model/provider request, - configuration/role/service change or OS isolation claim. -- Helper receipt initially named the previous audit's Test Runner; Primary caught - that mismatch and required correction to the actual tool-dispatch binding, then - inspected corrected `final-summary.json`, check log and full snapshot. All seven - docs/checkpoint/current-user guide matched actual SHA256. Helper self-reports - alone do not establish identity/state. Readable check scripts and filtered native - rule receipt are retained privately; no private config/body copied into Git. -- Current-user adopted guide SHA256: - `e62d4e0eecba3059474efb8318221055db73adbe248f71491febb2b161d5818b`. - This session received the ambient two-bullet diff without a restart. It proves - this session's receipt, not that every other session loaded/read the project plan. -- Five guidance source files plus current-user bullets are verified unchanged - after review/checks; only closeout records will change before local commit. - Current-user guidance is adopted; the public package/installed role files remain - unchanged. Real Plan→Build effectiveness/compliance and human acceptance pending. -- Verified seven task-owned documents committed locally as - `2561d1fac924f28c462e0bfe16f1e13b0ea5698b` on canonical `main`; staged index and - whitespace checked, checkpoint/private backup excluded. Primary rechecked the - five guidance source hashes/current-user guide and25protected inputs against the - actual independent receipt before commit. Main clean/ahead5 afterward. - This receipt reconciliation changes only ledger/product-state; its revision is - in Git history, not a self-referential SHA. Four unrelated worktrees preserved, - no cleanup needed; no push/publication or installed-role update. - -## Read-only cross-state consistency audit (2026-10-08) - -Status: audit complete; four improvement groups reproduced; repairs proposed, not approved - -### Authority and inspected state - -- User requested a check for other consistency gaps after the Plan→Build improvement. - This authorizes investigation and a findings record, not remediation, permission - changes, installation, service/runtime changes, product work or external delivery. -- Inspected canonical `main` at `6b781f19a8d6a2a659f9a1036ff0a917f2710fad`, clean/ahead6, - one Primary; four unrelated worktrees retained. Existing approved slice unchanged. - Only this audit record, backlog summary and ignored checkpoint are updated afterward. -- Reviewed status/report/readiness code and tests, role/installer/approval guidance, - and notifier pause/delivery/provenance paths. Reproductions import actual source - exports with disposable synthetic Git/state fixtures and a fake clock/daemon/send. - No live goal, notifier state, usage DB, secret, customer payload or design run inspected. - -### Reproduced findings and proposed priorities - -| id | priority | source / mechanism | observed result / impact | proposed correction, not execution approval | -| --- | --- | --- | --- | --- | -| audit-A | High | `scripts/work-status.mjs:39-40,68,77`: first `Status:` across the entire ledger | Reused ledger's later in-progress section is ignored; prior complete + current in-progress yields no warning. Actual ledger contains statuses at lines5/121/254, but CLI selects line5 rather than the checkpoint's current Plan→Build follow-up. Multi-section ambiguity is not surfaced. | Explicitly identify the current section or report ambiguity; do not blindly choose the last status or create another competing record. | -| audit-C | High | `scripts/work-report.mjs:206-214,86-130`: top-level object check only | `{version:3,goals:"corrupt-not-goal-map"}` and `{one:"invalid-record"}` bindings both display zero records with no warning. This contradicts unavailable/malformed-source reporting in `docs/execution.md:320-321`. | Validate the supported nested shape; keep valid empty `{}` distinct from unavailable or corrupt state. | -| audit-B | Medium | `scripts/work-report.mjs:117-119` versus `plugins/design-notifier/lib/messages.js:16-24` | A reachable tracking/running snapshot displays `artifact=invalid`, while notifier classifies it pending. Failed/canceled tracking snapshots also collapse to invalid before delivery; after delivery they use failed/canceled. | Share consistent state semantics and distinguish execution state, artifact validity and product verification; do not turn either into acceptance. | -| audit-D | Medium | `plugins/design-notifier/lib/notifier.js:338-353,414-422,692`; README:139-141 | Failed send sets a30s retry; pause holds it; resume's next tick still cannot send until retry time. Resume says next tick and README promises within one cycle, but retry cap can defer up to15min. | Recommended default: preserve protective retry and accurately expose deferred delivery/retry timing. Reset-on-resume is a separate behavior decision, not proven necessary by this audit. | - -- audit-A secondary edges: `inactive` falsely matches `active`; unchecked `* [ ]` - and numbered `1. [ ]` items are missed, unlike `- [ ]`. Group these with status - parsing, not separate high-priority incidents. A single in-progress status and - dash unchecked items are positive controls. An all-checked multi-section fixture - still selects the old active status; this establishes ambiguity/stale selection, - not a universal rule that the newest status always governs. -- audit-B controls: succeeded-invalid, succeeded-valid and delivered classification - agree with notifier. Invalid `entry_not_touched` remains an artifact-only verdict, - not automatically product failure. A held run that becomes running reverts to - tracking; the reachable tracking/running supplement is the report-defect evidence. -- audit-C controls: invalid JSON and a top-level string warn; legitimate empty `{}` - does not. No claim that actual live state is corrupt or actual goals were lost. -- audit-D controls: without a send failure, held→resume delivers on the next tick; - a failed send without pause properly waits and later succeeds. Cap900000ms was - verified separately with attempt6→7. No15-minute wall-clock/live latency claim. - -### Independent evidence and reconciliation - -- Roles/approval reviewer `ses_ee55f572bffeQXm4YqJCxDTxxf`: no new in-scope - Critical/Important contradiction. Source/config preservation is not effective - permission preservation: custom broad `read * allow` can override earlier `.env` - ask/global read protections under documented V2 ordering. Treat this as an existing - policy risk needing a separate decision, not a newly verified secret-access incident. - Official V2 permissions and Context7 `/websites/opencode_ai_v2` confirm ordering; - no actual `.env` read or live permission-verdict probe was performed. -- Notifier reviewer `ses_ee55ef284ffets1twaqMbHrLqf`: independently identified audit-D. - Unrelated tools' unconditional quick-poll kick is a low-priority efficiency - observation, not a correctness defect. Goal/notifier pauses, advisory completion - versus product/design acceptance, and pinned-source lag remain documented boundaries. -- Actual Test Runner dispatch `ses_ee55ad94affer3kxDzs7zF9sY3` independently ran - `TMPDIR=/tmp/opencode npm run check`: exit0,176total/174pass/0fail/2optional skips; - `git diff --check`: exit0. Cases A/B/C/D/D2 plus supplemental controls produced - the results above. Source/check-file hashes12/12 unchanged; Primary inspected the - actual scripts, logs and corrected receipt and matched all12hashes plus supplement. -- Two optional tests remain skipped, not passed: real isolated Compose preserves - base mounts/recovery; auto-only final-summary preserves native step limit. Package - check was not rerun because there is no package/code change. Passing existing tests - does not cover these newly reproduced interactions or prove model compliance. -- Evidence corrections: original A3 also had an unchecked old item and was confounded; - the all-checked supplement isolates stale status selection. Original B's held/running - was not a steady state; tracking/running supplies reachable proof. Original D's cap - branch accidentally succeeded; only D2 proves the cap. Prior logs were preserved - and annotated. An empty hash-comparison assertion was rejected; actual12hashes were - independently rechecked. Do not infer proof from helper summary/counts alone. -- Optional raw evidence/scripts: `/tmp/opencode/ksi-consistency-audit-20261008-da3a5941/`. - Inspected source identity is the immutable HEAD above; supplement script SHA256 - `f45d667a5dbad6b0a48deeb995cbd67d17b7d718a4ea2528133d20a3c224959a`. - Primary also matched all25previously protected config/role/upstream/notifier inputs; - no global operating-guide/config/role/runtime change in this audit. - -### Exclusions and next decision - -Doctor's product-state-over-ledger binding precedence and numbered Next-slices -parsing were not established as contract defects. Intended version lag is not -unauthorized drift; native edit denial is not an all-write sandbox. Real live -delivery timing, every host's effective policy, Plan→Build compliance and human -acceptance remain unverified. No new monitor, orchestrator, forced team, mandatory -Plan stage or synthetic string test as model-compliance proof is proposed. - -Recommend a bounded follow-up for audit-A/C first, audit-B next, and accurate -audit-D deferred-retry reporting without weakening retry protection. Permission -semantics and any reset-on-resume change retain a separate decision. All fixes -remain unapproved; no implicit goal, release, install or product task starts here. - -## Approved agent-owned completion loop (2026-10-08) - -Status: independently verified and locally committed; current-user guidance adopted, real-use acceptance separate - -### Approved outcome and scope - -The user accepted the preceding in-chat default-loop proposal with "내가 다 일일이 -하는 건 너무 싫어 이것도 개선해줘": do not hand routine discovery, prioritization, -repair and verification back as a list for the user to administer. Agent judgment -owns them within the agreed outcome; only material decisions return to the user. - -- Current target/base: canonical `main`, `eccef13aa49350f1a7155e431ee90f006d821b8c`, - initially clean/ahead7. Primary inline, one writer; four unrelated worktrees kept. -- Scope interpretation: adopt the proposed common loop in source/current-user - guidance and apply it to the four already reproduced harness consistency findings. - They are routine corrections serving this same outcome, not another product task. - The prior audit-only request did not authorize repairs; this subsequent improvement - request supplies execution authority. It does not authorize every backlog item. -- Preserve native prompts/modes/permissions/models/budgets, role templates/installer, - upstream skills, paused work/goal opt-in, design ownership, independent final checks, - sensitive-read policy and delivery retry behavior. No runtime installation/restart, - publication/push/deploy, goal creation, live state mutation or product/design run. -- Current-user operating guidance is the only live adoption. Notifier source fixes - remain separate from its pinned installed payload; no silent runtime upgrade. - -### Implementation plan - -**Goal:** approved outcome → agent-owned defect triage/repair/reverification → usable -result report, without routine approval loops or user-managed candidate lists. -**Architecture:** existing native flow/guidance, bounded parser/report corrections -and honest retry eligibility; no monitor or second orchestration authority. -**Tech stack:** existing Node ESM/node:test and Markdown; no added dependency. -**Spec basis:** preceding approved in-chat loop proposal plus this scope and the -read-only audit's narrowed A-D evidence; no separate competing spec/ledger. -**Execution:** inline with TDD, one fresh whole-change review and independent final -Test Runner. Existing operating adoption selects routine execution; no new method -choice or implementation-plan approval ceremony for these bounded corrections. - -Pre-flight: A changes section selection consumed by status only; C/B share the -report file and are sequential; B consumes the existing pure notifier classifier; -D preserves the retry state machine. Guides consume the actual resulting contracts. - -- [x] Task 1 — current-ledger status (`scripts/work-status.mjs`, its tests): RED - multi-status ambiguity, explicit optional `Ledger section:` selection, missing - section, inactive and supported unchecked Markdown; GREEN bounded recognition. - Unselected multi-status records warn instead of guessing newest/oldest. Existing - one-status behavior, safe namespace/root guards and three Next actions remain. -- [x] Task 2 — report semantics (`scripts/work-report.mjs`, its tests): RED nested - malformed goal/binding state, reachable running/failed/canceled classifications; - GREEN shape validation and existing canonical classifier. Preserve valid empty - maps, supported goal containers, terminal artifact-only verdict/product distinction, - unavailable-source warnings and process-local fixture isolation. -- [x] Task 3 — retry reporting (`plugins/design-notifier/lib/notifier.js`, its tests, - README): RED failed-send→hold→resume must retain retry delay, expose wait in list - and not promise next-tick delivery; GREEN metadata and accurate eligibility copy. - No immediate retry/reset, pause/claim/delivery semantics or live daemon effect. -- [x] Task 4 — common operating default (README/execution/architecture, project/autonomy - examples, current-user `AGENTS.md`): implementation includes agent-owned discovery, - priority and repair/recheck; proportionate tests and independent closure remain. - Audit/Plan-only requests do not become product-write authority. Design uses actual - rendered review; no self-approval, endless replay, forced team or automatic goal. -- [x] Task 5 — fresh review, independent source/package/targeted checks, local status - flow, protected25hashes and exact guide diff; integrate scoped commits and reconcile - records/checkpoint. Finish with outcomes, not another routine improvement menu. - -Review focus: ambiguous or unavailable current ledger; mixed/unsupported nested -state and valid legacy fields; execution versus artifact/product status; retry and -remaining pause protections; scope/Plan/design/goal boundaries under proactive work. - -### Verification and evidence - -Watch relevant new tests fail before production edits, then pass. Run author focused -tests plus the full `TMPDIR=/tmp/opencode npm run check`. Independent final checks: -same source command, offline `npm run check:package`, `git diff --check`, real status -CLI, local new links, protected hashes and exact current-user guide adoption. -Guidance review assesses interpretations, not paid model compliance/quality probes. -Private backup/evidence root: `/tmp/opencode/ksi-agent-owned-loop-20261008-v118o3xk`. -Raw audit evidence remains linked above; historical conclusions are not rewritten. - -- Author TDD: focused three-file command total42,35pass/7fail before production edits; - all failures match missing selection/shape/classification/retry-copy behavior. - After minimal fixes, focused42/42pass,0fail/skip. Logs `red-focused.log` and - `green-focused.log` in the private root. No assertion weakening or added skip. -- Current selection is optional exact `Ledger section:` for a unique `##` heading; - missing/duplicate/multi-status states disclose unknown/ambiguity. No first/latest - inference; root containment and ordinary one-status flow remain. -- Reporter uses the existing pure notifier classifier; valid legacy boolean-only - snapshots are retained. Goal/binding maps validate container/record shape before - normalization, including supported nested goal arrays. Retry attempt/timestamp/ - remaining wait are exposed in list; resume copy discloses eligibility, retry and - remaining pause protections. No delivery/retry state-machine behavior changed. -- Common loop is aligned in five source guides and two current-user bullets; - this session received the ambient two-bullet diff. Author full check183total/ - 181pass/0fail/2existing optional skips, diff pass. Exact2guide additions/0removals, - backup reconstruction and both examples match; protected25/25unchanged. Real status - selects this exact section/in-progress rather than the old completed entry; Next3 - retained in order. Independent final evidence/review still pending; no general - model-compliance, all-session loading or installed-notifier update claim. - -- Fresh Reviewer `ses_ee53c01b6ffef7heTWBASuHFHl` inspected the entire14-file diff - against this scope; no Critical findings. Important: two earlier notifier README - promises still said unconditional next tick. Corrected both, and qualified the - current product-summary latency statement; all retain retry protection. Prose-only - correction uses the existing actual retry regression, not string tests as compliance. -- Minor observations retained, not new user tasks: an empty/no-status selected section - reports unknown without an extra warning; examples/fenced status text can cause safe - ambiguity; version-only goal metadata warns rather than pretending no work; text - output lacks JSON's ledger detail. Current explicit selection/unknown handling is - safe. Initial-clean wording was clarified in the updated product summary. -- Actual Test Runner dispatch `ses_ee53b7de2ffeEFMdQg4lM68Y7j` independently ran - focused42/42, source183total/181pass/0fail/2existing optional skips, offline package, - diff, status JSON/text checks. Links48/48, protected25/25, exact2guide additions/ - 0removals with byte-identical reconstruction and both example matches. Primary read - machine receipt and matched all14task files/checkpoint/guide16full SHA256. -- This session and the Test Runner received the ambient guide diff; this proves those - receipts, not all-session/model compliance. No native/paid-provider probes or live - service/state writes. Post-documentation-fix checks passed; no source/guide changes - after that verified snapshot, only notifier README and common closeout records. -- Same actual Test Runner post-fix: fresh focused42/42, source183/181/0/2optional skips, - package/diff/status JSON+text pass; links49/49, protected25/25, guide2added0removed/ - byte reconstruction/both examples pass. All3README resume claims and current product - latency summary match eligibility/retry semantics. Primary matched all16post-fix - file/guide/checkpoint hashes to `postfix-summary.json`. No remaining blocking finding. -- Two optional tests are not passed: `real isolated Compose preserves base mounts - and recovers failed project connection`; `auto-only final-summary overlay preserves - the native step limit`. No new skip/todo or assertion weakening. Native config, - role/installer/upstream bytes and pinned runtime remain unchanged. -- Current-user adopted guide SHA256: - `69db311c9d716e261a66bd93c72da3dc1a2bab2952b7e85c24015bbbbe2a2931`. - Final source/guide snapshot is post-fix; subsequent local closeout updates only - ledger/product-state/checkpoint. No new workspace to integrate/remove; local target - remains canonical `main`, external delivery unapproved. -- Verified14task files committed locally as - `2d8701bf8b94177f57d16042f87ca29e3c91a57a`; staged paths/whitespace checked, - private evidence/current-user guide/ignored checkpoint excluded. Main clean/ahead8 - afterward. Primary rechecked post-fix12source/test/docs hashes + adopted guide and - protected25inputs before commit; only ledger/product-state/checkpoint closeout - changed after the independent verification snapshot. Receipt revision is in Git - history, not a self-referential SHA. Four unrelated worktrees remain untouched; - no task workspace needs integration/cleanup. No publication, runtime install, - service restart or new workstream. Routine fixes are complete, not handed back as - a user-managed candidate list. Model compliance and human acceptance unclaimed. - -## Proposed product binding and live-preview improvement (2026-10-08) - -Status: proposed; recommendation requested, implementation not approved - -### Intent and observed basis - -The user wants one recognizable OpenDesign project per product instead of a new -project for each development task/worktree, and wants to inspect the real frontend -inside that workspace rather than only a file-based mockup. The latest question -asks how best to improve this; it does not authorize product/server/project writes, -deletion/merging, installation/restart or release. The approved completion-loop -slice above stays technically verified; this proposal does not reopen it. - -- MCP project reads confirmed EVENTOUCH has two distinct folder-backed bindings: - the original `/home/ksi/Desktop/SI-Projects/EVENTOUCH` and its separate - `.worktrees/eventouch-postgres` working directory. This is not a duplicate of - the same exact path; the missing canonical product identity is the concern. -- The PostgreSQL workspace's actual Git state was `3ade57f` on - `feat/eventouch-postgres` when inspected. It belongs to the product session; - its checkpoint was older, and it is not this harness session's working directory. - Do not retarget, rename or clean it up while another session/run is using it. -- A browser inspection of the installed design UI found the imported `index.html` - rendered as `srcdoc` in a sandboxed file preview. An authentication request went - to the design daemon origin rather than the EVENTOUCH product server. The file - is the real product entry; its filename is not what makes the preview incomplete. -- At inspection time, the existing temporary synthetic-product fixture on tailnet - port46771 returned HTTP200 with an entry byte-identical to the PostgreSQL working - directory. This proves entry reachability, not login/DB/role/persistence behavior, - service ownership, an enduring URL or production readiness. No server was started, - stopped or restarted for the inspection. -- Context7 `/nexu-io/open-design` and the local upstream source describe folder - rebinding; `docs/rfc-drafts/dev-server-auto-detect.md` remains Draft. An automatic - live-server preview/launcher is not established in the installed application. - Host upstream source capability alone does not prove deployed support. -- Existing `scripts/work-doctor.mjs` reads Product binding fields from product-state - or the ledger and explicitly reports metadata presence only. Keep that read-only - contract; recorded commands/URLs must not become automatic execution authority. - -### Recommended staged scope (proposal, not an execution plan) - -1. **Canonical identity and reuse-first preflight (bounded harness follow-up).** - Reuse the existing Product binding record for product/repository identity, - representative design project, actual current source workspace/branch/revision, - entry, brand and product URL. Identify by verified binding, not title substring - alone. A repository may contain several products, so a Git root alone is not - a universal product key. Ordinary screens, artifacts and task conversations - stay in the representative project. Before commissioning, compare the record - with the real design project's resolved directory and the approved source. - Unknown/multiple/conflicting bindings stay explicit; a failed lookup does not - justify creating a fresh project. Record who owns any deliberate parallel - workspace and when/how it returns to the representative flow. -2. **Safe adoption, not destructive deduplication.** For EVENTOUCH, recommend the - currently used PostgreSQL-linked project as the representative, preserve the - older project/history as historical reference, and coordinate any rename, - rebind or lifecycle change with the owning product session after active work - is reconciled. Rebinding may preserve project/conversation identity but does - not automatically migrate old folder files, artifact paths or snapshots. - A hard prohibition on all secondary projects would break genuine concurrent - work; allow explicit temporary exceptions, never routine per-task clones. -3. **Existing-product URL preview (architectural OpenDesign follow-up).** Add a - clearly separated product-preview surface pointing at the already running, - approved product URL, alongside artifact files. Preserve that server/runtime; - do not launch another API/DB or rewrite the frontend into a mockup. Show target - source/URL and connection failure honestly; never fall back silently to demo. - Use a top-level browser opening when embedding is blocked by framing, mixed - content or cookie/auth restrictions. Do not relax the artifact sandbox, weaken - auth, forward secrets or add an unrestricted URL proxy to make embedding work. - First iteration excludes automatic dev-server launch, dependency installation, - runtime migration and production deployment. -4. **Evidence, ownership and delivery boundaries.** Later implementation should - test recorded binding/unknown/conflict/parallel-worktree cases and exercise - repeated design work without new ordinary projects or wrong-source writes. - Verify actual server login, changed route, API result and reload persistence - against approved synthetic state; renderer screenshots alone do not close those - checks. OpenDesign owns UI implementation and rendered review, Build functional - wiring/Git, and Test Runner independent final checks. Preview-connection work - must bind each browser-client, host and container address explicitly. Global - guidance adoption, source commits, installed overlay/notifier update and public - package delivery remain distinct; no prior release authority is reused. - -### Alternatives and pending decision - -Guidance alone is cheap but cannot enforce uniqueness or implement a live preview. -Making OpenDesign manage every framework, API and database is much larger and adds -process/security responsibilities unnecessarily. Recommend the middle path above: -canonical reuse/preflight first, existing-product URL preview next. The material -decision is adopting this scope and authorizing the OpenDesign application change, -not asking the user to prioritize or administer individual routine fixes. No new -design direction, design generation, active goal, runtime change or cleanup starts -from this recommendation. Detailed preview spec/implementation planning follows -only after direction approval; this record is not that approved spec or plan. - -## Approved canonical binding and actual-product preview (2026-10-08) - -Status: technically complete — reuse/preflight and guidance implemented and independently verified; preview self-development cancelled by user; human/real-use acceptance separate - -### Approved design and execution contract - -User approved the preceding recommendation with "그렇게 개선해줘용", and requested -continuation after a provider interruption. Adopt representative-project reuse, -source-binding preflight and an existing-product URL preview, preserving prior -projects/history. The recommendation above is historical proposal evidence; this -section is the single current workstream specification/plan and approval record. -No product deletion, automatic runtime launcher, product-server migration or -production deployment. No permission/model/budget/native prompt replacement. - -**Goal:** repeated design work reuses the intended product project and source; -OpenDesign can distinguish artifacts from the existing live product. -**Architecture:** strengthen the existing local Product binding/readiness flow -with explicit snapshot comparison; add a separately configured product-preview -surface in the actual OpenDesign source. Reuse project metadata/API contracts, -never add another backend, unrestricted URL proxy or project registry. -**Tech stack:** harness Node ESM/node:test; OpenDesign existing TypeScript, -contracts, daemon route/CLI and web component/i18n layers. No new dependency. -**Specification:** this approved contract plus the preceding proposal's intent, -source evidence and middle-path constraints; later details remain reversible -implementation choices within these contracts, not a competing design direction. - -- Harness base/target: `main` at `1eb6003c205eed98d524d76cb8241d81cbace753`, - ahead9; two task-owned uncommitted proposal record files retained. Primary owns - common records/guidance/integration. One bounded nonvisual Developer assignment - uses an isolated harness worktree; independent final Reviewer/Test Runner. -- OpenDesign source: `/home/ksi/opendesign/open-design`, `main` at - `5b19dfa4351b3eed33826ee72746a7c653c23a54`; existing modified - `deploy/docker-compose.linux.yml` is user work and must remain byte-identical. - Use a task-owned isolated source worktree for UI/daemon/contract changes and - preserve the existing prunable unrelated worktree. Installed overlay is a - different revision; a source commit does not imply installed feature support. -- Source-target integration is local/non-destructive only. Global operating - guidance may adopt the approved reuse contract after exact diff verification; - overlay/runtime installation, restart for delivery and public release remain - separate. Exact-folder connection/import follows standing infrastructure scope, - with active-run checks and no broad-mount/security changes. -- EVENTOUCH source/design sessions remain owned by their product session. Record - the PostgreSQL-linked representative recommendation, but do not silently rename, - retarget, merge/delete or mutate that product/history while another session owns - it. A representative declaration is not artifact migration or live acceptance. -- Visual source/style implementation and rendered review are design-owned, using - verified source access and a reachable renderer or readable product captures. - No Build-side visual fallback. Preserve existing UI/brand; no full redesign. -- Known preparation blocker: OpenDesign host checkout has no installed workspace - dependencies; deployed image has runtime bundles, not Vitest/dev dependency - closure. No install attempted. Request scoped existing-lockfile development - dependency preparation if needed; continue independent harness work meanwhile. - -### Review focus and acceptance - -1. Missing/stale/ambiguous project identity or failed lookup never authorizes a new - project; compare an exact recorded identity, not name substring or Git root alone. -2. Worktree/source changes and metadata-directory contradictions never silently - target the old checkout, a parent bind or another product in the same repository. -3. Malformed/empty snapshots stay distinguishable; a matching captured snapshot is - metadata evidence, not current API/auth/write/renderer readiness. -4. URLs with credentials, unsupported schemes or private query-bearing tokens are - rejected; no server-side fetch/proxy or weakening the artifact sandbox/auth. -5. Frame/auth/mixed-content/network failures remain visible, with top-level opening - available; never replace a real-product failure with a writable demo. - -Acceptance: repeat preflight for multiple tasks without creating normal projects; -literal fixtures pin exact identity/path/ambiguity/malformed cases. On the actual -changed OpenDesign UI, the product target is visible, primary open/reload action is -keyboard reachable at1280/390, connection failure does not claim readiness, artifact -preview remains unchanged, and first-screen controls fit without horizontal scroll. -Browser verification uses only scoped synthetic product state: login/route/API -result/save/reload evidence, not screenshots alone. No real customer mutation. - -### Implementation plan - -> Agentic execution: use executing-plans/TDD for inline coordination and a single -> bounded Developer implementation for the independent nonvisual harness unit. -> No forced per-task team, extra method menu or new native goal. - -#### Task 1 — exact representative binding preflight - -Files: `scripts/work-doctor.mjs`, new focused `scripts/design-binding.mjs` if useful, -`scripts/work-report.mjs` re-export only if required, `test/work-doctor.test.mjs` -and a focused binding test. Owned by Developer; excludes guidance/global/records. - -Interface: `inspectDesignBinding({ repo, binding, projects })` returns a bounded -metadata result with matched/unknown/mismatch/ambiguous/invalid state and reasons; -it never creates/rebinds projects or executes URLs/commands. Existing readiness -accepts an optional caller-supplied projects snapshot, with `--design-projects FILE` -for CLI parity; without snapshot it stays explicitly live-unverified. Snapshot -accepts actual MCP list `{projects:[...]}` and array fixtures, exact project id, -`metadata.baseDir`/`resolvedDir`. Keep legacy recorded-only readiness compatibility; -optional Product identity/Source workspace/branch/revision/exception record fields -must not invent product identity from folder names or stale Git assumptions. - -- [ ] RED: literal tests for exact representative matched twice/no writes; absent - snapshot/id, stale id/name-only, duplicate id, wrong/contradictory source directory, - malformed container/record, separate products/worktrees, symlink containment and - CLI malformed/absent snapshot. Recorded commands/URLs are never executed. -- [ ] GREEN: minimal validation/report wiring; expose mismatch as preparation work, - never runtime success or automatic duplicate cleanup. -- [ ] Verify: focused `TMPDIR=/tmp/opencode node --test test/work-doctor.test.mjs` - plus any focused new binding test, full `npm run check`, `git diff --check`. -- [ ] Scoped local implementation commit; return actual RED/GREEN logs, changed - paths, tested revision, assumptions and unverified work; Primary integrates. - -#### Task 2 — reuse-first guidance and adoption - -Files: `docs/integrations/opendesign.md`, `docs/execution.md`, project/autonomy -examples and concise README clarification; exact scoped global `AGENTS.md` update. -Primary only. Existing Product binding record stays the canonical cache. - -- [ ] Document lookup→exact project/source verification→reuse; new project only for - genuinely new product/no verified existing representative, not lookup failure. -- [ ] Record explicit temporary parallel exception/owner/return path; no silent - rebind, old file migration, live runtime mutation or hard one-folder-one-product. -- [ ] Align the previous connect→import rule to reuse/rebind first when safe; source - snapshots/preflight are advisory metadata checks, not permission or live proof. -- [ ] Verify exact global backup/diff, source examples, local links and unchanged - roles/config/upstream/pinned payload; do not duplicate old policy wholesale. - -#### Task 3 — actual-product preview source implementation — CANCELLED (2026-10-08 user decision) - -Files mapped after prepared-source inspection: contracts project metadata DTO/ -URL validator, existing daemon project route/CLI, web provider and a focused -product-preview component integrated with project tabs; app/package tests. -One writer per file. Design owns web source/styles and rendered review; Primary -owns nonvisual contracts/wiring and source integration. Do not edit generated dist. - -- [ ] Prepare isolated source/dependencies and verify inner input/render access - before commissioning. Preserve Compose, mounted roots, active runs and runtime. -- [ ] RED shared URL-contract/persistence/API/CLI tests: safe HTTP(S), no credential - or token-bearing target, explicit clear, existing metadata preserved, read-only - get, auth/permission route guards; no server fetch/proxy or implicit startup. -- [ ] GREEN contracts/route/CLI via existing project metadata; expose identical - API behavior to CLI and UI, preserving existing project auth/preview paths. -- [ ] Commission product-compatible web implementation in verified source, using - existing design patterns and task-appropriate live skills. Preserve artifact - sandbox; product frame is a separate intentional connection with safe top-level - fallback, honest connection state and no demo substitution. -- [ ] Actual render at1280/390 with configured/unconfigured/unreachable/blocked-frame, - keyboard/focus and loading states; distinct design-review skill consumes those - rendered inputs and returns concrete fixed/open defects. Repair within scope. -- [ ] Run applicable package tests/typechecks/builds and real changed-flow browser - checks. Keep baseline failures separately, never assertion weakening/auto-skips. - -#### Task 4 — integration, independent verification and truthful closeout - -- [ ] Reconcile actual diffs/revisions, safe local source-target integration and - existing records; never mix helper reports with actual integrated code evidence. -- [ ] Fresh Reviewer whole-change check; in-scope defect→repair→affected recheck. -- [ ] Independent Test Runner harness check/package, snapshot cases, preservation; - prepared OpenDesign focused/full applicable checks and actual changed-flow checks. - Retain actual stdout/stderr/exit, source hashes and design-render review evidence. -- [ ] Commit task-owned changes, leave external delivery/install gated, clean only - proven task-owned integrated workspaces and preserve untracked user/runtime files. -- [ ] Report usable level: guide adopted, source implemented, rendered/reviewed, - functionally checked, installed and human-accepted are separate states. A blocked - preview stage does not block independently deliverable harness improvements. - -Pre-flight: Task1's metadata result informs Task2 but does not perform MCP requests; -Task3 project URL metadata is not Task1 project identity. Harness/preview codebases -are independent, Primary owns their shared continuity/approval record. No second -registry/runtime, no implicit task on EVENTOUCH, no deployment permission expansion. - -### Progress, dependency decision and scoped refinement - -- Dependency question: the user asked whether implementing the new preview is worth - doing instead of approving installation. No dependency permission was granted. - Primary recommends deferring the new embedded-preview app feature and using - existing-product top-level links now; continue the approved independent harness - work. This is not approval to install, cancellation of the whole task, or a claim - that the originally proposed preview is complete. Task3 remains preparation-blocked - pending dependency approval or an explicit scope-defer decision; no OD source - worktree, source write, container recreation/import, new run or server was created. -- Developer actual dispatch `ses_ee47e6e87ffeW2EV5p0X35OnlN` read this parent section, - implemented Task1's three scripts/tests paths in the isolated workspace and committed - `5bd710c`. Baseline183/181/0/2optional skips; module-absence RED (missing new module, - not twenty independent reproduced defects), GREEN20binding/25focused, full203/201/0/ - 2optional skips. Actual logs under `/tmp/opencode/ksi-product-design-binding-20261008-pga9l86n`. -- Primary inspected actual three-file diff/source/tests and fast-forwarded canonical - main to5bd710c, preserving the pending shared records. No helper guidance/global/OD - edit. Important author-inspection findings: source-less identity could be marked - matched, blank/relative directories normalized to cwd, fallback precedence hid - contradictory redundant directories, stale recorded Design storage was ignored, - and explicit null snapshots were treated as omitted evidence. -- Five literal regression tests reproduced those findings (focused25pass/5fail), - then bounded input/source-correlation repairs passed30/30. All supplied directories - must be absolute/nonempty and agree; missing source stays unknown, recorded storage - must agree, explicit null is malformed. No renderer/network/write authority added. - Fresh authorfull208total/206pass/0fail/2existing optional skips; rawRED/GREEN/fulllogs - retained. Independent whole-change review/final checks pending. -- Reuse-first/global guidance applied: two identical new bullets in both examples - and global guide, plus one scoped replacement of the old unconditional connect→ - import bullet. Five source guides distinguish metadata checking from live access - and actual-product links from artifact previews. Primary received the ambient diff; - exact backup reconstruction/protected25/global adoption verification pending. -- Fresh exact MCP reads confirmed both EVENTOUCH roots unchanged and agreeing. - Sanitized capture `actual-projects.json` is metadata only, not source adoption, - historical-project migration or product authentication verification. Its values - were copied from the actual tool results, not a generated design manifest. -- User preview clarification (2026-10-08): self-developing the OpenDesign preview - feature is not wanted. The original wish was only to conveniently view developed - frontends; the user explicitly asked not to complicate or damage OpenDesign/the - product ("자체개발 할 것도 없고", "너무 건드리면 복잡해지니까"). Task 3 is therefore - cancelled, not deferred: no OpenDesign source, dependency, runtime or preview-UI - change will be made, and no install request is pending. The already-applied reuse - guidance (existing-product URL in a browser tab, labeled separately from artifact - previews) remains the adopted way to view the real product. - -## Approved evidence-based verdict and result contracts (2026-10-08) - -Status: technically complete — approved 2026-10-08; independently verified; real-use acceptance separate - -### Approved scope - -The user approved the reviewed direction with "내가 원하는 하네스로 개선해줘": Build's -default shifts from re-reading and re-reviewing everything to deciding from each -seat's verified results, with a common return contract, risk-based verification -depth and context protection. Preserved: milestone/closeout independent Test Runner -verification, design-owned visual work, native prompts/models/permissions/budgets and -the existing records. No new registry, orchestrator, plugin, automatic re-delegation -runtime or forced team; the user asked to change only duplicated or missing contracts, -so the change set is minimal: - -- `docs/execution.md`: new "Verdict from verified results, not repeated re-review" - subsection (default verdict scope, risk-based depth, expansion conditions, finding - routing, context protection) plus return identification in the assignment/return - table and a cross-seat evidence sentence. -- `templates/agents/{developer,reviewer,test-runner}.md`: tested revision/scope, - evidence locations and not-inspected/not-run distinctions in the return sentences. -- `examples/autonomous-development.md` and `examples/project-AGENTS.md`: one aligned - bullet each. -- Current-user global guide: one scoped bullet. Installed role files: the same - additive return-contract sentences with private backups, preserving existing - custom content (for example the Reviewer design-review additions) byte-for-byte. - -### Verification plan - -- Author: `npm run check`, `npm run check:package`, `git diff --check`, link/anchor - sanity on changed docs; global-guide and installed-role diffs reconstructed against - backups (exact intended additions only). -- Independent Reviewer and Test Runner on the integrated revision, also covering the - canonical-binding change; in-scope findings fixed and affected checks rerun. -- Commit source/guidance, then records; report the usable level honestly. - -### Verification and closure (2026-10-08) - -- Fresh independent Reviewer `ses_ee4587024ffeveAnKT7Z6CW5Kd` on `1eb6003..92c866c`: - no Critical/Important findings; two minors (the nested-inside mismatch branch - was reached only implicitly; the blank-source invalid reason was worded as a - type error). Adoption diffs verified exact (global guide additions/replacement; - each installed role one sentence, custom content preserved); anchors resolve; - design-binding semantics deny-by-default and metadata-only. -- Independent Test Runner `ses_ee4587021ffepOMaAiJ5jfemMz` at `92c866c`: all nine - assigned checks pass — npm check 208/206/0/2 exact; package pass; focused 30/30; - commit stat 11 files; whitespace clean; global-guide and installed-role - reconstructions exact; OD Compose sha256 match; links 73/0; status tooling - selects this section without ambiguity. Logs `tr-*.log` in the evidence dir. -- Both minors fixed in `7d3b716` (nested fixture pins the existing branch; the - wording change is accuracy-only) after author rerun focused 31/31 and full - 209/207/0/2; post-fix independent Test Runner `ses_ee44d5aeffferL2lXVVHPkl1G4` - at `7d3b716`: check 209/207/0/2, focused 31/31, exactly the two intended files, - one commit since `92c866c`, clean whitespace. -- Closure: source/guidance committed `92c866c`+`7d3b716` on main; the developer - workspace `.worktrees/design-product-binding` was clean/integrated and retired - non-forced with its merged branch; global guide and installed roles adopted - with byte-verified private backups. Real-use acceptance of the guidance and the - per-project real-design end-to-end flow remain separate and unclaimed. - -## Proposed two-harness task contract and verdict boundary (2026-10-08) - -Status: proposed; recommendation requested, implementation not approved - -The user asked how to improve the harness for the intended structure: OpenCode is -the single development window; OpenDesign is a self-contained professional design -harness (its own analyze → implement → render-review → refine → return loop) the -user should not have to operate directly; Build owns product completion. - -Proposed minimal change set (only duplicated or missing contracts; no new -orchestrator/registry, no OpenDesign change, native prompts/models/permissions -preserved, independent final verification and design ownership unchanged): - -1. Boundary: OpenCode review seats do not repeat the design workspace's internal - visual review. Reviewer/Test Runner cover engineering concerns (functional - contract, security, regression, integration) and validate that the design - review happened on the actual revision with defects tracked fixed/open; - aesthetic re-judgment and visual repairs stay with OpenDesign. -2. Return contract: design returns identify changed screens/files on the actual - source revision, rendered-review evidence (fixed/open), functional-contract - impacts, unresolved items and evidence locations — extending the existing - cross-seat evidence sentence. -3. Per-project readiness of the three conditions — (a) write access to the real - source path, (b) completion detection/handoff, (c) Build continuing from the - actual modified source — is verified and recorded per project, never assumed; - a missing condition is a blocker report while independent nonvisual work - continues. -4. Single-window principle: the user is not asked to open or operate OpenDesign - or ferry artifacts between tools; Build commissions, monitors, validates and - reports from OpenCode. - -Current limits: direct source editing was verified in bounded fixtures; the -completion notifier is technically verified with documented limits; full -real-project end-to-end use remains per-project unverified, not assumed. - -## Approved two-harness boundary and commissioning contracts (2026-10-08) - -Status: technically complete — approved 2026-10-08; independently verified; real-use acceptance separate - -### Approved scope - -User-directed: after the recorded proposal above, implement only the minimal, -substantive improvements ("실질적인 효과가 있는 개선만 선정하고 … 최소 변경으로 구현"), -preserving existing behavior. No new agent, MCP server, state DB, orchestrator or -forced approval step; native prompts/models/permissions, existing Superpowers -usage and OpenDesign internals unchanged; no mid-task progress/commit permission -requests. - -Selected (all guidance-level; no code/MCP/session-linkage defect was confirmed in -this round — the remaining gaps are per-project operational verification): - -1. Review-seat boundary: OpenCode does not repeat OpenDesign's internal visual - review; review seats validate the returned revision/evidence (defects fixed/open) - and cover engineering concerns (functional/API/data/permissions contract, - security, regression, integration). Files: `docs/execution.md`, the OpenDesign - commissioning section, the installed reviewer role, both examples, global guide. -2. Design return contract: changed screens/files on the actual source revision, - rendered-review evidence (fixed/open), functional-contract impacts, unresolved - items, evidence locations (`docs/execution.md` cross-seat sentence). -3. Project-level commissioning conditions: real-source write access; completion - handoff (notifier or explicit polling fallback); continue-from-actual-source — - confirmed and recorded per project, never assumed; missing → blocker report - while independent nonvisual work continues (`docs/integrations/opendesign.md`; - global guide). -4. Single-window principle: the user works from OpenCode alone; no opening or - operating OpenDesign or ferrying artifacts; commissioning, monitoring, - validation and reporting stay with Build (README, global guide). -5. OpenDesign owns its internal analyze → implement → render-review → refine loop; - Build judges by results + evidence + acceptance criteria, not per-round - aesthetic instructions (opendesign.md, examples; existing no-round-cap rule). - -### Verification plan - -- Author: `npm run check`, `npm run check:package`, `git diff --check`, link sweep, - backup reconstruction (global guide before-c, installed reviewer before-c). -- Independent Reviewer + Test Runner on the committed revision; findings fixed and - affected checks rerun; records committed; no OpenDesign/deps/publication. - -### Verification and closure (2026-10-08) - -- Fresh independent Reviewer `ses_ee440df8affeEQRst4rzY2KFv7` on `9105b06..4d3ce3f`: - no Critical/Important/Minor findings; "Ready: Yes". Confirmed the docs-only range - (no code/MCP/permission/model drift), exact outside-repo adoption (guide +2 - bullets; reviewer one sentence, line count unchanged), boundary consistency with - the required OpenDesign-internal review run, no-forced-helper, independent-final- - check and no-new-gate rules; anchors resolve. No change requested. -- Independent Test Runner `ses_ee440df7dffeTgjhd9Fs0zocMq` at `4d3ce3f` (clean tree): - all seven assigned checks pass — npm check 209/207/0/2 (two pre-existing optional - skips), package pass, 6-file commit stat, whitespace clean, guide +2-bullet and - reviewer one-sentence reconstructions exact, links 74/0, status tooling selects - this section with no ambiguity. Logs `tr-c-*.log` in the evidence dir. -- Closure: guidance-only change committed `4d3ce3f` on main; global guide and - installed reviewer adopted with byte-verified before-c backups; no OpenDesign, - dependency, runtime or publication change. Context/rework-reduction effects are - contract-level claims to observe in later real work, not measured here; telemetry - was deliberately not added. The three commissioning conditions remain per-project - operational verification items. - -### Pinned notifier update (2026-10-08) - -- Risk-③ improvement: the live pinned copy was at `c43fbea` (2026-10-07) while the - repo source carried verified fixes — `2d8701b` (accurate retry/resume reporting - and retry-list metadata) and `ab01b51` (render-review readiness advice). The - user's "그렇다면 개선해줘" after the risk list is treated as the explicit verified - pinned update the plugin README requires. -- Pre-update backup `notifier-installed-before-58d7520/` (commit `c43fbea`, 7 files) - in the evidence dir. Update via `node scripts/install-design-notifier.mjs` → - manifest commit `58d752025d273d795440be38fadb3e262ab78f7e`, 7 files; `--verify` - ok with no changed/missing/extra; byte-identical to source; focused notifier - tests 73/73. -- Independent Test Runner `ses_ee41e9b2effens7WFP2KsXprhg` confirmed: verify ok, - the sole diff is the target-only `installed.json`, hashes match across source/ - installed/manifest, backup provenance intact, 73/73, and the repo's `plugins` - config array does not reference the dev tree. Delivery semantics unchanged; no - service restart or reload was performed — the running service picks up the - pinned copy per its own plugin lifecycle. This closes the on-disk "stale pinned - copy" part of risk ③; the remaining risk-③ limits (pull-based polling, same-host, - leader takeover, goal-pause not observed) are unchanged and still documented. - -### GitHub delivery (2026-10-08) - -- User authorization: "github 푸시 배포만 해줘" — GitHub push only; no npm - publication, GitHub Release or other deployment. -- Pre-push preflight at `7e3e9f1`: clean tree, `npm run check` 209/207/0/2 - (log `push-preflight-check.log`), 16 commits ahead of `origin/main` with no - unexpected files. -- Pushed `f6df31a..7e3e9f1` to `origin/main` - (github.com/khakisketch/KSI-Opencode-Harness). CI run 37795281564 for - `7e3e9f1` completed **success**; prior remote runs also completed successfully. -- Delivered commits: this session's binding preflight, verdict/return contracts, - boundary/commissioning contracts and their records. The pinned notifier update - remains local-only (outside the repo); this closing records note is pushed with - the delivery. - -## Approved product-judgment and focus-shift contracts (2026-10-09) - -Status: technically complete — approved 2026-10-09; independently verified; real-use observation separate - -### Approved scope - -User-approved ("제안 전체 진행") shift of improvement focus from execution -procedure to product judgment and program efficiency, following the read-only -EVENTOUCH live-loop analysis. Minimal guidance/records changes only; no new agent, -orchestrator, state DB or browser service; native config and OpenDesign untouched. - -- A. Product judgment: requests are inputs, not orders — judge product value, the - smallest viable path and stop criteria before accepting scope; propose better or - smaller alternatives with evidence when they exist, while the product/business - decision stays with the user. Files: `docs/execution.md`, global guide Project - judgment, both examples. -- B①. Design-brief edge-state class checklist (dialog/modal interplay, - deep-scroll positioning, missing header/anchor states) to reduce per-defect - round trips: `docs/integrations/opendesign.md`, global guide. -- B③. Defect-origin tagging (self, Reviewer, Test Runner, design review) in the - existing record so agent contribution can be assessed over time — tracking, - explicitly not a quota: `docs/execution.md`, global guide. -- C. One canonical record per concern: skill-internal progress (e.g., a - Superpowers SDD ledger) stays with the skill and is linked, not mirrored; the - native session conversation is not a record. `docs/execution.md` record table, - global guide. -- D. Skills are tools, not mandatory stages: choose the lightest effective path; - procedure does not substitute for the model's own judgment. `docs/execution.md`, - global guide. -- B② (host-render access check for design runs) is deferred until the EVENTOUCH - owning session finishes; no new browser server or orchestrator is to be added. - -### Verification plan - -- Author: `npm run check`, `git diff --check`, link sweep, global-guide - reconstruction against the before-d backup. -- Independent Reviewer + Test Runner on the committed revision; findings fixed - and affected checks rerun; records committed; no OpenDesign/deps/publication. -- Product-judgment efficacy is a real-use observation item (e.g., the next - KSI-Safety-Edge product task), not a test claim. - -### Verification and closure (2026-10-09) - -- Fresh independent Reviewer `ses_ee3c8d9f4ffe37ar8hvRCJMbVE` on `b2a1ef2..8d84a76`: - "Ready: Yes"; no Critical/Important; four wording minors (origin label - unification to `self`; proportionality qualifier; required-checks clause; - brief-vs-iteration wording). Confirmed guidance-only scope, B② intentionally - absent, adoption exactly the intended hunks, and no contradiction with the - scope/focus/no-forced-helper guards. -- Independent Test Runner `ses_ee3c8d9f6ffeOaY0nn9jh5ZodM` at `8d84a76`: all six - checks pass — npm check 209/207/0/2 (two pre-existing optional skips), five-file - commit stat, whitespace clean, guide reconstruction exactly five hunks, links - 75/0, status tooling selects this section, EVENTOUCH worktree untouched - (HEAD `d440660`, pre-existing uncommitted set only). -- All four minors fixed in `9d40fab` (label unified across guide/execution/plan; - "Scale this judgment to the change; trivial edits need no product critique" - added to execution/global/examples; "The checks the chosen path requires still - apply" added to execution; checklist wording aligned to "first brief") after an - author rerun; post-fix independent Test Runner `ses_ee3c1ff89ffeIWvkAIWkbLNHIN` - at `9d40fab`: all six checks pass (209/207/0/2; 7+/7− across five files; guide - still exactly five hunks; links 75/0; EVENTOUCH untouched). -- Closure: guidance-only change committed `8d84a76`+`9d40fab` on main and pushed - to `origin/main` with this records commit; global guide and examples adopted - with a byte-verified before-d backup. Product-judgment efficacy remains a - real-use observation item; B② (host-render access check) stays deferred until - the EVENTOUCH owning session finishes. - -### Post-delivery assessment and observation phase (2026-10-09) - -- User review accepted the implemented contracts as the right first step but not - the completion of the product-judgment shift (mostly operating guidance, not - execution logic). Decision: **stop adding operating guidance** and move to - observing real model judgment. -- Deferred (recorded for later review, not implemented): (1) per-product - long-horizon completion criteria — long-term vision/customer value, approved - MVP/milestone, task done-criteria, code/test-proven level and field-validation - level kept distinct per product; (2) whether all four layers (native Plan / - task ledger / product-state / checkpoint) earn their keep long-term or should - shrink to native-where-sufficient plus persistent product/launch decisions; - (3) Reviewer/Test Runner cost-vs-contribution beyond defect counts — severity, - actual fixes, duplicate findings, tokens/time, and Test Runner overlap with - existing CI; keep both seats but leave room to shrink mandatory scope. -- `Requests are inputs, not orders` watch item: guard against over-reinterpreting - explicit instructions; existing mitigations (decisions stay with the user; - proportionality clause; no silent scope change) were confirmed sufficient for - now; observe in real use. -- Observation target: the next KSI-Safety-Edge development task — present existing - requirements and the actual code state without forcing an implementation list; - let Plan determine the most important problem, what not to build, the - highest-value contribution and the evidence that would complete the milestone; - then observe Build for plan over-dependence and repeated unnecessary - delegation/verification. Good results would substantiate Plan/Build sufficiency - without a separate product-manager agent; shallow judgment would first call for - checking missing information/tools/criteria rather than longer prompts. - -## Approved Test Runner retirement and two-role simplification (2026-10-09) - -Status: approved and executed in this workstream; verification receipts appended below. - -### Approved scope - -User-directed follow-up after the Build review proposal (supersedes the guidance -pause for this specific change, nothing else): - -- Remove Test Runner from the default harness bundle: `NATIVE_ROLE_NAMES` - becomes Developer + Reviewer and the `--developer-test-runner` installer - variant is removed. Developer owns the tests for its own implementation; - Reviewer is the independent read-only code/behaviour review seat and also - reviews test adequacy (may run safe read-only checks; artifact-producing - checks belong to Build/CI). Build verifies the integrated revision and - unverified areas and reuses already-valid checks instead of rerunning - everything. -- Independent verification where genuinely required is not eliminated: it can - come from Reviewer's safe read-only checks, an explicitly configured - verification role (existing user installs keep their Test Runner role file), - Build's recorded integration checks on the integrated revision, or an - authorized CI run. Reviewer permissions are not expanded. -- Clean up the option, installer/doctor/verifier/package scripts and tests, - package composition, shipped docs, and the adopted operating guidance - (machine-global `AGENTS.md`) to the two-role model. -- Preserve: existing user configuration (installed role files, JSONC routing), - past evidence/history (dated records, `.superpowers` archives, backups), and - native Plan/Build, Superpowers and OpenDesign behavior. No new agent, - orchestrator, MCP or runtime loop. -- No GitHub push, npm publication, or service restart in this task; local - commits only. - -### Planned change set - -- `src/native-roles.mjs`, `src/native-bundle.mjs`, `bin/ksi-opencode.mjs`: - two roles, no opt-in variant, explicit removal message for the old flag. -- `templates/agents/test-runner.md` deleted; Developer and Reviewer prompts - updated (own-tests / test-adequacy responsibilities). -- `scripts/check-package.mjs`, `scripts/work-doctor.mjs`, - `scripts/verify-native-v2-isolated.mjs` and the affected tests updated; new - installer tests cover the removed flag, the preserved user-owned Test Runner - file, and the retired role. -- `package.json`, README, INSTALL, architecture/execution/verification docs, - OpenDesign contract and both examples aligned. -- Machine-global `AGENTS.md`: targeted replacements only, byte-verified backup - before adoption. - -### Verification plan - -- Author run: `npm run check`, `npm run check:package`, - `node scripts/verify-native-v2-isolated.mjs`, `git diff --check`, link sweep. -- Independent Reviewer on the committed diff; independent execution - verification on the integrated revision; receipts and record updates below. - -### Verification and closure (2026-10-09) - -- Author checks on the change: `npm run check` 212→213 total after the fix - (211 pass / 0 fail / 2 pre-existing optional skips), `npm run check:package` - pass with "offline two-role install", `node scripts/verify-native-v2-isolated.mjs` - pass on OpenCode v2.0.24 (files.installed true = developer.md+reviewer.md, - helper decisions 2 all deny, 0 provider requests), `git diff --check` clean, - shipped-doc link sweep 64/0, `npm run release:check` pass (7 pins, clean tree). -- Independent Reviewer `ses_ee1a22dcaffei6salf7KtH2SiF` on `92f1612..ccb1bbc`: - no Critical/High; invariants confirmed from the actual diff (two-file bundle, - loud flag removal, preserved user file, unchanged collision guards and - Reviewer permissions, retained independent-verification path, history - preserved). Findings: Medium — shipped `opencode.jsonc.example` and - `docs/troubleshooting.md` still said three custom agents / three-role - installer; Low — `docs/releasing.md` still described a three-role inventory - and apply; Info — `buildNativeBundle({developerTestRunner:true})` was - silently ignored by a stale caller. All three addressed in `56fc131` - (wording fixes plus an argument guard with a regression test). -- Independent execution verification on the integrated revision: Test Runner - `ses_ee1a22dc8ffew9cyGzbKw4Bok6` at `ccb1bbc` — 6/6 pass (clean tree, linear - history, `npm run check` 212/210/0/2, package check, isolated two-role - install/helper denials/0 provider requests, whitespace clean). Post-fix - recheck `ses_ee1a005a3ffeeb93nO0pl78yl7` at `56fc131` — 5/5 pass - (`npm run check` 213/211/0/2, package check, isolated verifier, and a live - probe that `--developer-test-runner` exits non-zero with the removal message - and creates no target). -- Machine-global `AGENTS.md` adoption (operating guidance, per the approved - cleanup): backup `AGENTS.md.before` sha256 - `4a5e4143a0f901c2fb1bac03149df12dc0626e066185cc3b6f4256f3290b7a60` in - `/tmp/opencode/ksi-tr-retirement-20261009/`; five targeted replacements on - four lines (self-review, Build evidence flow, defect routing/origin tagging, - role-verification seat); result sha256 - `77f19b53b2ea929a5d43ef4131a8388765b48e594d5ddc0261445a2a70c0e482`, - reconstruction byte-exact, 124 lines unchanged, zero remaining Test Runner - references; the live instruction refresh confirmed the same diff. -- Preserved: installed user files (`~/.config/opencode/agents/` including the - user-owned `test-runner.md` and customized `reviewer.md`), JSONC routing - (`test-runner` model entry untouched), historical records/specs/archives, - and the published `0.5.0-beta.8` state. No push, publication, service - restart, new agent, orchestrator or permission expansion. -- Closure: commits `d233d06` + `ccb1bbc` + `56fc131` on local `main` (plus this - records commit); **not pushed** per the user's instruction — `origin/main` - remains `92f1612`, and a GitHub push needs separate approval. - -### Authorized GitHub delivery (2026-10-09) - -- Subsequent user instruction: "푸시해줘" — authorizes delivery to the previously - identified `origin/main`, not npm publication or deployment. -- Before delivery: clean local `main` at `23f803b`, four task-owned commits above - `92f1612`; `git diff --check origin/main..HEAD` clean. Only the two state/receipt - files changed after independently tested `56fc131`; implementation evidence - remains bound to that revision rather than unnecessarily rerunning the suite. -- `git push origin main` succeeded (`92f1612..23f803b`), and `git ls-remote --heads - origin refs/heads/main` confirmed `23f803bcb3ecf2124ee2c211ae0d4c65d692acd1`. - This delivery-record update follows on the same authorized target. CI status is - checked separately; push success alone does not establish CI success. -- Published npm `0.5.0-beta.8`, installed user role/config files, Superpowers and - OpenDesign remain unchanged; no publication, restart or deployment performed. - -## Approved post-retirement legacy audit and cleanup (2026-10-09) - -Status: user requested a comprehensive related legacy audit and cleanup; -technically verified, independently reviewed and locally committed (`c18031f`); -human acceptance and external delivery remain separate. This is a bounded follow-up, -not renewed general guidance expansion or product-development ownership. - -### Goal, basis and boundaries - -- Goal: remove misleading active two-role instructions and close retirement - verification gaps while preserving user configuration and historical evidence. -- Basis: the user's follow-up cleanup request and the preceding approved - Developer + Reviewer contract. No new role, default verification seat, - orchestrator, permission/model/step change, dependency or service restart. -- Primary/workspace/target: this Build session, canonical checkout - `/home/ksi/Desktop/KSI-Projects/KSI-Opencode-Harness`, `main`, base - `1840aada86b7d9123e5d5b2b8fb6a0946a608733`, local integration on the same branch; - clean at start, no concurrent writer or new worktree. Existing unrelated - worktrees and the EVENTOUCH owning session are excluded. No push or release - authorization is inferred from the previous completed delivery. -- Preserve customized Reviewer, JSON/JSONC bytes (including optional Test Runner - routing), installed Test Runner, backups, dated trials and earlier catalogs. - Only the installed Developer's proven stock pre-retirement prompt may be - aligned after a private backup, with frontmatter byte-identical. Unknown or - customized content must not be overwritten. - -### Reproduced findings and root causes (origin: self) - -| id | finding / cause | required result | -| --- | --- | --- | -| L1 | README/INSTALL say pinned npm beta.8 writes two files, but immutable release tag `v0.5.0-beta.8` (`178b2e9`) defines three roles; source-only retirement was described as published behavior. | Recommend reviewed-source installation for two roles; explicitly label published beta.8 as the legacy three-role release, without bumping/publishing. | -| L2 | `scripts/check-package.mjs` rejects the design-kit flag only, despite README claiming both removed options are verified in the packed consumer. | Exercise both removed flags on the actual installed tarball, nonzero status + removal message + no target writes; also verify legacy role/routing preservation. | -| L3 | Installed Developer body exactly matches `92f1612:templates/agents/developer.md`, including removed Developer-to-Test-Runner option; installer deliberately preserved it. | Back up and align only that known stock prompt; retain its permissions/frontmatter and every unrelated global file. | -| L4 | Product-state still labels the current beta milestone three-role without separating current source; dated verification uses present-tense three-role wording. | Refresh current summaries and mark old catalogs as historical, never rewrite past evidence. | - -Audit so far: active source/templates/options, installer, doctor, isolated -verifier, package manifest/check, CI, current docs/examples/config sample, -SVG textual labels, and relevant global agent/config entries inspected. Retired -flag errors, absence assertions, preservation tests and dated references are -intentional compatibility/evidence, not dead components to delete. Old design -backups and unrelated worktrees are preserved. CI `37882500976` for `1840aad` -is now completed/success; it does not test this follow-up. - -### Implementation and verification checklist - -Architecture: keep installer behavior unchanged. Add a small reusable packed -consumer assertion in the existing package checker; no new CLI/API/config tool. -Stack: existing Node.js 20+ ESM, node:test, local tarball verifier. - -Review focus: published/source identity; rejected-option side effects; stock -versus customized prompt provenance; preserved routing/permissions; historical -evidence not mistaken for current acceptance. - -- [x] Add failing regressions in `test/check-package.test.mjs` for - `verifyRetiredFlags(runCli, target)` (both flags, explicit message, no writes), - run these tests and capture RED. Human-facing source-first/legacy disclosure - is checked against actual artifacts and by review, not a prose-lock test. -- [x] Implement that internal helper in `scripts/check-package.mjs`, call it - on the installed tarball, and assert retained legacy file/JSONC bytes; - correct README/INSTALL/releasing/verification copy; run focused GREEN. -- [x] Privately back up the target global prompt and hash relevant preserved files, recheck the stock Developer - body and align it to the current template using a targeted patch; prove - frontmatter and all non-target file hashes unchanged. Do not delete the - optional installed Test Runner or its routing. -- [x] Run `npm run check`, `npm run check:package`, `git diff --check`, and - changed-doc link checks; reuse the unchanged native permission/catalog - evidence instead of restarting a shared service or retesting unrelated flows. -- [x] Independent read-only Reviewer checks actual task diff, evidence, - preservation and test adequacy; repair/recheck affected findings. Record - exact tested workspace/revision/pending changes, update product-state and - checkpoint, and commit only task-owned files locally. Human acceptance and - external delivery remain separate. - -Plan self-review: all four findings have an owned repair/check; preserving -existing optional verification is a constraint, not incomplete default-role -removal. Implementation can proceed under the existing local-execution policy; -no new scope/design/security decision is required. - -### Implementation/check receipts (2026-10-09) - -- Verification target: canonical `main`, base `1840aad` with pending changes to - README, INSTALL, releasing/verification docs, package-check script and its - tests, and this record. Installer, native-role definitions/bundle, templates, - doctor/isolated verifier and permission rules are unchanged. -- RED: `node --test test/check-package.test.mjs` — 4 existing pass / 4 new fail - for the absent packed-consumer validator; no import/syntax failure. GREEN: - `node --test test/check-package.test.mjs test/native-package.test.mjs` — 15/15. - Added scenarios fail on missing second probe, silent success, usage-only error - and target creation. Main package verification executes the actual installed - tarball, including retained optional role and exact commented JSONC bytes. -- Full check: `TMPDIR=/tmp/opencode npm run check` — 217 total / 215 pass / 0 - fail / 2 unchanged optional skips (real isolated Compose; auto-only endpoint - final-summary overlay). Packed check: `TMPDIR=/tmp/opencode npm run - check:package` — pass (two-role fresh install, both rejected flags with no - writes, repeat, legacy preservation and routing-protected replacement). -- `git diff --check` clean. Changed user-doc link sweep: 37 local paths/anchors, - 0 missing. `checkVersionPins` using the actual README/INSTALL/manifest: pass, - 5 pins. Full `release:check` is unnecessary here: this is not a release and - it would mechanically repeat the same checks and require a clean commit. -- Fresh registry metadata: both `latest` and `next` = `0.5.0-beta.8`. The - public tarball was fetched read-only in memory, its SHA512 matched registry - SRI, and its actual template inventory and role source include Developer, - Reviewer and Test Runner. No public package code was installed/executed. -- Global prompt alignment: installed Developer's body exactly matched stock - `92f1612` before edit. Private backup at - `/tmp/opencode/ksi-legacy-cleanup-20261009/global-backup/developer.md.before`, - SHA256 `cd6e8367c14fe8c7e261febda95c9bd978c009bff4d578944a49d36062be1e13`. - After targeted prompt-only patch, SHA256 - `6e1ea112adf8a4320a2518fda02c767287553560115332ffc55ae2116046a281`; - full file equals the current Developer bundle, frontmatter byte-identical. - Five non-target files remain hash-identical: customized Reviewer, installed - Test Runner, `opencode.json`, `opencode.jsonc`, global `AGENTS.md`. -- Evidence root: `/tmp/opencode/ksi-legacy-cleanup-20261009/` (`red.log`, - `focused-green.log`, `check.log`, `package.log`, `global-before.json`, - `global-preservation.json`, `public-beta8.json`). No service restart or live - model execution; prompt adoption is file evidence, not behavior acceptance. -- Ruling: do not add regex wording-lock tests for human prose — the loaded TDD - test-quality guide requires behavioral tests, while public/source identity is - proved by actual artifact comparison and independent reading. Cost if wrong: - future prose drift needs review rather than a brittle exact-copy CI assertion. - No existing test was removed; the manifest test's misleading "new publishable" - title is corrected without changing its version/permission assertions. - -### Independent review and disposition (2026-10-09) - -- Reviewer `ses_ee10880a4ffeq32CEZKWnC7K0C` inspected actual base `1840aad` + - nine task-owned pending files, the plan/receipts, neighboring installer/docs, - and global before/after provenance/hashes. No files modified; no tests, - package installations or model calls claimed. **No Critical or Important - behavioral defect.** Native installer/role/permission files unchanged; - two-role versus public three-role disclosure, packed flags, legacy routing - and stock/custom prompt distinction verified from actual sources/evidence. -- Origin Reviewer, Minor 1 (receipt persistence): exact 37-link / 5-pin outputs - existed in the tool transcript but were absent from the evidence directory. - Re-executed those two inexpensive read-only checks, saved `doc-links.json` - (37 local links / 0 missing) and `version-pins.json` (ok true / 5 pins). - Fixed evidence gap; no code or test edit and no full-suite replay required. -- Origin Reviewer, Minor 2 (process suggestion): recommended pre-review before - global prompt changes. **No defect/repair required:** this prompt-only update - was authorized by the cleanup contract, stock provenance checked before the - edit, backup created, permissions/frontmatter unchanged, five preserved - non-target hashes independently confirmed afterward. A new per-edit - pre-approval gate is not adopted; Reviewer has no approval authority and the - operating policy already permits this bounded backed-up maintenance. -- Declined-to-judge exclusions retain the agreed boundaries: B② remains - deferred to idle EVENTOUCH ownership; live model behavior/efficiency and - user acceptance unverified; external delivery unapproved; native internals, - other sessions/worktrees and unrelated product-wide auditing excluded. - None is required to complete this source/metadata cleanup or authorizes - further work here. No unresolved in-scope defect remains. -- Exact checked code snapshot (raw SHA256; no edits since the passing runs): - `scripts/check-package.mjs` = - `7c845fd9e65703e8eba029efb2fe617f8b3188559032b24cead49d73cf1b52d8`; - `test/check-package.test.mjs` = - `fba92c35a3294eba76f2b2e9a61c0841d339ecbb1865b2aadb927b20a08d11a5`; - `test/native-package.test.mjs` = - `82f844484217c4ed682e482042408a94cb9103acf012dc855686149c23265c27`. - Later record-only edits do not invalidate code/package test evidence. - -### Local closeout (2026-10-09) - -- Implementation/docs/checks/review receipts committed locally as `c18031f` on - canonical `main`; only the nine task-owned files were staged, index/diff - whitespace clean. This final record-only reconciliation follows on the same - local branch; no new push/publication/restart authority is implied. -- L1-L4 are fixed/verified. Retained references are intentional removal errors, - safety/preservation regressions, explicit legacy-release/migration guidance, - optional user role/routing and historical evidence. No active default-role or - removed delegation implementation remains. No user-owned backup/worktree or - historical catalog was deleted, and no task worktree was created. -- No in-scope blocker. Technical cleanup is complete; user acceptance and - live prompt behavior/efficiency remain unclaimed. Public beta.8 stays the - older three-role artifact; a new public two-role release requires separate - authorization. Product-judgment observation and B② retain their prior gates. - -## QA delegation direction — no new agent (2026-10-09) - -Status: current user direction recorded; no implementation, installation or -product experiment authorized by this discussion. - -- QA is necessary work, not necessarily another standing agent. Defer the - dedicated `qa` proposal; retain the Developer + Reviewer default and native - Build/Plan/Explore/General. Earlier assistant recommendations to register QA - are discussion proposals, not approved changes to the bundle or environment. -- For a later authorized product task, Build may delegate substantial functional - QA to an existing General child session when useful. Developer owns author - tests/repairs, Reviewer independent code/test-adequacy review, OpenDesign - visual implementation/rendered review, and Build integration/technical - completion. No mandatory QA call or duplicate check for valid same-revision - evidence; human acceptance remains separate. -- A short task contract names actual product URL/app build, source revision, - scenarios, permitted test account/data changes, target/session ownership and - return: PASS/FAIL/BLOCKED/NOT_RUN, concrete defects/reproduction, evidence - locations and unverified scope. General's own effective permissions and safe - execution access must be checked; an assignment or Skill is not a sandbox. -- Reuse prepared browser skills/tools. Android/iOS tool/device access is not - assumed; no automatic installs, credential/profile transfers or production - data changes. Playwright Agents remain optional and unadopted as recorded. -- A reusable KSI QA contract Skill is only a consideration, not approved work. - Do not duplicate vendor skills or add it before a recurring gap is evidenced. - Reconsider a standing QA role only if General's repeated setup, inconsistent - contracts or needed permission specialization warrants it after actual use. -- Observe delegated versus Build work, meaningful new defects, duplicate checks, - rework and combined context/time/usage where available in future authorized - development; neither agent count nor isolated parent-context reduction proves - whole-program efficiency. No new pilot, EVENTOUCH intervention, global policy - expansion, service change, push or publication follows from this direction. -- Record-only verification on `main` above `28f08f3`: diff scope is exactly this - ledger and product-state; `git diff --check` clean and the new decision link - resolves. No role/source/config change or new test execution; existing code - verification remains bound to the unchanged implementation snapshot. - -## Approved General model preference and native-methodology discussion (2026-10-09) - -Status: explicit General preference applied and file-verified; user confirmed -the native-first operating direction. Role retirement and external-reviewer -comparison remain proposals, not authorized execution. - -### Actual approved change - -- User explicitly requested Muse Spark `high` (interpreting "hugh" as `high`), - no General-specific step cap, and General remaining multipurpose rather than - an implementation-only replacement for Developer. -- Global `~/.config/opencode/opencode.jsonc`: only General's existing model - variant changes `medium` → `high`, exact reference - `opencode-go/muse-spark-1.3-contributor#high`. General already had no `steps`; - none added. No `system`, role file or permission override added for General. - Native modes, permission rules, existing sessions and other roles' budgets - remain unchanged. Omission is not an unsupported `steps:0`/infinite-execution - override or an automatic continuation/goal authorization. -- Native model discovery confirms Muse Spark 1.3 Contributor active, with `high` - among its supported variants. No model invocation or shared-service restart; - existing child-session models are not forcibly switched. -- Private pre-edit backup/receipt root: - `/tmp/opencode/ksi-general-high-20261009-t0fky6db/` (directory700, files600). - JSONC before SHA256 - `4fd5101a9f31ea8df135ce52c78311a15cbbb9443a90807be5259a8a5c744551`; - after `99b9162c509d405335c14c43bbd49f1084ca7b74bccd427a0ed10110e18e831e`. - Exact one-field byte reconstruction and parsed before/after equivalence - outside General.model pass. Five non-target files hash-identical: - `opencode.json`, global `AGENTS.md`, Developer, Reviewer and installed optional - Test Runner. Evidence `before-hashes.json`, private `opencode.jsonc.before`, - `verification.json`; no credentials/settings contents copied into Git. -- Verification boundary: declared global file plus native model catalog; - effective settings in a newly invoked child and real implementation/QA/review - behavior have not been exercised. No unrelated full-suite replay for this - model preference; source implementation is unchanged. - -### Direction and unapproved follow-up - -- Follow-up user confirmation: maximize native Plan/Build/General/Explore; - use Superpowers for methodology, OpenDesign for visual execution/review and - prepared browser tools for functional verification. KSI focuses on Build's - delegation/integration, product-goal judgment and accurate long-term recovery, - rather than maintaining more agent prompts. This is direction approval, not - a new policy expansion, role migration or comparison-experiment instruction. - Retirement remains conditional on evidence of quality, whole-program cost - and effective permission safety. Implementation and independent review use - separate children; before replacing Reviewer, verify an actual read-only - execution boundary rather than relying on a review prompt alone. -- Prefer native agents and upstream-maintained methodology over duplicating - agent prompts; KSI's value is scoped product orchestration, completion - criteria, evidence, recovery and safe ownership, not agent count. -- Developer retirement is a candidate, not a deletion instruction. General can - receive the existing compact task contract without replacing its native - prompt. Default bundle and installed role/routing files remain intact until - an approved migration with capability/permission and evidence checks. -- Reviewer replacement by General + upstream review guidance is also a proposal. - The installed Superpowers reviewer template supports a general-purpose child - assignment; that does not reproduce KSI Reviewer's native edit/delegation - denials or constrain shell/MCP effects. Keep current Reviewer pending a - compatible review execution boundary; do not make multipurpose General - globally read-only or let it recursively delegate reviews. Any independent - General review must use a fresh child, not the implementation child reviewing - its own work. -- Read-only inspection of the cited Anthropic feature-dev reviewer confirms - Claude-oriented tools, `model: sonnet`, CLAUDE.md conventions and confidence - filtering. No file was imported, modified, installed or adopted, and a mutable - upstream link is not a pinned maintained integration. Upstream compatibility, - methodology quality and update policy need validation before adoption. -- No synthetic comparison or product trial launched; no EVENTOUCH intervention, - new Skill/agent/global policy, permission weakening, install, push, publication - or runtime loop. QA remains conditional General work, not a new standing role. -- Closeout verification above `a095226`: fresh exact-change/parsed-preservation - check passes; five protected non-target hashes and the saved receipt still - match. Private backup permissions confirmed directory700/files600. Git diff - scope is only this ledger and product-state, `git diff --check` is clean, and - the new ledger anchor resolves. V2 agent docs confirm model `#variant` syntax - and positive-only `steps`; no native prompt or role source changes. These - records are local only; previous push approval does not cover this follow-up. -- Native-first confirmation follow-up above `07eeda9`: record-only diff in - this ledger/product-state; whitespace and ledger-link checks pass. General's - current config receipt and five protected global hashes remain unchanged. - No source test replay, migration, comparison run or external delivery. diff --git a/docs/superpowers/plans/2026-10-09-native-ce-transition.md b/docs/superpowers/plans/2026-10-09-native-ce-transition.md deleted file mode 100644 index 04b5790..0000000 --- a/docs/superpowers/plans/2026-10-09-native-ce-transition.md +++ /dev/null @@ -1,825 +0,0 @@ -# Native-first global Compound Engineering transition - -> Execution record for the user's approved operating-environment transition, -> not a new KSI framework or a product-development experiment. - -**Goal:** Retire global Superpowers bootstrap, use native agents plus optional -Compound Engineering `compound-engineering-v3.30.4`, preserve product continuity. -**Architecture:** Native Plan/Build coordinate; General implements/tests; -Explore investigates; retained Reviewer is optional independent review. -OpenDesign keeps all visual source/rendered-review ownership. -**Spec / approval:** User's numbered 1–6 request, explicitly resolved through -the question card as “지금 1~6 전체 전환”. The appended baseline-first discussion -does not defer this approved transition. Installation is authorized; external -push/publication, product experiments and other-session interruption are not. -**Workspace/base:** canonical `main`, `260f53f`; no new worktree, no product writes. - -## Rendered-review contract validation (2026-10-10) - -Status: technically complete - -- Authority: user approved the proposed environment probes, bounded completion - guidance/custom-instruction alignment and one read-only EVENTOUCH comparison - review ("진행해주세요"); interruption recovery continued that same action. - No product source/data, upstream runtime, model, permission, service restart, - Cloud switch, push, publication or deployment changes are authorized. -- Follow-up user direction (2026-10-10, same session): (1) apply design-side - global principles ("Product Design & Frontend Ownership") to the OpenDesign - `customInstructions` setting — product-UX quality plus autonomous completion - responsibility, deliberately without forcing a specific skill, fixed iteration - count, layout style or OpenCode orchestration rules; (2) raise the Build-side - delegation contract — delegate scope, purpose and preservation constraints, not - a presolved defect list or fix direction; the design workspace owns discovery→ - refinement; Build verifies function/contracts/integration and routes remaining - visual defects back to design; business-rule or product-direction changes go to - the user. Applied to `~/.config/opencode/AGENTS.md` (OpenDesign boundary), the - integration contract, notifier guidance and the setting itself. -- Workspace/base: canonical `main` at `91865f7`, clean before work. Native inline - coordination and guidance edits; connected design execution owns visual review. - Existing ledger/checkpoint remain authoritative; no new framework or CE pipeline. -- Private evidence/rollback root: - `/tmp/opencode/ksi-design-ownership-20261010-kes0mjjz/`. Existing global policy - and custom-instruction values backed up; snapshots contain no credentials. -- P1/P2: image-only probe `1b0e4e6a-2604-421d-b4de-8c80935f5980` succeeded in a - newly created, unseeded conversation. Native transcript proves `view_image` - called via `exec`, followed by `input_image` with decoded PNG SHA256 matching - the supplied 390px capture exactly. Native thread has no fork parent and one - turn, distinct from the implementation/review conversation. This validates - captured-image observation and fresh context, not live browser/UX quality. - Normalized daemon events omit this image invocation; missing event text alone - cannot establish that earlier reviewers never saw images. -- P3: built-in image-export probe returned HTTP501, desktop-runtime-only. This - specific renderer is unavailable here; other inner-browser capabilities remain - unverified. Actual product captures plus verified native image consumption are - the supported evidence path for this task; no browser install/server restart. -- P4: the probe and the review run both delivered the project-bound `Design - refine` body as the primary skill, chat mode included; `server.ts:10100–10124` - replaces the primary `skillBody` with the binding. In the actual review run the - requested extra skill WAS delivered after it as `## Composed skill — - web-design-guidelines` (verified in the composed prompt) — composed additions - survive the plugin override when the plugin ships its own SKILL.md. The - reviewer correctly noted the refine workflow's patch step conflicts with the - read-only request and stopped at inspection. No project rebinding or upstream - modification; a requested extra skill must still be verified in the run's - effective prompt before it is relied on. -- Product evidence conflict: initial EVENTOUCH baseline was `1f624be`; subsequent - unrelated work modified console CSS, list/checkin sources and browser tests. - Preserved untouched. Earlier rebuild review `4c074ba6` references overwritten - capture hashes and an older source snapshot; recovery scanning (1,159 PNGs, - zero hits) confirmed those inputs are gone. The review therefore used the - current frozen snapshot with explicit labels — no same-revision comparison or - current-product quality is claimed from reused filenames — and the product's - own ledger was not modified. -- Custom instructions (setting, applied 2026-10-10): a draft was applied first - (`od-setting.after.json`), then replaced by the user-supplied text - (`design-custom-instructions.user.md` → `od-setting.user.json`; 4,434 chars, - readback exact, other config fields hash-unchanged; original `null` preserved - in `od-setting.before.json`). The review run's composed prompt carried the - then-active draft (subject-markers verified); runs after the replacement carry - the user text. Setting text is user-owned configuration, not duplicated here. -- Review validation (`fb86129d-b2a7-47a2-96d4-9910e6749e16`): succeeded in a new - unseeded chat conversation `ef6e0260`. `no_artifact` is expected for a - read-only review, not a failure. Native verification: new thread `01a1263d` - (no fork parent, one turn, model gpt-6-luna); all six frozen capture hashes - decoded from the reviewer's `input_image` data exactly matched the expected - captures (`exactCapturesConsumed: true`); composed prompt 39,374 bytes - (sha256 `1ade54b3…`) contained the review task, the plugin-bound body, the - composed `web-design-guidelines` block and the draft custom instructions. - Product tree unchanged (status normalized + frozen file hashes; receipts in - `review-receipt.json`/`review-receipt-extra.json`; composed prompt saved). -- Design-reported open defects (captured states at the 2026-10-10 03:28 - list-ops revision; reported, not adjudicated here): (1) `현재 입장` metric not - prominent and the single check-in action not integrated into the tile - (390/320 evidence); (2) no recent-activity rows or full-history link despite - the page contract (all captured widths). The reviewer recorded the console.css - working-hash difference from the captured revision and could not confirm the - exact stylesheet revision used; unverified: loading/error, axe/screen-reader, - reduced-motion, physical devices, non-overview chrome, routes/permissions. - The earlier rebuild review's "no open defect" result stays historical (its - captures were overwritten); no same-revision comparison was possible. The - findings belong to the product workstream; no product source/data was touched. -- Verification: `TMPDIR=/tmp/opencode npm run check` — 220 total, 218 pass, - 0 fail, 2 existing optional skips (final state after all edits); notifier unit - file 35/35 pass after red→green updates; `npm run check:package` passed - (installer-only tarball, offline two-role install, retired flags rejected); - `git diff --check` clean. No runtime code, model, permission, service or - upstream-source change; no push, publication or deployment. -- Delivery/next: local commits `a2a5816` + `0428813`; pinned notifier copy - updated to source `0428813` (`--verify` ok; only `README.md` and - `lib/messages.js` differ from the prior copy — previous copy backed up in the - evidence root) and the managed service acknowledged `opencode reload`. Next: - one real delegated design improvement observed against the user's criterion — - how many user-facing problems the design workspace finds and resolves - autonomously without extra user direction. EVENTOUCH findings and the - reviewer's `od-next` suggestions remain advisory to the product workstream. - Resolved 2026-10-12: subsequent design-run notifications delivered with the - updated guidance text (steps 2a/3/3a/4/7), so the pinned copy is effective. - -## Autonomous design delegation validation (2026-10-12) - -Status: technically complete — awaiting the user's evaluation/acceptance - -- Authority: user's validation directive — keep the three harness commits, no - further harness structural changes before this validation, run one real design - delegation on the EVENTOUCH operations home with purpose + preservation - constraints ONLY (no presolved defects or fix directions), measure autonomous - discovery/completion, keep validation separate from remote sync, and work in - an isolated environment. No push/publication/deployment; no product-branch - writes; product WIP from the other workstream untouched. -- Isolation: new worktree `.worktrees/eventouch-ops-validation` (branch - `design/ops-autonomy-validation`, base `1f624be` = the frozen capture - revision); new OD project `EVENTOUCH Ops Validation (isolated)` via - `POST /api/import/folder`. The folder was registered in the remote-workspace - picker state (`connections.json`) + gateway restart WITHOUT container - recreation (the `/home/ksi/Desktop` mount already exposed the path); read and - write access probed (write→read→delete). Isolated fixture served the worktree - on port 46779 (same fixture DB schema; env incl. `EVENTOUCH_DEV_QUICK_LOGIN`). -- Delegation: brief carried purpose, target route, context pointers and - preservation constraints only (`validation-brief.txt`); every previously found - UX problem and solution direction was excluded. -- Runs and independent receipts (source-direct; `no_artifact` expected): - - `9ba65814` (implementation, refine pipeline inherited): autonomously - discovered the missing live check-in activity signal; test-first regression - test; `experience.js` +6/-3, `console.css` +16/-3 (최근 입퇴장 section: max 2 - stored records, name/IN-OUT/short time/correction badge, 전체 기록 보기, - `canChecks` gate; non-LIVE keeps the menu tile). Disclosed its own render - limitation honestly. - - Host capture r1 → `3fb36c37` (rendered review, fresh conversation): 12/12 - before/after captures consumed with exact hashes; delivered prompt carried - the plugin body + composed `web-design-guidelines` + the user's global - principles text; found one P2 (mobile activity metadata 11px < shared 14px) - and no other defect; disclosed the read-only/refine conflict. - - `cff755fa` (refinement): mobile override → `var(--small)`; focused test 1/1. - - Build objective re-verification: metadata 14px at 320/390/1280, no overflow, - all controls ≥44px, history destination (`checks`) renders; r2 captures - (`validation-r2-*`; the 1280 file is byte-identical to r1). - - `c7251c98` (re-check review, fresh conversation): 6 images consumed; - verdict “P2 closed — no open rendered defect in these states”. -- Evaluation (the user's frame; corrected 2026-10-12): the implementation run - autonomously discovered one user-facing defect (the missing live check-in - activity signal); the second defect (mobile activity metadata 11px < the - shared 14px token) was introduced by that implementation and caught by the - host-coordinated independent rendered review. Both were fixed and reverified. - Not addressed by this pass: the earlier read-only review's “현재 입장 tile - integration” item (design chose its own priorities within scope). - Design-decision requests from the user: 0 — but the cycle was host-assisted - (fixture serving, captures, review commissioning) and included user - continuation/error prompts, so it is not evidence of fully hands-off - autonomous completion. Functional regressions: none observed in the checked - scope — `npm test` 327/155/0/172 (172 skipped for unavailable isolated - PostgreSQL test config), route/permission/metric/stage logic untouched, - other-screen spot captures normal. Unverified: loading/error/empty-data - states, axe/screen-reader, reduced-motion, physical devices, some - route/permission variants. Note: the 1024 “before” capture differs from the - Oct-10 frozen set (re-captured by the other workstream at 13:21; 5 of 6 - byte-identical) — recorded, not used as a same-revision claim. -- Result freeze: isolated-branch commit `c8f65ba` (3 files, +78/-3). Not merged - or pushed; the product branch and its WIP untouched. The OD project and the - worktree remain for inspection; the validation fixture was stopped after use. -- Operations note (corrected 2026-10-12): tool-call interruptions occurred while - other activity shared the same evidence area. The observed cancellation - notices read "Command cancelled because the server restarted", so the - concurrent-driver cause was not conclusively established; keep single - command/single driver discipline for shared areas and record an - interruption's cause as unknown when it is unproven. A parallel session's - traces (OpenChamber/4096 probing, one fixture start and a page probe) appeared - in the shared evidence root on 10-11 19:23–19:46 and an untracked full - `design-inputs` copy landed in the isolated worktree at 19:33 — preserved, - not attributed to the design runs. -- Harness improvement proposals — APPLIED 2026-10-12 on the user's approval - ("그래 하네스 개선해봐"), docs-only: (1) the host-capture handoff recipe is now - in the integration guide (serve/verify-entry/capture-naming/receipts/reuse - rules); (2) the review-receipt schema (thread identity, delivered-instruction - markers, consumed-image hashes, source hashes, verdict, fixed/open list) is - recorded as standard practice in the same guide; (3) the four evaluation - criteria are recorded as the standing delegation-evaluation frame; (4) the - single-driver note for shared working areas is in `docs/execution.md`. No - installer, runtime, agent or permission changes. - -## Harness audit corrections (2026-10-12) - -Status: technically complete - -- Authority: user direction — explain the harness and audit docs/README/other - parts, then apply the identified corrections and legacy cleanup ("모두 - 개선해주세요 레거시나 더이상 안쓰는 거 정리 및 정정"). Local commits only; no - push/publication/deployment. -- Record accuracy: the evaluation and operations note in the section above were - corrected; working-state and product-state summaries reconciled with the - 2026-10-12 state (host-assisted cycle, bounded regression claim, unproven - interruption cause, ahead-of-origin delivery state). -- Diagnostics: `scripts/work-doctor.mjs` no longer treats the optional - Developer/Reviewer bundle as a required prerequisite — a native-first - environment reports `unverified` instead of `needs-setup`; regression test - added in `test/work-doctor.test.mjs`; the doctor description in - `docs/execution.md` updated. -- Docs: packaged-doc links fixed (`docs/integrations/opendesign.md`, - `docs/verification.md`) with a new packaged-link guard - (`test/docs-links.test.mjs`); notifier resume wording aligned to the - implementation ("eligible on a later poll tick, subject to existing retry - delays"); capture-recipe claims reframed as observed cases, not general - causes; the architecture opening distinguishes the npm package from - repository-only records; README, `examples/project-AGENTS.md` and - `examples/autonomous-development.md` audit-first phrasing aligned with the - design-owned discovery contract; the shipped Developer prompt no longer cites - Superpowers; the deployment record points to the current host-side MCP proxy. -- Legacy cleanup: unused retired-flow asset `.github/assets/pipeline.svg` - removed (recoverable from Git history). `.gitignore` was reviewed and left - unchanged: the ignored `design-previews/` material is still local review - content. -- EVENTOUCH write-path check (2026-10-12, on user request): daemon and container - healthy; all three EVENTOUCH paths connected, mounted rw, and a container - write→read→delete probe succeeded on each; recent product runs edited real - files (mtimes inside their run windows) and the validation diff is on disk. - Corrected the guide's writeback check: `workspace.storage.kind: od-owned` is - not a regression signal — working runs report it too; the check now uses the - actual file change plus a probe when in doubt. Observation for a user - decision: `/home/ksi/Desktop` is still a connected root and an rw mount in - the container although the 2026-10-06 record says the parent bind was - reverted; nothing was changed without approval. -- Checks: `npm run check` — 222 total / 220 pass / 0 fail / 2 optional skips; - `npm run check:package` passed; `git diff --check` clean. Documentation, - template and read-only helper changes only: no runtime, provider/model, - permission, service, release or external-delivery change. - -## Remote-workspace root coverage (2026-10-12) - -Status: technically complete — applied live 2026-10-12 - -- Authority: user direction — first "우회보다는 허용이 낫지 않나" (replace the - exact-path workaround pattern with root-based allowance), then "그래요 - 해봐요". This step changes source, tests and docs only; the live gateway - restart and the `/home/ksi/Desktop` cleanup await separate confirmation. -- Change: `connector.isConnected` now treats a connected canonical project root - as covering its canonical subdirectories (`withinRoot` exported from - `path-policy.mjs`; path-relative, so sibling name prefixes never match), and - `connectProject` returns `ready` without recreation for an already-covered - path — the manual `connections.json` edit the validation needed is no longer - necessary. Guards unchanged: canonical realpath resolution, credential roots, - home root, symlink escapes, active-run refusal, verified rollback. -- Rationale: the container already mounts a connected root's whole tree, so the - former exact-path check added no protection inside a mounted root while - forcing manual state edits and one recreation per worktree. The mount is the - real boundary; the still-present `/home/ksi/Desktop` root and rw mount is the - broader exposure and is to be removed in the live step. -- Tests: `connector.test.mjs` +2 — a root covers its canonical descendants - without new mounts and after reload; a sibling sharing a path-name prefix is - not covered and still recreates. -- Checks: `npm run check` — 224 total / 222 pass / 0 fail / 2 optional skips; - `git diff --check` clean (source checks at commit time; live application and - verification recorded below). -- Live application (applied 2026-10-12, 0 non-terminal runs): private backups - of the live modules and state in - `~/.local/state/ksi-harness-backups/remote-workspace-state-20261012/` (with - SHA256SUMS); the updated `connector.mjs`/`path-policy.mjs` copied into - `~/.local/share/od-remote-workspace/`; gateway user service restarted - ("Remote workspace gateway ready", 127.0.0.1:17456 listening); - `/home/ksi/Desktop` and the two redundant EVENTOUCH-worktree entries removed - from `connections.json` and `projects.compose.json` (paths 9→6, volumes - 8→6); one container recreation (a first compose attempt failed on the missing - token env with no partial state; retried with the service env file). -- Live verification: mounts = connected roots only (Desktop gone; the EVENTOUCH - root covers its worktrees); container write→read→delete probes OK on the - EVENTOUCH main worktree, `eventouch-postgres` and `eventouch-ops-validation`; - internal `connected` = true for the unregistered ops-validation path - (descendant coverage, no manual entry) and false for an unconnected project; - security profile retained (CapDrop=ALL, CapEff=0, NoNewPrivs=1, seccomp + - `ksi-codex-userns` AppArmor, not privileged); MCP `list_projects` returns all - projects. -- Follow-up: a supported disconnect/cleanup endpoint remains a follow-up; - connection removal is still a documented operator edit. - -## CE philosophy alignment (2026-10-12) - -Status: technically complete - -- Authority: user direction — "설치한 것들에 맞게 맞추는 게 맞겠죠? CE를 쓰면 CE" - and "CE의 철학에 맞추고 중복 충돌 및 불필요한 거 걷어내면서 개선해봅시다". - Operating guidance and docs only; no tool, runtime, permission, service or - external-delivery change. -- Principle: the selected working method owns its mechanics. KSI keeps purpose, - approval boundaries, preservation and cross-tool handoffs; it no longer - prescribes branch/worktree mechanics over a running CE workflow, no longer - mandates a parallel KSI ledger when the method produces its own record, and - no longer blanket-rejects the shipping workflows' documented effects. -- Live global `~/.config/opencode/AGENTS.md` (private backup and before/after - SHA256 in `/tmp/opencode/ksi-ce-align-20261012/`; before `e1b9e959…d8d5`, - after `04ad60d4…4840`; diff = exactly the four intended hunks): CE - method-governance sentence, one-record rule, workspace-mechanics rule, - shipping-invocation authorization. Effective immediately for new turns. -- Repository (this commit): `examples/autonomous-development.md` plan bullet, - shipping bullet and workspaces section compressed/aligned (8 bullets → 3); - `docs/execution.md` workspace-lifecycle section compressed to what stays - with Build regardless of method, record table + canonical-record paragraph - aligned, shipping-invocation authorization added; - `examples/project-AGENTS.md` record pointer; `README.md` workspace - paragraphs. -- Unchanged boundaries: deployment/registry publication/new credential access - approvals; OpenDesign ownership; preservation of user work; one writer per - shared file. -- Checks: `npm run check` — 224 total / 222 pass / 0 fail / 2 optional skips; - `git diff --check` clean. -- Follow-up (only on observed need): the status helper parses only - `docs/superpowers` ledger pointers; if CE artifacts become primary records in - a project, extend the pointer or accept an unknown status then. - -## Git policy alignment with Compound Engineering (2026-10-12) - -Status: technically complete - -- Authority: user direction — align the branch/worktree/commit policy with - Compound Engineering v3.30.4: feature-branch default with reuse, worktree - only for actual isolation, Build owning Git integration, no new KSI - plugin/agent/registry, no double review, existing approval scope preserved. -- Comparison performed against the installed CE 3.30.4: `ce-work` - `references/workspace-setup.md` (default branch → create a feature branch; - otherwise continue on the invoked branch without renaming; a worktree only - when the user asked this session; pre-work dirty files are recorded and never - committed/published), `references/execution-strategy.md` (worker isolation is - the method's own concern; the orchestrator owns canonical commits), - `ce-worktree` (detect existing isolation first; one branch checked out in one - worktree), `references/shipping-workflow.md` (review-receipt gate; push/PR as - documented effects; merging stays with the user). -- Differences found and fixed (minimal edits): (1) KSI said the native flow - "works in the current checkout" without a branch default — now a feature - branch is the default (created from the default branch when needed, the - workstream's existing feature branch reused), and a worktree is only for - actual isolation (concurrent writers/sessions, parallel development, checkout - protection); (2) nothing prevented running ce-code-review and the separate - Reviewer on the same change — now explicitly one or the other; (3) merge was - unspecified in the shipping authorization — now "merging stays the user's - decision unless separately granted". A CE workflow's own branch/workspace - handling (including internal worker isolation) owns that work; no KSI - workspace procedure is layered on top. -- Applied: live global `~/.config/opencode/AGENTS.md` (exactly 3 hunks; private - backup and before/after SHA256 in `/tmp/opencode/ksi-git-align-20261012/`; - before `04ad60d4…`, after `ad9436c8…`); repository - `examples/autonomous-development.md` (workspaces bullet, self-review bullet, - shipping bullet), `docs/execution.md` (workspace-lifecycle intro, - review-depth paragraph, external-delivery paragraph), `README.md` (branch - paragraph, review sentence). -- Unchanged: no new plugin/agent/registry; no CE upstream edit; local-commit - and preservation rules; a CE workflow's push/PR effects and the - deployment/registry/credential gates. -- Checks: `npm run check` — 224 total / 222 pass / 0 fail / 2 optional skips; - `git diff --check` clean; global diff reviewed hunk-by-hunk. -- Remaining verification: real-use observation only — the next actual product - task should confirm feature-branch reuse and worktree-when-needed behavior; - no code or runtime check is pending. - -## Native-first consolidation: removals (2026-10-12) - -Status: technically complete - -- Authority: user direction — "OpenCode가 이미 제공하는 기능은 네이티브에 맡기고, - CE가 잘하는 것은 CE에 맡기며, KSI에는 우리 제품 개발에만 필요한 책임과 판단 - 기준을 남기는 것이 좋겠습니다" and "명시적 비활성화보다는 쓰지 않거나 - 불필요하거나 안쓰면 제거해버려" (remove, do not disable). -- Runtime removals (outside git; private backups preserved): - `~/.config/opencode/opencode.jsonc` — the developer/test-runner/reviewer - entries deleted entirely (no disabled flags remain; agents now plan/explore/ - general; before `b1c61da4…`, after `351be069…`); `~/.config/opencode/agents/` - — all eight archived role files moved to - `~/.local/state/ksi-harness-backups/agent-removals-20261012/` (config agents - dir now empty); `~/.agents/skills/ui-ux-pro-max` removed from discovery and - archived at `~/.local/state/ksi-harness-backups/ui-ux-pro-max-20261012/` - (live catalog confirmed the skill is no longer available). -- Policy/docs: independent review runs through the CE review skill - (ce-code-review); the separate KSI Reviewer seat is removed and the - double-review rule is gone with it. The KSI parallel-development procedure is - removed — a CE workflow follows its execution strategy and native runs use - OpenCode's subagent capability; Build integrates and owns the final claim. - OD guidance no longer assigns any review role to UI-reference skills. Docs - swept: global AGENTS.md, README, architecture, execution, opendesign, both - examples, troubleshooting. -- Principle recorded: Native-first → existing specialized tools (CE) → only - problems neither solves get a minimal KSI rule. -- Checks: `npm run check` — 224/222/0/2; `git diff --check` clean; JSONC parse - verified (agents: plan, explore, general). -- Remaining verification: effective catalog of the shared server (skill removal - auto-reloaded; agent-config effect to confirm on next catalog inspection) and - real-use observation in the next product task. -- Follow-up (same day): effective catalog verified via the local API — agents = - build/plan/general/explore (reviewer absent); skills = 59 total, 34 `ce-*`, - ui-ux-pro-max absent. The shipped example policies - (`examples/autonomous-development.md`, `examples/project-AGENTS.md`) were - compressed: the OpenDesign and delegation sections now keep the KSI-specific - ownership, gates and evidence distinctions and defer procedure to the - integration/execution guides instead of restating them. - -## Current operating alignment (2026-10-10) - -Status: technically complete - -- Authority: the user approved the proposed bounded inconsistency repair - ("그래요 불일치 등 개선해주세여") after the read-only findings. This is a - documentation/checkpoint exception to the observation freeze, not approval for - a new runtime, Goal reinstall, agent/skill/model/permission changes, product - intervention or remote delivery. -- Workspace/base: the existing canonical `main` checkout at `6f42519`, clean - tracked/index/untracked state before work; 14 local commits ahead of cached - `origin/main` (`1840aad`). No fetch, push, new branch/worktree or other-session - intervention. Native inline execution follows the host's current-checkout - policy; this coherent documentation repair does not need a delegated worker. - CE work guidance is used only for scoped implementation/local verification; - no standalone shipping or cross-model engine is commissioned. -- Observed inconsistencies: checkpoint header/body HEAD disagreement; removed - Goal pause/behavior checks still listed as current work; preparation-only - outcome and pending-cutover backlog wording; operational docs still preferring - Developer/Superpowers and creating/resuming goals. The existing status helper - read this ledger's status as `unknown` before repair. -- Scope: README/INSTALL, architecture/execution guidance and their two optional - policy examples, current product-state and the ignored checkpoint. Clarify - current native General + optional CE operation separately from the unchanged - Developer/Reviewer source installer and historical three-role npm beta.8. - Keep the older General-delegation heading anchor for historical links. -- Current decisions: Build selects methods without a user-selected A/B/C mode, - forced labels or a no-mixing rule. Descriptive method/skill usage may be - recorded for observation, and material cost/scope/security changes still need - approval. The earlier A/B/C comparison wording below is historical and does - not override this agent-selected-method contract. -- Goal evidence clarification: removal verification established a valid v2 - state file and active1/complete2 counts, not field-by-field invariance. A - changed hash and one-byte size difference do not prove that the only change - was periodic accounting. Preserved `active` records are inert on the verified - Goal-free service and need no pause/clear/reinstall. Other listeners remain - unverified, not proven to share that effective state. -- Verification contract: no runtime-code behavior change or new tests required; - use existing source/package tests, local Markdown-link/anchor and statement - reconciliation, and the read-only work-status helper. Refresh the checkpoint - to the resulting Git HEAD at closeout. Runtime catalogs from 2026-10-09 are - dated prior evidence, not a new live/model/continuity/product acceptance test. -- Outcome: aligned current General/optional-CE instructions and removed - operational Goal-create/resume advice from the active guides/examples; - retained installer commands, role templates and beta.8 version pins. Corrected - pending-cutover backlog/current-cache wording and marked preparation outcomes - historical. The ignored checkpoint selects this exact ledger section and uses - one HEAD field rather than a second stale body revision. After this repair, - structural/runtime freeze and real-use observation remain the default. -- Verification (2026-10-10, base `6f42519` + the eight task-owned Markdown diffs): - `TMPDIR=/tmp/opencode npm run check` — 217 total, 215 pass, 0 fail, 2 existing - optional skips (real isolated Compose, auto-only final-summary overlay). - `TMPDIR=/tmp/opencode npm run check:package` — passed: offline disposable - two-role consumer, removed flags rejected, legacy role/routing preserved. - Local Markdown-link/anchor inspection — 64 links across all eight changed - documents, 0 broken; both current General and legacy Developer anchors valid. - Statement/checkpoint assertions passed (Goal-free resume, agent-vs-skill - boundary, no stale body HEAD/pause request, checkpoint <=80lines/6KiB). - `git diff --check` passed. Runtime/installer/template/script/test/manifest - paths have no diff from the base; no global configuration was edited. -- Record/status evidence: before repair, `work-report --status --json` returned - ledger status `unknown`; with the explicit current-section selector, read-only - assertions returned `in progress` during work with zero warnings and no - publication/deployment inference. Closeout selects `technically complete`; - the checkpoint HEAD and clean-tree state are refreshed after the local commit. -- Evidence strategy: documentation/cache only, no behavior-bearing runtime code - changed; existing tests used unchanged and no new/red test claimed. Scoped - local assertions and source/package checks replace a new prose-only test - suite. No independent Reviewer, provider-backed General/CE execution, real - product/browser test or fresh-session resume experiment was performed here. - Faster development, improved search decisions and autonomous completion are - still observation questions, not established by these checks. -- Delivery/next: task-owned local commit only; checkpoint stays ignored. No - push/PR/publication/deployment, service reload/restart, model/permission/plugin - change or product task commissioned. Next is an authorized product goal in - its own existing record; OpenChamber/4096 and CE-path compatibility stay - separate, approval-gated work. - -## Preserved contracts - -- Do not change native Plan/Build/General/Explore system prompts, other models, - permissions, MCP, unrelated plugins, or another tool's independent install. -- Preserve `docs/superpowers/`, `.superpowers/sdd/`, PRDs, plans, evidence, Git - and state-script paths. No project `ce-setup` or generated document by default. -- General remains multipurpose, Muse Spark high, without a General-specific cap. - Preserve Developer/Test Runner definitions and routing before disabling them; - keep customized Reviewer and its independent read-only contract. -- Skills are optional tools, not a mandatory whole pipeline. CE cannot authorize - unapproved push/PR/deploy, code disclosure, extra model/service use or installs. -- Do not interrupt/pause another session or Goal. Cutover requires a safe owning - session boundary and an authenticated route to the actual serving process. -- No npm release, default-bundle rewrite or new custom agent/skill in this task. - -## Survey / verified evidence (2026-10-09) - -- Host OpenCode `2.0.24`. Managed service PID796278 has no foreground drains at - lookup. Its authenticated native CLI reads confirm global sources - `/home/ksi/.config/opencode/opencode.json`, `opencode.jsonc`, that directory's - agents/plugins and `/home/ksi/.opencode` discovery. Superpowers is active, - 15 upstream skills among 39; no CE skills. Goal/notifier plugins are active. -- General effective model = Muse Spark 1.3 Contributor high, no `steps` or system - override. Developer = same model, steps80; Test Runner = LongCat, steps24; - Reviewer = Muse Spark xhigh, steps32. Native General and Developer both deny - child delegation; they are not identical profiles. Keep assignment scope, - author-test evidence and fresh independent-review children; do not copy the - Developer system prompt or its finite cap into General. -- OpenChamber app PID3021 owns OpenCode PID4902. Launch metadata selects - `/home/ksi/.config/openchamber/opencode.managed.json`; this supplements, not - substitutes for proving, actual resolved global config. It contains browser - exclusion and the app's agent-tool plugin, no role/skill overrides. - Bare explicit-server/proxy calls return401. Recovered through the installed - official CLI's existing authentication client (`cli-http.js:requestJson`), - without manual credential extraction, copied headers or shared passwords. - Actual app proxy confirms PID4902/version2.0.24 and both global documents plus - its managed document. It has no foreground drains at lookup; Site's official - session-status command reports idle, but the native Goal remains active. -- OpenChamber catalog discrepancy: root and explicit harness/Site/EVENTOUCH - locations return no plugins, only native unconfigured agent profiles and two - skills, despite configuration discovery naming the global documents. Explicit - location resolution is confirmed; the wrong-location hypothesis is rejected. - Do not infer effective global model/skill activation from source documents, - or compensate with project-local copies. Source/router/cache cause remains - unverified; investigate supported scoped reload only at the safe boundary. -- A third host listener, PID195177 at port4096, also uses the host home/config - defaults. Its explicit native client requires authentication; it is not the - discovered managed service. Preserve it, do not restart it or extract secrets. - Its live drains/catalog remain unverified. The separate design-container - process has a different home and is not a host-global migration target. -- Native goals: Site Intelligence session `ses_f0e6f0de9ffeYR7ZKuiu9uth3z` is - active; Safety Edge is budgetLimited; EVENTOUCH goal complete. Project-list - design statuses are terminal/not-started; notifier has two stale failed - requests with no run IDs, not confirmed in-flight executions. No switches changed. -- Site Intelligence main is clean, 150 local commits ahead at lookup. Safety - Edge has tracked/untracked Replay changes; EVENTOUCH PostgreSQL worktree has - untracked design evidence. Preserve all; no ownership transfer or intervention. -- Inspected OpenCode skill/plugin roots, compatibility skill roots and legacy - manifests show no active Superpowers symlink or package-manifest dependency. - The native package plugin is the confirmed registration source; caches and - unrelated tool installs are not authorized cleanup targets. -- Official release tag resolves to `efcb657d9a5733ccc154c36cc7d3d78b4136eb23`. - Tag-specific installer/plugin source confirms a V2 `setup` registering skills - and commands, without a controller bootstrap. Its package version is3.30.4; - ce-ideate/ce-explain/ce-work/ce-compound are present with sibling-reference - dependencies. Static evidence, not installation or runtime compatibility proof. -- Private backup/evidence: - `/tmp/opencode/ksi-native-ce-transition-20261009-x32huz_j/`. - Seven owner-confirmed configuration/policy/role files backed up; manifest - preserves SHA256, UID and modes; owner-private directory/files. - Authenticated native API captures are private. CLI stdout must target a regular - file for large responses; pipe captures truncated skill/OpenAPI JSON and are - not accepted as catalog evidence. No sensitive contents copied into Git. - -### Extended dependency inventory (2026-10-09, later read-only pass) - -- `opencode plugin list` confirms exactly three active plugin sources: pinned - `superpowers` (JSONC entry), `local.goal-mode.server` 0.1.54 (goal plugin) and - local `ksi.design-notifier` auto-discovered from `~/.config/opencode/plugins/` - (not a JSONC entry). Compound Engineering is not installed yet. -- The resolved skill catalog is 39 skills: 15 superpowers; built-in `opencode` and - `report`; 17 discovered from compatibility roots — 8 KSI (client-intake, contract, - design-proposal, labor-report, project-docs, proposal-message, quotation, - tier-proposal) plus docs, docx, google-workspace, import-memory, morning, pdf, - pptx, skill-creator and xlsx from `~/.claude/skills/synced//`; `grill-me` - and `ui-ux-pro-max` from `~/.agents/skills`; `agent-browser`, `mobbin-design` and - `playwright-cli` from `~/.config/opencode/skills`. No duplicate IDs observed. - Global JSONC has no `skills` array; discovery is via roots only. -- Official V2 docs confirm the discovery set (global `~/.config/opencode/skills`; - compatibility `~/.claude/skills` and `~/.agents/skills`; project - `.opencode|.claude|.agents/skills`; explicit `skills` arrays) and plugin discovery - including the global plugins directory. `opencode debug paths|config|agents`, - `opencode plugin list` all work against the managed service. -- Active agents: build/plan (primary), general/explore/developer/reviewer/test-runner - (subagents) plus hidden built-ins. Test Runner's residual global file and routing - are confirmed active. `design.md`, `design-critic.md` and `research.md` exist only - as inactive `.bak-*` files in the global agents dir; their final disposition is a - cleanup-stage item, not an active role. -- Active MCP servers in resolved config and live session tools: context7, mobbin, - opendesign (od-mcp-host proxy), playwright. `opencode mcp list` returned - "No MCP servers configured", disagreeing with both the resolved config and the - live tools; do not use that command as the active-MCP source of truth in 2.0.24. -- Candidate corrections: `impeccable-design-polish` and `web-design-guidelines` are - not global installs — they exist only as OpenDesign project assets in - `EVENTOUCH .../.od-skills/` (with design-review, frontend-design, od-design-refine), - used by current runs; preserve. `frontend-design` is a Claude Code official plugin - cache under `~/.claude/plugins/`, not OpenCode-discovered. GStack is not installed - in any user config root; only OpenDesign's own catalog references it upstream. - `ui-ux-pro-max` is active via the shared `~/.agents/skills` root; OpenCode-side - de-listing should use a `skill` permission deny rather than deleting a shared - install, and stays a separate single-variable decision from the SP/CE switch. - '고리' is not present in the active inventory; identification is still pending. -- Goals re-check: Safety Edge and EVENTOUCH goals are complete; the Site Intelligence - goal remains active — cutover gate unchanged. No runtime, config, product or - shared-install change occurred in this read-only pass. - -## Ordered execution and checks - -- [x] **1. Survey and backup:** real process/config identities, effective native - catalog/routing, goals/designs, foreign Git status, owner/private backups. - Actual OpenChamber reads recovered; catalog discrepancy and the third listener - are explicit unresolved survey limits. Backups revalidated after interruption. -- [x] **2. Confirm safe cutover:** superseded by the user's explicit “모두 개선해줘” - instruction. Applied while the Site Intelligence goal stayed active (its session - idle); a formal pause in that owning session is still recommended (session-scoped - tools cannot pause another session's goal). Listener4096 stayed unverified; - OpenChamber catalogs rechecked (still separate/empty). Applied at the owner's - instruction with backups and rollback bytes in place. -- [x] **3. Apply backed-up global cutover:** done — Superpowers entry replaced by - pinned CE v3.30.4; goal plugin pinned0.1.54 with `auto_continue:false` added; - cli.json entry pinned; Developer/Test Runner disabled via `disabled:true` plus - agent-file rename (`agents/*.md.bak-1791546420134`) because config-only `disabled` - did not suppress file-based re-registration; Reviewer untouched. -- [x] **4. Simplify global policy:** done — the prepared native-first AGENTS.md - applied with two recorded updates (delegated-design direction default; Goal - auto-continuation default off). No native prompts changed. -- [x] **5. Refresh and verify actual servers:** done at catalog level — - `opencode reload` → “Configuration reloaded”; plugin list shows compound-engineering - `efcb657` (matches the verified tag), goal0.1.54, notifier; `/api/skill`60 with - 34 `ce-*`, zero superpowers; `/api/agent` has no developer/test-runner and no - superpowers text; `/api/command`41 including `ce-*`; MCP keys unchanged; - behavioral auto-continuation-off confirmation still pending idle observation. - OpenChamber-managed server still returns an empty catalog (process env: - `OPENCODE_CONFIG=…opencode.managed.json`, `OPENCODE_DISABLE_CLAUDE_CODE`); - pre-existing, needs a user decision before touching that server. Listener4096 - remains password-protected and untouched. -- [x] **6. Cross-project smoke and closeout:** catalog-level smoke in - KSI-Site-Intelligence-Platform and KSI-Binance-Trader — both show the identical - global60-skill catalog (34 `ce-*`, `lfg`, no superpowers) with no per-project - install or generated docs. Repo checks: `npm run check`217/215/0/2 (the same two - optional skips); the updated notifier test passes. Closeout in this ledger, - product-state and checkpoint; efficiency, product acceptance and external - delivery remain unclaimed. - -### Applied change inventory and rollback map (2026-10-09) - -All applied changes are outside Git; byte copies + SHA256 are in the evidence root -(`pre-cutover-*`, `pre-cutover-manifest.json`). - -- `/home/ksi/.config/opencode/opencode.jsonc` → plugins (CE pin, goal0.1.54 with - `auto_continue:false`), agents developer/test-runner `disabled:true` + comments. - Diff vs backup: 16 changed lines only; MCP/other sections untouched. -- `/home/ksi/.config/opencode/cli.json` → goal entry pinned0.1.54. -- `/home/ksi/.config/opencode/AGENTS.md` → native-first replacement (~6.1KB). -- `~/.config/opencode/agents/developer.md`/`test-runner.md` → renamed to - `*.md.bak-1791546420134` (recoverable copies also in the evidence root). -- Repo consistency (delegated-direction default): `docs/integrations/opendesign.md` - (3 spots), `README.md`, `docs/walkthrough.md`, `examples/project-AGENTS.md`, - `examples/autonomous-development.md`, `plugins/design-notifier/README.md` and the - delivered notifier guidance; `test/design-notifier-messages.test.mjs` updated to - the new contract; all covered by `npm run check`. -- Rollback: restore the pre-cutover globals (or per-file copies), rename the agent - files back, revert the repo commit and run `opencode reload`. - -### Goal plugin verified facts and safe-disable plan (2026-10-09) - -- User-recommended direction recorded: default global operation without goal - auto-continuation, observe whether it is missed, re-enable only on repeated real - need. `ksi.design-notifier` is a separate system and stays untouched. -- Verified installed artifact: `@prevalentware/opencode-goal-plugin` v0.1.54 - (plugin id `local.goal-mode.server`; cache also holds0.1.49). Both configs use - the unpinned `@latest` tag — consider pinning the verified0.1.54 when the entry - is next edited. Configured in JSONC (server options; currently only - `restricted_agents:[plan]`) and in `cli.json` (TUI list). -- Verified behavior in the installed dist: idle continuation with `auto_continue` - default true; no-progress pause (50-token floor, 2 turns); auto-turn/elapsed/ - token budgets with wrap-up; close prompts require an audit of real artifacts - ("close with evidence or blocker"); `MAX_CHECKPOINTS=8`, - `CHECKPOINT_CHAR_LIMIT=280`. The user's summary is accurate — continuation plus - re-audit reminders, not blind repetition. -- State: `~/.local/share/opencode-goal-plugin/goals.json`,154638bytes, mode `0600`, - owner uid1000, keys archives/goals/version2 (v3 migrates on next write). Preserve - this file (private copy + hash) before any plugin edit; never downgrade the - plugin against it without isolating it. -- Safe procedure at the authorized boundary: (1) pause active goals in their owning - sessions — session-scoped tools cannot pause another session's goal from here; - the Site Intelligence goal is the current one and its pause also clears a - transition gate; (2) take a private copy + hash of the state file; (3) apply the - soft lever `options.auto_continue:false` in JSONC (keeps commands/tools/state; - one-line and reversible) rather than removing the plugin entry; (4) verify no - auto-continuation in a fresh session and that goal data/history remain intact; - (5) observe friction; full entry removal from JSONC+cli.json stays a later - decision, not this step. -- Attribution note: turning Goal off inside the same window as the SP→CE switch - makes any reduction in re-entry/verification a combined-stack effect. The - mechanical part (auto turns) is directly observable, so record auto-turn counts - before/after instead of reading fewer turns as verified-quality improvement. - -## Review focus - -- Actual server differs from the managed CLI: require both identities/catalogs. -- Config/Markdown precedence can revive retired agents: check actual catalogs. -- Shared installs or references look like duplicates: archive only owned active - OpenCode registrations, never delete shared source or historical records. -- CE workflows propose external effects: scope/permissions still govern execution. -- Reload during a Goal/design drain can change a running task: do not cut over - until the owning session is safe; retain exact rollback bytes and pending work. - -## Preparation outcome / blocker (historical, superseded) - -This section preserves the pre-cutover findings, not the current action list. -The later cutover/removal records and current alignment section govern resume. - -Survey and private backup prepared; **no global cutover, role disabling, install, -policy replacement, server restart, new execution Goal or product trial performed**. -Prepared policy candidate is private and inactive. Required blockers are the -active Site Intelligence Goal and unverified shared listener at4096. OpenChamber -authentication is recovered, not a remaining credential request; its runtime -catalog discrepancy still needs safe-boundary investigation. The candidate is -91lines/6026bytes versus active124lines/51353bytes, chmod600, not applied; -SHA256 `c2cd30137097e46729be4aec0602930d24835f899bb1319655760587fe90d330`. -All seven backed-up live files remain byte-identical after the interruption. -Continue from step2 when safety/access conditions are resolved; do not repeat -approval of the already selected transition scope. No timing/efficiency or -complete migration claim is made by these preparation records. - -Later same-day read-only inventory (above) extended the candidate list and -corrected three assumptions: OpenDesign project skills are not global legacy -installs; a Claude-official plugin is not a KSI skill; shared compatibility roots -(`~/.claude/skills`, `~/.agents/skills`) belong to other tools and are not -OpenCode-owned deletion targets. Blockers unchanged: active Site Intelligence -goal, unverified listener4096, OpenChamber catalog discrepancy. - -**Cutover applied (same day, user instruction “모두 개선해줘”).** The recorded plan -executed with private backups; see the applied-change inventory above. Residual -items: Site goal formal pause (owning session), listener4096 unverified, -OpenChamber-managed server catalog still empty (pre-existing env), and behavioral -confirmation of Goal auto-continuation off pending idle observation. Efficiency, -product acceptance and external delivery remain unclaimed. - -## Observation phase (user decision, 2026-10-09) - -- User accepted the cutover as appropriate and directed: stop harness/instruction - changes; use the configuration on real product work; keep OpenDesign's own - review/improvement loop untouched (the delegated-direction edit was KSI-side - guidance only, not an OpenDesign internal change). -- Observe in the next real work: development speed (request → working - functionality), user intervention (unnecessary questions/approvals), continuity - cost (a new session's re-collection cost), quality (find/fix/re-verify - iterations). No harness edits, tool additions or forced CE pipelines while - observing. -- Deferred until observed need: duplicate/overlapping-skill review (KSI8, - ui-ux-pro-max, CE overlaps), further policy edits, UI/UX Pro Max hide. -- Delivery status: local commits through `c879102` plus this record; `origin/main` - remains `1840aada`; nothing pushed, published or deployed — not remote delivery. -- Environment residuals stay separate from methodology: the Site goal remains - recorded `active` in the preserved state file (inert after plugin removal; - formal pause/clear would need a temporary pinned reinstall), OpenChamber - catalog issue, listener4096 verification. -- CE document-path integration check (2026-10-09, verified against the installed - v3.30.4): CE artifact trees resolve from a repo-local - `.compound-engineering/config.yaml` `docs_root` (validated, symlink-safe, - `config.yaml` only — never a global setting); CE-owned trees include plans, - ideation, solutions and `STRATEGY.md`. KSI's `work-status.mjs`/`work-doctor.mjs` - parse only `docs/superpowers/plans/.md` ledger pointers (code-confirmed), - so CE-generated documents would not be recognized by the status tools; the - native Plan agent can only write `~/.opencode/plan/`, so CE plan persistence - belongs to Build. No project contains any CE artifact yet (harness, Site, - EVENTOUCH, Binance checked) — a latent gap, not an active fault. -- Interim rule (no new changes, fits “one record per workstream”): the KSI ledger - stays the single canonical record; CE documents are supporting material linked - from it; `ce-plan` runs in Build only and never duplicates an already approved - plan. Re-evaluate a path-compatibility change (status tools reading CE locations - alongside the legacy path) only with observed real-use friction; do not add - per-project CE config now. -- Mode comparison frame (user proposal 2026-10-09): label each real task by mode — - A native-only, B native + selective CE (current default), C full CE workflow — - and judge each with the same four metrics; do not mix modes within one task. - A suits small/clear work; C must justify its process cost on large tasks. - `ce-compound` is kept only if it demonstrably reduces later re-investigation - (continuity metric). Native Plan/Build/General/Explore are the base; CE skills - are auxiliary and must earn their keep. -- Goal plugin removal (2026-10-09, user decision): entries removed from the global - JSONC and cli.json; verified after reload — 2 plugins (CE + design-notifier), - 38 commands (the three goal commands gone), skills unchanged (60, 34 `ce-*`); - goal state file preserved and valid (v2; active1/complete2; only a 1-byte - periodic accounting write after the pre-removal copy). Reinstall recipe - (pinned0.1.54 + options) kept as a JSONC comment; plugin cache and both state - copies retained. - -### Observation evaluation rubric (confirmed 2026-10-09) - -Method-selection clarification (2026-10-10): the approved native-first, -agent-selected-method contract takes precedence over the earlier comparison -labels. A/B/C is optional descriptive shorthand, not a mode selector, required -task label or prohibition on using a useful skill during native execution. - -Unit: one real product task = one existing task record, noting relevant -methods/skills, model, task type and duration when available. - -- Primary criteria: outcome correctness (works in the real product with actual - execution/reload/persistence and regression evidence — never self-reported); - speed (request → completed functionality, first useful change); user - intervention (unnecessary questions, approvals, re-approvals); continuity - (time/questions to resume in a new session, repeated investigation of settled - decisions); quality iterations (find/fix/re-verify rounds, post-completion - leakage). -- Secondary criteria: method-selection fit (A/B/C vs difficulty, verification - depth vs risk, per-delegation reason/return quality/re-instruction count); - real CE gain (reduced re-investigation, earlier defect discovery; - `ce-compound` judged only by reduced re-investigation on a later task); cost - (tokens/time) as an auxiliary signal only — never a standalone verdict; - boundary compliance (no unapproved push/deploy, other projects and data - protected, backups/rollback intact). -- Judging rules: compare same-model, similar-difficulty tasks; record material - method/model/scope changes instead of enforcing mode purity. No generalization - from a single task (2–3 per task type before judging); historical Superpowers - records and optional A/B/C descriptions are contextual comparisons, not - controlled efficiency proof. Adjust the selection policy only on repeated - observed under- or over-use of CE. -- Excluded from verdicts: token/document-volume reduction alone; static counts - of skills or agents. - -Preparation verification above `260f53f`: `git diff --check` passes, the new -product-state plan link resolves, all seven live/source backup hashes match, -private directory700/backup+candidate600 confirmed. Only this ledger and -product-state are pending tracked-record changes. No source test replay for -record-only preparation, no claimed role/skill/new-session smoke result. diff --git a/docs/superpowers/product-state.md b/docs/superpowers/product-state.md deleted file mode 100644 index 16a75e7..0000000 --- a/docs/superpowers/product-state.md +++ /dev/null @@ -1,137 +0,0 @@ -# Product State - KSI OpenCode Harness - -Updated: 2026-10-12 - -## Goal - -Long-running product development where OpenDesign owns product/visual design and this harness owns engineering -execution — Build/Plan leading, truthful scoped continuity and verification, and no duplicated design authority. - -Archive: 2026-09-23 m02 goal "Make operational continuity and routing/verification/failure claims -understandable and auditable without changing runtime behavior." superseded at m02 slice close 2026-09-24; -preserved for audit, not active. - -## Milestones - -| id | milestone | status | evidence | -| --- | --- | --- | --- | -| m01 | Long-running orchestration (coordinator-only Build, continuity, Research/design-task) | done | docs/superpowers/plans/2026-09-15-long-running-orchestration.md | -| m02 | Operational evidence consistency (docs/state only) | done | docs/superpowers/plans/2026-09-23-operational-evidence.md + user acceptance 2026-09-24 | -| m03 | Human-centered workflow (outcome-first Build, live-preview Design) | superseded | Design-primary premise removed 2026-09-30; docs/superpowers/plans/2026-09-23-human-centered-workflow.md kept as history | -| m04 | Design workspace separation (retire OpenCode Design + kit, adopt OpenDesign) | in_progress | Reference/snapshot guidance plus intake/result/readiness hardening and native Plan design-MCP boundary applied; reviewed deployment restored and one technical file-access fixture verified; user end-to-end flow acceptance separate; docs/superpowers/plans/2026-10-03-design-flow-hardening.md | -| m05 | Beta release and readable onboarding | in_progress | Published npm 0.5.0-beta.8 remains the historical three-role release on BOTH `next` + `latest`; default/unversioned consumer19/19 verified with all23originalbytes/preview/apply/repeat. Current reviewed source is two-role, not newly published. GitHub tag/release v0.5.0-beta.8, CI 3 OS × 2 Node all6 pass; user README/workflow acceptance remains pending; docs/superpowers/plans/2026-10-07-beta8-release.md | -| m06 | Autonomous local execution with design ownership preserved | in_progress | Exact approved policy applied globally; source0682659 pushed, npm0.5.0-beta.3/next published and public18-file consumer verified; real-development acceptance separate; docs/superpowers/plans/2026-10-01-autonomous-execution.md | -| m07 | Design completion notification (OpenDesign run → originating OpenCode session) | in_progress | Notifier plugin implemented, activated, hardened (per-session wake permission with `held` state, single elected poller, lock-serialized writes), installed to a pinned runtime copy after private backup; verified technically incl. live held→resume auto-wake E2E on existing terminal runs and idempotent duplicate handling; user real-run acceptance pending; docs/superpowers/plans/2026-10-03-design-completion-notifier.md | - -## Current slice - -- Name: Native-first global CE transition — operating-doc/checkpoint alignment technically complete 2026-10-10; cutover/removal already catalog-verified, real-development observation pending. -- Acceptance (user-visible): actual serving global environment loads pinned Compound Engineering3.30.4 without Superpowers/bootstrap or forced pipelines; General replaces default Developer use, Test Runner disabled, Reviewer optional; Goal plugin removed with its state preserved (soft-off then removal; reinstall on explicit need); native prompts/models/permissions/MCP/other plugins and all product histories preserved. Applied 2026-10-09; two-project catalog verification done. -- Plan ledger: [2026-10-09-native-ce-transition.md](plans/2026-10-09-native-ce-transition.md) (approved scope, cutover inventory, private backups, verification evidence and residuals). -- Historical cutover snapshot (2026-10-09, before Goal removal): managed-service catalogs — plugins compound-engineering `efcb657` (tag `compound-engineering-v3.30.4`), goal0.1.54, design-notifier; no Superpowers. Skills60 =34 `ce-*` + `lfg`/`wtf` +8 KSI +5 config-root + built-ins/doc; identical from KSI-Site-Intelligence-Platform and KSI-Binance-Trader without project installs. Agents: developer/test-runner removed (config `disabled` + file rename `*.md.bak-1791546420134`); reviewer/general/explore/plan/build unchanged with native prompts. Commands41 incl. `ce-*`. MCP4 unchanged. `opencode reload` confirmed. Repo `npm run check`217/215/0/2 with the updated notifier test. `AGENTS.md` replaced by the native-first policy with the delegated-direction default and Goal-off note. This is not the later removal inventory. -- Historical pre-cutover inventory (2026-10-09): exactly3 plugin sources then;39 discovered skills with ui-ux-pro-max via `~/.agents/skills` and8 KSI+9 document/tool skills via the `~/.claude/skills/synced` compatibility root;4 MCP servers live while `opencode mcp list` under-reports in2.0.24; candidates corrected (EVENTOUCH `.od-skills` and Claude-official plugins are not global legacy installs; gstack not installed); ui-ux-pro-max hide remains a separate later decision. Not the current CE/removal catalog. -- Goal plugin review (2026-10-09): safe disable applied (pinned0.1.54, `auto_continue:false`) then full removal per user decision — 2 plugins remain (CE + design-notifier), 3 goal commands gone (38 total), skills unchanged; state `goals.json` and private copies preserved (0600; valid v2, active1/complete2, not proof of field-level invariance); reinstall recipe kept in the JSONC comment; notifier/CE unaffected; Site goal recorded active but inert. No pause/clear/reinstall is needed for ordinary resume. -- Observation phase (user decision 2026-10-09): freeze structural/runtime changes; use real product work and record — development speed, user intervention, continuity cost, quality iterations. The user approved only a bounded operating-doc/checkpoint inconsistency repair on 2026-10-10. OpenDesign's internal review loop untouched. Duplicate-skill review and UI/UX Pro Max hide deferred to observed need. Cached `origin/main` is1840aada; no fetch/push or fresh remote comparison is claimed. -- CE document-path integration check (2026-10-09): latent gap confirmed, not active (no CE artifacts in any project). CE writes under a repo-local `docs_root` (`.compound-engineering/config.yaml`); KSI status tools recognize only `docs/superpowers/plans/` pointers. Interim rule without changes: KSI ledger remains the single canonical record, CE documents are supporting material; path-compatibility work deferred until observed friction. -- Method-selection/evaluation clarification (2026-10-10): Build chooses native execution or useful CE skills without asking for a mode or forbidding method changes. A/B/C may describe observed usage, not require labels or separate pipelines; record material method/model/scope changes and judge the same outcome metrics. CE value (e.g., reduced later re-investigation) remains unproven until observed. -- Current consistency repair (2026-10-10, technically complete): README/INSTALL, architecture/execution and optional examples now distinguish native General + selective CE / Goal-free record-based resume from unchanged installer/release scope. Current checkpoint/backlog reconciled, preparation outcomes marked historical and checkpoint bound to the current ledger section. Check217/215/0/2, offline package check,64 local links/anchors0broken and status assertions passed; evidence in the transition ledger's current alignment section. Documentation/checkpoint only; no global config, agents, source runtime, product task or external delivery change. Real search/delegation/continuity improvement unproven. -- Harness audit corrections (2026-10-12): delegation-validation metrics corrected (1 defect discovered by the design run + 1 implementation-introduced defect caught by the independent rendered review; host-assisted cycle), work-doctor now reports the optional role bundle as optional instead of required, packaged-doc links fixed with a new link guard, README/examples/architecture wording aligned, and the unused retired-flow asset `.github/assets/pipeline.svg` removed (recoverable from Git history). See the transition ledger's "Harness audit corrections (2026-10-12)". -- Prior General preference: records in local07eeda9/260f53f; exact one-field JSONC change and preservation receipt at /tmp/opencode/ksi-general-high-20261009-t0fky6db/. Native-first principle confirmed then; current user request now authorizes transition, not an efficiency/product comparison experiment. -- Prior legacy cleanup: implementation/docs/check receipts in local `c18031f`, closeout `28f08f3`, QA direction `a095226`; full217/215/0/2, focused15/15, packed consumer pass, links37/0 andpins5/0, Reviewer no Critical/Important. Public beta.8 is still the three-role release; source is two-role. Earlier byte-preservation receipt is dated evidence; the later authorized General-only model preference is the explicit exception. -- Prior retirement delivery: implementation `d233d06` + `ccb1bbc` + `56fc131` independently reviewed/checked (213/211/0/2), records `23f803b` + `1840aad` pushed with explicit approval. GitHub CI `37882500976` completed/success at `1840aad`; npm beta.8 was not republished. Prior review/adoption receipts remain in the ledger. -- Scope: approved host-global operating migration; cutover applied within it — no Superpowers/CE comparison experiment, no product writes, no source-bundle rewrite, no new KSI agent/skill, no project ce-setup, no generated product docs. Source remains two-role until separately changed; no release authority. Prior B② remains separate. -- Evidence boundary (last runtime observations 2026-10-09): Site goal recorded active in preserved state but inert on the verified Goal-free service, with no pause/clear/reinstall required; listener4096 unverified; OpenChamber-managed server catalog empty/cause unresolved (pre-existing). Goal removal verified (2 plugins, 38 commands, state file valid). Those other listeners are not proven to share the same effective state. The 2026-10-10 repair performs no reload/restart, push/publication, model/product experiment or efficiency/user-acceptance test. -- Previous release evidence (beta.8): source/tag178b2e9 and record6c776cf independently verified by Test Runner `ses_eeb122a3fffeCA2YiTJzrk4BTw`:171/170/0/1 optional Compose skip, clean preflight/package, isolated native2.0.24/23permissions, all6OS/NodeCI jobs pass. Publish/pinned publicconsumer18/18 and resumed latest/defaultconsumer19/19 pass: next/latest=beta.8, unversioned install beta.8, originalSRI/all23bytes/preview/apply/repeat/no-policy writes. Historical auth404 blocker resolved by actual standard approval after user-requested resumption; npm4installedsources unchanged, no persistent auth fix or config/2FA change. GitHub prerelease/tagv0.5.0-beta.8 at178b2e9 persists. Human/quality acceptance and live notifier/global-policy installation remain separate. -- Follow-on coverage (2026-10-06): direction options + audit-first, user-state reporting, project-binding review (`blocked` continuation + explicit-scenario alternatives + EVENTOUCH id drift), resident context/review bundle/accessibility-motion review inputs, goal-usage signals + first-cycle evidence protocol + recurring-defect patterns, the user-facing naming rule (say "OpenDesign (로컬 실행)", not "Local Codex"), and the writeback-degradation + external-export-intake rules from the EVENTOUCH audit; commits `1e64c2a`→`f828a03`→`c43fbea`→`8e529c7`→`5d7df28`→current, each with local checks and independent verification where code changed. - -## Human acceptance - -Historical: user explicitly rejected EVENTOUCH's bold agency-home result on 2026-10-06; this is not a claim that every later product revision remains that rejected design. The product session subsequently replaced and iterated the UI. The recent field-operation result at `86b532d` explicitly lacked real-browser 390/320 verification; a later preparation audit read seven supplied captures but does not close all field-operation review requirements. Current product acceptance belongs to that session and remains unclaimed here. Harness simplification/remediation is approved for implementation, not product acceptance. Technical checks, local policy adoption and external delivery remain separate. - -## Product binding - -| Field | Value | -| --- | --- | -| Repository | . | -| Start command | not applicable (installer-only CLI; no product server) | -| Verify command | npm run check; npm run check:package | - -## Next slices (미완(in_progress/blocked/proposed)만; done/abandoned/false_positive_complete 제외) - -- m04 design-workspace-separation — remaining: Human end-to-end acceptance of the design flow; reference/snapshot/preview and run/intake/readiness guidance plus current-user Plan permission boundary applied. One live technical file-access/output fixture verified, not a product-design generation or visual acceptance claim. -- m05 beta-release — remaining: user acceptance of README/workflow. GitHub/npm both-channel legacy three-role beta.8 and exact/default public consumer technically verified; current two-role source needs a separately authorized new version to reach npm, not a republish of beta.8. No remaining authorized release-channel work. -- m06 autonomous-local-execution — remaining: user real-development acceptance; source/public beta.3 delivery verified, not a long-running behavior guarantee - -## Backlog - -- Native/external methodology direction (2026-10-09) — **global cutover and Goal removal completed; real-use observation remains**. General stays multipurpose Muse Spark high/no cap; native roles + optional CE3.30.4 and Reviewer are the approved operation. Do not replay the completed installation, role retirement or policy replacement. No comparison experiment, product intervention, new custom skill/agent, automatic pipeline or upstream update authorized. The 2026-10-10 exception repairs operating documents/checkpoint only. [Current transition](plans/2026-10-09-native-ce-transition.md); [earlier direction/General preference](plans/2026-10-07-recorded-handoffs.md#approved-general-model-preference-and-native-methodology-discussion-2026-10-09). - -- QA delegation direction (2026-10-09) — **dedicated QA deferred; no new agent or Skill**. Retain the two-role bundle; for future authorized substantial product verification, use an existing General child session with a compact revision/environment/scenario/test-data/evidence contract when useful, without mandatory calls or repeating valid checks. Reconsider specialization only after actual recurring setup/permission/consistency costs are evidenced; no new pilot, tool install, product intervention or efficiency claim. [Recorded direction](plans/2026-10-07-recorded-handoffs.md#qa-delegation-direction--no-new-agent-2026-10-09). - -- Canonical product/design binding and actual-product preview (2026-10-08) — **harness scope complete and independently verified**; preview self-development cancelled by user; real-use acceptance separate. Historical [proposal](plans/2026-10-07-recorded-handoffs.md#proposed-product-binding-and-live-preview-improvement-2026-10-08); approved [plan](plans/2026-10-07-recorded-handoffs.md#approved-canonical-binding-and-actual-product-preview-2026-10-08). Preserve EVENTOUCH owning-session/history and product runtime. - -- Two-harness task contract and verdict boundary (2026-10-08) — **implemented as the current slice** (guidance-only; independently verified); real-use observation separate. Historical [proposal](plans/2026-10-07-recorded-handoffs.md#proposed-two-harness-task-contract-and-verdict-boundary-2026-10-08); approved and [closed section](plans/2026-10-07-recorded-handoffs.md#approved-two-harness-boundary-and-commissioning-contracts-2026-10-08). - -- Product-judgment observation phase (2026-10-09) — **monitoring, no new guidance**. User paused further operating-guidance additions and set the next KSI-Safety-Edge development task as the observation target (requirements + actual code state, no forced implementation list; Plan's own prioritization/stop judgment; Build's plan-dependence and delegation/verification discipline). Deferred review items: per-product completion criteria, native-centric state reduction, cost-vs-contribution metrics for the Reviewer seat and any explicitly configured verification role (Test Runner left the default bundle on 2026-10-09). [Post-delivery assessment](plans/2026-10-07-recorded-handoffs.md#post-delivery-assessment-and-observation-phase-2026-10-09). - -- Cross-state consistency audit (2026-10-08, historical read-only stage): four groups reproduced at `6b781f1`; that stage did not authorize repair. The subsequent agent-owned-loop improvement request approved bounded corrections, now implemented/independently checked in the current slice. Historical findings in [the audit section](plans/2026-10-07-recorded-handoffs.md#read-only-cross-state-consistency-audit-2026-10-08); current scope/evidence in [the completion-loop follow-up](plans/2026-10-07-recorded-handoffs.md#approved-agent-owned-completion-loop-2026-10-08). Protective retry reset and sensitive-read permission semantics remain separate decisions, not pending routine repairs for the user to triage. - -- Playwright Agents optional E2E adoption — **proposed, not implementation-approved**. Historical synthetic planner/generator/healer trial independently passed 2/2 with original assertions retained; proposed usage/adapters/readiness/safety-regressions and separate product evaluation are recorded in [the proposal and pilot summary](plans/2026-10-07-playwright-agents-adoption.md). This record/handoff approval grants no product/global Agents adoption or release authority. - -- Practical Developer delegation (2026-10-07, reviewed/checked locally): active useful bounded nonvisual implementation without forced delegation; compact contract/return, actual diff integration and cause-based partial-work recovery. README/execution/examples and three global-policy bullets aligned, native roles/config/runtime unchanged; independent166/165/0/1 + package/link/12-hash/exact-policy checks. Optional Plan/goal/concurrency repetition deferred because existing authoritative rules already cover them. Observe quality/rework/completion efficiency only during later authorized development, not a new pilot here. Ledger: docs/superpowers/plans/2026-10-07-effective-developer-delegation.md. Human acceptance and external delivery remain separate. - -- README responsibility-flow clarification (2026-10-07, locally reviewed/checked): optional Plan; Build overall integration/technical-completion responsibility; design actual UI implementation with discretion and verified source-direct capability; nature-not-size ownership; integrated-version independent final checks vs author tests; human acceptance/delivery/no-runtime-enforcement boundaries. Architecture/integration summaries aligned;5docs/state files only. Independent166/165/0/1 + package/link checks,7 interpretation cases; findings addressed without new runtime/agent/global policy or public delivery. Ledger: docs/superpowers/plans/2026-10-06-visual-ownership.md. - -- Audit remediation (2026-10-07, engineering checks pass; real-use acceptance pending): native finite-step final-summary incompatibility reproduced; model-scoped body auto workaround retains native guard/model/modes/steps/permissions. Global render-path policy applied. Follow-on probe-selection race/CLI safety defects repaired with regression tests and independent166/165/0/1; baseline notifier restored after helper side effect. New notifier advice requires authorized delivery. No push/release/restart/product run; transient pickup unknown. Generic reasoning/MCP errors, actual long-run reliability and product visual quality remain separate. Ledger: docs/superpowers/plans/2026-10-06-visual-ownership.md. - -- Usability alignment adoption: chain-follow fix `8aa350e` retained; pinned notifier updated to `24702e1` for design-owned output-contract advice and `designQuality:not_assessed`. Independent check140/pass139/fail0/skip1, targeted37/37, installed payload7/7 and verify pass. Runtime reload/real-use quality must not be inferred from static hashes or emitted-contract tests. No automatic publication/design run; optional binding-placeholder follow-ups remain separate. -- Source-registration flow (user-directed 2026-10-06 "전역 설정 하네스 개선", complete): the writable design capability is a standing, agent-executed flow — connect the exact project path (one container recreation for a new path, automatic rollback, refuses while runs are non-terminal), then `import` it as a folder-backed project; scope = every project under home except the home root/credentials; one writer. A transient Desktop parent bind was tested, found redundant (the daemon requires exact per-project connections) and against the deployment boundary, and reverted. `EVENTOUCH source` re-registered (`aabf3621`; later re-created by the product session as `9db026a4-670d-4a16-bc9f-f79bf0942142` — verify the current project id from `list_projects` instead of trusting a stored id). Guide + global policy updated. Ledger docs/superpowers/plans/2026-10-06-source-registration-flow.md. -- Design-quality pilot: EVENTOUCH source edits `5f7b92f` then bold `2270e6e` were rendered at 1440/390, proving source writing/rendering, not an autonomous critique-and-refinement loop. User rejected the bold result; no representative approval or wider-rollout authority. Product session must supply actual design-owned review/refinement evidence. This harness session owns common workflow, not that product's repairs. Engine remains codex temporarily; guidance is not runtime enforcement. -- Optional ergonomic wording polish (non-blocking review): more explicit base-ambiguity/submodule-detection hints if needed in real use; existing HEAD/base inspection and isolation skill cover them. Canonical guide pointer stays authoritative after integration; do not read unrelated product docs as the harness guide. -- Remaining tooling follow-up: global Playwright CLI/agent-browser/official skills and agent-browser's bundled axe are now prepared under user-approved tools-first scope; standalone Lighthouse/virtual-reader tooling and real desktop/audio setup remain separate. V2 native LSP runtime is absent per current migration docs; structural-navigation alternatives need scoped evaluation. A future verification-evidence helper needs a demonstrated gap/approved design, not a new orchestrator or idle auto-fixer. No single-project-only restriction or product dependency/CI rewrite. -- Release polish (closed 2026-10-06): README/INSTALL version-pin guards are now automated — `npm run release:check` (`scripts/release-check.mjs` + tests) verifies pins vs `package.json`, a clean tree and both source checks; `docs/releasing.md` gained the preflight, tag step and a checklist; git tags `v0.5.0-beta.2`…`v0.5.0-beta.6` were created at their verified version commits and pushed. No implicit new release or immutable republish for prose/tests. -- Codex intermittent connection-test failures: investigate if recurring; not a release blocker per user. Account policy restrictions are expected, not an installation defect. -- Design integration: DGX MCP now uses host-side exact-revision HTTP proxy; same-client outage and actual container restart/read verified 2026-10-01, no mutation replay. Host-process crash/reboot supervision and default artifact-entry lookup remain unverified/problematic; explicit file arguments work. -- Upstream daemon baseline resolved by test-fixture repair: pin fake executable rather than host primary, and make archive fixtures owner-private. Full native suite 2026-10-01: 912 files pass/4 skipped, 12134 tests pass/0 fail/16 skipped; /tmp/opencode/od-closeout-daemon-full.log. Production security checks/assertions unchanged; source committed locally as1bd12b5eb (ksi/remote-workspace), not pushed to the original upstream repository. -- MCP idle/relaunch verified: same client/process reads after31min idle, isolated proxy termination then normal client relaunch/read pass; /tmp/opencode/od-closeout-idle.log. Automatic host-crash supervision and actual reboot are not claimed. -- Global design workflow guidance in ~/.config/opencode/AGENTS.md: Local Codex default, actual server/project lookup, DGX execution not client-PC execution, explicit artifact-to-engineering handoff. Verified checks reused only for valid same-target context; changes/errors/restarts or active-context expiry require resolution. Live refresh/static invariants verified; no measured latency improvement claim. Later read-only review found EVENTOUCH registered with a valid HTML artifact in a managed design folder; this does not prove linkage to its product repository. Earlier absent-project observation is superseded, not a reason to create a replacement. -- EVENTOUCH's separate codex MCP reports Connection closed; design MCP is connected and hosted Local Codex execution previously verified. Do not conflate these integrations; no fix to unrelated MCP configured as part of global guidance change. -- Design-flow hardening limits: direct Plan design-MCP guard, not an OS/shell/subagent sandbox or proof of every project's config precedence. Future server-prefix changes require live re-evaluation. Verifier's example parsing is intentionally full-line-comment-only and fails closed for unsupported JSONC. Exact cause/actor of deployment drift is unknown; no automatic drift monitor/supervisor installed. -- Important design-flow follow-up (user inquiry2026-10-03): **implemented as m07, hardened the same day.** The notifier records request→session bindings, polls the daemon read-only through a single elected poller, and delivers one advisory notification to the originating session; wake permission is per session (paused → `held` in notifier state with zero session contact; resume → eligible for auto wake subject to retry/remaining pauses). The runtime loads a pinned copy from `~/.config/opencode/plugins/design-notifier/` (dev tree no longer watched). Verified live: hook capture, held→resume auto-wake on an existing terminal run, no duplicate inbox items, restart reconciliation from disk. Remaining limits: first real `start_run` capture observed 2026-10-03 (hook source, delivered within one poll cycle); same-host reachability required; up to one poll interval without delivery errors/remaining pauses, while protective retries can defer eligibility up to15min; leader takeover within the lease TTL after a crash; goal-pause state is not observed (explicit switch only); approval-hash binding is not implemented. No new orchestrator, no automatic design retry. Retry-wait metadata/copy corrections remain source-only until an authorized pinned update. -- Design-flow autonomy alignment (user-directed2026-10-03): default is **finish-and-report** for changes that follow the existing approved design language and authorized scope (brief → OpenDesign → verification → implementation → result report with preview); preview-and-wait only for a new visual direction, client-facing deliverables, out-of-scope changes, or an explicit "review first" request; non-dependent engineering continues while a run or check is pending. Encoded in the global AGENTS.md and the notifier message; human real-use acceptance pending. -- Verification & project hygiene (user-directed2026-10-04): test-runner final-verification rule restored in the global AGENTS.md (first independent run `ses_efa8404d…`, `npm run check` 107 pass/0 fail/1 skip); EVENTOUCH OpenDesign project entry declared (`eventouch-operations.html`) fixing recurring `entry_missing` verdicts (effective next real run); goal-mode proposal rule added for multi-hour unattended work; model routing guide excluded by the user. Ledger docs/superpowers/plans/2026-10-04-verification-and-project-hygiene.md. -- Autonomy gaps (user-directed2026-10-04): `scripts/work-report.mjs` provides an on-demand consolidated view of goals, design deliveries, usage, worktrees and next slices; the original live measurement found 2 active goals and an in-flight design run, not a current observation. The later user decision supersedes mandatory goal limits: generous safety rails, no-progress auto-pause as the primary brake, and user-adjustable/removable limits for product-critical runs. Explicit goal opt-in and the stop-report contract remain; no existing goal is changed here. Closeout reports propose goal-relevant backlog candidates without executing new scope. Ledger docs/superpowers/plans/2026-10-04-autonomy-gaps.md. -- Guidance alignment (user-approved2026-10-04, technically complete locally): preserve native Build/Plan/Explore prompts; align shipped design guidance with default finish-and-report, distinguish implementation evidence from latest approved requirements, and add conditional goal-based next-task judgment without mandatory audits. Global AGENTS.md and optional project guidance receive the criteria; the installer still does not merge policy. Mandatory-limit wording in the autonomy-gaps ledger is historical (see its Notes), and blanket pre-implementation direction approval in the project/autonomy examples is replaced by the conditional gates; new visual direction, client delivery, out-of-scope changes and explicit review-first requests still require a decision. Ledger docs/superpowers/plans/2026-10-04-guidance-alignment.md. Independent review repaired/rechecked three findings; `84bbbc5` fast-forwarded to main with independent merged check112/pass111/fail0/skip1, package pass, six config/role hashes preserved. Owned task workspace removed non-forced; no push/npm publication. Improved real-work behavior and human acceptance remain to be observed (m06/m04). -- Product-aware design (user-approved2026-10-05, technically complete locally): the operating flow requires relevant product sources/components/tokens, sanitized states and integration contracts as verified inputs to Local Codex; supported UI output is integrated faithfully and compared with the real product. Approved-pattern exceptions require an actual source, not token reuse/file count; required design or checks cannot be hidden behind “only user acceptance remains.” Direct source edits are a separately scoped capability-dependent option, not enabled here. Global AGENTS.md and shipped guide/examples updated; no product edit/design generation/mount or permission change. Independent six-scenario interpretation review: no blocking findings; `39835f1` fast-forwarded to main and independently verified check112/pass111/fail0/skip1, package pass, six config/role hashes preserved. Owned worktree removed non-forced; no push/publication. Ledger docs/superpowers/plans/2026-10-05-product-aware-design.md; actual product input/output and design quality remain unverified in this task, real-use acceptance pending (m04/m06). Guidance is not runtime enforcement. -- Source-direct feasibility (user-approved2026-10-05 research/throwaway trial, complete): primary-source folder-import implementation and related tools support the approach. One Local Codex run modified actual host React TSX/CSS in an already connected synthetic fixture, built it without installs, and returned a valid canonical HTML result; Build did not rebuild the UI manually. Independent test-runner ses_ef50782acffekyjjxI5qXfr4v0 verified exact two-line diff, deterministic build, byte-identical HTTP outputs, real mouse/keyboard state updates,390px and reload checks, preserved originals/config/security. Agent input-CSS receipt hash normalized away final newline and is rejected as raw-byte provenance; real baseline/output independently checked. No global/product activation, new mounts/restart/security change or publication. This proves bounded direct editing/compiled React preview, not Next/Vite server/HMR/auth/API/visual quality/file-level allowlist or universal source-only delivery. Ledger docs/superpowers/plans/2026-10-05-source-direct-feasibility.md. Next m04 candidate: authorized one-screen isolated product pilot, not automatic all-project activation. -- Source-direct flow simplified (user-approved2026-10-05 "그렇게 개선해줘", complete locally): the verified preferred path is direct editing of the actual product repository — dedicated branch or the user's checkout, worktree optional convenience — with one writer at a time and Build owning contracts, verification, integration and Git. Real boundaries kept: home/credential roots and unrelated projects out of reach; new mounts/security changes and push/deploy/API/data/auth/dependency changes keep their own authorization. Source-direct completion evidence is the actual source diff plus a real build/render check; an intentionally untouched canonical entry may report entry_not_touched and does not by itself invalidate the result. Global AGENTS.md, integration guide, execution/architecture docs and examples updated; no product/mount/container/security change or design generation. Ledger docs/superpowers/plans/2026-10-05-source-direct-flow.md. Real-product pilot remains separate (m04). -- System-pass guidance (user-approved2026-10-05, complete locally; approval ordering superseded by review-once below): accumulated multi-screen visual drift prefers a visual-system pass over per-screen patches or a big-bang rewrite; minimum product context includes screen/file mapping. Current internal existing-brand flow is audit → token/shared-component scales → verified representative screen → one user review/approval → staged rollout. Global AGENTS.md + integration guide updated; no product/mount/security change. Ledger docs/superpowers/plans/2026-10-05-system-pass-guidance.md. First real large-scale pass observation remains separate (EVENTOUCH). -- Review-once flow (user-approved2026-10-05 "그렇게 해보자", complete locally): internal tools keeping the existing brand run a visual-system pass to one verified representative screen and present definition + scope + real result as a single review (pre-approval only for a new visual direction, client deliverable, out-of-scope or review-first request); unavoidable decision batches come once with a recommended default per item; rollout then continues with per-stage checks without per-screen approvals, reported at completion. Global AGENTS.md + integration guide + examples updated; the wider guidance diet is deferred until the first real pass shows which rules chafe. Ledger docs/superpowers/plans/2026-10-05-review-once-flow.md. - -## Design-flow hardening checkpoint (2026-10-03) - -- Existing integration guide/shared policy/current-user guidance now distinguish new intake from existing refinement, bind actual project/storage/brand identity, classify physical execution separately from required-output success, and specify cause-based recovery/narrow polling. No upstream application/prompt edits or new orchestrator. -- Optional sample Plan-only deny/read-allow rules adopted globally after private backup; native isolated and live23-decision evaluation passes, Build catalog unchanged and unrelated configuration bytes preserved. Reviewer coverage finding reproduced RED then fixed; fresh source68pass/0fail/1opt-in Docker skip, packed consumer and isolated native checks pass. Evidence in the active ledger. -- User separately approved restoring existing reviewed deployment. Same base layers/version/revision confirmed; no active work/invalid roots. Attempt1 safely rolled back after a verifier-command formatting error; attempt2 restored actual reviewed image/5Compose inputs/enforced profile with capabilities0/no-new-privileges/seccomp preserved. Managed volume, three selected mounts and EVENTOUCH source hashes preserved; existing MCP reads recovered without manual reconnect. -- One authorized technical Local Codex fixture actually read input, computed SHA256 and wrote an exclusive318-byte HTML report; independently verified bytes/hash. Run verdict `entry_not_touched` correctly remains non-delivery because the canonical entry was intentionally unchanged. No EVENTOUCH retry, product design/visual/browser acceptance, new dependency, provider/model reconfiguration, OpenCode service restart, host reboot, push or npm publication. Published beta.5 stays immutable/unchanged. -- Source3fbf64b fast-forwarded into clean canonical main fromc35a57a; fresh merged68pass/0fail/1opt-in skip, packed consumer and isolated23native decisions/role preservation pass. Technical scope complete locally; reviewer coverage findings fixed by RED→GREEN. Human acceptance remains pending, source not pushed/new version not published. - -## Delivery checkpoint (2026-10-01) - -- Latest browser-toolkit/ergonomic source and beta.5 release-contentc341faa/preflight3a5eedf pushed GitHub main; standard npm user approval completed both publication and latest promotion. Public next/latest bothbeta.5, previous versions retained. Full exact/default consumers match reviewed tarball/SHA512/all18source+installed files; read-only preview/exactly3roles/repeat/no automatic policy application/GitHub README verified: /tmp/opencode/ksi-beta5-public-latest.log at0015596. Fresh source64/0fail/1skip, packed consumer and isolated nativeV2 metadata passed: /tmp/opencode/ksi-beta5-final-{check,package,isolated}.log. Initial registry lag resolved read-only; prior approval expirations resolved with explicitly user-requested new links, no auth bypass or immutable republish. Final current-server global policy/config/tool versions/official skills and368priorfiles/custom Reviewer preservation reverified. No restart/new runtime/forced loop/project dependency or CI rewrite; product/audio/WCAG/human acceptance separate. Ledger docs/superpowers/plans/2026-10-02-browser-toolkit-beta5-release.md. - -- Global browser toolkit (2026-10-02, host setup/local-only source974d97f): pinned Playwright CLI0.1.22/agent-browser0.38.2 and official global skills prepared; native skill loading/advertisement confirmed. Existing system Chrome retained sandboxing for both drivers; independent disposable HTTP capability sessions exercised real actions/keyboard/save/backend-file/reload/390px/screenshots/logs, plus bundled axe4.12.1 and independent-cwd launch. Evidence /tmp/opencode/ksi-browser-toolkit-20261002/smoke-summary.json, raw sandbox/audit reports and four inspected screenshots. Exact global policy edits,368 prior file hashes and existing global npm versions preserved; source64/0/1 and packed consumer pass, independent read-only review no implementation defect. Only owned browsers/fixture server closed; four unrelated worktrees retained. No product auth/API/WCAG/audio/human acceptance claim, core config/permission/model/native-agent/upstream change, new orchestrator/forced loop, project dependency/CI rewrite, remote push/publication or restart. Ledger docs/superpowers/plans/2026-10-02-global-browser-toolkit.md. - -- Ergonomic guidance supplement (2026-10-02, local-only): outcome-first comfortable defaults, agent-managed workspace/session/integration/owned-cleanup and change-focused actual-product browser/a11y guidance applied globally. Exact intended edits and86 config/role/upstream-skill preservation hashes verified; independent read-only interpretation review no Critical/Important findings. Source7a2aab4 integrated via fast-forward to main; pre-integration/merged checks64pass/0fail/1opt-in skip and packed consumer pass: /tmp/opencode/ksi-ergonomic-{final,merged}-{check,package}.log. Session returned to main and only proven task-owned ergonomic-workflow tree/merged branch removed non-forced; four unrelated worktrees preserved. No new CLI/skill/plugin/config/permission/service changes or remote/npm delivery; public beta.4 remains unchanged. Human end-to-end/comfort acceptance pending, not a runtime/browser/audio guarantee. Ledger docs/superpowers/plans/2026-10-02-ergonomic-workflow.md. - -- Approved main/npm0.5.0-beta.4 delivery completed, including explicitly authorized latest/default promotion after standard browser approvals. Public next/latest bothbeta.4; prior versions retained, no immutable republish. Public reviewed-tarball/SHA512/all18 source+installed bytes, exact-version and unversioned default installs, read-only preview/exactly3roles/repeat/no automatic policy application/GitHub README verified: /tmp/opencode/ksi-beta4-public-latest.log(source82216fd; release contente2263b5). Fresh final check64pass/0fail/1opt-in skip, packed consumer and isolated nativeV2 metadata pass: /tmp/opencode/ksi-beta4-final-{check,package,isolated}.log. Earlier authentication/approval expirations resolved without credential collection, auth bypass or npm modification; no design generation/service changes. Ledger docs/superpowers/plans/2026-10-02-design-handoff-beta4-release.md; human workflow acceptance remains separate. - -- Design-reference supplement (2026-10-02): canonical shared policy applied globally and refreshed, original guidance preserved except explicit Build-implements/Plan-reviews clarification; config/roles/Mobbin/UI/upstream hashes unchanged. Source68a0b07 integrated main locally; final/merged check64/0fail/1skip, packed18-file consumer and isolated native V2 metadata passed; independent review no Critical/Important issue. No new remote/npm release, design run, inner MCP setup or service restart. Previous beta.3 evidencebbfeae4 is pushed; follow-up source remains local-only. Ledger docs/superpowers/plans/2026-10-02-design-reference-handoff.md. Human workflow/visual acceptance is separate. - -- Autonomous policy source0682659 pushed and beta.3 published to next; latest0.4.0-beta.2 preserved. Public tarball/integrity and18 installed bytes, read-only preview/exactly3 roles/repeat/no automatic policy merge verified. Closeout fresh rerun on2026-10-02: /tmp/opencode/ksi-beta3-closeout-public.log. Native prompts/config/models/permissions/upstream skills and customized live Reviewer preserved; no restart. User real-development acceptance still separate. - -- User-approved Korean workflow-first README renewal and main push + npm0.5.0-beta.2/next delivered. Browser login/publish approval resolved earlier authentication blockers. Source200d86e pushed; registry nextbeta.2/latest0.4.0-beta.2 (unchanged), beta.1 retained. Harness64/0fail/1skip, packed consumer and isolated V2 checks pass; fresh public consumer verifies exact tarball/source17 files, no-write preview, exactly3-role apply and repeat idempotence. Public GitHub/npm README bytes match. Ledger: docs/superpowers/plans/2026-10-01-readme-beta2-release.md. No DGX restart; human rendered README/design acceptance remains separate. - -- User authorized commit/deployment closeout. KSI implementation/docs committed as e72e78f on refactor/retire-design-opendesign; separate daemon/web/contracts changes committed locally as1bd12b5eb. Unrelated upstream critique-opt-out-skill.md preserved untracked. -- Current DGX deployment verified: health200/ok true, Labs200/active, runtime gateway/connector modules match source. Global45-line policy and on-demand template already applied; no service restart needed. They remain separately managed user configuration, not installer-managed files. -- User selected local main integration. After fast-forward-only pull, main fast-forwarded to f3e4c18; merged-tree check64 pass/0fail/1 opt-in skip and packed offline three-role install pass. No push/npm publication performed; existing0.5.0-beta.1 and dist-tags unchanged. Separate upstream source worktree remains preserved. diff --git a/docs/superpowers/specs/2026-09-15-long-running-orchestration-design.md b/docs/superpowers/specs/2026-09-15-long-running-orchestration-design.md deleted file mode 100644 index 727451f..0000000 --- a/docs/superpowers/specs/2026-09-15-long-running-orchestration-design.md +++ /dev/null @@ -1,57 +0,0 @@ -# Long-running orchestration design - -Status: user approved implementation in the 2026-09-15 Plan-to-Build handoff. - -## Objective - -Make long tasks resumable and evidence-driven while keeping native OpenCode and official Superpowers. Build orchestrates and never implements product code; Design remains a user-facing Primary with a bounded delegated entrypoint. - -## Authority and scope - -- Plan owns requirements, architecture and acceptance. Build decomposes execution inside that approval, schedules workers, adjudicates evidence and completion, and writes bookkeeping only. -- Developer owns all product/configuration/integration changes, including small fixes. Batch related small tasks rather than adding a direct-Build exception. -- Design owns visual/interaction choices inside product scope and prototypes only. A hidden `design-task` subagent shares its design contract but has a separate, narrower execution profile. Only Build dispatches design-task; neither Design entrypoint recursively delegates. -- Explore investigates repository facts, optionally checking a directly relevant document. Research investigates external versioned documentation, upstream source/issues and alternatives. Neither modifies project source or makes final product decisions. -- Preserve native user model/variant choices, existing shared Codex/Claude configurations, official Superpowers installations, and existing opt-in Developer Test Runner behavior. - -## Strict Build capabilities - -Build has source reading, bounded repository evidence, questions/todos/skills and authorized task calls. Deny general shell, production edits and arbitrary MCP/custom execution. Allow only concrete bookkeeping paths for checkpoint, approved plan/spec documents and execution reports. Apply effective permissions and a narrow native before-tool check; do not claim an OS sandbox or shell-effect classification. - -Repository evidence is prerequisite: a fixed-command Git reader reports worktree/HEAD/status and creates task-scoped immutable review artifacts including staged, unstaged and relevant untracked files. It returns paths and fingerprints, not a full diff in Parent context. Snapshot verification detects relevant content drift. It never commits, changes the index, invokes diff drivers/textconv/hooks, restores files or follows escaping paths/symlinks. - -## Conversation, execution state and memory - -Conversation is transient. Existing specs/project instructions own durable decisions. One selected task-progress ledger (existing tracker or SDD per-plan ledger) owns plan execution progress. `.opencode/working-state.md` remains the short resumability cache, not a second task database. - -Native lifecycle records may retain task/session identity and unfinished calls; these are transport/recovery evidence, not a competing product plan. After compaction or process restart, require repository/checkpoint reconciliation before new Primary dispatch. Persist at semantic milestones; do not promise a checkpoint immediately before an unexpected interruption. - -Keep native automatic compaction and existing user token settings. Inject bounded recovery pointers into compaction context, not all logs/history. Do not invent a percentage threshold or force a new Primary session per task. Use a fresh child for an independent task; same-task resume must resolve a real child owned by the calling parent, with compatible role/worktree/task binding. Missing identity blocks instead of silently starting again. - -## Evidence and output - -Worker reports distinguish completed implementation from verified completion. Review packages identify exact scoped content, including dirty/untracked changes without requiring commits. Independent reviewers get requirements, actual diff, tests and unknowns; Primary adjudicates findings and integration rather than duplicating the whole review. - -Explore/Research return answer, evidence, implications, unknowns, coverage and references. Long results are retained as attributable task artifacts and reduced to a bounded Parent preview with an explicit pointer; no silent truncation or promotion of partial output to success. Read-only investigators do not gain unrestricted edit for report storage. - -## Design sufficiency and approval - -Before UI implementation, Build judges whether a relevant approved artifact or actual established design system/pattern resolves the task. Unresolved material hierarchy/interaction/fidelity requires Design; unrelated agreed work can continue. No keyword classifier or mandatory new design document for every tiny correction. - -Delegated Design returns draft/rendered/needs-decision evidence, never Human approval. Build brokers user questions and records approval tied to artifact version/scope/user evidence. Root Design and delegated Design must not compete for the same prototype. Workers reuse approved prototype source and return missing material decisions. - -## Acceptance - -1. Build cannot use native production edit/shell/custom execution; allowed bookkeeping and repository observation work. -2. Explore and Research remain read-only; Research tools and sources are explicit. Design Primary and hidden design-task have distinct authority and no recursion. -3. Repository snapshot includes dirty/untracked task files and rejects unsafe paths; reviewers can inspect the exact package; later changes invalidate it. -4. Initial/restarted/compacted Primary cannot dispatch before reconciliation; stale or unrelated native task IDs are rejected. -5. Long child output is stored with provenance and an explicit bounded reference in Parent context. -6. Existing developer-helper safeguards still pass; models/variants are preserved; native step values remain user-owned. -7. Offline unit/package tests, actual native config/tool loading and targeted lifecycle smoke validate claims. Actual model-backed worker execution may be separately unavailable; report that boundary. - -## Observed baseline - -OpenCode 1.18.31; clean source main e11ea26 before creating improve/long-running-orchestration. Existing 65 tests pass. Active database is opencode.db; opencode-local.db has no sessions. Read-only audit at 2026-09-15T05:03:33Z sampled latest20 roots active in14days and78 descendants: Build root completed edit/write/apply_patch events215/60/38 and completed task calls74. Tool events include bookkeeping and are not a product-change count. Separate older/cap-excluded lineages contain actual compaction events; no causality/cost claim follows. - -Current session Test Runner failed before execution because its loaded legacy model is unsupported. Live global model selections changed independently and must be preserved. Primary-run tests and independent code review are the fallback verification evidence, not mislabeled Test Runner execution. diff --git a/docs/superpowers/specs/2026-09-27-v2-native-agent-ownership-design.md b/docs/superpowers/specs/2026-09-27-v2-native-agent-ownership-design.md deleted file mode 100644 index a5ef215..0000000 --- a/docs/superpowers/specs/2026-09-27-v2-native-agent-ownership-design.md +++ /dev/null @@ -1,90 +0,0 @@ -# OpenCode V2 Native KSI Agent Ownership - -## Superseding distribution decision (2026-09-28) - -The Human clarified that the product to distribute is an installer for a KSI-configured OpenCode environment, not a runtime OpenCode plugin. The Human then explicitly approved removing the KSI plugin. This supersedes the plugin lifecycle, plugin-owned surfaces, and plugin distribution portions below; their dated investigation remains historical evidence. KSI's default distribution is native V2 agent and command files installed by a standalone CLI, with no KSI entry in `plugins` and no runtime KSI `setup(ctx)` entrypoint. Official Superpowers and the optional goal plugin remain independently user-owned, not bundled or removed. - -The installer must be non-destructive: preview by default, refuse differing existing files unless the user explicitly selects replacement, back up every replaced file, and never rewrite `opencode.jsonc` or select/replace models, variants, credentials, providers, MCPs, or unrelated settings. Native role files omit model and steps so existing native model/variant/positive step selections remain user-owned. The default Developer cannot delegate; the optional Developer-to-Test-Runner variant remains explicitly opt-in but no runtime single-helper guard is claimed. Build, Plan, Design, seven KSI roles, `/complete`, and `/review` become static native files. Static guidance cannot reproduce automatic checkpoint injection, KSI evidence tools, in-process delegation validation, or output archiving; documentation must name those losses instead of claiming parity. Existing global configuration and shared service remain unchanged until separate approval for an exact diff and restart. - -## Status - -The native config ownership direction and the soft-role operating model were approved in conversation on 2026-09-27. KSI is guidance and orchestration, not a punitive sandbox. This design authorizes the scoped source, test, example, and installation-guidance repair; it does not authorize automated edits to a user's global configuration, a service restart, provider use, credential access, publishing, or Git publication actions. - -## Context - -KSI's V2 plugin currently applies one `ctx.agent.transform` to OpenCode built-ins and KSI-defined roles. OpenCode 2.0.18 exposes built-ins to `AgentEditor` during plugin setup, but config-defined custom agents are absent at that point and appear in `/api/agent` after plugin transforms. KSI's missing-ID guard consequently throws before tools, commands, runtime hooks, and event subscriptions are registered. KSI defines seven native-config-owned roles: the six IDs in `ROLES` (including `explore`) plus `design`. - -The behavior is reproduced with a minimal isolated V2 server. The V2 `AgentEditor` reference documents `list`, `get`, `update`, `remove`, and `default`; it has no `add`. Calling `update` for an ID that is absent from `get` did not throw in one probe, but this is not a documented way to create agents and must not become an implicit registration contract. OpenCode's built-in transform IDs include `build`, `plan`, and `explore`; `design` is configured by KSI and is not a built-in transform target. - -## Goal - -Make KSI operate using OpenCode V2's native config-owned custom agents while retaining plugin-owned runtime tools, commands, hooks, events, and built-in `build`/`plan` guidance. Roles should make the normal workflow clear without preventing recovery: Build normally delegates implementation, but can directly inspect, edit, and use shell when needed. Keep user-selected model/variant values and valid explicit step budgets when the KSI agent snippet is merged into an OpenCode configuration. - -## Approved decisions - -- KSI supplies its seven role definitions (`design` plus the six IDs in `ROLES`) as a copyable native V2 `agents` settings snippet. `explore` is a built-in OpenCode ID that KSI deliberately configures with its own subagent policy; `design` and the other KSI worker roles are config-defined. -- The exact native mode mapping is Primary: `plan`, `design`, `build`; Subagent: `explore`, `developer`, `test-runner`, `reviewer`, `research`, `design-critic`. -- OpenCode configuration is the runtime source for custom agent `mode`, `description`, `system`, minimal role `permissions`, `hidden`, and default `steps` fields. KSI's role prompts and policy metadata remain the source used to produce and verify the snippet. -- During `agent.transform` and plugin setup, the plugin does not query for, require, transform, create, or upsert KSI-defined native-config-owned roles. It transforms only OpenCode's built-in `build` and `plan` agents. It does not transform `design` or `explore`. -- Missing KSI role configuration is an accepted deployment failure mode: KSI plugin setup still registers its tools, commands, hooks, and events, but missing subagent IDs remain unavailable and attempts to delegate to them fail at use. At delegation time, the plugin read-only validates the final agent catalog and KSI role signature; it must not silently synthesize or upsert roles. This validation is especially required for `explore` so the built-in native Explore cannot be mistaken for the KSI-configured Subagent. -- Install guidance requires merging the KSI snippet into every OpenCode server configuration that loads the KSI plugin: shared, OpenChamber-managed, and external servers. The project example is not assumed to be discovered from the plugin package directory. -- The snippet omits all `model` values. When merging entries, retain existing model/variant, any valid positive user `steps`, and unrelated request settings; add KSI fields rather than replacing whole agent objects. -- The existing `developerTestRunner` opt-in remains opt-in and defaults to `false`. Its plugin option must be paired with the corresponding native Developer permission/prompt variant in the agent snippet. -- Design has normal workspace capability within the approved task scope. `design-previews/` is the default convention for standalone previews and visual artifacts, not a mandatory destination or a permission boundary. Do not add a `**/design-previews/**` workaround or any active-location-derived path enforcement. -- Avoid catch-all `*: deny` lockdowns for productive roles. Preserve evidence-role independence with narrow explicit read-only rules where it is needed, rather than a global deny that prevents legitimate work or recovery. -- Do not automatically edit the user's global or OpenChamber-managed config, access credentials, call providers, change the public version, publish/release the KSI package, commit, or mark milestone m03 accepted. - -## Proposed design - -### Native snippet and source consistency - -- Add a standalone V2 JSON snippet under `examples/` containing an `agents` object for `design`, `explore`, `developer`, `test-runner`, `reviewer`, `research`, and `design-critic`. -- Include native V2 modes, role descriptions, prompts, ordered `{ action, resource, effect }` rules, hidden state for `design-critic`, and KSI default step budgets. Preserve useful role distinctions and the Developer-to-Test-Runner opt-in without turning the roles into an execution deadlock. -- Produce the checked-in snippet deterministically with a dependency-free ESM generator using KSI role metadata/permissions and the existing prompt sources. The package gate regenerates both variants in a temporary directory and compares bytes with the checked-in artifacts; do not maintain untested duplicate policy definitions. -- Update `opencode.jsonc.example`, `INSTALL.md`, and OpenChamber guidance to show a non-destructive merge: retain each pre-existing agent object's `model`, valid `steps`, request settings, and unrelated fields; merge KSI fields and permission rules according to V2 ordering semantics. -- Add a pure object-level merge helper used by the generator/tests: preserve an existing `model`, any safe positive integer `steps`, and unrelated/request fields. It must not replace whole agent objects, introduce catch-all productive-role lockdowns, or read/write user configuration files. Permission ordering must be tested against V2 semantics only where an explicit narrow rule depends on it. - -### Plugin lifecycle - -- Split native transforms so the KSI built-in policy transform handles only `build` and `plan`. -- Remove the requirement that KSI-defined native-config-owned role IDs (including `design`) exist in the transform editor and remove KSI-role `editor.update` calls. No custom-role checks may throw before plugin registrations; final catalog/signature validation occurs read-only when delegation is requested. -- Keep V2 plugin setup order and native tool/command/hook/event APIs. A missing snippet must not disable the whole plugin; KSI custom roles simply remain absent. -- Keep runtime policy for Build, Plan, continuity, evidence tools, helper ancestry, and cleanup, while correcting the recovery failure: Build normally delegates to Developer but may directly read, edit, and run shell commands to repair or complete approved work. Do not use the plugin to lock Design to preview paths. - -### Configuration variants - -- Ship two generated snippets: the default with Developer-to-Test-Runner delegation disabled, and an explicit opt-in variant enabling only the existing narrow permission and matching prompt text. The generator takes the same boolean option as the plugin; documentation requires the selected snippet and plugin option to agree. The secure default remains off. -- Do not include a plugin-time fallback that creates the roles when configuration is absent. - -## Compatibility and failure behavior - -- With the snippet installed, KSI plugin status is active; all seven KSI-defined native-config-owned roles appear with the intended native V2 modes, permissions, descriptions, prompts, hidden state, and default steps; user's model/variant and valid explicit step selections survive the merge; `/complete`, `/review`, evidence tools, hooks, and event handling are registered. -- Without the snippet, KSI plugin status is still active and plugin-owned surfaces register. KSI-defined native-config-owned roles are absent, so delegated use of an absent role fails rather than silently invoking another agent. Because `explore` is also an OpenCode built-in, delegation must read-only validate the final catalog and reject a missing KSI Explore signature rather than silently falling back to native Explore. -- Built-in primary policy remains enforced by KSI's plugin transform. No V1 plugin API, V1 permission maps, undocumented AgentEditor upsert behavior, or provider calls are introduced. - -## Verification design - -- Unit tests verify generated snippets are valid JSON, contain exactly the seven KSI-defined native-config-owned role IDs, use V2-native fields/rules, contain the exact Primary/Subagent mode mapping, include prompt text and budgets, preserve unrelated configuration fields, and keep the opt-in off by default. -- Parity tests compare generated snippet data to the policy/prompt source so edits cannot silently desynchronize runtime expectations and install artifacts. -- Plugin tests use a V2 transform editor containing built-ins only. Setup must complete without `design` or worker-role IDs and must register tools, commands, session/tool hooks, events, and cleanup. Only `build` and `plan` receive KSI transforms; the native configuration owns `explore` and `design` along with the other KSI-defined roles. Separate delegation tests read-only validate the final catalog/signature and reject an omitted KSI Explore signature. -- Merge-helper tests demonstrate preserving existing model/variant, valid positive `steps`, request settings, and unrelated agent fields; test both default and opt-in variants. -- Package/documentation tests require the native V2 snippet, all-server installation instructions, the missing-snippet behavior, and the managed/external OpenChamber ownership boundary. -- Disposable OpenCode 2.0.18 runtime checks use an isolated config with no provider/auth state. Verify both installed-snippet and omitted-snippet cases, including plugin status, exact Primary/Subagent mode mapping for all KSI-defined roles, `/complete`, tool catalog, Build's direct recovery capability, clear late failure for absent KSI roles (including built-in `explore` fallback), and Design workspace capability. Verify `design-previews/` is documented as a convention, not enforced as a path gate. Do not modify the user's global config as part of these checks. -- Run the existing offline suite, package-install gate, tarball dry run, and `git diff --check`. Live OpenChamber UI, user acceptance, provider behavior, and milestone m03 remain separate gates. - -## Scope boundaries - -- No edit to OpenCode itself or global OpenCode/OpenChamber configuration in this implementation slice. -- No KSI package publication/version bump, credentials access, provider invocation, OpenChamber authentication, commit, or push. -- No unapproved change to Test Runner's delegated-execution option, unrelated models, tools, or continuity semantics. -- No claim that publishing an OpenCode issue fixes the runtime; upstream issue #51598 remains independent and pending. - -## Implementation constraints - -- The merge helper is a pure function over parsed objects. It never parses, writes, or rewrites global/project JSONC; installation remains an explicit user action. -- Runtime tests must verify actual OpenCode 2.0.18 configuration precedence and effective permission ordering in an isolated server. OpenCode 2.0.18 resolves internal edit resources relative to the active Location, so a static `design-previews/**` rule would make nested locations authorize their own nested preview directories. This is why path enforcement was abandoned: it neither expresses a stable project-root contract nor serves the approved guidance-first model. -- A user-selected model/variant and valid positive `steps` are retained. Existing unrelated settings remain intact; only the narrowly specified native role fields are merged. - -## Current implementation state - -The working tree contains a partial, uncommitted V2 ownership transition. Its recorded baseline is `npm run check`: 202 passed, 8 failed stale ownership tests; this is diagnostic WIP, not a completed implementation or acceptance result. The m03 product milestone remains in progress and unaccepted. The remaining repair must add the RED coverage for the approved soft-role behavior, update implementation and installation artifacts, run isolated no-auth V2 verification, and report shared-service verification separately only after explicit approval for any global configuration change and restart. diff --git a/docs/superpowers/specs/2026-09-29-v2-npm-distribution-design.md b/docs/superpowers/specs/2026-09-29-v2-npm-distribution-design.md deleted file mode 100644 index e0f2719..0000000 --- a/docs/superpowers/specs/2026-09-29-v2-npm-distribution-design.md +++ /dev/null @@ -1,45 +0,0 @@ -# KSI OpenCode Harness V2 npm distribution - -Date: 2026-09-29 -Status: Design for Human review; no registry action authorized by this document alone - -## Intent and boundaries - -Make the current native OpenCode V2 installer publicly installable from the existing `ksi-opencode-harness` npm package name. The old `0.4.0-beta.0` tarball is a V1 runtime plugin; the Human reports no existing installers. Remove that *version* after the new V2 version is published and independently install-tested. Keep the source implementation a standalone, preview-first CLI with four optional Markdown agents and an opt-in design kit. Do not reintroduce the runtime plugin or change the user's global OpenCode configuration, Codex, Superpowers, or the separate dirty human-centered-workflow worktree. - -This release is a public **beta**, not a claim that real-product design output has Human acceptance. Human acceptance of product-screen quality remains a separate deferred decision. No npm release, registry deletion, service restart, or global installer apply is part of writing this design. - -## Distribution contract - -- Retain the unscoped public name `ksi-opencode-harness`; publish new version `0.4.0-beta.1`, never republish `0.4.0-beta.0` (npm versions are immutable even after unpublish). -- Remove the manifest's `private: true` guard only in the reviewed release candidate. Explicitly publish the pre-release under `latest` so an unqualified installation selects V2; label it beta in package documentation. The existing `next` tag also points to V1 and must be moved to V2 or removed after V2 installation succeeds, so no documented/default tag silently installs the retired plugin. -- The npm artifact exposes the `ksi-opencode` executable via `bin` and includes only the CLI, four role sources, approved optional design-kit/vendor files, licenses, and public documentation. It excludes old plugin entrypoints, internal plans/specs, tests, credentials, live configuration, and preview/session artifacts. Installing the npm package alone must not write OpenCode configuration or start a plugin. Explicit `install --target ...` still previews by default; `--apply` writes only after review under the current collision/back-up rules. -- Update `README.md`, `INSTALL.md`, and `docs/releasing.md` for an npm-package consumer, with an explicit versioned or otherwise reproducible CLI invocation. Explain the V1-to-V2 change of interface, how to preview before apply, optional design-kit behavior, Node requirement, and that provider/model choices remain user-owned. Do not suggest adding this package to OpenCode's `plugins` list. - -## Safe release sequence - -1. Read-only preflight: confirm source branch and exact commit; check the registry's existing versions/tags, package ownership/publish authentication without exposing credentials, and the npm unpublish policy for the V1 version. Verify that no other package depends on it and that the registry permits removal; a report of no known *users* is not proof that registry-policy conditions are satisfied. -2. Prepare `0.4.0-beta.1` in the isolated release worktree. Review the exact diff and the generated tarball file list. Run full source checks, package checks, a disposable offline tarball install, and an isolated OpenCode V2 effective role/skill catalog check without using provider credentials. Run a command-path smoke check using the *packed* package (not the source checkout) to show read-only preview and repeatable application in a disposable config target. Confirm that installing the package by itself has no global side effects. -3. Commit and integrate the reviewed release source through the normal branch process; record the exact commit to be published. Present the final release candidate and verification to the Human. Only after written spec review, implementation-plan approval, and reviewed candidate should the externally visible registry actions run. Publish V2 under `latest`, verify the registry tarball metadata and versioned install/CLI behavior, and ensure `latest` resolves to V2. Fix `next` so it does not point to V1. -4. Only after V2 passes independent install checks, unpublish **only** `ksi-opencode-harness@0.4.0-beta.0` if the registry allows it. Never unpublish the whole package: doing so may impose a 24-hour name cooldown. If version removal is denied, keep V2 published and its tags correct; deprecate the V1 *version only* with an explanatory message, record the blocker, and stop rather than force-removing the package. -5. Verify registry versions/tags and the V2 CLI again, then report the exact source commit, version, tag, deletion/deprecation outcome, and installation command. Keep any registry action and its result in release evidence without pretending a Git commit reverses npm publication. - -## Failure and recovery - -- Preflight, packaging, or disposable-install failure: do not publish or remove V1; correct the candidate and rerun checks. Do not modify the active user environment to make a test pass. -- Missing npm owner permission, authentication, or 2FA: stop before publishing. Do not request secrets in chat or store them in repository files. -- Publish succeeds but registry install/smoke check fails: do not remove V1. Freeze tag changes, diagnose using a new version (never overwrite a published tarball), and report the partial release state. Preserve evidence; do not assume unpublishing V2 is an automatic rollback. -- V1 unpublish denied: retain the functioning V2 version, change only V1's deprecation metadata when permitted, and disclose that physical removal was not possible. A successful V2 release is not evidence that registry deletion succeeded. - -## Acceptance and non-goals - -Acceptance requires a new V2 version obtainable from the public npm registry, an unqualified install resolving to V2, a functioning packed CLI that previews and applies only to an explicit disposable target, the expected four native agents and optional kit in the package, and no default tag resolving to the V1 plugin. Record whether V1 was removed or merely deprecated. Do not claim a live product-design quality improvement from package or catalog checks. - -Non-goals: npm organization/name migration, background updater, OpenCode plugin registration, automatic migration/deletion of legacy global files, modification of other tools' configuration, npm stable-version claim, or removing the separate worktree with unsaved work. - -## Sources and current evidence - -- Current source manifest and packaging contract: `package.json`, `README.md`, `INSTALL.md`, `docs/releasing.md`, `scripts/check-package.mjs` at `a163da7` (V2 installer; source `0.4.0-beta.0`, `private: true`). -- Read-only npm lookup before this design: `ksi-opencode-harness@0.4.0-beta.0` is published and both `latest` and `next` point to it. This must be rechecked at release time. -- npm registry policy: (published name/version cannot be reused; total package removal causes a 24-hour republish wait; older versions have additional eligibility conditions). -- OpenCode V2 agents and skills are native files, not KSI runtime-plugin registration: and . diff --git a/docs/superpowers/specs/2026-09-30-dgx-remote-project-workspace-design.md b/docs/superpowers/specs/2026-09-30-dgx-remote-project-workspace-design.md deleted file mode 100644 index 1b134d9..0000000 --- a/docs/superpowers/specs/2026-09-30-dgx-remote-project-workspace-design.md +++ /dev/null @@ -1,142 +0,0 @@ -# DGX 원격 프로젝트 폴더 작업 — 설계 - -Date: 2026-09-30 -Status: revised spec and native execution plan approved; Labs gateway deployed; workspace implementation partial, custom-image rollout paused on pre-change daemon test failures - -## 1. 목적과 합의된 범위 - -어느 기기에서 브라우저로 접속하든 DGX 서버의 프로젝트 폴더를 선택하고, 그 폴더의 실제 파일을 -작업 대상으로 사용한다. 접속 기기의 파일 선택기나 단순 참고 폴더 첨부로 대체하지 않는다. - -- 탐색 범위: DGX의 `/home/ksi` 전체 하위 디렉터리. 기본 시작 화면은 홈이며 Desktop 바로가기를 제공한다. -- 사용자가 프로젝트 폴더를 선택하고 `열기`를 누르면 그 폴더의 읽기·쓰기 연결을 요청한 것으로 처리한다. - 프로젝트별 사전 승인 목록, KSI-CCTV 전용 제한, 별도 관리자 승인 절차는 없다. -- 홈 탐색은 디렉터리 목록 열람이다. 홈 전체 파일 내용을 모델에 전달하거나 홈 전체를 writable 연결하지 않는다. -- 첫 검증 대상은 `/home/ksi/Desktop/KSI-Projects/KSI-CCTV`이며, 이후 다른 홈 하위 프로젝트도 같은 흐름으로 연다. -- 홈 자체는 탐색 루트이지 프로젝트 작업 루트가 아니다. 실제 실행/저장은 선택한 하위 프로젝트를 기준으로 한다. -- 기존 KSI-CCTV 제품 코드와 운영 설정은 초기 연결 검증에서 수정하지 않는다. -- 이는 별도로 설치된 디자인 작업 공간의 원격 운영 개선이다. KSI npm 설치기의 범위를 넓히지 않는다. - -## 2. 확인된 문제 - -실제 설치된 서버의 폴더 선택 경로는 Linux에서 native `zenity`를 실행한다. 현재 컨테이너에는 -해당 실행 파일과 데스크톱 display 연결이 없으며, 요청은 500과 `zenity is not installed`를 반환한다. -또한 DGX 프로젝트 경로가 컨테이너에 연결돼 있지 않다. UID 정렬이나 GUI 패키지 설치만으로는 -원격 브라우저에서 서버 폴더를 선택하는 요구를 해결하지 못한다. - -기존 코드에는 서로 다른 두 기능이 있다. - -- 작업 디렉터리/linkedDirs 첨부: 에이전트에게 참고 폴더를 알려주는 기능. -- 기존 폴더 프로젝트 열기: `/api/import/folder`가 canonical `metadata.baseDir`를 실제 프로젝트 루트로 설정. - -두 번째 기능을 재사용한다. 일반 프로젝트 생성/metadata 수정으로 baseDir 보호를 우회하지 않는다. -실제 실행 이미지와 소스 checkout의 버전 표시가 다르므로 구현 전 빌드 산출물의 대응 리비전을 확인한다. - -## 3. 대안과 선택 - -1. **선택: 홈 탐색 + 선택 기반 자동 작업 루트 연결.** 사용자가 폴더를 고르는 일반 개발 도구처럼 - 동작한다. 기존 folder import를 재사용하고, host 연결 서비스가 선택한 프로젝트의 연결을 처리한다. -2. **제외: 프로젝트별 고정 승인 목록.** 초기 smoke test에는 간단하지만 프로젝트를 바꿀 때마다 - 운영자가 설정해야 하므로 이번 사용성 요구에 맞지 않는다. -3. **제외: 홈 전체 writable 마운트.** 연결은 간단하지만 폴더 탐색을 홈 전체 쓰기 권한으로 확대한다. - 사용자가 설명한 탐색/실제 작업의 구분에 맞지 않는다. native GUI 공유도 원격 웹 선택을 대체하지 못한다. - -## 4. 구성과 데이터 흐름 - -```text -원격 브라우저 - → 기존 인증/동일 출처 검사 - → daemon의 원격 폴더 목록 프록시 - → DGX host의 목록 API (홈 하위 디렉터리 이름만) - → 사용자가 프로젝트 폴더 선택/열기 - → host 연결 서비스가 선택한 프로젝트 경로를 자동 연결 - → 기존 folder-import 경로 (기존 workspace 권한 검사 유지) - → canonical 외부 프로젝트 루트 - → 실제 DGX 파일에 읽기/쓰기 및 미리보기 -``` - -### Host 탐색과 연결 서비스 - -- 서비스는 DGX loopback에만 노출하고 daemon과의 호출을 별도 인증한다. 토큰은 문서/소스/Git에 저장하지 않는다. -- 목록 API는 디렉터리 목록과 경로 상태만 반환한다. 파일 내용, 임의 명령, 마운트 변경 기능을 겸하지 않는다. -- 서버가 정한 홈 루트와 상대 경로를 사용한다. 직접 경로 입력도 같은 canonical 경로 검사에 통과하면 허용한다. -- 숨김 폴더는 기본 숨김/표시 토글을 제공한다. 심볼릭 링크는 canonical 대상이 홈 안이면 탐색 가능하며 - 홈 밖이면 연결할 수 없는 이유를 표시한다. 일괄적인 숨김/링크 차단으로 프로젝트를 찾지 못하게 하지 않는다. -- 응답은 이름, 상대 경로, 탐색 가능 여부, 선택 가능 여부와 불가 사유로 제한한다. 홈에서 부모 이동은 비활성화한다. -- 연결 API는 인증된 `열기` 요청의 canonical 프로젝트 경로만 받는다. 서버 설정에 고정된 디자인 컨테이너의 - bind mount만 관리하며, 임의 컨테이너/명령/host 경로/이미지를 클라이언트가 지정할 수 없다. -- host 연결 서비스는 Docker 관리 권한을 필요로 하는 별도 신뢰 컴포넌트다. 이 권한을 에이전트나 - 컨테이너에 전달하지 않으며, 입력 검증과 인증 실패 테스트를 배포 전 필수로 한다. -- 도우미가 실패하면 브라우저에 원인과 재시도 안내를 표시한다. 접속 기기 선택기로 자동 대체하지 않는다. - -### 선택 기반 연결과 컨테이너 - -- 인증된 사용자의 `열기`가 연결 의사 표시다. 서버는 존재하는 디렉터리인지, 홈의 하위 경로인지 확인하고 - host와 container에 같은 절대경로로 writable 연결한다. 일반 folder import의 workspace 권한 검사는 유지한다. -- 직접 import/작업 루트 교체 요청에도 같은 홈 경계와 연결 상태 검사를 적용한다. UI만 검사하지 않는다. -- 홈 루트 자체와 SSH/인증 저장소 등 자격증명 전용 디렉터리는 프로젝트 선택 대상이 아니다. -- Desktop 전체, 홈 전체, Docker socket을 에이전트 컨테이너에 추가 마운트하지 않는다. -- 연결 상태는 host 서비스가 관리한다. 이미 연결된 프로젝트는 바로 열고, 새 bind mount가 필요하면 - 서비스가 기존 설정을 보존한 채 컨테이너를 재생성한다. 사용자가 compose를 직접 편집하지 않는다. -- 실행 중인 생성 작업이 있으면 자동 재생성하지 않고 완료 후 다시 열도록 안내한다. 연결 중 상태를 - 표시하고 health 회복 후 import를 이어간다. 실패하면 기존 구성으로 복구하고 연결 실패를 표시한다. -- 연결한 프로젝트들은 이후 다시 열 수 있도록 유지한다. 공유 컨테이너의 에이전트는 이미 연결된 - 다른 프로젝트에도 OS 수준 접근이 가능하다. 선택한 cwd를 프로젝트별 sandbox라고 설명하지 않는다. -- 기존 credential mounts와 host networking은 별도의 알려진 신뢰 경계다. 이 변경을 완전한 OS sandbox로 설명하지 않는다. -- 프로젝트 안의 `.env` 등 비밀정보도 자동 읽기/모델 전달 대상이 아니다. UI/API의 민감 경로 차단과 - 기존 credential 노출 경계를 구현 계획에 명시하고, 전체 프로젝트 마운트만으로 비밀정보 격리를 주장하지 않는다. - -### 웹 UI와 기존 import - -- headless/remote 모드에서는 브라우저 내부의 서버 폴더 목록을 사용하고 `DGX 서버 폴더`라는 위치를 표시한다. -- 홈/ Desktop 바로가기, breadcrumb, 부모 이동, 직접 경로 입력, 숨김 폴더 토글을 제공한다. - `추가 승인 필요` 버튼 대신 선택 → 연결 상태 → 프로젝트 열기로 이어지는 단일 흐름을 제공한다. -- 선택한 루트와 실제 쓰기 대상 경로를 열기 전에 명시한다. 기존 파일에 영향을 줄 수 있음을 알린다. -- 폴더 프로젝트 열기에는 기존 import의 workspace 권한/경로 검사/entry 감지를 사용한다. -- 파일 읽기/쓰기도 canonical 작업 루트 밖을 가리키는 symlink를 거부해야 한다. 에이전트 실행의 - cwd/산출물 루트가 이 폴더와 일치하는지도 확인하며, UI 저장 성공만으로 생성 실행까지 검증됐다고 주장하지 않는다. -- 기존 관리형 디자인 프로젝트의 저장 루트를 조용히 바꾸거나 산출물을 자동 이동하지 않는다. -- 참고 폴더 첨부 UI에 같은 목록을 사용하더라도 그 기능은 참고용으로 유지한다. 실제 작업에는 - `프로젝트 폴더 열기` 흐름을 사용한다. -- 기존 로컬 데스크톱 모드의 native picker는 유지한다. 원격 모드에서만 명확한 분기를 사용한다. - -## 5. 기존 파일 보호와 배포 - -- KSI-CCTV 현재 baseline은 `master`, `a89ef2d`, tracked worktree clean으로 관찰했다. 실행 시 다시 확인한다. -- `.env`, 카메라/운영 설정, 데이터, 모델, Git 내부 파일은 초기 테스트에서 읽거나 변경하지 않는다. -- 연결 검증용 파일만 `design-previews/remote-workspace-smoke/` 아래 새 이름으로 생성한다. -- 동명 파일이 있으면 거부한다. 실제 제품 화면 생성/수정은 별도의 사용자 요청에서 수행한다. -- rollback은 기존 compose 설정/이미지 복구와 이번 변경에서 생성한 파일만 대상으로 한다. -- 기존 upstream checkout의 compose 변경은 사용자 작업으로 보존한다. 별도 작업 공간에서 구현/빌드하고 - 재현 가능한 source-only 통합 자산으로 기록한다. 게시된 npm beta와 태그는 변경하지 않는다. -- container 재생성 및 서비스 추가는 승인된 구현 계획/배포 범위에 따라 수행하고, 진행 중인 디자인 작업을 확인한다. - -## 6. 검증과 사용자 acceptance - -자동/통합 검증: - -1. 인증 없는 목록 요청, 다른 origin, 상위 이탈, 절대경로 주입, symlink 이탈이 거부된다. -2. 홈/ Desktop/다른 홈 하위 프로젝트를 탐색·선택할 수 있고, 고정 승인 목록 없이 연결된다. - 직접 경로 입력과 홈 내부 symlink는 정상 동작하고, 홈 밖 경로와 자격증명 저장소 선택은 거부된다. -3. KSI-CCTV 및 별도 임시 fixture 프로젝트의 import가 canonical 외부 루트를 유지한다. - 파일을 관리형 저장소로 복사하지 않는다. 임시 fixture 외 다른 제품 프로젝트를 테스트 목적으로 수정하지 않는다. -4. 지정 테스트 파일을 UI/API에서 작성하면 DGX의 같은 파일에 같은 bytes가 존재한다. -5. DGX에서 테스트 파일을 수정하면 디자인 작업 공간에서도 변경을 읽을 수 있다. -6. 컨테이너 재시작 뒤 프로젝트 경로 연결이 유지되고, MCP는 별도로 재확인한다. -7. 기존 tracked 파일, 운영 설정, provider 설정을 변경하지 않았음을 before/after 증거로 확인한다. -8. 새 프로젝트 연결 시 자동 재생성/health 대기/import 재개, 연결 실패 복구, 실행 중 작업 보호를 검증한다. - 연결 API에 임의 컨테이너/명령/홈 밖 경로를 주입해도 host 관리 동작이 일어나지 않아야 한다. - -사용자 acceptance: 원격 브라우저에서 `홈 → Desktop → KSI-Projects → KSI-CCTV`를 열고, -테스트 파일을 저장/다시 읽어 DGX 원본 폴더에서 실제 작업된다고 확인한다. 이어 별도 홈 하위 fixture -프로젝트를 관리자 설정/추가 승인 없이 열 수 있는지 확인한다. - -Cloud 상태/모델 오류, optional brand/font 파일, 협업 workspace context 문제는 별도 원인이다. -폴더 연결을 위해 Cloud CLI 설치나 가짜 브랜드 파일 생성을 하지 않는다. - -## 7. 승인 단계 - -초기 Desktop/KSI-CCTV 전용 설계는 사용자 검토로 승인됐으나, 이후 사용자가 홈 탐색과 적극적인 연결을 -요청해 이 문서를 수정했다. 이후 수정본과 구현 계획의 native 실행/서버 반영이 승인됐다. -계획: `../plans/2026-09-30-dgx-remote-workspace.md`. Labs 조회 gateway만 우선 배포했으며, -홈 폴더 연결은 원본 daemon 테스트 기준선 실패에 대한 처리 결정 전까지 배포하지 않았다. diff --git a/docs/superpowers/specs/2026-10-03-design-flow-hardening.md b/docs/superpowers/specs/2026-10-03-design-flow-hardening.md deleted file mode 100644 index 3f6ae2d..0000000 --- a/docs/superpowers/specs/2026-10-03-design-flow-hardening.md +++ /dev/null @@ -1,23 +0,0 @@ -# Design-flow hardening - -## Approved intent -The user requested implementation of the preceding read-only review's improvements on 2026-10-03. Preserve Local Codex design ownership, OpenCode engineering ownership and human direction approval. This is a bounded hardening of the existing integration, not a new orchestration architecture. - -## Scope -- Separate new-artifact intake from existing-artifact refinement; reuse agreed requirements and ask only for missing material decisions. Disclose unsupported brief-card locales rather than pretending Korean is supported. -- Interpret physical run status alongside deliverable validation, agent explanation and actual required output. Distinguish produced output, questions, environment blockers, verified no-change and unknown outcomes. Never treat an old project preview as the latest run's result or automatically retry a failed design run. -- Keep a compact product/design/storage/brand binding and approval snapshot in the existing task ledger; an OD-owned design folder is legitimate but is not proof of product-repository linkage. -- Add optional V2 Plan-only permissions: deny the design MCP namespace, then allow its explicitly known read tools. Preserve native prompts, Build permissions, models, unrelated configuration and installer behavior. -- Apply the approved guidance and Plan rules to the current user's global configuration after private backup and verify effective native permission decisions without invoking design mutations or providers. -- Document deployment-readiness checks that compare the effective image, Compose files and security profiles with the approved deployment and prove file-access capability when needed. Preserve security restrictions; infrastructure changes and design retries require separate authorization. -- Avoid repeat context discovery and large diagnostic/bundle pulls during status polling. Do not claim measured latency or cost improvements. - -## Evidence and constraints -The reviewed EVENTOUCH refinement physically succeeded but returned no artifact and explicit file-access failure. Its current container uses default AppArmor and only base/Linux Compose files, unlike the previously recorded restricted deployment. The remote connector still records the approved image/security Compose files; this proves drift, not who changed it or the exact namespace-denial mechanism. - -No upstream application edits, new dependencies, native prompt replacements, cloud fallback, credential reading/sharing, container restart, security-profile change, product implementation, push, npm publication or automatic generation are authorized by this slice. Existing upstream dirty files and unrelated worktrees remain untouched. - -Later scoped authorization: the user approved restoring the existing reviewed deployment after compatibility/active-work checks, with volumes/credentials/security preserved and one task-owned Local Codex file-access/output fixture. This permits the necessary fixed-service recreation with the existing approved profiles; it does not authorize EVENTOUCH regeneration, unreviewed profiles or general infrastructure changes. Stop if compatibility or active work cannot be established. - -## Acceptance -Guidance is consistent across the integration guide and live operating instructions. Native isolated permission evaluation denies Plan design mutations and unknown future tools while allowing required reads; Build behavior remains unchanged. Existing harness and packed-consumer checks pass. Report infrastructure recovery and real end-to-end design acceptance separately rather than claiming they were completed by documentation tests. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md deleted file mode 100644 index 9e7a6a9..0000000 --- a/docs/troubleshooting.md +++ /dev/null @@ -1,42 +0,0 @@ -# Native installer troubleshooting - -- Absolute target, other machines: Linux/macOS use `"$PWD/.opencode"`; Windows PowerShell uses `(Join-Path (Get-Location) ".opencode")`. A project target affects one project; the server-global config directory (usually `~/.config/opencode`, managed/external servers differ) affects every project on that server. Verify the actual server's directory on that machine; no Windows/macOS execution outcome is promised. -- `conflict`: A target custom `agents/*.md` file differs. Review the preview output. `--apply --replace` backs each replaced file up before writing; do not use it until the owner approves the change. -- `symlink`: The installer refuses a symlinked managed directory or file. Choose the real intended config directory and inspect where the link points; do not bypass the check blindly. -- A role still appears Primary: confirm its effective V2 `mode: subagent` from a fresh OpenCode session and inspect other global/project config layers. The installer does not rewrite existing JSONC or prove which layer wins. New custom roles otherwise default to Primary. -- `Agent developer cannot run as a subagent`: the effective Developer definition is absent, disabled, or still Primary. Check the server's actual config directory and native agent catalog; a file in a different OpenCode server's config is not enough. -- `/complete` or `/review` missing: expected for a fresh installation. Ask Build directly. Older command files or the old plugin can still provide these shortcuts until separately removed. -- Built-in Build, Plan, or Explore still uses a KSI prompt: inspect other JSONC layers and older `agents/build.md`, `agents/plan.md`, or `agents/explore.md` files. The new installer does not remove an older override automatically. -- `ksi_*` tool missing or no automatic checkpoint injection: expected in the native-only edition. Use normal authorized tools and explicit state reading; do not claim plugin-only behavior remains. -- HTTP 401 from a local API catalog is an authentication boundary, not evidence that agents or commands loaded successfully. Do not use provider calls or credentials as a shortcut to package verification. -- Design run says success but no output: execution termination is not design completion. Check required-output validation and the inner explanation; distinguish a genuine question, verified no-change and environment failure. Never present the project's old preview as the latest run's output or retry blindly. See [result interpretation](integrations/opendesign.md#interpret-run-results-before-reporting-completion). -- A session stopped mid-work with `Failed to drain Session` / `AI.Error: ...`: preserve uncommitted work and record the active checkout/diff before recovery; a delivered notification may still be unprocessed. Inspect bounded actual ERROR records in `~/.local/share/opencode/log/opencode.log`, not command echoes or entire request bodies. `invalid parameters`/reasoning-item/tool-choice errors identify a request boundary, not which component caused it. Compare effective agent/model/steps, the failing step and the actual service version (`opencode api get /api/info`, which can differ from `opencode --version`). Do not call it a provider-only failure, disable Code Mode, or repeatedly compact/retry without a cause-specific hypothesis. Never delete the worktree or replay design runs. -- The local design execution agent cannot start its sandbox/file tools while the web server is healthy: compare actual image, Compose files and enforced security profiles with the approved connector deployment. Preserve prior artifacts and inspect the concrete error. Do not disable isolation or recreate a shared container without scoped authorization/active-run checks; see [capability verification](integrations/opendesign.md#resolve-once-verify-the-actual-capability). -- Plan can invoke a design mutation despite its edit denial: MCP calls use separate normalized permission actions. Explicitly adopt the Plan-only deny/read-allow block in `opencode.jsonc.example`, preserving existing rules and checking actual project precedence. The two-role installer does not apply it. - -The removed `0.4.0-beta.0` was an old V1 plugin, not the native V2 installer. Its removal from npm does not uninstall any local V1 files or registration. Do not register this source as an OpenCode plugin. Keep any separately installed Superpowers or goal plugin untouched when migrating. - -## Auto-only endpoint rejects the final summary - -Observed in V2 2.0.22/2.0.24: a finite agent `steps` allowance makes the final -request use `tool_choice: none`. An auto-only endpoint rejects that request before -the agent can summarize. This is not evidence of MCP failure or product failure. - -In a source checkout, run the credential-isolated local characterization: - -```sh -node scripts/verify-tool-choice-isolated.mjs -``` - -It reproduces rejection, tests a **model-scoped** `body.tool_choice: "auto"` -overlay, and checks that an unsolicited final-step tool call is still rejected by -the native step-limit guard. It starts only owned loopback fixtures, never calls a -real provider or shared session, and does not install the CLI. Other child egress -is not monitored. The opt-in test uses `KSI_TOOL_CHOICE_INTEGRATION=1`. - -For a confirmed auto-only model, the V2 [model request body overlay](https://opencode.ai/v2/docs/models#options) -can be a narrow compatibility workaround. Preserve the selected model/variant, -agent steps, permissions and native modes. Do not force this provider-wide or -drop step limits; providers and future versions may have different behavior. -Verify the actual serving version and resolved configuration before applying it, -and observe real use afterward. The KSI installer applies no such model policy. diff --git a/docs/verification.md b/docs/verification.md index e4c681f..c5520f9 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -1,49 +1,36 @@ -# Verification and remaining acceptance +# Verification reference -The dated catalogs below describe earlier configurations and are kept as history. Research, Design Critic, and then the Design primary and its vendored skill kit were retired in turn. Test Runner left the default bundle in the 2026-10-09 two-role simplification. The current bundle ships two subagent roles (Developer, Reviewer), no design kit, and no design primary role. Public npm 0.5.0-beta.9 is the release candidate for that two-role bundle; registry publication, tag, and shipped status remain with Primary and are not claimed here. Previous public release 0.5.0-beta.8 was the historical three-role release. +The [product-completion principles](../examples/project-AGENTS.md) define completion; this page is +a tool reference, not another approval gate. Review opinions are not execution results. -`npm run check` covers the two-role native bundle, installer safeguards, the explicit rejection of the removed `--with-design-kit` and `--developer-test-runner` flags, absence of retired plugin entrypoints, and relative links in packaged documents (a link that leaves the packaged file set fails the check). `npm run check:package` packs the current source and performs an offline local-tarball install in a disposable config directory, including both removed-flag rejections with no target writes and preservation of an existing optional verification role and its JSONC routing. This local tarball is the beta.9 candidate, not the historical three-role public release 0.5.0-beta.8. `node scripts/verify-native-v2-isolated.mjs` checks the effective agent catalog in a credential-free OpenCode V2 environment and that the retired design skills have not reappeared. It evaluates the Plan design boundary and two Reviewer edit/delegation cases through the native permission API without executing those tools. Its separate shell/Code Mode/MCP observations describe inherited access, not all-write isolation, a live tool probe or the shared service's policy. `git diff --check` checks patch whitespace. None of these calls a model or proves behavior in a shared service. +Use existing project checks according to the actual risk: -## Design retirement source change (2026-09-30) +| Change | Useful evidence | +| --- | --- | +| Simple edit | Relevant behavior and a minimum applicable check | +| Feature | Related tests and the integrated user flow | +| API/data | Contracts, compatibility and regression checks | +| Auth/permissions/payment | Failure, denial and security scenarios | +| UI/interaction | Real renders and relevant viewports/states, functional wiring and persistence where needed | -At that date, the repository shipped Developer, Test Runner, and Reviewer only. `src/native-roles.mjs` lost the `design` primary, `templates/agents/design.md` and `vendor/open-design/` were deleted, `src/native-bundle.mjs` and `bin/ksi-opencode.mjs` lost the design-kit path, and `docs/design.md`, `docs/design-critique.md`, `docs/design-system-template.md`, `examples/design.project.jsonc`, and `examples/design-handoff.md` were removed. [The OpenDesign integration contract](integrations/opendesign.md) replaces them. +Lint/typecheck, unit/integration tests, build, E2E/browser and CI are available means, not a list +to run after every edit. Reuse valid same-state results and rerun affected checks after repairs. +Report commands, tested state, actual results and gaps; do not infer a pass from unavailable evidence. +V2 does not provide LSP tools/diagnostics; use project lint/typecheck/compiler commands +([upstream limitation](https://opencode.ai/v2/docs/migrate-v1#supported-fields-without-direct-native-equivalents)). -Package version moved to `0.5.0-beta.1`. Supplying the retired flag exits non-zero with an explicit message and writes nothing; a legacy installed `agents/design.md` is deliberately left untouched for a separately reviewed migration. Reviewer was reduced to code and behaviour review, and the retired skill IDs are asserted absent in the isolated skill catalog. +## This repository -This is source-level package evidence only. No global OpenCode configuration was changed, no role was removed from any installed environment, no provider call was made, and no Human acceptance of a real product screen is claimed. The deployed OpenDesign daemon itself is verified separately. +- `npm run check`: native Node tests for documentation links, the complete minimal configuration example and the private/no-runtime package boundary. +- `git diff --check`: patch whitespace only. -## Test Runner retirement (2026-10-09) +No installer, provider-backed pilot, old integration fixture or review agent is needed for these checks. +They verify this guidance repository, not product functionality, visual quality, human acceptance or delivery. -The default bundle now ships Developer and Reviewer only. `templates/agents/test-runner.md` and the `--developer-test-runner` installer variant were removed; the installer, doctor helper, isolated verifier and package checks were updated, and the old flag fails with an explicit removal message instead of a silent usage error. Developer now owns the tests for its own implementation and Reviewer reviews code and test adequacy (safe read-only checks only); Build verifies the integrated revision and unverified areas, reusing already-valid checks, and independent execution verification remains available through an explicitly configured verification role or authorized CI. The installer still never deletes a previously installed `test-runner.md`; existing user files and JSONC routing stay untouched. This is source-level evidence; no user configuration was changed by this task, and no push or publication was performed. +## Installed runtime -## Dated history (earlier configurations) - -On 2026-09-28 after the legacy-source cleanup and review corrections, before the later Design permission fix: `npm run check` passed 21/21; `npm run check:package` passed; `git diff --check` passed. The isolated verifier passed on OpenCode 2.0.18: six native role files and the two opt-in skills installed, built-in roles untouched, no KSI commands installed, expected nine effective role modes, retained user Developer model/steps, visible Design Critic, and default Developer nondelegation. The verifier issued zero provider requests; child egress was not monitored. - -The old plugin hooks, automatic checkpoint injection, `ksi_*` tools, command shortcuts, and signature guards are not in this source distribution. Earlier plugin and nine-role installer checks are dated history in the [migration ledger](https://github.com/khakisketch/KSI-Opencode-Harness/blob/main/docs/superpowers/plans/2026-09-26-opencode-v2-plugins.md), not evidence for the current package. - -The isolated result is package evidence only. It does not establish the contents or effective role catalog of any user's global OpenCode configuration. In particular, this package check did not change a global Design Critic file, restart a shared `openchamber.service`, authenticate against it, or test it. An unauthenticated HTTP 401 is not acceptance. At that source-only milestone, live model delegation, source editing through Build, OpenChamber UI behavior, visual quality, and Human end-to-end acceptance remained unverified; the product-state m03 slice stayed in progress. - -## Approved global adoption and shared-service catalog (2026-09-28) - -After separate Human approval, the global installer preview showed only one existing-file change (`agents/design-critic.md`: remove `hidden: true`) and five new files for the two skills. The pre-change Critic and unchanged `opencode.jsonc` were privately backed up; `--apply --replace` also created a sibling Critic backup. A repeated installer preview returned zero changes, and the JSONC SHA256 stayed `d1916c10f2c35cb2e59e986eeac4d8b84f71332a889600a49dc6b7b1c87d986e`. No provider/model or credential file was read or changed. - -The approved `openchamber.service` restart returned active. Direct unauthenticated OpenCode `/api/agent` and `/api/skill` requests returned 401, so they are not counted as catalog evidence. Through a fresh, authenticated OpenChamber browser session, the restarted service returned 200 for both catalogs: Build/Plan/Design were Primary; Explore/Developer/Test Runner/Reviewer/Research/Design Critic were Subagent; Critic was visible. Both selected skills resolved to the new global `skills/` paths. The effective Developer model/variant/steps and default no-child permission remained present; Design had workspace edit permission and Critic denied edit/shell/delegation. This is shared-service metadata verification, not a live model delegation, UI quality comparison, or Human acceptance. The browser session was closed without saving authentication state. - -## Provider-backed copy pilot and live delegation (2026-09-28) - -With subsequent Human approval for model use, the original `design-previews/index.html` (SHA256 `f107b3c1bfd6877bebbc7a53fe580cd3de651899cf2329a25303360efc2d79b6`) was copied into separate disposable Build and Design locations. Both received the same Korean brief and `opencode-go/muse-spark-1.3-contributor#high` in standalone OpenCode V2 sessions. Both loaded `frontend-design` and `web-design-guidelines` and edited only their local `index.html`. The original hash and global JSONC hash remained unchanged. The HTML candidates and six baseline/Build/Design desktop/mobile PNGs are preserved in `/home/ksi/.local/state/ksi-harness-pilots/2026-09-28-build-design/`. - -The first Design run failed before editing: its then-installed `external_directory: ask` agent rule requested access to the globally installed guidelines reference file, and non-interactive `opencode run` auto-rejected it. A TTY did not change that behavior. A new Design run with `--auto` read that exact file and completed; Build needed no `--auto`. This is a real headless skill-use friction and a comparison caveat, not a claim that the shared OpenChamber client fails. A later project-local role overlay without that ask also failed because the existing global ask persisted as the last effective matching rule. No global role permissions were changed for the pilot. - -Playwright loaded all three local HTML files at 1440×900 and 390×844, saved full-page captures, and observed no page errors or horizontal overflow. Build and Design both corrected the Build-first/optional-Plan/optional-Design message. Build's mobile page measured 4454 px high; Design's measured 4852 px. In this one task, Build's shorter three-part explanation better conveyed that delegation is optional; Design retained a five-node arrow sequence and a legacy “Developer·Research 호출은 하지 않음” statement, which should not be promoted as a general role restriction. These observations do not establish overall agent quality or user approval. Neither candidate replaced the original preview. - -Separately, a standalone Build session using `opencode-go/deepseek-v4.1-flash` invoked the configured Developer subagent, which changed an isolated `probe.txt` from `status=pending` to `status=delegated`. A fresh local read confirmed that exact content and that the disposable directory contained only `probe.txt`. This verifies live Developer delegation under the installed global roles in a standalone runtime, not a provider-backed operation against the shared OpenChamber service. Human m03 end-to-end acceptance, shared-service live delegation, and any candidate promotion remain pending. No commit, push, or release was performed. - -After that finding, source `src/native-roles.mjs` removed only Design's redundant catch-all external-directory ask; OpenCode's own managed-global-config allow remains effective in a fresh isolated install, while its base ask still covers other external paths. A new `checkAgents` regression first failed against the old behavior, then passed; `npm run check` passed 22/22, `npm run check:package` and `git diff --check` passed, and the no-auth isolated OpenCode 2.0.18 verifier passed with a new effective-permission check for the installed guidelines reference. Three preceding verifier attempts timed out during isolated service startup while an unrelated background OpenCode service started by this investigation was running; after that background service was stopped, the verifier passed. The exact cause of the startup contention was not independently proven. The managed `openchamber.service` stayed active. The global Design file still had the old ask at this source-only milestone; the subsequently approved global correction is recorded below. - -## Approved global Design permission correction (2026-09-28) - -After separate Human approval, the live global `agents/design.md` and private pre-change backup `/home/ksi/.local/state/ksi-harness-backups/design-skill-permission-20260928.axtX9s/design.md.before` both matched SHA256 `f3301fac571ccce6cec95c923d906064df65eccdf27473fe8f0269858737c378`. The installer preview showed exactly one conflict: delete the 62-byte `{"action":"external_directory","resource":"*","effect":"ask"},` line. `--with-design-kit --apply --replace` made that one-file change, creating sibling backup `agents/design.md.bak-1790583427430-be3c6107`. The resulting Design SHA256 is `46a5178a0839ca17847deded1153222417dfd5fb02d1edba548039b7b3cb0341`; a repeat preview reported zero changes. Global `opencode.jsonc` remained byte-identical at SHA256 `d1916c10f2c35cb2e59e986eeac4d8b84f71332a889600a49dc6b7b1c87d986e`. - -The approved `openchamber.service` restart returned active/running with a new main PID. In a separate standalone, provider-backed OpenCode V2 Design run **without `--auto`**, the agent loaded installed `web-design-guidelines`, read its global `references/guidelines.md`, and changed only a disposable `probe.txt` from `status=pending` to `status=read`; the CLI exited zero and a fresh read confirmed the file. Through a fresh authenticated OpenChamber browser session, the restarted shared service returned HTTP 200 agent/skill catalogs: Design remained Primary; the final effective matching `external_directory` rule for the global config path was `allow`; both design skills resolved to global paths. The browser session was closed without saving authentication state. This distinguishes shared-service catalog evidence from the standalone live skill-use result: no model prompt was sent through the shared OpenChamber service, and Human m03 end-to-end acceptance remains pending. +When checking setup, use `opencode --version`, `opencode debug config` and `opencode debug agents` +in the actual project against the active server. Keep private configuration output local. +Plan `all` enables primary/child use, subject to the parent's native permissions; it does not guarantee routing. +An actual child call establishes execution, and an observed render establishes visual evidence—not catalog entries. +Plan's edit denial is not a shell/MCP sandbox. Do not add permission overrides or mandatory setup pilots. diff --git a/docs/walkthrough.md b/docs/walkthrough.md deleted file mode 100644 index a2ccbe9..0000000 --- a/docs/walkthrough.md +++ /dev/null @@ -1,45 +0,0 @@ -# 개발 흐름 시각 가이드 - -> 범위: 아래 캡처는 **과거의 OpenDesign 실행 예시(역사적 참고용)**입니다. 현재 기본 흐름은 -> Design Specialist가 맡으며, OpenDesign은 별도로 명시적으로 선택한 환경에서만 쓰는 외부 -> 워크스페이스 통합입니다. 캡처 자체가 최신 제품 상태나 디자인·기능 검증 완료를 증명하지 않습니다. - -글로만 설명하면 어려운 부분을 **실제 작업 화면의 과거 캡처**로 보여줍니다. 화면은 흐름을 설명하는 예시이며, 최신 제품 상태나 디자인·기능 검증 완료를 증명하지 않습니다. - -## 한눈에 보는 흐름 (과거 예시) - -![개발 흐름 한눈에 — 과거 OpenDesign 실행 예시](assets/walkthrough/00-flow.png) - -| 주체 | 하는 일 | -| --- | --- | -| **대표님 (사용자)** | 요청 · 제약 · 완료 기준 · 최종 수용 | -| **OpenCode (Build)** | 작업 계약 · 조율 · 기능 검증 · Git · 보고 | -| **OpenDesign (로컬 실행)** | 분석·감사 → 실제 UI 소스 수정 → 렌더 리뷰(필수) → 결함 목록 → 개선 반복 | -| **실제 제품 (소스)** | 빌드 · 라우팅 · 저장 · 권한 검증 — 스크린샷이 아니라 **동작**으로 확인 | - -## 1. OpenDesign 워크스페이스 — 별도 명시적 선택 시 디자인 작업이 일어나는 곳 (과거 예시) - -![OpenDesign 워크스페이스 — 과거 캡처](assets/walkthrough/01-opendesign-workspace.png) - -프로젝트 대화와 파일·문서를 확인하는 워크스페이스 화면입니다. 이 캡처 자체가 디자인 실행 중이거나 UI 렌더 검수를 마쳤다는 뜻은 아닙니다. - -- **분석·감사 → UI 소스 수정 → 렌더 리뷰 → 개선**이 이 워크스페이스에서 일어납니다 -- 리뷰는 필수 단계이고, 결과는 "좋다/나쁘다"가 아니라 **결함 목록**(무엇이/어느 화면·상태/사용자 영향/원하는 결과)으로 받습니다 -- 위임된 재설계는 디자인 측이 방향을 정해 진행하고, **방향 옵션 2~3개**는 사용자가 요청할 때만 만듭니다 - -## 2. 실제 제품 — 검증과 결과 확인 (과거 예시) - -![실제 제품 대시보드 — 과거 캡처](assets/walkthrough/02-product-dashboard.png) - -디자인 변경은 **실제 제품 소스**에 반영되고, OpenCode가 실제 제품에서 기능(라우팅·저장·권한)을 검증합니다. - -위 제품 화면은 샘플 데이터가 있는 UI 예시입니다. 검증 결과는 해당 소스 버전의 실행·리뷰 기록에서 별도로 확인해야 합니다. - -- 보고는 **리뷰 묶음**으로: 제품 링크(로컬이면 tailnet·임시 표기) + 전/후 스크린샷 + 디자인 프리뷰 링크 -- 기능·데이터·권한은 보존하되, 승인된 목적 안에서 **화면 구조·계층·컴포넌트는 재설계**할 수 있습니다 - -## 더 알아보기 - -- [README](../README.md) — 설치와 사용 방법 -- [OpenDesign 통합 계약](integrations/opendesign.md) — 상세 절차 (감사·옵션·리뷰·개선 규칙) -- [설치 가이드](../INSTALL.md) — 충돌·업그레이드·마이그레이션 diff --git a/examples/autonomous-development.md b/examples/autonomous-development.md deleted file mode 100644 index 82611e0..0000000 --- a/examples/autonomous-development.md +++ /dev/null @@ -1,47 +0,0 @@ -## Autonomous local execution - -- Adopting this policy delegates routine local development through implementation, bounded delegation, integration, verification, and scoped local commits. It does not delegate new product scope or external delivery. Explicit user/repository restrictions, native agent modes, tool permissions and execution budgets remain authoritative. -- Build owns execution of the agreed outcome; Plan supports understanding, alternatives, risk analysis and consequential decisions without implementing product files. Clear small tasks can start in Build; a Plan handoff is not mandatory for every edit. Do not replace native agent prompts. -- Continue all remaining authorized tasks without asking "should I continue?" or reopening an approved plan, execution method, commit, push or deployment decision. Report meaningful progress without turning it into an approval request. Resolve reversible implementation details within agreed contracts and record significant assumptions; do not guess missing material product/design decisions. -- Own the completion loop for an authorized outcome: discover relevant defects, prioritize by acceptance/risk, implement or route bounded repairs, review actual results and reverify affected behavior until evidence meets the agreed outcome. Do not stop at a findings/candidate list or make the user triage, select routine fixes, repeat existing requirements or approve each repair; keep the defect→revision→check→fixed/open chain in the existing ledger. Scope unrelated work out; audit-only/Plan-only requests remain read-only for product files, and explicit pauses/native modes/permissions/budgets still apply. -- Self-review is not self-approval: preserve design-owned rendered review/refinement, run the CE review skill (ce-code-review) when a change warrants independent review, and keep the integration checks the change needs — no forced helper per edit and no separate KSI Reviewer seat. Diagnose no-progress/repeated failures and adjust safely within scope rather than blind replay; unresolved quality/access or material scope/design/security/cost/external-effect decisions are reported truthfully with a recommended default while safe independent work continues. Report usable outcomes and real evidence briefly, not a routine improvement menu for the user. This default adds no runtime loop, automatic goal or permission grant. -- Persist substantive requested multi-step plans and consequential scope/design decisions in the relevant existing ledger before delegation, pause or closeout, within native write permissions. Label proposals and the user's actual approved scope separately from progress, verification, human acceptance and external delivery; writing a file or approving a probe grants no further authority. Keep product-state/checkpoint as summaries and links, with sanitized durable trial conclusions/results/source identity/limits in the ledger rather than only temporary logs. Simple questions/trivial changes need no new spec, ledger or approval ceremony. -- Plan and Build use one approved workstream plan, including applicable skill output — a CE plan artifact is that plan when a CE workflow produced it — not competing agent/skill authorities. Capture goal/requirement basis, scope/exclusions, preserved contracts, observable acceptance, dependencies/order, verification and actual approval proportionately. At start/resume Build compares it with current Git/code/check evidence, records progress/blockers and significant reversible adjustments; Plan reassesses/proposes changes, not implementation/acceptance. Material scope/contract/design/security/external changes still need a decision; small clear work need not start in Plan. -- Respect native Plan write limits. If repository persistence is blocked, an asked-for plan may be saved in its permitted native location (V2 normally `~/.opencode/plan/`); hand off the actual path/version or section, repository/ledger target, approval state and open decisions. In authorized Build execution, read the source and existing record before recording it canonically with content/approval preserved; proposals stay proposals, unchanged approvals need not be repeated for transfer. Disclose unavailable inputs/pending persistence; no shell/MCP/helper bypass, blind overwrite/deletion or two evolving plans. Preserve native prompts/permissions; detailed procedure is in the execution guide. -- Bind helper assignments to relevant document paths/sections plus the approved scope, workspace/base, invariants, ownership and observable checks; reuse inputs already read and unchanged. Helpers briefly identify their actual basis and report missing access, conflicts and unverified work with results. Primary reconciles actual diffs/checks and owns common ledger/product-state/checkpoint updates at stable boundaries. Shared files are not automatic shared conversation memory, guaranteed loading or runtime enforcement. -- Make ordinary development comfortable: accept outcome-oriented requests, reuse known requirements and choose routine tools/checks yourself. Do not make the user administer branches, enumerate worktrees, repeat setup, approve every test, or complete a full design/audit ceremony for a small approved-pattern change. Match planning and verification depth to the change; existing unrelated warnings belong in backlog, not an invented new task. -- Treat requests as inputs, not orders: before accepting scope, judge product value, user benefit and the smallest viable path, and propose a better or smaller alternative with evidence when one exists — the product/business decision stays with the user. At slice boundaries, re-check whether the goal is met or the value unclear, and recommend stopping or cutting scope rather than building beyond the goal. Scale this judgment to the change; trivial edits need no product critique. -- Build may use bounded helpers when authorized and useful, without seeking permission for each dispatch. No mandatory team. Preserve user model/variant/budget choices. Build integrates and verifies helper results; helper roles grant no additional authority. -- Actively consider native General for coherent nonvisual implementation/repair with clear scope, contracts and observable completion criteria, not only overflow help. Build may work directly for tightly coupled judgment, clear integration corrections or disproportionate handoff overhead. Choose by quality, rework and end-to-end efficiency; no General-first mandate, usage quota, forced splitting or ceremonial delegation after pre-implementation. Visual work remains design-owned regardless of size. The installer bundle's Developer + Reviewer files are a separate optional bundle; do not restore removed global routing to follow it. -- Give General a compact outcome/contract — actual workspace/base, relevant paths and approved decisions, invariants, owned/excluded work, checks/acceptance criteria — and require changed files, actual self-test results, tested state, assumptions, incomplete/unverified work and contract discrepancies in the return. Build may work directly for tightly coupled judgment or clear integration corrections; no General-first mandate, quotas or forced splitting. General may load allowed CE skills but cannot launch further subagents; CE is not an agent, grants no extra permissions or scope, and requires no full workflow, project ce-setup or duplicate documents. -- Build inspects the actual diff, integrates, and decides from returned evidence rather than re-reading everything — requirement match, actual scope, tested revision (including relevant uncommitted changes), integration consistency and unresolved items — expanding to diffs/logs only when evidence is missing/stale/conflicting, unexpected changes appear, or a high-risk boundary is touched. Helper reports/author tests never replace independent review or the integration checks; diagnose wrong/partial results by cause (context/access/permission/provider/step-limit) instead of blind replay. Shared files are handed off sequentially; unresolved blockers do not prevent safe independent authorized work. -- Commit only task-owned changes at coherent, verified milestones in the authorized repository/branch. Inspect the index and diff; exclude unrelated user changes, secrets and checkpoints. Do not claim failing or unrun checks passed. Do not rewrite history, clean up user work or force-push as routine completion. An explicit no-commit instruction overrides this default. -- Ask for a material scope/product/data/architecture/design decision, a substantial cost or risk increase, an unapproved external effect, a destructive/security-sensitive action, or a genuine blocker that cannot be resolved within the task. Recoverable test failures and finishing an intermediate task are not reasons to stop. If only part is blocked, continue independent authorized work where safe. When a decision batch is unavoidable, present each item once with a recommended default so a single approval can settle the batch. -- Push, publish and deploy only with authorization covering the target and relevant effects (including automatic deployment from a push). Invoking a shipping workflow (lfg, ce-commit-push-pr) is that authorization for its documented repository effects (task-branch push, PR); deployment, registry publication and new credential access stay separate, and merge stays the user's decision unless granted. Once granted, do not ask again unless the target, effects or risk materially change. Required tool, identity or browser approvals still apply; do not bypass them. -- Return a final report when the agreed technical outcome is achieved, or a truthful partial report when blocked or budget-limited. Write reports in the user's terms, not as a workstream log: (1) the usable level now — what the user can actually do with it; (2) what is blocked and who can unblock it (agent work / required external input / material decision); (3) what you will do next. Separate technical completion, user product acceptance and external delivery. Pending human acceptance alone must not stall remaining authorized engineering work. End with the next action you are taking, not a naked candidate list or a routine "shall I continue?" question: within an explicitly authorized development mandate, continue through goal-serving work without re-asking; new milestones, new scope, external delivery and genuinely material/irreversible decisions still return to the user, with a recommended default stated whenever one exists (proceed with everything not blocked). -- Recover from unexpected stops (AI/provider error, drain failure, crash): preserve uncommitted work first (commit/stash; note active worktrees), then retry the session — a notification recorded as delivered may be unprocessed, so re-check pending runs and the checkpoint. If the same AI/provider error repeats, compact or continue in a fresh session from the checkpoint and check the model/provider; recurring `Model is unavailable` / `reasoning item` / `invalid parameters` errors are environment/provider issues to report, not task failure. -- All visual frontend work belongs by default to the Design Specialist — UI/UX analysis, actual UI source/styles, responsive/interaction presentation, rendered design review and refinement, including small fixes. Where OpenDesign's local execution was separately and explicitly selected, it takes that seat instead; it is never the sole, default, or mandatory path. OpenCode owns purpose/scope, verified inputs, coordination, business/API/data/auth contracts, functional integration/verification and Git; no substitute visual fixes in Build. Procedure and evidence contracts for the explicitly selected case live in the [integration guide](../docs/integrations/opendesign.md). -- Commission with observable acceptance criteria and verified input access — an unreadable reference/asset/source blocks the run. Reuse the representative project and resident product-context.md; small visual repairs use compact refinement, not fresh intake or a full audit; focused Mobbin research runs before new/material design when useful and is skipped for approved-pattern work; UI-reference skills are optional inputs, not alternative design paths. -- Where OpenDesign was explicitly selected, the design workspace owns its internal analyze → implement → rendered review → refine loop, with a review step distinct from the generator for substantive work. OpenCode validates the returned revision and rendered evidence (a concrete defect list tracked fixed/open) instead of repeating the visual review; for accumulated multi-screen drift prefer the guide's visual-system pass over per-screen patches. -- Gates: a new visual direction, a client-facing deliverable, an out-of-scope change or an explicit review-first request get approval first; a delegated redesign resolves its direction from the design system and brief and proceeds (2-3 direction options only when the user asks or the brand identity itself is in question). OpenDesign Cloud is explicit-request only. -- Keep solution/preview links labeled separately from the real product; screenshots inform design review but do not prove authentication/API/data. Keep generated, design-reviewed (criterion/defect → revision + viewport/state render → fixed/open), functionally verified and human-accepted distinct; refine until criteria are met (no fixed round cap) — no measurable improvement means quality unmet with open defects, not "only user acceptance remains"; access failures block rather than authorize a Build-designed fallback. -- OpenCode coordinates; where explicitly selected, the design workspace supplies artifacts and findings — no automatic outer-session launches, recursive generation, product API/data/Git changes or two writers in product files. Capability checks, run interpretation, source-direct access and Plan's design-MCP guard follow the guide; new mounts/security changes, container recreation, credentials and unrelated-project access keep their own scoped authorization. -- Local autonomy is not perpetual background execution. The current KSI host has no Goal plugin or goal auto-continuation; preserved `active` goal records are inert history, not live work or a pause/cleanup requirement. Resume long-running work from its existing approved record and actual Git state. Reinstallation or another continuation mechanism needs a separate explicit decision; do not restore one for ordinary development. -- An explicit task/session pause stops further task work and uses native tools to pause this session's notifier wake permission; it does not implicitly cancel an external design run. Resume only on request, from the authorized task record and actual state; Plan cannot resume implementation. Report unavailable/partial switches and external work still running rather than claiming everything stopped. Do not edit preserved Goal state or act on another session. - -## Agent-managed workspaces - -- Default development uses a feature branch — create it from the default branch when needed and reuse the workstream's existing feature branch when one exists. A worktree is for actual isolation only: concurrent writers or sessions, parallel development, or protecting the user's checkout. A CE workflow applies its own branch/workspace handling, including its internal worker isolation, when it runs. Adoption delegates routine local integration into the recorded target and safe task-owned cleanup; ask only for an unresolved material target, unsafe conflict, external delivery, or other existing approval boundary. An explicit no-merge/no-cleanup instruction overrides this delegation. -- Concurrent writers get disjoint ownership and separate workspaces; one Primary integrates and verifies. Before local integration, recheck the target HEAD/status and the task diff; prefer fast-forward and preserve unrelated changes; rerun relevant checks on the integrated result. Do not force-push, rewrite history, reset or silently replace the target. Ask for genuinely unsafe or ambiguous conflicts, not routine Git steps. -- Reuse a suitable task-owned workspace when needed; do not commandeer an unfamiliar branch/worktree or move dirty user work. Clean up only workspaces proven created/owned by this task, after their required commits are integrated and unique notes are preserved; non-forced removal only, no sweeping or broad prune. Preserve dirty, active, externally managed or unfamiliar workspaces/branches and report retained exceptions briefly. - -## Change-focused browser verification - -- Tools alone are not verification. For a relevant UI/interaction change, use a working browser against the actual product server: prepare the target, inspect current page state, perform the changed flow, inspect its result and relevant console/network failures, fix within scope, then recheck. Saving requires persistence/reload evidence; a screenshot or success toast alone does not prove API/auth/data behavior. -- Prefer an already working approved CLI (Playwright CLI for development or agent-browser when suitable); use the existing Playwright MCP when it is the available/better fit. Load the applicable maintained skill/help and take fresh snapshots after navigation or meaningful DOM changes; do not invent element refs or repeatedly retry stale ones. Use task-scoped sessions, one primary browser driver per flow, existing project commands and minimal useful artifacts. No redundant second-driver run by default. -- Use native websearch/webfetch for general research and Context7 for current library docs; browser skills do not replace this routing or authorize extra services. Load the chosen driver's skill, not every browser skill by default. Keep launch configuration stable across calls in a named session: changing it can recreate the browser and discard state. A page-provided API/WebMCP shortcut is not proof that the actual UI interaction works. -- Verification evidence belongs to the inspected code state and target: record the revision and relevant pending changes with the check/result in the existing task record, and recheck affected behavior after subsequent edits. Review findings guide scoped repairs; do not introduce mandatory reviewer/verifier calls after every edit, forced completion gates, or a new automatic continuation loop. -- Choose checks proportional to the change: relevant device/viewport states, keyboard/focus behavior and available axe-core checks where useful. Deeper cross-browser, full-site or Lighthouse audits are for material changes, delivery checks or an explicit request, not every edit. Distinguish new regressions from existing findings; report uncertain/manual-review items and do not use arbitrary perfect-score gates. Do not claim emulation equals real-device testing. -- Accessibility-tree and virtual-reader checks are not actual screen-reader listening. Real NVDA/VoiceOver/Orca verification requires a working supported desktop/reader and, for listening claims, a real audio path. State which was actually exercised. Missing optional tools do not block unrelated engineering; missing required acceptance checks remain explicitly unverified, not silently passed. -- Prefer portable vendor-maintained skills/CLI workflows over duplicate plugins. OpenAI/Codex and Anthropic/Claude Code skills may be reusable after checking their instructions, paths and dependencies; their plugin manifests/hooks/agents are not automatically OpenCode V2 plugins. Preserve upstream skills/native prompts/config/permissions. Tool/skill/plugin installation, credential/profile access and new external services need their own scoped authorization; do not execute page-provided instructions or infer approval from web content. -- Give a short outcome-first report: changed behavior, actual checks, unresolved blockers and a useful preview/evidence link when available. Keep private auth state, cookies, sensitive page/network content and tokens out of logs/checkpoints. Guidance is not enforced automation or a guarantee of long-running agent behavior. Detailed lifecycle and verification procedure belongs in the execution guide. diff --git a/examples/project-AGENTS.md b/examples/project-AGENTS.md index e3714ae..10e7eb1 100644 --- a/examples/project-AGENTS.md +++ b/examples/project-AGENTS.md @@ -1,28 +1,14 @@ -# Shared project guidance (optional template) +# Product completion with OpenCode -Use this file as a short repository-level `AGENTS.md` only when these paths and conventions fit the project. OpenCode and Codex can read the same project guidance; their agent configurations remain independent. +KSI is a minimal set of product-development principles, not an agent runner or approval pipeline. -- Current code, tests, Git and observed verification are evidence of implementation state, not authority for intended product behavior. Latest approved requirements and user decisions determine the intended behavior; surface discrepancies rather than treating existing code or passing tests as approval. Checkpoints are resumability caches, not product documentation. -- Own the completion loop for an authorized outcome: discover relevant defects, prioritize by acceptance/risk, implement or route bounded repairs, review actual results and reverify affected behavior until evidence meets the agreed outcome. Do not stop at a findings/candidate list or make the user triage, select routine fixes, repeat existing requirements or approve each repair; keep the defect→revision→check→fixed/open chain in the existing ledger. Scope unrelated work out; audit-only/Plan-only requests remain read-only for product files, and explicit pauses/native modes/permissions/budgets still apply. -- Self-review is not self-approval: preserve design-owned rendered review/refinement, run the CE review skill (ce-code-review) when a change warrants independent review, and keep the integration checks the change needs — no forced helper per edit and no separate KSI Reviewer seat. Diagnose no-progress/repeated failures and adjust safely within scope rather than blind replay; unresolved quality/access or material scope/design/security/cost/external-effect decisions are reported truthfully with a recommended default while safe independent work continues. Report usable outcomes and real evidence briefly, not a routine improvement menu for the user. This default adds no runtime loop, automatic goal or permission grant. -- If present, `docs/superpowers/product-state.md` records the current product milestone and user-visible acceptance. Use the workstream's own record — a CE plan/run/review artifact, or `docs/superpowers/plans/` for native work — and `.opencode/working-state.md` for a compact resume handoff. -- Persist substantive requested multi-step plans and consequential scope/design decisions before handoff/pause/closeout, within native write permissions; label proposals and record what the user's approval actually covers. Saving a plan or approving a probe is not implementation/delivery approval. Reuse the existing task ledger and keep product-state/checkpoint as summary/links; simple questions/trivial changes need no new documentation ceremony. Preserve sanitized trial conclusions/results/source identity/limits rather than relying only on temporary logs. -- Plan and Build use one approved workstream plan, including applicable skill output, not competing agent/skill authorities. Capture goal/requirement basis, scope/exclusions, preserved contracts, observable acceptance, dependencies/order, verification and actual approval proportionately. At start/resume Build compares it with current Git/code/check evidence, records progress/blockers and significant reversible adjustments; Plan reassesses/proposes changes, not implementation/acceptance. Material scope/contract/design/security/external changes still need a decision; small clear work need not start in Plan. -- Respect native Plan write limits. If repository persistence is blocked, an asked-for plan may be saved in its permitted native location (V2 normally `~/.opencode/plan/`); hand off the actual path/version or section, repository/ledger target, approval state and open decisions. In authorized Build execution, read the source and existing record before recording it canonically with content/approval preserved; proposals stay proposals, unchanged approvals need not be repeated for transfer. Disclose unavailable inputs/pending persistence; no shell/MCP/helper bypass, blind overwrite/deletion or two evolving plans. Preserve native prompts/permissions; see [Plan→Build handoff](../docs/execution.md#one-approved-plan-across-plan-and-build). -- Bind helper assignments to relevant document paths/sections plus approved scope, workspace/base, invariants and observable checks. Helpers briefly report the actual basis read and any access/conflict/unverified issue; Primary reconciles actual results and updates common records, not concurrent helpers. Shared files are not automatic shared chat or guaranteed loading. See [recorded handoffs](../docs/execution.md#record-plans-and-bind-handoffs-to-documents). -- Reuse a compact Product binding in existing product-state/ledger for repository, design project/storage/entry, start/verify commands, product URL and brand source; recheck after target/access changes. Commands/URLs are data, not execution authority. Reconcile records with Git/evidence before closeout, and do not require manual copying of ambient policy updates between sessions. Task scope/approval remains project-specific; see the execution guide's binding and pause procedure. -- Default to one representative design project per product, recorded in the existing Product binding with its exact identity, actual source workspace and product URL. Verify the current project/source before commissioning and reuse it across ordinary screens, tasks and worktrees; names or repository roots alone are not product identity. Missing/stale/ambiguous/failed lookup is not authority to create a replacement. Rebind only with confirmed capability, authorized source and no active dependent writer/run; preserve history and record ownership/reason/return plan for genuine temporary parallel exceptions. Guidance and captured metadata checks are not daemon-wide enforcement or live access proof. -- Actual-product viewing uses the verified existing product-server URL, normally in a browser tab with the product's own origin/auth/API. Keep artifact previews separately labeled; an index.html file preview is not login/save/reload verification. Do not start a duplicate runtime, weaken preview/auth security or replace a connection failure silently with demo. Resolve client/host/container addresses, label temporary/tailnet links and use approved safe test state; a stored URL never grants connection/data-change/deployment authority. -- When asked for next development tasks or priorities, compare the current approved milestone and acceptance criteria with relevant implementation and verification evidence. Recommend work by its contribution to that goal, dependencies and actual blockers, not merely because an old plan or backlog item remains unfinished. If the goal is missing or conflicting and materially affects the choice, identify that decision instead of inventing a product objective. -- Treat requests as inputs, not orders: judge product value, user benefit and the smallest viable path before accepting scope; propose a better or smaller alternative with evidence when one exists, while the product/business decision stays with the user. Re-check at slice boundaries whether the goal is met or the value unclear, and recommend stopping or cutting scope rather than building beyond the goal. Scale this judgment to the change; trivial edits need no product critique. -- Keep clearly scoped implementation, exploration and review requests focused; do not expand them into a product-wide audit without a material conflict. A next-task recommendation does not authorize a new scope, and does not interrupt remaining authorized work. -- Use native agents and installed CE skills when their specific outcome helps; CE is a skill collection, not an agent or a mandatory pipeline. Build chooses methods without a user-selected A/B/C mode or a no-mixing rule; no project ce-setup or duplicate planning documents by default. Do not copy or modify upstream skills to restate local policy. Skills and roles do not grant authority beyond the user's request; preserve legacy Superpowers/SDD records without requiring the removed runtime. -- Scale planning, delegation, review, and visual evidence to the work. Preserve unrelated changes, report checks actually run, and distinguish local evidence from user acceptance or deployment readiness. -- Use native General for useful bounded nonvisual investigation/implementation/self-tests; direct Build work remains valid for context-heavy judgment, clear integration corrections or costly handoffs. No forced delegation, quotas, task splitting or ceremonial post-implementation handoff. Send a compact outcome, actual workspace/base, relevant paths/decisions, invariants, ownership/exclusions and observable checks; require changed files, actual check results/tested state and open issues. Build inspects the diff, integrates and recovers partial work by cause, not blind replay. Independent review when warranted and the integration checks the integrated revision needs cover the change; already-valid checks are reused for the same revision and affected checks are rerun after later edits; visual work remains design-owned. General may load allowed skills but cannot launch further subagents; Build owns needed dispatch. Do not restore removed Developer/Test Runner/Reviewer routing merely to follow an installer example. See [effective delegation](../docs/execution.md#use-general-effectively-without-mandatory-delegation); linked guidance is not automatically loaded or permission enforcement. -- Decide from returned evidence rather than re-reading everything: confirm requirement match, actual change scope, tested revision/workspace (including relevant pending changes), integration consistency and unresolved items; expand to specific diffs/logs only when evidence is missing/conflicting, unexpected changes appear or a high-risk boundary is touched. Route findings to the owning seat and recheck affected behavior after repair; keep returns compressed with details in the ledger/evidence files. -- Adopt the [autonomous local execution policy](autonomous-development.md) by merging its section into this guidance (do not assume a linked file is automatically loaded). That policy delegates approved implementation, bounded helpers, integration, verification and task-owned local commits without routine progress/commit confirmation. Explicit user/repository restrictions still win. -- Pushes, releases and deployments need explicit authorization for their targets and effects, including push-triggered deployment. Reuse valid prior authorization instead of asking again. Material product/design decisions, destructive/security-sensitive actions and genuine blockers still return to the user. -- Visual frontend work belongs by default to the Design Specialist — analysis, actual UI source/styles, responsive/interaction presentation, rendered review and refinement, including small fixes. Where OpenDesign's local execution was separately and explicitly selected, it takes that seat instead; it is never the sole, default, or mandatory path. OpenCode owns contracts, verified inputs, nonvisual logic/API/data/auth, functional verification and Git; no substitute visual fixes, and UI references are optional inputs, not a competing path. Follow the [integration guide](../docs/integrations/opendesign.md) for the workflow, handoff and evidence. -- Commission with observable acceptance criteria and verified input access; separate behavior/brand invariants from redesign freedom; prefer verified direct source editing (one writer per file) with a small handoff snapshot in the task record. Small visual fixes use compact refinement, not fresh intake or a full audit; Mobbin research is conditional and skipped for approved-pattern work. -- Substantive changes get a design-owned rendered review distinct from the generator, consuming the actual rendered states; track a concrete defect list fixed/open and keep generated, design-reviewed, functionally verified and human-accepted separate — refine until the bar is met (no fixed round cap); no improvement means quality unmet. For accumulated multi-screen drift, prefer the guide's visual-system pass over per-screen patches. -- Gates stay: a new visual direction, client-facing deliverable, out-of-scope change or explicit review-first request; OpenDesign Cloud is explicit-request only; design preview and real product-server verification stay separate; home/credentials/unrelated projects and external-delivery/security/API/data/auth/dependency boundaries stay intact. +1. **Goal.** Follow approved requirements and observable acceptance criteria. Clarify material ambiguity; do not invent scope. Code and tests show implementation state, not approval. +2. **Implementation.** Build owns implementation and integration. Preserve user work and existing API/data/auth/permission/business contracts. Use the existing Design Specialist when material visual judgment is needed; mechanical edits need no handoff. +3. **Evidence.** Use existing project checks and real user flows in proportion to risk. UI changes need actual renders; saving needs persistence/reload evidence. Reuse valid same-state results and recheck affected behavior after edits. Agent review is not execution evidence. +4. **Repair.** Fix relevant defects within approved scope without routine re-approval. Ask only for material scope/design/security/data/cost decisions or genuine blockers. Failed or missing required checks cannot be called complete. +5. **Finish.** Stop when acceptance, integration and required verification are satisfied. Report usable results, checks and gaps; distinguish technical completion, user acceptance and external delivery. Do not create more work to keep agents running. + +Use native Plan when architectural uncertainty, significant regression risk or unresolved verification gaps warrant separate analysis. Give it the approved scope, acceptance criteria and current evidence; it returns concrete findings without product edits or new goals. +Build evaluates findings within scope. Do not require Plan approval or repeat analysis without new evidence. + +Follow existing OpenCode permissions and the user's requested scope. Protect secrets and unrelated work. diff --git a/integrations/opendesign/README.md b/integrations/opendesign/README.md deleted file mode 100644 index 4abcd3f..0000000 --- a/integrations/opendesign/README.md +++ /dev/null @@ -1,246 +0,0 @@ -# OpenDesign integration (workstation-specific deployment record) - -Reproduction assets for the OpenDesign workspace that this harness uses for design work. These files live in -the source repository so the deployment can be rebuilt after a machine change; they are **not** part of the -published npm package (`package.json#files` deliberately excludes `integrations/`). - -OpenDesign is a separate application. It is not an agent role in this harness, and this repository does not -vendor, patch, or redistribute any OpenDesign code. Everything here is either a deployment patch for the -user's own checkout or a file that runs beside it. - -**Optional and manual; not installed by KSI.** This is a record of one Ubuntu arm64 host, not a turnkey -cross-platform setup. Start with upstream documentation. Do not apply these mounts, UID settings, CLI -shim, or database sync to another host without inspecting them and obtaining approval. - -### Security and portability boundaries - -- Docker access and host networking are powerful privileges, not a sandbox. -- The full host `~/.codex` and `~/.gemini` stores are mounted **read-write**. Container agents can alter - host sessions/settings/credentials. Prefer dedicated signed-in stores if isolation is required. -- The compose patch retains a **legacy** `auth.json` mount. It does not provide OpenCode V2 credentials; - V2 uses the separate container database. Do not assume that mount authenticates anything. -- UID/GID and tmpfs ownership must agree. The patch's tmpfs is fixed at `1000:1000`, despite the service's - UID variables. Persistent volumes also need matching ownership; the patch does not migrate ownership. -- Credential sync directly edits an internal SQLite schema, stops container OpenCode, replaces all API-key - rows, and writes secret-bearing SQL temporarily on the host (private mktemp file, removed on exit). - SQL is streamed to the container without creating a container SQL file. It is an experimental maintenance - aid, **not a supported sign-in API**. Back up state and never run during a design job. This safer repository - copy is not automatically installed over an older local script; review and replace your copy separately. -- The shim drops `--pure`; it does **not** recreate the original isolation semantics. Normal design - generation was tested, not sandbox isolation or every CLI adapter feature. - -## Architecture - -The diagram below records the **original deployment**, not the current operating-role hierarchy. Current guidance uses OpenDesign's local execution for material design and OpenCode Build/Plan for the outer product-development conversation; see [the current integration contract](../../docs/integrations/opendesign.md). A coding runtime launched inside the design application is an implementation detail, not authority to call or control the outer engineering session. Host MCP/skills/auth are not assumed to be inherited by that inner runtime. - -```text -people ──────────────► OpenDesign web UI (local + tailnet) - │ - ├─ daemon spawns OpenCode as the design agent ─┐ - │ │ - └─ MCP (stdio) ──► OpenCode session ◄──────────┘ - │ - reads/writes the live design files -``` - -- OpenDesign owns design direction, layout, artifacts, and critique. -- OpenCode is spawned by the OpenDesign daemon as the agent that generates designs. -- OpenCode can also call back into OpenDesign through the `opendesign` MCP server registered in its own config. -- The container's OpenCode is a **separate, minimal installation** from the host engineering harness. - -## Verified environment - -| Component | Version | -| --- | --- | -| Host | Ubuntu 24.04, aarch64 | -| Docker / Compose | 29.2.1 / v5.0.2 | -| OpenDesign image | `ghcr.io/nexu-io/od:0.24.1` | -| OpenDesign checkout | `nexu-io/open-design` at `5b19dfa` | -| OpenCode (shared by both sides) | v2.0.18 | -| Tailscale | 1.102.3 | - -## Reproduction - -### 1. Install OpenDesign - -The following commands describe the original installation. Current upstream `main` may differ from the -tested checkout. The patch is tied to commit `5b19dfa`; pin a reviewed compatible checkout before applying it. -Define `KSI_ASSETS` as the absolute path to this repository's `integrations/opendesign` directory for later steps. - -```sh -export KSI_ASSETS=/absolute/path/to/KSI-Opencode-Harness/integrations/opendesign -``` - -```sh -mkdir -p ~/opendesign && cd ~/opendesign -git clone --depth 1 https://github.com/nexu-io/open-design.git -cd open-design -bash deploy/scripts/install.sh --non-interactive --port 7456 --image ghcr.io/nexu-io/od:0.24.1 -``` - -The installer writes `deploy/.env` (including a generated `OD_API_TOKEN`), pulls the image, starts the -container, and creates a `systemd --user` unit. - -### 1b. Raise the memory limit - -The installer defaults to a 384 MB container cap with a 192 MB Node heap. A real design run exceeds it: -the container was OOM-killed mid-run and the run failed with -`Transport: The socket connection was closed unexpectedly`. Raise both in `deploy/.env` before running designs: - -```sh -sed -i 's|^OPEN_DESIGN_MEM_LIMIT=.*|OPEN_DESIGN_MEM_LIMIT=2g|; s|^NODE_OPTIONS=.*|NODE_OPTIONS=--max-old-space-size=1024|' deploy/.env -``` - -### 2. Apply the Linux override patch - -Upstream's `deploy/docker-compose.linux.yml` is the documented place for Linux CLI mounts, and both -`install.sh` and `update.sh` auto-detect that exact file — a differently named compose file would be silently -dropped on update. - -```sh -cd ~/opendesign/open-design -git apply /path/to/integrations/opendesign/docker-compose.linux.patch -``` - -The patch adds, on top of upstream: - -| Change | Why | -| --- | --- | -| `user: "1000:1000"` | Host files are owned by uid 1000 and mode 600; the image default (uid 1001) cannot read them | -| `HOME: /home/open-design` | uid 1000 is named `node` in the image, so `HOME` would resolve to the non-existent `/home/node` | -| `CODEX_HOME` | Codex resolves its home from this variable, not from the mount target | -| aarch64 glibc mounts | Upstream only mounts the amd64 library paths; this host is arm64 | -| `~/.codex`, `~/.gemini` mounts | Codex and Antigravity need their own state and sign-in | -| container-owned OpenCode config | Keeps the host's engineering config out of the container | -| OpenCode state volume, port pin | Isolates sessions and avoids the host service port | - -### 3. Install the OpenCode shim - -```sh -cp "$KSI_ASSETS/opencode-cli" ~/.opencode/bin/opencode-cli -chmod +x ~/.opencode/bin/opencode-cli -``` - -The shim translates the argv shape OpenDesign's adapter expects into what OpenCode 2.x accepts: - -| Adapter sends | Shim does | Why | -| --- | --- | --- | -| `run --dir ` | moves the path to a trailing positional argument | 2.x removed `--dir` | -| `run --pure` | drops it | 2.x has no `--pure`; it is only used for the connection smoke test | -| `models --verbose` | drops `--verbose` | 2.x has no `--verbose`, and without this the daemon falls back to a hardcoded model list | - -### 4. Container OpenCode configuration - -```sh -mkdir -p ~/opendesign/opencode-config-container -cp "$KSI_ASSETS/opencode-config-container.jsonc" ~/opendesign/opencode-config-container/opencode.jsonc -printf '{\n "port": 49380\n}\n' > ~/opendesign/opencode-config-container/service.json -``` - -This config intentionally has no roles, skills, or MCP servers: those belong to the host engineering harness -and cannot run inside the container. - -### 5. Share credentials - -OpenCode 2.x stores credentials in its SQLite `credential` table, **not** in `auth.json`. The container has its -own database, so it does not inherit the host's providers. - -```sh -cp "$KSI_ASSETS/sync-opencode-credentials.sh" ~/opendesign/ -bash ~/opendesign/sync-opencode-credentials.sh -``` - -Only `API key` rows are copied. OAuth rows are excluded on purpose: a rotating refresh token shared between -two stores would invalidate one of them. **Re-run this whenever host credentials change.** - -### 6. Register the MCP server in OpenCode - -After reviewing the actual server's configuration, merge this **V2** example without replacing unrelated -settings. This Docker command is specific to the recorded image/container, not a universal upstream command. -The tested host retains a compatible older-shaped block; that is not the recommended new configuration. The current deployment uses the host-side HTTP proxy instead ([host-side MCP continuity](../../docs/integrations/opendesign.md#host-side-mcp-continuity)); the Docker block below is kept as the original-deployment record. - -```jsonc -{ - "mcp": { - "servers": { - "opendesign": { - "type": "local", - "command": [ - "docker", "exec", "-i", - "-e", "OD_DAEMON_URL=http://127.0.0.1:7456", - "-e", "OD_MCP_STDIO_IDLE_EXIT_MS=0", - "open-design", - "sh", "-c", "cd /app && node apps/daemon/dist/cli.js mcp" - ], - "disabled": false, - "timeout": { "startup": 60000, "catalog": 60000 } - } - } - } -} -``` - -The tested upstream MCP wrapper defaults to exiting after **30 minutes without a request** -(`DEFAULT_MCP_STDIO_IDLE_EXIT_MS`). Setting `OD_MCP_STDIO_IDLE_EXIT_MS=0` disables that timer for this -stdio process. It does not keep a killed/restarted container alive or guarantee automatic reconnection. -Verified with real wrapper processes: a 200 ms idle policy closed the transport; zero stayed connected -and returned 22 tools after the same idle interval. The live host config was backed up, changed with -explicit approval, and project-scoped reconnected; process environment and a live project read were checked. -A full 30-minute no-request soak has not been performed. - -### 7. Remote access - -```sh -tailscale serve --bg --https=7456 http://127.0.0.1:7456 -``` - -Also set `OPEN_DESIGN_ALLOWED_ORIGINS=https://..ts.net:7456` in `deploy/.env` so the web UI's -API calls pass the origin check. - -### 8. KSI design guidance - -```sh -docker cp "$KSI_ASSETS/ksi-design-system/." open-design:/app/.od/design-systems/ksi/ -``` - -It appears in the design-system picker as `user:ksi` and is readable by agents at -`od://design-systems/user%3Aksi/DESIGN.md`. The package is prose-only: a full manifest requires a compiled -`tokens.css`, and inventing a KSI token set is worse than shipping none. Add real tokens later if they exist. - -`metadata.json` must be present with `"status": "published"`. A user design system defaults to `draft`, and -projects are rejected with `DESIGN_SYSTEM_NOT_PUBLISHED` until it is published. - -## Operations - -```sh -systemctl --user status open-design # service state -docker compose -f docker-compose.yml -f docker-compose.linux.yml logs -f # from deploy/ -bash ~/opendesign/sync-opencode-credentials.sh # after host credential changes -docker exec -u 1000 -e HOME=/home/open-design open-design sh -c 'opencode auth list' -``` - -Login to the web UI uses username `open-design` and the `OD_API_TOKEN` from `deploy/.env`. - -After editing `.env` or compose mounts, recreate the container deliberately from `deploy/` with both -compose files. Review the generated user-service unit: on this host an invalid user-unit -`Requires=docker.service` needed removal because Docker is a system service. Verify startup, persistent-volume -ownership and agent sign-in, then run `opencode mcp list` **from the consuming project** and exercise a read tool. - -## Known limitations - -- OpenCode 2.x has no `export --sanitize --pure` or `run --variant`, so those adapter features stay - unavailable. -- Restarting the OpenDesign container drops the `opendesign` MCP connection held by any live OpenCode session; - use `/mcps` in the consuming project to disconnect/reconnect, then verify a real read. A new session alone - was not established as sufficient. Explicit project-scoped reconnect restored 22 tools in the live audit. -- Independently of container restarts, the upstream wrapper's default 30-minute idle exit also closes - the connection. The example above disables it; keep explicit reconnect available for other failures. -- Artifact reads need explicit project and entry-file arguments in the tested deployment; default entry - lookup failed. Existing successful runs and reads were checked; restart recovery is not automatic. -- Codex connection tests intermittently failed with a signal/timeout and retries passed; the cause is - unresolved. Load is a hypothesis, not an established cause. This is not a release blocker per the user. -- Claude Code through this deployment fails on an Anthropic account restriction - (`organization has disabled Claude subscription access for Claude Code`), not on configuration. -- OpenDesign Cloud and its `vela` CLI are explicitly out of scope: Local AI only. -- Upgrading OpenDesign (`deploy/scripts/update.sh`) may overwrite `docker-compose.linux.yml`; re-apply the - patch afterwards. diff --git a/integrations/opendesign/docker-compose.linux.patch b/integrations/opendesign/docker-compose.linux.patch deleted file mode 100644 index 041ac1b..0000000 --- a/integrations/opendesign/docker-compose.linux.patch +++ /dev/null @@ -1,99 +0,0 @@ -diff --git a/deploy/docker-compose.linux.yml b/deploy/docker-compose.linux.yml -index a32ab71..d4ee855 100644 ---- a/deploy/docker-compose.linux.yml -+++ b/deploy/docker-compose.linux.yml -@@ -18,12 +18,26 @@ name: open-design - - services: - open-design: -+ # Run as the host user. The image default (uid 1001) cannot read host files -+ # that are mode 600 and owned by the host user (OpenCode's auth.json, -+ # opencode.jsonc), which silently hid every authenticated provider. Running -+ # as 1000 makes host bind mounts behave exactly as they do on the host. -+ # Requires the OpenDesign volumes to be owned by the same uid/gid. -+ user: "${OPEN_DESIGN_UID:-1000}:${OPEN_DESIGN_GID:-1000}" - # Cap CPU so a busy agent run does not starve the host. - cpus: '1.5' - # Drop the bridge port mapping — host networking makes it irrelevant. - ports: !reset [] - network_mode: host - environment: -+ # uid 1000 is named "node" in the image, so the image would resolve HOME -+ # to /home/node, which does not exist on the read-only rootfs. Every -+ # daemon-spawned agent CLI failed with EROFS: mkdir '/home/node/.config' -+ # until this was pinned to the tmpfs home. -+ HOME: /home/open-design -+ # Codex resolves its home from CODEX_HOME, not from the mounted path, so -+ # the mounted host store must be named explicitly. -+ CODEX_HOME: ${HOME}/.codex - # Re-declare both port vars so the daemon listener follows OPEN_DESIGN_PORT - # even without the bridge mapping (which normally enforced the remap). - OD_PORT: ${OPEN_DESIGN_PORT:-7456} -@@ -35,6 +49,11 @@ services: - # Prepend host-bin mounts so the daemon finds agent CLIs before - # any container-local stubs. - PATH: /mnt/host-local-bin:/mnt/host-opencode:/usr/local/bin:/usr/bin:/bin -+ # OpenCode state lives in the container's own writable volume; only the -+ # host auth store is mounted read-only. The host data directory is never -+ # written from the container. -+ XDG_DATA_HOME: /home/open-design/.local/share -+ XDG_STATE_HOME: /home/open-design/.local/state - # API keys for agent CLIs. Leave blank to omit. - DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-} - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} -@@ -47,13 +66,36 @@ services: - - ${HOME}/.local/share/claude:${HOME}/.local/share/claude:ro - # Claude Code credentials — read-only; never written inside the container. - - ${HOME}/.claude:/home/open-design/.claude:ro -+ # Codex standalone install — resolves the ~/.local/bin/codex symlink at its -+ # absolute host path. Read-write: Codex keeps its session, history, log, and -+ # credential state under ~/.codex and fails when that home is read-only. -+ # The container runs as the host uid, so files it writes stay host-owned. -+ # CODEX_HOME in the environment points here as well. -+ - ${HOME}/.codex:${HOME}/.codex -+ # Antigravity CLI credentials and settings. It stores an OAuth token under -+ # ~/.gemini/antigravity-cli and refuses to start without an authenticated -+ # session, so the host's signed-in state is shared read-write. -+ - ${HOME}/.gemini:/home/open-design/.gemini - # Full glibc for CLIs that exceed what libc6-compat (gcompat) covers. -- # amd64 only — on arm64 replace with /lib/aarch64-linux-gnu:/lib/aarch64-linux-gnu:ro -- - /lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu:ro -- - /lib64:/lib64:ro -+ # KSI arm64 (aarch64) host: the stock amd64 paths do not exist here. -+ - /lib/aarch64-linux-gnu:/lib/aarch64-linux-gnu:ro -+ # glibc dynamic linker at the path the binaries' PT_INTERP requests. -+ - /lib/ld-linux-aarch64.so.1:/lib/ld-linux-aarch64.so.1:ro -+ # OpenCode state (auth excluded: mounted read-only below). Persists across -+ # container restarts so OpenDesign can resume OpenCode sessions. -+ - open_design_opencode:/home/open-design/.local -+ # Container-owned OpenCode config root. Keeping it separate from the host -+ # config is what makes the two environments independent: the container -+ # gets a minimal config with no host-absolute paths and no MCP servers it -+ # cannot run, and it can write its own service.json beside it. -+ - ${HOME}/opendesign/opencode-config-container:/home/open-design/.config/opencode -+ # OpenCode credentials (read-only file mount at its expected path). The -+ # parent directory stays container-local: never bind a host directory -+ # here, so the host auth store and session DB stay untouched. -+ - ${HOME}/.local/share/opencode/auth.json:/home/open-design/.local/share/opencode/auth.json:ro - tmpfs: -- # uid/gid must match the base image's open-design user (1001:1001). -- - /home/open-design:uid=1001,gid=1001 -+ # Must match the uid/gid this container runs as. -+ - /home/open-design:uid=1000,gid=1000 - healthcheck: - # Use wget (available in Alpine) instead of node — faster and does not - # compete with the daemon for CPU during startup. -@@ -62,3 +104,13 @@ services: - timeout: 10s - retries: 5 - start_period: 30s # increased from 20s — glibc host mounts add startup latency -+ -+volumes: -+ # OpenCode state produced inside the container (logs, session DB). Kept -+ # separate from the daemon data volume so `uninstall.sh` semantics stay clear. -+ open_design_opencode: -+ name: open-design_opencode_data -+ # Retained for data continuity only: the OpenCode config root is now the -+ # host-side directory mounted above, so nothing mounts this volume anymore. -+ open_design_opencode_config: -+ name: open-design_opencode_config diff --git a/integrations/opendesign/ksi-design-system/DESIGN.md b/integrations/opendesign/ksi-design-system/DESIGN.md deleted file mode 100644 index cc35960..0000000 --- a/integrations/opendesign/ksi-design-system/DESIGN.md +++ /dev/null @@ -1,127 +0,0 @@ -# KSI Design System - -> Category: Enterprise / Field Operations -> Restrained, work-first interface language for KSI products. - -## 1. Overview & Philosophy - -KSI builds operational software: field inspection, site monitoring, drone and observation-session control, -safety reporting, and the back-office tools around them. These are tools used *during* work, often outdoors or -under time pressure. The interface exists to make the next action obvious, not to demonstrate how much data -exists. - -**Core principles** - -- **Do not scatter KPI cards.** A metric earns a place only when it drives a decision. A wall of equally - weighted numbers hides the one that matters. -- **Design around the real task flow.** Model the sequence a person actually performs, including the - interruptions. Do not start from a dashboard template. -- **Every status connects to a next action.** A state without an available action is incomplete: show what the - user can do, or say explicitly that nothing is required. -- **Use a map only when location is the point.** A map that does not answer a spatial question is decoration. - If a list or a timeline carries the information better, use that. -- **Make the relationship visible.** A field site, a device, and an observation session are different things. - The interface must make their relationship explicit rather than implying it through layout proximity. -- **Prefer hierarchy over density.** Progressive disclosure beats a crowded screen. Show the primary object - and its state first; reveal the rest on demand. -- **Restraint is the visual language.** Neutral surfaces, few accents, strong typography. Bold colour is - reserved for state and action, not for decoration. - -## 2. Layout & Hierarchy - -- Establish one primary object per screen: the site, the session, the report. Everything else is supporting. -- Lead with the object's current state and the action available on it. Metadata is secondary. -- Use a single reading column for narrative content and a bounded grid for comparison content. Do not mix - both in one region. -- Group by decision, not by data source. Two fields belong together if the user evaluates them together. -- Keep primary navigation stable across the product. Avoid per-screen navigation models. -- Wide screens get more context, not more columns. Narrow screens get fewer elements, not smaller ones. -- Prefer full-width sections to nested cards. Cards inside cards flatten hierarchy. - -## 3. Status & Action - -- Define statuses as a small, explicit vocabulary (for example: scheduled, in progress, needs review, - complete, blocked). Never rely on colour alone to carry state — pair colour with a label and an icon or shape. -- Every status shows its owner and its next step where that is known. -- Distinguish *informational* states from *actionable* ones. Informational states are quiet; actionable states - carry the affordance. -- Stale or unknown data must look stale or unknown. Never present last-known values as current. -- Failures state what failed, what it affects, and what the user can do. Avoid generic error copy. -- Never use a status badge as a button. Separate reading from changing. - -## 4. Maps & Spatial Views - -- A map appears when the task is spatial: locating a site, judging coverage, planning a flight path, reviewing - a route or a captured area. -- Always pair a map with a non-spatial alternative for the same information (list or table) when the task can - be completed without geography. -- Show the map's scope and currency: what area, from when, and whether it is live or captured. -- Do not use a map as a hero image or a decorative header. -- Cluster or aggregate at low zoom. An unreadable cloud of pins is not an overview. -- Mark the difference between planned, in-progress, and completed spatial data consistently with the status - vocabulary. - -## 5. Data Display - -- Choose the representation from the question: trend over time, comparison across items, distribution, or a - single current value. Do not default to a chart. -- One chart answers one question. Split multi-question charts rather than adding axes and legends. -- Always label units and the time window. An unlabelled axis is an incomplete statement. -- Show empty, partial, and error states for every data region. A blank panel must say why it is blank. -- Prefer a table over a chart when the user needs exact values or row-level actions. -- Colour in data display encodes meaning and must match the status vocabulary; it is not a palette exercise. - -## 6. Typography & Density - -- One family across the product. Use weight and size for hierarchy, not a second typeface. -- Establish a small, fixed size scale and use it consistently. Do not tune sizes per screen. -- Numbers that are compared must be tabular and aligned. Right-align numeric columns. -- Keep line length readable for prose; allow denser rows for tabular scanning. -- Korean and English text coexist. Do not compress line height to fit more rows; keep both scripts readable. -- Dense layouts are acceptable when the user is scanning, not reading. Reading surfaces stay quiet. - -## 7. Color - -- Neutral surfaces carry the interface. Accent colour marks action and state only. -- Reserve the strongest colour for the single primary action on a screen. -- Status colours are a fixed, small set used consistently product-wide. Do not invent a new colour for a new - status; extend the vocabulary instead. -- Ensure text and state colours meet contrast requirements on their actual background, including over imagery. -- Never encode required information in colour alone. - -## 8. Components - -- Reuse the product's existing components and tokens. This document defines intent; the product's real - components define implementation. -- Buttons state the action ("Approve report"), not the mechanism ("Submit"). -- Prefer inline editing with an explicit save or undo over modal forms for single-field changes. -- Confirmations are reserved for irreversible actions and must name the object and the consequence. -- Empty states teach the next action; they are not just "no data". -- Loading states preserve layout. Do not replace a region with a spinner that collapses the page. - -## 9. Motion - -- Motion clarifies a change of state or spatial relationship. It is not decorative. -- Keep durations short and consistent. Respect reduced-motion preferences. -- Do not animate data into place in a way that delays reading. -- Use motion to show where something came from or went to when a panel or detail view opens. - -## 10. Anti-patterns - -- A dashboard that opens with six or more equal KPI cards. -- A map used as decoration or as a hero background. -- Status communicated only by colour, dot, or badge with no label. -- "No data" with no explanation and no next step. -- Charts without units, time windows, or axis labels. -- Mixing field-site, device, and session identities in one list without distinguishing them. -- Replacing a dense but useful view with a simplified one that removes required information. -- Introducing a new accent colour or typeface to make a screen "look better". -- Modal dialogs for routine actions. -- Inventing a parallel design system instead of using the product's existing tokens and components. - -## 11. Relationship to the Project - -This package describes KSI design intent. A specific project's own `DESIGN.md`, tokens, and components remain -authoritative for that product, and an approved artifact from the Human overrides generic guidance here. Where -this document is silent, use the project's established patterns rather than importing another product's visual -language. diff --git a/integrations/opendesign/ksi-design-system/metadata.json b/integrations/opendesign/ksi-design-system/metadata.json deleted file mode 100644 index 7ff87a0..0000000 --- a/integrations/opendesign/ksi-design-system/metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "title": "KSI Design System", - "category": "Enterprise / Field Operations", - "surface": "web", - "status": "published" -} diff --git a/integrations/opendesign/opencode-cli b/integrations/opendesign/opencode-cli deleted file mode 100755 index 0504e7c..0000000 --- a/integrations/opendesign/opencode-cli +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env node -// OpenDesign → OpenCode compatibility shim (KSI deployment). -// -// OpenDesign's OpenCode adapter targets the older `opencode-cli` argv shape. -// OpenCode 2.x changed it, so this shim translates before delegating to the -// real binary that sits beside it: -// -// run --dir → the workspace directory becomes a trailing positional -// run --pure → dropped (2.x has no --pure; it only guards the -// daemon's connection smoke test, not normal runs) -// models --verbose → dropped (2.x has no --verbose; the daemon accepts the -// plain one-id-per-line list as its documented fallback, -// which is far better than the hardcoded model list) -// -// Everything else, and every other subcommand, is passed through unchanged. - -const { spawn } = require("node:child_process") -const { join } = require("node:path") - -const real = join(__dirname, "opencode") -const argv = process.argv.slice(2) - -function translate(args) { - if (args[0] === "models") { - return args.filter((arg) => arg !== "--verbose") - } - if (args[0] !== "run") return args - const out = ["run"] - let dir = null - for (let i = 1; i < args.length; i++) { - const arg = args[i] - if (arg === "--dir") { - dir = args[++i] ?? null - continue - } - if (arg === "--pure") continue - out.push(arg) - } - if (dir) out.push(dir) - return out -} - -const child = spawn(real, translate(argv), { stdio: "inherit" }) -child.on("error", (error) => { - process.stderr.write(`opencode-cli shim: ${error.message}\n`) - process.exit(127) -}) -child.on("exit", (code, signal) => { - if (signal) process.kill(process.pid, signal) - else process.exit(code ?? 0) -}) diff --git a/integrations/opendesign/opencode-config-container.jsonc b/integrations/opendesign/opencode-config-container.jsonc deleted file mode 100644 index 74ac4a2..0000000 --- a/integrations/opendesign/opencode-config-container.jsonc +++ /dev/null @@ -1,22 +0,0 @@ -{ - // OpenCode running inside the OpenDesign container. - // - // This is deliberately NOT the host engineering config. The container's - // OpenCode exists only as the design-generation agent that OpenDesign spawns, - // so it carries no engineering roles, no skills, and no MCP servers: the - // host's MCP entries (docker exec, npx, host-absolute commands) cannot work - // inside the container and only slow startup and pollute the model/tool - // catalog. OpenDesign injects whatever MCP context a run needs itself. - // - // Credentials are shared read-only from the host auth store, so the models - // listed here are the user's real authenticated providers. - "$schema": "https://opencode.ai/config.json", - "share": "disabled", - "update": "disable", - "tool_output": { "max_lines": 600, "max_bytes": 24576 }, - "compaction": { - "auto": true, - "keep": { "tokens": 8192 }, - "buffer": 4096 - } -} diff --git a/integrations/opendesign/remote-workspace/Dockerfile.overlay.example b/integrations/opendesign/remote-workspace/Dockerfile.overlay.example deleted file mode 100644 index 6d655f0..0000000 --- a/integrations/opendesign/remote-workspace/Dockerfile.overlay.example +++ /dev/null @@ -1,6 +0,0 @@ -# Build context contains generated outputs from the exact installed-image revision. -# Set BASE_IMAGE to the installed immutable digest, never an unreviewed latest tag. -ARG BASE_IMAGE -FROM ${BASE_IMAGE} -COPY --chown=1000:1000 daemon-dist/ /app/apps/daemon/dist/ -COPY --chown=1000:1000 web-out/ /app/apps/web/out/ diff --git a/integrations/opendesign/remote-workspace/config.example.json b/integrations/opendesign/remote-workspace/config.example.json deleted file mode 100644 index 90beb2c..0000000 --- a/integrations/opendesign/remote-workspace/config.example.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "origin": "https://your-dgx.your-tailnet.ts.net:7456", - "upstream": "http://127.0.0.1:7456", - "port": 17456, - "workspaceEnabled": false, - "home": "/home/your-user", - "stateDir": "/home/your-user/.local/state/od-remote-workspace", - "composeCwd": "/home/your-user/opendesign/open-design/deploy", - "composeProject": "open-design", - "service": "open-design", - "container": "open-design", - "composeFiles": [] -} diff --git a/integrations/opendesign/remote-workspace/connector.mjs b/integrations/opendesign/remote-workspace/connector.mjs deleted file mode 100644 index 33fe73f..0000000 --- a/integrations/opendesign/remote-workspace/connector.mjs +++ /dev/null @@ -1,138 +0,0 @@ -import { mkdir, readFile, writeFile, rename } from 'node:fs/promises'; -import path from 'node:path'; -import { randomUUID } from 'node:crypto'; -import { spawn } from 'node:child_process'; -import { resolveWorkspacePath, workspaceError, withinRoot } from './path-policy.mjs'; - -export async function atomicJson(file, value) { - await mkdir(path.dirname(file), { recursive: true, mode: 0o700 }); - const temp = file + '.' + randomUUID(); - await writeFile(temp, JSON.stringify(value, null, 2) + '\n', { mode: 0o600, flag: 'wx' }); - await rename(temp, file); -} - -async function readState(file) { - try { return JSON.parse(await readFile(file, 'utf8')); } - catch (error) { if (error.code === 'ENOENT') return { paths: [], operations: [] }; throw error; } -} - -export function composeRecreate(config) { - return async ({ overrideFile }) => { - const args = ['compose', '--project-name', config.composeProject]; - for (const file of [...config.composeFiles, overrideFile]) args.push('--file', file); - args.push('up', '-d', '--no-deps', config.service); - await new Promise((resolve, reject) => { - const child = spawn('docker', args, { cwd: config.composeCwd, stdio: ['ignore', 'ignore', 'pipe'] }); - // Do not persist Docker output: it can contain deployment environment values. - child.stderr.resume(); - const deadline = setTimeout(() => { child.kill('SIGTERM'); reject(new Error('Container recreation timed out')); }, 120000); - child.on('error', error => { clearTimeout(deadline); reject(error); }); - child.on('exit', code => { clearTimeout(deadline); code === 0 ? resolve() : reject(new Error('Container recreation failed')); }); - }); - }; -} - -export async function createConnector({ config, inspectRuns, recreate = composeRecreate(config), waitHealthy }) { - const stateFile = path.join(config.stateDir, 'connections.json'); - const overrideFile = path.join(config.stateDir, 'projects.compose.json'); - const saved = await readState(stateFile); - const paths = new Set(saved.paths); - const operations = new Map(saved.operations.map(op => [op.operationId, op])); - for (const op of operations.values()) if (['pending', 'connecting'].includes(op.status)) { - op.status = 'failed'; op.message = 'Connection process interrupted; retry with a new request'; - } - let active = null; - const visibleOperation = op => ({ ...op, ...(active === op && !['pending', 'connecting'].includes(op.status) ? { status: 'connecting' } : {}) }); - let writes = Promise.resolve(); - const persist = () => { - writes = writes.then(() => atomicJson(stateFile, { paths: [...paths], operations: [...operations.values()] })); - return writes; - }; - const override = selected => ({ services: { [config.service]: { volumes: selected.map(p => ({ type: 'bind', source: p, target: p, bind: { create_host_path: false } })) } } }); - const verifyRoots = async selected => { - for (const p of selected) if (await resolveWorkspacePath(p, { home: config.home, forSelection: true }) !== p) throw workspaceError('Connected directory changed during connection'); - }; - // A connected project root covers its own canonical subdirectories: the - // container mounts the whole root tree, so a descendant needs neither a new - // exact-path entry nor a recreation. Both sides are canonical realpaths and - // the boundary check is path-relative (a sibling like `project-old` never - // matches). Unconnected roots still require the guarded connect flow. - const coveringRoot = p => { - for (const root of paths) if (withinRoot(root, p)) return root; - return null; - }; - - async function perform(op, originalInput) { - const previous = [...paths]; - let attempted = false; - try { - const runs = await inspectRuns(); - const terminals = ['succeeded', 'failed', 'canceled']; - if (!Array.isArray(runs?.runs) || runs.runs.some(run => !terminals.includes(run.status))) { - op.status = 'busy'; op.message = 'Generation is active or its state cannot be verified; retry when idle'; - return; - } - const checked = await resolveWorkspacePath(originalInput, { home: config.home, forSelection: true }); - if (checked !== op.path) throw workspaceError('Selected directory changed before connection'); - // Recheck all retained roots; a moved root must not silently expose a replacement symlink. - for (const p of previous) if (await resolveWorkspacePath(p, { home: config.home, forSelection: true }) !== p) throw workspaceError('Previously connected directory changed'); - op.status = 'connecting'; - await persist(); - await atomicJson(overrideFile, override([...previous, op.path])); - attempted = true; - await verifyRoots([...previous, op.path]); - await recreate({ overrideFile }); - await waitHealthy(); - await verifyRoots([...previous, op.path]); - paths.add(op.path); - op.status = 'ready'; - } catch (error) { - op.status = 'failed'; - op.message = error.status ? error.message : 'Connection failed; see rollback status and retry'; - if (attempted) { - try { - await atomicJson(overrideFile, override(previous)); - await verifyRoots(previous); - await recreate({ overrideFile }); - await waitHealthy(); - await verifyRoots(previous); - op.rollbackRecovered = true; - } catch { op.rollbackRecovered = false; op.message = 'Connection and rollback recovery failed; operator recovery required'; } - } - } finally { - await persist(); - active = null; - } - } - - return { - isConnecting: () => active !== null, - isConnected: p => coveringRoot(p) !== null, - getOperation: id => { - const op = operations.get(id); - if (!op) throw workspaceError('Connection operation not found', 404); - return visibleOperation(op); - }, - async connectProject(input) { - if (!input || Object.keys(input).some(key => !['path', 'requestId'].includes(key))) throw workspaceError('Unexpected connection fields'); - if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(input.requestId ?? '')) throw workspaceError('requestId must be a UUID'); - const canonical = await resolveWorkspacePath(input.path, { home: config.home, forSelection: true }); - const existing = [...operations.values()].find(op => op.requestIds.includes(input.requestId)); - if (existing) { - if (existing.path !== canonical) throw workspaceError('requestId was used for a different path'); - return visibleOperation(existing); - } - if (active?.path === canonical) { - active.requestIds.push(input.requestId); - await persist(); - return visibleOperation(active); - } - const op = { operationId: randomUUID(), requestIds: [input.requestId], path: canonical, status: coveringRoot(canonical) ? 'ready' : active ? 'busy' : 'pending' }; - operations.set(op.operationId, op); - if (op.status === 'pending') active = op; - await persist(); - if (op.status === 'pending') void perform(op, input.path).catch(() => { active = null; op.status = 'failed'; op.message = 'Could not persist connection state'; }); - return visibleOperation(op); - }, - }; -} diff --git a/integrations/opendesign/remote-workspace/gateway.mjs b/integrations/opendesign/remote-workspace/gateway.mjs deleted file mode 100644 index 94bb0b7..0000000 --- a/integrations/opendesign/remote-workspace/gateway.mjs +++ /dev/null @@ -1,100 +0,0 @@ -import http from 'node:http'; -import { timingSafeEqual } from 'node:crypto'; -import { isLoopback, validateRemoteRequest } from './request-policy.mjs'; - -const LABS_STATUS = '/api/strategies/od-next/rollout'; - -export function sendJson(res, status, body) { - res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' }); - res.end(JSON.stringify(body)); -} - -export function createGateway({ config, workspaceService, upstream }) { - const target = new URL(upstream); - if (target.protocol !== 'http:' || !isLoopback(target.hostname) || target.pathname !== '/') { - throw new Error('Upstream must be a loopback HTTP origin'); - } - const validate = req => validateRemoteRequest({ - peer: req.socket.remoteAddress, - host: req.headers.host, - origin: req.headers.origin, - fetchSite: req.headers['sec-fetch-site'], - }, config); - - const server = http.createServer(async (req, res) => { - if (req.url.startsWith('/api/remote-workspace/internal/')) { - const supplied = Buffer.from(req.headers.authorization ?? ''); - const expected = Buffer.from(`Bearer ${config.daemonToken ?? ''}`); - const localAuthority = /^(?:127\.0\.0\.1|localhost|\[::1\]):\d+$/.test(req.headers.host ?? ''); - if (req.method !== 'GET' || !localAuthority || req.headers.origin !== undefined || !isLoopback(req.socket.remoteAddress) || !config.daemonToken || supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) { - return sendJson(res, 403, { error: { code: 'FORBIDDEN', message: 'Internal daemon authentication required' } }); - } - try { await workspaceService.handleInternal(req, res); } - catch (error) { sendJson(res, error.status ?? 500, { error: { code: 'WORKSPACE_ERROR', message: error.status ? error.message : 'Internal status unavailable' } }); } - return; - } - const validation = validate(req); - if (!validation.ok) return sendJson(res, 403, { error: { code: 'FORBIDDEN', message: validation.reason } }); - try { - if (workspaceService && await workspaceService.handle(req, res)) return; - } catch (error) { - if (!res.headersSent) sendJson(res, error.status ?? 500, { error: { code: error.code ?? 'WORKSPACE_ERROR', message: error.status ? error.message : 'Workspace request failed' } }); - else res.destroy(); - return; - } - const headers = { ...req.headers }; - if (req.method === 'GET' && req.url === LABS_STATUS) { - headers.host = target.host; - delete headers.origin; - } - const outbound = http.request(target, { method: req.method, path: req.url, headers }); - const deadline = setTimeout(() => outbound.destroy(new Error('Upstream response timeout')), 15000); - outbound.on('response', response => { - clearTimeout(deadline); - res.writeHead(response.statusCode, response.headers); - response.pipe(res); - response.on('error', () => res.destroy()); - }); - outbound.on('error', () => { - clearTimeout(deadline); - if (!res.headersSent && !res.destroyed) sendJson(res, 502, { error: { code: 'UPSTREAM_UNAVAILABLE', message: 'Local daemon is unavailable; retry after reconnect' } }); - else res.destroy(); - }); - req.on('aborted', () => outbound.destroy()); - res.on('close', () => { if (!res.writableEnded) outbound.destroy(); }); - req.pipe(outbound); - }); - - server.on('upgrade', (req, socket, head) => { - if (req.url.startsWith('/api/remote-workspace/') || !validate(req).ok) return socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n'); - const outbound = http.request(target, { method: req.method, path: req.url, headers: req.headers }); - const deadline = setTimeout(() => outbound.destroy(new Error('Upgrade timeout')), 15000); - outbound.on('upgrade', (response, peer, upstreamHead) => { - clearTimeout(deadline); - let responseHeaders = `HTTP/1.1 ${response.statusCode} ${response.statusMessage}\r\n`; - for (let i = 0; i < response.rawHeaders.length; i += 2) { - responseHeaders += `${response.rawHeaders[i]}: ${response.rawHeaders[i + 1]}\r\n`; - } - socket.write(responseHeaders + '\r\n'); - if (upstreamHead.length) socket.write(upstreamHead); - if (head.length) peer.write(head); - peer.pipe(socket).pipe(peer); - socket.on('close', () => peer.destroy()); - peer.on('close', () => socket.destroy()); - peer.on('error', () => socket.destroy()); - }); - outbound.on('response', response => { - clearTimeout(deadline); - response.resume(); - socket.end(`HTTP/1.1 ${response.statusCode} Upstream rejected upgrade\r\nConnection: close\r\n\r\n`); - }); - outbound.on('error', () => { - clearTimeout(deadline); - if (!socket.destroyed) socket.end('HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n'); - }); - socket.on('error', () => outbound.destroy()); - socket.on('close', () => outbound.destroy()); - outbound.end(); - }); - return server; -} diff --git a/integrations/opendesign/remote-workspace/ksi-codex-userns.apparmor b/integrations/opendesign/remote-workspace/ksi-codex-userns.apparmor deleted file mode 100644 index f9a0500..0000000 --- a/integrations/opendesign/remote-workspace/ksi-codex-userns.apparmor +++ /dev/null @@ -1,51 +0,0 @@ -# Based on Moby docker-default (Apache-2.0); optional, separately approved host policy. -abi , -#include -profile ksi-codex-userns flags=(attach_disconnected,mediate_deleted) { - #include - network, - network unix, - deny network alg, - deny network vsock, - capability, - file, - userns, - umount, - signal (receive) peer=unconfined, - signal (receive) peer=runc, - signal (send,receive) peer=ksi-codex-userns, - deny @{PROC}/* w, - deny @{PROC}/{[^1-9/],[^1-9/][^0-9/],[^1-9s/][^0-9y/][^0-9s/],[^1-9/][^0-9/][^0-9/][^0-9/]*}/** w, - deny @{PROC}/sys/[^k]** w, - deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, - deny @{PROC}/sysrq-trigger rwklx, - deny @{PROC}/kcore rwklx, - # Nested bubblewrap only. Drop ALL outer container capabilities separately. - mount options=(rw, slave), - mount options=(rw, rslave), - mount options=(rw, silent, rslave), - mount options=(rw, private), - mount options=(rw, rprivate), - mount options=(rw, silent, rprivate), - mount options=(rw, bind), - mount options=(rw, rbind), - mount options=(ro, remount, bind), - mount options=(rw, remount, bind), - mount options=(ro, nosuid, nodev, remount, bind, silent, relatime), - mount options=(ro, nosuid, nodev, noexec, remount, bind, silent, relatime), - mount options=(rw, nosuid, nodev, remount, bind, silent, relatime), - mount options=(rw, nosuid, nodev, noexec, remount, bind, silent, relatime), - mount fstype=tmpfs, - mount fstype=proc, - mount fstype=devpts, - pivot_root, - deny /sys/[^f]*/** wklx, - deny /sys/f[^s]*/** wklx, - deny /sys/fs/[^c]*/** wklx, - deny /sys/fs/c[^g]*/** wklx, - deny /sys/fs/cg[^r]*/** wklx, - deny /sys/firmware/** rwklx, - deny /sys/devices/virtual/powercap/** rwklx, - deny /sys/kernel/security/** rwklx, - ptrace (trace,tracedby,read,readby) peer=ksi-codex-userns, -} diff --git a/integrations/opendesign/remote-workspace/mcp-host-entry.ts.example b/integrations/opendesign/remote-workspace/mcp-host-entry.ts.example deleted file mode 100644 index 74d6c27..0000000 --- a/integrations/opendesign/remote-workspace/mcp-host-entry.ts.example +++ /dev/null @@ -1,10 +0,0 @@ -// Copy into apps/daemon/mcp-host-entry.ts in the exact-revision upstream build tree. -// Bundle with its installed dependencies; no daemon/server lifecycle import needed. -import { runMcpStdio } from './src/mcp.js'; -const daemonUrl = process.env.OD_DAEMON_URL ?? 'http://127.0.0.1:7456'; -const target = new URL(daemonUrl); -if (target.protocol !== 'http:' || target.hostname !== '127.0.0.1' || target.username || target.password || target.pathname !== '/' || target.search || target.hash) throw new Error('Pinned loopback daemon required'); -void runMcpStdio({ daemonUrl }).catch(() => { - console.error('Host MCP proxy failed'); - process.exitCode = 1; -}); diff --git a/integrations/opendesign/remote-workspace/od-remote-workspace.service.example b/integrations/opendesign/remote-workspace/od-remote-workspace.service.example deleted file mode 100644 index 9de1231..0000000 --- a/integrations/opendesign/remote-workspace/od-remote-workspace.service.example +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=DGX remote workspace gateway -After=network-online.target - -[Service] -Type=simple -ExecStart=/usr/bin/node %h/.local/share/od-remote-workspace/service.mjs --config %h/.local/state/od-remote-workspace/config.json -EnvironmentFile=-%h/.local/state/od-remote-workspace/service.env -Restart=on-failure -RestartSec=3 -UMask=0077 -NoNewPrivileges=true - -[Install] -WantedBy=default.target diff --git a/integrations/opendesign/remote-workspace/path-policy.mjs b/integrations/opendesign/remote-workspace/path-policy.mjs deleted file mode 100644 index 717544e..0000000 --- a/integrations/opendesign/remote-workspace/path-policy.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import path from 'node:path'; -import { realpath, stat } from 'node:fs/promises'; - -const CREDENTIAL_ROOTS = ['.ssh', '.gnupg', '.codex', '.claude', '.gemini', '.config', '.local']; -export function workspaceError(message, status = 400, code = 'INVALID_WORKSPACE') { - return Object.assign(new Error(message), { status, code }); -} - -// Shared path-boundary check: true when `candidate` is `root` itself or a -// descendant of it. Path-relative semantics, so a sibling sharing a string -// prefix (EVENTOUCH vs EVENTOUCH-other) never matches. -export function withinRoot(root, candidate) { - const rel = path.relative(root, candidate); - return rel === '' || (!rel.startsWith('..' + path.sep) && rel !== '..' && !path.isAbsolute(rel)); -} - -export async function resolveWorkspacePath(input, { home, forSelection = false }) { - if (typeof input !== 'string' || input.includes('\0') || input.length > 4096) throw workspaceError('Invalid directory path'); - const root = await realpath(home); - const expanded = input === '~' ? root : input.startsWith('~/') ? path.join(root, input.slice(2)) : input; - const lexical = path.resolve(root, expanded); - if (!withinRoot(root, lexical)) throw workspaceError('Directory is outside the home browse root', 403); - let canonical; - try { - canonical = await realpath(lexical); - if (!(await stat(canonical)).isDirectory()) throw workspaceError('Select an existing directory'); - } catch (error) { - if (error.status) throw error; - throw workspaceError('Directory does not exist or is not accessible', error.code === 'EACCES' ? 403 : 404); - } - if (!withinRoot(root, canonical)) throw workspaceError('Symlink target is outside the home browse root', 403); - if (forSelection) { - if (canonical === root) throw workspaceError('Choose a project below home, not home itself', 403); - for (const p of [lexical, canonical]) { - if (CREDENTIAL_ROOTS.some(name => withinRoot(path.join(root, name), p))) throw workspaceError('Credential/configuration directory is not a project workspace', 403); - } - } - return canonical; -} diff --git a/integrations/opendesign/remote-workspace/request-policy.mjs b/integrations/opendesign/remote-workspace/request-policy.mjs deleted file mode 100644 index ea5efe8..0000000 --- a/integrations/opendesign/remote-workspace/request-policy.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import net from 'node:net'; - -export function isLoopback(peer) { - const value = String(peer ?? '').replace(/^::ffff:/, ''); - return value === '::1' || (net.isIP(value) === 4 && value.startsWith('127.')); -} - -export function validateRemoteRequest({ peer, host, origin, fetchSite }, config) { - const fail = reason => ({ ok: false, reason }); - let expected; - try { - expected = new URL(config.origin); - if (expected.protocol !== 'https:' || expected.origin !== config.origin) return fail('Invalid configured origin'); - } catch { return fail('Invalid configured origin'); } - if (!isLoopback(peer)) return fail('Loopback proxy required'); - if (host !== expected.host) return fail('Unapproved request host'); - if (origin !== undefined && origin !== expected.origin) return fail('Unapproved request origin'); - if (fetchSite !== undefined && !['same-origin', 'none'].includes(fetchSite)) return fail('Cross-site request forbidden'); - return { ok: true }; -} diff --git a/integrations/opendesign/remote-workspace/seccomp-policy.mjs b/integrations/opendesign/remote-workspace/seccomp-policy.mjs deleted file mode 100644 index 70413bb..0000000 --- a/integrations/opendesign/remote-workspace/seccomp-policy.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import { readFile, writeFile } from 'node:fs/promises'; -import { pathToFileURL } from 'node:url'; - -/** Explicit operator opt-in; never installed by KSI or selected by a browser request. */ -export function createCodexSeccompProfile(base, architecture = process.arch) { - if (!['arm64', 'x64'].includes(architecture)) throw new Error('Unsupported clone argument layout'); - if (base?.defaultAction !== 'SCMP_ACT_ERRNO' || !Array.isArray(base.syscalls)) throw new Error('Reviewed default-deny Docker profile required'); - const profile = structuredClone(base); - profile.syscalls.push( - { names: ['clone'], action: 'SCMP_ACT_ALLOW', args: [{ index: 0, value: 0x10000000, valueTwo: 0x10000000, op: 'SCMP_CMP_MASKED_EQ' }], excludes: { arches: ['s390', 's390x'] }, comment: 'Nested user namespace required; no host capabilities added' }, - { names: ['unshare'], action: 'SCMP_ACT_ALLOW', args: [{ index: 0, value: 0x81fdffff, valueTwo: 0, op: 'SCMP_CMP_MASKED_EQ' }], comment: 'Namespace flags only; kernel rejects flags above its 32-bit valid mask' }, - { names: ['mount', 'umount2', 'pivot_root'], action: 'SCMP_ACT_ALLOW', comment: 'Namespace-local CAP_SYS_ADMIN required by kernel; AppArmor mount filtering retained' }, - ); - return profile; -} - -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - if (process.argv.length !== 4) throw new Error('Usage: node seccomp-policy.mjs '); - const base = JSON.parse(await readFile(process.argv[2], 'utf8')); - await writeFile(process.argv[3], JSON.stringify(createCodexSeccompProfile(base), null, 2) + '\n', { flag: 'wx', mode: 0o600 }); -} diff --git a/integrations/opendesign/remote-workspace/service.mjs b/integrations/opendesign/remote-workspace/service.mjs deleted file mode 100644 index 884c171..0000000 --- a/integrations/opendesign/remote-workspace/service.mjs +++ /dev/null @@ -1,55 +0,0 @@ -import { readFile } from 'node:fs/promises'; -import path from 'node:path'; -import { pathToFileURL } from 'node:url'; -import { createGateway } from './gateway.mjs'; -import { createConnector } from './connector.mjs'; -import { createWorkspaceService } from './workspace-service.mjs'; -import { validateRemoteRequest } from './request-policy.mjs'; - -export async function createService(config) { - const origin = new URL(config.origin); - if (!validateRemoteRequest({ peer: '127.0.0.1', host: origin.host }, config).ok) throw new Error('Invalid remote origin'); - const upstream = new URL(config.upstream); - if (!Number.isInteger(config.port) || config.port < 1024 || config.port > 65535 || config.port === Number(upstream.port || 80)) throw new Error('Gateway port must differ from upstream'); - let workspaceService; - if (config.workspaceEnabled !== false) { - if (typeof config.daemonToken !== 'string' || config.daemonToken.length < 32) throw new Error('Private daemon token required'); - if (!path.isAbsolute(config.home ?? '') || !path.isAbsolute(config.stateDir ?? '') || !Array.isArray(config.composeFiles) || config.composeFiles.some(p => !path.isAbsolute(p)) || config.service !== 'open-design') throw new Error('Invalid workspace service configuration'); - const headers = config.upstreamToken ? { authorization: `Bearer ${config.upstreamToken}` } : {}; - async function inspectRuns() { - const response = await fetch(new URL('/api/runs', upstream), { headers, signal: AbortSignal.timeout(10000) }); - if (!response.ok) throw new Error('Cannot verify daemon run state'); - return response.json(); - } - async function waitHealthy() { - const until = Date.now() + 120000; - while (Date.now() < until) { - try { - const response = await fetch(new URL('/api/health', upstream), { headers, signal: AbortSignal.timeout(2000) }); - if (response.ok && (await response.json()).ok === true) return; - } catch {} - await new Promise(resolve => setTimeout(resolve, 1000)); - } - throw new Error('Daemon health recovery timed out'); - } - const connector = await createConnector({ config, inspectRuns, waitHealthy }); - workspaceService = createWorkspaceService({ config, connector }); - } - return createGateway({ config, workspaceService, upstream: config.upstream }); -} - -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - try { - if (process.argv[2] !== '--config' || !process.argv[3]) throw new Error('Usage: service.mjs --config '); - const config = JSON.parse(await readFile(process.argv[3], 'utf8')); - config.daemonToken = process.env.OD_REMOTE_DAEMON_TOKEN; - config.upstreamToken = process.env.OD_REMOTE_UPSTREAM_TOKEN; - const server = await createService(config); - server.on('error', () => { console.error('Remote workspace listener failed'); process.exitCode = 1; }); - server.listen(config.port, '127.0.0.1', () => console.log('Remote workspace gateway ready')); - for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => { - server.close(() => process.exit(0)); - setTimeout(() => process.exit(0), 5000).unref(); - }); - } catch { console.error('Remote workspace startup failed; verify private configuration'); process.exitCode = 1; } -} diff --git a/integrations/opendesign/remote-workspace/tests/connector-docker-smoke.test.mjs b/integrations/opendesign/remote-workspace/tests/connector-docker-smoke.test.mjs deleted file mode 100644 index 1b6fd78..0000000 --- a/integrations/opendesign/remote-workspace/tests/connector-docker-smoke.test.mjs +++ /dev/null @@ -1,59 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { randomUUID } from 'node:crypto'; -import { execFile } from 'node:child_process'; -import { promisify } from 'node:util'; -import { createConnector } from '../connector.mjs'; - -const exec = promisify(execFile); -test('real isolated Compose preserves base mounts and recovers failed project connection', { skip: process.env.OD_REMOTE_DOCKER_SMOKE !== '1', timeout: 120000 }, async t => { - const root = await mkdtemp(path.join(os.tmpdir(), 'remote-docker-smoke-')); - const project = 'ksi-remote-smoke-' + randomUUID().slice(0, 8); - const container = project; - const home = path.join(root, 'home'); - for (const p of ['one', 'two']) await mkdir(path.join(home, p), { recursive: true }); - const data = path.join(root, 'data'); await mkdir(data); await writeFile(path.join(data, 'sentinel'), 'preserved'); - const base = path.join(root, 'base.compose.json'); - await writeFile(base, JSON.stringify({ services: { 'open-design': { - image: 'ksi-open-design:remote-workspace-20260930-ready', container_name: container, - entrypoint: ['node'], command: ['--input-type=module', '-e', "import http from 'node:http';http.createServer((q,r)=>r.end('healthy')).listen(7456,'127.0.0.1');"], - volumes: [{ type: 'bind', source: data, target: '/fixture-data' }], - } } })); - const compose = ['compose', '--project-name', project, '-f', base]; - t.after(async () => { - await exec('docker', [...compose, 'down'], { timeout: 30000 }); - await rm(root, { recursive: true, force: true }); - }); - const config = { home, stateDir: path.join(root, 'state'), composeFiles: [base], composeProject: project, composeCwd: root, service: 'open-design', container }; - let healthChecks = 0; - const connector = await createConnector({ config, inspectRuns: async () => ({ runs: [] }), waitHealthy: async () => { - for (let i = 0; i < 50; i++) { - try { - await exec('docker', ['exec', container, 'node', '-e', "fetch('http://127.0.0.1:7456').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"], { timeout: 5000 }); - break; - } catch (e) { if (i === 49) throw e; await new Promise(r => setTimeout(r, 200)); } - } - const metadata = JSON.parse((await exec('docker', ['inspect', container], { timeout: 10000 })).stdout)[0]; - assert(metadata.Mounts.some(m => m.Source === data && m.Destination === '/fixture-data')); - assert(metadata.Mounts.some(m => m.Source === path.join(home, 'one'))); - if (++healthChecks === 2) { - assert(metadata.Mounts.some(m => m.Source === path.join(home, 'two'))); - throw new Error('controlled post-recreate health failure'); - } - } }); - async function settled(id) { - for (let i = 0; i < 500; i++) { const op = connector.getOperation(id); if (!['pending', 'connecting'].includes(op.status)) return op; await new Promise(r => setTimeout(r, 100)); } - throw new Error('operation timeout'); - } - const first = await connector.connectProject({ path: 'one', requestId: randomUUID() }); - assert.equal((await settled(first.operationId)).status, 'ready'); - const second = await connector.connectProject({ path: 'two', requestId: randomUUID() }); - const failed = await settled(second.operationId); - assert.equal(failed.status, 'failed'); assert.equal(failed.rollbackRecovered, true); - const mounts = JSON.parse((await exec('docker', ['inspect', container])).stdout)[0].Mounts; - assert(!mounts.some(m => m.Source === path.join(home, 'two'))); - assert.equal((await exec('docker', ['exec', container, 'cat', '/fixture-data/sentinel'])).stdout, 'preserved'); -}); diff --git a/integrations/opendesign/remote-workspace/tests/connector.test.mjs b/integrations/opendesign/remote-workspace/tests/connector.test.mjs deleted file mode 100644 index 4ab3597..0000000 --- a/integrations/opendesign/remote-workspace/tests/connector.test.mjs +++ /dev/null @@ -1,171 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import { mkdtemp, mkdir, readFile, rm, symlink, unlink, rename, realpath } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { randomUUID } from 'node:crypto'; -import { createConnector } from '../connector.mjs'; - -async function fixture(t, overrides = {}) { - // Canonicalize: production resolves every path with realpath, so the - // fixture base must be canonical too (/var vs /private/var on macOS, - // 8.3 short names in TMPDIR on Windows) before deriving home/state. - const base = await realpath(await mkdtemp(path.join(os.tmpdir(), 'remote-connect-'))); - t.after(() => rm(base, { recursive: true, force: true })); - const home = path.join(base, 'home'); - for (const p of ['project', 'second']) await mkdir(path.join(home, p), { recursive: true }); - const config = { home, stateDir: path.join(base, 'state'), composeFiles: ['/fixed/base.yml', '/fixed/linux.yml'], composeProject: 'open-design', service: 'open-design', container: 'open-design' }; - const effects = []; - const deps = { - inspectRuns: async () => ({ runs: [], awaitingInputProjectIds: [] }), - recreate: async ({ overrideFile }) => effects.push(JSON.parse(await readFile(overrideFile, 'utf8'))), - waitHealthy: async () => {}, - ...overrides, - }; - const connector = await createConnector({ config, ...deps }); - // Escape target for symlink-swap tests: an existing directory outside the - // fixture home but inside the owned fixture root. A host-specific path such - // as /tmp may be missing or drive-relative on other platforms. - const outside = config.stateDir; - await mkdir(outside, { recursive: true }); - assert.ok(path.relative(home, outside).startsWith('..')); - return { config, connector, home, outside, effects, deps }; -} - -async function settled(connector, id) { - for (let i = 0; i < 100; i++) { - const op = connector.getOperation(id); - if (!['pending', 'connecting'].includes(op.status)) return op; - await new Promise(r => setTimeout(r, 2)); - } - throw new Error('operation did not finish'); -} - -test('connects only selected canonical project with same-path mount and survives reload', async t => { - const { connector, config, home, effects, deps } = await fixture(t); - const requestId = randomUUID(); - const opened = await connector.connectProject({ path: 'project', requestId }); - assert.equal((await settled(connector, opened.operationId)).status, 'ready'); - assert.deepEqual(effects[0].services['open-design'].volumes, [{ type: 'bind', source: path.join(home, 'project'), target: path.join(home, 'project'), bind: { create_host_path: false } }]); - const resumed = await createConnector({ config, ...deps }); - assert.equal(resumed.getOperation(opened.operationId).status, 'ready'); - assert.equal((await resumed.connectProject({ path: 'project', requestId })).operationId, opened.operationId); - assert.equal(effects.length, 1); -}); - -test('joins duplicate requests while connecting and refuses a second concurrent project', async t => { - let release; - const held = new Promise(r => { release = r; }); - const { connector } = await fixture(t, { waitHealthy: () => held }); - const requestId = randomUUID(); - const first = await connector.connectProject({ path: 'project', requestId }); - const duplicate = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - assert.equal(first.operationId, duplicate.operationId); - const second = await connector.connectProject({ path: 'second', requestId: randomUUID() }); - assert.equal(second.status, 'busy'); - release(); - assert.equal((await settled(connector, first.operationId)).status, 'ready'); -}); - -test('active and unknown run state never restarts container', async t => { - for (const runs of [{ runs: [{ status: 'running' }] }, {}, { runs: [{ status: 'unexpected' }] }]) { - const { connector, effects } = await fixture(t, { inspectRuns: async () => runs }); - const opened = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - assert.equal((await settled(connector, opened.operationId)).status, 'busy'); - assert.equal(effects.length, 0); - } -}); - -test('health failure restores previous mount set and records recovered rollback', async t => { - let checks = 0; - const { connector, effects, home } = await fixture(t, { waitHealthy: async () => { if (++checks === 2) throw new Error('fixture health timeout'); } }); - const first = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - await settled(connector, first.operationId); - const second = await connector.connectProject({ path: 'second', requestId: randomUUID() }); - const result = await settled(connector, second.operationId); - assert.equal(result.status, 'failed'); - assert.equal(result.rollbackRecovered, true); - assert.deepEqual(effects[2].services['open-design'].volumes.map(x => x.source), [path.join(home, 'project')]); - assert.equal(connector.isConnected(path.join(home, 'second')), false); -}); - -test('rejects command/container/image injection and request identity reuse for another path', async t => { - const { connector } = await fixture(t); - for (const extra of [{ container: 'other' }, { image: 'evil' }, { command: 'rm' }]) { - await assert.rejects(connector.connectProject({ path: 'project', requestId: randomUUID(), ...extra }), /Unexpected/); - } - const requestId = randomUUID(); - const first = await connector.connectProject({ path: 'project', requestId }); - await settled(connector, first.operationId); - await assert.rejects(connector.connectProject({ path: 'second', requestId }), /different path/); -}); - -test('rechecks selected symlink after run inspection and before mount side effect', async t => { - let fixtureHome, fixtureOutside; - const { connector, home, effects, outside } = await fixture(t, { inspectRuns: async () => { - await unlink(path.join(fixtureHome, 'project')); - await symlink(fixtureOutside, path.join(fixtureHome, 'project')); - return { runs: [] }; - } }); - fixtureHome = home; fixtureOutside = outside; - await rm(path.join(home, 'project'), { recursive: true }); - await symlink(path.join(home, 'second'), path.join(home, 'project')); - const first = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - assert.equal((await settled(connector, first.operationId)).status, 'failed'); - assert.equal(effects.length, 0); -}); - -test('fresh request to a connected root does not restart or erase another operation', async t => { - const { connector, effects } = await fixture(t); - const first = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - await settled(connector, first.operationId); - const reopened = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - assert.equal(reopened.status, 'ready'); - assert.equal(connector.getOperation(first.operationId).status, 'ready'); - assert.equal(effects.length, 1); -}); - -test('late canonical-root swap during recreation never becomes ready and rolls back', async t => { - let fixtureHome, fixtureOutside; - let calls = 0; - const { connector, home, outside } = await fixture(t, { recreate: async () => { - if (++calls === 1) { - await rename(path.join(fixtureHome, 'project'), path.join(fixtureHome, 'moved')); - await symlink(fixtureOutside, path.join(fixtureHome, 'project')); - } - } }); - fixtureHome = home; fixtureOutside = outside; - const operation = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - const result = await settled(connector, operation.operationId); - assert.equal(result.status, 'failed'); - assert.equal(result.rollbackRecovered, true); - assert.equal(connector.isConnected(path.join(home, 'project')), false); - assert.equal(calls, 2); -}); - -test('a connected root covers its canonical descendants without new mounts', async t => { - const { connector, config, home, effects, deps } = await fixture(t); - await mkdir(path.join(home, 'project', 'nested'), { recursive: true }); - const root = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - assert.equal((await settled(connector, root.operationId)).status, 'ready'); - assert.equal(effects.length, 1); - const descendant = await connector.connectProject({ path: 'project/nested', requestId: randomUUID() }); - assert.equal(descendant.status, 'ready'); - assert.equal(effects.length, 1); - assert.equal(connector.isConnected(path.join(home, 'project', 'nested')), true); - const resumed = await createConnector({ config, ...deps }); - assert.equal(resumed.isConnected(path.join(home, 'project', 'nested')), true); -}); - -test('a sibling sharing a path-name prefix is not covered and still recreates', async t => { - const { connector, home, effects } = await fixture(t); - await mkdir(path.join(home, 'project-old'), { recursive: true }); - const root = await connector.connectProject({ path: 'project', requestId: randomUUID() }); - assert.equal((await settled(connector, root.operationId)).status, 'ready'); - assert.equal(connector.isConnected(path.join(home, 'project-old')), false); - const sibling = await connector.connectProject({ path: 'project-old', requestId: randomUUID() }); - assert.equal(sibling.status, 'pending'); - assert.equal((await settled(connector, sibling.operationId)).status, 'ready'); - assert.equal(connector.isConnected(path.join(home, 'project-old')), true); - assert.equal(effects.length, 2); -}); diff --git a/integrations/opendesign/remote-workspace/tests/gateway.test.mjs b/integrations/opendesign/remote-workspace/tests/gateway.test.mjs deleted file mode 100644 index 845def8..0000000 --- a/integrations/opendesign/remote-workspace/tests/gateway.test.mjs +++ /dev/null @@ -1,134 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import http from 'node:http'; -import net from 'node:net'; -import { Readable } from 'node:stream'; -import { createGateway } from '../gateway.mjs'; - -const origin = 'https://dgx.example.ts.net:7456'; -const remoteHost = 'dgx.example.ts.net:7456'; -const labs = '/api/strategies/od-next/rollout'; - -// Node fetch overrides Host; use real HTTP requests like the tailnet proxy. -function fetch(url, { method = 'GET', headers = {} } = {}) { - return new Promise((resolve, reject) => { - const req = http.request(url, { method, headers }, res => { - resolve(new Response(Readable.toWeb(res), { status: res.statusCode, headers: res.headers })); - }); - req.on('error', reject); - req.end(); - }); -} - -async function listen(server, t) { - await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); - t.after(() => { server.closeAllConnections(); server.close(); }); - return `http://127.0.0.1:${server.address().port}`; -} - -async function fixture(t) { - let calls = 0; - const upstream = await listen(http.createServer((req, res) => { - calls++; - res.setHeader('content-type', 'application/json'); - res.end(JSON.stringify({ host: req.headers.host, origin: req.headers.origin ?? null, authorization: req.headers.authorization ?? null, url: req.url, method: req.method })); - }), t); - const gateway = await listen(createGateway({ config: { origin }, upstream }), t); - return { gateway, upstream, calls: () => calls }; -} - -test('only exact read-only Labs request gets loopback Host and validated Origin removal', async t => { - const { gateway, upstream } = await fixture(t); - const response = await fetch(gateway + labs, { headers: { host: remoteHost, origin, authorization: 'Bearer fixture-only' } }); - assert.equal(response.status, 200); - assert.deepEqual(await response.json(), { host: new URL(upstream).host, origin: null, authorization: 'Bearer fixture-only', url: labs, method: 'GET' }); -}); - -for (const [method, path] of [['POST', labs], ['GET', labs + '?x=1'], ['GET', labs + '/'], ['PUT', '/api/app-config'], ['GET', '/api/health']]) { - test(`does not grant local authority to ${method} ${path}`, async t => { - const { gateway } = await fixture(t); - const result = await fetch(gateway + path, { method, headers: { host: remoteHost, origin } }); - assert.equal(result.status, 200); - const body = await result.json(); - assert.equal(body.host, remoteHost); - assert.equal(body.origin, origin); - }); -} - -test('rejects unapproved host/origin before upstream and ignores forged forwarding authority', async t => { - const { gateway, calls } = await fixture(t); - for (const headers of [ - { host: 'evil.example', 'x-forwarded-host': remoteHost }, - { host: remoteHost, origin: 'https://evil.example' }, - { host: remoteHost, 'sec-fetch-site': 'cross-site' }, - ]) assert.equal((await fetch(gateway + labs, { headers })).status, 403); - assert.equal(calls(), 0); -}); - -test('unavailable daemon returns a JSON 502 rather than hanging', async t => { - const gateway = await listen(createGateway({ config: { origin }, upstream: 'http://127.0.0.1:1' }), t); - const result = await fetch(gateway + '/api/health', { headers: { host: remoteHost } }); - assert.equal(result.status, 502); - assert.equal((await result.json()).error.code, 'UPSTREAM_UNAVAILABLE'); -}); - -test('internal daemon connection check requires separate token, never public request authority', async t => { - const token = 'fixture-daemon-token-not-a-real-secret'; - const workspaceService = { - async handleInternal(_req, res) { res.end('internal fixture'); }, - async handle() { return false; }, - }; - const gateway = await listen(createGateway({ config: { origin, daemonToken: token }, workspaceService, upstream: 'http://127.0.0.1:1' }), t); - const internal = '/api/remote-workspace/internal/connected?path=/home/fixture/project'; - assert.equal((await fetch(gateway + internal, { headers: { host: remoteHost } })).status, 403); - const allowed = await fetch(gateway + internal, { headers: { authorization: `Bearer ${token}` } }); - assert.equal(allowed.status, 200); - assert.equal(await allowed.text(), 'internal fixture'); - assert.equal((await fetch(gateway + internal, { method: 'POST', headers: { authorization: `Bearer ${token}` } })).status, 403); - assert.equal((await fetch(gateway + internal, { headers: { host: remoteHost, authorization: `Bearer ${token}` } })).status, 403); - assert.equal((await fetch(gateway + internal, { headers: { origin, authorization: `Bearer ${token}` } })).status, 403); -}); - -test('streams SSE data before upstream finishes', async t => { - let finish; - const upstream = await listen(http.createServer((_req, res) => { - res.setHeader('content-type', 'text/event-stream'); - res.write('data: first\n\n'); - finish = () => res.end('data: last\n\n'); - }), t); - const gateway = await listen(createGateway({ config: { origin }, upstream }), t); - const result = await fetch(gateway + '/api/events', { headers: { host: remoteHost } }); - const reader = result.body.getReader(); - assert.equal(new TextDecoder().decode((await reader.read()).value), 'data: first\n\n'); - finish(); - assert.equal(new TextDecoder().decode((await reader.read()).value), 'data: last\n\n'); - await reader.cancel(); -}); - -test('forwards websocket upgrade and bytes without changing remote authority', async t => { - const server = http.createServer(); - const sockets = new Set(); - t.after(() => { for (const socket of sockets) socket.destroy(); }); - server.on('upgrade', (req, socket, head) => { - assert.equal(req.headers.host, remoteHost); - sockets.add(socket); - socket.write('HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n'); - if (head.length) socket.write(head); - socket.on('data', chunk => socket.write(chunk)); - }); - const upstream = await listen(server, t); - const gateway = await listen(createGateway({ config: { origin }, upstream }), t); - await new Promise((resolve, reject) => { - const socket = net.connect(new URL(gateway).port, '127.0.0.1'); - t.after(() => socket.destroy()); - socket.setTimeout(3000, () => reject(new Error('upgrade timeout'))); - socket.on('error', reject); - let text = ''; - socket.on('data', chunk => { - text += chunk; - if (text.includes('fixture-ping')) { socket.destroy(); resolve(); } - else if (text.includes('\r\n\r\n')) socket.write('fixture-ping'); - }); - socket.on('connect', () => socket.write(`GET /socket HTTP/1.1\r\nHost: ${remoteHost}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n`)); - }); -}); diff --git a/integrations/opendesign/remote-workspace/tests/request-policy.test.mjs b/integrations/opendesign/remote-workspace/tests/request-policy.test.mjs deleted file mode 100644 index fbca6c0..0000000 --- a/integrations/opendesign/remote-workspace/tests/request-policy.test.mjs +++ /dev/null @@ -1,26 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import { validateRemoteRequest } from '../request-policy.mjs'; - -const config = { origin: 'https://dgx.example.ts.net:7456' }; -const valid = { peer: '127.0.0.1', host: 'dgx.example.ts.net:7456' }; - -test('tailnet gateway accepts same-origin browser requests and loopback peers', () => { - assert.equal(validateRemoteRequest(valid, config).ok, true); - assert.equal(validateRemoteRequest({ ...valid, peer: '::ffff:127.0.0.1', origin: config.origin, fetchSite: 'same-origin' }, config).ok, true); -}); - -for (const [name, changed] of [ - ['non-loopback peer', { peer: '100.1.2.3' }], - ['other host', { host: 'evil.example:7456' }], - ['host userinfo', { host: 'x@dgx.example.ts.net:7456' }], - ['other origin', { origin: 'https://evil.example' }], - ['null origin', { origin: 'null' }], - ['cross-site browser', { fetchSite: 'cross-site' }], - ['same-site but not same-origin browser', { fetchSite: 'same-site' }], -]) test(`rejects ${name}`, () => assert.equal(validateRemoteRequest({ ...valid, ...changed }, config).ok, false)); - -test('malformed or non-HTTPS deployment origin fails closed', () => { - assert.equal(validateRemoteRequest(valid, { origin: 'not a URL' }).ok, false); - assert.equal(validateRemoteRequest(valid, { origin: 'http://dgx.example.ts.net:7456' }).ok, false); -}); diff --git a/integrations/opendesign/remote-workspace/tests/seccomp-policy.test.mjs b/integrations/opendesign/remote-workspace/tests/seccomp-policy.test.mjs deleted file mode 100644 index d775730..0000000 --- a/integrations/opendesign/remote-workspace/tests/seccomp-policy.test.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import { createCodexSeccompProfile } from '../seccomp-policy.mjs'; - -test('preserves default-deny and every upstream rule; adds only nested namespace/mount rules', () => { - const original = { defaultAction: 'SCMP_ACT_ERRNO', defaultErrnoRet: 1, archMap: [{ architecture: 'SCMP_ARCH_AARCH64', subArchitectures: ['SCMP_ARCH_ARM'] }], syscalls: [{ names: ['read'], action: 'SCMP_ACT_ALLOW', includes: { caps: ['CAP_AUDIT_READ'] } }] }; - const profile = createCodexSeccompProfile(original); - assert.equal(profile.defaultAction, 'SCMP_ACT_ERRNO'); - assert.equal(profile.defaultErrnoRet, 1); - assert.deepEqual(profile.archMap, original.archMap); - assert.deepEqual(profile.syscalls[0], original.syscalls[0]); - assert.equal(original.syscalls.length, 1); - assert.deepEqual(profile.syscalls.flatMap(r => r.names), ['read', 'clone', 'unshare', 'mount', 'umount2', 'pivot_root']); - const clone = profile.syscalls[1]; - assert.equal(clone.args[0].value, 0x10000000); - assert.equal(clone.args[0].valueTwo, 0x10000000); - assert.equal(clone.args[0].op, 'SCMP_CMP_MASKED_EQ'); - assert.deepEqual(clone.excludes.arches, ['s390', 's390x']); - assert.equal(profile.syscalls[2].args[0].value, 0x81fdffff); - assert.equal(profile.syscalls[2].args[0].valueTwo, 0); -}); -test('refuses an allow-all base or unsupported architecture', () => { - assert.throws(() => createCodexSeccompProfile({ defaultAction: 'SCMP_ACT_ALLOW', syscalls: [] })); - assert.throws(() => createCodexSeccompProfile({ defaultAction: 'SCMP_ACT_ERRNO', syscalls: [] }, 's390x')); -}); diff --git a/integrations/opendesign/remote-workspace/tests/service.test.mjs b/integrations/opendesign/remote-workspace/tests/service.test.mjs deleted file mode 100644 index e06f1df..0000000 --- a/integrations/opendesign/remote-workspace/tests/service.test.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import http from 'node:http'; -import { createService } from '../service.mjs'; - -test('service can start Labs-only gateway with workspace capabilities disabled', async t => { - const upstream = http.createServer((_req, res) => res.end('{"status":{"effectiveMode":"active"}}')); - await new Promise(r => upstream.listen(0, '127.0.0.1', r)); - t.after(() => { upstream.closeAllConnections(); upstream.close(); }); - const server = await createService({ origin: 'https://dgx.example.ts.net:7456', upstream: `http://127.0.0.1:${upstream.address().port}`, port: 17456, workspaceEnabled: false }); - await new Promise(r => server.listen(0, '127.0.0.1', r)); - t.after(() => { server.closeAllConnections(); server.close(); }); - const result = await new Promise((resolve, reject) => { - const req = http.get(`http://127.0.0.1:${server.address().port}/api/strategies/od-next/rollout`, { headers: { host: 'dgx.example.ts.net:7456' } }, res => { - let body = ''; res.on('data', c => { body += c; }); res.on('end', () => resolve({ status: res.statusCode, body })); - }); req.on('error', reject); - }); - assert.equal(result.status, 200); - assert.equal(JSON.parse(result.body).status.effectiveMode, 'active'); -}); - -test('invalid origin/upstream/port or missing daemon token refuses service startup', async () => { - const base = { origin: 'https://dgx.example.ts.net:7456', upstream: 'http://127.0.0.1:7456', port: 17456, workspaceEnabled: false }; - for (const change of [{ origin: 'http://evil' }, { upstream: 'http://evil:7456' }, { port: 7456 }, { workspaceEnabled: true }]) { - await assert.rejects(createService({ ...base, ...change })); - } -}); diff --git a/integrations/opendesign/remote-workspace/tests/workspace-service.test.mjs b/integrations/opendesign/remote-workspace/tests/workspace-service.test.mjs deleted file mode 100644 index 5ffad01..0000000 --- a/integrations/opendesign/remote-workspace/tests/workspace-service.test.mjs +++ /dev/null @@ -1,85 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import { mkdtemp, mkdir, symlink, writeFile, rm, realpath } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { resolveWorkspacePath } from '../path-policy.mjs'; -import { createWorkspaceService } from '../workspace-service.mjs'; -import { createGateway } from '../gateway.mjs'; -import http from 'node:http'; - -function request(url, method, body) { - return new Promise((resolve, reject) => { - const req = http.request(url, { method, headers: { host: 'dgx.example.ts.net:7456', 'content-type': 'application/json' } }, res => { - let text = ''; res.on('data', c => { text += c; }); - res.on('end', () => resolve({ status: res.statusCode, body: JSON.parse(text) })); - }); - req.on('error', reject); req.end(body === undefined ? undefined : JSON.stringify(body)); - }); -} - -async function fixture(t) { - // Canonicalize for the same reason as connector.test.mjs: production - // answers are realpath-resolved, so expectations derived from the base - // must start from the canonical base. - const base = await realpath(await mkdtemp(path.join(os.tmpdir(), 'remote-path-'))); - const home = path.join(base, 'home'); - t.after(() => rm(base, { recursive: true, force: true })); - for (const dir of ['Desktop/Project A', 'Other', '.hidden-project', '.ssh']) await mkdir(path.join(home, dir), { recursive: true }); - await symlink(path.join(home, 'Other'), path.join(home, 'alias')); - await symlink(base, path.join(home, 'escape')); - await writeFile(path.join(home, 'not-a-directory'), 'do not show contents'); - const service = createWorkspaceService({ config: { home }, connector: {} }); - return { home, base, service }; -} - -test('lists directories, hidden toggle and symlink reasons without file contents', async t => { - const { service, home } = await fixture(t); - const listing = await service.listDirectories({ path: '' }); - assert.equal(listing.path, home); - assert.deepEqual(listing.entries.map(x => x.name), ['alias', 'Desktop', 'escape', 'Other']); - assert.equal(listing.entries.find(x => x.name === 'alias').selectable, true); - assert.equal(listing.entries.find(x => x.name === 'escape').selectable, false); - assert.equal((await service.listDirectories({ path: '', showHidden: true })).entries.find(x => x.name === '.ssh').selectable, false); - assert.equal((await service.listDirectories({ path: '', showHidden: true })).entries.find(x => x.name === '.hidden-project').selectable, true); -}); - -test('canonicalizes home-relative, absolute and internal symlink paths', async t => { - const { home } = await fixture(t); - assert.equal(await resolveWorkspacePath('Desktop/Project A', { home, forSelection: true }), path.join(home, 'Desktop/Project A')); - assert.equal(await resolveWorkspacePath(path.join(home, 'Other'), { home, forSelection: true }), path.join(home, 'Other')); - assert.equal(await resolveWorkspacePath('alias', { home, forSelection: true }), path.join(home, 'Other')); -}); - -test('refuses home workspace, credential roots, outside paths, links and missing/file targets', async t => { - const { home, base } = await fixture(t); - for (const input of ['', '.ssh', base, '../', 'escape', 'missing', 'not-a-directory']) { - await assert.rejects(resolveWorkspacePath(input, { home, forSelection: true })); - } - assert.equal(await resolveWorkspacePath('', { home, forSelection: false }), home); -}); - -test('gateway directory API exposes only metadata and refuses unknown connection fields', async t => { - const { home } = await fixture(t); - const { createConnector } = await import('../connector.mjs'); - const connector = await createConnector({ config: { home, stateDir: path.join(home, 'state'), service: 'open-design' }, inspectRuns: async () => ({ runs: [] }), recreate: async () => {}, waitHealthy: async () => {} }); - const gateway = createGateway({ config: { origin: 'https://dgx.example.ts.net:7456' }, workspaceService: createWorkspaceService({ config: { home }, connector }), upstream: 'http://127.0.0.1:1' }); - await new Promise(r => gateway.listen(0, '127.0.0.1', r)); - t.after(() => { gateway.closeAllConnections(); gateway.close(); }); - const base = `http://127.0.0.1:${gateway.address().port}/api/remote-workspace`; - const directories = await request(base + '/directories', 'GET'); - assert.equal(directories.status, 200); - assert.equal(JSON.stringify(directories.body).includes('do not show contents'), false); - const rejected = await request(base + '/connections', 'POST', { path: 'Other', requestId: '00000000-0000-4000-8000-000000000001', command: 'evil' }); - assert.equal(rejected.status, 400); - const accepted = await request(base + '/connections', 'POST', { path: 'Other', requestId: '00000000-0000-4000-8000-000000000002' }); - assert.equal(accepted.status, 202); - assert.equal(typeof accepted.body.operationId, 'string'); - // Let the operation finish before its fixture directory is removed. - for (let i = 0; i < 100; i++) { - const op = await request(base + '/operations/' + accepted.body.operationId, 'GET'); - if (op.body.status === 'ready') return; - await new Promise(r => setTimeout(r, 2)); - } - assert.fail('connection did not become ready'); -}); diff --git a/integrations/opendesign/remote-workspace/workspace-service.mjs b/integrations/opendesign/remote-workspace/workspace-service.mjs deleted file mode 100644 index 1db0892..0000000 --- a/integrations/opendesign/remote-workspace/workspace-service.mjs +++ /dev/null @@ -1,64 +0,0 @@ -import path from 'node:path'; -import { readdir } from 'node:fs/promises'; -import { resolveWorkspacePath, workspaceError } from './path-policy.mjs'; -import { sendJson } from './gateway.mjs'; - -async function jsonBody(req) { - let text = ''; - for await (const chunk of req) { - text += chunk; - if (Buffer.byteLength(text) > 8192) throw workspaceError('Request body too large', 413); - } - try { return JSON.parse(text); } catch { throw workspaceError('Invalid JSON'); } -} - -export function createWorkspaceService({ config, connector }) { - async function listDirectories({ path: input = '', showHidden = false }) { - const canonical = await resolveWorkspacePath(input, { home: config.home }); - const children = await readdir(canonical, { withFileTypes: true }); - const entries = []; - for (const child of children) { - if ((!child.isDirectory() && !child.isSymbolicLink()) || (!showHidden && child.name.startsWith('.'))) continue; - const selected = path.join(canonical, child.name); - let browsable = true, selectable = true, reason = null; - try { await resolveWorkspacePath(selected, { home: config.home }); } - catch (error) { browsable = false; selectable = false; reason = error.message; } - if (browsable) { - try { await resolveWorkspacePath(selected, { home: config.home, forSelection: true }); } - catch (error) { selectable = false; reason = error.message; } - } - entries.push({ name: child.name, path: selected, browsable, selectable, reason }); - } - entries.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })); - let selectable = false; - try { await resolveWorkspacePath(canonical, { home: config.home, forSelection: true }); selectable = true; } catch {} - return { home: config.home, path: canonical, selectable, entries }; - } - return { - listDirectories, - async handleInternal(req, res) { - const url = new URL(req.url, 'http://localhost'); - if (url.pathname === '/api/remote-workspace/internal/connected') { - const canonical = await resolveWorkspacePath(url.searchParams.get('path') ?? '', { home: config.home, forSelection: true }); - sendJson(res, 200, { path: canonical, connected: connector.isConnected(canonical), connecting: connector.isConnecting() }); - } else if (url.pathname === '/api/remote-workspace/internal/status') { - sendJson(res, 200, { connecting: connector.isConnecting() }); - } else sendJson(res, 404, { error: { code: 'NOT_FOUND', message: 'Unknown internal status request' } }); - }, - async handle(req, res) { - const url = new URL(req.url, 'http://localhost'); - if (!url.pathname.startsWith('/api/remote-workspace/')) return false; - if (req.method === 'GET' && url.pathname === '/api/remote-workspace/capabilities') { - sendJson(res, 200, { enabled: true, home: config.home, desktop: path.join(config.home, 'Desktop') }); - } else if (req.method === 'GET' && url.pathname === '/api/remote-workspace/directories') { - sendJson(res, 200, await listDirectories({ path: url.searchParams.get('path') ?? '', showHidden: url.searchParams.get('showHidden') === 'true' })); - } else if (req.method === 'POST' && url.pathname === '/api/remote-workspace/connections') { - if (!/^application\/json(?:;|$)/i.test(req.headers['content-type'] ?? '')) throw workspaceError('JSON content type required', 415); - sendJson(res, 202, await connector.connectProject(await jsonBody(req))); - } else if (req.method === 'GET' && /^\/api\/remote-workspace\/operations\/[0-9a-f-]+$/.test(url.pathname)) { - sendJson(res, 200, connector.getOperation(url.pathname.split('/').at(-1))); - } else sendJson(res, 404, { error: { code: 'NOT_FOUND', message: 'Unknown remote workspace request' } }); - return true; - }, - }; -} diff --git a/integrations/opendesign/sync-opencode-credentials.sh b/integrations/opendesign/sync-opencode-credentials.sh deleted file mode 100644 index f0acc74..0000000 --- a/integrations/opendesign/sync-opencode-credentials.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -# -# Sync the host OpenCode API-key credentials into the OpenDesign container's -# OpenCode database. -# -# Why this exists: OpenCode 2.x stores credentials in its SQLite database -# (`credential` table), not in `auth.json`. The container has its own database, -# so it does not inherit the host's providers even with `auth.json` mounted. -# -# Only rows labelled `API key` are copied. OAuth credentials are deliberately -# excluded: an OAuth refresh token that rotates would be invalidated for one of -# the two stores, breaking the host's own authentication. -# -# Re-run this whenever host credentials change. -# -# Usage: bash sync-opencode-credentials.sh -set -euo pipefail - -HOST_DB="${HOME}/.local/share/opencode/opencode.db" -CONTAINER="${OPEN_DESIGN_CONTAINER:-open-design}" -CONTAINER_DB="/home/open-design/.local/share/opencode/opencode.db" -TMP_SQL="$(mktemp "${TMPDIR:-/tmp}/opencode-credentials.XXXXXX.sql")" -trap 'rm -f "$TMP_SQL"' EXIT - -if [ ! -f "$HOST_DB" ]; then - echo "host OpenCode database not found: $HOST_DB" >&2 - exit 1 -fi - -{ - echo "DELETE FROM credential WHERE label = 'API key';" - sqlite3 "file:${HOST_DB}?mode=ro" ".mode insert credential" \ - "SELECT * FROM credential WHERE label = 'API key';" -} > "$TMP_SQL" - -if ! grep -q '^INSERT INTO' "$TMP_SQL"; then - echo "no API-key credentials found in the host database" >&2 - exit 1 -fi - -echo "syncing $(grep -c '^INSERT INTO' "$TMP_SQL") API-key credential(s) into ${CONTAINER}" - -# The container's OpenCode must not hold the database while it is written. -# Stream SQL directly to stdin: never persist secret-bearing SQL in the container. -# The host mktemp file is private and the EXIT trap removes it even if apply fails. -docker exec -u 1000 -e HOME=/home/open-design "$CONTAINER" sh -c 'opencode service stop >/dev/null 2>&1 || true' -docker exec -i -u 1000 -e HOME=/home/open-design "$CONTAINER" sh -c " - cd /app/apps/daemon && node -e \" -const fs = require('node:fs'); -const Database = require('better-sqlite3'); -const db = new Database('${CONTAINER_DB}'); -db.exec('BEGIN'); -db.exec(fs.readFileSync(0, 'utf8')); -db.exec('COMMIT'); -console.log('credential rows now:', db.prepare('SELECT count(*) AS c FROM credential').get().c); -\"" < "$TMP_SQL" - -echo "result:" -docker exec -u 1000 -e HOME=/home/open-design "$CONTAINER" sh -c 'opencode auth list 2>&1 | head -10' diff --git a/opencode.jsonc.example b/opencode.jsonc.example index b7ed993..6d82cdc 100644 --- a/opencode.jsonc.example +++ b/opencode.jsonc.example @@ -1,37 +1,9 @@ { "$schema": "https://opencode.ai/config.json", - // KSI's standalone installer writes two custom native agents/*.md files - // next to this configuration file. Do not register KSI in plugins. - // Built-in Build, Plan, and Explore keep their original system prompts. - // Preserve your existing model, variant, positive steps, permissions, - // provider, MCP, and unrelated settings when adopting KSI. + // Merge this one field into the configuration of the server you actually use. + // Keep native prompts, permissions, models, variants and other settings. + // This enables primary AND child use; it does not force automatic delegation. "agents": { - // Optional explicit adoption for the design MCP connection named opendesign. - // Plan may inspect designs, but cannot generate, change, delete or cancel. - // Last match wins: unknown future tools remain denied. Rename the prefix - // if your MCP server has a different name. The installer does not apply this. - "plan": { - "permissions": [ - { "action": "opendesign_*", "resource": "*", "effect": "deny" }, - { "action": "opendesign_get_active_context", "resource": "*", "effect": "allow" }, - { "action": "opendesign_get_project", "resource": "*", "effect": "allow" }, - { "action": "opendesign_list_projects", "resource": "*", "effect": "allow" }, - { "action": "opendesign_list_files", "resource": "*", "effect": "allow" }, - { "action": "opendesign_get_file", "resource": "*", "effect": "allow" }, - { "action": "opendesign_get_artifact", "resource": "*", "effect": "allow" }, - { "action": "opendesign_search_files", "resource": "*", "effect": "allow" }, - { "action": "opendesign_list_skills", "resource": "*", "effect": "allow" }, - { "action": "opendesign_list_plugins", "resource": "*", "effect": "allow" }, - { "action": "opendesign_list_agents", "resource": "*", "effect": "allow" }, - { "action": "opendesign_get_run", "resource": "*", "effect": "allow" } - ] - }, - "developer": { - "model": "provider/model#variant", - "steps": 80 - } - }, - "plugins": [ - // Optional, independent user-owned plugins may remain here. - ] + "plan": { "mode": "all" } + } } diff --git a/package.json b/package.json index fb91d11..002af95 100644 --- a/package.json +++ b/package.json @@ -1,50 +1,19 @@ { "name": "ksi-opencode-harness", - "version": "0.5.0-beta.9", - "description": "Installer for two optional native OpenCode V2 agents", + "description": "Minimal product-completion principles for an existing OpenCode environment", + "private": true, "type": "module", - "bin": { - "ksi-opencode": "./bin/ksi-opencode.mjs" - }, "repository": { "type": "git", "url": "https://github.com/khakisketch/KSI-Opencode-Harness" }, - "files": [ - "bin/ksi-opencode.mjs", - "src/native-bundle.mjs", - "src/native-roles.mjs", - "templates/agents/developer.md", - "templates/agents/reviewer.md", - "README.md", - "INSTALL.md", - "docs/architecture.md", - "docs/integrations/opendesign.md", - "docs/execution.md", - "docs/troubleshooting.md", - "docs/verification.md", - "docs/walkthrough.md", - "docs/assets", - "examples/project-AGENTS.md", - "examples/autonomous-development.md", - "opencode.jsonc.example", - "LICENSE" - ], "scripts": { - "test": "node --test", - "check": "node --check bin/ksi-opencode.mjs && node --check src/native-bundle.mjs && node --check src/native-roles.mjs && node --test", - "check:package": "node scripts/check-package.mjs", - "release:check": "node scripts/release-check.mjs" - }, - "engines": { - "node": ">=20" + "check": "node --test" }, "license": "MIT", "keywords": [ "opencode", - "agent", - "harness", - "routing", - "opendesign" + "product-development", + "documentation" ] } diff --git a/plugins/design-notifier/README.md b/plugins/design-notifier/README.md deleted file mode 100644 index 4f5cee9..0000000 --- a/plugins/design-notifier/README.md +++ /dev/null @@ -1,171 +0,0 @@ -# design-notifier (OpenCode V2 plugin) - -Delivers OpenDesign run completion to the OpenCode session that asked for the -run, so the user does not have to poll or ask again. The notification is -advisory: the receiving agent verifies the run and reports to the user; the -plugin never claims success, never retries generation and never implements. - -## What it does - -- Records `{requestId → sessionID}` when a session calls - `opendesign_start_run`, before the response can be lost. -- Attaches the returned run id (`execute.after`) and re-keys the record to - `run:@`. -- Polls the local OpenDesign daemon (`GET /api/runs/:id`, default 60 s) through - an **elected poller**: every location instance captures its own sessions' - runs, but a state-lock lease (`leader.json`) elects one instance to poll and - deliver, so the daemon is not queried per location. Overlapping ticks during - a slow-tick lease handover are possible and are fenced by the delivery claim. -- **Wake permission is per session.** An unpaused session receives the - completion with automatic wake (`resume:true`, `delivery:queue`). A paused - session is not contacted at all: the terminal result is stored as `held` in - notifier state. After `resume`, it becomes eligible for auto wake on poll ticks - once existing delivery retry delays and any remaining pause switches permit it. -- Recovers across restarts: state lives on disk; a lost `start_run` response is - reconciled by `clientRequestId` (`GET /api/runs?projectId=`) for up to 24 h — - it never starts a replacement run. -- Failure modes: `deliverableValid:false` output is reported as unverified, a - deleted session is orphaned (never delivered to another session), a vanished - run is marked `missing`, and duplicates are prevented by a delivery claim - under the state lock plus a deterministic `msg_...` id (OpenCode's synthetic - endpoint returns the original admission for a repeated id — verified - 2026-10-03). -- Any verified UI output points the receiving agent at design-owned rendered - review/refinement, regardless of the canonical artifact verdict. Small visual - fixes are design-owned too; OpenCode coordinates and checks evidence/functionality. - Report-only/intermediate stages are not UI output or authority for a new run. - The advice pins the OD-usage loop: observable acceptance criteria and verified - input access before commissioning, a required review run (review skill distinct - from the generator) fed with actual rendered states (target viewports plus - empty/error/loading) and confirmed to have read them, and a concrete defect list - (what/where/user impact/desired result) tracked fixed/open — never a good/bad - verdict. Record the skill selected per stage. For an existing-screen redesign or - vague dissatisfaction report, run a read-only design audit first and use its - defect list as the redesign's problem statement; for a user-delegated redesign, the design workspace resolves the direction - from the design system and brief and proceeds; 2-3 lightweight direction options - are commissioned only when the user asks or the brand identity is in question. Metadata `designQuality: "not_assessed"` is advisory, never a computed - quality pass. No-improvement rounds mean quality unmet; no fixed round cap is - imposed. Delivery/classification is unchanged; pinned copies need a verified update. -- Strategy chains are followed: when a terminal run maps a follow-up run - (`strategyTask.terminal === false` + `nextRunId`), that completion is worded - as intermediate ("not the final completion") and the follow-up is watched for - the same session until the chain ends. A mapped run not yet visible on the - daemon stays tracked within a 10-minute grace window instead of being parked - as `missing`. -- Artifact verdict and product completion are separate: source notification - metadata adds `productVerification: required|blocked`. An untouched HTML entry - is not an automatic product failure/success; Build verifies actual source diff, - build and changed flow. The internal-tool visual-system representative review - remains a user gate before rollout. No classification/transport/retry behavior - is changed; an installed pinned copy needs an explicit verified update. - -## Policy - -| Situation | Behavior | -| --- | --- | -| Waiting, session idle | queue delivery + auto wake (`resume:true`) | -| Session busy | `delivery:"queue"` — does not interrupt the current turn | -| Session paused | completion stored as `held`; session untouched until `resume` | -| Resume with held completions | eligible for auto wake on poll ticks after existing retry delays; remaining pause switches still apply (`retryAfterMs` in list) | -| Target session deleted | marked `orphaned`; no cross-session delivery, no new agent | -| Run missing on daemon | marked `missing`; kept for diagnosis, no delivery | -| Plan-mode session | wakes, but the message requires analysis/report only | -| Design output invalid | message forbids claiming a deliverable; no auto retry | - -Pausing is explicit: `design_runs pause` pauses the calling session, -`design_runs pause` with `scope:"all"` pauses every session. The plugin does not -observe the goal plugin's pause state. - -## Files - -- `index.js` — plugin entry (`setup`), tool registration, delivery adapter. -- `lib/notifier.js` — binding capture, leader coordination, polling, delivery - policy, recovery. -- `lib/daemon.js` — read-only daemon client (`/api/runs`). -- `lib/messages.js` — classification and the notification text. -- `lib/state.js` — durable JSON state (atomic writes, lock, leader lease, log - rotation). - -## State - -Default directory: `~/.local/state/opencode-design-notifier/` - -- `bindings.json` — records keyed `req:` or - `run:@`; states: `requested`, `tracking`, `held`, - `delivered`, `orphaned`, `missing`, `unresolved`. `held` records are not - TTL-reaped: they are stored results waiting for wake permission. -- `paused.json` — `{ "ses_...": {at} }` per-session pauses and/or - `{ "*": {at} }` global pause. -- `leader.json` — single-poller lease `{ instanceId, at }` (60 s TTL, renewed - every ≤20 s, released on clean unload). -- `events.log` — bounded JSONL diagnostics; never stores prompts or content. - -Environment overrides: `KSI_DESIGN_NOTIFIER_STATE_DIR`, -`KSI_DESIGN_NOTIFIER_DAEMON_URL`, `KSI_DESIGN_NOTIFIER_POLL_MS`, -`KSI_DESIGN_NOTIFIER_TOOL_PREFIX`. Plugin options: `{ stateDir, daemonUrl, -pollMs, quickPollMs, initialDelayMs, toolNamePrefix, instanceLabel, -renewEveryMs, leaseTtlMs }`. The default state directory honors -`XDG_STATE_HOME` and `HOME`. - -## `design_runs` tool - -- `list` — tracked/held runs, pause state for the calling session, poller - leadership, observed tool names. -- `watch` — bind an existing run to the calling session (recovery path when - the automatic binding was lost; a run+session that was already notified is - not re-notified). -- `pause` / `resume` — wake permission for the calling session - (`scope:"all"` for every session). - -## Install (pinned runtime) - -Completion guidance also distinguishes user-task quality from tool use. Substantive -review needs fresh native evaluation context, verified requested/effective review -instructions and actual image-consumption evidence; screenshot hashes, skill names -and separate run ids do not certify those facts. Normalized daemon events may omit -image calls, so inspect the native trace rather than claiming non-consumption from -missing normalized events. Report render/instruction conflicts and quality unmet; -do not hide them or make the user rediscover routine defects. Small repairs may use -a compact same-run design review/refinement, without calling it independent. -These are advisory completion obligations, not new runtime enforcement. Classification, -delivery, pause/retry behavior and `not_assessed`/`unverified` metadata are unchanged. - -The runtime should not load the live development tree. Copy a verified revision -into the global OpenCode config directory and let discovery load it: - -```sh -node scripts/install-design-notifier.mjs # install/update the pinned copy -node scripts/install-design-notifier.mjs --verify # compare the copy to its manifest -node scripts/install-design-notifier.mjs --uninstall -``` - -The copy lands in `~/.config/opencode/plugins/design-notifier/` with an -`installed.json` manifest (source commit + SHA256 per file). Remove any -`plugins` config entry that points at the development tree so only the pinned -copy loads; the installer refuses to touch a directory that is not this plugin. - -## Limits - -- Same-host only: the OpenCode server process must reach the daemon gateway - (default `http://127.0.0.1:7456`). -- Notifications are pull-based; without a delivery error or remaining pause, - expect up to one poll cycle (default 60 s) after a run finishes or `resume`. - Failed deliveries use exponential retry delays capped at 15 min; pause/resume - preserves that protective delay. `design_runs list` exposes `deliveryAttempts`, - `deliveryNextAttemptAt` and the remaining `retryAfterMs`, not a delivery guarantee. - A capture in a non-leader location is picked up by the leader's next cycle. -- If the OpenCode server is stopped entirely, deliveries happen after it - restarts (state is durable). A crashed leader is replaced after its lease - expires (≤60 s) or immediately on clean unload. -- A pause that lands while a delivery is already in flight takes effect on the - next tick; a post-claim re-check narrows but does not eliminate this window. -- `paused` is the explicit stop switch; goal-pause state and Plan mode - restrictions are not observed (Plan remains restricted natively). -- Automatic binding requires the MCP server tool name to contain the configured - prefix (default `opendesign`). If your server uses a different prefix, set - `toolNamePrefix` or use `design_runs watch`; `design_runs list` shows - `observedTools` to diagnose what the hooks actually see. -- The plugin does not produce approval records; the gated direction check - (new visual direction, client deliverable, out-of-scope) stays a - conversation-level decision, while default-path changes proceed to - implementation and a result report. diff --git a/plugins/design-notifier/index.js b/plugins/design-notifier/index.js deleted file mode 100644 index 4ae8022..0000000 --- a/plugins/design-notifier/index.js +++ /dev/null @@ -1,177 +0,0 @@ -// OpenCode V2 plugin: deliver OpenDesign run completion to the originating -// session. -// -// The plugin never starts, retries or cancels a design run. It records which -// session asked for a run, watches the run on the local OpenDesign daemon, and -// admits one advisory notification into that session when the run reaches a -// terminal state. Result interpretation and any implementation stay with the -// receiving agent and the user. -// -// Load from the global OpenCode config directory -// (`~/.config/opencode/plugins/design-notifier`) or with a plugins entry. -// Install with `node scripts/install-design-notifier.mjs` so the runtime uses -// a pinned copy instead of the live development tree. -// Options: { stateDir, daemonUrl, pollMs, quickPollMs, initialDelayMs, -// toolNamePrefix, instanceLabel, renewEveryMs, leaseTtlMs }. - -import { createDaemon } from "./lib/daemon.js"; -import { createNotifier } from "./lib/notifier.js"; -import { createStore, defaultStateDir } from "./lib/state.js"; - -const PLUGIN_ID = "ksi.design-notifier"; -const PLUGIN_VERSION = "0.2.0"; - -const DESIGN_RUNS_SCHEMA = { - type: "object", - properties: { - action: { - type: "string", - enum: ["list", "watch", "pause", "resume"], - description: - "list: show tracked/held runs, pause state and poller leadership. watch: bind an existing run to the calling session (recovery when the automatic binding was lost). pause/resume: control automatic wake permission — paused completions are stored in notifier state and delivered when resumed.", - }, - scope: { - type: "string", - enum: ["session", "all"], - description: "pause/resume scope: this session (default) or every session.", - }, - runId: { - type: "string", - description: "OpenDesign run id. Required for action=watch.", - }, - }, - required: ["action"], - additionalProperties: false, -}; - -function pickString(...values) { - for (const value of values) { - if (typeof value === "string" && value.trim()) return value.trim(); - } - return null; -} - -function clampInt(value, min, max, fallback) { - const number = Number(value); - if (!Number.isFinite(number)) return fallback; - return Math.min(max, Math.max(min, Math.trunc(number))); -} - -function normalizeSyntheticError(error) { - const status = - error?.status ?? error?.data?.status ?? error?.response?.status ?? error?.cause?.status ?? null; - const wrapped = new Error( - `session synthetic delivery failed${status ? ` (${status})` : ""}: ${error?.message ?? String(error)}`, - ); - if (status) wrapped.status = status; - wrapped.cause = error; - return wrapped; -} - -export default { - id: PLUGIN_ID, - - async setup(ctx) { - const options = ctx?.options && typeof ctx.options === "object" ? ctx.options : {}; - const stateDir = - pickString(options.stateDir, process.env.KSI_DESIGN_NOTIFIER_STATE_DIR) ?? defaultStateDir(); - const daemonUrl = - pickString(options.daemonUrl, process.env.KSI_DESIGN_NOTIFIER_DAEMON_URL) ?? - "http://127.0.0.1:7456"; - const pollMs = clampInt( - options.pollMs ?? process.env.KSI_DESIGN_NOTIFIER_POLL_MS ?? 60_000, - 5_000, - 60 * 60 * 1000, - 60_000, - ); - const quickPollMs = clampInt(options.quickPollMs ?? 4_000, 250, 60_000, 4_000); - const initialDelayMs = clampInt(options.initialDelayMs ?? 1_500, 0, 60_000, 1_500); - const toolNamePrefix = - pickString(options.toolNamePrefix, process.env.KSI_DESIGN_NOTIFIER_TOOL_PREFIX) ?? "opendesign"; - - const store = createStore({ dir: stateDir }); - try { - await store.init(); - } catch (error) { - console.error(`[${PLUGIN_ID}] disabled: cannot use state dir ${stateDir}: ${error?.message ?? error}`); - return () => {}; - } - - const daemon = createDaemon({ baseUrl: daemonUrl }); - const log = (event, fields) => { - void store.log(event, fields); - }; - const deliver = async ({ sessionID, message }) => { - try { - await ctx.session.synthetic({ - sessionID, - id: message.id, - text: message.text, - description: message.description, - metadata: message.metadata, - delivery: message.delivery, - resume: message.resume, - }); - } catch (error) { - throw normalizeSyntheticError(error); - } - }; - - const notifier = createNotifier({ - store, - daemon, - deliver, - pollMs, - quickPollMs, - initialDelayMs, - toolNamePrefix, - instanceLabel: ctx.location?.directory ?? null, - log, - }); - const registrations = []; - try { - registrations.push(await ctx.tool.hook("execute.before", (event) => notifier.handleToolBefore(event))); - registrations.push(await ctx.tool.hook("execute.after", (event) => notifier.handleToolAfter(event))); - registrations.push( - await ctx.tool.transform((editor) => { - editor.add({ - name: "design_runs", - description: - "Inspect and control the KSI design completion notifier: list tracked OpenDesign runs and pause state, watch an existing run so its completion reaches this session, or pause/resume automatic wake deliveries.", - input: DESIGN_RUNS_SCHEMA, - execute: (input, toolContext) => notifier.runTool(input, toolContext), - }); - }), - ); - } catch (error) { - console.error(`[${PLUGIN_ID}] registration failed: ${error?.message ?? error}`); - for (const registration of registrations) { - try { - await registration?.dispose?.(); - } catch {} - } - return () => {}; - } - - const stop = notifier.start(); - log("setup", { - version: ctx.app?.version ?? null, - pluginVersion: PLUGIN_VERSION, - instanceId: String(notifier.instanceId).slice(0, 8), - location: ctx.location?.directory ?? null, - stateDir, - daemonUrl, - pollMs, - }); - - return async () => { - stop(); - await notifier.release(); - for (const registration of registrations) { - try { - await registration?.dispose?.(); - } catch {} - } - }; - }, -}; diff --git a/plugins/design-notifier/lib/daemon.js b/plugins/design-notifier/lib/daemon.js deleted file mode 100644 index 2233dae..0000000 --- a/plugins/design-notifier/lib/daemon.js +++ /dev/null @@ -1,71 +0,0 @@ -// Read-only client for the local OpenDesign daemon HTTP API. -// -// The daemon exposes run status on the host-side gateway (default -// http://127.0.0.1:7456). This module performs reads only: it never starts, -// cancels, resumes or mutates a design run. - -const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; -const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/; - -export const TERMINAL_STATUSES = new Set(["succeeded", "failed", "canceled"]); - -export function isValidRunId(value) { - return typeof value === "string" && (UUID_RE.test(value) || ULID_RE.test(value)); -} - -export function createDaemon({ baseUrl, timeoutMs = 10000, fetchImpl } = {}) { - const base = String(baseUrl || "http://127.0.0.1:7456").replace(/\/+$/, ""); - const doFetch = fetchImpl ?? globalThis.fetch; - - async function getJson(path) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); - try { - const response = await doFetch(`${base}${path}`, { - method: "GET", - signal: controller.signal, - headers: { accept: "application/json" }, - }); - if (!response.ok) { - const error = new Error(`daemon responded ${response.status} for ${path}`); - error.status = response.status; - try { - error.body = String(await response.text()).slice(0, 300); - } catch {} - throw error; - } - return await response.json(); - } finally { - clearTimeout(timer); - } - } - - async function getRun(runId) { - if (!isValidRunId(runId)) throw new Error(`invalid run id: ${String(runId).slice(0, 80)}`); - return getJson(`/api/runs/${encodeURIComponent(runId)}`); - } - - // List runs, optionally scoped to a project. The daemon returns the full - // run records; callers match on `clientRequestId` themselves. - async function listRuns({ projectId } = {}) { - const query = projectId ? `?projectId=${encodeURIComponent(projectId)}` : ""; - const body = await getJson(`/api/runs${query}`); - return Array.isArray(body?.runs) ? body.runs : []; - } - - // Find an existing run for a start request whose response was lost. - // Matches the daemon's stored `clientRequestId`. - async function findByRequestId({ requestId, projectId } = {}) { - if (typeof requestId !== "string" || !requestId) return null; - const runs = await listRuns({ projectId }); - const matches = runs.filter((run) => run && run.clientRequestId === requestId); - if (matches.length > 1) { - matches.sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0)); - } - const match = matches[0]; - if (!match) return null; - return isValidRunId(match.id) ? match : null; - } - - return { baseUrl: base, isValidRunId, getRun, listRuns, findByRequestId }; -} diff --git a/plugins/design-notifier/lib/messages.js b/plugins/design-notifier/lib/messages.js deleted file mode 100644 index 0b059bf..0000000 --- a/plugins/design-notifier/lib/messages.js +++ /dev/null @@ -1,141 +0,0 @@ -// Run classification and the notification message delivered into the -// originating OpenCode session. -// -// The message is deliberately advisory: it tells the receiving agent what -// happened, what to verify, and which decisions still belong to the user. -// It never claims a deliverable succeeded when the daemon verdict is invalid -// or missing, and it never instructs an automatic retry. - -import { createHash } from "node:crypto"; -import { TERMINAL_STATUSES } from "./daemon.js"; - -export function isTerminalStatus(status) { - return TERMINAL_STATUSES.has(status); -} - -export function classifyRun(run) { - const status = run?.status; - if (status === "succeeded") { - return run?.deliverableValid === true ? "valid" : "invalid"; - } - if (status === "failed") return "failed"; - if (status === "canceled") return "canceled"; - return "pending"; -} - -export function messageIdFor({ runId, status, sessionID }) { - const digest = createHash("sha256").update(`${runId}:${status}:${sessionID}`).digest("hex"); - return `msg_${digest.slice(0, 32)}`; -} - -function short(value, fallback = "-") { - if (value === null || value === undefined || value === "") return fallback; - return String(value); -} - -function buildLines(run) { - const lines = []; - lines.push(`Run: ${short(run.id)} | Project: ${short(run.projectId)}`); - const verdict = run.deliverableValid === true ? "valid" : `invalid (${short(run.deliverableValidation, "unreported")})`; - lines.push(`Status: ${short(run.status)} | Deliverable: ${verdict}`); - lines.push(run.status === "succeeded" - ? "Product: verification required — execution and artifact validation are not product completion. An invalid entry verdict alone is not proof of product failure." - : "Product: blocked or pending — inspect execution status and the concrete cause before continuing."); - lines.push("Design quality: not assessed by this notification — neither artifact validation nor a completed run is a quality pass."); - lines.push("Rendered review: unverified — renderer access and the design-owned review remain separate from execution/artifact status; applies when the task produces UI changes."); - const failure = [ - run.errorCode ? `error=${run.errorCode}` : null, - run.failureCategory ? `category=${run.failureCategory}` : null, - run.failureAction ? `action=${run.failureAction}` : null, - run.error ? `message=${String(run.error).slice(0, 200)}` : null, - ].filter(Boolean); - if (failure.length) lines.push(`Failure: ${failure.join(" ")}`); - const flags = [ - `retryable=${run.retryable === true}`, - `resumable=${run.resumable === true}`, - run.artifactCount !== undefined ? `artifacts=${run.artifactCount}` : null, - run.deliverableEntryFile ? `entry=${run.deliverableEntryFile}` : null, - ].filter(Boolean); - lines.push(`Flags: ${flags.join(" ")}`); - if (run?.strategyTask && run.strategyTask.terminal !== true) { - lines.push("Strategy: intermediate stage — the task continues in a follow-up run; this is not the final completion."); - } - return lines; -} - -const VERIFY_STEPS = [ - "1. Verify with OpenDesign tools before reporting: call get_run(runId) for the authoritative previewUrl/studioUrl and the inner agent's final message (agentMessage). Terminal status alone is execution-only.", - "2. Resolve the original task and actual output first. If the deliverable is invalid or the run failed: do not claim a design deliverable merely from the status. Explain the concrete reason, relay a genuine question from agentMessage if present, and do not regenerate, replay or cancel-retry the run blindly. Successful entry_not_touched/no_artifact is not automatically product/task failure or success: source-direct work requires the actual source diff, build and changed-flow browser verification; a report-only result is not verified UI output. Missing required output remains unfinished. Do not blindly regenerate to change the entry verdict. For an intermediate strategy stage, follow the existing chain; do not start another design run just because this stage finished.", - "2a. Establish a render-review path before commissioning visual source changes: a host browser is not proof of inner-agent browser access. Verify either a working renderer in the design execution environment or a handoff of actual product captures whose files the design reviewer can read and visually consume. Record the source revision, viewports/states and an image-tool/image-input evidence receipt; file access, hashes and reviewer prose alone are not visual consumption. Inspect the underlying native transcript when normalized daemon events omit image calls; a missing normalized call alone does not prove images were never seen. Static source review cannot replace rendered design review. If neither path works, the dependent visual stage is blocked; continue independent nonvisual work. Disclose render failures and limitations; missing required render review remains unfinished, not completed UI or an implicit OpenCode visual fix.", - "3. For any verified UI output, regardless of the artifact verdict, run the design-quality loop: the design workspace owns UI analysis, problem discovery, source/style edits, rendered design review and refinement, including small CSS or other visual fixes. Judge representative user tasks, information priority, cognitive burden, mobile/desktop operation and cross-screen consistency while preserving functionality. Give the design workspace the scope, purpose and preservation constraints — not a presolved problem list or fix direction; the concrete problems, priorities and layout decisions come from its own analysis. Define success at the observable purpose level: what the intended user must be able to do or know, and what counts as failure. Verify input access (references, assets, actual source) before commissioning; if an input is unreadable, do not commission — block and report the gap. For substantive changes, commission a design review run in a fresh evaluation context rather than the generator's own approval: verify a new, unseeded native conversation different from the implementation conversation. Different run ids are not proof of independence. Small repairs may use a compact same-run render/review/refine loop, without calling that independent review. Feed the reviewer the actual rendered states (the target viewports, empty/error/loading states, keyboard/focus, accessibility (axe where available) and motion/interaction states) plus source, references and criteria; confirm the review actually read and visually consumed those inputs. When the project keeps a resident product-context.md, use it as the standing brief and keep it current. Require the review output as a concrete defect list: what is wrong, which screen/state, the user impact and the desired result, tracked fixed/open across rounds. Select the task-appropriate OpenDesign skill per stage and record requested versus effective review instructions; skill names alone are not quality evidence. OpenCode owns task contracts, coordination, functional verification and Git, not substitute visual fixes. Check the review's criterion/defect -> revision -> render evidence; do not infer a quality pass from tests or artifact validation. Commission concrete refinements within the authorized scope until the bar is met (no fixed round cap); no measurable improvement means quality unmet, with remaining defects and next options reported. Direct UI edits stay provisional until verified. Then default to finishing the job and report the real product preview with a review bundle: the reachable implemented-product link (labeled tailnet-only/temporary when served locally), before/after screenshots at the target viewports, and the design preview link. Stop and ask for direction only when the work introduces an unapproved new visual direction, is a client-facing deliverable, or goes beyond authorized scope — or when the user asked to review the direction first.", - "3a. Verify instruction delivery, not just selection: a project plugin/scenario binding can replace the primary skill body, even on an explicitly requested review run. Inspect the effective prompt and full review instructions before treating that stage as review. Additional composed instructions or an explicit scenario are only candidates until their actual delivery is verified. If active rules prohibit required rendering/refinement, report the conflict and quality unmet; custom instructions do not override higher-priority rules. Do not silently rebind the project, patch runtime policies, switch models or install a tool to bypass the conflict.", - "4. Diagnosis belongs to the delegated scope: for an existing screen or a vague dissatisfaction report, the design workspace derives its own prioritized problem statement from the actual current screens at the relevant viewports/states (a dedicated read-only design audit stage is a supported way to do this); Build neither authors the defect list nor prescribes fixes, and the user should not have to enumerate the problems. When the user has delegated the redesign, let the design workspace resolve the visual direction from the design system, brief and references and proceed; commission a lightweight direction options run (2-3 distinct direction boards, each with a rendered sample of the critical screen area) only when the user asks for options or the brand identity itself is in question, and continue non-dependent work while a choice is pending.", - "5. If this session is in Plan mode, analyze and report only; do not implement.", - "6. Follow the existing task ledger and review gates: for an internal-tool visual-system pass keeping the existing brand, verify one representative screen and present definition, scope and actual result as a single review. Obtain user approval of that result before wider rollout; then use per-stage checks without per-screen approvals. This representative-result gate is distinct from new-direction/client/out-of-scope/review-first pre-approval.", - "7. Respect explicit session pauses. The current harness has no Goal continuation; do not create or resume a goal because this notification arrived. If a goal is explicitly enabled in another tool, a paused, cancelled or closed goal cannot continue. Ordinary authorized work without a goal is separate; a notification never grants new scope or delivery permission.", - "8. Report to the user in their language, keeping internal runtime identifiers out of product copy except the run reference needed for traceability.", -]; - -export function buildDelivery({ run, sessionID, requestId, source, paused = false }) { - const classified = classifyRun(run); - const status = short(run.status, "unknown"); - const header = "[ksi-design-notifier] OpenDesign run finished — automatic notification, not a user message."; - const text = [ - header, - "", - ...buildLines(run), - `Watch source: ${short(source, "hook")} | Request: ${short(requestId)}`, - "", - "Next steps for you (the receiving agent):", - ...VERIFY_STEPS, - ].join("\n"); - - const metadata = { - "ksi.design-notifier": { - runId: run.id, - projectId: run.projectId ?? null, - status: run.status ?? null, - classification: classified, - productVerification: run.status === "succeeded" ? "required" : "blocked", - designQuality: "not_assessed", - renderInputAccess: "unverified", - renderReview: "unverified", - deliverableValid: run.deliverableValid ?? null, - deliverableValidation: run.deliverableValidation ?? null, - requestId: requestId ?? null, - strategyTerminal: run?.strategyTask ? run.strategyTask.terminal === true : null, - strategyNextRunId: typeof run?.strategyTask?.nextRunId === "string" && run.strategyTask.nextRunId ? run.strategyTask.nextRunId : null, - }, - }; - - return { - id: messageIdFor({ runId: run.id, status: run.status ?? "unknown", sessionID }), - text, - description: `OpenDesign run ${String(run.id).slice(0, 8)} ${status}`, - metadata, - delivery: "queue", - resume: !paused, - classification: classified, - }; -} - -// Compact, log-safe snapshot of a run (whitelisted fields only). -export function summarizeRun(run) { - if (!run || typeof run !== "object") return null; - return { - status: run.status ?? null, - deliverableValid: run.deliverableValid ?? null, - deliverableValidation: run.deliverableValidation ?? null, - deliverableEntryFile: run.deliverableEntryFile ?? null, - failureCategory: run.failureCategory ?? null, - failureAction: run.failureAction ?? null, - errorCode: run.errorCode ?? null, - retryable: run.retryable === true, - resumable: run.resumable === true, - artifactCount: run.artifactCount ?? null, - terminalAt: run.terminalAt ?? null, - updatedAt: run.updatedAt ?? null, - strategyTerminal: run.strategyTask ? run.strategyTask.terminal === true : null, - strategyNextRunId: typeof run.strategyTask?.nextRunId === "string" && run.strategyTask.nextRunId ? run.strategyTask.nextRunId : null, - }; -} diff --git a/plugins/design-notifier/lib/notifier.js b/plugins/design-notifier/lib/notifier.js deleted file mode 100644 index 4a6529d..0000000 --- a/plugins/design-notifier/lib/notifier.js +++ /dev/null @@ -1,723 +0,0 @@ -// Core notifier logic. All side effects (state store, daemon client, session -// delivery, clock, logging) are injected so the same module runs inside the -// OpenCode plugin and under node:test with fakes. -// -// Lifecycle: -// execute.before(opendesign_start_run) → record {requestId, sessionID} -// execute.after(opendesign_start_run) → attach {runId} (or leave for recovery) -// poll tick (leader only) → session completion delivery -// -// Policies: -// active → delivery admitted with auto wake (resume:true, queue) -// paused(session) → completion is stored in notifier state only; the session -// is not contacted. `resume` delivers held completions. -// session missing → orphaned; never deliver elsewhere or spawn an agent -// invalid output → message instructs verification, never claims success -// restart → state is re-read from disk; delivered runs are not resent -// -// Delivery is performed by a single elected poller (a state-lock lease makes -// one plugin instance the poller; overlapping ticks during a slow-tick lease -// handover are possible and are fenced by the delivery claim). Every instance -// still captures its own sessions' start_run calls. - -import { randomUUID } from "node:crypto"; -import { buildDelivery, classifyRun, isTerminalStatus, summarizeRun } from "./messages.js"; -import { reapBindings } from "./state.js"; - -const REQUEST_KEY_PREFIX = "req:"; -const RUN_KEY_PREFIX = "run:"; -const RECOVERY_MIN_AGE_MS = 10 * 1000; -const RECOVERY_RETRY_MS = 5 * 60 * 1000; -const RECOVERY_GIVE_UP_MS = 24 * 60 * 60 * 1000; -const DELIVERY_BASE_BACKOFF_MS = 15 * 1000; -const DELIVERY_MAX_BACKOFF_MS = 15 * 60 * 1000; -const CHAIN_START_GRACE_MS = 10 * 60 * 1000; - -function describeError(error) { - const message = error instanceof Error ? error.message : String(error); - return message.slice(0, 300); -} - -function isSessionMissingError(error) { - const status = error?.status ?? error?.data?.status ?? error?.response?.status ?? error?.cause?.status; - if (status === 404) return true; - const text = String(error?.message ?? error ?? ""); - return /session/i.test(text) && /(not.?found|404|missing|deleted)/i.test(text); -} - -function isStartRunTool(name, prefix = "opendesign") { - if (typeof name !== "string" || !name) return false; - if (!name.toLowerCase().includes(prefix.toLowerCase())) return false; - return /(^|[^a-z0-9])start_run$/i.test(name); -} - -function looksLikeDesignTool(name, prefix = "opendesign") { - return typeof name === "string" && name.toLowerCase().includes(prefix.toLowerCase()); -} - -// The start_run MCP result carries the created run record; be defensive about -// wrapping (content text vs structured output) and prefer a candidate whose -// requestId matches the recorded request. -export function extractRunId(result, requestId, isValidRunId) { - const valid = typeof isValidRunId === "function" ? isValidRunId : (value) => typeof value === "string" && value.length >= 8; - const objects = []; - const texts = []; - const content = result?.content; - if (typeof content === "string") texts.push(content); - else if (Array.isArray(content)) { - for (const part of content) { - if (part && part.type === "text" && typeof part.text === "string") texts.push(part.text); - } - } - if (result && typeof result.output === "object" && result.output) objects.push(result.output); - for (const key of ["structuredContent", "structuredOutput"]) { - const value = result?.[key]; - if (value && typeof value === "object") objects.push(value); - } - for (const text of texts) { - try { - const parsed = JSON.parse(text); - if (parsed && typeof parsed === "object") objects.push(parsed); - } catch {} - } - - const matchesRequest = (object) => - typeof requestId === "string" && - requestId !== "" && - (object.requestId === requestId || object.clientRequestId === requestId); - - for (const object of objects) { - if (typeof object.runId === "string" && valid(object.runId)) { - if (matchesRequest(object) || !requestId) return object.runId; - } - } - for (const object of objects) { - if (typeof object.id === "string" && object.id !== object.projectId && valid(object.id) && matchesRequest(object)) { - return object.id; - } - } - for (const object of objects) { - if (typeof object.runId === "string" && valid(object.runId)) return object.runId; - } - for (const object of objects) { - if (typeof object.id === "string" && object.id !== object.projectId && valid(object.id)) return object.id; - } - // Regex fallback: only when a single run-id-shaped token exists in the text. - const found = new Set(); - for (const text of texts) { - for (const match of text.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|[0-9A-HJKMNP-TV-Z]{26}/g) ?? []) { - if (valid(match)) found.add(match); - } - } - return found.size === 1 ? [...found][0] : null; -} - -export function createNotifier({ - store, - daemon, - deliver, - pollMs = 60_000, - clock = () => Date.now(), - log = () => {}, - quickPollMs = 4_000, - initialDelayMs = 1_500, - toolNamePrefix = "opendesign", - instanceId = randomUUID(), - instanceLabel = null, - renewEveryMs = Math.min(pollMs, 20_000), - leaseTtlMs = Math.max(3 * Math.min(pollMs, 20_000), 60_000), -} = {}) { - const sightingCounts = new Map(); - let running = false; - let stopped = true; - let leaderNow = false; - let tickDue = false; - let lastTickAt = 0; - let startupTimer = null; - let coordinationTimer = null; - let kickTimer = null; - - const requestKey = (requestId) => `${REQUEST_KEY_PREFIX}${requestId}`; - const runKey = (runId, sessionID) => `${RUN_KEY_PREFIX}${runId}@${sessionID}`; - const shortId = (value) => String(value).slice(0, 8); - - function sightingsSnapshot() { - return Object.fromEntries(sightingCounts); - } - - function recordSighting(tool) { - const entry = sightingCounts.get(tool); - sightingCounts.set(tool, { count: (entry?.count ?? 0) + 1, lastAt: clock() }); - if (!entry) log("tool-sighting", { tool }); - } - - async function patch(key, fields) { - await store.mutate((bindings) => { - if (bindings[key]) bindings[key] = { ...bindings[key], ...fields }; - }); - } - - async function handleToolBefore(event) { - try { - await recordStartRunRequest(event); - } catch (error) { - // Hooks must not reject into the host tool pipeline; the after-hook can - // still attach the run via its own session id. - log("hook-error", { phase: "before", tool: event?.tool ?? null, error: describeError(error) }); - } - } - - async function recordStartRunRequest(event) { - const name = typeof event?.tool === "string" ? event.tool : ""; - if (looksLikeDesignTool(name, toolNamePrefix)) recordSighting(name); - if (!isStartRunTool(name, toolNamePrefix)) return; - const input = event.input && typeof event.input === "object" ? event.input : {}; - const requestId = typeof input.requestId === "string" && input.requestId ? input.requestId : null; - if (!requestId) return; - const now = clock(); - await store.mutate((bindings) => { - const key = requestKey(requestId); - const existing = bindings[key]; - if (existing && (existing.state === "tracking" || existing.state === "delivered" || existing.state === "held")) return; - bindings[key] = { - ...(existing ?? {}), - key, - kind: "request", - requestId, - projectId: typeof input.project === "string" && input.project ? input.project : existing?.projectId ?? null, - sessionID: event.sessionID ?? existing?.sessionID ?? null, - agent: event.agent ?? existing?.agent ?? null, - toolCallId: event.id ?? existing?.toolCallId ?? null, - source: existing?.source ?? "hook", - state: existing?.runId ? "tracking" : "requested", - createdAt: existing?.createdAt ?? now, - updatedAt: now, - }; - }); - log("request-recorded", { requestId, sessionID: event.sessionID ?? null }); - } - - async function attachRun({ runId, requestId, sessionID, agent, projectId, source = "hook" }) { - if (!daemon.isValidRunId(runId)) return false; - const now = clock(); - const previousSession = requestId ? (await store.get(requestKey(requestId)))?.sessionID ?? null : null; - const resolvedSession = sessionID ?? previousSession ?? null; - if (!resolvedSession) { - if (requestId) { - await patch(requestKey(requestId), { - lastError: "run id resolved but no session id is available; cannot deliver", - updatedAt: now, - }); - } - log("attach-missing-session", { runId, requestId: requestId ?? null }); - return false; - } - let created = false; - await store.mutate((bindings) => { - const reqKey = requestId ? requestKey(requestId) : null; - const previous = reqKey ? bindings[reqKey] : null; - if (reqKey && previous) delete bindings[reqKey]; - const targetKey = runKey(runId, resolvedSession); - const existing = bindings[targetKey]; - if (existing && (existing.state === "delivered" || existing.state === "held")) return; - bindings[targetKey] = { - ...(existing ?? {}), - key: targetKey, - kind: "run", - runId, - requestId: requestId ?? existing?.requestId ?? null, - projectId: projectId ?? previous?.projectId ?? existing?.projectId ?? null, - sessionID: resolvedSession, - agent: agent ?? previous?.agent ?? existing?.agent ?? null, - toolCallId: previous?.toolCallId ?? existing?.toolCallId ?? null, - source: existing?.source ?? previous?.source ?? source, - state: "tracking", - createdAt: existing?.createdAt ?? previous?.createdAt ?? now, - updatedAt: now, - }; - created = !existing; - }); - if (created) log("run-attached", { runId, requestId: requestId ?? null, sessionID: sessionID ?? null }); - return true; - } - - async function handleToolAfter(event) { - try { - await processStartRunResult(event); - kick(); - } catch (error) { - log("hook-error", { phase: "after", tool: event?.tool ?? null, error: describeError(error) }); - } - } - - async function processStartRunResult(event) { - const name = typeof event?.tool === "string" ? event.tool : ""; - if (looksLikeDesignTool(name, toolNamePrefix)) recordSighting(name); - if (!isStartRunTool(name, toolNamePrefix)) return; - const input = event.input && typeof event.input === "object" ? event.input : {}; - const requestId = typeof input.requestId === "string" && input.requestId ? input.requestId : null; - if (event.status === "completed") { - const runId = extractRunId(event.result, requestId, daemon.isValidRunId); - if (runId) { - await attachRun({ - runId, - requestId, - sessionID: event.sessionID, - agent: event.agent, - projectId: typeof input.project === "string" && input.project ? input.project : null, - }); - } else if (requestId) { - await patch(requestKey(requestId), { - lastError: "run id not found in start_run result; will reconcile by request id", - updatedAt: clock(), - }); - log("run-id-missing", { requestId }); - } - } else if (requestId) { - await patch(requestKey(requestId), { - lastError: `start_run failed: ${describeError(event.error)}`, - updatedAt: clock(), - }); - log("start-failed", { requestId }); - } - } - - async function watchRun({ runId, sessionID, agent }) { - if (!daemon.isValidRunId(runId)) throw new Error(`invalid run id: ${String(runId).slice(0, 80)}`); - if (typeof sessionID !== "string" || !sessionID) throw new Error("watch requires a session"); - const run = await daemon.getRun(runId); - const key = runKey(runId, sessionID); - const now = clock(); - let resultState = null; - await store.mutate((bindings) => { - const existing = bindings[key]; - if (existing) { - resultState = existing.state; - if (existing.state === "tracking" || existing.state === "held") { - bindings[key] = { ...existing, updatedAt: now }; - return true; - } - // Delivered or final: keep the record as-is. - return false; - } - bindings[key] = { - key, - kind: "run", - runId, - sessionID, - projectId: run.projectId ?? null, - requestId: typeof run.clientRequestId === "string" ? run.clientRequestId : null, - agent: agent ?? null, - source: "manual", - state: "tracking", - createdAt: now, - updatedAt: now, - }; - resultState = "tracking"; - return true; - }); - log("manual-watch", { runId, sessionID, state: resultState }); - if (resultState === "tracking" || resultState === "held") kick(); - return { - runId, - status: run.status ?? null, - state: resultState, - alreadyDelivered: resultState === "delivered", - }; - } - - // Deliver a terminal run to its session, or store it as `held` while the - // session's wake permission is paused. Delivery is claimed under the state - // lock; the deterministic message id makes a re-send a session-level no-op. - async function attemptDelivery(binding, run) { - const current = (await store.get(binding.key)) ?? binding; - if (current.state !== "tracking" && current.state !== "held") return; - const now = clock(); - const paused = await store.isPausedFor(current.sessionID); - if (paused) { - if (current.state !== "held") { - await patch(current.key, { - state: "held", - heldAt: now, - lastStatus: summarizeRun(run), - updatedAt: now, - }); - log("held", { - runId: current.runId, - sessionID: current.sessionID, - classification: classifyRun(run), - }); - } - return; - } - if (current.deliveryNextAttemptAt && now < current.deliveryNextAttemptAt) return; - const claimToken = randomUUID(); - const claimed = await store.claimDelivery(current.key, { now, token: claimToken }); - if (!claimed) return; // another instance is delivering, or the state changed - // Narrow the pause-vs-delivery window: a pause that landed while we were - // claiming holds the completion instead of contacting the session. A pause - // during the deliver() call itself still takes effect on the next tick. - if (await store.isPausedFor(current.sessionID)) { - await patch(current.key, { - state: "held", - heldAt: current.heldAt ?? now, - lastStatus: summarizeRun(run), - deliveryClaim: null, - lastError: null, - updatedAt: now, - }); - log("held", { - runId: current.runId, - sessionID: current.sessionID, - classification: classifyRun(run), - afterClaim: true, - }); - return; - } - const message = buildDelivery({ - run, - sessionID: current.sessionID, - requestId: current.requestId, - source: current.source, - paused: false, - }); - try { - await deliver({ sessionID: current.sessionID, message }); - await patch(current.key, { - state: "delivered", - deliveredAt: now, - delivery: { messageId: message.id, resume: message.resume, classification: message.classification }, - lastStatus: summarizeRun(run), - lastError: null, - deliveryClaim: null, - updatedAt: now, - }); - log("delivered", { - runId: current.runId, - sessionID: current.sessionID, - messageId: message.id, - resume: message.resume, - classification: message.classification, - wasHeld: Boolean(current.heldAt), - }); - } catch (error) { - if (isSessionMissingError(error)) { - await patch(current.key, { - state: "orphaned", - lastError: "target session not found; not delivered elsewhere", - deliveryClaim: null, - updatedAt: now, - }); - log("orphaned", { runId: current.runId, sessionID: current.sessionID }); - return; - } - const attempts = (current.deliveryAttempts ?? 0) + 1; - const backoff = Math.min(DELIVERY_BASE_BACKOFF_MS * 2 ** Math.min(attempts, 6), DELIVERY_MAX_BACKOFF_MS); - await patch(current.key, { - deliveryAttempts: attempts, - deliveryNextAttemptAt: now + backoff, - lastError: `delivery failed: ${describeError(error)}`, - deliveryClaim: null, - updatedAt: now, - }); - log("delivery-error", { runId: current.runId, sessionID: current.sessionID, attempts, error: describeError(error) }); - } - } - - async function progressRequested(binding) { - const now = clock(); - if (!binding.requestId) { - await patch(binding.key, { state: "unresolved", lastError: "no request id recorded", updatedAt: now }); - return; - } - const age = now - (binding.createdAt ?? now); - if (age < RECOVERY_MIN_AGE_MS) return; - if (now - (binding.lastRecoveryAttemptAt ?? 0) < RECOVERY_RETRY_MS) return; - await patch(binding.key, { lastRecoveryAttemptAt: now }); - try { - const run = await daemon.findByRequestId({ requestId: binding.requestId, projectId: binding.projectId ?? undefined }); - if (run) { - await attachRun({ - runId: run.id, - requestId: binding.requestId, - sessionID: binding.sessionID, - agent: binding.agent, - projectId: run.projectId ?? binding.projectId ?? null, - source: binding.source ?? "hook", - }); - log("recovered-run", { requestId: binding.requestId, runId: run.id }); - } else if (age > RECOVERY_GIVE_UP_MS) { - await patch(binding.key, { - state: "unresolved", - lastError: "no matching run found within 24h; no blind retry", - updatedAt: clock(), - }); - } - } catch (error) { - await patch(binding.key, { lastError: describeError(error), updatedAt: clock() }); - } - } - - // Poll one watched (tracking) or stored (held) run. - async function progressTracking(binding) { - let run; - try { - run = await daemon.getRun(binding.runId); - } catch (error) { - if (error?.status === 404) { - // A strategy chain may map the follow-up run before the daemon has - // materialized it; keep polling within the grace window instead of - // parking the chain as `missing`. - if (binding.source === "strategy-chain" && clock() - (binding.createdAt ?? 0) < CHAIN_START_GRACE_MS) { - await patch(binding.key, { - lastPollAt: clock(), - lastError: "follow-up run not visible yet; awaiting strategy chain", - updatedAt: clock(), - }); - return; - } - await patch(binding.key, { - state: "missing", - lastError: "run not found on daemon (kept for diagnosis; no delivery)", - updatedAt: clock(), - }); - log("run-missing", { runId: binding.runId }); - return; - } - await patch(binding.key, { lastPollAt: clock(), lastError: describeError(error), updatedAt: clock() }); - return; - } - const now = clock(); - const snapshot = summarizeRun(run); - if (!isTerminalStatus(run.status)) { - const fields = { lastPollAt: now, lastStatus: snapshot, lastError: null, updatedAt: now }; - // A held run that became active again (manual resume in OpenDesign) - // goes back to tracking and will be delivered at its next terminal end. - if (binding.state === "held") { - fields.state = "tracking"; - fields.heldAt = null; - } - await patch(binding.key, fields); - return; - } - await patch(binding.key, { lastPollAt: now, lastStatus: snapshot, lastError: null, updatedAt: now }); - await attemptDelivery(binding, run); - await continueStrategyChain(binding, run); - } - - // Follow a plan→execute style strategy chain: a terminal stage that maps a - // follow-up run keeps the chain watched for the same session, so the final - // stage still reaches it (the intermediate notification is worded as - // non-final). Idempotent; existing records are never overwritten. - async function continueStrategyChain(binding, run) { - const nextRunId = run?.strategyTask?.nextRunId; - if (run?.strategyTask?.terminal !== false || typeof nextRunId !== "string" || !nextRunId) return; - if (!daemon.isValidRunId(nextRunId)) return; - if (!binding.sessionID) return; - const key = runKey(nextRunId, binding.sessionID); - const now = clock(); - let created = false; - await store.mutate((bindings) => { - if (bindings[key]) return; - bindings[key] = { - key, - kind: "run", - runId: nextRunId, - sessionID: binding.sessionID, - projectId: run.projectId ?? binding.projectId ?? null, - requestId: typeof run.clientRequestId === "string" ? run.clientRequestId : null, - agent: binding.agent ?? null, - source: "strategy-chain", - state: "tracking", - createdAt: now, - updatedAt: now, - }; - created = true; - }); - if (created) log("strategy-next-run", { runId: binding.runId, nextRunId }); - } - - async function tick({ reason = "interval" } = {}) { - if (running) return; - running = true; - try { - const bindings = await store.load(); - for (const binding of Object.values(bindings)) { - try { - if (binding.state === "requested") await progressRequested(binding); - else if (binding.state === "tracking" || binding.state === "held") await progressTracking(binding); - } catch (error) { - log("tick-error", { key: binding.key, reason, error: describeError(error) }); - } - } - // Skip the write when there is nothing to reap. - await store.mutate((current) => reapBindings(current, clock())); - } catch (error) { - log("tick-fatal", { reason, error: describeError(error) }); - } finally { - running = false; - } - } - - // One coordination beat: renew or acquire the poller lease, then tick when - // this instance holds it and a tick is due. Followers only renew nothing and - // stay cheap; they still capture bindings through the tool hooks. - // Safe to call directly (tests) — timers are disposed by stop(). - async function coordination(reason) { - let leader = false; - try { - leader = await store.tryAcquireLeadership(instanceId, { ttlMs: leaseTtlMs }); - } catch (error) { - log("leadership-error", { error: describeError(error) }); - return; - } - if (leader && !leaderNow) { - log("leader-claimed", { instanceId: shortId(instanceId), label: instanceLabel }); - } - leaderNow = leader; - if (!leader) return; - const due = tickDue || reason === "startup" || clock() - lastTickAt >= pollMs; - if (!due) return; - tickDue = false; - lastTickAt = clock(); - await tick({ reason }); - } - - function kick() { - if (stopped || kickTimer) return; - tickDue = true; - kickTimer = setTimeout(() => { - kickTimer = null; - void coordination("kick"); - }, quickPollMs); - if (typeof kickTimer.unref === "function") kickTimer.unref(); - } - - function start() { - stopped = false; - startupTimer = setTimeout(() => { - startupTimer = null; - void coordination("startup"); - }, initialDelayMs); - if (typeof startupTimer.unref === "function") startupTimer.unref(); - coordinationTimer = setInterval(() => void coordination("interval"), renewEveryMs); - if (typeof coordinationTimer.unref === "function") coordinationTimer.unref(); - return () => { - stopped = true; - if (startupTimer) clearTimeout(startupTimer); - if (coordinationTimer) clearInterval(coordinationTimer); - if (kickTimer) clearTimeout(kickTimer); - startupTimer = null; - coordinationTimer = null; - kickTimer = null; - }; - } - - // Best-effort: give the lease back so another instance takes over quickly - // (used on plugin unload; a crash leaves the lease to expire by TTL). - async function release() { - try { - return await store.releaseLeadership(instanceId); - } catch { - return false; - } - } - - function compactBinding(binding) { - return { - key: binding.key, - state: binding.state, - source: binding.source ?? null, - sessionID: binding.sessionID ?? null, - runId: binding.runId ?? null, - projectId: binding.projectId ?? null, - status: binding.lastStatus?.status ?? null, - deliverableValid: binding.lastStatus?.deliverableValid ?? null, - strategyTerminal: binding.lastStatus?.strategyTerminal ?? null, - heldAt: binding.heldAt ?? null, - deliveredAt: binding.deliveredAt ?? null, - deliveryAttempts: binding.deliveryAttempts ?? 0, - deliveryNextAttemptAt: binding.deliveryNextAttemptAt ?? null, - retryAfterMs: ["tracking", "held"].includes(binding.state) && Number.isFinite(binding.deliveryNextAttemptAt) - ? Math.max(0, binding.deliveryNextAttemptAt - clock()) : 0, - updatedAt: binding.updatedAt ?? null, - lastError: binding.lastError ? String(binding.lastError).slice(0, 160) : null, - }; - } - - async function runTool(input, toolContext = {}) { - const action = input && typeof input === "object" ? input.action : undefined; - const text = (value) => ({ content: [{ type: "text", text: value }] }); - if (action === "list") { - const bindings = await store.load(); - const paused = await store.listPaused(); - const callerSession = typeof toolContext.sessionID === "string" ? toolContext.sessionID : null; - const leadership = await store.readLeadership().catch(() => null); - const rows = Object.values(bindings).map(compactBinding); - return text( - JSON.stringify( - { - paused: { - global: paused.global, - sessions: paused.sessions, - forThisSession: callerSession ? await store.isPausedFor(callerSession) : null, - }, - leadership: leadership - ? { holder: shortId(leadership.instanceId), isSelf: leadership.instanceId === instanceId, at: leadership.at } - : null, - instanceId: shortId(instanceId), - stateDir: store.dir, - daemon: daemon.baseUrl, - observedTools: sightingsSnapshot(), - bindings: rows, - }, - null, - 2, - ), - ); - } - if (action === "pause" || action === "resume") { - const scope = input.scope === "all" ? "all" : "session"; - const key = scope === "all" ? "*" : (typeof toolContext.sessionID === "string" && toolContext.sessionID) || null; - if (!key) { - return text("pause/resume with scope=session requires a calling session; use scope=all for the global switch"); - } - try { - await store.setPaused(key, action === "pause"); - } catch (error) { - return text(`${action} failed: ${describeError(error)}`); - } - log(action === "pause" ? "paused" : "resumed", { scope }); - if (action === "resume") kick(); - const what = scope === "all" ? "all sessions" : "this session"; - return text( - action === "pause" - ? `Automatic wake-up paused for ${what}. Terminal runs are kept in notifier state (visible via design_runs list) and the session is not contacted until resume.` - : `Automatic wake-up resumed for ${what}. Held completions become eligible on poll ticks after existing retry delays; remaining pause switches still apply. Use design_runs list to inspect the remaining retry wait.`, - ); - } - if (action === "watch") { - const runId = input.runId; - try { - const info = await watchRun({ runId, sessionID: toolContext.sessionID ?? null, agent: toolContext.agent ?? null }); - return text(JSON.stringify(info)); - } catch (error) { - return text(`watch failed: ${describeError(error)}`); - } - } - return text(`unknown action: ${String(action)}`); - } - - return { - instanceId, - handleToolBefore, - handleToolAfter, - watchRun, - tick, - coordination, - start, - release, - runTool, - sightings: sightingsSnapshot, - }; -} diff --git a/plugins/design-notifier/lib/state.js b/plugins/design-notifier/lib/state.js deleted file mode 100644 index eb4f45c..0000000 --- a/plugins/design-notifier/lib/state.js +++ /dev/null @@ -1,362 +0,0 @@ -// Durable, file-based state for the design completion notifier. -// -// Layout (default `~/.local/state/opencode-design-notifier/`): -// bindings.json — one record per watched (run, session) or pending request -// paused.json — { "*": {at} } global pause and/or { "ses_...": {at} } per session -// leader.json — single-poller lease { instanceId, at } -// events.log — bounded JSONL diagnostics (never stores prompts) -// -// All writes are atomic (temp file + rename). A lock file serializes writers; -// on contention the lock waits up to `lockTimeoutMs` and then fails the write -// instead of proceeding unlocked, so an update is retried instead of lost. - -import { appendFile, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises"; -import { homedir } from "node:os"; -import { join } from "node:path"; - -const BINDINGS_FILE = "bindings.json"; -const PAUSED_FILE = "paused.json"; -const LEGACY_PAUSE_FILE = "paused"; -const LEADER_FILE = "leader.json"; -const LOG_FILE = "events.log"; -const LOCK_FILE = "bindings.lock"; -const LOG_CAP_BYTES = 256 * 1024; -const RECORD_CAP = 500; -const DELIVERED_TTL_MS = 30 * 24 * 60 * 60 * 1000; -const FINAL_TTL_MS = 90 * 24 * 60 * 60 * 1000; -const STALE_LOCK_MS = 5_000; -const LOCK_TIMEOUT_MS = 5_000; - -const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - -export function defaultStateDir(env = process.env) { - const override = env.KSI_DESIGN_NOTIFIER_STATE_DIR; - if (typeof override === "string" && override.trim()) return override.trim(); - const base = - typeof env.XDG_STATE_HOME === "string" && env.XDG_STATE_HOME.trim() - ? env.XDG_STATE_HOME.trim() - : join( - typeof env.HOME === "string" && env.HOME.trim() ? env.HOME.trim() : homedir(), - ".local", - "state", - ); - return join(base, "opencode-design-notifier"); -} - -// Prune records that no longer need to be kept. Mutates `bindings`; returns -// true when anything was removed. `held` is deliberately not final: it is a -// stored completion waiting for wake permission, not a finished record. -export function reapBindings(bindings, now, { - recordCap = RECORD_CAP, - deliveredTtlMs = DELIVERED_TTL_MS, - finalTtlMs = FINAL_TTL_MS, -} = {}) { - let changed = false; - for (const [key, record] of Object.entries(bindings)) { - if (!record || typeof record !== "object") { - delete bindings[key]; - changed = true; - continue; - } - const age = now - (record.updatedAt ?? record.createdAt ?? now); - const finalState = - record.state === "delivered" || - record.state === "orphaned" || - record.state === "unresolved" || - record.state === "missing"; - const ttl = record.state === "delivered" ? deliveredTtlMs : finalTtlMs; - if (finalState && age > ttl) { - delete bindings[key]; - changed = true; - } - } - const keys = Object.keys(bindings); - if (keys.length > recordCap) { - const removable = keys - .filter((key) => { - const state = bindings[key].state; - return state === "delivered" || state === "orphaned" || state === "unresolved" || state === "missing"; - }) - .sort((a, b) => (bindings[a].updatedAt ?? 0) - (bindings[b].updatedAt ?? 0)); - while (Object.keys(bindings).length > recordCap && removable.length > 0) { - delete bindings[removable.shift()]; - changed = true; - } - } - return changed; -} - -export function createStore({ dir, now = () => Date.now(), lockTimeoutMs = LOCK_TIMEOUT_MS }) { - const bindingsPath = join(dir, BINDINGS_FILE); - const pausedPath = join(dir, PAUSED_FILE); - const legacyPausePath = join(dir, LEGACY_PAUSE_FILE); - const leaderPath = join(dir, LEADER_FILE); - const logPath = join(dir, LOG_FILE); - const lockPath = join(dir, LOCK_FILE); - - async function writeJsonAtomic(path, value) { - const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`; - await writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 }); - await rename(tmp, path); - } - - async function readJsonFile(path) { - try { - const value = JSON.parse(await readFile(path, "utf8")); - if (value && typeof value === "object" && !Array.isArray(value)) return value; - return null; - } catch (error) { - if (error?.code === "ENOENT") return null; - if (error instanceof SyntaxError) return null; - throw error; - } - } - - async function init() { - await mkdir(dir, { recursive: true }); - // Clean up temp files stranded by a crash (best effort). - try { - const entries = await readdir(dir); - for (const name of entries) { - if (!name.includes(".tmp-")) continue; - const info = await stat(join(dir, name)).catch(() => null); - if (info && now() - info.mtimeMs > 60 * 60 * 1000) { - await rm(join(dir, name), { force: true }).catch(() => {}); - } - } - } catch {} - // Migrate the legacy global `paused` file into paused.json. - try { - const legacy = await stat(legacyPausePath); - const existing = await readJsonFile(pausedPath); - if (!existing || Object.keys(existing).length === 0) { - await writeJsonAtomic(pausedPath, { "*": { at: Math.round(legacy.mtimeMs) } }); - } - await rm(legacyPausePath, { force: true }); - } catch {} - } - - async function load() { - let raw; - try { - raw = await readFile(bindingsPath, "utf8"); - } catch (error) { - if (error?.code === "ENOENT") return {}; - throw error; - } - try { - const value = JSON.parse(raw); - if (value && typeof value === "object" && !Array.isArray(value)) return value; - } catch {} - // Corrupt or non-object JSON: preserve it once, then start clean. - await rename(bindingsPath, `${bindingsPath}.corrupt-${now()}`).catch(() => {}); - return {}; - } - - async function save(bindings) { - await writeJsonAtomic(bindingsPath, bindings); - } - - async function withLock(fn) { - await mkdir(dir, { recursive: true }); - const deadline = Date.now() + lockTimeoutMs; - for (;;) { - let handle = null; - try { - handle = await open(lockPath, "wx"); - } catch (error) { - if (error?.code !== "EEXIST") throw error; - try { - const info = await stat(lockPath); - if (Date.now() - info.mtimeMs > STALE_LOCK_MS) { - await rm(lockPath, { force: true }); - continue; - } - } catch {} - if (Date.now() >= deadline) { - const timeout = new Error(`state lock timeout after ${lockTimeoutMs}ms`); - timeout.code = "STATE_LOCK_TIMEOUT"; - throw timeout; - } - await sleep(20 + Math.floor(Math.random() * 40)); - continue; - } - try { - return await fn(); - } finally { - await handle.close().catch(() => {}); - await rm(lockPath, { force: true }).catch(() => {}); - } - } - } - - async function mutate(fn) { - return withLock(async () => { - const bindings = await load(); - const result = await fn(bindings); - // Returning exactly `false` skips the write (used to avoid rewriting - // unchanged state on every poll). - if (result !== false) await save(bindings); - return result; - }); - } - - // Claim a delivery under the state lock so concurrent plugin instances - // (OpenCode loads the global plugin per location) cannot both deliver the - // same binding. The claim expires so a crashed instance does not strand it. - async function claimDelivery(key, { now: claimedAt = now(), token, ttlMs = 120_000 } = {}) { - let claimed = false; - await mutate((bindings) => { - const record = bindings[key]; - if (!record || (record.state !== "tracking" && record.state !== "held")) return false; - const claim = record.deliveryClaim; - if (claim && typeof claim.at === "number" && claimedAt - claim.at < ttlMs) return false; - bindings[key] = { ...record, deliveryClaim: { at: claimedAt, token }, updatedAt: claimedAt }; - claimed = true; - return true; - }); - return claimed; - } - - async function get(key) { - const bindings = await load(); - return bindings[key] ?? null; - } - - async function put(key, record) { - return mutate((bindings) => { - bindings[key] = record; - }); - } - - async function remove(key) { - return mutate((bindings) => { - delete bindings[key]; - }); - } - - // ---- wake permission (per session, with an optional global switch) ---- - - async function readPausedMap() { - try { - const value = JSON.parse(await readFile(pausedPath, "utf8")); - if (value && typeof value === "object" && !Array.isArray(value)) return value; - throw new SyntaxError("paused.json is not an object"); - } catch (error) { - if (error?.code === "ENOENT") return {}; - // Preserve a corrupt switch instead of silently discarding it. - await rename(pausedPath, `${pausedPath}.corrupt-${now()}`).catch(() => {}); - await log("paused-corrupt", { error: String(error?.message ?? error).slice(0, 120) }); - return {}; - } - } - - async function isPausedFor(sessionID) { - const map = await readPausedMap(); - if (map["*"]) return true; - if (typeof sessionID === "string" && sessionID && map[sessionID]) return true; - if (Object.keys(map).length === 0) { - // Pre-migration legacy file (init migrates it). - try { - await stat(legacyPausePath); - return true; - } catch {} - } - return false; - } - - async function listPaused() { - const map = await readPausedMap(); - return { - global: Boolean(map["*"]), - sessions: Object.keys(map).filter((key) => key !== "*"), - }; - } - - // key: "ses_..." for one session, "*" for every session. - async function setPaused(key, paused) { - if (key !== "*" && (typeof key !== "string" || !key.startsWith("ses"))) { - throw new Error(`invalid pause key: ${String(key).slice(0, 40)}`); - } - await withLock(async () => { - const map = await readPausedMap(); - if (paused) map[key] = { at: now() }; - else delete map[key]; - await writeJsonAtomic(pausedPath, map); - }); - await rm(legacyPausePath, { force: true }).catch(() => {}); - } - - // ---- single-poller leadership lease ---- - - async function readLeadership() { - const value = await readJsonFile(leaderPath); - if (!value || typeof value.instanceId !== "string" || typeof value.at !== "number") return null; - return { instanceId: value.instanceId, at: value.at }; - } - - // Acquire or renew the poller lease. Returns true when this instance holds - // it. A fresh lease held by another instance is respected; a lease older - // than ttlMs is taken over. - async function tryAcquireLeadership(instanceId, { ttlMs = 60_000, now: at = now() } = {}) { - return withLock(async () => { - const current = await readJsonFile(leaderPath); - const fresh = current && typeof current.at === "number" && at - current.at < ttlMs; - if (fresh && current.instanceId !== instanceId) return false; - await writeJsonAtomic(leaderPath, { instanceId, at }); - return true; - }); - } - - async function releaseLeadership(instanceId) { - try { - return await withLock(async () => { - const current = await readJsonFile(leaderPath); - if (!current || current.instanceId !== instanceId) return false; - await rm(leaderPath, { force: true }); - return true; - }); - } catch { - return false; - } - } - - async function log(event, fields = {}) { - try { - await mkdir(dir, { recursive: true }); - let rotate = false; - try { - const info = await stat(logPath); - rotate = info.size > LOG_CAP_BYTES; - } catch {} - if (rotate) { - const raw = await readFile(logPath, "utf8").catch(() => ""); - const keep = raw.slice(-Math.floor(LOG_CAP_BYTES / 2)); - const trimmed = keep.slice(keep.indexOf("\n") + 1 || 0); - await writeFile(logPath, trimmed, { mode: 0o600 }); - } - await appendFile(logPath, `${JSON.stringify({ t: now(), event, ...fields })}\n`, { mode: 0o600 }); - } catch { - // Diagnostics must never break the notifier. - } - } - - return { - dir, - init, - load, - save, - mutate, - claimDelivery, - get, - put, - remove, - isPausedFor, - setPaused, - listPaused, - tryAcquireLeadership, - releaseLeadership, - readLeadership, - log, - }; -} diff --git a/plugins/design-notifier/package.json b/plugins/design-notifier/package.json deleted file mode 100644 index ebdf229..0000000 --- a/plugins/design-notifier/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "ksi-design-notifier", - "version": "0.2.0", - "private": true, - "type": "module", - "main": "index.js", - "description": "OpenCode V2 plugin: deliver OpenDesign run completion to the originating session" -} diff --git a/scripts/check-package.mjs b/scripts/check-package.mjs deleted file mode 100644 index cdd787b..0000000 --- a/scripts/check-package.mjs +++ /dev/null @@ -1,153 +0,0 @@ -import assert from "node:assert/strict" -import { spawn } from "node:child_process" -import { constants } from "node:fs" -import { access, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { basename, isAbsolute, join } from "node:path" -import { fileURLToPath } from "node:url" - -const root = fileURLToPath(new URL("..", import.meta.url)) -const required = [ - "bin/ksi-opencode.mjs", "src/native-bundle.mjs", "src/native-roles.mjs", - "templates/agents/developer.md", "templates/agents/reviewer.md", - "examples/project-AGENTS.md", "examples/autonomous-development.md", "README.md", "INSTALL.md", - "docs/architecture.md", "docs/integrations/opendesign.md", -] -const forbidden = /(?:^|\/)(?:index\.mjs|plugin-v2\.mjs|node_modules|test|tests|docs\/superpowers|\.opencode|design-previews)(?:\/|$)|^(?:src\/agents\.mjs|agents\/|commands\/)|(?:^|\/)(?:\.env(?:\.[^/]*)?|[^/]*credentials[^/]*|[^/]*auth[^/]*\.json)$/i - -function run(command, args, cwd, env) { - return new Promise((resolve, reject) => { - const child = spawn(command, args, { cwd, env, shell: false, stdio: ["ignore", "pipe", "pipe"] }) - let stdout = "" - let stderr = "" - child.stdout.setEncoding("utf8") - child.stderr.setEncoding("utf8") - child.stdout.on("data", (chunk) => { stdout += chunk }) - child.stderr.on("data", (chunk) => { stderr += chunk }) - child.on("error", reject) - child.on("close", (code) => resolve({ code, stdout, stderr })) - }) -} - -// Portable npm invocation. `npm run` supplies the trusted npm CLI script path -// as npm_execpath; driving it with the current Node binary needs no shell and -// no PATH lookup, so Windows (npm.cmd) behaves like POSIX. Without -// npm_execpath there is no safe resolution: fail descriptively instead of -// guessing a shell string. -export function npmTarget(env = process.env, execPath = process.execPath) { - const npmCli = env.npm_execpath - if (typeof npmCli !== "string" || npmCli === "" || !isAbsolute(npmCli)) { - throw new Error("npm CLI unavailable: run this verifier via `npm run check:package` so npm_execpath is set to an absolute path") - } - return { command: execPath, baseArgs: [npmCli] } -} - -// Portable installed-CLI target. On Windows the .bin entry is an sh script -// plus .cmd/.ps1 shims (not directly executable without a shell), so the -// registered .cmd shim presence is asserted while behavior runs through Node -// + the installed bin JS. POSIX keeps the actual standalone executable check. -export function installedCliTarget({ installRoot, packageDir, platform = process.platform, execPath = process.execPath }) { - const dotBin = join(installRoot, "node_modules", ".bin", "ksi-opencode") - if (platform === "win32") { - const binJs = join(packageDir, "bin", "ksi-opencode.mjs") - return { command: execPath, baseArgs: [binJs], present: [`${dotBin}.cmd`, binJs], executable: null } - } - return { command: dotBin, baseArgs: [], present: [], executable: dotBin } -} - -export async function verifyRetiredFlags(runCli, target) { - for (const flag of ["--with-design-kit", "--developer-test-runner"]) { - const result = await runCli(["install", "--target", target, flag, "--apply"]) - assert.notEqual(result.code, 0, `the removed ${flag} flag must fail loudly`) - assert.match(result.stderr, new RegExp(`${flag.slice(2)}.*removed`, "i")) - await assert.rejects(access(target), { code: "ENOENT" }, "a rejected flag must not create the target") - } -} - -async function main() { - const npm = npmTarget() - const work = await mkdtemp(join(tmpdir(), "ksi-native-package-")) - try { - const env = { - PATH: process.env.PATH, - HOME: work, - npm_config_cache: join(work, "npm-cache"), - npm_config_offline: "true", - npm_config_update_notifier: "false", - } - const runNpm = (args) => run(npm.command, [...npm.baseArgs, ...args], root, env) - const packed = await runNpm(["pack", "--json", "--ignore-scripts", "--pack-destination", work]) - assert.equal(packed.code, 0, packed.stderr) - const [record] = JSON.parse(packed.stdout) - const paths = record.files.map(({ path }) => path.replaceAll("\\", "/")) - for (const path of required) assert.ok(paths.includes(path), `missing packaged file: ${path}`) - assert.ok(!paths.includes("templates/agents/design.md"), "retired Design role must not be packaged") - assert.ok(!paths.includes("templates/agents/research.md"), "retired Research role must not be packaged") - assert.ok(!paths.includes("templates/agents/design-critic.md"), "retired Design Critic role must not be packaged") - assert.ok(!paths.includes("templates/agents/test-runner.md"), "retired Test Runner role must not be packaged") - assert.ok(!paths.some((path) => path.startsWith("vendor/")), "vendored design kit must not be packaged") - assert.ok(!paths.some((path) => path.startsWith("integrations/")), "manual deployment assets must not be packaged") - assert.ok(!paths.some((path) => path.startsWith("docs/design")), "retired design docs must not be packaged") - assert.ok(!paths.some((path) => path.startsWith("examples/design")), "retired design examples must not be packaged") - assert.ok(!paths.includes("docs/releasing.md"), "maintainer-only release guide must not be packaged") - for (const path of paths) assert.doesNotMatch(path, forbidden, `forbidden packaged file: ${path}`) - const tarball = isAbsolute(record.filename) ? record.filename : join(work, basename(record.filename)) - await access(tarball) - const installRoot = join(work, "consumer") - const installed = await runNpm(["install", "--offline", "--ignore-scripts", "--no-audit", "--no-fund", "--prefix", installRoot, tarball]) - assert.equal(installed.code, 0, installed.stderr) - const packageDir = join(installRoot, "node_modules", "ksi-opencode-harness") - const manifest = JSON.parse(await readFile(join(packageDir, "package.json"), "utf8")) - assert.equal(manifest.version, "0.5.0-beta.9") - assert.notEqual(manifest.private, true) - assert.equal(manifest.main, undefined) - assert.equal(manifest.exports, undefined) - assert.equal(manifest.bin["ksi-opencode"], "./bin/ksi-opencode.mjs") - const config = join(work, "opencode-config") - const cli = installedCliTarget({ installRoot, packageDir }) - for (const entry of cli.present) await access(entry) - if (cli.executable) await access(cli.executable, constants.X_OK) - await assert.rejects(access(join(work, ".config", "opencode")), { code: "ENOENT" }, "package install must not change config") - const runCli = (args) => run(cli.command, [...cli.baseArgs, ...args], work, env) - const relative = await runCli(["install", "--target", "relative-config", "--apply"]) - assert.notEqual(relative.code, 0) - await assert.rejects(access(join(work, "relative-config")), { code: "ENOENT" }) - await verifyRetiredFlags(runCli, config) - const preview = await runCli(["install", "--target", config]) - assert.equal(preview.code, 0, preview.stderr) - assert.equal(JSON.parse(preview.stdout).applied, false) - await assert.rejects(access(config), { code: "ENOENT" }, "preview must not create target") - const ran = await runCli(["install", "--target", config, "--apply"]) - assert.equal(ran.code, 0, ran.stderr) - assert.deepEqual((await readdir(join(config, "agents"))).sort(), ["developer.md", "reviewer.md"]) - assert.deepEqual(await readdir(config), ["agents"]) - const repeat = await runCli(["install", "--target", config, "--apply"]) - assert.equal(repeat.code, 0, repeat.stderr) - assert.deepEqual(JSON.parse(repeat.stdout).changes, [], "repeated apply must be idempotent") - const legacyRole = join(config, "agents", "test-runner.md") - const legacyPrompt = "Existing user-owned verification role; preserve it.\n" - const routingPath = join(config, "opencode.jsonc") - const routing = '{\n // existing optional verifier routing\n "agents": {"test-runner": {"model": "user-owned/verifier", "steps": 24}}\n}\n' - await writeFile(legacyRole, legacyPrompt) - await writeFile(routingPath, routing) - const legacyUpdate = await runCli(["install", "--target", config, "--apply"]) - assert.equal(legacyUpdate.code, 0, legacyUpdate.stderr) - assert.deepEqual(JSON.parse(legacyUpdate.stdout).changes, [], "legacy files must stay outside the managed bundle") - assert.equal(await readFile(legacyRole, "utf8"), legacyPrompt) - assert.equal(await readFile(routingPath, "utf8"), routing) - const ownedRole = join(config, "agents", "reviewer.md") - const userRouting = "---\nmodel: user-owned/model\n---\nDo not replace this role.\n" - await writeFile(ownedRole, userRouting) - const conflict = await runCli(["install", "--target", config, "--apply", "--replace"]) - assert.notEqual(conflict.code, 0, "user routing must block replacement") - assert.equal(await readFile(ownedRole, "utf8"), userRouting) - assert.deepEqual((await readdir(join(config, "agents"))).sort(), ["developer.md", "reviewer.md", "test-runner.md"]) - console.log("Package verification passed: installer-only tarball, offline two-role install, both retired flags rejected, and legacy role/routing preserved.") - } finally { - await rm(work, { recursive: true, force: true }) - } -} - -// Importing this module (e.g. from unit tests) must not verify anything; -// only direct invocation runs the verifier. -if (process.argv[1] === fileURLToPath(import.meta.url)) await main() diff --git a/scripts/design-binding.mjs b/scripts/design-binding.mjs deleted file mode 100644 index 1822748..0000000 --- a/scripts/design-binding.mjs +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env node -// Bounded advisory snapshot validation for the representative OpenDesign project. -// -// Compares the recorded Product binding identity/source against a caller-supplied -// design-projects snapshot (actual MCP `{projects:[...]}` list or a plain array -// fixture). It never creates/rebinds projects, never executes recorded -// commands/URLs and never performs network fetches. A snapshot match is captured -// metadata evidence only, not tool/permission/auth/render readiness, and lookup -// failure never authorizes creating a fresh project. -import { resolve, sep, isAbsolute } from "node:path"; -import { realpathSync, readFileSync } from "node:fs"; - -function isRecord(value) { - return value !== null && typeof value === "object" && !Array.isArray(value); -} - -function canonical(root, candidate) { - const absolute = resolve(root, candidate); - try { - return realpathSync(absolute); - } catch { - return absolute; - } -} - -function normalizeProjects(projects) { - if (projects === undefined) return { absent: true }; - const list = Array.isArray(projects) ? projects : (isRecord(projects) && Array.isArray(projects.projects) ? projects.projects : null); - if (list === null) { - return { error: "Design projects snapshot container is malformed; expected an array or an MCP list shaped as {projects:[...]}." }; - } - const seen = new Set(); - for (let index = 0; index < list.length; index += 1) { - const record = list[index]; - if (!isRecord(record)) return { error: `Design projects snapshot record ${index} is malformed; expected an object.` }; - if (typeof record.id !== "string" || !record.id.trim()) { - return { error: `Design projects snapshot record ${index} has a missing or wrong-type id; expected a non-empty string.` }; - } - if (seen.has(record.id)) return { error: `Design projects snapshot has a duplicate id '${record.id}'; refusing to guess.` }; - seen.add(record.id); - const metadata = record.metadata; - if (metadata !== undefined && metadata !== null && !isRecord(metadata)) { - return { error: `Design projects snapshot record '${record.id}' has malformed metadata; expected an object.` }; - } - for (const [label, value] of [ - [`${record.id}.metadata.baseDir`, metadata?.baseDir], - [`${record.id}.metadata.resolvedDir`, metadata?.resolvedDir], - [`${record.id}.baseDir`, record.baseDir], - [`${record.id}.resolvedDir`, record.resolvedDir], - ]) { - if (value !== undefined && value !== null && (typeof value !== "string" || !value.trim() || !isAbsolute(value))) { - return { error: `Design projects snapshot directory '${label}' is malformed; expected a nonempty absolute path.` }; - } - } - } - return { records: list }; -} - -// Inspect a recorded binding against a caller snapshot. States: -// matched (exact id plus agreeing source), unknown (identity not established and -// no guess is made), mismatch (identity found but source contradicts, or an -// explicit snapshot contradicts nothing matchable), ambiguous (several -// candidates, exact identity required), invalid (malformed input). -export function inspectDesignBinding({ repo = process.cwd(), binding = {}, projects } = {}) { - let root; - try { - root = resolve(repo); - } catch { - return { state: "invalid", reasons: ["Requested repository path is malformed."], selectedId: null, expectedRoot: null }; - } - if (!isRecord(binding)) { - return { state: "invalid", reasons: ["Product binding is unavailable or malformed; refusing to infer identity."], selectedId: null, expectedRoot: null }; - } - const recorded = binding["Design project"]; - if (recorded !== undefined && recorded !== null && recorded !== "" && (typeof recorded !== "string" || !recorded.trim())) { - return { state: "invalid", reasons: ["Recorded Design project identity has the wrong type; expected a string."], selectedId: null, expectedRoot: null }; - } - const sourceField = binding["Source workspace"] ?? binding.Repository ?? "."; - if (sourceField !== undefined && sourceField !== null && (typeof sourceField !== "string" || !sourceField.trim())) { - return { state: "invalid", reasons: ["Recorded source workspace is malformed; expected a nonempty string."], selectedId: null, expectedRoot: null }; - } - const normalized = normalizeProjects(projects); - if (normalized.error) { - return { state: "invalid", reasons: [normalized.error], selectedId: null, expectedRoot: null }; - } - const expectedRoot = canonical(root, sourceField ?? "."); - const storage = binding["Design storage"]; - if (storage !== undefined && storage !== null) { - if (typeof storage !== "string" || !storage.trim()) { - return { state: "invalid", reasons: ["Recorded Design storage is malformed; expected a nonempty path."], selectedId: null, expectedRoot }; - } - if (canonical(root, storage) !== expectedRoot) { - return { state: "mismatch", reasons: ["Recorded Design storage contradicts the intended source workspace; reconcile the binding before selecting a project."], selectedId: null, expectedRoot }; - } - } - if (normalized.absent) { - if (!recorded) { - return { state: "unknown", reasons: ["No recorded Design project and no caller snapshot supplied; live project state is unverified."], selectedId: null, expectedRoot }; - } - return { state: "unknown", reasons: [`No caller snapshot supplied; the recorded Design project '${recorded}' stays live-unverified. Lookup failure never creates a project.`], selectedId: null, expectedRoot }; - } - const records = normalized.records; - if (!recorded) { - if (records.length === 0) { - return { state: "unknown", reasons: ["No recorded Design project and the caller snapshot is empty; nothing to select."], selectedId: null, expectedRoot }; - } - if (records.length === 1) { - return { state: "unknown", reasons: [`No recorded Design project; refusing to select the only snapshot project '${records[0].id}' automatically.`], selectedId: null, expectedRoot }; - } - return { state: "ambiguous", reasons: [`No recorded Design project and ${records.length} snapshot projects; exact recorded identity is required.`], selectedId: null, expectedRoot }; - } - const exact = records.find((record) => record.id === recorded); - if (!exact) { - const named = records.filter((record) => record.name === recorded || record.title === recorded); - if (named.length === 1) { - return { state: "unknown", reasons: [`Snapshot project '${named[0].id}' matches by name-only; exact recorded id '${recorded}' is required, not a name.`], selectedId: null, expectedRoot }; - } - if (named.length > 1) { - return { state: "ambiguous", reasons: [`${named.length} snapshot projects share the name '${recorded}'; exact recorded id is required.`], selectedId: null, expectedRoot }; - } - const partial = records.filter((record) => [record.id, record.name, record.title].some((value) => typeof value === "string" && value.includes(recorded))); - if (partial.length === 1) { - return { state: "unknown", reasons: [`Single substring candidate '${partial[0].id}' for '${recorded}'; refusing to guess by substring, exact id required.`], selectedId: null, expectedRoot }; - } - if (partial.length > 1) { - return { state: "ambiguous", reasons: [`${partial.length} snapshot projects contain '${recorded}'; exact recorded id is required, not a substring.`], selectedId: null, expectedRoot }; - } - return { state: "unknown", reasons: [`Recorded Design project '${recorded}' is not in the caller snapshot (stale record or partial snapshot); lookup failure never creates a project.`], selectedId: null, expectedRoot }; - } - const metadata = isRecord(exact.metadata) ? exact.metadata : {}; - const dirs = [metadata.baseDir, metadata.resolvedDir, exact.baseDir, exact.resolvedDir].filter((value) => value !== undefined && value !== null); - if (dirs.length === 0) { - return { state: "unknown", reasons: [`Exact recorded project id '${recorded}' is present, but source directories are unavailable; identity alone cannot establish source correspondence.`], selectedId: recorded, expectedRoot }; - } - const canonicalDirs = dirs.map(value => canonical(root, value)); - if (new Set(canonicalDirs).size !== 1) { - return { state: "mismatch", reasons: ["Snapshot source directories contradict each other; refusing to hide conflicting paths behind fallback precedence."], selectedId: recorded, expectedRoot }; - } - const actual = canonicalDirs[0]; - if (actual === expectedRoot) { - return { state: "matched", reasons: [`Exact recorded project id '${recorded}' matched and snapshot directories agree with the expected source root '${expectedRoot}' (metadata evidence only, not tool/permission/auth/render readiness).`], selectedId: recorded, expectedRoot }; - } - if (expectedRoot.startsWith(actual + sep)) { - return { state: "mismatch", reasons: [`Snapshot directory '${actual}' is an ancestor of the expected source root '${expectedRoot}', not the exact root; refusing to retarget silently.`], selectedId: recorded, expectedRoot }; - } - if (actual.startsWith(expectedRoot + sep)) { - return { state: "mismatch", reasons: [`Snapshot directory '${actual}' is nested inside the expected source root '${expectedRoot}', not the exact root.`], selectedId: recorded, expectedRoot }; - } - return { state: "mismatch", reasons: [`Snapshot directories point at '${actual}', not the expected source root '${expectedRoot}'.`], selectedId: recorded, expectedRoot }; -} - -// Read a caller snapshot file for CLI parity. Throws a plain Error when the -// file is absent or unparsable; the caller reports it as unavailable/invalid. -export function readDesignSnapshotFile(path) { - let text; - try { - text = readFileSync(path, "utf8"); - } catch { - throw new Error(`Design projects snapshot file is unavailable: ${path}`); - } - try { - return JSON.parse(text); - } catch { - throw new Error(`Design projects snapshot file is malformed JSON: ${path}`); - } -} diff --git a/scripts/install-design-notifier.mjs b/scripts/install-design-notifier.mjs deleted file mode 100644 index 5b99760..0000000 --- a/scripts/install-design-notifier.mjs +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env node -// Install, verify, or remove the pinned design-notifier plugin copy. -// -// The runtime should not load the live development tree: a pinned copy under -// the OpenCode config directory means repo edits no longer reload the running -// service. Run this script after verified changes: -// -// node scripts/install-design-notifier.mjs # install/update the copy -// node scripts/install-design-notifier.mjs --verify # compare copy to manifest -// node scripts/install-design-notifier.mjs --uninstall -// -// The manifest (`installed.json`) records the source commit and a SHA256 per -// file so drift is detectable. - -import { cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises"; -import { createHash } from "node:crypto"; -import { spawnSync } from "node:child_process"; -import { homedir } from "node:os"; -import { dirname, join, relative, resolve, sep } from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; - -export const PLUGIN_NAME = "ksi-design-notifier"; - -export function resolveTargetDir({ env = process.env, home = homedir() } = {}) { - const configHome = - typeof env.XDG_CONFIG_HOME === "string" && env.XDG_CONFIG_HOME.trim() - ? env.XDG_CONFIG_HOME.trim() - : join(home, ".config"); - return join(configHome, "opencode", "plugins", "design-notifier"); -} - -export function resolveSourceDir(scriptURL = import.meta.url) { - const here = dirname(fileURLToPath(scriptURL)); - return resolve(join(here, "..", "plugins", "design-notifier")); -} - -async function walkFiles(root, current = root) { - const entries = await readdir(current, { withFileTypes: true }); - const files = []; - for (const entry of entries) { - const full = join(current, entry.name); - if (entry.isDirectory()) { - files.push(...(await walkFiles(root, full))); - } else if (entry.isFile()) { - files.push(relative(root, full).split(sep).join("/")); - } - } - return files.sort(); -} - -export async function hashTree(root) { - const files = await walkFiles(root); - const hashes = {}; - for (const file of files) { - // The manifest itself is not part of the hashed payload. - if (file === "installed.json") continue; - const data = await readFile(join(root, file)); - hashes[file] = createHash("sha256").update(data).digest("hex"); - } - return hashes; -} - -async function readPackageName(dir) { - try { - const pkg = JSON.parse(await readFile(join(dir, "package.json"), "utf8")); - return typeof pkg.name === "string" ? pkg.name : null; - } catch { - return null; - } -} - -async function assertOwnedTarget(targetDir) { - let info = null; - try { - info = await stat(targetDir); - } catch { - return; // does not exist yet - } - if (!info.isDirectory()) throw new Error(`target exists and is not a directory: ${targetDir}`); - const name = await readPackageName(targetDir); - if (name !== PLUGIN_NAME) { - throw new Error( - `refusing to touch ${targetDir}: package name is ${JSON.stringify(name)}, expected ${PLUGIN_NAME}`, - ); - } -} - -export async function installDesignNotifier({ - sourceDir = resolveSourceDir(), - targetDir = resolveTargetDir(), - now = () => new Date(), - commit = null, -} = {}) { - await assertOwnedTarget(targetDir); - // Stage the copy next to the target, then swap directories so the service - // never scans a partially written plugin directory (rm+rename window only). - const staging = `${targetDir}.staging-${process.pid}`; - await mkdir(dirname(targetDir), { recursive: true }); - await rm(staging, { recursive: true, force: true }); - try { - await cp(sourceDir, staging, { recursive: true, force: true }); - } catch (error) { - await rm(staging, { recursive: true, force: true }).catch(() => {}); - throw error; - } - const pkg = JSON.parse(await readFile(join(staging, "package.json"), "utf8")); - const files = await hashTree(staging); - const manifest = { - plugin: PLUGIN_NAME, - version: typeof pkg.version === "string" ? pkg.version : null, - commit, - sourceDir, - installedAt: now().toISOString(), - files, - }; - await rm(targetDir, { recursive: true, force: true }); - await rename(staging, targetDir); - await writeFile(join(targetDir, "installed.json"), `${JSON.stringify(manifest, null, 2)}\n`, { - mode: 0o600, - }); - return manifest; -} - -export async function verifyInstall({ targetDir = resolveTargetDir() } = {}) { - await assertOwnedTarget(targetDir); - let manifest; - try { - manifest = JSON.parse(await readFile(join(targetDir, "installed.json"), "utf8")); - } catch { - return { ok: false, reason: "installed.json missing or unreadable" }; - } - const expected = manifest.files && typeof manifest.files === "object" ? manifest.files : {}; - const actual = await hashTree(targetDir); - const changed = []; - const missing = []; - for (const [file, hash] of Object.entries(expected)) { - if (!(file in actual)) missing.push(file); - else if (actual[file] !== hash) changed.push(file); - } - const extra = Object.keys(actual).filter( - (file) => file !== "installed.json" && !(file in expected), - ); - return { - ok: changed.length === 0 && missing.length === 0 && extra.length === 0, - version: manifest.version ?? null, - commit: manifest.commit ?? null, - installedAt: manifest.installedAt ?? null, - changed, - missing, - extra, - }; -} - -export async function uninstallDesignNotifier({ targetDir = resolveTargetDir() } = {}) { - await assertOwnedTarget(targetDir); - await rm(targetDir, { recursive: true, force: true }); - return { removed: targetDir }; -} - -function gitCommit(cwd) { - try { - const result = spawnSync("git", ["-C", cwd, "rev-parse", "HEAD"], { encoding: "utf8" }); - if (result.status === 0) return result.stdout.trim(); - } catch {} - return null; -} - -async function main(argv) { - const args = argv.filter((arg) => arg !== "--"); - const flags = new Set(["--help", "-h", "--verify", "--uninstall", "--target", "--source"]); - for (let index = 0; index < args.length; index++) { - const arg = args[index]; - if (!flags.has(arg)) throw new Error(`unknown option: ${arg}`); - if (arg === "--target" || arg === "--source") { - if (!args[index + 1] || args[index + 1].startsWith("-")) throw new Error(`${arg} requires a path`); - index++; - } - } - if (args.includes("--help") || args.includes("-h")) { - console.log("Usage: node scripts/install-design-notifier.mjs [--verify | --uninstall] [--target PATH] [--source PATH]"); - console.log("No arguments installs/updates the pinned copy. --help and --verify do not write files."); - return; - } - const uninstall = args.includes("--uninstall"); - const verify = args.includes("--verify"); - if (uninstall && verify) throw new Error("--verify and --uninstall are mutually exclusive"); - const targetIndex = args.indexOf("--target"); - const sourceIndex = args.indexOf("--source"); - if (targetIndex !== -1 && !args[targetIndex + 1]) throw new Error("--target requires a path"); - if (sourceIndex !== -1 && !args[sourceIndex + 1]) throw new Error("--source requires a path"); - const targetArg = targetIndex === -1 ? undefined : args[targetIndex + 1]; - const sourceArg = sourceIndex === -1 ? undefined : args[sourceIndex + 1]; - - const sourceDir = sourceArg ? resolve(sourceArg) : resolveSourceDir(); - const targetDir = targetArg ? resolve(targetArg) : resolveTargetDir(); - - if (uninstall) { - const result = await uninstallDesignNotifier({ targetDir }); - console.log(`removed ${result.removed}`); - return; - } - if (verify) { - const result = await verifyInstall({ targetDir }); - console.log(JSON.stringify(result, null, 2)); - process.exitCode = result.ok ? 0 : 1; - return; - } - const manifest = await installDesignNotifier({ - sourceDir, - targetDir, - commit: gitCommit(resolve(join(dirname(fileURLToPath(import.meta.url)), ".."))), - }); - console.log(`installed ${manifest.plugin}@${manifest.version} -> ${targetDir}`); - console.log(`commit ${manifest.commit ?? "(unknown)"} files ${Object.keys(manifest.files).length}`); - console.log("If a plugins config entry pointed at the development tree, remove it so only the pinned copy loads."); -} - -const isMain = - typeof process.argv[1] === "string" && import.meta.url === pathToFileURL(process.argv[1]).href; -if (isMain) { - main(process.argv.slice(2)).catch((error) => { - console.error(`install-design-notifier: ${error?.message ?? error}`); - process.exitCode = 1; - }); -} diff --git a/scripts/release-check.mjs b/scripts/release-check.mjs deleted file mode 100644 index 23b8c94..0000000 --- a/scripts/release-check.mjs +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env node -// Release preflight for maintainers. -// -// Repository-only tooling: this file is NOT part of the npm tarball (see the -// `files` list in package.json). Publishing, tag pushing and channel changes -// remain separate, explicitly authorized steps (docs/releasing.md). -// -// Usage: -// npm run release:check # pins + git state + source/package checks -// -// Exit code 0 means the local preflight passed; it does not authorize or -// perform any registry write. - -import { readFile } from "node:fs/promises"; -import { spawnSync } from "node:child_process"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; - -const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); - -export function extractVersionPins(text) { - const source = String(text ?? ""); - const pins = []; - for (const match of source.matchAll(/package=ksi-opencode-harness@([0-9][^\s`"]*)/g)) { - pins.push(match[1]); - } - for (const match of source.matchAll(/Beta\s+`([0-9][^`]*)`/g)) { - pins.push(match[1]); - } - return pins; -} - -export function checkVersionPins({ version, readme, install }) { - const pins = [...extractVersionPins(readme), ...extractVersionPins(install)]; - const problems = []; - if (pins.length === 0) { - problems.push("no version pins found in README/INSTALL"); - return { ok: false, problems, pins }; - } - for (const pin of pins) { - if (pin !== version) { - problems.push(`pin ${pin} does not match package version ${version}`); - } - } - return { ok: problems.length === 0, problems, pins }; -} - -function git(args) { - const result = spawnSync("git", args, { cwd: ROOT, encoding: "utf8" }); - return { - ok: result.status === 0, - out: (result.stdout ?? "").trim(), - err: (result.stderr ?? "").trim(), - }; -} - -function run(command, args) { - const result = spawnSync(command, args, { cwd: ROOT, encoding: "utf8" }); - const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(); - const tail = output.split("\n").slice(-3).join("\n"); - return { ok: result.status === 0, tail }; -} - -async function main() { - const problems = []; - - const pkg = JSON.parse(await readFile(join(ROOT, "package.json"), "utf8")); - const version = pkg.version; - const readme = await readFile(join(ROOT, "README.md"), "utf8"); - const install = await readFile(join(ROOT, "INSTALL.md"), "utf8"); - - const pins = checkVersionPins({ version, readme, install }); - console.log(`version: ${version} | pins: ${pins.pins.length}`); - for (const problem of pins.problems) problems.push(`pins: ${problem}`); - - const status = git(["status", "--porcelain"]); - if (!status.ok) problems.push(`git status failed: ${status.err}`); - else if (status.out) problems.push(`working tree not clean:\n${status.out}`); - - const branch = git(["status", "--branch", "--porcelain"]); - console.log(`git: ${branch.out.split("\n")[0] ?? "(unknown branch)"}`); - - const check = run("npm", ["run", "check"]); - console.log(`npm run check: ${check.ok ? "pass" : "FAIL"}`); - if (!check.ok) problems.push(`npm run check failed:\n${check.tail}`); - - const packageCheck = run("npm", ["run", "check:package"]); - console.log(`npm run check:package: ${packageCheck.ok ? "pass" : "FAIL"}`); - if (!packageCheck.ok) problems.push(`npm run check:package failed:\n${packageCheck.tail}`); - - console.log(""); - if (problems.length > 0) { - console.log("release preflight: FAIL"); - for (const problem of problems) console.log(`- ${problem}`); - process.exitCode = 1; - return; - } - - console.log("release preflight: pass"); - console.log("remaining manual steps (see docs/releasing.md):"); - console.log("- node scripts/verify-native-v2-isolated.mjs"); - console.log("- npm pack --dry-run --json (review the tarball inventory)"); - console.log("- publish only with explicit authorization, then verify the registry"); - console.log(`- tag the release commit (v${version}) and push the tag after verification`); -} - -const isMain = - typeof process.argv[1] === "string" && import.meta.url === pathToFileURL(process.argv[1]).href; -if (isMain) { - main().catch((error) => { - console.error(`release-check: ${error?.message ?? error}`); - process.exitCode = 1; - }); -} diff --git a/scripts/verify-native-v2-isolated.mjs b/scripts/verify-native-v2-isolated.mjs deleted file mode 100644 index 0821adb..0000000 --- a/scripts/verify-native-v2-isolated.mjs +++ /dev/null @@ -1,277 +0,0 @@ -import { spawn, spawnSync } from "node:child_process" -import { mkdtemp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { join } from "node:path" -import { fileURLToPath } from "node:url" - -const root = fileURLToPath(new URL("..", import.meta.url)) -const roles = { - build: "primary", plan: "primary", - explore: "subagent", developer: "subagent", - reviewer: "subagent", -} - -export const RETIRED_DESIGN_SKILLS = ["frontend-design", "impeccable-design-polish", "web-design-guidelines"] - -const designPermissionCases = [ - ...["get_active_context", "get_project", "list_projects", "list_files", "get_file", "get_artifact", "search_files", "list_skills", "list_plugins", "list_agents", "get_run"] - .map((name) => ({ agent: "plan", action: `opendesign_${name}`, expected: "allow" })), - ...["start_run", "cancel_run", "write_file", "delete_file", "create_project", "delete_project", "create_artifact", "collect_brief", "confirm_brief", "future_mutation"] - .map((name) => ({ agent: "plan", action: `opendesign_${name}`, expected: "deny" })), - { agent: "build", action: "opendesign_start_run", expected: "allow" }, - { agent: "plan", action: "edit", expected: "deny" }, -] - -const helperPermissionCases = ["reviewer"].flatMap(agent => [ - { agent, action: "edit", resources: ["src/permission-boundary-probe.mjs"], expected: "deny" }, - { agent, action: "subagent", resources: ["general"], expected: "deny" }, -]) - -// These decisions describe inherited access; no observation is an isolation guarantee. -const helperPermissionObservations = ["reviewer"].flatMap(agent => [ - { agent, action: "shell", resources: ["node -e synthetic_permission_probe"] }, - { agent, action: "execute", resources: ["*"] }, - { agent, action: "opendesign_write_file", resources: ["*"] }, -]) - -export function buildIsolatedEnv(directory, path) { - return { - PATH: path, - HOME: join(directory, "home"), - XDG_CONFIG_HOME: join(directory, "home", ".config"), - XDG_DATA_HOME: join(directory, "data"), - XDG_CACHE_HOME: join(directory, "cache"), - XDG_STATE_HOME: join(directory, "state"), - TMPDIR: directory, - } -} - -export function checkAgents(agents) { - const failures = [] - for (const [id, mode] of Object.entries(roles)) { - const agent = agents.find((item) => item.id === id) - if (!agent) failures.push(`${id} absent from effective agent catalog`) - else if (agent.mode !== mode) failures.push(`${id} expected ${mode}, got ${agent.mode}`) - } - const developer = agents.find((item) => item.id === "developer") - const model = developer?.model - const modelText = typeof model === "string" ? model - : model?.providerID && (model?.id || model?.model) ? `${model.providerID}/${model.id ?? model.model}${model.variant ? `#${model.variant}` : ""}` : null - if (developer && modelText !== "opencode-go/deepseek-v4.1-flash") failures.push(`developer model not retained: ${String(modelText)}`) - if (developer && developer.steps !== 47) failures.push(`developer steps not retained: ${String(developer.steps)}`) - if (agents.some((item) => item.id === "design")) failures.push("retired design role still installed") - if (agents.some((item) => item.id === "research")) failures.push("retired research role still installed") - if (agents.some((item) => item.id === "design-critic")) failures.push("retired design-critic role still installed") - if (agents.some((item) => item.id === "test-runner")) failures.push("retired test-runner role still installed") - for (const id of ["developer", "reviewer"]) { - const agent = agents.find((item) => item.id === id) - if (agent?.permissions?.some((rule) => rule.action === "skill" && rule.resource === "*" && rule.effect === "deny")) { - failures.push(`${id} blocks native skill discovery`) - } - } - if (developer && !developer.permissions?.some((rule) => rule.action === "subagent" && rule.resource === "*" && rule.effect === "deny")) { - failures.push("developer delegation is not default-denied") - } - return { ok: failures.length === 0, failures } -} - -export function checkRetiredSkills(skills) { - const failures = [] - for (const id of RETIRED_DESIGN_SKILLS) { - if (skills.some((item) => item.id === id)) failures.push(`retired design skill still installed: ${id}`) - } - return { ok: failures.length === 0, failures } -} - -export function checkDesignPermissions(decisions) { - return checkPermissionDecisions(decisions, designPermissionCases, "design") -} - -export function checkHelperPermissions(decisions) { - return checkPermissionDecisions(decisions, helperPermissionCases, "helper") -} - -function checkPermissionDecisions(decisions, cases, label) { - const failures = [] - if (!decisions.length) return { ok: false, failures: [`${label} permissions were not evaluated`] } - if (decisions.length !== cases.length) failures.push(`expected ${cases.length} ${label} permission decisions, got ${decisions.length}`) - for (const { agent, action, expected } of cases) { - const matches = decisions.filter((item) => item.agent === agent && item.action === action) - if (matches.length !== 1) { - failures.push(`${agent} ${action}: expected one decision, got ${matches.length}`) - continue - } - const decision = matches[0] - if (decision.expected !== expected) failures.push(`${agent} ${action}: declared expectation must be ${expected}`) - if (decision.effect !== expected) failures.push(`${agent} ${action}: expected ${expected}, got ${String(decision.effect)}`) - } - return { ok: failures.length === 0, failures } -} - -function run(command, args, env, cwd) { - const child = spawnSync(command, args, { env, cwd, encoding: "utf8", timeout: 15000, maxBuffer: 8 * 1024 * 1024 }) - return { status: child.status, stdout: child.stdout ?? "", stderr: child.stderr ?? "", error: child.error?.message } -} - -function parseJson(result, label) { - if (result.status !== 0) throw new Error(`${label} exited ${String(result.status)}: ${result.error ?? result.stderr.slice(0, 300)}`) - try { return JSON.parse(result.stdout) } catch { throw new Error(`${label} did not return JSON`) } -} - -export function parseServeStartup(output) { - const url = output.match(/(?:^|\n)server listening on (http:\/\/127\.0\.0\.1:[1-9]\d*)(?:\r?\n|$)/)?.[1] - const password = output.match(/(?:^|\n)server password ([^\s\r\n]+)(?:\r?\n|$)/)?.[1] - return url && password ? { url, password } : null -} - -function startIsolatedServer(env, cwd) { - const child = spawn("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], { - env, cwd, stdio: ["ignore", "pipe", "pipe"], - }) - const ready = new Promise((resolve, reject) => { - let output = "" - const timer = setTimeout(() => reject(new Error("isolated OpenCode server startup timed out")), 15000) - const onData = (chunk) => { - output = (output + chunk.toString()).slice(-4096) - const started = parseServeStartup(output) - if (started) { - clearTimeout(timer) - resolve(started) - } - } - child.stdout.on("data", onData) - child.stderr.on("data", onData) - child.once("error", (error) => { - clearTimeout(timer) - reject(new Error(`isolated OpenCode server failed to start: ${error.message}`)) - }) - child.once("exit", (code) => { - clearTimeout(timer) - reject(new Error(`isolated OpenCode server exited before ready: ${String(code)}`)) - }) - }) - return { child, ready } -} - -async function stopIsolatedServer(child) { - if (!child || child.exitCode !== null || child.signalCode !== null) return - child.kill("SIGTERM") - await Promise.race([ - new Promise((resolve) => child.once("exit", resolve)), - new Promise((resolve) => setTimeout(resolve, 3000)), - ]) - if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL") -} - -async function getCatalog({ url, password }, path) { - const response = await fetch(`${url}${path}`, { - headers: { authorization: `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` }, - redirect: "error", - signal: AbortSignal.timeout(10000), - }) - if (!response.ok) throw new Error(`${path} returned HTTP ${response.status}`) - return response.json() -} - -async function postApi({ url, password }, path, body) { - const response = await fetch(`${url}${path}`, { - method: "POST", - headers: { - authorization: `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`, - "content-type": "application/json", - }, - body: JSON.stringify(body), - redirect: "error", - signal: AbortSignal.timeout(10000), - }) - if (!response.ok) throw new Error(`${path} returned HTTP ${response.status}`) - return response.json() -} - -async function evaluatePermissions(connection, cases, check, label, observationCases = []) { - // Native policy evaluation only: no MCP server, tool execution or provider call. - const session = await postApi(connection, "/api/session", { title: `Isolated ${label}-permission verification` }) - const id = session.data?.id - if (!id) throw new Error("isolated permission session has no id") - const decisions = [] - for (const item of cases) { - const result = await postApi(connection, `/api/session/${id}/permission`, { - agent: item.agent, action: item.action, resources: item.resources ?? ["*"], - }) - decisions.push({ ...item, effect: result.data?.effect }) - } - const evidence = { ...check(decisions), decisions, toolsInvoked: 0 } - if (observationCases.length) { - evidence.observations = [] - for (const item of observationCases) { - const result = await postApi(connection, `/api/session/${id}/permission`, item) - evidence.observations.push({ ...item, effect: result.data?.effect }) - } - evidence.scope = "Sampled direct edit/delegation denials only; shell/Code Mode/MCP observations are not all-write isolation or tool-execution evidence." - } - return evidence -} - -async function verify() { - const directory = await mkdtemp(join(tmpdir(), "ksi-native-v2-")) - const env = buildIsolatedEnv(directory, process.env.PATH ?? "") - let server - try { - const config = join(env.XDG_CONFIG_HOME, "opencode") - await mkdir(config, { recursive: true }) - const cli = join(root, "bin", "ksi-opencode.mjs") - parseJson(run(process.execPath, [cli, "install", "--target", config, "--apply"], env, directory), "native installer") - // The maintained example uses comment-only lines, not arbitrary JSONC parsing. - const sampleText = await readFile(join(root, "opencode.jsonc.example"), "utf8") - const sample = JSON.parse(sampleText.split("\n").filter((line) => !/^\s*\/\//.test(line)).join("\n")) - await writeFile(join(config, "opencode.jsonc"), `${JSON.stringify({ agents: { - developer: { model: "opencode-go/deepseek-v4.1-flash", steps: 47 }, - ...(sample.agents?.plan ? { plan: sample.agents.plan } : {}), - } }, null, 2)}\n`) - const source = await readFile(join(config, "agents", "developer.md"), "utf8") - const installedAgents = (await readdir(join(config, "agents"))).sort() - const configEntries = (await readdir(config)).sort() - const files = { - installed: installedAgents.join(",") === ["developer.md", "reviewer.md"].join(","), - modelAndStepsOmitted: !/\n(?:model|steps):/.test(source), - builtinsUntouched: !installedAgents.some((name) => ["build.md", "plan.md", "explore.md"].includes(name)), - noSkillsInstalled: !configEntries.includes("skills"), - commandsNotInstalled: configEntries.join(",") === "agents,opencode.jsonc", - } - server = startIsolatedServer(env, directory) - const connection = await server.ready - let agents = { ok: false, failures: ["agent catalog not queried"] } - let agentAttempts = 0 - for (let attempt = 1; attempt <= 8; attempt++) { - agentAttempts = attempt - const catalog = await getCatalog(connection, "/api/agent") - agents = checkAgents(catalog.data ?? []) - if (agents.ok) break - await new Promise((resolve) => setTimeout(resolve, 100)) - } - let skills = { ok: false, failures: ["skill catalog not queried"] } - let skillAttempts = 0 - for (let attempt = 1; attempt <= 8; attempt++) { - skillAttempts = attempt - const catalog = await getCatalog(connection, "/api/skill") - skills = checkRetiredSkills(catalog.data ?? []) - if (skills.ok) break - await new Promise((resolve) => setTimeout(resolve, 100)) - } - const designPermissions = await evaluatePermissions(connection, designPermissionCases, checkDesignPermissions, "design") - const helperPermissions = await evaluatePermissions(connection, helperPermissionCases, checkHelperPermissions, "helper", helperPermissionObservations) - const result = { version: run("opencode", ["--version"], env, directory).stdout.trim(), files, agents: { ...agents, attempts: agentAttempts }, retiredSkills: { ...skills, attempts: skillAttempts }, designPermissions, helperPermissions, providerRequestsIssuedByVerifier: 0, childEgress: "not monitored", isolated: true } - process.stdout.write(`${JSON.stringify(result, null, 2)}\n`) - if (!files.installed || !files.modelAndStepsOmitted || !files.builtinsUntouched || !files.noSkillsInstalled || !files.commandsNotInstalled || !agents.ok || !skills.ok || !designPermissions.ok || !helperPermissions.ok) process.exitCode = 1 - } finally { - await stopIsolatedServer(server?.child) - await rm(directory, { recursive: true, force: true }) - } -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) { - verify().catch((error) => { - console.error(error instanceof Error ? error.message : String(error)) - process.exitCode = 1 - }) -} diff --git a/scripts/verify-tool-choice-isolated.mjs b/scripts/verify-tool-choice-isolated.mjs deleted file mode 100644 index c477026..0000000 --- a/scripts/verify-tool-choice-isolated.mjs +++ /dev/null @@ -1,132 +0,0 @@ -// Characterize the native final-step request against an auto-only local provider. -// Never inherits credentials or contacts a shared session/real model provider. -import { createServer } from "node:http"; -import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises"; -import { spawn } from "node:child_process"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { pathToFileURL } from "node:url"; -import { buildIsolatedEnv, parseServeStartup } from "./verify-native-v2-isolated.mjs"; - -export function selectFinalAssistant(messages, requestCount) { - if (requestCount < 2 || !Array.isArray(messages) || messages.length < 2) return undefined; - const message = messages[1]; - return message?.time?.completed ? message : undefined; -} - -async function runCase({ override, terminalTool = false }) { - const directory = await mkdtemp(join(tmpdir(), "ksi-tool-choice-")); - const requests = []; - let child, connection, output = ""; - const provider = createServer(async (req, res) => { - try { - if (req.method === "GET") { - res.setHeader("content-type", "application/json"); - res.end(JSON.stringify({ object: "list", data: [{ id: "fixture", object: "model" }] })); - return; - } - let raw = ""; - for await (const chunk of req) raw += chunk; - const body = JSON.parse(raw); - requests.push({ toolChoice: body.tool_choice ?? null }); - if (body.tool_choice && body.tool_choice !== "auto") { - res.writeHead(400, { "content-type": "application/json" }); - res.end(JSON.stringify({ error: { message: "only auto is supported for tool_choice", type: "invalid_request_error" } })); - return; - } - const callsTool = requests.length === 1 || terminalTool; - const delta = callsTool - ? { role: "assistant", tool_calls: [{ index: 0, id: `call_fixture_${requests.length}`, type: "function", function: { name: "read", arguments: JSON.stringify({ path: join(directory, "fixture.txt") }) } }] } - : { role: "assistant", content: "bounded-summary" }; - res.writeHead(200, { "content-type": "text/event-stream" }); - const chunk = choices => ({ id: "chatcmpl_fixture", object: "chat.completion.chunk", created: 1, model: "fixture", choices }); - res.write(`data: ${JSON.stringify(chunk([{ index: 0, delta, finish_reason: null }]))}\n\n`); - res.write(`data: ${JSON.stringify(chunk([{ index: 0, delta: {}, finish_reason: callsTool ? "tool_calls" : "stop" }]))}\n\n`); - res.end("data: [DONE]\n\n"); - } catch { - res.writeHead(500).end(); - } - }); - await new Promise(resolve => provider.listen(0, "127.0.0.1", resolve)); - const env = buildIsolatedEnv(directory, process.env.PATH ?? ""); - async function api(path, body) { - const response = await fetch(connection.url + path, { - method: body === undefined ? "GET" : "POST", - headers: { authorization: `Basic ${Buffer.from(`opencode:${connection.password}`).toString("base64")}`, "content-type": "application/json" }, - ...(body === undefined ? {} : { body: JSON.stringify(body) }), - redirect: "error", signal: AbortSignal.timeout(12000), - }); - if (!response.ok) throw new Error(`isolated API returned HTTP ${response.status}`); - return response.json(); - } - try { - const config = join(env.XDG_CONFIG_HOME, "opencode"); - await mkdir(config, { recursive: true }); - await writeFile(join(directory, "fixture.txt"), "read-only fixture\n"); - await writeFile(join(config, "opencode.json"), JSON.stringify({ - model: "fixture/fixture", default_agent: "probe", - agents: { probe: { mode: "primary", steps: 2, system: "Read the fixture once, then summarize. No edits.", permissions: [{ action: "*", resource: "*", effect: "deny" }, { action: "read", resource: join(directory, "*"), effect: "allow" }] } }, - providers: { fixture: { package: "@opencode/ai/providers/openai-compatible", settings: { baseURL: `http://127.0.0.1:${provider.address().port}/v1`, apiKey: "non-secret-fixture" }, models: { fixture: { capabilities: { tools: true, input: ["text"], output: ["text"] }, ...(override ? { body: { tool_choice: "auto" } } : {}) } } } }, - })); - child = spawn("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], { env, cwd: directory, stdio: ["ignore", "pipe", "pipe"] }); - connection = await new Promise((resolve, reject) => { - const timer = setTimeout(() => reject(new Error("isolated startup timeout")), 15000); - const data = chunk => { - output = (output + chunk.toString()).slice(-4096); - const ready = parseServeStartup(output); - if (ready) { clearTimeout(timer); resolve(ready); } - }; - child.stdout.on("data", data); child.stderr.on("data", data); - child.once("error", error => { clearTimeout(timer); reject(error); }); - child.once("exit", code => { clearTimeout(timer); reject(new Error(`isolated startup exit ${code}`)); }); - }); - const model = { providerID: "fixture", id: "fixture" }; - const session = (await api("/api/session", { title: "Local step-limit compatibility probe", agent: "probe", model })).data.id; - await api(`/api/session/${session}/prompt`, { text: "Read fixture.txt once and summarize." }); - let final; - for (let attempt = 0; attempt < 60; attempt++) { - await new Promise(resolve => setTimeout(resolve, 200)); - const messages = (await api(`/api/session/${session}/message?type=assistant&order=asc&limit=3`)).data; - const message = selectFinalAssistant(messages, requests.length); - if (message) { - const content = message.content ?? []; - final = { - text: content.filter(item => item.type === "text").map(item => item.text).join(""), - toolErrors: content.filter(item => item.type === "tool").map(item => item.state?.error?.message ?? item.state?.error ?? ""), - }; - break; - } - } - if (!final) throw new Error("isolated final-step probe timed out"); - const agent = (await api("/api/agent/probe")).data; - return { override, terminalTool, steps: agent.steps, requests, final }; - } finally { - if (child && child.exitCode === null && child.signalCode === null) { - child.kill("SIGTERM"); - await Promise.race([new Promise(resolve => child.once("exit", resolve)), new Promise(resolve => setTimeout(resolve, 3000))]); - if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL"); - } - provider.closeAllConnections(); - await new Promise(resolve => provider.close(resolve)); - await rm(directory, { recursive: true, force: true }); - } -} - -export async function verifyToolChoice() { - const baseline = await runCase({ override: false }); - const compatible = await runCase({ override: true }); - const limit = await runCase({ override: true, terminalTool: true }); - const failures = []; - if (baseline.requests.length !== 2 || baseline.requests[1].toolChoice !== "none" || baseline.final.text !== "") failures.push("native final-step rejection was not reproduced"); - if (compatible.requests.length !== 2 || compatible.requests[1].toolChoice !== "auto" || compatible.final.text !== "bounded-summary") failures.push("model-scoped overlay did not recover the final summary"); - if (limit.requests.length !== 2 || !limit.final.toolErrors.some(error => /Tools are disabled after the maximum agent steps/.test(error))) failures.push("terminal tool call bypassed the native step limit"); - if ([baseline, compatible, limit].some(result => result.steps !== 2)) failures.push("native step allowance changed"); - return { ok: failures.length === 0, failures, baseline, compatible, limit, sharedSessionsTouched: false, realProviderCalls: 0, inheritedCredentials: false, childEgress: "not monitored" }; -} - -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - verifyToolChoice().then(result => { - console.log(JSON.stringify(result, null, 2)); - if (!result.ok) process.exitCode = 1; - }).catch(error => { console.error(error.message); process.exitCode = 1; }); -} diff --git a/scripts/work-doctor.mjs b/scripts/work-doctor.mjs deleted file mode 100644 index dec0000..0000000 --- a/scripts/work-doctor.mjs +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env node -// Source-checkout helper, not an installer or runtime health probe. It reads -// known local metadata only; commands/URLs in project records are never run. -import { statSync, accessSync, constants } from "node:fs"; -import { resolve, join } from "node:path"; -import { homedir } from "node:os"; -import { pathToFileURL } from "node:url"; -import { readProjectText, section } from "./work-status.mjs"; -import { inspectDesignBinding, readDesignSnapshotFile } from "./design-binding.mjs"; - -const usage = "Usage: node scripts/work-doctor.mjs [--repo PATH] [--config PATH] [--frontend] [--json] [--design-projects FILE]"; -const fields = ["Repository", "Start command", "Verify command"]; -// Optional recorded identity/source fields. They are read from the existing -// table only; product identity is never invented from folder or Git state, and -// branch/revision presence is not a verification claim. -const identityFields = ["Product", "Product identity", "Source workspace", "Branch", "Revision", "Design exception"]; -const frontendFields = ["Design project", "Design storage", "Entry", "Product URL", "Brand source"]; - -function filePresent(path) { - try { return statSync(path).isFile(); } catch { return false; } -} - -function browserPresent() { - return (process.env.PATH ?? "").split(":").filter(Boolean).some(dir => - ["playwright-cli", "agent-browser"].some(name => { - try { accessSync(join(dir, name), constants.X_OK); return filePresent(join(dir, name)); } catch { return false; } - }), - ); -} - -export function readProductBinding(repo) { - let body = section(readProjectText(repo, "docs/superpowers/product-state.md"), "Product binding"); - if (!body) { - const checkpoint = readProjectText(repo, ".opencode/working-state.md"); - const path = checkpoint?.match(/ledger:\s*(docs\/superpowers\/plans\/[a-zA-Z0-9_-]+\.md)\s*$/m)?.[1]; - if (path) body = section(readProjectText(repo, path), "Product binding"); - } - const binding = {}; - for (const line of body.split("\n")) { - const match = line.match(/^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*$/); - if (!match || ![...fields, ...identityFields, ...frontendFields].includes(match[1])) continue; - // Unknown/placeholder values cannot satisfy a prerequisite. - if (!/^(?:unknown|not recorded|TBD|[-—])$/i.test(match[2])) binding[match[1]] = match[2]; - } - return binding; -} - -export function inspectReadiness({ repo = process.cwd(), configDir = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode"), frontend = false, projects, projectsError } = {}) { - const root = resolve(repo); - const config = resolve(configDir); - const checks = []; - let repositoryPresent = false; - try { repositoryPresent = statSync(root).isDirectory(); } catch {} - checks.push({ id: "repository", state: repositoryPresent ? "present" : "missing", detail: "Requested working directory presence; product binding still needs verification." }); - const roles = ["developer", "reviewer"]; - const missingRoles = roles.filter(name => !filePresent(join(config, `agents/${name}.md`)) && !filePresent(join(root, `.opencode/agents/${name}.md`))); - // The Developer/Reviewer bundle is optional: native-first operation - // (Build/Plan/General/Explore + optional Reviewer/CE) works without these - // files, so their absence is informational and never a setup blocker. - checks.push({ id: "roles", state: missingRoles.length ? "optional" : "present", detail: missingRoles.length ? `Optional KSI role bundle not installed (${missingRoles.join(", ")}); the native-first operation does not require it.` : "Optional KSI role bundle present; effective role/model/permission catalog not inspected." }); - const policyPresent = filePresent(join(root, "AGENTS.md")) || filePresent(join(config, "AGENTS.md")); - checks.push({ id: "policy", state: policyPresent ? "present" : "missing", detail: "File presence only; adopted policy and instruction precedence require agent inspection." }); - const binding = readProductBinding(root); - const required = frontend ? [...fields, ...frontendFields] : fields; - const missing = required.filter(name => !binding[name]); - const mismatch = binding.Repository && resolve(root, binding.Repository) !== root; - checks.push({ id: "binding", state: mismatch ? "mismatch" : missing.length ? "unknown" : "present", detail: mismatch ? "Recorded repository differs from the requested working directory; verify the actual project." : missing.length ? `Binding not fully recorded: ${missing.join(", ")}` : "Binding recorded; actual storage/read/write/URL access is not proven." }); - if (frontend) checks.push({ id: "browser", state: browserPresent() ? "present" : "unknown", detail: "Executable presence only; launch/sandbox/product interaction not tested." }); - // Bounded representative-project metadata check. A missing caller snapshot - // stays live-unknown and preserves legacy record-only behavior; an explicit - // snapshot (or an unreadable explicit file) that fails to establish the exact - // recorded identity needs preparation. A match is metadata evidence only, - // never tool/permission/auth/render readiness, and never creates a project. - const explicitSnapshot = projects !== undefined; - let designState = null; - if (binding["Design project"] || explicitSnapshot || projectsError) { - const design = projectsError - ? { state: "invalid", reasons: [`Design projects snapshot unavailable: ${projectsError}`] } - : inspectDesignBinding({ repo: root, binding, projects }); - designState = design.state; - checks.push({ - id: "design-binding", - state: design.state, - detail: `${design.reasons.join(" ")} Snapshot match is metadata evidence only, not tool/permission/auth/render readiness; no project is created automatically, and recorded commands/URLs are never executed here.`, - }); - } - checks.push({ id: "notifier", state: filePresent(join(config, "plugins/design-notifier/installed.json")) ? "present" : "unknown", detail: "Optional runtime manifest presence; activation/revision/delivery not verified." }); - checks.push({ id: "effective-tools", state: "unknown", detail: "Use native catalog and real project read/changed-flow checks; no credentials, service calls or commands executed here." }); - const designNeedsSetup = designState === "mismatch" || designState === "invalid" || designState === "ambiguous" || (designState === "unknown" && explicitSnapshot); - const needsSetup = !repositoryPresent || !policyPresent || mismatch || designNeedsSetup || (frontend && missing.length > 0); - return { - status: needsSetup ? "needs-setup" : "unverified", - scope: "local metadata only; not runtime readiness, product acceptance or deployment", - repo: root, - checks, - }; -} - -export function renderReadiness(result) { - return [`[Preparation: ${result.status}]`, result.scope, ...result.checks.map(check => ` ${check.id}: ${check.state} — ${check.detail}`)].join("\n"); -} - -function main(args) { - const options = {}; - let json = false; - for (let i = 0; i < args.length; i += 1) { - const arg = args[i]; - if ((arg === "--repo" || arg === "--config") && args[i + 1] && !args[i + 1].startsWith("--")) options[arg === "--repo" ? "repo" : "configDir"] = args[++i]; - else if (arg === "--design-projects" && args[i + 1] && !args[i + 1].startsWith("--")) { - const file = args[++i]; - try { - options.projects = readDesignSnapshotFile(file); - } catch (error) { - options.projectsError = error.message; - } - } - else if (arg === "--frontend") options.frontend = true; - else if (arg === "--json") json = true; - else throw new Error(usage); - } - const result = inspectReadiness(options); - console.log(json ? JSON.stringify(result, null, 2) : renderReadiness(result)); -} - -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - try { main(process.argv.slice(2)); } catch (error) { - console.error(error.message); - process.exitCode = 1; - } -} diff --git a/scripts/work-report.mjs b/scripts/work-report.mjs deleted file mode 100644 index f28cd53..0000000 --- a/scripts/work-report.mjs +++ /dev/null @@ -1,362 +0,0 @@ -#!/usr/bin/env node -// Read-only consolidated work report for the KSI OpenCode workflow: -// goals, design-run deliveries, recent usage, worktrees and backlog next -// slices. Opens the session database in read-only mode and never writes to -// any inspected state. -// -// Usage: -// node scripts/work-report.mjs # last 7 days, cwd repo -// node scripts/work-report.mjs --days 30 -// node scripts/work-report.mjs --repo /path/to/repo -// node scripts/work-report.mjs --json - -import { execFileSync } from "node:child_process"; -import { existsSync, readFileSync } from "node:fs"; -import { homedir } from "node:os"; -import { join, resolve } from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; -import { inspectWorkStatus, renderWorkStatus } from "./work-status.mjs"; -import { classifyRun } from "../plugins/design-notifier/lib/messages.js"; -export { inspectWorkStatus, renderWorkStatus } from "./work-status.mjs"; -export { inspectReadiness } from "./work-doctor.mjs"; - -function envPath(name, fallback) { - const value = process.env[name]; - return typeof value === "string" && value.trim() ? value.trim() : fallback; -} - -export function defaultPaths(env = process.env) { - const home = env.HOME?.trim() || homedir(); - const dataHome = env.XDG_DATA_HOME?.trim() || join(home, ".local", "share"); - const stateHome = env.XDG_STATE_HOME?.trim() || join(home, ".local", "state"); - return { - goals: envPath("KSI_WORK_REPORT_GOALS", join(dataHome, "opencode-goal-plugin", "goals.json")), - bindings: envPath("KSI_WORK_REPORT_BINDINGS", join(stateHome, "opencode-design-notifier", "bindings.json")), - paused: envPath("KSI_WORK_REPORT_PAUSED", join(stateHome, "opencode-design-notifier", "paused.json")), - db: envPath("KSI_WORK_REPORT_DB", join(dataHome, "opencode", "opencode.db")), - }; -} - -function oneLine(value) { - return String(value ?? "") - .replace(/\s+/g, " ") - .trim(); -} - -function numberOrNull(value) { - return typeof value === "number" && Number.isFinite(value) ? value : null; -} - -// Goal state stores seconds; session state stores milliseconds. -function toMillis(value) { - const n = numberOrNull(value); - if (n === null) return null; - return n > 1e12 ? n : n * 1000; -} - -export function formatTokens(value) { - if (typeof value !== "number" || !Number.isFinite(value)) return "?"; - const abs = Math.abs(value); - if (abs >= 1e9) return `${(value / 1e9).toFixed(2)}B`; - if (abs >= 1e6) return `${(value / 1e6).toFixed(1)}M`; - if (abs >= 1e3) return `${(value / 1e3).toFixed(1)}k`; - return String(value); -} - -function formatTime(ms) { - if (typeof ms !== "number" || !Number.isFinite(ms)) return "?"; - const d = new Date(ms); - const pad = (n) => String(n).padStart(2, "0"); - return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; -} - -export function parseNextSlices(markdown) { - const lines = String(markdown ?? "").split(/\r?\n/); - const start = lines.findIndex((line) => /^##\s+Next slices/i.test(line)); - if (start === -1) return []; - const items = []; - for (let i = start + 1; i < lines.length; i += 1) { - const line = lines[i]; - if (/^##\s+/.test(line)) break; - const match = line.match(/^\s*-\s+(.+)$/); - if (match) items.push(match[1].trim()); - } - return items; -} - -export function summarizeGoals(raw) { - const container = raw && typeof raw === "object" ? raw : {}; - const goalsRaw = container.goals ?? container; - const list = Array.isArray(goalsRaw) ? goalsRaw : Object.values(goalsRaw ?? {}); - return list - .filter((goal) => goal && typeof goal === "object") - .map((goal) => ({ - sessionID: typeof goal.sessionID === "string" ? goal.sessionID : null, - objective: oneLine(goal.objective).slice(0, 110), - status: typeof goal.status === "string" ? goal.status : "unknown", - tokensUsed: numberOrNull(goal.tokensUsed), - tokenBudget: numberOrNull(goal.tokenBudget), - autoTurns: numberOrNull(goal.autoTurns), - maxAutoTurns: numberOrNull(goal.maxAutoTurns), - maxDurationSeconds: numberOrNull(goal.maxDurationSeconds), - updatedAt: toMillis(goal.updatedAt), - })) - .sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)); -} - -export function summarizeBindings(raw) { - const list = Object.values(raw && typeof raw === "object" ? raw : {}).filter( - (record) => record && typeof record === "object", - ); - const isPending = (state) => state === "requested" || state === "tracking" || state === "held"; - return list - .map((record) => ({ - key: typeof record.key === "string" ? record.key : "", - state: typeof record.state === "string" ? record.state : "unknown", - runId: typeof record.runId === "string" ? record.runId : null, - sessionID: typeof record.sessionID === "string" ? record.sessionID : null, - classification: - record.delivery?.classification ?? - (record.lastStatus?.status ? classifyRun(record.lastStatus) : record.lastStatus?.deliverableValid === true ? "valid" : null), - productVerification: record.lastStatus?.status === "succeeded" ? "required" - : ["failed", "canceled"].includes(record.lastStatus?.status) ? "blocked" : "unknown", - updatedAt: numberOrNull(record.updatedAt), - })) - .sort((a, b) => { - const pa = isPending(a.state) ? 0 : 1; - const pb = isPending(b.state) ? 0 : 1; - if (pa !== pb) return pa - pb; - return (b.updatedAt ?? 0) - (a.updatedAt ?? 0); - }); -} - -function parseJson(text) { - const trimmed = String(text ?? "").trim(); - if (!trimmed) return []; - try { - return JSON.parse(trimmed); - } catch { - return []; - } -} - -export function queryUsage(dbPath, sinceMs, { topSessions = 6 } = {}) { - const open = `file:${resolve(dbPath)}?mode=ro`; - const byProject = parseJson( - execFileSync( - "sqlite3", - [ - "-json", - open, - `SELECT directory AS project, COUNT(*) AS sessions, - COALESCE(SUM(tokens_input),0) AS tokens_in, - COALESCE(SUM(tokens_output),0) AS tokens_out, - ROUND(COALESCE(SUM(cost),0),4) AS cost - FROM session_v2 WHERE time_created >= ${Number(sinceMs)} - GROUP BY directory ORDER BY tokens_in DESC LIMIT 12;`, - ], - { encoding: "utf8" }, - ), - ); - const top = parseJson( - execFileSync( - "sqlite3", - [ - "-json", - open, - `SELECT substr(id,1,14) AS id, agent, - substr(COALESCE(title,''),1,64) AS title, - tokens_input AS tokens_in, tokens_output AS tokens_out, - ROUND(cost,4) AS cost - FROM session_v2 WHERE time_created >= ${Number(sinceMs)} - ORDER BY tokens_input DESC LIMIT ${Number(topSessions)};`, - ], - { encoding: "utf8" }, - ), - ); - return { byProject, top }; -} - -export function queryWorktrees(repoPath) { - try { - const out = execFileSync("git", ["-C", resolve(repoPath), "worktree", "list"], { - encoding: "utf8", - }); - return out - .split("\n") - .map((line) => line.trim()) - .filter(Boolean); - } catch { - return []; - } -} - -export function readJsonFile(path, fallback, onUnavailable = () => {}) { - try { - return JSON.parse(readFileSync(path, "utf8")); - } catch { - onUnavailable(); - return fallback; - } -} - -function isRecord(value) { - return value !== null && typeof value === "object" && !Array.isArray(value); -} - -function validGoalState(value) { - const goals = Object.hasOwn(value, "goals") ? value.goals : value; - return (isRecord(goals) || Array.isArray(goals)) && Object.values(goals).every(isRecord); -} - -function validBindingState(value) { - return Object.values(value).every(record => isRecord(record) - && (record.lastStatus == null || isRecord(record.lastStatus)) - && (record.delivery == null || isRecord(record.delivery))); -} - -export function buildReport({ repo = null, days = 7, now = Date.now(), includeUsage = true } = {}) { - const paths = defaultPaths(); - const since = now - days * 24 * 60 * 60 * 1000; - const warnings = []; - const state = (path, fallback, label, validate = () => true) => { - let unavailable = false; - const value = readJsonFile(path, fallback, () => { unavailable = true; }); - if (unavailable || !isRecord(value) || !validate(value)) { - warnings.push(`${label} unavailable or malformed; empty output is not evidence of no work.`); - return fallback; - } - return value; - }; - let usage = { byProject: [], top: [] }; - if (includeUsage) { - try { - if (!existsSync(paths.db)) throw new Error("absent"); - usage = queryUsage(paths.db, since); - } catch { - warnings.push("Usage unavailable; database/schema/tool access was not verified."); - } - } - const report = { - generatedAt: now, - days, - repo: repo ? resolve(repo) : null, - goals: summarizeGoals(state(paths.goals, {}, "Goal state", validGoalState)), - designRuns: summarizeBindings(state(paths.bindings, {}, "Design state", validBindingState)), - designPaused: state(paths.paused, {}, "Design pause state"), - usage, - worktrees: repo ? queryWorktrees(repo) : [], - nextSlices: [], - status: repo ? inspectWorkStatus(repo) : null, - warnings, - }; - if (repo) { - const productState = join(resolve(repo), "docs/superpowers/product-state.md"); - if (existsSync(productState)) { - report.nextSlices = parseNextSlices(readFileSync(productState, "utf8")); - } - } - return report; -} - -export function renderReport(report) { - const lines = []; - lines.push( - `=== KSI work report — ${formatTime(report.generatedAt)} (last ${report.days} days) ===`, - ); - if (report.status) lines.push("", renderWorkStatus(report.status)); - for (const warning of report.warnings ?? []) lines.push(`Warning: ${warning}`); - - lines.push(""); - lines.push(`[Goals] (${report.goals.length})`); - if (report.goals.length === 0) lines.push(" (none)"); - for (const goal of report.goals) { - const tokens = `${formatTokens(goal.tokensUsed ?? 0)}${goal.tokenBudget ? `/${formatTokens(goal.tokenBudget)}` : ""}`; - const turns = goal.autoTurns === null ? "?" : `${goal.autoTurns}${goal.maxAutoTurns ? `/${goal.maxAutoTurns}` : ""}`; - const unbounded = - goal.tokenBudget === null && goal.maxAutoTurns === null && goal.maxDurationSeconds === null; - lines.push( - ` - [${goal.status}] ${goal.sessionID ? goal.sessionID.slice(0, 16) : "?"} tokens ${tokens} turns ${turns} ${formatTime(goal.updatedAt)}${unbounded ? " [limits: none — user-adjustable]" : ""}`, - ); - lines.push(` ${goal.objective}`); - } - - const pending = report.designRuns.filter((run) => - ["requested", "tracking", "held"].includes(run.state), - ); - lines.push(""); - lines.push(`[Design runs] (${report.designRuns.length}, pending ${pending.length}) paused=${JSON.stringify(report.designPaused ?? {})}`); - if (report.designRuns.length === 0) lines.push(" (none)"); - for (const run of report.designRuns.slice(0, 12)) { - lines.push( - ` - [${run.state}] ${run.runId ? run.runId.slice(0, 8) : "?"} -> ${run.sessionID ? run.sessionID.slice(0, 16) : "?"} artifact=${run.classification ?? "unknown"} product=${run.productVerification ?? "unknown"} ${formatTime(run.updatedAt)}`, - ); - } - - lines.push(""); - lines.push("[Usage by project]"); - if (report.usage.byProject.length === 0) lines.push(" (no sessions in range)"); - for (const row of report.usage.byProject) { - lines.push( - ` in ${formatTokens(row.tokens_in).padEnd(8)} out ${formatTokens(row.tokens_out).padEnd(7)} cost $${row.cost} sessions ${String(row.sessions).padEnd(4)} ${row.project}`, - ); - } - - lines.push(""); - lines.push("[Top sessions]"); - for (const row of report.usage.top) { - lines.push( - ` ${formatTokens(row.tokens_in).padEnd(8)} ${String(row.agent ?? "?").padEnd(12)} ${row.id} ${row.title}`, - ); - } - - if (report.repo) { - lines.push(""); - lines.push(`[Worktrees] (${report.repo})`); - if (report.worktrees.length === 0) lines.push(" (none)"); - for (const line of report.worktrees) lines.push(` ${line}`); - - lines.push(""); - lines.push("[Next slices]"); - if (report.nextSlices.length === 0) lines.push(" (none found)"); - for (const item of report.nextSlices) lines.push(` - ${item.slice(0, 160)}`); - } - - return lines.join("\n"); -} - -async function main(argv) { - const args = argv.filter((arg) => arg !== "--"); - const usage = "Usage: node scripts/work-report.mjs [--repo PATH] [--days NUMBER] [--status] [--json]"; - for (let i = 0; i < args.length; i += 1) { - if (["--repo", "--days"].includes(args[i])) { - if (!args[i + 1] || args[i + 1].startsWith("--")) throw new Error(usage); - i += 1; - } else if (!["--json", "--status"].includes(args[i])) throw new Error(usage); - } - const json = args.includes("--json"); - const repoIndex = args.indexOf("--repo"); - const daysIndex = args.indexOf("--days"); - if (repoIndex !== -1 && !args[repoIndex + 1]) throw new Error("--repo requires a path"); - if (daysIndex !== -1 && !args[daysIndex + 1]) throw new Error("--days requires a number"); - const repo = repoIndex === -1 ? process.cwd() : args[repoIndex + 1]; - const days = daysIndex === -1 ? 7 : Number(args[daysIndex + 1]); - if (!Number.isFinite(days) || days <= 0) throw new Error("--days must be a positive number"); - - if (args.includes("--status")) { - const status = inspectWorkStatus(repo); - process.stdout.write(json ? `${JSON.stringify(status, null, 2)}\n` : `${renderWorkStatus(status)}\n`); - return; - } - const report = buildReport({ repo, days }); - process.stdout.write(json ? `${JSON.stringify(report, null, 2)}\n` : `${renderReport(report)}\n`); -} - -const isMain = - typeof process.argv[1] === "string" && import.meta.url === pathToFileURL(process.argv[1]).href; -if (isMain) { - main(process.argv.slice(2)).catch((error) => { - console.error(`work-report: ${error?.message ?? error}`); - process.exitCode = 1; - }); -} diff --git a/scripts/work-status.mjs b/scripts/work-status.mjs deleted file mode 100644 index 6785103..0000000 --- a/scripts/work-status.mjs +++ /dev/null @@ -1,121 +0,0 @@ -// Local, read-only record/Git reconciliation. Never infer user acceptance or -// deployment from idle state, a complete ledger, or an upstream comparison. -import { readFileSync, realpathSync } from "node:fs"; -import { execFileSync } from "node:child_process"; -import { resolve, join, relative, isAbsolute, sep } from "node:path"; - -export function readProjectText(repo, path) { - try { - const root = realpathSync(repo); - const target = realpathSync(join(root, path)); - const rel = relative(root, target); - if (isOutsideRoot(rel)) return null; - return readFileSync(target, "utf8"); - } catch { - return null; - } -} - -// Native-separator containment for a root-relative path: only a leading `..` -// segment escapes. `relative` joins with the native separator, so on Windows -// `..\outside` escapes, while on POSIX a backslash is an ordinary filename -// character. Split out so both separators are unit-testable on any platform. -export function isOutsideRoot(rel, separator = sep) { - return rel.split(separator)[0] === ".." || isAbsolute(rel); -} - -export function section(text, name) { - const lines = String(text ?? "").split(/\r?\n/); - const start = lines.findIndex(line => line.trim() === `## ${name}`); - if (start === -1) return ""; - const body = []; - for (const line of lines.slice(start + 1)) { - if (/^##\s/.test(line)) break; - body.push(line); - } - return body.join("\n").trim(); -} - -function field(text, name) { - return String(text ?? "").match(new RegExp(`^${name}:\\s*(.+)$`, "m"))?.[1].trim() ?? null; -} - -function gitRead(repo, args) { - try { - return execFileSync("git", ["--no-optional-locks", "-c", "core.fsmonitor=false", "-C", repo, ...args], { - encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000, - }).trim(); - } catch { - return null; - } -} - -export function inspectWorkStatus(repo) { - const root = resolve(repo); - const checkpoint = readProjectText(root, ".opencode/working-state.md"); - const product = readProjectText(root, "docs/superpowers/product-state.md"); - const head = gitRead(root, ["rev-parse", "HEAD"]); - const branch = gitRead(root, ["branch", "--show-current"]); - const changes = gitRead(root, ["status", "--porcelain"]); - const aheadText = gitRead(root, ["rev-list", "--count", "@{upstream}..HEAD"]); - const ahead = aheadText !== null && /^\d+$/.test(aheadText) ? Number(aheadText) : null; - const recordedHead = field(checkpoint, "HEAD")?.match(/^[a-f0-9]{7,40}\b/i)?.[0] ?? null; - const slice = field(checkpoint, "Slice"); - // Only the established plans namespace, no traversal/absolute paths. - const ledgerPath = slice?.match(/ledger:\s*(docs\/superpowers\/plans\/[a-zA-Z0-9_-]+\.md)\s*$/)?.[1] ?? null; - const ledgerText = ledgerPath ? readProjectText(root, ledgerPath) : null; - const recordedStatus = field(checkpoint, "Status") ?? "unknown"; - const warnings = []; - const ledgerSection = field(checkpoint, "Ledger section"); - let ledgerBody = ledgerText; - let sectionAmbiguous = false; - if (ledgerSection && ledgerText) { - const matches = ledgerText.split(/\r?\n/).filter(line => line.trim() === `## ${ledgerSection}`).length; - if (matches !== 1) { - ledgerBody = null; - sectionAmbiguous = matches > 1; - warnings.push(sectionAmbiguous ? "Ledger section is ambiguous (duplicate headings); reconcile the selector." : "Ledger section missing or unavailable; current work is unknown."); - } else { - ledgerBody = section(ledgerText, ledgerSection); - } - } - const ledgerStatuses = [...String(ledgerBody ?? "").matchAll(/^Status:[ \t]*(.+)$/gm)].map(match => match[1].trim()); - const ledgerStatus = sectionAmbiguous || ledgerStatuses.length > 1 ? "ambiguous" : ledgerStatuses[0] ?? "unknown"; - if (ledgerStatuses.length > 1) warnings.push("Ledger has multiple Status fields; identify the current section before reconciling."); - if (!checkpoint) warnings.push("Checkpoint missing or unreadable; recorded work is unknown."); - if (!head) warnings.push("Git revision unavailable; not a verified Git checkout."); - if (head && checkpoint && (!recordedHead || !head.startsWith(recordedHead))) warnings.push("Checkpoint revision differs from current Git HEAD or is unrecorded."); - const recordedBranch = field(checkpoint, "Branch"); - if (branch && recordedBranch && branch !== recordedBranch) warnings.push("Checkpoint branch differs from current Git branch."); - if (slice?.includes("ledger:") && !ledgerPath) warnings.push("Ledger pointer malformed or outside the supported plans namespace."); - if (ledgerPath && !ledgerText) warnings.push("Ledger missing, unreadable or outside the project."); - if (/^idle(?:$|\s+|:\s*)/i.test(recordedStatus) && ledgerBody && (/\b(?:in[ _-]?progress|active)\b/i.test(ledgerStatus) || /^ {0,3}(?:[-*+]|\d+[.)])\s+\[ \](?:\s|$)/m.test(ledgerBody))) { - warnings.push("Idle checkpoint has an unfinished ledger; reconcile before claiming completion."); - } - return { - repo: root, - recordedStatus, - slice, - git: { head, branch: branch || null, changedPaths: changes === null ? null : changes ? changes.split("\n").length : 0, ahead }, - ledger: { path: ledgerPath, section: ledgerSection, status: ledgerStatus }, - acceptance: section(product, "Human acceptance") || "not recorded", - delivery: ahead === null ? "unknown" : ahead > 0 ? "local commits ahead of upstream; publication/deployment not inferred" : "no commits ahead of upstream; deployment not inferred", - next: section(checkpoint, "Next").split("\n").flatMap(line => { - const item = line.match(/^ {0,3}(?:[-*+]|\d+[.)])\s+(.+)$/); - return item ? [item[1].trim()] : []; - }).slice(0, 3), - warnings, - }; -} - -export function renderWorkStatus(status) { - return [ - "[Work status — recorded, not user acceptance]", - ` Work: ${status.recordedStatus} | ${status.slice ?? "not recorded"}`, - ` Git: ${status.git.branch ?? "unknown"} @ ${status.git.head?.slice(0, 7) ?? "unknown"}; changed paths: ${status.git.changedPaths ?? "unknown"}`, - ` User acceptance: ${status.acceptance}`, - ` External delivery: ${status.delivery}`, - ...status.next.map(item => ` Next: ${item}`), - ...status.warnings.map(item => ` Warning: ${item}`), - ].join("\n"); -} diff --git a/src/native-bundle.mjs b/src/native-bundle.mjs deleted file mode 100644 index 8a5ff95..0000000 --- a/src/native-bundle.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import { readFile } from "node:fs/promises" -import { NATIVE_ROLE_NAMES, nativeRoleDefinition } from "./native-roles.mjs" - -function frontmatter(definition) { - const lines = [ - "---", - `description: ${JSON.stringify(definition.description)}`, - `mode: ${definition.mode}`, - `permissions: ${JSON.stringify(definition.permissions)}`, - ] - if (definition.hidden) lines.push("hidden: true") - return `${lines.join("\n")}\n---\n\n${definition.system.trim()}\n` -} - -export async function buildNativeBundle(options) { - if (options !== undefined) throw new Error("buildNativeBundle no longer accepts options: the developerTestRunner variant was removed with Test Runner") - const bundle = new Map() - for (const name of NATIVE_ROLE_NAMES) { - const definition = nativeRoleDefinition(name) - const prompt = await readFile(new URL(`../templates/agents/${name}.md`, import.meta.url), "utf8") - bundle.set(`agents/${name}.md`, frontmatter({ - ...definition, - system: prompt, - })) - } - return bundle -} diff --git a/src/native-roles.mjs b/src/native-roles.mjs deleted file mode 100644 index 94c7219..0000000 --- a/src/native-roles.mjs +++ /dev/null @@ -1,29 +0,0 @@ -export const NATIVE_ROLE_NAMES = ["developer", "reviewer"] - -const rule = (action, effect, resource = "*") => ({ action, resource, effect }) -const allow = (...actions) => actions.map((action) => rule(action, "allow")) -const deny = (...actions) => actions.map((action) => rule(action, "deny")) -const ask = (...actions) => actions.map((action) => rule(action, "ask")) - -const roles = { - developer: { - mode: "subagent", - description: "Bounded implementation and repair within an assigned task; report changes and checks.", - permissions: [ - ...allow("read", "glob", "grep", "list", "lsp", "edit", "shell", "question", "todowrite"), - ...ask("external_directory", "webfetch", "websearch", "context7_*"), - ...deny("subagent"), - ], - }, - reviewer: { - mode: "subagent", - description: "Independent read-only review of code changes and behaviour; no approval authority.", - permissions: [...allow("read", "glob", "grep", "lsp"), ...deny("edit", "subagent")], - }, -} - -export function nativeRoleDefinition(name) { - const role = roles[name] - if (!role) throw new Error(`Unknown native role: ${name}`) - return role -} diff --git a/templates/agents/developer.md b/templates/agents/developer.md deleted file mode 100644 index 663304a..0000000 --- a/templates/agents/developer.md +++ /dev/null @@ -1,3 +0,0 @@ -You are Developer, a subagent for a bounded implementation or repair. Work from the assigned objective and paths, inspect relevant code, preserve unrelated changes, and make the smallest correct change. Use installed skills when relevant; a skill does not expand your tool permissions or the user's authorization. - -You own the tests for the behavior you change: add regression tests when they prove a behavior change, and run the relevant checks before returning. You may read, edit, and run trusted local checks within the task scope. Ask before changing the product contract, data meaning, architecture, credentials, or external state. Report changed files, the tested revision (workspace plus relevant uncommitted changes), observed test commands/results with their evidence locations, and any unresolved issue to the Primary. You do not delegate by default; your tests are author evidence for the Primary's integration and verification, not independent acceptance. diff --git a/templates/agents/reviewer.md b/templates/agents/reviewer.md deleted file mode 100644 index ee29373..0000000 --- a/templates/agents/reviewer.md +++ /dev/null @@ -1,5 +0,0 @@ -You are Reviewer, an independent read-only subagent. For a code review, read the actual diff, the requirements it claims to satisfy, and the available test evidence. Focus on correctness, regressions, security or data boundaries where relevant, and test adequacy: whether the change's tests actually prove the behaviour and which important tests are missing. Check behaviour against the stated intent, not only style. You may request a safe read-only check through native permissions when evidence requires it; do not edit or delegate. - -Read-only is your task contract, not an all-write sandbox. Native `edit` and `subagent` denials do not block every shell, MCP, API or Code Mode side effect; other actions inherit the effective configuration. Do not use another tool to modify implementation, configuration, services or external data. Inspect a command's side effects before requesting a check, and report uncertainty rather than probing a write path. Checks that require writing artifacts belong to the Primary (Build or an explicitly configured verification role) or CI, not Reviewer; report the needed check to the Primary. - -Return the diff range and revision reviewed, actionable findings with severity and impact citing file and line, and what you could not verify or did not inspect. Review findings are advice to the Primary, not authorization for a commit, release, deployment, or user acceptance. Do not claim a check, test run, or inspection that did not actually happen. diff --git a/test/check-package.test.mjs b/test/check-package.test.mjs deleted file mode 100644 index c3c3a6c..0000000 --- a/test/check-package.test.mjs +++ /dev/null @@ -1,92 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { join } from "node:path" -import { spawnSync } from "node:child_process" -import { mkdtemp, mkdir, rm } from "node:fs/promises" -import { tmpdir } from "node:os" -import { fileURLToPath } from "node:url" -import { npmTarget, installedCliTarget } from "../scripts/check-package.mjs" -import * as packageCheck from "../scripts/check-package.mjs" - -const installer = fileURLToPath(new URL("../bin/ksi-opencode.mjs", import.meta.url)) - -async function withTarget(fn) { - const directory = await mkdtemp(join(tmpdir(), "ksi-retired-flags-test-")) - try { await fn(join(directory, "config")) } finally { await rm(directory, { recursive: true, force: true }) } -} - -function runCli(args) { - const result = spawnSync(process.execPath, [installer, ...args], { encoding: "utf8" }) - return { code: result.status, stdout: result.stdout, stderr: result.stderr } -} - -test("npm target drives the trusted npm CLI script with the current Node binary", () => { - const npm = npmTarget({ npm_execpath: "/usr/lib/node_modules/npm/bin/npm-cli.js" }, "/usr/bin/node") - assert.deepEqual(npm, { command: "/usr/bin/node", baseArgs: ["/usr/lib/node_modules/npm/bin/npm-cli.js"] }) - // argv stays an array of absolute paths: no shell string, no bare-name lookup. - assert.ok(npm.baseArgs.every((arg) => typeof arg === "string")) -}) - -test("npm target fails descriptively without an absolute npm_execpath instead of guessing", () => { - assert.throws(() => npmTarget({}), /npm run check:package/) - assert.throws(() => npmTarget({ npm_execpath: "" }), /npm run check:package/) - assert.throws(() => npmTarget({ npm_execpath: "relative/npm-cli.js" }), /npm run check:package/) - assert.throws(() => npmTarget({ npm_execpath: 42 }), /npm run check:package/) -}) - -test("installed CLI target keeps the standalone .bin executable on POSIX", () => { - const cli = installedCliTarget({ installRoot: "/root", packageDir: "/pkg", platform: "linux" }) - assert.equal(cli.command, join("/root", "node_modules", ".bin", "ksi-opencode")) - assert.deepEqual(cli.baseArgs, []) - assert.equal(cli.executable, cli.command) -}) - -test("installed CLI target asserts the .cmd shim and runs bin JS through Node on Windows", () => { - const cli = installedCliTarget({ installRoot: "C:\\root", packageDir: "C:\\pkg", platform: "win32", execPath: "C:\\node.exe" }) - assert.equal(cli.command, "C:\\node.exe") - assert.deepEqual(cli.baseArgs, [join("C:\\pkg", "bin", "ksi-opencode.mjs")]) - assert.ok(cli.present.some((entry) => entry.endsWith("ksi-opencode.cmd"))) - assert.ok(cli.present.includes(join("C:\\pkg", "bin", "ksi-opencode.mjs"))) - assert.equal(cli.executable, null) -}) - -test("packed-consumer verifier exercises both removed installer flags", async () => { - assert.equal(typeof packageCheck.verifyRetiredFlags, "function") - await withTarget(async (target) => { - const commands = [] - await packageCheck.verifyRetiredFlags((args) => { - commands.push(args) - return runCli(args) - }, target) - assert.deepEqual(commands, [ - ["install", "--target", target, "--with-design-kit", "--apply"], - ["install", "--target", target, "--developer-test-runner", "--apply"], - ]) - }) -}) - -test("packed-consumer verifier rejects a removed flag that silently succeeds", async () => { - assert.equal(typeof packageCheck.verifyRetiredFlags, "function") - await withTarget(async (target) => { - await assert.rejects(packageCheck.verifyRetiredFlags((args) => - args.includes("--developer-test-runner") ? { code: 0, stderr: "" } : runCli(args), target), /must fail loudly/) - }) -}) - -test("packed-consumer verifier rejects a usage error without a removal message", async () => { - assert.equal(typeof packageCheck.verifyRetiredFlags, "function") - await withTarget(async (target) => { - await assert.rejects(packageCheck.verifyRetiredFlags((args) => - args.includes("--developer-test-runner") ? { code: 1, stderr: "Usage: ksi-opencode install" } : runCli(args), target), /removed/i) - }) -}) - -test("packed-consumer verifier rejects target creation by a rejected flag", async () => { - assert.equal(typeof packageCheck.verifyRetiredFlags, "function") - await withTarget(async (target) => { - await assert.rejects(packageCheck.verifyRetiredFlags(async (args) => { - if (args.includes("--developer-test-runner")) await mkdir(target) - return runCli(args) - }, target), /must not create the target/) - }) -}) diff --git a/test/config.test.mjs b/test/config.test.mjs new file mode 100644 index 0000000..a6cf825 --- /dev/null +++ b/test/config.test.mjs @@ -0,0 +1,24 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { readFile } from "node:fs/promises" + +const root = new URL("..", import.meta.url) + +test("default configuration preserves native Plan primary and child use", async () => { + const text = await readFile(new URL("opencode.jsonc.example", root), "utf8") + const config = JSON.parse(text.split("\n").filter(line => !/^\s*\/\//.test(line)).join("\n")) + assert.deepEqual(config, { + $schema: "https://opencode.ai/config.json", + agents: { plan: { mode: "all" } }, + }) +}) + +test("repository stays private and has no custom runtime entrypoints", async () => { + const config = JSON.parse(await readFile(new URL("package.json", root), "utf8")) + assert.equal(config.private, true) + assert.equal(config.bin, undefined) + assert.equal(config.main, undefined) + assert.equal(config.exports, undefined) + assert.equal(config.dependencies, undefined) + assert.deepEqual(config.scripts, { check: "node --test" }) +}) diff --git a/test/design-binding.test.mjs b/test/design-binding.test.mjs deleted file mode 100644 index 00ff5c0..0000000 --- a/test/design-binding.test.mjs +++ /dev/null @@ -1,395 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, readdirSync, symlinkSync, existsSync, realpathSync } from "node:fs" -import { tmpdir } from "node:os" -import { join, resolve } from "node:path" -import { fileURLToPath } from "node:url" -import { execFileSync, spawnSync } from "node:child_process" -import { inspectDesignBinding } from "../scripts/design-binding.mjs" -import * as report from "../scripts/work-report.mjs" - -const DOCTOR = fileURLToPath(new URL("../scripts/work-doctor.mjs", import.meta.url)) - -function fixture(t) { - const root = mkdtempSync(join(tmpdir(), "ksi-design-binding-test-")) - t.after(() => rmSync(root, { recursive: true, force: true })) - const repo = join(root, "product") - const configDir = join(root, "config") - mkdirSync(join(repo, "docs/superpowers"), { recursive: true }) - mkdirSync(join(configDir, "agents"), { recursive: true }) - return { root, repo, configDir } -} - -function setupLocal({ repo, configDir }, { design = "fixture-design-id", sourceWorkspace = null, extra = "" } = {}) { - for (const name of ["developer", "reviewer"]) writeFileSync(join(configDir, `agents/${name}.md`), "role fixture\n") - writeFileSync(join(configDir, "AGENTS.md"), "Fixture policy; no live activation proof\n") - const sourceRow = sourceWorkspace === null ? "" : `| Source workspace | ${sourceWorkspace} |\n` - writeFileSync(join(repo, "docs/superpowers/product-state.md"), `## Product binding\n\n| Field | Value |\n| --- | --- |\n| Repository | . |\n${sourceRow}| Design project | ${design} |\n| Design storage | . |\n| Entry | index.html |\n| Start command | node never-execute-this.mjs |\n| Verify command | node also-never-execute-this.mjs |\n| Product URL | https://example.invalid |\n| Brand source | tokens.css |\n${extra}`) -} - -function project(id, dir, name = `${id}-name`) { - return { id, name, metadata: { baseDir: dir, resolvedDir: dir } } -} - -// Missing caller snapshot stays live-unknown; nothing is created or inferred. -test("missing snapshot stays live-unknown without creating or selecting a project", t => { - const f = fixture(t) - setupLocal(f) - const result = inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id" } }) - assert.equal(result.state, "unknown") - assert.equal(result.selectedId, null) - assert.match(result.reasons.join("\n"), /live|snapshot not supplied/i) - assert.deepEqual(readdirSync(f.repo).sort(), ["docs"]) -}) - -// Name-only matches never count as identity, even for a single candidate. -test("name-only match without exact id stays unknown and never matches", t => { - const f = fixture(t) - setupLocal(f) - const dir = f.repo - const result = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "Fixture Product" }, - projects: [{ id: "actual-id-1", name: "Fixture Product", metadata: { baseDir: dir, resolvedDir: dir } }], - }) - assert.equal(result.state, "unknown") - assert.equal(result.selectedId, null) - assert.match(result.reasons.join("\n"), /name-only|exact id/i) -}) - -// A stale recorded id absent from an explicit snapshot stays unknown, not matched. -test("stale recorded id absent from the snapshot stays unknown, never matched", t => { - const f = fixture(t) - setupLocal(f) - const result = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "stale-id-aaa" }, - projects: [project("current-id-bbb", f.repo)], - }) - assert.equal(result.state, "unknown") - assert.equal(result.selectedId, null) -}) - -// Substring candidates are ambiguous; the tool must not guess by name. -test("substring candidates across several projects are ambiguous", t => { - const f = fixture(t) - setupLocal(f) - const result = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "EVENTOUCH" }, - projects: [ - { id: "proj-aaa", name: "EVENTOUCH source", metadata: { baseDir: f.repo, resolvedDir: f.repo } }, - { id: "proj-bbb", name: "EVENTOUCH legacy", metadata: { baseDir: f.repo, resolvedDir: f.repo } }, - ], - }) - assert.equal(result.state, "ambiguous") - assert.equal(result.selectedId, null) -}) - -// Exact id plus agreeing directories is a metadata match, repeatable without writes. -test("exact id with agreeing directories matches repeatedly without writes", t => { - const f = fixture(t) - setupLocal(f) - const binding = { "Design project": "fixture-design-id" } - const projects = { projects: [project("fixture-design-id", f.repo)] } - const before = readFileSync(join(f.repo, "docs/superpowers/product-state.md"), "utf8") - const first = inspectDesignBinding({ repo: f.repo, binding, projects }) - const second = inspectDesignBinding({ repo: f.repo, binding, projects }) - assert.equal(first.state, "matched") - assert.equal(first.selectedId, "fixture-design-id") - assert.deepEqual(second, first) - assert.equal(readFileSync(join(f.repo, "docs/superpowers/product-state.md"), "utf8"), before) - assert.deepEqual(readdirSync(f.repo).sort(), ["docs"]) - assert.match(first.reasons.join("\n"), /not .*readiness|metadata only/i) -}) - -// Array snapshots are accepted exactly like actual MCP {projects:[...]} lists. -test("array snapshots behave exactly like MCP list containers", t => { - const f = fixture(t) - setupLocal(f) - const binding = { "Design project": "fixture-design-id" } - const record = project("fixture-design-id", f.repo) - const fromList = inspectDesignBinding({ repo: f.repo, binding, projects: { projects: [record] } }) - const fromArray = inspectDesignBinding({ repo: f.repo, binding, projects: [record] }) - assert.equal(fromList.state, "matched") - assert.deepEqual(fromArray, fromList) -}) - -// Contradictory metadata directories never match. -test("contradictory baseDir and resolvedDir are a mismatch", t => { - const f = fixture(t) - setupLocal(f) - const other = join(f.root, "elsewhere") - mkdirSync(other, { recursive: true }) - const result = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "fixture-design-id" }, - projects: [{ id: "fixture-design-id", name: "x", metadata: { baseDir: f.repo, resolvedDir: other } }], - }) - assert.equal(result.state, "mismatch") - assert.equal(result.selectedId, "fixture-design-id") -}) - -// Directories pointing elsewhere are a mismatch, not a silent retarget. -test("metadata directories pointing at another checkout are a mismatch", t => { - const f = fixture(t) - setupLocal(f) - const other = join(f.root, "other-checkout") - mkdirSync(other, { recursive: true }) - const result = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "fixture-design-id" }, - projects: [project("fixture-design-id", other)], - }) - assert.equal(result.state, "mismatch") - assert.equal(result.selectedId, "fixture-design-id") -}) - -// An ancestor directory is not the exact expected root. -test("ancestor metadata directory is a mismatch, not a match", t => { - const f = fixture(t) - setupLocal(f) - const nested = join(f.repo, "nested") - mkdirSync(nested, { recursive: true }) - const result = inspectDesignBinding({ - repo: nested, - binding: { "Design project": "fixture-design-id", "Source workspace": "." }, - projects: [project("fixture-design-id", f.repo)], - }) - assert.equal(result.state, "mismatch") -}) - -// A directory nested inside the expected root is not the exact root either. -test("nested metadata directory is a mismatch, not a match", t => { - const f = fixture(t) - setupLocal(f) - const nested = join(f.repo, "packages", "web") - mkdirSync(nested, { recursive: true }) - const result = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "fixture-design-id" }, - projects: [project("fixture-design-id", nested)], - }) - assert.equal(result.state, "mismatch") - assert.match(result.reasons.join(" "), /nested inside/) -}) - -// Recorded source workspace selects the expected root for comparison. -test("recorded source workspace is honored against the snapshot directories", t => { - const f = fixture(t) - const ws = join(f.repo, "apps", "web") - mkdirSync(ws, { recursive: true }) - setupLocal(f, { sourceWorkspace: "apps/web" }) - const good = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "fixture-design-id", "Source workspace": "apps/web" }, - projects: [project("fixture-design-id", ws)], - }) - assert.equal(good.state, "matched") - const bad = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "fixture-design-id", "Source workspace": "apps/web" }, - projects: [project("fixture-design-id", f.repo)], - }) - assert.equal(bad.state, "mismatch") -}) - -// Two products sharing one repository keep explicit identities and workspaces. -test("same repository with separate products keeps explicit identities apart", t => { - const f = fixture(t) - setupLocal(f) - const a = join(f.repo, "apps", "a") - const b = join(f.repo, "apps", "b") - mkdirSync(a, { recursive: true }) - mkdirSync(b, { recursive: true }) - const snapshot = [project("product-a-id", a), project("product-b-id", b)] - const matchA = inspectDesignBinding({ repo: f.repo, binding: { "Design project": "product-a-id", "Source workspace": "apps/a" }, projects: snapshot }) - const matchB = inspectDesignBinding({ repo: f.repo, binding: { "Design project": "product-b-id", "Source workspace": "apps/b" }, projects: snapshot }) - const crossed = inspectDesignBinding({ repo: f.repo, binding: { "Design project": "product-a-id", "Source workspace": "apps/a" }, projects: [project("product-a-id", b)] }) - assert.equal(matchA.state, "matched") - assert.equal(matchA.selectedId, "product-a-id") - assert.equal(matchB.state, "matched") - assert.equal(matchB.selectedId, "product-b-id") - assert.equal(crossed.state, "mismatch") -}) - -// A worktree checkout differing from the recorded source is a mismatch. -test("worktree directory differing from the recorded source is a mismatch", t => { - const f = fixture(t) - setupLocal(f) - const worktree = join(f.root, "product-worktree") - mkdirSync(worktree, { recursive: true }) - const result = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "fixture-design-id" }, - projects: [project("fixture-design-id", worktree)], - }) - assert.equal(result.state, "mismatch") -}) - -// Real symlinked paths resolve to the same identity instead of mismatching. -test("real symlink paths resolve to the same source identity", t => { - const f = fixture(t) - setupLocal(f) - const real = join(f.root, "real-source") - mkdirSync(real, { recursive: true }) - const link = join(f.root, "linked-source") - symlinkSync(real, link) - assert.equal(realpathSync(link), realpathSync(real)) - const result = inspectDesignBinding({ - repo: link, - binding: { "Design project": "fixture-design-id" }, - projects: [project("fixture-design-id", real)], - }) - assert.equal(result.state, "matched") -}) - -// Malformed snapshots are invalid, never empty-matched. -test("malformed snapshot containers and records are invalid", t => { - const f = fixture(t) - setupLocal(f) - const binding = { "Design project": "fixture-design-id" } - for (const projects of ["oops", 42, { projects: "nope" }, { projects: [{ id: 7 }] }, { projects: [null] }, { projects: [{ name: "no-id" }] }]) { - const result = inspectDesignBinding({ repo: f.repo, binding, projects }) - assert.equal(result.state, "invalid", JSON.stringify(projects)) - } - const dupes = inspectDesignBinding({ repo: f.repo, binding, projects: [project("dup", f.repo), project("dup", f.repo)] }) - assert.equal(dupes.state, "invalid") - const wrongMeta = inspectDesignBinding({ repo: f.repo, binding, projects: [{ id: "fixture-design-id", metadata: { baseDir: 7 } }] }) - assert.equal(wrongMeta.state, "invalid") -}) - -// An explicitly empty snapshot never matches a recorded identity. -test("explicitly empty snapshot never matches a recorded identity", t => { - const f = fixture(t) - setupLocal(f) - const result = inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id" }, projects: [] }) - assert.notEqual(result.state, "matched") - assert.equal(result.selectedId, null) -}) - -// Recorded commands and URLs are witnesses only; inspection never runs them. -test("recorded commands and URLs are never executed during inspection", t => { - const f = fixture(t) - setupLocal(f) - const sentinel = join(f.root, "must-never-exist") - const path = join(f.repo, "docs/superpowers/product-state.md") - writeFileSync(path, readFileSync(path, "utf8").replace("node never-execute-this.mjs", `touch ${sentinel}`).replace("https://example.invalid", "https://example.invalid/hook?token=secret")) - const before = readFileSync(path, "utf8") - const result = inspectDesignBinding({ - repo: f.repo, - binding: { "Design project": "fixture-design-id", "Start command": `touch ${sentinel}`, "Product URL": "https://example.invalid/hook?token=secret" }, - projects: [project("fixture-design-id", f.repo)], - }) - assert.equal(result.state, "matched") - assert.equal(existsSync(sentinel), false) - assert.equal(readFileSync(path, "utf8"), before) - assert.deepEqual(readdirSync(f.root).sort(), ["config", "product"]) -}) - -// Readiness integration: matched metadata stays live-unverified, never runtime-ready. -test("readiness with a matching snapshot stays unverified and discloses metadata limits", t => { - const f = fixture(t) - setupLocal(f) - const result = report.inspectReadiness({ ...f, frontend: true, projects: [project("fixture-design-id", f.repo)] }) - const design = result.checks.find(c => c.id === "design-binding") - assert.equal(design.state, "matched") - assert.match(design.detail, /not .*readiness|metadata/i) - assert.match(design.detail, /no project .*creat/i) - assert.equal(result.status, "unverified") -}) - -// Readiness integration: explicit mismatch or malformed snapshot needs preparation. -test("readiness with mismatch or malformed snapshot needs preparation", t => { - const f = fixture(t) - setupLocal(f) - const other = join(f.root, "elsewhere") - mkdirSync(other, { recursive: true }) - const mismatched = report.inspectReadiness({ ...f, frontend: true, projects: [project("fixture-design-id", other)] }) - assert.equal(mismatched.checks.find(c => c.id === "design-binding").state, "mismatch") - assert.equal(mismatched.status, "needs-setup") - const malformed = report.inspectReadiness({ ...f, frontend: true, projects: "oops" }) - assert.equal(malformed.checks.find(c => c.id === "design-binding").state, "invalid") - assert.equal(malformed.status, "needs-setup") -}) - -// Readiness integration: legacy record-only behavior is preserved without a snapshot. -test("readiness without a snapshot preserves legacy record-only behavior", t => { - const f = fixture(t) - setupLocal(f) - const before = readFileSync(join(f.repo, "docs/superpowers/product-state.md"), "utf8") - const result = report.inspectReadiness({ ...f, frontend: true }) - assert.equal(result.status, "unverified") - assert.equal(result.checks.find(c => c.id === "binding").state, "present") - const design = result.checks.find(c => c.id === "design-binding") - assert.equal(design.state, "unknown") - assert.equal(readFileSync(join(f.repo, "docs/superpowers/product-state.md"), "utf8"), before) -}) - -// CLI: absent flag keeps legacy output; malformed uses report unavailable instead of matching. -test("doctor CLI design-projects flag handles absent, missing and malformed files", t => { - const f = fixture(t) - setupLocal(f) - const legacy = JSON.parse(execFileSync(process.execPath, [DOCTOR, "--repo", f.repo, "--config", f.configDir, "--frontend", "--json"], { encoding: "utf8" })) - assert.equal(legacy.checks.find(c => c.id === "design-binding").state, "unknown") - const missingValue = spawnSync(process.execPath, [DOCTOR, "--design-projects"], { encoding: "utf8" }) - assert.notEqual(missingValue.status, 0) - assert.match(missingValue.stderr, /Usage:/) - const absent = resolve(f.root, "does-not-exist.json") - const absentOut = JSON.parse(execFileSync(process.execPath, [DOCTOR, "--repo", f.repo, "--config", f.configDir, "--frontend", "--json", "--design-projects", absent], { encoding: "utf8" })) - assert.equal(absentOut.checks.find(c => c.id === "design-binding").state, "invalid") - assert.equal(absentOut.status, "needs-setup") - const malformed = join(f.root, "malformed.json") - writeFileSync(malformed, "{not json") - const malformedOut = JSON.parse(execFileSync(process.execPath, [DOCTOR, "--repo", f.repo, "--config", f.configDir, "--frontend", "--json", "--design-projects", malformed], { encoding: "utf8" })) - assert.equal(malformedOut.checks.find(c => c.id === "design-binding").state, "invalid") - const snapshot = join(f.root, "projects.json") - writeFileSync(snapshot, JSON.stringify({ projects: [{ id: "fixture-design-id", metadata: { baseDir: f.repo, resolvedDir: f.repo } }] })) - const matched = JSON.parse(execFileSync(process.execPath, [DOCTOR, "--repo", f.repo, "--config", f.configDir, "--frontend", "--json", "--design-projects", snapshot], { encoding: "utf8" })) - assert.equal(matched.checks.find(c => c.id === "design-binding").state, "matched") - assert.equal(matched.status, "unverified") -}) - -// Break caught: an identity-only snapshot claims source correspondence without a source. -test("exact identity without source directories stays unknown and needs preparation", t => { - const f = fixture(t) - setupLocal(f) - const projects = [{ id: "fixture-design-id" }] - assert.equal(inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id" }, projects }).state, "unknown") - const result = report.inspectReadiness({ ...f, frontend: true, projects }) - assert.equal(result.checks.find(c => c.id === "design-binding").state, "unknown") - assert.equal(result.status, "needs-setup") -}) - -// Break caught: empty/relative directories normalize to cwd and falsely match it. -test("snapshot directories must be nonempty absolute paths", t => { - const f = fixture(t) - for (const baseDir of ["", " ", ".", "nested/.."]) - assert.equal(inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id" }, projects: [{ id: "fixture-design-id", metadata: { baseDir } }] }).state, "invalid", JSON.stringify(baseDir)) -}) - -// Break caught: fallback precedence hides contradictory redundant path fields. -test("all supplied source directories must agree rather than shadow contradictions", t => { - const f = fixture(t) - const other = join(f.root, "other") - mkdirSync(other) - const projects = [{ id: "fixture-design-id", metadata: { baseDir: f.repo, resolvedDir: f.repo }, baseDir: other, resolvedDir: f.repo }] - assert.equal(inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id" }, projects }).state, "mismatch") -}) - -// Break caught: stale recorded storage is ignored despite an otherwise matching snapshot. -test("recorded design storage must agree with the intended source", t => { - const f = fixture(t) - const other = join(f.root, "other") - mkdirSync(other) - const projects = [project("fixture-design-id", f.repo)] - assert.equal(inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id", "Design storage": other }, projects }).state, "mismatch") - assert.equal(inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id", "Design storage": "." }, projects }).state, "matched") -}) - -// Break caught: an explicitly corrupt null snapshot is mistaken for omitted evidence. -test("explicit null snapshots and blank identities are invalid", t => { - const f = fixture(t) - assert.equal(inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id" }, projects: null }).state, "invalid") - assert.equal(inspectDesignBinding({ repo: f.repo, binding: { "Design project": "fixture-design-id" }, projects: [{ id: " ", metadata: { baseDir: f.repo } }] }).state, "invalid") -}) diff --git a/test/design-notifier-install.test.mjs b/test/design-notifier-install.test.mjs deleted file mode 100644 index 0dbc5e9..0000000 --- a/test/design-notifier-install.test.mjs +++ /dev/null @@ -1,124 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { mkdir, mkdtemp, readFile, rm, writeFile, access } from "node:fs/promises" -import { spawnSync } from "node:child_process" -import { fileURLToPath } from "node:url" -import { tmpdir } from "node:os" -import { join } from "node:path" - -import { - installDesignNotifier, - resolveTargetDir, - uninstallDesignNotifier, - verifyInstall, -} from "../scripts/install-design-notifier.mjs" - -async function makeSource(root) { - await mkdir(join(root, "lib"), { recursive: true }) - await writeFile(join(root, "package.json"), `${JSON.stringify({ name: "ksi-design-notifier", version: "9.9.9" })}\n`) - await writeFile(join(root, "index.js"), "export default {}\n") - await writeFile(join(root, "lib", "a.js"), "export const a = 1\n") -} - -test("resolveTargetDir honors XDG_CONFIG_HOME and the home fallback", () => { - assert.equal( - resolveTargetDir({ env: { XDG_CONFIG_HOME: "/xdg" }, home: "/home/u" }), - join("/xdg", "opencode", "plugins", "design-notifier"), - ) - assert.equal( - resolveTargetDir({ env: {}, home: "/home/u" }), - join("/home/u", ".config", "opencode", "plugins", "design-notifier"), - ) -}) - -test("install writes a hashed manifest, verify detects drift, uninstall removes the copy", async () => { - const root = await mkdtemp(join(tmpdir(), "design-notifier-install-")) - try { - const source = join(root, "source") - const target = join(root, "target") - await makeSource(source) - - const manifest = await installDesignNotifier({ sourceDir: source, targetDir: target, commit: "abc123" }) - assert.equal(manifest.version, "9.9.9") - assert.equal(manifest.commit, "abc123") - assert.ok(manifest.files["index.js"]) - assert.ok(manifest.files["lib/a.js"]) - - const verified = await verifyInstall({ targetDir: target }) - assert.equal(verified.ok, true) - assert.deepEqual(verified.changed, []) - - await writeFile(join(target, "lib", "a.js"), "export const a = 2\n") - const drifted = await verifyInstall({ targetDir: target }) - assert.equal(drifted.ok, false) - assert.deepEqual(drifted.changed, ["lib/a.js"]) - - await uninstallDesignNotifier({ targetDir: target }) - const gone = await verifyInstall({ targetDir: target }) - assert.equal(gone.ok, false) - assert.match(gone.reason, /installed\.json/) - } finally { - await rm(root, { recursive: true, force: true }) - } -}) - -test("install refuses to replace a directory that is not this plugin", async () => { - const root = await mkdtemp(join(tmpdir(), "design-notifier-install-")) - try { - const source = join(root, "source") - const target = join(root, "target") - await makeSource(source) - await mkdir(target, { recursive: true }) - await writeFile(join(target, "package.json"), `${JSON.stringify({ name: "something-else" })}\n`) - await assert.rejects( - () => installDesignNotifier({ sourceDir: source, targetDir: target }), - /refusing to touch/, - ) - // The unrelated directory was left untouched. - const pkg = JSON.parse(await readFile(join(target, "package.json"), "utf8")) - assert.equal(pkg.name, "something-else") - } finally { - await rm(root, { recursive: true, force: true }) - } -}) - -for (const flag of ["--help", "-h", "--unknown"]) { - test(`${flag} cannot install the default notifier as a side effect`, async () => { - const root = await mkdtemp(join(tmpdir(), "notifier-cli-readonly-")) - try { - const result = spawnSync(process.execPath, [fileURLToPath(new URL("../scripts/install-design-notifier.mjs", import.meta.url)), flag], { - cwd: root, env: { PATH: process.env.PATH, HOME: root, XDG_CONFIG_HOME: root }, encoding: "utf8", - }) - await assert.rejects(access(join(root, "opencode", "plugins", "design-notifier")), /ENOENT/) - if (flag === "--unknown") { - assert.notEqual(result.status, 0) - assert.match(result.stderr, /unknown.*--unknown/i) - } else { - assert.equal(result.status, 0, result.stderr) - assert.match(result.stdout, /usage:/i) - } - } finally { - await rm(root, { recursive: true, force: true }) - } - }) -} - -for (const args of [["--target"], ["--target", "--verify"], ["--verify", "--uninstall"]]) { - test(`${args.join(" ")} cannot mutate the existing default notifier`, async () => { - const root = await mkdtemp(join(tmpdir(), "notifier-cli-invalid-")) - try { - const target = join(root, "opencode", "plugins", "design-notifier") - await makeSource(target) - const before = await readFile(join(target, "package.json"), "utf8") - const result = spawnSync(process.execPath, [fileURLToPath(new URL("../scripts/install-design-notifier.mjs", import.meta.url)), ...args], { - cwd: root, env: { PATH: process.env.PATH, HOME: root, XDG_CONFIG_HOME: root }, encoding: "utf8", - }) - assert.notEqual(result.status, 0) - assert.match(result.stderr, /requires a path|mutually exclusive/) - assert.equal(await readFile(join(target, "package.json"), "utf8"), before) - await assert.rejects(access(join(target, "installed.json")), /ENOENT/) - } finally { - await rm(root, { recursive: true, force: true }) - } - }) -} diff --git a/test/design-notifier-messages.test.mjs b/test/design-notifier-messages.test.mjs deleted file mode 100644 index fcbfe5d..0000000 --- a/test/design-notifier-messages.test.mjs +++ /dev/null @@ -1,317 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" - -import { buildDelivery, classifyRun, messageIdFor, summarizeRun } from "../plugins/design-notifier/lib/messages.js" - -const RUN = "11111111-1111-4111-8111-111111111111" -const SESSION = "ses_test000000000000000000000" - -const validRun = { - id: RUN, - projectId: "proj-a", - status: "succeeded", - deliverableValid: true, - deliverableValidation: "valid", - deliverableEntryFile: "index.html", - artifactCount: 3, - retryable: false, - resumable: false, -} - -test("classifyRun follows the daemon verdict, not just terminal status", () => { - assert.equal(classifyRun(validRun), "valid") - assert.equal(classifyRun({ ...validRun, deliverableValid: false, deliverableValidation: "entry_not_touched" }), "invalid") - assert.equal(classifyRun({ ...validRun, status: "failed" }), "failed") - assert.equal(classifyRun({ ...validRun, status: "canceled" }), "canceled") - assert.equal(classifyRun({ ...validRun, status: "running" }), "pending") -}) - -test("message ids are deterministic per run/status/session and differ across sessions", () => { - const one = messageIdFor({ runId: RUN, status: "succeeded", sessionID: SESSION }) - const again = messageIdFor({ runId: RUN, status: "succeeded", sessionID: SESSION }) - const other = messageIdFor({ runId: RUN, status: "succeeded", sessionID: "ses_other" }) - const otherStatus = messageIdFor({ runId: RUN, status: "failed", sessionID: SESSION }) - assert.match(one, /^msg_[0-9a-f]{32}$/) - assert.equal(one, again) - assert.notEqual(one, other) - assert.notEqual(one, otherStatus) -}) - -test("an invalid deliverable is never reported as success", () => { - const delivery = buildDelivery({ - run: { ...validRun, deliverableValid: false, deliverableValidation: "entry_not_touched" }, - sessionID: SESSION, - requestId: "req-1", - source: "hook", - }) - assert.equal(delivery.classification, "invalid") - assert.equal(delivery.delivery, "queue") - assert.equal(delivery.resume, true) - assert.match(delivery.text, /Deliverable: invalid \(entry_not_touched\)/) - assert.match(delivery.text, /do not claim a design deliverable/) - assert.match(delivery.text, /do not regenerate, replay or cancel-retry/) - assert.match(delivery.text, /not a user message/) - assert.equal(delivery.metadata["ksi.design-notifier"].runId, RUN) - assert.equal(delivery.metadata["ksi.design-notifier"].classification, "invalid") -}) - -test("a valid deliverable defaults to finishing within authorized scope with a gate for new direction", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION, requestId: "req-1", source: "hook" }) - assert.equal(delivery.classification, "valid") - assert.match(delivery.text, /Deliverable: valid/) - assert.match(delivery.text, /default to finishing the job/) - assert.match(delivery.text, /Stop and ask for direction only when/) - assert.match(delivery.text, /new visual direction/) - assert.match(delivery.text, /client-facing deliverable/) - assert.match(delivery.text, /design-quality loop/) - assert.match(delivery.text, /task-appropriate OpenDesign skill/) - assert.match(delivery.text, /no fixed round cap/) - assert.match(delivery.text, /call get_run\(runId\)/) -}) - -test("paused mode admits the notification without auto wake", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION, paused: true }) - assert.equal(delivery.resume, false) - assert.equal(delivery.delivery, "queue") -}) - -test("failure details and recharge action are surfaced verbatim", () => { - const delivery = buildDelivery({ - run: { - ...validRun, - status: "failed", - deliverableValid: false, - deliverableValidation: "no_artifact", - errorCode: "AMR_INSUFFICIENT_BALANCE", - failureCategory: "billing", - failureAction: "recharge", - retryable: false, - resumable: true, - }, - sessionID: SESSION, - }) - assert.equal(delivery.classification, "failed") - assert.match(delivery.text, /error=AMR_INSUFFICIENT_BALANCE/) - assert.match(delivery.text, /category=billing/) - assert.match(delivery.text, /action=recharge/) - assert.match(delivery.text, /resumable=true/) -}) - -test("summarizeRun keeps only whitelisted fields", () => { - const summary = summarizeRun({ ...validRun, prompt: "secret prompt text", eventsLogPath: "/tmp/events.log" }) - assert.equal(summary.status, "succeeded") - assert.equal(summary.deliverableValid, true) - assert.equal(summary.deliverableEntryFile, "index.html") - assert.ok(!("prompt" in summary)) - assert.ok(!("eventsLogPath" in summary)) -}) - -// Catches conflating an entry-centric artifact verdict with product completion. -test("successful execution with untouched entry requires product verification, not an automatic failure or success", () => { - const delivery = buildDelivery({ - run: { ...validRun, deliverableValid: false, deliverableValidation: "entry_not_touched" }, - sessionID: SESSION, - }) - assert.equal(delivery.classification, "invalid", "preserve the daemon artifact verdict") - assert.equal(delivery.metadata["ksi.design-notifier"].productVerification, "required") - assert.match(delivery.text, /actual source diff.*build.*changed.flow/i) - assert.match(delivery.text, /not.*(?:product|task) failure/i) - assert.match(delivery.text, /do not.*(?:automatically|blindly).*regenerate/i) -}) - -test("failed execution is not upgraded to a source verification success exception", () => { - const delivery = buildDelivery({ run: { ...validRun, status: "failed", deliverableValidation: "entry_not_touched" }, sessionID: SESSION }) - assert.equal(delivery.classification, "failed") - assert.equal(delivery.metadata["ksi.design-notifier"].productVerification, "blocked") -}) - -test("even valid artifacts require verification and respect the representative review and pause boundaries", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.equal(delivery.metadata["ksi.design-notifier"].productVerification, "required") - assert.match(delivery.text, /representative screen.*single review/i) - assert.match(delivery.text, /approval.*before.*rollout/i) - assert.match(delivery.text, /goal.*pause|paused.*goal/i) - assert.match(delivery.text, /do not.*(?:create|resume).*goal/i) -}) - -test("an intermediate strategy stage is marked non-final with chain metadata", () => { - const delivery = buildDelivery({ - run: { ...validRun, strategyTask: { terminal: false, nextRunId: "22222222-2222-4222-8222-222222222222" } }, - sessionID: SESSION, - }) - assert.match(delivery.text, /intermediate stage/) - assert.match(delivery.text, /not the final completion/) - const meta = delivery.metadata["ksi.design-notifier"] - assert.equal(meta.strategyTerminal, false) - assert.equal(meta.strategyNextRunId, "22222222-2222-4222-8222-222222222222") -}) - -test("a terminal strategy stage carries no intermediate note", () => { - const delivery = buildDelivery({ run: { ...validRun, strategyTask: { terminal: true } }, sessionID: SESSION }) - assert.doesNotMatch(delivery.text, /intermediate stage/) - assert.equal(delivery.metadata["ksi.design-notifier"].strategyTerminal, true) -}) - -// These exercise the emitted notification contract, not agent obedience or taste. -for (const [verdict, valid] of [["valid", true], ["entry_not_touched", false], ["no_artifact", false]]) { - test(`UI review advice is output-contract-based even with ${verdict}`, () => { - const delivery = buildDelivery({ - run: { ...validRun, deliverableValid: valid, deliverableValidation: verdict }, - sessionID: SESSION, - }) - assert.equal(delivery.classification, valid ? "valid" : "invalid") - assert.equal(delivery.metadata["ksi.design-notifier"].designQuality, "not_assessed") - assert.match(delivery.text, /any verified UI output.*regardless of.*artifact verdict/i) - assert.doesNotMatch(delivery.text, /If the deliverable is valid: run the design-quality loop/) - assert.match(delivery.text, /design workspace owns.*rendered.*review.*refinement/i) - assert.match(delivery.text, /small CSS.*visual.*fixes/i) - assert.match(delivery.text, /OpenCode owns.*functional.*verification.*Git/i) - }) -} - -test("a report-only or intermediate stage does not authorize a UI refinement run", () => { - const delivery = buildDelivery({ - run: { ...validRun, deliverableValid: false, deliverableValidation: "no_artifact", strategyTask: { terminal: false } }, - sessionID: SESSION, - }) - assert.match(delivery.text, /report-only.*not.*UI output/i) - assert.match(delivery.text, /intermediate.*follow.*chain.*do not.*start.*design run/i) - assert.match(delivery.text, /notification.*(?:does not|never).*grant.*scope/i) - assert.equal(delivery.metadata["ksi.design-notifier"].designQuality, "not_assessed") -}) - -test("stalled design refinement remains quality unmet rather than completed", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /no measurable improvement.*quality unmet/i) - assert.match(delivery.text, /criterion.*defect.*revision.*render evidence/i) - assert.match(delivery.text, /not.*quality.*pass.*(?:tests|artifact)/i) -}) - -for (const status of ["failed", "canceled"]) { - test(`${status} cannot be promoted to design quality success`, () => { - const delivery = buildDelivery({ run: { ...validRun, status }, sessionID: SESSION }) - assert.equal(delivery.classification, status) - assert.equal(delivery.metadata["ksi.design-notifier"].productVerification, "blocked") - assert.equal(delivery.metadata["ksi.design-notifier"].designQuality, "not_assessed") - assert.match(delivery.text, /do not regenerate, replay or cancel-retry/) - }) -} - -// The emitted advice must pin the OD-usage loop: mandatory review run with real -// rendered inputs and a concrete fix list, not an optional self-check. -test("the design loop requires a review run fed with actual rendered states", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /review run/i) - assert.match(delivery.text, /fresh evaluation context/i) - assert.match(delivery.text, /native conversation.*(?:different|new)|new.*native conversation/i) - assert.match(delivery.text, /actual rendered states/i) - assert.match(delivery.text, /viewport|390|768|1440/i) - assert.match(delivery.text, /empty|error|loading/i) - assert.match(delivery.text, /confirm the review (actually )?read/i) - assert.match(delivery.text, /defect list|fix list/i) - assert.match(delivery.text, /user impact/i) - assert.match(delivery.text, /fixed.*open|open.*fixed/i) -}) - -test("render receipt requires actual image consumption, not just file access or hashes", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /image.*(?:tool|input).*(?:evidence|receipt)|(?:evidence|receipt).*image.*(?:tool|input)/i) - assert.match(delivery.text, /native.*(?:trace|transcript)|underlying.*(?:trace|transcript)/i) - assert.match(delivery.text, /hash.*(?:not|insufficient)|(?:not|insufficient).*hash/i) -}) - -test("requested review skills and new run ids do not prove effective review instructions or independence", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /requested.*effective.*(?:skill|instructions)/i) - assert.match(delivery.text, /(?:plugin|scenario).*binding.*(?:replace|overwrite)/i) - assert.match(delivery.text, /different run.*(?:not|insufficient)|(?:not|insufficient).*different run/i) -}) - -test("design completion is judged by user tasks and cannot hide a missing render check", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /representative user tasks/i) - assert.match(delivery.text, /information priority/i) - assert.match(delivery.text, /cognitive burden/i) - assert.match(delivery.text, /cross.screen consistency/i) - assert.match(delivery.text, /(?:disclose|report).*render.*(?:failure|limit)|render.*(?:failure|limit).*(?:disclose|report)/i) -}) - -test("the task contract delegates discovery and keeps observable purpose-level criteria", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /observable/i) - assert.match(delivery.text, /purpose level/i) - assert.match(delivery.text, /what the intended user must be able to/i) - assert.match(delivery.text, /not a presolved problem list/i) - assert.match(delivery.text, /verify.*(input|reference|asset).*access/i) - assert.match(delivery.text, /do not commission|block and report/i) - assert.match(delivery.text, /record.*skill|skill.*record/i) -}) - -// OD-usage follow-on: diagnose existing screens before redesigning; a delegated -// redesign resolves its own direction, with options only when asked or in question. -test("an existing-screen redesign starts from a design audit, not a user defect list", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /design audit/i) - assert.match(delivery.text, /current screens/i) - assert.match(delivery.text, /user should not have to enumerate/i) - assert.match(delivery.text, /problem statement/i) -}) - -test("a delegated redesign resolves its own direction; options only when asked or the brand is in question", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /direction options/i) - assert.match(delivery.text, /2-3|two or three/i) - assert.match(delivery.text, /design workspace/i) - assert.match(delivery.text, /resolve the visual direction/i) - assert.match(delivery.text, /user asks|brand identity/i) - assert.match(delivery.text, /choice is pending/i) -}) - -// OD-usage follow-on: accessibility/motion review inputs, resident product -// context, and the review-delivery bundle. -test("review inputs include keyboard, accessibility and motion states", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /keyboard|focus/i) - assert.match(delivery.text, /axe|accessibility/i) - assert.match(delivery.text, /motion|interaction states/i) -}) - -test("a resident product context is used when present", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /product-context\.md/) - assert.match(delivery.text, /resident|standing brief/i) -}) - -test("the result report includes the review-delivery bundle", () => { - const delivery = buildDelivery({ run: validRun, sessionID: SESSION }) - assert.match(delivery.text, /implemented-product link|product link/i) - assert.match(delivery.text, /before\/after|before.after screenshots/i) - assert.match(delivery.text, /design preview link/i) -}) - -// Catches losing the pending rendered-review stage when the artifact is valid. -// This tests notification output, not agent compliance or visual quality. -for (const verdict of ["valid", "entry_not_touched", "no_artifact"]) { - test(`${verdict} cannot substitute for renderer access or rendered review`, () => { - const delivery = buildDelivery({ - run: { ...validRun, deliverableValid: verdict === "valid", deliverableValidation: verdict }, - sessionID: SESSION, - }) - assert.equal(delivery.metadata["ksi.design-notifier"].renderInputAccess, "unverified") - assert.equal(delivery.metadata["ksi.design-notifier"].renderReview, "unverified") - assert.match(delivery.text, /host.*browser.*not.*inner|host.*not.*inner.*browser/i) - assert.match(delivery.text, /captures.*read|read.*captures/i) - assert.match(delivery.text, /static.*(?:not|cannot).*rendered.*review/i) - assert.match(delivery.text, /missing.*render.*(?:unfinished|blocked)/i) - }) -} - -for (const status of ["failed", "canceled", "running"]) { - test(`${status} does not certify renderer access or rendered review`, () => { - const delivery = buildDelivery({ run: { ...validRun, status }, sessionID: SESSION }) - assert.equal(delivery.classification, status === "running" ? "pending" : status) - assert.equal(delivery.metadata["ksi.design-notifier"].renderInputAccess, "unverified") - assert.equal(delivery.metadata["ksi.design-notifier"].renderReview, "unverified") - assert.equal(delivery.metadata["ksi.design-notifier"].designQuality, "not_assessed") - }) -} diff --git a/test/design-notifier-notifier.test.mjs b/test/design-notifier-notifier.test.mjs deleted file mode 100644 index b73d180..0000000 --- a/test/design-notifier-notifier.test.mjs +++ /dev/null @@ -1,595 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { mkdtemp, rm } from "node:fs/promises" -import { tmpdir } from "node:os" -import { join } from "node:path" - -import { createStore } from "../plugins/design-notifier/lib/state.js" -import { isValidRunId } from "../plugins/design-notifier/lib/daemon.js" -import { createNotifier, extractRunId } from "../plugins/design-notifier/lib/notifier.js" - -const RUN_A = "11111111-1111-4111-8111-111111111111" -const RUN_B = "22222222-2222-4222-8222-222222222222" -const SESSION = "ses_test000000000000000000000" -const SESSION_2 = "ses_test000000000000000000001" - -const startResult = (runId, requestId, projectId = "proj-a") => ({ - content: [ - { - type: "text", - text: JSON.stringify({ id: runId, projectId, requestId, requestClientId: undefined, hint: "Run started." }), - }, - ], -}) - -function terminalRun(id, overrides = {}) { - return { - id, - projectId: "proj-a", - status: "succeeded", - deliverableValid: true, - deliverableValidation: "valid", - deliverableEntryFile: "index.html", - artifactCount: 2, - retryable: false, - resumable: false, - terminalAt: 1_700_000_000_000, - ...overrides, - } -} - -async function withHarness(fn, { runs = {}, list = [] } = {}) { - const dir = await mkdtemp(join(tmpdir(), "design-notifier-test-")) - const state = { now: 1_700_000_000_000 } - const clock = () => state.now - const deliveries = [] - const errors = new Map() - const harness = { - dir, - state, - clock, - deliveries, - errors, - runs, - deliverError: null, - daemon: { - baseUrl: "http://fake", - isValidRunId, - getRun: async (runId) => { - const key = `getRun:${runId}` - if (errors.has(key)) throw errors.get(key) - const run = harness.runs[runId] - if (!run) { - const error = new Error("daemon responded 404") - error.status = 404 - throw error - } - return run - }, - listRuns: async () => list, - findByRequestId: async ({ requestId }) => list.find((run) => run.clientRequestId === requestId) ?? null, - }, - } - try { - const store = createStore({ dir, now: clock }) - await store.init() - const notifier = createNotifier({ - store, - daemon: harness.daemon, - deliver: async ({ sessionID, message }) => { - if (harness.deliverError) throw harness.deliverError - deliveries.push({ sessionID, message }) - }, - pollMs: 60_000, - clock, - log: () => {}, - }) - const setDeliverError = (error) => { - harness.deliverError = error - } - const advance = (ms) => { - state.now += ms - } - return await fn({ ...harness, store, notifier, setDeliverError, advance }) - } finally { - await rm(dir, { recursive: true, force: true }) - } -} - -const beforeEvent = (requestId, project = "proj-a") => ({ - tool: "opendesign_start_run", - sessionID: SESSION, - agent: "build", - messageID: "msg_event", - id: "call_1", - input: { requestId, project, prompt: "do not persist this" }, -}) - -const afterEvent = (requestId, result) => ({ - tool: "opendesign_start_run", - sessionID: SESSION, - agent: "build", - messageID: "msg_event", - id: "call_1", - input: { requestId, project: "proj-a" }, - status: "completed", - result, -}) - -test("captures a start_run request, attaches the run id and delivers exactly once", async () => { - await withHarness( - async ({ store, notifier, deliveries }) => { - await notifier.handleToolBefore(beforeEvent("req-1")) - const requested = await store.get("req:req-1") - assert.equal(requested.state, "requested") - assert.equal(requested.sessionID, SESSION) - assert.ok(!JSON.stringify(requested).includes("do not persist"), "prompt text must not be stored") - - await notifier.handleToolAfter(afterEvent("req-1", startResult(RUN_A, "req-1"))) - const attached = await store.get(`run:${RUN_A}@${SESSION}`) - assert.equal(attached.state, "tracking") - assert.equal(attached.requestId, "req-1") - assert.equal(await store.get("req:req-1"), null, "request key is re-keyed to the run") - - await notifier.tick() - assert.equal(deliveries.length, 1) - const [{ sessionID, message }] = deliveries - assert.equal(sessionID, SESSION) - assert.match(message.id, /^msg_[0-9a-f]{32}$/) - assert.equal(message.resume, true) - assert.equal(message.delivery, "queue") - assert.match(message.text, new RegExp(RUN_A)) - assert.equal(message.metadata["ksi.design-notifier"].classification, "valid") - - const delivered = await store.get(`run:${RUN_A}@${SESSION}`) - assert.equal(delivered.state, "delivered") - await notifier.tick() - assert.equal(deliveries.length, 1, "no duplicate delivery on the next tick") - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("recovers a lost start response by clientRequestId without starting a replacement run", async () => { - const lost = terminalRun(RUN_B, { - clientRequestId: "req-2", - status: "failed", - deliverableValid: false, - deliverableValidation: "no_artifact", - failureCategory: "environment", - }) - await withHarness( - async ({ store, notifier, deliveries, clock }) => { - await notifier.handleToolBefore(beforeEvent("req-2", "proj-b")) - await notifier.handleToolAfter({ ...afterEvent("req-2", { content: [] }), input: { requestId: "req-2", project: "proj-b" } }) - const pending = await store.get("req:req-2") - assert.equal(pending.state, "requested") - assert.match(String(pending.lastError), /run id not found/) - - await store.mutate((bindings) => { - bindings["req:req-2"].createdAt = clock() - 30_000 - }) - await notifier.tick() - const attached = await store.get(`run:${RUN_B}@${SESSION}`) - assert.equal(attached.state, "tracking") - assert.equal(attached.requestId, "req-2") - - await notifier.tick() - assert.equal(deliveries.length, 1) - assert.equal(deliveries[0].message.metadata["ksi.design-notifier"].classification, "failed") - }, - { runs: { [RUN_B]: lost }, list: [lost] }, - ) -}) - -test("a daemon outage records the error and retries on a later tick", async () => { - await withHarness( - async ({ notifier, deliveries, errors }) => { - await notifier.handleToolBefore(beforeEvent("req-3")) - await notifier.handleToolAfter(afterEvent("req-3", startResult(RUN_A, "req-3"))) - errors.set(`getRun:${RUN_A}`, new Error("connect ECONNREFUSED 127.0.0.1:7456")) - - await notifier.tick() - assert.equal(deliveries.length, 0, "no delivery while the daemon is unreachable") - - errors.delete(`getRun:${RUN_A}`) - await notifier.tick() - assert.equal(deliveries.length, 1, "delivery happens once the daemon recovers") - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("session-not-found orphans the binding without cross-session delivery", async () => { - await withHarness( - async ({ store, notifier, deliveries, setDeliverError }) => { - await notifier.handleToolBefore(beforeEvent("req-5")) - await notifier.handleToolAfter(afterEvent("req-5", startResult(RUN_A, "req-5"))) - const notFound = new Error("session not found") - notFound.status = 404 - setDeliverError(notFound) - - await notifier.tick() - assert.equal(deliveries.length, 0) - const binding = await store.get(`run:${RUN_A}@${SESSION}`) - assert.equal(binding.state, "orphaned") - assert.match(String(binding.lastError), /not delivered elsewhere/) - - setDeliverError(null) - await notifier.tick() - assert.equal(deliveries.length, 0, "orphaned bindings are not retried or rerouted") - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("a paused session stores the completion as held without contacting the session", async () => { - await withHarness( - async ({ store, notifier, deliveries }) => { - await store.setPaused(SESSION, true) - await notifier.handleToolBefore(beforeEvent("req-6")) - await notifier.handleToolAfter(afterEvent("req-6", startResult(RUN_A, "req-6"))) - - await notifier.tick() - assert.equal(deliveries.length, 0, "no session contact while paused") - const held = await store.get(`run:${RUN_A}@${SESSION}`) - assert.equal(held.state, "held") - assert.equal(typeof held.heldAt, "number") - assert.equal(held.lastStatus.status, "succeeded") - - await notifier.tick() - assert.equal(deliveries.length, 0, "still held on later ticks") - - await store.setPaused(SESSION, false) - await notifier.tick() - assert.equal(deliveries.length, 1, "resume delivers the held completion") - assert.equal(deliveries[0].message.resume, true) - assert.equal((await store.get(`run:${RUN_A}@${SESSION}`)).state, "delivered") - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("a held run that becomes active again returns to tracking and is delivered at the new terminal end", async () => { - await withHarness( - async ({ store, notifier, deliveries, runs }) => { - await store.setPaused(SESSION, true) - await notifier.handleToolBefore(beforeEvent("req-back")) - await notifier.handleToolAfter(afterEvent("req-back", startResult(RUN_A, "req-back"))) - await notifier.tick() - assert.equal((await store.get(`run:${RUN_A}@${SESSION}`)).state, "held") - - // Manual resume inside OpenDesign makes the run active again. - runs[RUN_A] = { ...terminalRun(RUN_A), status: "running" } - await notifier.tick() - const tracking = await store.get(`run:${RUN_A}@${SESSION}`) - assert.equal(tracking.state, "tracking", "held returns to tracking while the run is active") - assert.equal(tracking.heldAt ?? null, null) - - runs[RUN_A] = terminalRun(RUN_A) - await store.setPaused(SESSION, false) - await notifier.tick() - assert.equal(deliveries.length, 1, "the new terminal end is delivered once") - assert.equal(deliveries[0].message.resume, true) - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -// Break caught: resume promises next-tick delivery while hidden backoff still applies. -test("resume reports retry eligibility and list exposes the preserved delivery wait", async () => { - await withHarness(async ({ notifier, store, deliveries, setDeliverError, advance, clock }) => { - await notifier.watchRun({ runId: RUN_A, sessionID: SESSION }) - setDeliverError(new Error("transient send failure")) - await notifier.tick() - const key = `run:${RUN_A}@${SESSION}` - const failed = await store.get(key) - assert.equal(failed.deliveryAttempts, 1) - assert.equal(failed.deliveryNextAttemptAt - clock(), 30_000) - setDeliverError(null) - await store.setPaused(SESSION, true) - await notifier.tick() - const resumed = await notifier.runTool({ action: "resume" }, { sessionID: SESSION }) - assert.match(resumed.content[0].text, /retry.*delay|retry.*wait/i) - assert.doesNotMatch(resumed.content[0].text, /are delivered.*next poll tick/i) - await notifier.tick() - assert.equal(deliveries.length, 0, "resume must not bypass protective retry") - const listed = JSON.parse((await notifier.runTool({ action: "list" }, { sessionID: SESSION })).content[0].text) - const row = listed.bindings.find(b => b.runId === RUN_A) - assert.equal(row.state, "held") - assert.equal(row.deliveryAttempts, 1) - assert.equal(row.deliveryNextAttemptAt, failed.deliveryNextAttemptAt) - assert.equal(row.retryAfterMs, 30_000) - advance(30_001) - await notifier.tick() - assert.equal(deliveries.length, 1) - assert.equal((await store.get(key)).state, "delivered") - const deliveredList = JSON.parse((await notifier.runTool({ action: "list" }, { sessionID: SESSION })).content[0].text) - assert.equal(deliveredList.bindings.find(b => b.runId === RUN_A).retryAfterMs, 0) - }, { runs: { [RUN_A]: terminalRun(RUN_A) } }) -}) - -test("pausing one session leaves other sessions unaffected", async () => { - await withHarness( - async ({ store, notifier, deliveries }) => { - await store.setPaused(SESSION, true) - await notifier.watchRun({ runId: RUN_A, sessionID: SESSION_2 }) - await notifier.tick() - assert.equal(deliveries.length, 1) - assert.equal(deliveries[0].sessionID, SESSION_2) - assert.equal(deliveries[0].message.resume, true) - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("the global pause switch holds and releases every session", async () => { - await withHarness( - async ({ store, notifier, deliveries }) => { - await store.setPaused("*", true) - await notifier.watchRun({ runId: RUN_A, sessionID: SESSION }) - await notifier.tick() - assert.equal(deliveries.length, 0) - assert.equal((await store.get(`run:${RUN_A}@${SESSION}`)).state, "held") - await store.setPaused("*", false) - await notifier.tick() - assert.equal(deliveries.length, 1) - assert.equal(deliveries[0].message.resume, true) - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("manual watch delivers per session and never re-delivers to the same session", async () => { - await withHarness( - async ({ notifier, deliveries }) => { - const first = await notifier.watchRun({ runId: RUN_A, sessionID: SESSION }) - assert.equal(first.alreadyDelivered, false) - await notifier.tick() - assert.equal(deliveries.length, 1) - - const again = await notifier.watchRun({ runId: RUN_A, sessionID: SESSION }) - assert.equal(again.alreadyDelivered, true) - await notifier.tick() - assert.equal(deliveries.length, 1, "same session is not notified twice") - - await notifier.watchRun({ runId: RUN_A, sessionID: SESSION_2 }) - await notifier.tick() - assert.equal(deliveries.length, 2, "a different session gets its own notification") - assert.notEqual(deliveries[0].message.id, deliveries[1].message.id) - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("restart reconciliation delivers a pending run once", async () => { - await withHarness( - async ({ store, daemon, deliveries, clock }) => { - await store.put(`run:${RUN_B}@${SESSION}`, { - key: `run:${RUN_B}@${SESSION}`, - kind: "run", - runId: RUN_B, - sessionID: SESSION, - source: "hook", - state: "tracking", - createdAt: clock() - 60_000, - updatedAt: clock() - 60_000, - lastStatus: { status: "running" }, - }) - const restarted = createNotifier({ - store, - daemon, - deliver: async ({ sessionID, message }) => deliveries.push({ sessionID, message }), - pollMs: 60_000, - clock, - log: () => {}, - }) - await restarted.tick() - assert.equal(deliveries.length, 1) - await restarted.tick() - assert.equal(deliveries.length, 1) - - const third = createNotifier({ - store, - daemon, - deliver: async ({ sessionID, message }) => deliveries.push({ sessionID, message }), - pollMs: 60_000, - clock, - log: () => {}, - }) - await third.tick() - assert.equal(deliveries.length, 1) - }, - { runs: { [RUN_B]: terminalRun(RUN_B) } }, - ) -}) - -test("a failed start_run is recorded and does not create a watched run", async () => { - await withHarness(async ({ store, notifier, deliveries }) => { - await notifier.handleToolBefore(beforeEvent("req-7")) - await notifier.handleToolAfter({ - ...afterEvent("req-7", undefined), - status: "error", - result: undefined, - error: { message: "recharge required" }, - }) - const binding = await store.get("req:req-7") - assert.equal(binding.state, "requested") - assert.match(String(binding.lastError), /start_run failed/) - await notifier.tick() - await notifier.tick() - assert.equal(deliveries.length, 0) - assert.equal((await store.get("req:req-7")).state, "requested") - }) -}) - -test("a run missing on the daemon is marked missing, not orphaned, and is not delivered", async () => { - await withHarness(async ({ store, notifier, deliveries }) => { - await notifier.handleToolBefore(beforeEvent("req-missing")) - await notifier.handleToolAfter(afterEvent("req-missing", startResult(RUN_A, "req-missing"))) - await notifier.tick() - const binding = await store.get(`run:${RUN_A}@${SESSION}`) - assert.equal(binding.state, "missing") - assert.match(String(binding.lastError), /run not found on daemon/) - assert.equal(deliveries.length, 0) - }) -}) - -test("concurrent instances admit at most one delivery for a binding (claim)", async () => { - await withHarness( - async ({ store, daemon, deliveries, clock }) => { - const deliver = async ({ sessionID, message }) => { - await new Promise((resolve) => setTimeout(resolve, 25)); - deliveries.push({ sessionID, message }); - }; - const first = createNotifier({ store, daemon, deliver, pollMs: 60_000, clock, log: () => {} }); - const second = createNotifier({ store, daemon, deliver, pollMs: 60_000, clock, log: () => {} }); - await store.put(`run:${RUN_A}@${SESSION}`, { - key: `run:${RUN_A}@${SESSION}`, - kind: "run", - runId: RUN_A, - sessionID: SESSION, - source: "hook", - state: "tracking", - createdAt: clock() - 60_000, - updatedAt: clock() - 60_000, - }); - await Promise.all([first.tick(), second.tick()]); - assert.equal(deliveries.length, 1, "only the claiming instance delivers"); - const binding = await store.get(`run:${RUN_A}@${SESSION}`) - assert.equal(binding.state, "delivered") - assert.equal(binding.deliveryClaim, null) - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("only the poller leader contacts the daemon and a stale lease is taken over", async () => { - await withHarness( - async ({ store, daemon, clock, advance }) => { - let daemonCalls = 0 - const countingDaemon = { - ...daemon, - getRun: (...args) => { - daemonCalls += 1 - return daemon.getRun(...args) - }, - } - await store.put(`run:${RUN_A}@${SESSION}`, { - key: `run:${RUN_A}@${SESSION}`, - kind: "run", - runId: RUN_A, - sessionID: SESSION, - source: "hook", - state: "tracking", - createdAt: clock() - 60_000, - updatedAt: clock() - 60_000, - }) - // Paused so each tick keeps polling (held stays pollable) instead of - // delivering once and becoming a no-op. - await store.setPaused(SESSION, true) - const deliver = async () => {} - const first = createNotifier({ store, daemon: countingDaemon, deliver, pollMs: 60_000, clock, log: () => {}, instanceId: "instance-a" }) - const second = createNotifier({ store, daemon: countingDaemon, deliver, pollMs: 60_000, clock, log: () => {}, instanceId: "instance-b" }) - - await first.coordination("test") - const afterFirst = daemonCalls - assert.equal(afterFirst, 1, "leader polls") - await second.coordination("test") - assert.equal(daemonCalls, afterFirst, "follower does not poll") - assert.equal((await store.readLeadership()).instanceId, "instance-a") - - advance(61_000) - await second.coordination("test") - assert.equal(daemonCalls, afterFirst + 1, "new leader polls after the stale lease is taken over") - assert.equal((await store.readLeadership()).instanceId, "instance-b") - - assert.equal(await first.release(), false, "a non-holder cannot release") - assert.equal(await second.release(), true) - assert.equal(await store.readLeadership(), null) - }, - { runs: { [RUN_A]: terminalRun(RUN_A) } }, - ) -}) - -test("attach without any session id keeps the request record instead of stranding a run key", async () => { - await withHarness(async ({ store, notifier }) => { - await notifier.handleToolBefore({ ...beforeEvent("req-nosession"), sessionID: undefined }) - await notifier.handleToolAfter({ - ...afterEvent("req-nosession", startResult(RUN_A, "req-nosession")), - sessionID: undefined, - }) - const request = await store.get("req:req-nosession") - assert.equal(request.state, "requested") - assert.match(String(request.lastError), /no session id/) - const bindings = await store.load() - assert.ok(!Object.keys(bindings).some((key) => key.includes(RUN_A)), "no run binding is created") - }) -}) - -test("extractRunId prefers request-matched candidates and rejects ambiguity", () => { - const result = startResult(RUN_A, "req-1") - assert.equal(extractRunId(result, "req-1", isValidRunId), RUN_A) - assert.equal(extractRunId({ output: { runId: RUN_B } }, null, isValidRunId), RUN_B) - assert.equal( - extractRunId({ content: [{ type: "text", text: `two ids ${RUN_A} and ${RUN_B} without fields` }] }, null, isValidRunId), - null, - "ambiguous regex fallback returns null", - ) - assert.equal( - extractRunId({ content: [{ type: "text", text: JSON.stringify({ id: "ksi-deploy-smoke", projectId: "ksi-deploy-smoke" }) }] }, null, isValidRunId), - null, - "a project id alone is not a run id", - ) - assert.equal(extractRunId({ content: [{ type: "text", text: JSON.stringify({ id: RUN_B, projectId: RUN_B }) }] }, null, isValidRunId), RUN_B) - assert.equal( - extractRunId({ content: JSON.stringify({ id: RUN_A, projectId: "proj-a", requestId: "req-9" }) }, "req-9", isValidRunId), - RUN_A, - "a plain string content is parsed too", - ) - assert.equal( - extractRunId({ structuredContent: { id: RUN_A, projectId: "proj-a" } }, null, isValidRunId), - RUN_A, - "structured MCP content is parsed", - ) - assert.equal( - extractRunId({ structuredOutput: { id: RUN_B, projectId: "proj-b" } }, null, isValidRunId), - RUN_B, - "structured output is parsed", - ) -}) - -test("a strategy chain keeps watching: the follow-up run is tracked and delivered as the final stage", async () => { - await withHarness(async ({ notifier, store, runs, deliveries }) => { - runs[RUN_A] = terminalRun(RUN_A, { strategyTask: { terminal: false, nextRunId: RUN_B } }) - await notifier.handleToolBefore(beforeEvent("req-chain")) - await notifier.handleToolAfter(afterEvent("req-chain", startResult(RUN_A, "req-chain"))) - await notifier.tick() - assert.equal(deliveries.length, 1, "intermediate stage delivered once") - assert.match(deliveries[0].message.text, /intermediate stage/) - const key = `run:${RUN_B}@${SESSION}` - let bindings = await store.load() - assert.equal(bindings[key]?.state, "tracking", "follow-up run is watched for the same session") - runs[RUN_B] = terminalRun(RUN_B, { strategyTask: { terminal: true } }) - await notifier.tick() - assert.equal(deliveries.length, 2, "final stage delivered") - assert.doesNotMatch(deliveries[1].message.text, /intermediate stage/) - assert.equal(deliveries[1].sessionID, SESSION) - bindings = await store.load() - assert.equal(bindings[key]?.state, "delivered") - }, { runs: {} }) -}) - -test("a mapped follow-up run that is not visible yet stays tracked within the chain grace window", async () => { - await withHarness(async ({ notifier, store, runs }) => { - runs[RUN_A] = terminalRun(RUN_A, { strategyTask: { terminal: false, nextRunId: RUN_B } }) - await notifier.handleToolBefore(beforeEvent("req-chain-2")) - await notifier.handleToolAfter(afterEvent("req-chain-2", startResult(RUN_A, "req-chain-2"))) - await notifier.tick() - await notifier.tick() - const key = `run:${RUN_B}@${SESSION}` - const bindings = await store.load() - assert.equal(bindings[key]?.state, "tracking", "not marked missing before the follow-up appears") - assert.match(String(bindings[key]?.lastError ?? ""), /not visible yet/) - }, { runs: {} }) -}) diff --git a/test/design-notifier-plugin.test.mjs b/test/design-notifier-plugin.test.mjs deleted file mode 100644 index f0eb8d7..0000000 --- a/test/design-notifier-plugin.test.mjs +++ /dev/null @@ -1,178 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { mkdtemp, readFile, rm } from "node:fs/promises" -import { tmpdir } from "node:os" -import { join } from "node:path" - -import plugin from "../plugins/design-notifier/index.js" - -const RUN_A = "11111111-1111-4111-8111-111111111111" -const SESSION = "ses_test000000000000000000000" - -const runRecord = { - id: RUN_A, - projectId: "proj-a", - status: "succeeded", - deliverableValid: true, - deliverableValidation: "valid", - deliverableEntryFile: "index.html", - artifactCount: 1, - retryable: false, - resumable: false, -} - -const startResult = (runId, requestId) => ({ - content: [{ type: "text", text: JSON.stringify({ id: runId, projectId: "proj-a", requestId }) }], -}) - -const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) - -async function waitFor(condition, { timeout = 4000, step = 25 } = {}) { - const deadline = Date.now() + timeout - while (Date.now() < deadline) { - if (await condition()) return true - await sleep(step) - } - return false -} - -test("plugin setup wires hooks, the design_runs tool and one-shot delivery", async () => { - const dir = await mkdtemp(join(tmpdir(), "design-notifier-plugin-")) - const originalFetch = globalThis.fetch - const syntheticCalls = [] - const hooks = new Map() - const tools = [] - let disposed = 0 - const makeRegistration = () => ({ - dispose: async () => { - disposed += 1 - }, - }) - const ctx = { - options: { - stateDir: dir, - daemonUrl: "http://daemon.test", - pollMs: 5000, - quickPollMs: 50, - initialDelayMs: 50, - }, - app: { version: "2.0.22-test" }, - tool: { - hook: async (name, callback) => { - hooks.set(name, callback) - return makeRegistration() - }, - transform: async (callback) => { - callback({ - add: (tool) => tools.push(tool), - namespace: () => {}, - update: () => {}, - remove: () => {}, - list: () => [], - get: () => undefined, - }) - return makeRegistration() - }, - }, - session: { - synthetic: async (input) => { - syntheticCalls.push(input) - }, - }, - } - globalThis.fetch = async (url) => { - const parsed = new URL(url) - if (parsed.pathname.startsWith("/api/runs/")) { - const id = decodeURIComponent(parsed.pathname.slice("/api/runs/".length)) - if (id === RUN_A) { - return new Response(JSON.stringify(runRecord), { status: 200, headers: { "content-type": "application/json" } }) - } - return new Response("not found", { status: 404 }) - } - if (parsed.pathname === "/api/runs") { - return new Response(JSON.stringify({ runs: [runRecord] }), { status: 200, headers: { "content-type": "application/json" } }) - } - return new Response("not found", { status: 404 }) - } - - try { - const cleanup = await plugin.setup(ctx) - assert.equal(plugin.id, "ksi.design-notifier") - assert.equal(typeof cleanup, "function") - assert.ok(hooks.has("execute.before")) - assert.ok(hooks.has("execute.after")) - assert.equal(tools.length, 1) - assert.equal(tools[0].name, "design_runs") - - await hooks.get("execute.before")({ - tool: "opendesign_start_run", - sessionID: SESSION, - agent: "build", - id: "call_1", - input: { requestId: "req-1", project: "proj-a" }, - }) - await hooks.get("execute.after")({ - tool: "opendesign_start_run", - sessionID: SESSION, - agent: "build", - id: "call_1", - input: { requestId: "req-1", project: "proj-a" }, - status: "completed", - result: startResult(RUN_A, "req-1"), - }) - - const delivered = await waitFor(() => syntheticCalls.length === 1) - assert.ok(delivered, "the poller delivered the completion notification") - const [call] = syntheticCalls - assert.equal(call.sessionID, SESSION) - assert.match(call.id, /^msg_[0-9a-f]{32}$/) - assert.equal(call.resume, true) - assert.equal(call.delivery, "queue") - assert.match(call.text, new RegExp(RUN_A)) - assert.match(call.text, /automatic notification/) - assert.ok(call.metadata["ksi.design-notifier"]) - - await sleep(200) - assert.equal(syntheticCalls.length, 1, "no duplicate delivery") - - const list = JSON.parse((await tools[0].execute({ action: "list" }, { sessionID: SESSION })).content[0].text) - assert.equal(list.paused.global, false) - assert.equal(list.paused.forThisSession, false) - assert.equal(list.bindings.length, 1) - assert.equal(list.bindings[0].state, "delivered") - assert.equal(list.bindings[0].runId, RUN_A) - assert.ok(list.leadership) - assert.equal(list.leadership.isSelf, true) - - // Per-session pause applies to the calling session only. - await tools[0].execute({ action: "pause" }, { sessionID: SESSION }) - const pausedFile = JSON.parse(await readFile(join(dir, "paused.json"), "utf8")) - assert.ok(pausedFile[SESSION], "pause is keyed by session") - const sessionPaused = JSON.parse((await tools[0].execute({ action: "list" }, { sessionID: SESSION })).content[0].text) - assert.equal(sessionPaused.paused.forThisSession, true) - await tools[0].execute({ action: "resume" }, { sessionID: SESSION }) - assert.deepEqual(JSON.parse(await readFile(join(dir, "paused.json"), "utf8")), {}) - - // Global pause scope holds every session. - await tools[0].execute({ action: "pause", scope: "all" }, {}) - const globalPaused = JSON.parse((await tools[0].execute({ action: "list" }, {})).content[0].text) - assert.equal(globalPaused.paused.global, true) - await tools[0].execute({ action: "resume", scope: "all" }, {}) - - // scope=session without a calling session is rejected instead of guessed. - const badPause = await tools[0].execute({ action: "pause" }, {}) - assert.match(badPause.content[0].text, /requires a calling session/) - - const watch = await tools[0].execute({ action: "watch", runId: RUN_A }, { sessionID: SESSION }) - assert.match(watch.content[0].text, /"alreadyDelivered":true/) - - const badWatch = await tools[0].execute({ action: "watch", runId: "not-a-run" }, { sessionID: SESSION }) - assert.match(badWatch.content[0].text, /watch failed/) - - await cleanup() - assert.equal(disposed, 3) - } finally { - globalThis.fetch = originalFetch - await rm(dir, { recursive: true, force: true }) - } -}) diff --git a/test/design-notifier-state.test.mjs b/test/design-notifier-state.test.mjs deleted file mode 100644 index c6e457e..0000000 --- a/test/design-notifier-state.test.mjs +++ /dev/null @@ -1,204 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { join } from "node:path" - -import { createStore, defaultStateDir, reapBindings } from "../plugins/design-notifier/lib/state.js" - -async function withTempDir(fn) { - const dir = await mkdtemp(join(tmpdir(), "design-notifier-state-")) - try { - return await fn(dir) - } finally { - await rm(dir, { recursive: true, force: true }) - } -} - -test("defaultStateDir honors the environment override and XDG default", async () => { - assert.equal(defaultStateDir({ KSI_DESIGN_NOTIFIER_STATE_DIR: " /custom/state " }), "/custom/state") - assert.equal(defaultStateDir({ XDG_STATE_HOME: "/xdg" }), join("/xdg", "opencode-design-notifier")) - const withHome = defaultStateDir({ HOME: "/home/tester" }) - assert.equal(withHome, join("/home/tester", ".local", "state", "opencode-design-notifier")) -}) - -test("bindings survive a store restart and round-trip through put/get/remove", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir }) - await store.init() - await store.put("run:abc@ses_1", { key: "run:abc@ses_1", state: "tracking", runId: "abc" }) - const reopened = createStore({ dir }) - assert.deepEqual(await reopened.get("run:abc@ses_1"), { - key: "run:abc@ses_1", - state: "tracking", - runId: "abc", - }) - await reopened.remove("run:abc@ses_1") - assert.equal(await reopened.get("run:abc@ses_1"), null) - }) -}) - -test("concurrent updates do not lose keys (serialized lock)", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir }) - await store.init() - await Promise.all( - Array.from({ length: 12 }, (_, index) => - store.put(`run:${index}@ses`, { key: `run:${index}@ses`, state: "tracking", runId: String(index) }), - ), - ) - const bindings = await store.load() - assert.equal(Object.keys(bindings).length, 12) - }) -}) - -test("a corrupt bindings file is preserved aside and replaced with empty state", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir }) - await store.init() - await writeFile(join(dir, "bindings.json"), "{not json") - assert.deepEqual(await store.load(), {}) - const entries = await readdir(dir) - assert.ok(entries.some((name) => name.startsWith("bindings.json.corrupt-")), `corrupt copy kept: ${entries}`) - }) -}) - -test("pause is per session with an optional global switch", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir }) - await store.init() - assert.equal(await store.isPausedFor("ses_a"), false) - - await store.setPaused("ses_a", true) - assert.equal(await store.isPausedFor("ses_a"), true) - assert.equal(await store.isPausedFor("ses_b"), false) - assert.deepEqual(await store.listPaused(), { global: false, sessions: ["ses_a"] }) - - await store.setPaused("*", true) - assert.equal(await store.isPausedFor("ses_b"), true) - assert.deepEqual(await store.listPaused(), { global: true, sessions: ["ses_a"] }) - - await store.setPaused("*", false) - assert.equal(await store.isPausedFor("ses_b"), false) - assert.equal(await store.isPausedFor("ses_a"), true) - - await store.setPaused("ses_a", false) - assert.equal(await store.isPausedFor("ses_a"), false) - await assert.rejects(() => store.setPaused("not-a-session", true), /invalid pause key/) - }) -}) - -test("a legacy global paused file is migrated into paused.json on init", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir }) - await store.init() - await writeFile(join(dir, "paused"), "2026-01-01T00:00:00.000Z\n") - const reopened = createStore({ dir }) - await reopened.init() - assert.equal(await reopened.isPausedFor("ses_any"), true) - assert.deepEqual(await reopened.listPaused(), { global: true, sessions: [] }) - await assert.rejects(() => stat(join(dir, "paused"))) - }) -}) - -test("a corrupt paused.json is preserved aside and treated as not paused", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir }) - await store.init() - await writeFile(join(dir, "paused.json"), "{broken") - assert.equal(await store.isPausedFor("ses_x"), false) - const entries = await readdir(dir) - assert.ok(entries.some((name) => name.startsWith("paused.json.corrupt-")), `corrupt copy kept: ${entries}`) - // The switch still works after the corrupt copy is set aside. - await store.setPaused("ses_x", true) - assert.equal(await store.isPausedFor("ses_x"), true) - }) -}) - -test("log rotation caps the events log", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir }) - await store.init() - for (let index = 0; index < 400; index += 1) await store.log("bulk", { index, blob: "x".repeat(1024) }) - const info = await stat(join(dir, "events.log")) - assert.ok(info.size <= 256 * 1024 + 4096, `log stayed bounded: ${info.size}`) - const raw = await readFile(join(dir, "events.log"), "utf8") - assert.match(raw, /"event":"bulk"/) - }) -}) - -test("a contended lock fails the write after the timeout instead of writing unlocked", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir, lockTimeoutMs: 120 }) - await store.init() - await writeFile(join(dir, "bindings.lock"), "") - await assert.rejects(() => store.put("run:x@ses", { state: "tracking" }), /state lock timeout/) - await rm(join(dir, "bindings.lock"), { force: true }) - await store.put("run:x@ses", { state: "tracking" }) - assert.equal((await store.get("run:x@ses")).state, "tracking") - }) -}) - -test("leadership is single-holder, renewable, and expireable", async () => { - await withTempDir(async (dir) => { - let now = 1_700_000_000_000 - const store = createStore({ dir, now: () => now }) - await store.init() - - assert.equal(await store.tryAcquireLeadership("inst-a", { ttlMs: 60_000 }), true) - assert.equal(await store.tryAcquireLeadership("inst-b", { ttlMs: 60_000 }), false) - assert.equal((await store.readLeadership()).instanceId, "inst-a") - - now += 30_000 - assert.equal(await store.tryAcquireLeadership("inst-a", { ttlMs: 60_000 }), true, "holder renews") - assert.equal(await store.tryAcquireLeadership("inst-b", { ttlMs: 60_000 }), false) - - now += 61_000 - assert.equal(await store.tryAcquireLeadership("inst-b", { ttlMs: 60_000 }), true, "stale lease is taken over") - assert.equal(await store.tryAcquireLeadership("inst-a", { ttlMs: 60_000 }), false) - - assert.equal(await store.releaseLeadership("inst-a"), false, "non-holder cannot release") - assert.equal(await store.releaseLeadership("inst-b"), true) - assert.equal(await store.readLeadership(), null) - assert.equal(await store.tryAcquireLeadership("inst-a", { ttlMs: 60_000 }), true) - }) -}) - -test("concurrent leadership claims produce exactly one holder", async () => { - await withTempDir(async (dir) => { - const store = createStore({ dir }) - await store.init() - const results = await Promise.all([ - store.tryAcquireLeadership("inst-x", { ttlMs: 60_000 }), - store.tryAcquireLeadership("inst-y", { ttlMs: 60_000 }), - store.tryAcquireLeadership("inst-z", { ttlMs: 60_000 }), - ]) - assert.equal(results.filter(Boolean).length, 1, "exactly one concurrent claim wins") - const holder = await store.readLeadership() - assert.ok(["inst-x", "inst-y", "inst-z"].includes(holder.instanceId)) - }) -}) - -test("reapBindings drops expired final records, keeps held records, and enforces the cap", () => { - const now = 1_000_000_000 - const bindings = { - "run:a@ses": { state: "delivered", updatedAt: now - 31 * 24 * 60 * 60 * 1000 }, - "run:b@ses": { state: "tracking", updatedAt: now - 400 * 24 * 60 * 60 * 1000 }, - "run:c@ses": { state: "orphaned", updatedAt: now - 91 * 24 * 60 * 60 * 1000 }, - "run:h@ses": { state: "held", updatedAt: now - 400 * 24 * 60 * 60 * 1000 }, - "run:d@ses": { state: "delivered", updatedAt: now }, - } - assert.equal(reapBindings(bindings, now), true) - assert.deepEqual(Object.keys(bindings).sort(), ["run:b@ses", "run:d@ses", "run:h@ses"]) - - const crowded = {} - for (let index = 0; index < 12; index += 1) { - crowded[`run:${index}@ses`] = { state: "delivered", updatedAt: now - index } - } - crowded["run:live@ses"] = { state: "tracking", updatedAt: now } - crowded["run:held@ses"] = { state: "held", updatedAt: now } - reapBindings(crowded, now, { recordCap: 5, deliveredTtlMs: Infinity, finalTtlMs: Infinity }) - assert.equal(Object.keys(crowded).length, 5) - assert.ok("run:live@ses" in crowded, "tracking record survives the cap") - assert.ok("run:held@ses" in crowded, "held record survives the cap") -}) diff --git a/test/docs-links.test.mjs b/test/docs-links.test.mjs index 982457d..b19a32c 100644 --- a/test/docs-links.test.mjs +++ b/test/docs-links.test.mjs @@ -1,8 +1,4 @@ -// Packaged-document link guard: every relative link in a document that ships in -// the npm package must resolve to a file that also ships in the package. This -// catches links to repository-only records (docs/superpowers, integrations/) -// that would dangle for installed consumers. Anchors are not checked here; -// this guard is file-level. +// Check local documentation links, not npm package contents. Anchors are not checked. import test from "node:test" import assert from "node:assert/strict" import { readFile, readdir, stat } from "node:fs/promises" @@ -10,20 +6,9 @@ import { dirname, join, relative, resolve, sep } from "node:path" import { fileURLToPath } from "node:url" const root = fileURLToPath(new URL("..", import.meta.url)) -const manifest = JSON.parse(await readFile(join(root, "package.json"), "utf8")) - -async function exists(path) { - try { - await stat(path) - return true - } catch { - return false - } -} async function expand(entry) { const absolute = resolve(root, entry) - if (!(await exists(absolute))) return [] if (!(await stat(absolute)).isDirectory()) return [entry] const files = [] for (const child of await readdir(absolute, { withFileTypes: true })) { @@ -33,11 +18,10 @@ async function expand(entry) { return files } -const packaged = new Set(["package.json", ...(await Promise.all(manifest.files.map(expand))).flat()]) +const documents = (await Promise.all(["README.md", "docs", "examples"].map(expand))) + .flat().filter(file => file.endsWith(".md")) -test("relative links in packaged documents stay inside the packaged file set", async () => { - const documents = [...packaged].filter((file) => file.endsWith(".md")) - assert.ok(documents.includes("README.md"), "the packaged set should include README.md") +test("relative documentation links resolve inside the repository", async () => { const dangling = [] for (const document of documents) { const text = await readFile(join(root, document), "utf8") @@ -53,9 +37,8 @@ test("relative links in packaged documents stay inside the packaged file set", a dangling.push(`${document} -> ${target} (outside the repository)`) continue } - const packagedPath = escaped.split(sep).join("/") - if (!packaged.has(packagedPath)) dangling.push(`${document} -> ${target}`) + try { await stat(absolute) } catch { dangling.push(`${document} -> ${target}`) } } } - assert.deepEqual(dangling, [], `packaged documents must only link to packaged files:\n${dangling.join("\n")}`) + assert.deepEqual(dangling, [], `broken documentation links:\n${dangling.join("\n")}`) }) diff --git a/test/installer.test.mjs b/test/installer.test.mjs deleted file mode 100644 index 72afe95..0000000 --- a/test/installer.test.mjs +++ /dev/null @@ -1,169 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { spawnSync } from "node:child_process" -import { lstat, mkdtemp, mkdir, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises" -import { tmpdir } from "node:os" -import { join } from "node:path" -import { fileURLToPath } from "node:url" - -const cli = fileURLToPath(new URL("../bin/ksi-opencode.mjs", import.meta.url)) - -function run(target, ...args) { - const result = spawnSync(process.execPath, [cli, "install", "--target", target, ...args], { encoding: "utf8" }) - return { ...result, data: result.stdout ? JSON.parse(result.stdout) : null } -} - -async function withTarget(fn) { - const root = await mkdtemp(join(tmpdir(), "ksi-native-install-test-")) - try { await fn(root) } finally { await rm(root, { recursive: true, force: true }) } -} - -test("preview is read-only; apply creates only two custom native agents", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - const preview = run(target) - assert.equal(preview.status, 0, preview.stderr) - assert.equal(preview.data.applied, false) - assert.equal(preview.data.changes.length, 2) - await assert.rejects(lstat(target), { code: "ENOENT" }) - - const applied = run(target, "--apply") - assert.equal(applied.status, 0, applied.stderr) - assert.equal(applied.data.applied, true) - assert.deepEqual((await readdir(target)).sort(), ["agents"]) - assert.deepEqual((await readdir(join(target, "agents"))).sort(), ["developer.md", "reviewer.md"]) - assert.match(await readFile(join(target, "agents", "developer.md"), "utf8"), /mode: subagent/) - const again = run(target, "--apply") - assert.equal(again.status, 0, again.stderr) - assert.equal(again.data.changes.length, 0) - }) -}) - -test("the removed design-kit flag fails loudly and writes nothing", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - const result = run(target, "--with-design-kit", "--apply") - assert.notEqual(result.status, 0) - assert.match(result.stderr, /with-design-kit.*removed/i) - await assert.rejects(lstat(target), { code: "ENOENT" }) - }) -}) - -test("the removed --developer-test-runner flag fails loudly and writes nothing", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - const result = run(target, "--developer-test-runner", "--apply") - assert.notEqual(result.status, 0) - assert.match(result.stderr, /developer-test-runner.*removed/i) - await assert.rejects(lstat(target), { code: "ENOENT" }) - }) -}) - -test("an existing user Test Runner file is preserved and stays outside the bundle", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - await mkdir(join(target, "agents"), { recursive: true }) - await writeFile(join(target, "agents", "test-runner.md"), "existing user Test Runner role\n") - const installed = run(target, "--apply") - assert.equal(installed.status, 0, installed.stderr) - assert.equal(await readFile(join(target, "agents", "test-runner.md"), "utf8"), "existing user Test Runner role\n") - assert.deepEqual((await readdir(join(target, "agents"))).sort(), ["developer.md", "reviewer.md", "test-runner.md"]) - }) -}) - -test("a legacy installed design role is left untouched for explicit migration", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - await mkdir(join(target, "agents"), { recursive: true }) - await writeFile(join(target, "agents", "design.md"), "legacy KSI Design role\n") - const installed = run(target, "--apply") - assert.equal(installed.status, 0, installed.stderr) - assert.equal(await readFile(join(target, "agents", "design.md"), "utf8"), "legacy KSI Design role\n") - assert.deepEqual((await readdir(join(target, "agents"))).sort(), ["design.md", "developer.md", "reviewer.md"]) - }) -}) - -test("different existing role blocks all writes until explicit replacement; replacement backs it up", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - await mkdir(join(target, "agents"), { recursive: true }) - const role = join(target, "agents", "developer.md") - await writeFile(role, "existing user Developer\n") - const blocked = run(target, "--apply") - assert.notEqual(blocked.status, 0) - assert.match(blocked.stderr, /conflict/i) - assert.equal(await readFile(role, "utf8"), "existing user Developer\n") - await assert.rejects(lstat(join(target, "agents", "reviewer.md")), { code: "ENOENT" }) - - const replaced = run(target, "--apply", "--replace") - assert.equal(replaced.status, 0, replaced.stderr) - const backup = (await readdir(join(target, "agents"))).find((name) => name.startsWith("developer.md.bak-")) - assert.ok(backup) - assert.equal(await readFile(join(target, "agents", backup), "utf8"), "existing user Developer\n") - assert.match(await readFile(role, "utf8"), /mode: subagent/) - }) -}) - -test("replacement refuses to discard user-owned native model, variant, or steps", async () => { - for (const field of ['model: "provider/custom"', "variant: high", "steps: 47", " model: provider/custom", '"variant": high', "'steps': 47"]) { - await withTarget(async (root) => { - const target = join(root, "config") - await mkdir(join(target, "agents"), { recursive: true }) - const role = join(target, "agents", "developer.md") - const original = `---\n${field}\n---\nExisting Developer\n` - await writeFile(role, original) - const result = run(target, "--apply", "--replace") - assert.notEqual(result.status, 0, `${field}: replacement unexpectedly succeeded`) - assert.match(result.stderr, /model|variant|steps/i) - assert.equal(await readFile(role, "utf8"), original) - assert.deepEqual(await readdir(join(target, "agents")), ["developer.md"]) - }) - } -}) - -test("updating an earlier install leaves old built-in overrides and commands untouched for explicit migration", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - await mkdir(join(target, "agents"), { recursive: true }) - await mkdir(join(target, "commands")) - await writeFile(join(target, "agents", "build.md"), "existing Build override\n") - await writeFile(join(target, "agents", "research.md"), "existing Research role\n") - await writeFile(join(target, "agents", "design-critic.md"), "existing Design Critic role\n") - await writeFile(join(target, "commands", "review.md"), "existing command\n") - const installed = run(target, "--apply") - assert.equal(installed.status, 0, installed.stderr) - assert.equal(await readFile(join(target, "agents", "build.md"), "utf8"), "existing Build override\n") - assert.equal(await readFile(join(target, "agents", "research.md"), "utf8"), "existing Research role\n") - assert.equal(await readFile(join(target, "agents", "design-critic.md"), "utf8"), "existing Design Critic role\n") - assert.equal(await readFile(join(target, "commands", "review.md"), "utf8"), "existing command\n") - }) -}) - -test("installer does not rewrite existing JSONC or register KSI as a plugin", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - await mkdir(target) - const config = join(target, "opencode.jsonc") - const original = '{\n // user model and plugin config\n "agents": {"developer": {"model": "provider/model#high", "steps": 47}},\n "plugins": ["other-plugin"]\n}\n' - await writeFile(config, original) - const installed = run(target, "--apply") - assert.equal(installed.status, 0, installed.stderr) - assert.equal(await readFile(config, "utf8"), original) - assert.doesNotMatch(installed.stdout, /ksi-opencode-harness.*plugins/) - assert.doesNotMatch(await readFile(join(target, "agents", "developer.md"), "utf8"), /\n(?:model|steps):/) - }) -}) - -test("installer refuses a symlink in a managed destination", async () => { - await withTarget(async (root) => { - const target = join(root, "config") - const elsewhere = join(root, "elsewhere") - await mkdir(target) - await mkdir(elsewhere) - await symlink(elsewhere, join(target, "agents")) - const result = run(target, "--apply") - assert.notEqual(result.status, 0) - assert.match(result.stderr, /symlink/i) - assert.deepEqual(await readdir(elsewhere), []) - }) -}) diff --git a/test/native-bundle.test.mjs b/test/native-bundle.test.mjs deleted file mode 100644 index e54e669..0000000 --- a/test/native-bundle.test.mjs +++ /dev/null @@ -1,62 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { buildNativeBundle } from "../src/native-bundle.mjs" - -const agentNames = ["developer", "reviewer"] - -test("native bundle adds two subagent roles without replacing built-in prompts or installing slash commands", async () => { - const bundle = await buildNativeBundle() - assert.deepEqual([...bundle.keys()].sort(), agentNames.map((name) => `agents/${name}.md`).sort()) - for (const name of agentNames) { - const content = bundle.get(`agents/${name}.md`) - assert.match(content, /^---\n/) - assert.match(content, /\nmode: subagent\n/) - assert.match(content, /\npermissions: \[/) - assert.doesNotMatch(content, /\n(?:model|steps|variant):/) - assert.doesNotMatch(content, /"action":"ksi_[^"]+"/, "removed runtime tools must not be granted by the native bundle") - assert.doesNotMatch(content, /"action":"skill"/, "native skill discovery must not be restricted by KSI") - assert.doesNotMatch(content, /"action":"\*","resource":"\*","effect":"deny"/) - assert.ok(content.split("---\n").at(-1).trim().length > 40) - } - assert.equal(bundle.has("agents/design.md"), false) - assert.equal(bundle.has("agents/design-critic.md"), false) - assert.equal(bundle.has("agents/research.md"), false) - assert.match(bundle.get("agents/reviewer.md"), /"action":"edit","resource":"\*","effect":"deny"/) - for (const name of agentNames) assert.doesNotMatch(bundle.get(`agents/${name}.md`), /You are (?:the )?KSI/i) -}) - -test("the retired design kit no longer contributes skills or craft references", async () => { - const bundle = await buildNativeBundle() - assert.deepEqual([...bundle.keys()].filter((path) => path.startsWith("skills/")), []) - assert.deepEqual([...bundle.keys()].filter((path) => path.includes("vendor/")), []) -}) - -test("the retired Test Runner role and its delegation variant are gone", async () => { - const bundle = await buildNativeBundle() - assert.equal(bundle.has("agents/test-runner.md"), false) - for (const name of agentNames) { - assert.doesNotMatch(bundle.get(`agents/${name}.md`), /Test Runner/) - } -}) - -test("removed bundle options fail loudly instead of being silently ignored", async () => { - await assert.rejects(buildNativeBundle({ developerTestRunner: true }), /no longer accepts options/) -}) - -test("Reviewer stays a code and behaviour review role without design ownership", async () => { - const reviewer = (await buildNativeBundle()).get("agents/reviewer.md") - assert.match(reviewer, /"action":"edit","resource":"\*","effect":"deny"/) - assert.match(reviewer, /"action":"subagent","resource":"\*","effect":"deny"/) - assert.match(reviewer, /correctness, regressions/i) - assert.doesNotMatch(reviewer, /design review|visual craft|approved direction/i) - assert.doesNotMatch(reviewer, /frontend-design|impeccable-design-polish|web-design-guidelines/) -}) - -test("no remaining role claims design ownership or OpenDesign-vendored skills", async () => { - const bundle = await buildNativeBundle() - for (const name of agentNames) { - const content = bundle.get(`agents/${name}.md`) - assert.doesNotMatch(content, /material product, visual, and interaction design/i, `${name} must not claim design ownership`) - assert.doesNotMatch(content, /frontend-design|impeccable-design-polish|web-design-guidelines/, `${name} must not reference the removed kit`) - } -}) diff --git a/test/native-package.test.mjs b/test/native-package.test.mjs deleted file mode 100644 index 2eb4dc3..0000000 --- a/test/native-package.test.mjs +++ /dev/null @@ -1,87 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { access, readFile } from "node:fs/promises" - -const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) - -test("manifest retains the last published beta until an authorized release bump", () => { - assert.equal(packageJson.version, "0.5.0-beta.9") - assert.notEqual(packageJson.private, true) -}) - -test("package exposes a standalone installer and no OpenCode plugin entrypoint", () => { - assert.equal(packageJson.bin?.["ksi-opencode"], "./bin/ksi-opencode.mjs") - assert.equal(packageJson.main, undefined) - assert.equal(packageJson.exports, undefined) - assert.ok(packageJson.files.includes("bin/ksi-opencode.mjs")) - assert.ok(packageJson.files.includes("src/native-bundle.mjs")) - assert.ok(packageJson.files.includes("src/native-roles.mjs")) - assert.ok(packageJson.files.includes("examples/project-AGENTS.md")) - assert.ok(packageJson.files.includes("docs/integrations/opendesign.md")) - assert.ok(!packageJson.files.includes("src/agents.mjs"), "historical plugin policy must not ship") - assert.ok(!packageJson.files.includes("agents/"), "ship only the two custom role prompts") - for (const name of ["developer", "reviewer"]) { - assert.ok(packageJson.files.includes(`templates/agents/${name}.md`)) - assert.ok(!packageJson.files.includes(`agents/${name}.md`), "historical plugin prompts must not ship") - } - assert.ok(!packageJson.files.includes("templates/agents/test-runner.md"), "retired Test Runner role must not ship") - assert.ok(!packageJson.files.includes("templates/agents/design.md"), "retired Design role must not ship") - assert.ok(!packageJson.files.includes("templates/agents/research.md"), "retired role must not ship") - assert.ok(!packageJson.files.includes("templates/agents/design-critic.md"), "retired critic must not ship") - assert.ok(!packageJson.files.includes("vendor/open-design"), "the retired design kit must not ship") - assert.ok(!packageJson.files.includes("docs/design.md"), "retired design docs must not ship") - assert.ok(!packageJson.files.includes("examples/design.project.jsonc"), "retired design examples must not ship") - assert.ok(!packageJson.files.includes("index.mjs")) - assert.ok(!packageJson.files.includes("src/")) - assert.ok(!packageJson.files.includes("docs/releasing.md"), "maintainer release operations are not user-facing package docs") - assert.ok(!packageJson.scripts.check.includes("plugin-v2")) -}) - -test("retired design artifacts are absent from the source tree", async () => { - for (const path of [ - "templates/agents/design.md", - "vendor/open-design", - "docs/design.md", - "docs/design-critique.md", - "docs/design-system-template.md", - "examples/design.project.jsonc", - "examples/design-handoff.md", - ]) { - await assert.rejects(access(new URL(`../${path}`, import.meta.url)), { code: "ENOENT" }, path) - } -}) - -test("native installer guidance never instructs registering KSI in plugins", async () => { - const install = await readFile(new URL("../INSTALL.md", import.meta.url), "utf8") - const readme = await readFile(new URL("../README.md", import.meta.url), "utf8") - const example = await readFile(new URL("../opencode.jsonc.example", import.meta.url), "utf8") - assert.match(install, /ksi-opencode\.mjs install --target/) - assert.match(readme, /ksi-opencode\.mjs install --target/) - assert.doesNotMatch(example, /KSI-Opencode-Harness.*plugins|ksi-opencode-harness.*plugins/is) - assert.match(install, /plugin-only.*(?:removed|unavailable)|runtime.*(?:removed|unavailable)/i) -}) - -test("guidance points design work at OpenDesign instead of a vendored kit", async () => { - const install = await readFile(new URL("../INSTALL.md", import.meta.url), "utf8") - const readme = await readFile(new URL("../README.md", import.meta.url), "utf8") - const integration = await readFile(new URL("../docs/integrations/opendesign.md", import.meta.url), "utf8") - assert.match(install, /OpenDesign/) - assert.match(readme, /OpenDesign/) - assert.match(integration, /start_run|read/i) - assert.match(readme, /0\.5\.0부터 Design primary와 `--with-design-kit`은 제거되었습니다/) - for (const text of [readme, install]) { - assert.doesNotMatch(text, /append `--with-design-kit`/i, "guidance must not offer the removed flag") - assert.doesNotMatch(text, /--with-design-kit.*(?:both|preview and apply)/is, "guidance must not instruct using the removed flag") - } -}) - -test("retired plugin entrypoints and configuration generators are absent from source", async () => { - for (const path of ["index.mjs", "src/plugin-v2.mjs", "scripts/generate-agent-config.mjs", "bin/ksi-continuity-inject.mjs"]) { - await assert.rejects(access(new URL(`../${path}`, import.meta.url)), { code: "ENOENT" }, path) - } -}) - -test("retired model routing sample is not shipped", async () => { - assert.ok(!packageJson.files.includes("examples/model-routing.json")) - await assert.rejects(access(new URL("../examples/model-routing.json", import.meta.url)), { code: "ENOENT" }) -}) diff --git a/test/opendesign-credential-sync.test.mjs b/test/opendesign-credential-sync.test.mjs deleted file mode 100644 index 4a416be..0000000 --- a/test/opendesign-credential-sync.test.mjs +++ /dev/null @@ -1,72 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { spawnSync } from "node:child_process" -import { mkdtemp, mkdir, writeFile, readFile, readdir, rm } from "node:fs/promises" -import { tmpdir } from "node:os" -import { join } from "node:path" -import { fileURLToPath } from "node:url" - -const script = fileURLToPath(new URL("../integrations/opendesign/sync-opencode-credentials.sh", import.meta.url)) - -// External Docker/sqlite are replaced so no real provider store, service or DB is touched. -// Catch the old failure: secret-bearing SQL persisted in the container volume after DB apply failed. -for (const dbExit of [0, 42]) { - test(`credential sync streams SQL without a container secret file when DB apply exits ${dbExit}`, async () => { - const root = await mkdtemp(join(tmpdir(), "ksi-sync-test-")) - try { - const bin = join(root, "bin") - const tmp = join(root, "tmp") - const dbDir = join(root, ".local/share/opencode") - await Promise.all([mkdir(bin), mkdir(tmp), mkdir(dbDir, { recursive: true })]) - await writeFile(join(dbDir, "opencode.db"), "fixture only") - await writeFile(join(bin, "sqlite3"), "#!/bin/sh\nprintf '%s\\n' \"INSERT INTO credential VALUES ('test', 'API key', 'fixture-secret');\"\n", { mode: 0o755 }) - await writeFile(join(bin, "docker"), `#!/usr/bin/env node -const fs = require('node:fs'); -const cp = require('node:child_process'); -const path = require('node:path'); -const args = process.argv.slice(2); -const root = process.env.TEST_ROOT; -const secretPath = path.join(root, 'container-secret.sql'); -const log = event => fs.appendFileSync(path.join(root, 'events'), event + '\\n'); -const ci = args.indexOf('-c'); -const cmd = ci < 0 ? '' : args[ci + 1]; -if (cmd.includes('cat >')) { - log('container-secret-created'); - const mapped = cmd.replaceAll('/home/open-design/.local/share/opencode/credentials-sync.sql', secretPath); - const result = cp.spawnSync('sh', ['-c', mapped], {input: fs.readFileSync(0), env: process.env}); - process.exit(result.status ?? 1); -} else if (cmd.includes('better-sqlite3')) { - if (cmd.includes('readFileSync(0')) { - const sql = fs.readFileSync(0, 'utf8'); - if (!sql.includes("INSERT INTO credential") || !sql.includes('fixture-secret')) process.exit(43); - log('sql-stream-received'); - } - process.exit(Number(process.env.TEST_DB_EXIT)); -} else if (args.includes('rm')) { - fs.rmSync(secretPath, { force: true }); -} else if (cmd.includes('opencode service stop')) { - log('service-stopped'); -} else if (cmd.includes('opencode auth list')) { - console.log('test provider'); -} else { - console.error('unexpected Docker invocation'); process.exit(44); -} -`, { mode: 0o755 }) - const result = spawnSync("bash", [script], { - encoding: "utf8", - env: { PATH: `${bin}:${process.env.PATH}`, HOME: root, TMPDIR: tmp, TEST_ROOT: root, TEST_DB_EXIT: String(dbExit) }, - timeout: 15000, - }) - assert.equal(result.status, dbExit, result.stderr) - const events = await readFile(join(root, "events"), "utf8") - assert.doesNotMatch(events, /container-secret-created/) - assert.match(events, /service-stopped/) - assert.match(events, /sql-stream-received/) - assert.deepEqual(await readdir(tmp), [], "host secret temp must be cleaned on success and failure") - assert.ok(!(await readdir(root)).includes("container-secret.sql"), "no persisted container SQL") - assert.doesNotMatch(result.stdout + result.stderr, /fixture-secret/) - } finally { - await rm(root, { recursive: true, force: true }) - } - }) -} diff --git a/test/release-check.test.mjs b/test/release-check.test.mjs deleted file mode 100644 index dcce10e..0000000 --- a/test/release-check.test.mjs +++ /dev/null @@ -1,51 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" - -import { checkVersionPins, extractVersionPins } from "../scripts/release-check.mjs" - -test("extractVersionPins finds every pinned version string", () => { - const text = [ - "npm exec --yes --package=ksi-opencode-harness@0.5.0-beta.6 -- ksi-opencode install", - "**Beta `0.5.0-beta.6`** · [MIT](LICENSE)", - ].join("\n") - assert.deepEqual(extractVersionPins(text), ["0.5.0-beta.6", "0.5.0-beta.6"]) -}) - -test("checkVersionPins fails when a pin does not match the package version", () => { - const result = checkVersionPins({ - version: "0.5.0-beta.7", - readme: "package=ksi-opencode-harness@0.5.0-beta.6", - install: "package=ksi-opencode-harness@0.5.0-beta.6", - }) - assert.equal(result.ok, false) - assert.match(result.problems.join("\n"), /beta\.6/) - assert.match(result.problems.join("\n"), /beta\.7/) -}) - -test("checkVersionPins fails when no pin exists at all", () => { - const result = checkVersionPins({ version: "0.5.0-beta.6", readme: "no pins here", install: "" }) - assert.equal(result.ok, false) - assert.match(result.problems.join("\n"), /no version pins found/i) -}) - -test("checkVersionPins passes when every pin matches", () => { - const result = checkVersionPins({ - version: "0.5.0-beta.6", - readme: "npm exec --package=ksi-opencode-harness@0.5.0-beta.6 and **Beta `0.5.0-beta.6`**", - install: "npm exec --package=ksi-opencode-harness@0.5.0-beta.6", - }) - assert.equal(result.ok, true) - assert.deepEqual(result.problems, []) -}) - -// Guard: the real repository files must stay in sync, so CI catches pin drift. -test("repository README/INSTALL pins match package.json", async () => { - const { readFile } = await import("node:fs/promises") - const root = new URL("..", import.meta.url) - const pkg = JSON.parse(await readFile(new URL("package.json", root), "utf8")) - const readme = await readFile(new URL("README.md", root), "utf8") - const install = await readFile(new URL("INSTALL.md", root), "utf8") - const result = checkVersionPins({ version: pkg.version, readme, install }) - assert.ok(result.pins.length > 0, "expected at least one version pin") - assert.equal(result.ok, true, result.problems.join("\n")) -}) diff --git a/test/tool-choice-isolated.test.mjs b/test/tool-choice-isolated.test.mjs deleted file mode 100644 index d3fac87..0000000 --- a/test/tool-choice-isolated.test.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; -import { verifyToolChoice } from "../scripts/verify-tool-choice-isolated.mjs"; -import * as probe from "../scripts/verify-tool-choice-isolated.mjs"; - -test("a received second request cannot make the first completed assistant the final result", () => { - assert.equal(typeof probe.selectFinalAssistant, "function"); - const first = { id: "first", time: { completed: 1 }, content: [{ type: "tool" }] }; - const streaming = { id: "second", time: { created: 2 }, content: [] }; - const final = { id: "second", time: { created: 2, completed: 3 }, content: [{ type: "text", text: "bounded-summary" }] }; - assert.equal(probe.selectFinalAssistant([first], 2), undefined); - assert.equal(probe.selectFinalAssistant([first, streaming], 2), undefined); - assert.equal(probe.selectFinalAssistant([first, final], 1), undefined); - assert.equal(probe.selectFinalAssistant([first, final], 2), final); -}); - -// Narrow characterization of a native assumption that actually failed in use. -// Opt-in: requires an already installed OpenCode CLI, never installs one. -test("auto-only final-summary overlay preserves the native step limit", { - skip: process.env.KSI_TOOL_CHOICE_INTEGRATION !== "1", - timeout: 90000, -}, async () => { - const result = await verifyToolChoice(); - assert.equal(result.ok, true, JSON.stringify(result)); - assert.equal(result.sharedSessionsTouched, false); - assert.equal(result.realProviderCalls, 0); -}); diff --git a/test/verify-native-v2-isolated.test.mjs b/test/verify-native-v2-isolated.test.mjs deleted file mode 100644 index 67ee867..0000000 --- a/test/verify-native-v2-isolated.test.mjs +++ /dev/null @@ -1,159 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { join } from "node:path" -import { buildIsolatedEnv, checkAgents, checkRetiredSkills, RETIRED_DESIGN_SKILLS } from "../scripts/verify-native-v2-isolated.mjs" -import * as verifier from "../scripts/verify-native-v2-isolated.mjs" - -function catalog(overrides = {}) { - const agents = [ - ...["build", "plan"].map((id) => ({ id, mode: "primary" })), - ...["explore", "developer", "reviewer"].map((id) => ({ id, mode: "subagent" })), - ] - const developer = agents.find((agent) => agent.id === "developer") - developer.model = { providerID: "opencode-go", id: "deepseek-v4.1-flash" } - developer.steps = 47 - developer.permissions = [{ action: "subagent", resource: "*", effect: "deny" }] - Object.assign(developer, overrides.developer ?? {}) - return agents -} - -test("isolated verifier child environment never inherits credentials or shared OpenCode overrides", () => { - const env = buildIsolatedEnv("/tmp/ksi-test", "/usr/bin") - assert.deepEqual(Object.keys(env).sort(), ["HOME", "PATH", "TMPDIR", "XDG_CACHE_HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME"].sort()) - assert.equal(env.HOME, join("/tmp/ksi-test", "home")) - assert.equal(env.XDG_CONFIG_HOME, join("/tmp/ksi-test", "home", ".config")) -}) - -test("isolated server readiness parser accepts only loopback startup with a generated password", () => { - assert.deepEqual(verifier.parseServeStartup("server listening on http://127.0.0.1:49374\nserver password ephemeral-token\n"), { - url: "http://127.0.0.1:49374", - password: "ephemeral-token", - }) - assert.equal(verifier.parseServeStartup("server listening on http://0.0.0.0:49374\nserver password token\n"), null) - assert.equal(verifier.parseServeStartup("server listening on http://127.0.0.1:49374\n"), null) -}) - -test("catalog check retains built-in modes and user routing without requiring KSI Build permissions", () => { - const agents = catalog() - assert.deepEqual(checkAgents(agents), { ok: true, failures: [] }) - agents.find((agent) => agent.id === "developer").mode = "primary" - assert.match(checkAgents(agents).failures.join(" "), /developer.*subagent/) - agents.find((agent) => agent.id === "developer").mode = "subagent" - agents.find((agent) => agent.id === "developer").permissions = [] - assert.match(checkAgents(agents).failures.join(" "), /delegation is not default-denied/) -}) - -test("catalog check rejects a custom-role skill blockade", () => { - const agents = catalog() - agents.find((agent) => agent.id === "developer").permissions = [ - { action: "subagent", resource: "*", effect: "deny" }, - { action: "skill", resource: "*", effect: "deny" }, - ] - assert.match(checkAgents(agents).failures.join(" "), /skill/i) -}) - -test("catalog check rejects every leftover retired role", () => { - for (const retired of ["design", "research", "design-critic", "test-runner"]) { - const agents = catalog() - agents.push({ id: retired, mode: "subagent" }) - assert.match(checkAgents(agents).failures.join(" "), new RegExp(`retired ${retired}`, "i"), retired) - } -}) - -test("retired design skills must not reappear in the isolated skill catalog", () => { - assert.deepEqual(RETIRED_DESIGN_SKILLS, ["frontend-design", "impeccable-design-polish", "web-design-guidelines"]) - assert.deepEqual(checkRetiredSkills([]), { ok: true, failures: [] }) - assert.deepEqual(checkRetiredSkills([{ id: "brainstorming", path: "/tmp/skills/brainstorming/SKILL.md" }]), { ok: true, failures: [] }) - const leaked = checkRetiredSkills([ - { id: "frontend-design", path: "/tmp/skills/frontend-design/SKILL.md" }, - { id: "impeccable-design-polish", path: "/tmp/skills/impeccable-design-polish/SKILL.md" }, - ]) - assert.equal(leaked.ok, false) - assert.match(leaked.failures.join(" "), /frontend-design/) - assert.match(leaked.failures.join(" "), /impeccable-design-polish/) -}) - -function designDecisions() { - return [ - ...["get_active_context", "get_project", "list_projects", "list_files", "get_file", "get_artifact", "search_files", "list_skills", "list_plugins", "list_agents", "get_run"] - .map(name => ({ agent: "plan", action: `opendesign_${name}`, expected: "allow", effect: "allow" })), - ...["start_run", "cancel_run", "write_file", "delete_file", "create_project", "delete_project", "create_artifact", "collect_brief", "confirm_brief", "future_mutation"] - .map(name => ({ agent: "plan", action: `opendesign_${name}`, expected: "deny", effect: "deny" })), - { agent: "build", action: "opendesign_start_run", expected: "allow", effect: "allow" }, - { agent: "plan", action: "edit", expected: "deny", effect: "deny" }, - ] -} - -test("design permission evidence rejects an allowed Plan mutation and missing evaluation", () => { - assert.deepEqual(verifier.checkDesignPermissions(designDecisions()), { ok: true, failures: [] }) - const decisions = designDecisions() - decisions.find(d => d.agent === "plan" && d.action === "opendesign_start_run").effect = "allow" - const unsafe = verifier.checkDesignPermissions(decisions) - assert.equal(unsafe.ok, false) - assert.match(unsafe.failures[0], /plan opendesign_start_run.*expected deny, got allow/) - assert.equal(verifier.checkDesignPermissions([]).ok, false) -}) - -test("design permission evidence catches a blocked read and an unreported native verdict", () => { - const decisions = designDecisions() - decisions.find(d => d.action === "opendesign_get_artifact").effect = "deny" - delete decisions.find(d => d.action === "opendesign_future_mutation").effect - const result = verifier.checkDesignPermissions(decisions) - assert.equal(result.ok, false) - assert.equal(result.failures.length, 2) -}) - -test("design permission evidence rejects dropped and duplicate contract cases", () => { - for (const action of ["opendesign_future_mutation", "opendesign_get_artifact", "edit"]) { - const missing = designDecisions().filter(d => d.action !== action) - assert.equal(verifier.checkDesignPermissions(missing).ok, false, action) - } - const missingBuild = designDecisions().filter(d => d.agent !== "build") - assert.equal(verifier.checkDesignPermissions(missingBuild).ok, false) - const duplicate = designDecisions() - duplicate[duplicate.findIndex(d => d.action === "opendesign_future_mutation")] = { ...duplicate[0] } - assert.equal(duplicate.length, 23) - assert.equal(verifier.checkDesignPermissions(duplicate).ok, false) -}) - -test("design permission evidence does not trust a rewritten expected verdict", () => { - const decisions = designDecisions() - const unknown = decisions.find(d => d.action === "opendesign_future_mutation") - unknown.expected = "allow" - unknown.effect = "allow" - assert.equal(verifier.checkDesignPermissions(decisions).ok, false) -}) - -function helperDecisions() { - return [ - { agent: "reviewer", action: "edit", expected: "deny", effect: "deny" }, - { agent: "reviewer", action: "subagent", expected: "deny", effect: "deny" }, - ] -} - -// Break caught: the verifier reports success when a non-implementing helper can edit/delegate. -test("helper permission evidence rejects an allowed edit or delegation", () => { - assert.equal(typeof verifier.checkHelperPermissions, "function") - assert.deepEqual(verifier.checkHelperPermissions(helperDecisions()), { ok: true, failures: [] }) - for (let index = 0; index < 2; index++) { - const decisions = helperDecisions() - decisions[index].effect = "allow" - const result = verifier.checkHelperPermissions(decisions) - assert.equal(result.ok, false) - assert.match(result.failures.join(" "), /expected deny, got allow/) - } -}) - -// Break caught: missing, duplicate or self-rewritten receipt expectations hide an unchecked boundary. -test("helper permission evidence requires every unique native verdict and fixed expectations", () => { - assert.equal(typeof verifier.checkHelperPermissions, "function") - const missingEffect = helperDecisions() - delete missingEffect[0].effect - const duplicate = helperDecisions() - duplicate[1] = { ...duplicate[0] } - const rewritten = helperDecisions() - rewritten[0] = { ...rewritten[0], expected: "allow", effect: "allow" } - for (const decisions of [[], helperDecisions().slice(1), missingEffect, duplicate, rewritten]) { - assert.equal(verifier.checkHelperPermissions(decisions).ok, false) - } -}) diff --git a/test/work-doctor.test.mjs b/test/work-doctor.test.mjs deleted file mode 100644 index 446f40d..0000000 --- a/test/work-doctor.test.mjs +++ /dev/null @@ -1,95 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, readdirSync } from "node:fs" -import { tmpdir } from "node:os" -import { join } from "node:path" -import { fileURLToPath } from "node:url" -import { execFileSync, spawnSync } from "node:child_process" -import * as report from "../scripts/work-report.mjs" - -function fixture(t) { - const root = mkdtempSync(join(tmpdir(), "ksi-doctor-test-")) - t.after(() => rmSync(root, { recursive: true, force: true })) - const repo = join(root, "product") - const configDir = join(root, "config") - mkdirSync(join(repo, "docs/superpowers"), { recursive: true }) - mkdirSync(join(configDir, "agents"), { recursive: true }) - return { root, repo, configDir } -} - -function setupLocal({ repo, configDir }, extra = "") { - for (const name of ["developer", "reviewer"]) writeFileSync(join(configDir, `agents/${name}.md`), "role fixture\n") - writeFileSync(join(configDir, "AGENTS.md"), "Fixture policy; no live activation proof\n") - writeFileSync(join(repo, "docs/superpowers/product-state.md"), `## Product binding\n\n| Field | Value |\n| --- | --- |\n| Repository | . |\n| Design project | fixture-design |\n| Design storage | . |\n| Entry | index.html |\n| Start command | node never-execute-this.mjs |\n| Verify command | node also-never-execute-this.mjs |\n| Product URL | https://example.invalid |\n| Brand source | tokens.css |\n${extra}`) -} - -test("absent policy requires setup; the optional role bundle never does", t => { - assert.equal(typeof report.inspectReadiness, "function") - const result = report.inspectReadiness(fixture(t)) - assert.equal(result.status, "needs-setup") - assert.equal(result.checks.find(c => c.id === "roles").state, "optional") - assert.equal(result.checks.find(c => c.id === "policy").state, "missing") -}) - -// Native-first environments do not install the optional Developer/Reviewer bundle. -test("a missing optional role bundle alone stays unverified, not needs-setup", t => { - const f = fixture(t) - writeFileSync(join(f.configDir, "AGENTS.md"), "Fixture policy\n") - writeFileSync(join(f.repo, "docs/superpowers/product-state.md"), `## Product binding\n\n| Field | Value |\n| --- | --- |\n| Repository | . |\n| Start command | node never-execute-this.mjs |\n| Verify command | node also-never-execute-this.mjs |\n`) - const result = report.inspectReadiness({ ...f }) - assert.equal(result.checks.find(c => c.id === "roles").state, "optional") - assert.equal(result.status, "unverified") -}) - -// Catches running recorded commands or equating file presence to runtime capability. -test("complete local files remain live-unverified and are never executed or changed", t => { - assert.equal(typeof report.inspectReadiness, "function") - const f = fixture(t) - setupLocal(f) - const path = join(f.repo, "docs/superpowers/product-state.md") - const before = readFileSync(path, "utf8") - const result = report.inspectReadiness({ ...f, frontend: true }) - assert.equal(result.status, "unverified") - assert.equal(result.checks.find(c => c.id === "binding").state, "present") - assert.equal(result.checks.find(c => c.id === "effective-tools").state, "unknown") - assert.equal(readFileSync(path, "utf8"), before) - assert.deepEqual(readdirSync(f.repo).sort(), ["docs"]) -}) - -test("extra legacy role files are tolerated and never deleted", t => { - const f = fixture(t) - setupLocal(f) - writeFileSync(join(f.configDir, "agents", "test-runner.md"), "legacy Test Runner fixture\n") - const result = report.inspectReadiness({ ...f }) - assert.equal(result.checks.find(c => c.id === "roles").state, "present") - assert.equal(readFileSync(join(f.configDir, "agents", "test-runner.md"), "utf8"), "legacy Test Runner fixture\n") -}) - -test("mismatched repository binding is not usable even if all fields exist", t => { - assert.equal(typeof report.inspectReadiness, "function") - const f = fixture(t) - setupLocal(f) - const path = join(f.repo, "docs/superpowers/product-state.md") - writeFileSync(path, readFileSync(path, "utf8").replace("| Repository | . |", "| Repository | ../other |")) - const result = report.inspectReadiness({ ...f, frontend: true }) - assert.equal(result.status, "needs-setup") - assert.equal(result.checks.find(c => c.id === "binding").state, "mismatch") -}) - -test("doctor CLI validates flags and produces a read-only JSON report", t => { - const f = fixture(t) - const command = fileURLToPath(new URL("../scripts/work-doctor.mjs", import.meta.url)) - const invalid = spawnSync(process.execPath, [command, "--unknown"], { encoding: "utf8" }) - assert.notEqual(invalid.status, 0) - assert.match(invalid.stderr, /Usage:/) - const result = execFileSync(process.execPath, [command, "--repo", f.repo, "--config", f.configDir, "--frontend", "--json"], { encoding: "utf8" }) - assert.equal(JSON.parse(result).status, "needs-setup") -}) - -test("a nonexistent requested repository is a missing prerequisite despite global setup", t => { - const f = fixture(t) - setupLocal(f) - const result = report.inspectReadiness({ ...f, repo: join(f.root, "absent") }) - assert.equal(result.status, "needs-setup") - assert.equal(result.checks.find(c => c.id === "repository").state, "missing") -}) diff --git a/test/work-report.test.mjs b/test/work-report.test.mjs deleted file mode 100644 index 5634780..0000000 --- a/test/work-report.test.mjs +++ /dev/null @@ -1,206 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { mkdtempSync, writeFileSync, rmSync } from "node:fs" -import { tmpdir } from "node:os" -import { join } from "node:path" - -import { - formatTokens, - parseNextSlices, - summarizeBindings, - summarizeGoals, - renderReport, - buildReport, -} from "../scripts/work-report.mjs" - -test("parseNextSlices extracts only the Next slices section", () => { - const markdown = [ - "# Product State", - "", - "## Next slices (미완만)", - "", - "- m04 design-workspace-separation — remaining: 사용자 수용", - "- m05 beta release — remaining: README acceptance", - "", - "## Backlog", - "", - "- something else", - ].join("\n") - assert.deepEqual(parseNextSlices(markdown), [ - "m04 design-workspace-separation — remaining: 사용자 수용", - "m05 beta release — remaining: README acceptance", - ]) - assert.deepEqual(parseNextSlices("# no section"), []) -}) - -test("summarizeGoals normalizes the v2 goals.json shape and sorts by recency", () => { - const raw = { - version: 3, - goals: { - ses_a: { - sessionID: "ses_a", - objective: "first\nobjective", - status: "complete", - tokensUsed: 393061352, - tokenBudget: null, - autoTurns: 9, - maxAutoTurns: 80, - updatedAt: 1790990000, - }, - ses_b: { - sessionID: "ses_b", - objective: "second", - status: "active", - tokensUsed: 10, - tokenBudget: 1000, - autoTurns: 1, - maxAutoTurns: 5, - updatedAt: 1790990500, - }, - }, - } - const goals = summarizeGoals(raw) - assert.equal(goals.length, 2) - assert.equal(goals[0].sessionID, "ses_b", "most recently updated first") - assert.equal(goals[1].objective, "first objective", "newlines collapsed") - assert.equal(goals[1].tokensUsed, 393061352) - assert.equal(goals[1].updatedAt, 1790990000 * 1000, "second-based goal timestamps become milliseconds") - assert.equal(goals[1].maxDurationSeconds, null) -}) - -test("summarizeBindings lists pending runs first and keeps classification", () => { - const raw = { - "run:a@ses_1": { - key: "run:a@ses_1", - state: "delivered", - runId: "a", - sessionID: "ses_1", - updatedAt: 300, - delivery: { classification: "invalid" }, - }, - "run:b@ses_2": { - key: "run:b@ses_2", - state: "held", - runId: "b", - sessionID: "ses_2", - updatedAt: 100, - }, - "run:c@ses_3": { - key: "run:c@ses_3", - state: "tracking", - runId: "c", - sessionID: "ses_3", - updatedAt: 200, - lastStatus: { deliverableValid: true }, - }, - } - const rows = summarizeBindings(raw) - assert.equal(rows.length, 3) - assert.deepEqual( - rows.map((row) => row.state), - ["tracking", "held", "delivered"], - "pending states sort first", - ) - assert.equal(rows[2].classification, "invalid") - assert.equal(rows[0].classification, "valid") -}) - -test("formatTokens keeps numbers readable", () => { - assert.equal(formatTokens(393061352), "393.1M") - assert.equal(formatTokens(922), "922") - assert.equal(formatTokens(1_050_000), "1.1M") - assert.equal(formatTokens(2_500_000_000), "2.50B") - assert.equal(formatTokens(undefined), "?") -}) - -test("unlimited goals are informational rather than a failure warning", () => { - const report = renderReport({generatedAt: 0, days: 7, goals: [{status: "active", tokenBudget: null, maxAutoTurns: null, maxDurationSeconds: null}], designRuns: [], designPaused: {}, usage: {byProject: [], top: []}, repo: null, nextSlices: []}) - assert.doesNotMatch(report, /!unbounded/) - assert.match(report, /limits: none/) -}) - -test("missing or malformed state sources surface warnings instead of silently meaning none", () => { - const before = process.env.KSI_WORK_REPORT_GOALS - process.env.KSI_WORK_REPORT_GOALS = join(tmpdir(), "ksi-definitely-absent-report-state.json") - try { - const report = buildReport({ includeUsage: false }) - assert.equal(report.warnings.some(w => /goal.*unavailable/i.test(w)), true) - } finally { - if (before === undefined) delete process.env.KSI_WORK_REPORT_GOALS - else process.env.KSI_WORK_REPORT_GOALS = before - } -}) - -test("valid JSON of the wrong state shape is unavailable, not zero goals", t => { - const dir = mkdtempSync(join(tmpdir(), "ksi-report-test-")) - t.after(() => rmSync(dir, { recursive: true, force: true })) - const file = join(dir, "state.json") - writeFileSync(file, '"not a goal container"') - const before = process.env.KSI_WORK_REPORT_GOALS - process.env.KSI_WORK_REPORT_GOALS = file - try { - assert.equal(buildReport({ includeUsage: false }).warnings.some(w => /Goal state unavailable/i.test(w)), true) - } finally { - if (before === undefined) delete process.env.KSI_WORK_REPORT_GOALS - else process.env.KSI_WORK_REPORT_GOALS = before - } -}) - -test("design artifact verdict is kept separate from pending product verification in reports", () => { - const rows = summarizeBindings({ one: { state: "delivered", lastStatus: { status: "succeeded", deliverableValid: false, deliverableValidation: "entry_not_touched" } } }) - assert.equal(rows[0].classification, "invalid") - assert.equal(rows[0].productVerification, "required") - const report = renderReport({ generatedAt: 0, days: 7, goals: [], designRuns: rows, designPaused: {}, usage: {byProject: [], top: []}, repo: null, nextSlices: [] }) - assert.match(report, /artifact=invalid.*product=required/) -}) - -// Break caught: undelivered running/failed/canceled snapshots collapse to invalid. -test("design report keeps execution classification consistent before and after delivery", () => { - for (const [state, runStatus, valid, expected, product] of [ - ["tracking", "running", null, "pending", "unknown"], - ["held", "failed", null, "failed", "blocked"], - ["tracking", "canceled", false, "canceled", "blocked"], - ["held", "succeeded", false, "invalid", "required"], - ["tracking", "succeeded", true, "valid", "required"], - ]) { - const record = { state, lastStatus: { status: runStatus, deliverableValid: valid } } - const row = summarizeBindings({ one: record })[0] - assert.equal(row.classification, expected, runStatus) - assert.equal(row.productVerification, product, runStatus) - assert.equal(summarizeBindings({ one: { ...record, state: "delivered", delivery: { classification: expected } } })[0].classification, expected) - } -}) - -// Break caught: deep wrong-type containers disappear as zero activity without warnings. -test("nested malformed goal and design containers are unavailable rather than empty", t => { - const dir = mkdtempSync(join(tmpdir(), "ksi-report-shape-")) - t.after(() => rmSync(dir, { recursive: true, force: true })) - const keys = ["KSI_WORK_REPORT_GOALS", "KSI_WORK_REPORT_BINDINGS", "KSI_WORK_REPORT_PAUSED"] - const before = Object.fromEntries(keys.map(key => [key, process.env[key]])) - const paths = keys.map((key, i) => join(dir, `${i}.json`)) - for (const [i, key] of keys.entries()) process.env[key] = paths[i] - t.after(() => { - for (const key of keys) { - if (before[key] === undefined) delete process.env[key] - else process.env[key] = before[key] - } - }) - writeFileSync(paths[2], "{}") - for (const goals of [{ version: 3, goals: "invalid" }, { goals: null }, { goals: { one: "invalid-record" } }, { goals: { one: [] } }]) { - writeFileSync(paths[0], JSON.stringify(goals)) - writeFileSync(paths[1], "{}") - const report = buildReport({ includeUsage: false }) - assert.equal(report.warnings.some(w => /Goal state unavailable/i.test(w)), true, JSON.stringify(goals)) - } - writeFileSync(paths[0], "{}") - for (const bindings of [{ one: "invalid-record" }, { one: [] }, { one: { lastStatus: "invalid" } }]) { - writeFileSync(paths[1], JSON.stringify(bindings)) - assert.equal(buildReport({ includeUsage: false }).warnings.some(w => /Design state unavailable/i.test(w)), true, JSON.stringify(bindings)) - } - // Empty and supported legacy containers must remain valid and isolated from live files. - for (const goals of [{}, { goals: {} }, { goals: [{ sessionID: "ses_fixture", status: "active" }] }, { ses_fixture: { status: "active" } }]) { - writeFileSync(paths[0], JSON.stringify(goals)) - writeFileSync(paths[1], JSON.stringify({ one: { state: "tracking", lastStatus: null } })) - assert.deepEqual(buildReport({ includeUsage: false }).warnings, []) - } -}) diff --git a/test/work-status.test.mjs b/test/work-status.test.mjs deleted file mode 100644 index 3e13160..0000000 --- a/test/work-status.test.mjs +++ /dev/null @@ -1,203 +0,0 @@ -import test from "node:test" -import assert from "node:assert/strict" -import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, symlinkSync } from "node:fs" -import { tmpdir } from "node:os" -import { execFileSync, spawnSync } from "node:child_process" -import { join } from "node:path" -import { fileURLToPath } from "node:url" - -// Namespace import allows a missing API to fail an assertion, not an import error. -import * as status from "../scripts/work-report.mjs" -import { isOutsideRoot } from "../scripts/work-status.mjs" - -function fixture(t, git = true) { - const root = mkdtempSync(join(tmpdir(), "ksi-status-test-")) - t.after(() => rmSync(root, { recursive: true, force: true })) - if (git) { - execFileSync("git", ["init", "-q", root]) - execFileSync("git", ["-C", root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "--allow-empty", "-qm", "fixture"]) - } - mkdirSync(join(root, ".opencode")) - mkdirSync(join(root, "docs/superpowers/plans"), { recursive: true }) - return root -} - -// Break caught: checkpoint completion is trusted despite a different checked-out revision. -test("status warns on stale HEAD and idle checkpoint with unfinished ledger", t => { - assert.equal(typeof status.inspectWorkStatus, "function") - const root = fixture(t) - writeFileSync(join(root, ".opencode/working-state.md"), "HEAD: 0000000\nStatus: idle\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\n\n## Next\n- inspect result\n") - writeFileSync(join(root, "docs/superpowers/plans/fixture.md"), "Status: in progress\n- [ ] Integration\n") - const result = status.inspectWorkStatus(root) - assert.equal(result.recordedStatus, "idle") - assert.equal(result.warnings.some(w => /revision/i.test(w)), true) - assert.equal(result.warnings.some(w => /unfinished ledger/i.test(w)), true) - assert.deepEqual(result.next, ["inspect result"]) - assert.equal(result.acceptance, "not recorded") -}) - -test("missing/non-Git status stays unknown and never invents completion", t => { - assert.equal(typeof status.inspectWorkStatus, "function") - const result = status.inspectWorkStatus(fixture(t, false)) - assert.equal(result.recordedStatus, "unknown") - assert.equal(result.git.head, null) - assert.equal(result.delivery, "unknown") - assert.equal(result.warnings.length > 0, true) -}) - -test("matching status is read-only and reports local delivery separately from acceptance", t => { - assert.equal(typeof status.inspectWorkStatus, "function") - const root = fixture(t) - const head = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" }).trim() - const file = join(root, ".opencode/working-state.md") - const text = `HEAD: ${head.slice(0, 7)}\nStatus: idle\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\n` - writeFileSync(file, text) - writeFileSync(join(root, "docs/superpowers/plans/fixture.md"), "Status: technically complete\n- [x] Integration\n") - const result = status.inspectWorkStatus(root) - assert.equal(result.warnings.length, 0) - assert.equal(result.delivery, "unknown", "no upstream does not mean deployed") - assert.equal(result.acceptance, "not recorded", "technical completion is not acceptance") - assert.equal(readFileSync(file, "utf8"), text) -}) - -// Break caught: normal numbered checkpoint actions disappear from JSON and text. -test("status retains numbered Next actions in recorded order and rendered output", t => { - const root = fixture(t) - const file = join(root, ".opencode/working-state.md") - const text = "Status: active\n\n## Next\n1. Inspect the actual diff\n2. Verify the integrated revision\n\n## Blockers\n- Not a next action\n" - writeFileSync(file, text) - const result = status.inspectWorkStatus(root) - assert.deepEqual(result.next, ["Inspect the actual diff", "Verify the integrated revision"]) - assert.match(status.renderWorkStatus(result), /Next: Inspect the actual diff\n Next: Verify the integrated revision/) - assert.doesNotMatch(status.renderWorkStatus(result), /Next: Not a next action/) - assert.equal(readFileSync(file, "utf8"), text) -}) - -// Break caught: supported Markdown markers/CRLF are skipped or the three-action cap is lost. -test("status accepts mixed Markdown Next markers and caps reported actions at three", t => { - const root = fixture(t) - writeFileSync(join(root, ".opencode/working-state.md"), [ - "Status: active", "", "## Next", "Introductory prose, not an action.", - " 1) inspect result", "* verify persistence", "+ report evidence", "- later action", "", - ].join("\r\n")) - assert.deepEqual(status.inspectWorkStatus(root).next, ["inspect result", "verify persistence", "report evidence"]) -}) - -// Break caught: explanations suppress the idle/unfinished warning or broaden it to non-idle states. -test("descriptive idle statuses reconcile unfinished ledgers without discarding their explanation", t => { - const root = fixture(t) - const head = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" }).trim() - const checkpoint = join(root, ".opencode/working-state.md") - const ledger = join(root, "docs/superpowers/plans/fixture.md") - for (const recorded of ["idle", "idle — checks complete", "idle: checks complete", "Idle - checks complete"]) { - writeFileSync(checkpoint, `HEAD: ${head}\nStatus: ${recorded}\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\n`) - for (const unfinished of ["Status: in progress\n", "Status: technically complete\n- [ ] Required check\n"]) { - writeFileSync(ledger, unfinished) - const result = status.inspectWorkStatus(root) - assert.equal(result.recordedStatus, recorded) - assert.deepEqual(result.warnings, ["Idle checkpoint has an unfinished ledger; reconcile before claiming completion."], recorded) - } - writeFileSync(ledger, "Status: technically complete\n- [x] Required check\n") - assert.deepEqual(status.inspectWorkStatus(root).warnings, [], recorded) - } - writeFileSync(ledger, "Status: in progress\n- [ ] Required check\n") - for (const recorded of ["active — not idle", "unknown", "idleness", "idle-ish", "not idle"]) { - writeFileSync(checkpoint, `HEAD: ${head}\nStatus: ${recorded}\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\n`) - assert.deepEqual(status.inspectWorkStatus(root).warnings, [], recorded) - } -}) - -test("ledger pointers cannot escape the project through traversal or symlinks", t => { - assert.equal(typeof status.inspectWorkStatus, "function") - const root = fixture(t, false) - const outside = fixture(t, false) - writeFileSync(join(outside, "private.md"), "Status: secret-private-content\n") - symlinkSync(join(outside, "private.md"), join(root, "docs/superpowers/plans/linked.md")) - writeFileSync(join(root, ".opencode/working-state.md"), "Status: idle\nSlice: fixture | ledger: docs/superpowers/plans/linked.md\n") - const result = status.inspectWorkStatus(root) - assert.equal(result.ledger.status, "unknown") - assert.equal(JSON.stringify(result).includes("secret-private-content"), false) - assert.equal(result.warnings.some(w => /outside|unreadable/i.test(w)), true) -}) - -// Break caught: a reused ledger's first completed status hides later active work. -test("multiple ledger statuses are ambiguous without an explicit current section", t => { - const root = fixture(t) - const head = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" }).trim() - writeFileSync(join(root, ".opencode/working-state.md"), `HEAD: ${head}\nStatus: idle\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\n`) - writeFileSync(join(root, "docs/superpowers/plans/fixture.md"), "## Prior\nStatus: complete\n- [x] Old\n\n## Current\nStatus: in progress\n") - const result = status.inspectWorkStatus(root) - assert.equal(result.ledger.status, "ambiguous") - assert.equal(result.warnings.some(w => /multiple.*status|ambiguous/i.test(w)), true) -}) - -// Break caught: historical statuses/checkboxes contaminate the selected current slice. -test("an explicit ledger section isolates current status and unfinished work", t => { - const root = fixture(t) - const head = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" }).trim() - const checkpoint = join(root, ".opencode/working-state.md") - writeFileSync(join(root, "docs/superpowers/plans/fixture.md"), "## Prior\nStatus: active\n- [ ] Historical item\n\n## Current\nStatus: complete\n- [x] Done\n\n## Following\nStatus: active\n- [ ] Other slice\n") - writeFileSync(checkpoint, `HEAD: ${head}\nStatus: idle\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\nLedger section: Current\n`) - assert.equal(status.inspectWorkStatus(root).ledger.status, "complete") - assert.deepEqual(status.inspectWorkStatus(root).warnings, []) - writeFileSync(checkpoint, `HEAD: ${head}\nStatus: idle\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\nLedger section: Following\n`) - assert.equal(status.inspectWorkStatus(root).ledger.status, "active") - assert.equal(status.inspectWorkStatus(root).warnings.some(w => /unfinished ledger/i.test(w)), true) -}) - -// Break caught: missing/duplicate section selectors silently fall back to older work. -test("missing or duplicate ledger sections stay unknown or ambiguous with a warning", t => { - const root = fixture(t) - const head = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" }).trim() - const checkpoint = join(root, ".opencode/working-state.md") - const ledger = join(root, "docs/superpowers/plans/fixture.md") - writeFileSync(checkpoint, `HEAD: ${head}\nStatus: idle\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\nLedger section: Current\n`) - writeFileSync(ledger, "## Prior\nStatus: complete\n") - const missing = status.inspectWorkStatus(root) - assert.equal(missing.ledger.status, "unknown") - assert.equal(missing.warnings.some(w => /section.*missing|section.*unavailable/i.test(w)), true) - writeFileSync(ledger, "## Current\nStatus: complete\n\n## Current\nStatus: active\n") - const duplicate = status.inspectWorkStatus(root) - assert.equal(duplicate.ledger.status, "ambiguous") - assert.equal(duplicate.warnings.some(w => /section.*ambiguous|duplicate/i.test(w)), true) -}) - -// Break caught: inactive matches active; equivalent Markdown tasks are missed. -test("inactive is not active and supported unchecked list markers warn consistently", t => { - const root = fixture(t) - const head = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8" }).trim() - writeFileSync(join(root, ".opencode/working-state.md"), `HEAD: ${head}\nStatus: idle\nSlice: fixture | ledger: docs/superpowers/plans/fixture.md\n`) - const ledger = join(root, "docs/superpowers/plans/fixture.md") - writeFileSync(ledger, "Status: inactive\n- [x] Done\n") - assert.deepEqual(status.inspectWorkStatus(root).warnings, []) - for (const marker of ["-", "*", "+", "1.", "2)"]) { - writeFileSync(ledger, `Status: complete\n${marker} [ ] Required check\n`) - assert.equal(status.inspectWorkStatus(root).warnings.some(w => /unfinished ledger/i.test(w)), true, marker) - } -}) - -test("outside-root guard refuses parent escapes on native and Windows separators", () => { - assert.equal(typeof isOutsideRoot, "function") - // Windows form: backslash escapes are refused, siblings allowed. - assert.equal(isOutsideRoot("..\\outside", "\\"), true) - assert.equal(isOutsideRoot("..\\..\\outside", "\\"), true) - assert.equal(isOutsideRoot("..", "\\"), true) - assert.equal(isOutsideRoot("plans\\linked.md", "\\"), false) - // Only a leading `..` segment escapes; deeper segments are contained. - assert.equal(isOutsideRoot("plans\\..\\linked.md", "\\"), false) - // POSIX form: behavior unchanged. - assert.equal(isOutsideRoot("../outside", "/"), true) - assert.equal(isOutsideRoot("..", "/"), true) - assert.equal(isOutsideRoot("plans/linked.md", "/"), false) - // A backslash is an ordinary filename character on POSIX, not an escape. - assert.equal(isOutsideRoot("..\\outside", "/"), false) -}) - -test("status CLI rejects unknown options and option names used as paths", () => { - const command = fileURLToPath(new URL("../scripts/work-report.mjs", import.meta.url)) - for (const args of [["--unknown", "--status"], ["--repo", "--json", "--status"]]) { - const result = spawnSync(process.execPath, [command, ...args], { encoding: "utf8" }) - assert.notEqual(result.status, 0) - assert.match(result.stderr, /Usage:/) - } -})