Skip to content

feat(terminal): embedded web terminal — sub-project #1/4 (spec→plan→Sprint 175) - #16

Merged
AlbSar merged 34 commits into
mainfrom
docs/embedded-web-terminal-spec
May 20, 2026
Merged

feat(terminal): embedded web terminal — sub-project #1/4 (spec→plan→Sprint 175)#16
AlbSar merged 34 commits into
mainfrom
docs/embedded-web-terminal-spec

Conversation

@AlbSar

@AlbSar AlbSar commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Ships sub-project #1/4 of the embedded web terminal initiative: a VSCode-style dockable terminal inside the deckent dashboard with interactive claude/gemini/codex/deckent/shell PTY sessions, ws transport, tmux-style reattach, and a hard security boundary that is independent of (and stricter than) the global API auth bypass.

26 commits / 145 files / +7,082 / −1,561.

What's in this PR

Spec & Plan (commits 03ff09c..905087d)

  • docs/superpowers/specs/2026-05-19-embedded-web-terminal-design.md — design with §1c verified Step-A, §1c.2 auth root-cause, §1d VSCode dock + enterprise seams
  • docs/superpowers/plans/2026-05-19-embedded-web-terminal.md — 5-wave TDD plan (binding per-task contract)
  • ADR-062 (docs/adr/062-embedded-web-terminal.md) accepted + ADR-010 amendment for ws & @lydell/node-pty

Sprint 175 Implementation — 5 sequential waves

Wave Scope Commit
W0 foundations: deps + ADR + config + shared types 84b1640
W1 backend core: AuthProvider + LocalPtyBackend + TerminalAudit + PtySessionManager 6b8e554
W2 wiring: WS gateway + HTTP control routes + serve CLI surface c496d88, 1a69b68
W3 frontend: DockPanel + xterm multi-tab + ConfigPage Terminal category 88e51e3, 4664cf6
W4 e2e reattach + EN/TR user guides ffb67b7

Follow-up fixes (post Sprint 175 dogfood)

  • 742af849 — plug SQLite URI phantom-file leak in audit.test.ts
  • 89bc7d5b — record milestone across canonical docs
  • 110c24c2swap node-pty@lydell/node-pty for zero-compile install (closes debt-175-006: linux-x64 prebuild gap)
  • 713ba9bf — Sprint 175 follow-up: terminal config fallback + dashboard test sync
  • ac3af194 — refresh runtime regen + ADR-062 propagation
  • 4b5cec7b — archive directives + audit outputs (sprints 173–175)
  • eb683b44 — gitignore: drop sprint runtime debris + untrack already-ignored files

Security invariant (non-negotiable)

The terminal's WS auth is independent of DECKENT_API_AUTH_DISABLED and stricter than the global API path. Token is delivered via WebSocket subprotocol (not Authorization header), and auth happens before any PTY spawn (4401 close on bad token → no shell, no audit, no resource leak). Raw PTY output is never persistedTerminalAudit records structured events only (action + tenant_id + kind, no bytes).

Tests proving both rails:

  • tests/api/terminal/auth-provider.test.ts — 4 cases incl. DECKENT_API_AUTH_DISABLED=1 with bad token → still rejected
  • tests/api/terminal/ws-gateway.test.ts — bad subprotocol token → close before bridge, no spawn
  • tests/api/terminal/audit.test.ts — assertion that audit content never contains raw PTY bytes

Enterprise seams (interfaces only — #3 implements)

Per spec §1d, the protocol surface is multi-tenant-ready but the only concrete impl is local:

  • AuthProvider interface + LocalTokenAuthProvider (SHA-256 + timingSafeEqual)
  • SessionBackend interface + LocalPtyBackend (remote / k8s pod-exec deferred to ci: Bump actions/setup-node from 4 to 6 #3)
  • tenantId field in every type from day one

Smoke test ✅ (Alperen, 2026-05-20 evening)

Live confirmation: claude / gemini / shell tabs spawn, type-through works, ANSI rendering correct, detach→reattach replays buffer, idle reaper exempts deckent sessions, multi-tab in DockPanel toggle/resize functional.

Roadmap — what's next

This is #1/4. Subsequent sub-projects (each its own spec→plan→sprint):

Server-restart session persistence (disk-backed) is backlog for post-#1.

Test plan

  • npm install clean on linux-x64 (was failing pre-@lydell/node-pty swap)
  • npm run lint (tsc --noEmit) exit 0
  • npm run lint:adr exit 0 (ADR-062 valid)
  • npx vitest run tests/api/terminal/ — all PASS (auth-provider, session-backend, audit, session-manager, ws-gateway, server-routes, e2e-reattach)
  • npm run test:dashboard — 493/493 PASS (incl. new terminal panel/view/tabs/dock + ConfigPage Terminal category)
  • Manual smoke: npm run build:all && deckent serve → dashboard at http://127.0.0.1:7777 → open dock → spawn each of (claude, gemini, codex, deckent, shell), type, detach, reattach
  • npm pack --dry-run clean (deps include @lydell/node-pty + ws; no internal state files)

🤖 Generated with Claude Code

AlbSar and others added 30 commits May 19, 2026 18:21
… kayıp 722 satır

afc2638'den iç-kanonik anchor geri getirildi (Sprint 149-200 master roadmap,
20 Beta GA gate, debt envanteri, risk matrisi, rekabet/pazarlama, anchor kuralları).
Sprint 172 doc-reorg (1c8cef2) docs/vision/roadmap.md'ye damıtırken ~210 satır düşmüştü.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Yeni ⚡ 2026-05-19 bölümü: Brain/Deckent stabil; Sprint 152-174 olgunlaşma;
  1 Haziran 2026 = OSS Public Beta (Sprint Mode); post-beta ark (AEGIS 175-200 + vertical)
- §4 Master Roadmap / §5 20-Gate / §6 Debt → 🕓 HISTORICAL banner (içerik kanıt olarak korundu)
- Header Last update + reconciliation note güncellendi

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m A)

deckent init artık eksik ön-koşulları (claude/codex/gemini CLI, tmux) tespit
edip onaylı kurar — blueprint §3.4 "auto-install" gerçeği. ADR-062.

- NEW src/core/provisioner.ts: planInstall/installTool/provisionMissing/
  resolveProvisionMode/collectMissingTools; OS-aware, ADR-006 shell-free spawn,
  PROVISIONER_BIN_WHITELIST, no silent sudo (23 test, RED→GREEN)
- init.ts: --yes / --no-install + per-tool consent prompt + post-install re-verify
- doctor.ts + doctor-format.ts: getProviderInstallHint → provisioner tek-kaynak
  (legacy format korundu, 0 test regresyonu)
- mcp/tools/init.ts: installMissing opt-in paritesi (MCP interaktif consent yok)
- ADR-062 (numara DB+FS doğrulandı; 049-059 TaskType ailesine ayrık)

Tests: provisioner 23 + doctor 252 + init 211 PASS, tsc 0 error, lint:adr OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brainstormed design for VSCode-like embedded web terminal:
full PTY (node-pty) + ws + xterm.js, multi-tab claude/gemini/codex/deckent/shell,
localhost-default + zero-config token, tmux-like reattach, transparent DB audit.
4-part decomposition; #1 only. ADR-010 amendment + new ADR required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion constraint checklist for the upcoming dashboard reorg so the
embedded web terminal integrates painlessly later. Adds process gate:
joint current-state analysis + verified-processes-only plan + run
systematic-debugging before finalizing the implementation plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Kırık dashboard onarıldı — kullanıcı tarafında çalışır web uygulaması.

- NEW dashboard-dir.ts: bundled dashboard resolver (dist/dashboard, kurulu-paket
  güvenli). Eski bug: web/serve staticDir'i <projectRoot>/src/dashboard/dist'e
  bakıyordu + serve hiç geçmiyordu (2 test bu bug'ı encode ediyordu → doğru
  kontrata güncellendi)
- NEW chat-handler.ts: gerçek /api/chat (status/help/guidance) — 404 stub kapandı
- web.ts/serve.ts: getDashboardStaticDir() wire; server.ts: POST /api/chat route
- NEW scripts/build-dashboard.mjs: dayanıklı build (src/dashboard deps oto-kur +
  --emptyOutDir) — yayınlanan paket artık boş dist/dashboard ile çıkmaz
  (build canlı doğrulandı: dist/dashboard/index.html + assets üretildi)
- npx-first: README quick start/requirements/installation + blueprint §3.4/§12
  doc-honesty (gerçek durum + Sprint 175 repair notu)

Tests: 666 PASS (provisioner+doctor+init+server+serve+web+chat+dashboard-dir),
tsc 0 error. dist/ gitignored — npm files+prepublishOnly üretir.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gerçek amblem (1024px pixel-art devre-kartı kraken) kanonik konumlara kondu:
- docs/assets/logo.png (README üstü, ortalı)
- src/dashboard/public/{logo,favicon}.png + index.html favicon link +
  Layout.tsx sidebar header'da logo (pixelated render)
- docs/.vitepress/public/{logo,favicon}.png + config.ts logo/favicon → png

Dashboard build doğrulandı: logo/favicon dist/dashboard/'a düşüyor. ADR-021
(Kraken Brand Identity) artık grafik amblemle somut.

Not: favicon = full 1024px png (~1.4MB) — imagemagick/sharp yok, ADR-010
minimal-dep gereği image-tooling eklenmedi; küçük favicon post-beta optimize.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Windows PowerShell System.Drawing ile 1024px → 64x64 RGBA yeniden
boyutlandırıldı (şeffaflık korundu), aynı isim (favicon.png). Logo (logo.png)
tam çözünürlükte kalır. Dashboard build doğrulandı.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gate #1 joint analysis (Alperen-approved). Adds §1c verified facts:
ADR-010 real path + extend existing Sprint-172 Amendment; frontend
auth gap (systematic-debugging target); WS auth via verifyBearerToken
primitive; absent upgrade handler; config type-surface debt; self-mod
sequential mandatory; branch strategy. §8/§5 aligned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…isions

Phase 1 root cause: dashboard works only via DECKENT_API_AUTH_DISABLED=1
(no browser auth path for header-less transports; B-022 flagged).
Decisions: terminal WS auth independent+stricter than global bypass;
token via localhost page-inject → WS subprotocol; serve.ts CLI surface.
+ VSCode dock-panel UX; enterprise AuthProvider/SessionBackend/tenantId
seams (design now, implement #3). Decomposition preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bite-sized TDD plan from verified §1c/§1d ground truth. Wave 0 deps/ADR/
config/types → 1 backend core → 2 wiring → 3 frontend dock+xterm → 4
e2e/docs/verify. Self-modifying sequential. Self-review included.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ential wave

Plan→DIRECTIVES (self-modifying dogfood, sequential). Plan binding contract
905087d. opus=code/sonnet=doc per project rule. Security invariant (auth
bypass-independence + raw-output-never-persist) = auto NO_GO. Build/run =
Alperen only. Sprint NOT started — awaiting user approval.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
systematic-debugging Phase 1+4.5: 5-sprint loop root cause = auto-debt-
injection (sprint-planner.ts:197-216) producing empty-scope CRITICAL
tasks; workers cannot resolve a bookkeeping artifact; re-injection 4
sprints (170→174) was the 3+ failed identical-mode fixes pattern.

Original 170-001 code verified in repo (tmux.ts:61-70, commit 5ffbf3e).
Structural cause already fixed (Sprint 138 T-13 Docker HB Core Fix).

Non-destructive DB upsert: status active→resolved, metadata.resolution
attached (1020 char honest closure). Unblocks Sprint 175 dry-run:
21→20 tasks, dependency IDs realigned (W0.1=175-001, off-by-one fixed).

Architectural follow-up (empty-scope auto-inject) deferred to sub-project
#2 per Alperen 2026-05-20.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cts)

Caught during Sprint 175 prep, deferred to #2 per Alperen 2026-05-20:
(1) auto-debt-injection empty-scope bug — sprint-planner.ts:197-216,
4-sprint loop root cause for debt-170-001-fix; (2) re-plan orphan
cleanup — .tasks/task-{id}-*.json from prior iteration not unlinked
(today's finding: orphan task-175-021.json hand-removed).

.tasks/ is gitignored — orphan removal not in git history; spec §1d
backlog is the durable record.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Observed during Sprint 175 EXECUTE (user reported terminal warning).
Three call-sites pass args array WITH shell:true on all platforms:
- src/core/plugin-hooks.ts:395, :577
- src/orchestra/baseline-tracker.ts:85
Node DEP0190 + ADR-006 violation + already flagged by
authority-enforcer.ts:464-481 (lint, not runtime-enforced).
Deferred to sub-project #2 per Alperen 2026-05-20.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint-175 audit (W1.3/175-007-xfix) used a SQLite URI shared-cache
trick (`file:audit-idem-XXX?mode=memory&cache=shared`) to share an
in-memory DB between two MemoryStore handles for the idempotent-
migration regression test. better-sqlite3 requires `{ uri: true }`
to honor URI syntax — the option is missing from MemoryStore's
constructor AND from @types/better-sqlite3, so the entire URI string
was written to disk as a literal filename (~100KB SQLite DB per run).

22 phantom files (2.2MB) had accumulated at repo root. Fixed by
refactoring the test to use a real tmpdir() path with explicit
rmSync cleanup (incl. -wal/-shm sidecars from WAL mode). Production
code unchanged. Added .gitignore guard against accidental regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 0 of the Embedded Web Terminal (Sprint 175, sub-project #1/4):

- node-pty ^1.0.0 + ws ^8.18.0 runtime deps
- ADR-010 Sprint-172 Amendment extended with both deps → ADR-062
- ADR-062 (Embedded Web Terminal) accepted; 062-vs-062 collision with
  Workstream A's consent-based-provisioning resolved by renaming the
  latter to 063-* (README ADR index regenerated)
- TerminalConfig added to DeckentConfig type (no bolt-on intersection
  pattern — proper field) with secure defaults (localhost-default,
  shell-allowed, conservative caps)
- src/api/terminal/types.ts — 7 shared exports incl. tenantId enterprise
  seam baked in from day one

Plan: docs/superpowers/plans/2026-05-19-embedded-web-terminal.md W0
Spec: docs/superpowers/specs/2026-05-19-embedded-web-terminal-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 1 of the Embedded Web Terminal (Sprint 175, sub-project #1/4):

- LocalTokenAuthProvider: SHA-256 + timingSafeEqual constant-time compare;
  DELIBERATELY ignores DECKENT_API_AUTH_DISABLED — terminal auth is
  independent of and stricter than the global API bypass (spec §1c.2,
  aligns with Sprint-171 B-022 hardening). RCE endpoint must NEVER inherit
  a dev convenience.
- SessionBackend interface + LocalPtyBackend impl (node-pty spawn). The
  interface is the enterprise seam: future remote / k8s pod-exec impls
  ship in sub-project #3 without call-site changes.
- TerminalAudit: low-volume structured events to memory.db (audit type,
  tenant_id column, decay_exempt). Raw PTY output is NEVER passed in.
  memory-store.ts schema: additive ALTER TABLE for tenant_id (idempotent
  PRAGMA-guarded migration); memory-types.ts adds 'audit' EntryType.
- PtySessionManager: Map<sessionId,PtySession>, bounded ring buffer
  (scrollbackBytes), attach/detach ≠ kill (tmux-like), explicit kill,
  idle reaper via idleTimeoutMs (deckent kind exempt for long sprints).

Tests: 4/4 auth-provider (incl. bypass-independence), 1/1 session-backend
(real bash spawn), 4/4 session-manager (ring bound, detach≠kill,
maxSessions, reaper deckent-exempt). All TDD RED→GREEN.

Plan: docs/superpowers/plans/2026-05-19-embedded-web-terminal.md W1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 2 of the Embedded Web Terminal (Sprint 175, sub-project #1/4):

- attachTerminalGateway: registers server.on('upgrade') for /api/terminal/ws,
  extracts token from Sec-WebSocket-Protocol (deckent.<token> — browsers
  cannot set Authorization on WebSocket, spec §1c.2), verifies via
  AuthProvider.verify() BEFORE any pty spawn, then bridges socket ↔
  session-manager. JSON protocol: attach/input/resize ↑, output/exit ↓.
  Backpressure: pause sending when ws.bufferedAmount > 1MB. Reattach
  replays bounded ring buffer then live-streams.
- src/api/server.ts: GET/POST /api/terminal/sessions + DELETE /:id (Bearer
  middleware reused for HTTP control); terminal auto-token (randomUUID)
  generated even when API auth is disabled; localhost-only bootstrap
  injection of window.__DECKENT_TERMINAL_TOKEN__ into served index.html
  (127.0.0.1 / ::1 only — remote callers must supply token explicitly).
- src/cli/commands/serve.ts: --host <addr> + --no-terminal flags. Refuses
  to enable the terminal on a non-localhost bind without an explicit token.

Tests: 2/2 ws-gateway (reject-before-spawn + accept+replay), 5/5
server-routes (CRUD + localhost-only inject + terminal-disabled path),
3/3 serve-terminal CLI options.

Plan: docs/superpowers/plans/2026-05-19-embedded-web-terminal.md W2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 3 of the Embedded Web Terminal (Sprint 175, sub-project #1/4):

- @xterm/xterm ^5.5.0 + @xterm/addon-fit ^0.10.0 as dashboard devDeps
  (ADR-010 unaffected — frontend devDep, not runtime).
- src/dashboard/src/lib/terminal-api.ts: getBootstrapToken (reads
  window.__DECKENT_TERMINAL_TOKEN__ injected by server.ts for localhost
  callers only) + sessions CRUD fetch helpers.
- useTerminalSocket: WS hook with Sec-WebSocket-Protocol deckent.<token>,
  auto-reconnect with exponential backoff (5-retry cap), reattach by
  sessionId on every reconnect (tmux-like UX).
- TerminalView: xterm.js + FitAddon, ResizeObserver-driven fit + WS
  resize protocol, term.onData → socket.send, dispose cleanup.
- TerminalTabs + TerminalPanel: multi-tab state with 5 quick-launch
  kinds (claude / gemini / codex / deckent / shell), close + switch.
- DockPanel: VSCode-like resizable + collapsible bottom dock; Layout.tsx
  hosts <DockPanel><TerminalPanel/></DockPanel> OUTSIDE the React Router
  Outlet so terminal sessions persist across page navigation.
- ConfigPage: new 'Terminal' category surfacing all 5 TerminalConfig
  fields (enabled, allowShellKind, maxSessions, idleTimeoutMs,
  scrollbackBytes); EN + TR i18n labels.

Tests: 15/15 dashboard terminal suite (terminal-api 2, useTerminalSocket
6, DockPanel 3, TerminalView 2, TerminalPanel 2) — all PASS.

Plan: docs/superpowers/plans/2026-05-19-embedded-web-terminal.md W3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 4 of the Embedded Web Terminal (Sprint 175, sub-project #1/4):

- e2e-reattach.test.ts: real http server + real ws + real node-pty +
  full PtySessionManager + ws-gateway stack. Phase: ws1 attach → input →
  disconnect → (while detached) mgr.write MARKER_ONE + MARKER_TWO →
  ws2 reattach by sessionId → assert BOTH markers replayed from ring
  buffer before live stream resumes. Validates the tmux-like client-
  disconnect-survives invariant. Runtime ~960ms in CI.
- docs/guide/terminal.md (EN canonical): what it is, security model
  (localhost-default, token auto-inject, bypass-independent auth,
  remote = explicit --host + token + user-managed TLS), audit
  timeline, reattach semantics + explicit server-restart boundary,
  config keys, ws subprotocol contract, sub-project roadmap.
- docs/guide/terminal-tr.md: TR parallel (project rule: TR docs are
  never removed; full parity maintained).

Plan: docs/superpowers/plans/2026-05-19-embedded-web-terminal.md W4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint-175 Wave 2 (T2.3) added --host / --no-terminal flags but
serve.ts still called createHttpServer(root, port, staticDir) with the
positional signature — terminalBackend was never passed, so server.ts'
`if (terminalBackend)` guard at line 877 stayed false:
- terminalToken not minted
- window.__DECKENT_TERMINAL_TOKEN__ not injected into served index.html
- attachTerminalGateway never called (no /api/terminal/ws upgrade handler)
- GET /api/terminal/sessions 401'd via the generic Bearer middleware
- Terminal panel rendered visually but had no live channel

Wire it: import LocalPtyBackend, instantiate when isLocalhost && !--no-terminal,
pass via options object to createHttpServer. Server signature already
supported it (HttpServerOptions.terminalBackend, line 784 of server.ts);
only the CLI hand-off was missing.

Verified: tsc clean, dist/cli/commands/serve.js now imports LocalPtyBackend
and threads terminalBackend through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint-175 Wave 3 (T3.1) plumbed window.__DECKENT_TERMINAL_TOKEN__
into the SPA and used it on the WebSocket subprotocol — but the HTTP
fetch helpers (createSession / listSessions / killSession) sent the
request WITHOUT any Authorization header. The terminal HTTP block at
src/api/server.ts:919 reads token from req.headers['authorization']
('Bearer …') and rejects every empty-header call with 401, by design
(bypass-independence — spec §1c.2).

Symptom: dashboard panel opened, +shell clicked, all calls 401:
  POST /api/terminal/sessions → 401
  GET /api/terminal/sessions → 401
  DELETE /api/terminal/sessions/:id → 401

Fix: terminal-api.ts now reads the bootstrap token via getBootstrapToken()
and attaches 'Authorization: Bearer <token>' to every terminal HTTP call.
Build smoke: dashboard bundle hash flipped (index-MoZ7g0zT.js →
index-D-n_0X2r.js), Authorization Bearer present in minified output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ical docs

Sub-project #1/4 (Embedded Web Terminal) shipped 2026-05-19→2026-05-20,
operationally smoke-confirmed by Alperen. Update reader-facing docs to
reflect the delivery honestly + the four-part path ahead.

- README + README-TR: new Highlights bullet (top), link to terminal guide
- VISION + VISION-TR: extend 'Where we are now' / 'Where we are going'
  with the embedded terminal as the first concrete step toward agentic-
  OS workflows + the 3 deferred sub-projects (#2 self-security,
  #3 multi-tenant/k8s, #4 enterprise integrations) with the seams
  (AuthProvider / SessionBackend / tenantId) called out
- docs/release/roadmap.md: new 'Phase 3.6: Embedded Web Terminal' before
  Phase 3.5, with delivered checklist + sub-project #2-4 backlog
- docs/release/beta-tracker.md + -tr.md: Last-updated header refreshed
  (2026-05-14→2026-05-20); new 'Sprint 175 — Embedded Web Terminal'
  section with metrics (46/46 tests, build/pack clean), honest debt
  (node-pty linux-x64 prebuild, DECKENT_API_AUTH_DISABLED=1 dashboard
  precondition), process learnings (two durable feedback memories)
- docs/ROADMAP-GOD-LEVEL.md: new ⚡ 2026-05-20 section at the top, full
  Sprint 175 record + sub-project backlog table + Sprint 176+ priorities
- docs/adr/README.md: regenerated index (already had ADR-062/063 after
  collision rename earlier)

No exaggeration: smoke confirmed, but DECKENT_API_AUTH_DISABLED=1 is
still required for dashboard data calls (frontend general API auth
plumbing is sub-project #2/#3 scope) and node-pty linux-x64 prebuild
needs the optionalDep fix in Sprint 176. Recorded as honest debt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
175-006 left a tech-debt: node-pty@^1.1.0 ships no linux-x64 prebuild and
falls back to node-gyp rebuild (needs python3/build-essential). On OSS GA
installs this breaks `npm install` for users without C++ toolchain.

@lydell/node-pty is a thin drop-in fork that distributes prebuilt binaries
via npm-native optionalDependencies (6 platforms: linux/darwin/win32 ×
x64/arm64). Same API surface (`pty.spawn(...)`), one-line import change.

Result: tests/api/terminal/session-backend.test.ts (skipped on linux-x64
in Sprint 175) now PASSES, e2e reattach still green, tsc --noEmit clean.

Closes debt-175-006 (env build-tooling dependency).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three deferred fixes from Sprint 175 implementation that were missing
from the wave commits:

1. sprint-docs-updater.ts — add `terminal:` block to the fallback config
   used when projects haven't customized .deckent/config.json, so the
   ConfigPage Terminal category (W3.6) has live values on first run.

2. dashboard-page.test.ts — CATEGORIES count assertion 13 → 14 to match
   the Terminal category added by W3.6.

3. dashboard-build-smoke.test.ts — adapt to build:dashboard now delegating
   to scripts/build-dashboard.mjs (script wrapper, not a raw vite call).
   Verifies wrapper script content matches the expected dashboard path.

All 493 dashboard tests pass; tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auto-regenerated bookkeeping from Sprint 175 (embedded web terminal,
sub-project #1/4). No semantic changes — these are outputs of brain/
auditor scans + sprint-docs-updater hooks.

- .brain/exports/{summary,memory,debt}.md: ADR-062 added to active
  decisions, Sprint 175 learnings recorded, debt-175-006 closed by
  follow-up commit (now resolved via @lydell/node-pty swap).
- .claude/.codex/.cursor/.gemini/rules/{brain,auditor,worker-default}.md:
  ADR list regenerated to include ADR-062 (Embedded Web Terminal).
- .deckent/agents/*/agent.json + skills/*/manifest.json: routing stats
  (totalUses/successRate) updated by sprint-router outcome tracking.
- .deckent/features-manifest.json: Sprint 175 feature counts refreshed.
- docs/CHANGELOG.md, docs/SPRINT-LOG.md: Sprint 175 milestone appended.
- docs/audits/sprint-139/dead-code-report.md: minor stat resync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preserve work artifacts from the three sprints that culminated in the
embedded web terminal (Sprint 175 ships sub-project #1/4):

- .brain/archive/DIRECTIVES-sprint-{173,174,175}.md: original sprint
  goal text + per-task scope. Memory V2 stores parsed content in
  memory.db, but the verbatim markdown is useful for git-history grep
  ("which sprint introduced X?") and for review of brain's planning
  decisions.
- docs/audits/sprint-{173,174,175}/load-test-report.md: read-only
  audit outputs captured during each sprint's EVALUATE phase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d files

Branch carried 70+ runtime/debris files that gitignore patterns didn't
fully cover, plus several files that became gitignored long ago but
remained tracked (gitignore is retroactive only). One-time cleanup:

New ignores:
- .brain/ERRORS.md, .brain/PROJECT-IDENTITY.md (deprecated per ADR-046),
  .brain/directives-backup/
- .deckent/sprint-*-panic-*.json, sprint-*-*-result.json, sprint-*-
  subagent-prompts/, sprint-*-merge-runbook.sh, sprint-*-worktree-
  setup.sh, sprint-*-dispatch-locks.json, evaluations/
- src/dashboard/tsconfig.tsbuildinfo
- .claude/settings.local.json
- .npm-cache/, vitest-out.txt, kendimenot.md

Untracked (git rm --cached, file remains on disk):
- .brain/{ERRORS,MEMORY,RETRO}.md
- .deckent/{config,project-stack}.json
- src/dashboard/tsconfig.tsbuildinfo
- .claude/settings.local.json
- .deckent/archive/metrics/metrics-sprint-164.jsonl.gz (already-deleted)

These files regenerate on first sprint run, so removing from index
doesn't break workflow; it just stops the per-sprint diff noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ems)

The §1d backlog had 3 items captured during Sprint 175 prep; the
post-implementation review surfaced 3 more (4–6) plus a self-security
procedure scope section for the #2 spec phase. All references checked
against current source (npm run lint:link clean):

4. Schema-gate coverage enforcement gap — coverage_threshold auto-lowers
   when avg <70%, so the gate moves with reality instead of holding the
   bar. Needs hard-floor vs aspirational split.
5. WorkerCard/DashboardPage pre-existing TS errors — TS2345 i18n
   contravariance, suppressed because root `lint` doesn't recurse into
   src/dashboard/tsconfig.json. Wire the dashboard tsc into root lint.
6. `doctor` DECISIONS.md obsolete check — Memory V2 (Sprint 143) moved
   this to memory.db; doctor still hard-requires .brain/DECISIONS.md.
   Cascade fossils across constants.ts, debt-manager.ts, sprint-docs-
   helpers.ts, authority-enforcer.ts catalogued.

Self-security scope section (sub-project #2): prompt guard, command
guard, outbound rate-limit, mutual-TLS hook on AuthProvider, self-audit-
of-audit. To be ADR'd in the #2 spec phase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AlbSar and others added 3 commits May 20, 2026 09:23
Six independent build-blockers surfaced when PR #16 triggered the docs
workflow for the first time (main never had a passing docs build).
Pre-existing root causes, none introduced by Sprint 175:

1. docs/launch/blog-devto-launch.md — YAML frontmatter `description:`
   contained an unquoted second colon ("...one hard-won insight: multi-
   agent..."), parsed as an incomplete explicit mapping pair. Quoted.

2. docs/.vitepress/config.ts — srcExclude expanded to skip developer-
   only directories (superpowers/, audits/, launch/, governance/) that
   contain TS generics (`trace<T>`), placeholder syntax (`<PID>`,
   `<noreply@anthropic.com>`), and other patterns VitePress's Vue
   compiler interprets as unclosed HTML elements. reference/ was
   re-included now that placeholders inside it are escaped (see #3).

3. scripts/gen-reference-docs.mjs — tableCell() now HTML-entity-escapes
   bare-word placeholders like `<sprint-id>`, `<command>`, `<id>` in
   auto-generated reference tables. Regen propagated to cli.md and
   mcp-tools.md. Real HTML tags in source (`<br/>`, `<sub>`) won't
   match this anchored single-token pattern.

4. src/mcp/tools/audit.ts — `.deckent/<sprint-id>-gate.json` in the
   description rewritten as `.deckent/{sprintId}-gate.json` so the
   auto-gen output doesn't ship the breaking placeholder.

5. docs/adr/030 + docs/reference/managed-docs.md — `{{path.to.value}}`
   template placeholders in prose escaped as `&#123;&#123;…&#125;&#125;`
   so Vue's interpolation parser leaves them alone. (Code blocks like
   JSON examples are safe — VitePress treats fenced code as raw.)

6. docs/reference/security.md + docs/guide/docker-backend.md — two
   dead-link callouts fixed: `[ADR Index](../adr/)` → `(../adr/README)`
   (vitepress directory links don't fall back to README), and the
   architecture-overview cross-link replaced with a GitHub URL (the
   architecture/ dir stays in srcExclude due to its TS generics).

Verification: `cd docs && npx vitepress build` completes clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six pre-existing test failures surfaced by PR #16 CI dogfood (all
present on main, none introduced by Sprint 175). Each test asserted
an obsolete contract — the production code was updated in an earlier
sprint but the matching test was not.

CLI suite (3 fails):
- tests/cli/commands/serve-overhaul.test.ts:128 — `createHttpServer`
  signature became `(root, opts)` in Sprint 175 W2.3 (commit 1a69b68)
  to wire host + terminalBackend. Updated to match opts-object shape.
- tests/cli/serve.test.ts (2 cases — default & custom port) — same
  signature change; also the listen-on banner now uses 127.0.0.1
  (explicit-bind security default) instead of "localhost".

Security suite (2 fails):
- tests/security/shell-injection.test.ts (2 cases) — Sprint 170 P0-3
  (Tmux Prompt Filename TaskId-Aware) renamed `.prompt-{rand}.txt`
  to `.prompt-{taskId}-{rand}.txt`. Updated both assertions to
  include the taskId prefix.

Monitor suite (1 fail):
- tests/monitor/alert-emitter.test.ts:112 — "provider parity" asserted
  `^paths:` frontmatter on `.codex/.gemini/.cursor` rules, but
  src/core/rule-generator.ts:77-113 intentionally emits the
  `paths:` hint only for Claude (the other three loaders don't honour
  it). Test relaxed to assert the AUTO-START marker — the actual
  parity invariant.

Verified locally: tests/cli/, tests/security/, tests/monitor/ now
green; full vitest suites pass (Core 4728, MCP+API 2500, Dashboard 493).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two CI infrastructure fixes for PR #16:

1. .github/workflows/dashboard-build.yml — Node 18 dropped from the
   build matrix. Tailwind 4 ships `@tailwindcss/oxide` as a per-platform
   optionalDependency; npm CLI bug #4828 fails to resolve it on Node 18
   in CI runners (the cache layout differs). Tailwind 4 officially
   supports Node 20+. engines.node remains >=18 for the library — only
   the dashboard build needs 20+. The Node 18 row also masked test-
   build smoke failures on otherwise-green matrices.

2. .secrets-baseline — regenerated with `--build-baseline` after the
   Sprint 171 doc reorg moved audit fixtures from `.audit/sprint-167/`
   to `docs/audits/sprint-167/`. All 8 unallowlisted hits are
   confirmed-safe:
   - 6 audit-report fixtures (Sprint 167 T6, Sprint 171 security)
     that document detection patterns by quoting the literal regex.
   - 2 false positives in docs/adr/README.md — the OPENAI_KEY pattern
     matches the substring `sk-detection-...` inside the ADR-039
     filename slug ("self-modifying-task-detection-..."), not a key.

Verified: `node scripts/security/secret-baseline.mjs` exits 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…TS + Current

Sprint 175 PR #16 CI dogfood surfaced 230+ test failures across the entire
matrix. Root cause was not Sprint 175 work — it was systemic CI rot.

## Why now

| Node | Status (May 2026) | We were testing? |
|------|---------------------|---------------------|
| v18 (Hydrogen) | EOL Apr 2025 — 1+ year dead | ❌ Yes (wasted) |
| v20 (Iron) | EOL Mar 2026 — 2 months dead | ❌ Yes (wasted) |
| v22 (Jod) | Maintenance EOL May 2026 — this month | ❌ Yes (wasted) |
| **v24 (Krypton)** | **Active LTS (until May 2027) — production target** | ❌ NO (untested) |
| v26 | Current (latest) | ❌ NO (untested) |

The matrix burned three slots on dead-or-dying releases while never
touching Active LTS or Current. better-sqlite3 v12.10.0 (released May 12,
2026) explicitly removed Node 20/23 prebuilds and added Node 26 — so the
old matrix was guaranteed to fail post-12.10.0 anyway.

## Changes

- All 7 workflows (`ci.yml`, `dashboard-build.yml`, `cross-platform-e2e.yml`,
  `docs.yml`, `publish.yml`, `release.yml`, `secret-scan.yml`):
  - Matrix `[18.x, 20.x, 22.x]` → `[24.x, 26.x]` (test-core, test-orchestra,
    test-cli, test-remaining, dashboard build)
  - Fixed `'22.x'` / `'20'` → `'24.x'` / `'24'` (typecheck, security,
    test-docs-scripts, test-dashboard, test-windows, coverage, build,
    docs, publish, release, secret-scan, cross-platform-e2e)
  - Dashboard artifact upload condition: `matrix.node-version == '22.x'`
    → `'24.x'` so the LTS run ships the artifact.

- `package.json`:
  - `engines.node`: `>=18.0.0` → `>=24.0.0`. Breaking-change-by-design for
    OSS GA — Node 18/20/22 users get a clear error instead of a runtime
    crash later (npm shows `engineStrict` warning at install time).
  - `better-sqlite3`: `^12.9.0` → `^12.10.0` (Node 26 prebuilds, Node 20/23
    cleanup). Verified locally: 12.10.0 ships prebuilds for the new
    matrix; no native compile required at install time.
  - New script `ci:rebuild-native: npm rebuild better-sqlite3 --ignore-
    scripts=false`. Required because `.npmrc` sets `ignore-scripts=true`
    (Sprint 167 H3 supply-chain hardening); `npm ci` honours that and
    skips better-sqlite3's `prebuild-install || node-gyp rebuild`, so
    the native binding never lands without an explicit rebuild step.
    Targeted to the one package that needs it — the .npmrc guard for
    every other dependency stays intact.

- All 7 workflows: new step `npm run ci:rebuild-native` after `npm ci`.
  Five-second cost per job; without it the binding is missing and 200+
  tests collapse on `Database` instantiation.

- `src/cli/entry.ts`: runtime Node guard `< 18` → `< 24`. Error message
  updated to mention Active LTS.

- 7 test files realigned:
  - `tests/github/ci-workflow.test.ts`, `tests/github/workflows/ci.test.ts`,
    `tests/github/workflows/release.test.ts` — workflow assertions
    against the new matrix and fixed versions.
  - `tests/cli/bin-entry-validation.test.ts`, `tests/cli/npx-compat.test.ts`
    — engines.node and entry.ts guard assertions.
  - `tests/core/nervous-enabled-integration.test.ts` — `.deckent/config.
    json` dogfood-only assertion now skipped when the file is absent
    (PR #16 made the config gitignored).

## Local verification (Node 24.15.0, the current Active LTS)

| Gate | Result |
|------|--------|
| Non-orchestra suites | 475 files, **10,625 pass**, 23 skip, **0 fail** |
| Orchestra suite | 205 files, **4,416 pass**, 11 skip, **0 fail** |
| Dashboard suite | 23 files, **493 pass**, **0 fail** |
| `tsc --noEmit` | clean |
| `npm run build` | dist/cli/{index,entry}.js, dist/mcp/server.js, dist/index.js all present with shebang |
| VitePress build | complete |
| `secret-baseline.mjs` | 0 unallowlisted secrets |

Total: **15,534 tests pass, 0 fail**.

## Baseline note

This PR is based on `docs/embedded-web-terminal-spec` (PR #16). PR #16's
test/vitepress/secret-baseline fixes are inherited; once PR #16 merges
to main, this PR's base becomes main automatically with no conflict.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AlbSar
AlbSar merged commit 83e289b into main May 20, 2026
18 of 23 checks passed
@AlbSar
AlbSar deleted the docs/embedded-web-terminal-spec branch May 20, 2026 07:48
AlbSar added a commit that referenced this pull request May 20, 2026
Six independent build-blockers surfaced when PR #16 triggered the docs
workflow for the first time (main never had a passing docs build).
Pre-existing root causes, none introduced by Sprint 175:

1. docs/launch/blog-devto-launch.md — YAML frontmatter `description:`
   contained an unquoted second colon ("...one hard-won insight: multi-
   agent..."), parsed as an incomplete explicit mapping pair. Quoted.

2. docs/.vitepress/config.ts — srcExclude expanded to skip developer-
   only directories (superpowers/, audits/, launch/, governance/) that
   contain TS generics (`trace<T>`), placeholder syntax (`<PID>`,
   `<noreply@anthropic.com>`), and other patterns VitePress's Vue
   compiler interprets as unclosed HTML elements. reference/ was
   re-included now that placeholders inside it are escaped (see #3).

3. scripts/gen-reference-docs.mjs — tableCell() now HTML-entity-escapes
   bare-word placeholders like `<sprint-id>`, `<command>`, `<id>` in
   auto-generated reference tables. Regen propagated to cli.md and
   mcp-tools.md. Real HTML tags in source (`<br/>`, `<sub>`) won't
   match this anchored single-token pattern.

4. src/mcp/tools/audit.ts — `.deckent/<sprint-id>-gate.json` in the
   description rewritten as `.deckent/{sprintId}-gate.json` so the
   auto-gen output doesn't ship the breaking placeholder.

5. docs/adr/030 + docs/reference/managed-docs.md — `{{path.to.value}}`
   template placeholders in prose escaped as `&#123;&#123;…&#125;&#125;`
   so Vue's interpolation parser leaves them alone. (Code blocks like
   JSON examples are safe — VitePress treats fenced code as raw.)

6. docs/reference/security.md + docs/guide/docker-backend.md — two
   dead-link callouts fixed: `[ADR Index](../adr/)` → `(../adr/README)`
   (vitepress directory links don't fall back to README), and the
   architecture-overview cross-link replaced with a GitHub URL (the
   architecture/ dir stays in srcExclude due to its TS generics).

Verification: `cd docs && npx vitepress build` completes clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AlbSar added a commit that referenced this pull request May 20, 2026
Six pre-existing test failures surfaced by PR #16 CI dogfood (all
present on main, none introduced by Sprint 175). Each test asserted
an obsolete contract — the production code was updated in an earlier
sprint but the matching test was not.

CLI suite (3 fails):
- tests/cli/commands/serve-overhaul.test.ts:128 — `createHttpServer`
  signature became `(root, opts)` in Sprint 175 W2.3 (commit 1a69b68)
  to wire host + terminalBackend. Updated to match opts-object shape.
- tests/cli/serve.test.ts (2 cases — default & custom port) — same
  signature change; also the listen-on banner now uses 127.0.0.1
  (explicit-bind security default) instead of "localhost".

Security suite (2 fails):
- tests/security/shell-injection.test.ts (2 cases) — Sprint 170 P0-3
  (Tmux Prompt Filename TaskId-Aware) renamed `.prompt-{rand}.txt`
  to `.prompt-{taskId}-{rand}.txt`. Updated both assertions to
  include the taskId prefix.

Monitor suite (1 fail):
- tests/monitor/alert-emitter.test.ts:112 — "provider parity" asserted
  `^paths:` frontmatter on `.codex/.gemini/.cursor` rules, but
  src/core/rule-generator.ts:77-113 intentionally emits the
  `paths:` hint only for Claude (the other three loaders don't honour
  it). Test relaxed to assert the AUTO-START marker — the actual
  parity invariant.

Verified locally: tests/cli/, tests/security/, tests/monitor/ now
green; full vitest suites pass (Core 4728, MCP+API 2500, Dashboard 493).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AlbSar added a commit that referenced this pull request May 20, 2026
Two CI infrastructure fixes for PR #16:

1. .github/workflows/dashboard-build.yml — Node 18 dropped from the
   build matrix. Tailwind 4 ships `@tailwindcss/oxide` as a per-platform
   optionalDependency; npm CLI bug #4828 fails to resolve it on Node 18
   in CI runners (the cache layout differs). Tailwind 4 officially
   supports Node 20+. engines.node remains >=18 for the library — only
   the dashboard build needs 20+. The Node 18 row also masked test-
   build smoke failures on otherwise-green matrices.

2. .secrets-baseline — regenerated with `--build-baseline` after the
   Sprint 171 doc reorg moved audit fixtures from `.audit/sprint-167/`
   to `docs/audits/sprint-167/`. All 8 unallowlisted hits are
   confirmed-safe:
   - 6 audit-report fixtures (Sprint 167 T6, Sprint 171 security)
     that document detection patterns by quoting the literal regex.
   - 2 false positives in docs/adr/README.md — the OPENAI_KEY pattern
     matches the substring `sk-detection-...` inside the ADR-039
     filename slug ("self-modifying-task-detection-..."), not a key.

Verified: `node scripts/security/secret-baseline.mjs` exits 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AlbSar added a commit that referenced this pull request May 20, 2026
PR #16 main merge surfaced 3 test failures that all pass locally on the
same Node 24.15.0 runtime — pure CI environment divergence. Captured in
sub-project #2 backlog as item #7 (lokal-CI gap) with concrete fix
candidates (mock hygiene, process.kill semantics, vitest --retry tag).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AlbSar added a commit that referenced this pull request May 20, 2026
…rity

Sprint 176 spec — 12 task, 5 wave, June 1 2026 beta-blocker:

A. Planner state-hygiene (W1-W3, 7 task):
   1. Auto-debt empty-scope (sprint-planner.ts:197-216)
   2. Re-plan orphan task file cleanup
   3. DEP0190 shell:true (3 call-site)
   4. Coverage hard-floor / aspirational split
   5. Dashboard TS errors + root lint wire
   6. doctor DECISIONS.md obsolete + 5-file cascade
   7. CI-only test flakes (lokal-CI divergence)

B. Self-security guards (W4-W5, 5 task — interceptor pattern, 5 new files):
   8. Prompt guard (input pattern matcher)
   9. Command guard (shell-kind deny-list, remote-only)
   10. Outbound rate-limit (daily tenant-scoped quota)
   11. mTLS hook (AuthProvider interface extension)
   12. Self-audit-of-audit (HMAC append-only chain + verify CLI)

Five non-negotiable security invariants (I1-I5):
- I1 no silent drop, I2 no raw payload in audit, I3 default-deny
  on host≠127.0.0.1, I4 append-only audit + HMAC tamper detect,
  I5 tenant-scope isolation (audit + quota + guard state).

Locked decisions: single sprint #176, structured planning, sequential
self-modifying dispatch (Sprint 175 pattern), Brain manuel wave gates
(ADR-047), non-destructive memory.db ALTER, GO_WITH_TECH_DEBT acceptable
verdict (≤2 GWT, W1-W3≥5 DONE, W4≥2 DONE).

Predecessor: PR #16 (sub-project #1, terminal, merged 2026-05-20).
Successor: sub-project #3 (multi-tenant + k8s + mTLS impl, post-beta).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AlbSar added a commit that referenced this pull request May 20, 2026
…rift

Sprint 176 worker on task 176-001 produced a 3 KB NO_GO refuse note
documenting auto-debt-injection empty-scope failure — a live reproduction
of spec §1d.1, the exact bug W1-1 fixes. On SIGTERM the brain-honest-
gate mechanism stub-overwrote the result file to 364 bytes erasing the
forensic notes.

This document restores the original notes from the conversation
transcript + records the two concurrent dogfood bugs that triggered the
kill: (1) config.json template-regen losing spawn_backend (cascade from
PR #16 git rm --cached); (2) nervous_system.directives_protection
auto_restore rolling DIRECTIVES.md back to Sprint 175 after cleanup.

Cross-referenced in sub-project #2 plan Task 1 — to be cited in the
regression fixture for sprint-planner debt injection. Open question
captured for sub-project #2 backlog #8 (nervous_system baseline-update
hook on deckent_set_directives success).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant