From 6e04c13d3de08c76289f226fcb7a13ccfdbfb56d Mon Sep 17 00:00:00 2001 From: AnhMTV Date: Tue, 2 Jun 2026 17:04:16 +0700 Subject: [PATCH 01/67] docs: add koni-docs integration design spec (sub-task 1) Brainstorming output for ai-development branch. Covers scope decomposition (1 of 3 sub-tasks), file inventory, content shape, execution workflow, risks, and out-of-scope items deferred to sub-task 2 (docs content) and sub-task 3 (issue migration). --- ...2026-06-02-koni-docs-integration-design.md | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-02-koni-docs-integration-design.md diff --git a/docs/superpowers/specs/2026-06-02-koni-docs-integration-design.md b/docs/superpowers/specs/2026-06-02-koni-docs-integration-design.md new file mode 100644 index 00000000000..47b8c0ebdc5 --- /dev/null +++ b/docs/superpowers/specs/2026-06-02-koni-docs-integration-design.md @@ -0,0 +1,192 @@ +--- +title: Koni-Docs Skill Integration (Sub-task 1) +date: 2026-06-02 +branch: ai-development +parent_branch: subwallet-dev +status: approved +author: AnhMTV +--- + +# Koni-Docs Skill Integration — Design Spec + +## 1. Context + +SubWallet-Extension currently has minimal documentation infrastructure: +- `docs/` folder contains only a landing page (`index.html`, logo, favicon). +- Root has `README.md`, `CONTRIBUTING.md`, `CHANGELOG.md`, `BOUNTIES.md`. +- No `CLAUDE.md` / `AGENTS.md` / `VERSION` file. +- No standardized AI agent guidance. + +The user wants to integrate `koni-docs` — the Koniverse documentation +management skill from `Koniverse/Koni-Skills` — to: + +1. Standardize AI agent instructions across Cursor / Claude / Codex / Gemini. +2. Establish a canonical `docs/` structure (BRIEF / PRD / ARCHITECTURE / + CONTEXT / LESSONS / SETUP / sprints / CHANGELOG) for follow-on work. +3. Provide the structural foundation for migrating GitHub issues into a + story/epic/sprint workflow. + +This spec covers **Sub-task 1 only**: install the skill + add the minimal +koni convention files (AGENTS, CLAUDE, .active-context.example, VERSION). +Documentation content (sub-task 2) and GitHub issue migration (sub-task 3) +are out of scope and will be handled in separate branches/PRs. + +## 2. Decomposition + +The user's original request bundles three independent sub-projects of very +different sizes. We decompose: + +| Sub-task | Scope | Branch | This spec? | +|---|---|---|---| +| 1. Install skill + koni convention | `ai-development` | This branch | YES | +| 2. Standardize docs (BRIEF/PRD/ARCH/…) | TBD child branch off `ai-development` | Separate spec | No | +| 3. Migrate GitHub issues to stories/epics | TBD child branch | Separate spec | No | + +## 3. Files to create + +### 3.1 Auto-generated by `npx skills add` +- `skills-lock.json` (root) +- `.agents/skills/koni-docs/` (skill content: SKILL.md, references/, scripts/, assets/) + +### 3.2 Manually authored +| Path | Purpose | +|---|---| +| `AGENTS.md` (root) | Canonical AI agent guide for SubWallet-Extension | +| `CLAUDE.md` (root) | Thin pointer to AGENTS.md + Koni-Docs Integration YAML block + Active Context | +| `.active-context.example.md` (root) | Committed template for per-developer `.active-context.md` | +| `VERSION` (root) | `1.3.79` (sync with root `package.json` version) | + +### 3.3 `.gitignore` updates +Append: +``` +.active-context.md +.agents/.cache/ +``` + +The `.agents/.cache/` entry is conditional — only add if installer creates it. + +## 4. File content shape + +### 4.1 `CLAUDE.md` (~30 lines, pointer-only) + +```markdown +# CLAUDE.md + +> This file is a thin pointer. **AGENTS.md is canonical.** +> On any conflict between AGENTS.md and CLAUDE.md, AGENTS.md wins. + +See [AGENTS.md](AGENTS.md) for the full project guide. + +## Koni-Docs Integration +koni-docs: + plugins: [] + docs_path: docs/ + active_sprint: + version_file: VERSION + +## Active Context +- Sprint: (not started — pending sub-task 2) +- Active Stories: +- Last Version: 1.3.79 +- Recent Decisions: +- Recent Lessons: + + +See `.active-context.md` (gitignored, per-developer) for live snapshot; +copy from `.active-context.example.md` on first checkout. +``` + +### 4.2 `AGENTS.md` (~150-200 lines) + +Sections, in order: + +1. **Project purpose** — non-custodial multi-chain wallet (Substrate + EVM + + Bitcoin + TON), browser extension + web app. +2. **Monorepo layout** — table of `packages/*` with columns: name (from + `package.json`), one-line purpose (inferred from `package.json` description + if present, else from README/folder structure), key external dependencies + (top 3 from `package.json` dependencies). Generated by reading each + `packages/*/package.json` (no fabrication; if purpose unclear, leave as + "TBD — confirm in sub-task 2"). +3. **Tech stack** — Node version (from `.nvmrc`), Yarn 3 berry, TypeScript, + React, Polkadot.js, Web3, ethers, etc. +4. **Build / dev commands** — top 5-10 commands (`yarn install`, + `yarn build`, `yarn webpack:build:extension`, `yarn start:extension`, + …); link to `CONTRIBUTING.md` and `package.json` scripts for the rest. +5. **Conventions** — branch naming (`koni/dev/issue-XXXX`), commit prefix + per RULE-14 (`feat: / fix: / chore: / docs: / style: / refactor: / test:`), + English-only per RULE-13, PR template. +6. **Documentation** — pointer to current `README.md` / `CONTRIBUTING.md` / + `CHANGELOG.md` / `BOUNTIES.md`. Note that canonical `docs/BRIEF.md`, + `docs/PRD.md`, `docs/ARCHITECTURE.md`, etc. are pending sub-task 2. +7. **Koniverse pipeline** — short paragraph: BMad → GStack → Superpowers + → Koni-docs. +8. **Active context pattern** — Pattern B (file-extracted): + `.active-context.md` gitignored; `.active-context.example.md` is the + committed template. +9. **Skill quick reference** — table mapping skill (currently just + `koni-docs`) to trigger contexts. + +### 4.3 `.active-context.example.md` + +Empty template with placeholders, matching the koni-docs reference template. + +### 4.4 `VERSION` + +``` +1.3.79 +``` + +Sync with root `package.json` version. Note: `packages/*` use `1.3.79-1`; +the trailing `-1` is the monorepo-internal pre-release suffix. VERSION +tracks the canonical user-facing version (root `package.json`). + +## 5. Execution workflow + +1. **Branch**: `git checkout subwallet-dev && git pull && git checkout -b ai-development` ✓ (done) +2. **Install skill**: `npx skills add Koniverse/Koni-Skills --skill koni-docs` +3. **Author files**: AGENTS.md, CLAUDE.md, .active-context.example.md, VERSION +4. **Update `.gitignore`**: add `.active-context.md` (and `.agents/.cache/` if installer produces it) +5. **Verify**: + - `npx skills experimental_install` re-runs without error (tests lockfile) + - `cat VERSION` → `1.3.79` + - Grep AGENTS.md for placeholder strings (``, ``, `???`) → none + - `.active-context.md` does not appear in `git status` + - CLAUDE.md integration block YAML indent valid (2 spaces) + - `yarn install` clean + `yarn build` on a small package (e.g. `extension-base`) succeeds +6. **Commit** (per RULE-14): `chore: integrate koni-docs skill + AGENTS/CLAUDE.md (sub-task 1)` +7. **Push**: `git push -u origin ai-development` +8. **PR** → target `subwallet-dev`, body includes: summary, out-of-scope items + (docs/ content = sub-task 2, issue migration = sub-task 3), test plan. + +## 6. Risks & mitigation + +| Risk | Mitigation | +|---|---| +| `npx skills add` requires GitHub auth (Koni-Skills may be private) | Check `gh auth status` first; install requires repo-scoped token | +| Installer pulls > 10 MB into `.agents/` | Inspect size before commit; if oversized, gitignore `.agents/` and rely on `skills-lock.json` (`npx skills experimental_install` restores on clone) | +| AGENTS.md conflicts with existing AI instructions | SubWallet has none currently → no conflict | +| VERSION file conflicts with existing release scripts | Grep `.github/workflows/` and `scripts/` for `VERSION` before commit; escalate if found | +| `packages/*` use `1.3.79-1` (with `-1` suffix), VERSION uses `1.3.79` (root) | Documented in AGENTS.md §6 + risk note in PR body; sub-task 2 will reconcile | + +## 7. Out of scope (for sub-task 2/3) + +- `docs/BRIEF.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/CONTEXT.md`, + `docs/LESSONS.md`, `docs/SETUP.md`, `docs/CHANGELOG.md` +- `docs/sprints/` structure (epics, stories, sprint-YYYY-WNN, STATUS.md) +- `DEPLOY.md`, `DESIGN.md` at root +- Migration of GitHub issues to stories/epics +- Reconciling `packages/*` version suffix (`-1`) with root VERSION +- Active sprint identifier (`active_sprint:` in CLAUDE.md → `` until + sub-task 2 starts a sprint) + +## 8. Success criteria + +- PR `ai-development` → `subwallet-dev` is mergeable with no conflicts. +- After merge, any AI agent (Claude/Cursor/Codex/Gemini) reading the repo + finds AGENTS.md as the canonical entry point. +- `npx koni-docs status` (from the installed skill) runs without crash + even though many docs are missing (rules are not yet enforced because + docs/ content is intentionally absent). +- A new contributor can clone the repo and run `npx skills + experimental_install` to restore the skill from `skills-lock.json`. From c8461b586f0661a475e6d9f9e9959a2dd1d00791 Mon Sep 17 00:00:00 2001 From: AnhMTV Date: Tue, 2 Jun 2026 17:10:32 +0700 Subject: [PATCH 02/67] docs: add implementation plan for koni-docs integration (sub-task 1) --- ...-06-02-koni-docs-integration-sub-task-1.md | 776 ++++++++++++++++++ 1 file changed, 776 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-02-koni-docs-integration-sub-task-1.md diff --git a/docs/superpowers/plans/2026-06-02-koni-docs-integration-sub-task-1.md b/docs/superpowers/plans/2026-06-02-koni-docs-integration-sub-task-1.md new file mode 100644 index 00000000000..94fa9e1aa06 --- /dev/null +++ b/docs/superpowers/plans/2026-06-02-koni-docs-integration-sub-task-1.md @@ -0,0 +1,776 @@ +# Koni-Docs Integration (Sub-task 1) — 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:** Install the `koni-docs` skill from `Koniverse/Koni-Skills` into the SubWallet-Extension monorepo on branch `ai-development`, and add the minimal koni-convention files (AGENTS.md, CLAUDE.md, .active-context.example.md, VERSION) so all AI agents have a canonical project guide and the foundation for sub-tasks 2 and 3. + +**Architecture:** This is config + docs work, not application code. No new runtime behavior, no tests in the conventional unit-test sense. Verification is procedural: file existence, content shape, lockfile re-install, build still passes. Each task produces one self-contained commit. + +**Tech Stack:** `npx skills` (Anthropic skills CLI), `koni-docs` skill spec from `Koniverse/Koni-Skills`, Yarn 3 monorepo, Node 24 (current shell; `.nvmrc` says 12 for build). + +**Spec:** `docs/superpowers/specs/2026-06-02-koni-docs-integration-design.md` + +**Branch:** `ai-development` (already created from `subwallet-dev`). + +--- + +## File Structure + +Files this plan creates or modifies: + +| Path | Op | Responsibility | +|---|---|---| +| `skills-lock.json` (root) | Create (auto) | Lockfile for `npx skills experimental_install` | +| `.agents/skills/koni-docs/` (root) | Create (auto) | Installed skill content | +| `VERSION` (root) | Create | Canonical semver — sync with root `package.json` | +| `.active-context.example.md` (root) | Create | Per-developer template (committed) | +| `.gitignore` | Modify | Ignore `.active-context.md` | +| `AGENTS.md` (root) | Create | Canonical AI agent guide | +| `CLAUDE.md` (root) | Create | Thin pointer + Koni-Docs Integration block | + +No application code touched. No `packages/*` modified. + +--- + +## Task 1: Run skill installer and commit installer output + +**Files:** +- Create (by installer): `skills-lock.json`, `.agents/skills/koni-docs/` + +- [ ] **Step 1: Verify clean working tree on branch ai-development** + +Run: +```bash +git status +git rev-parse --abbrev-ref HEAD +``` + +Expected: +``` +On branch ai-development +nothing to commit, working tree clean +ai-development +``` + +If not clean, stash or resolve before proceeding. + +- [ ] **Step 2: Check GitHub auth (Koni-Skills may be private)** + +Run: +```bash +gh auth status +``` + +Expected: shows logged-in user with `repo` scope. If not, run `gh auth login` and select `repo` scope. + +- [ ] **Step 3: Run the installer** + +Run from repo root: +```bash +npx skills add Koniverse/Koni-Skills --skill koni-docs +``` + +Expected output (illustrative — exact wording may differ): +``` +Resolving Koniverse/Koni-Skills... +Installing skill: koni-docs +Wrote .agents/skills/koni-docs/... +Updated skills-lock.json +``` + +If the command fails: +- "command not found: skills" → upgrade Node or install via `npm i -g @anthropic/skills` (consult `Koni-Skills` README §Installation) +- Auth error → re-run `gh auth login` +- Network error → retry once; if persistent, document in CONTEXT and escalate + +- [ ] **Step 4: Verify installer output** + +Run: +```bash +ls -la skills-lock.json +ls -la .agents/skills/koni-docs/ +du -sh .agents/ +``` + +Expected: +- `skills-lock.json` exists, non-empty +- `.agents/skills/koni-docs/SKILL.md` exists +- `.agents/skills/koni-docs/references/` exists +- Total size of `.agents/` < 10 MB (if >10 MB, see Risk Mitigation at end of plan) + +- [ ] **Step 5: Check installer didn't modify unexpected files** + +Run: +```bash +git status +``` + +Expected: only `skills-lock.json` and `.agents/` show as new/modified. If anything in `packages/`, `scripts/`, or workflow files changed, STOP and investigate before committing. + +- [ ] **Step 6: Stage and commit installer output** + +Run: +```bash +git add skills-lock.json .agents/ +git commit -m "chore: install koni-docs skill from Koniverse/Koni-Skills" +``` + +Expected: commit succeeds. If a pre-commit hook fails, fix the underlying issue (do NOT use `--no-verify`). + +--- + +## Task 2: Create VERSION file + +**Files:** +- Create: `VERSION` + +- [ ] **Step 1: Confirm root version** + +Run: +```bash +node -p "require('./package.json').version" +``` + +Expected: `1.3.79` + +(If it differs, use whatever the command prints — that is the canonical value.) + +- [ ] **Step 2: Write VERSION** + +Create file `VERSION` at repo root with exactly this content (with trailing newline): + +``` +1.3.79 +``` + +- [ ] **Step 3: Verify VERSION** + +Run: +```bash +cat VERSION +wc -c VERSION +``` + +Expected: +- `cat VERSION` outputs `1.3.79` +- `wc -c VERSION` shows 7 bytes (`1.3.79` = 6 chars + 1 newline) + +Do NOT commit yet — Task 3 groups VERSION with `.gitignore`. + +--- + +## Task 3: Update .gitignore for active-context and commit with VERSION + +**Files:** +- Modify: `.gitignore` +- Already-created (uncommitted): `VERSION` + +- [ ] **Step 1: Read current .gitignore tail** + +Run: +```bash +tail -20 .gitignore +``` + +Note whether the file ends with a blank line. Append rules below accordingly. + +- [ ] **Step 2: Append koni-docs entries to .gitignore** + +Append the following block at the end of `.gitignore`: + +``` + +# Koni-docs Active Context (per-developer, Pattern B) +.active-context.md +``` + +(Leading blank line for visual separation from the existing tail. Do NOT include `.agents/.cache/` yet — only add it if Task 1 produced such a directory.) + +- [ ] **Step 3: Check if .agents/.cache/ exists from Task 1** + +Run: +```bash +ls -la .agents/ | grep -E "cache|tmp" || echo "no cache dir" +``` + +If output shows a `.cache` or `.tmp` directory inside `.agents/`, append to `.gitignore`: + +``` +.agents/.cache/ +``` + +If output is `no cache dir`, skip this step. + +- [ ] **Step 4: Verify .gitignore is valid and active-context.md is ignored** + +Run: +```bash +touch .active-context.md +git check-ignore .active-context.md +``` + +Expected: prints `.active-context.md` (meaning it IS ignored). + +Then clean up: +```bash +rm .active-context.md +``` + +- [ ] **Step 5: Stage and commit VERSION + .gitignore together** + +Run: +```bash +git add VERSION .gitignore +git status +``` + +Expected: `git status` shows exactly these two files staged. + +Run: +```bash +git commit -m "chore: add VERSION file and gitignore active-context" +``` + +--- + +## Task 4: Create .active-context.example.md + +**Files:** +- Create: `.active-context.example.md` + +- [ ] **Step 1: Fetch koni-docs reference template** + +Run from repo root: +```bash +cat .agents/skills/koni-docs/references/templates/integration.md 2>/dev/null | head -100 +``` + +If the file does not exist at that path, look in `.agents/skills/koni-docs/references/` for any `active-context*` or `integration*` template file and use that. + +- [ ] **Step 2: Write the example template** + +Create `.active-context.example.md` at repo root with this content: + +```markdown +# Active Context (Example Template) + +> **Per-developer file.** Copy this to `.active-context.md` (gitignored) on +> first checkout and update as you work. This `.example.md` version is the +> committed template — keep it minimal and generic. + +## Sprint + +Sprint: + +## Active Stories + +- + +## Last Version Shipped + +Last Version: 1.3.79 + +## Recent Decisions + +- + +## Recent Lessons + +- + +## Personal Notes + +``` + +- [ ] **Step 3: Verify the file** + +Run: +```bash +cat .active-context.example.md | head -5 +git status .active-context.example.md +``` + +Expected: +- First 5 lines show the heading and intro. +- `git status` shows `.active-context.example.md` as untracked. + +- [ ] **Step 4: Commit** + +Run: +```bash +git add .active-context.example.md +git commit -m "docs: add active-context example template (koni Pattern B)" +``` + +--- + +## Task 5: Generate monorepo layout data for AGENTS.md + +**Files:** +- Read-only: `packages/*/package.json` +- No commit in this task — output is consumed by Task 6. + +- [ ] **Step 1: Extract name, description, top dependencies per package** + +Run from repo root: +```bash +for f in packages/*/package.json; do + python3 -c " +import json +d = json.load(open('$f')) +name = d.get('name', '?') +desc = d.get('description', '') or '(no description in package.json)' +deps = list((d.get('dependencies') or {}).keys())[:3] +print(f'| \`{name}\` | {desc} | {\", \".join(deps) if deps else \"—\"} |') +" +done +``` + +Expected: one markdown table row per package, like: +``` +| `@subwallet/extension-base` | Base library for SubWallet extension | @polkadot/api, @subwallet/keyring, dexie | +| `@subwallet/extension-koni` | Background script for SubWallet | ... | +... +``` + +- [ ] **Step 2: Save the output for Task 6** + +Copy the output. You will paste it into AGENTS.md §Monorepo layout in Task 6. + +If a package has empty description, leave the description column as `(no description in package.json — confirm in sub-task 2)`. Do not fabricate descriptions. + +--- + +## Task 6: Write AGENTS.md + +**Files:** +- Create: `AGENTS.md` + +- [ ] **Step 1: Compose the file** + +Create `AGENTS.md` at repo root with this content. Replace `` with the table rows from Task 5 Step 1. + +```markdown +# AGENTS.md — SubWallet-Extension + +> **This file is the single source of truth for all AI agent instructions in this project.** +> Cursor, Gemini, Codex CLI, Copilot CLI, and Claude Code all read it. +> [`CLAUDE.md`](CLAUDE.md) is a thin pointer back to this file plus the +> Koni-Docs Integration block and an Active Context pointer. +> On any conflict between AGENTS.md and CLAUDE.md, AGENTS.md wins. + +## 1. Project purpose + +SubWallet-Extension is a non-custodial multi-chain wallet delivered as a +browser extension and web app. It supports Substrate (Polkadot / Kusama +ecosystem), EVM (Ethereum, Base, Arbitrum, …), Bitcoin, and TON. The +codebase is a Yarn 3 monorepo of TypeScript packages: background services +(account / balance / chain / earning / NFT / staking / transaction), a +React UI (extension popup + full-page web app), and shared message bus +layer between them. + +## 2. Monorepo layout + +| Package | Purpose | Top dependencies | +|---|---|---| + + +For full details, see each package's `README.md` (where present) and +`package.json`. + +## 3. Tech stack + +- **Node:** `.nvmrc` → 12 (build target). Local shell may be newer (Node 18+ recommended for dev). +- **Package manager:** Yarn 3 (berry), workspaces. Never use npm install. +- **Language:** TypeScript (strict mode in most packages). +- **UI:** React 18, styled-components, react-router. +- **Blockchain libs:** `@polkadot/api`, `@polkadot/keyring`, `ethers`, `web3`, `@ton/core`, `bitcoinjs-lib`. +- **Storage:** IndexedDB via `dexie`. +- **Build:** Webpack 5 for extension; separate build for web-runner and webapp. +- **Lint / format:** ESLint (config: `.eslintrc.js`), Prettier (`.prettierrc.cjs`). +- **CI:** GitHub Actions (`.github/workflows/`). + +## 4. Build / dev commands + +| Goal | Command | +|---|---| +| Install dependencies | `yarn install` | +| Build all packages | `yarn build` | +| Build extension only | `yarn webpack:build:extension` | +| Watch extension during dev | `yarn webpack:watch:extension` | +| Start web app dev server | `yarn webpack:dev:webapp` | +| Build webapp | `yarn webpack:build:webapp` | +| Run lint | `yarn lint` | +| Run tests | `yarn test` | + +See `package.json` (root) `scripts` for the full list, and `CONTRIBUTING.md` +for the contributor workflow. + +## 5. Conventions + +- **Branch naming:** `koni/dev/issue-` for feature/bug branches + tied to GitHub issues; `koni/dev/` for branches without a + ticket. AI-driven branches use `ai-development` or `ai-`. +- **Commit prefix (RULE-14):** `feat:` / `fix:` / `chore:` / `docs:` / + `style:` / `refactor:` / `test:`. Subject in imperative mood, English. +- **Language (RULE-13):** All code, comments, UI strings, error messages, + commit messages, and docs are English. Localization happens via + `public/locales/` translation bundles, not inline. +- **PR template:** `.github/PULL_REQUEST_TEMPLATE/resolve-issue.md` — fill + every section, link the issue, attach screenshots for UI changes. +- **Versioning:** Root `package.json` carries the user-facing semver + (currently `1.3.79`). `packages/*` carry a per-monorepo internal version + with `-N` suffix (currently `1.3.79-1`). The canonical user-facing + version also lives in `VERSION` (repo root, per koni-docs §0). + +## 6. Documentation + +Current docs at the repo root: + +- `README.md` — quickstart + feature overview +- `CONTRIBUTING.md` — contributor workflow +- `CHANGELOG.md` — release history +- `BOUNTIES.md` — open bounties for contributors +- `LICENSE` — Apache-2.0 +- `VERSION` — canonical semver (= root `package.json` version) + +Canonical `docs/` content per koni-docs spec (BRIEF, PRD, ARCHITECTURE, +CONTEXT, LESSONS, SETUP, sprints/, CHANGELOG) is **pending sub-task 2**. +Until then, koni-docs `RULE-1` / `RULE-2` (VERSION + CHANGELOG in same +commit) enforcement is deferred for this repo. + +GitHub issue → story/epic migration is **pending sub-task 3**. + +## 7. Koniverse pipeline + +This repo follows the Koniverse product development pipeline: + +``` +BRAINSTORM → BRIEF → PRD → ARCH → EPIC/US → DESIGN → REVIEW → QA → IMPLEMENT → COMMIT/DOCS + BMAD BMAD BMAD BMAD BMAD GSTACK GSTACK GSTACK SUPERPOWERS KONI-DOCS +``` + +Koni-docs is the **final standardization stage**: it maps planning +artifacts produced by upstream tools into the canonical `docs/` structure +and enforces the 12 core rules. + +## 8. Active context pattern (Pattern B — file-extracted) + +Live per-developer state lives in `.active-context.md` (gitignored). The +committed template is `.active-context.example.md`. On first checkout: + +```bash +cp .active-context.example.md .active-context.md +``` + +Update `.active-context.md` as you work — sprint, active stories, recent +decisions, recent lessons. It is consumed by koni-docs status / sync +commands. + +## 9. Skill quick reference + +| Skill | Triggers on | +|---|---| +| `koni-docs` | Update docs, create story, record decision, log lesson, write changelog entry, document architecture, run pre-commit doc checklist | + +Additional skills (e.g. `koni-api`, plugin skills for Supabase / Next.js) +may be added to `skills-lock.json` over time. + +## 10. Out of scope for sub-task 1 + +The following are tracked in separate sub-tasks and **not** part of this +branch (`ai-development`): + +- Authoring `docs/BRIEF.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, + `docs/CONTEXT.md`, `docs/LESSONS.md`, `docs/SETUP.md`, + `docs/CHANGELOG.md`, `docs/sprints/` — **sub-task 2**. +- Migrating GitHub issues to stories/epics — **sub-task 3**. +- Reconciling `packages/*` version suffix (`-N`) with root VERSION — + **sub-task 2**. +``` + +- [ ] **Step 2: Verify AGENTS.md has no unresolved placeholders** + +Run: +```bash +grep -nE "|||\?\?\?" AGENTS.md +``` + +Expected: no output (all placeholders resolved). + +If `` still appears, re-run Task 5 and substitute properly. + +- [ ] **Step 3: Verify table renders correctly** + +Run: +```bash +grep -E "^\| " AGENTS.md | wc -l +``` + +Expected: ≥ (number of packages) + table headers. (Roughly 15+ for SubWallet's ~12 packages.) + +- [ ] **Step 4: Commit** + +Run: +```bash +git add AGENTS.md +git commit -m "docs: add AGENTS.md (canonical AI agent guide for SubWallet)" +``` + +--- + +## Task 7: Write CLAUDE.md + +**Files:** +- Create: `CLAUDE.md` + +- [ ] **Step 1: Compose the file** + +Create `CLAUDE.md` at repo root with this exact content: + +```markdown +# CLAUDE.md + +> This file is a thin pointer. **AGENTS.md is canonical.** +> On any conflict between AGENTS.md and CLAUDE.md, AGENTS.md wins. + +See [AGENTS.md](AGENTS.md) for the full project guide. + +## Koni-Docs Integration +koni-docs: + plugins: [] + docs_path: docs/ + active_sprint: + version_file: VERSION + +## Active Context +- Sprint: (not started — pending sub-task 2) +- Active Stories: +- Last Version: 1.3.79 +- Recent Decisions: +- Recent Lessons: + + +See `.active-context.md` (gitignored, per-developer) for live snapshot; +copy from `.active-context.example.md` on first checkout. +``` + +Note: `` is intentional literal placeholder content for the `active_sprint:` field — it indicates to readers and to the koni-docs CLI that no sprint is active yet. Do NOT replace it. + +- [ ] **Step 2: Verify YAML indent of integration block** + +Run: +```bash +sed -n '/^## Koni-Docs Integration/,/^## /p' CLAUDE.md | head -10 +``` + +Expected: lines under `koni-docs:` are indented with exactly 2 spaces (not tabs, not 4 spaces). + +- [ ] **Step 3: Commit** + +Run: +```bash +git add CLAUDE.md +git commit -m "docs: add CLAUDE.md pointer with koni-docs integration block" +``` + +--- + +## Task 8: Verification suite + +**Files:** Read-only checks across all created files. + +- [ ] **Step 1: Re-run skill installer from lockfile (round-trip test)** + +Move the installed skill aside temporarily and re-install from lockfile: + +```bash +mv .agents .agents.bak +npx skills experimental_install +``` + +Expected: +- `.agents/skills/koni-docs/` is recreated +- `skills-lock.json` content unchanged (verify with `git diff skills-lock.json` → no output) + +Then verify and restore: +```bash +diff -r .agents .agents.bak >/dev/null && echo "MATCH" || echo "MISMATCH" +rm -rf .agents.bak +``` + +Expected: `MATCH`. If `MISMATCH`, the lockfile is non-deterministic — document in CONTEXT and escalate. + +- [ ] **Step 2: Confirm .active-context.md remains ignored** + +Run: +```bash +touch .active-context.md +git status --short .active-context.md +``` + +Expected: empty output (file is ignored, not shown). Then: +```bash +rm .active-context.md +``` + +- [ ] **Step 3: Grep for placeholder leakage** + +Run: +```bash +grep -rEn "||XXX-FIXME|\\bfill in\\b" AGENTS.md CLAUDE.md .active-context.example.md VERSION 2>/dev/null +``` + +Expected: no output. (Note: `` in CLAUDE.md is intentional and is NOT in this grep pattern.) + +- [ ] **Step 4: Verify yarn install still works** + +Run: +```bash +yarn install --immutable +``` + +Expected: exits 0. If `--immutable` flag is rejected by this Yarn version, run `yarn install --frozen-lockfile`. + +- [ ] **Step 5: Build one small package end-to-end** + +Run: +```bash +yarn workspace @subwallet/extension-base run build 2>&1 | tail -20 +``` + +Expected: build completes successfully. If the workspace command differs, run from the package dir directly: +```bash +cd packages/extension-base && yarn build && cd ../.. +``` + +If the build fails with errors unrelated to our changes (pre-existing build break), document in PR body and proceed. + +- [ ] **Step 6: Verify CLAUDE.md YAML block parses** + +Run: +```bash +python3 -c " +import re, yaml +content = open('CLAUDE.md').read() +m = re.search(r'## Koni-Docs Integration\n(.+?)\n## ', content, re.DOTALL) +assert m, 'integration block not found' +block = m.group(1) +parsed = yaml.safe_load(block) +assert 'koni-docs' in parsed, f'koni-docs key missing: {parsed}' +print('OK:', parsed) +" +``` + +Expected: prints `OK: {'koni-docs': {'plugins': [], 'docs_path': 'docs/', 'active_sprint': '', 'version_file': 'VERSION'}}`. + +If `pyyaml` is not installed: `python3 -m pip install --user pyyaml` then re-run. + +- [ ] **Step 7: Print git log summary** + +Run: +```bash +git log --oneline subwallet-dev..HEAD +``` + +Expected output (in reverse chronological order — your task commits): +``` + docs: add CLAUDE.md pointer with koni-docs integration block + docs: add AGENTS.md (canonical AI agent guide for SubWallet) + docs: add active-context example template (koni Pattern B) + chore: add VERSION file and gitignore active-context + chore: install koni-docs skill from Koniverse/Koni-Skills + docs: add koni-docs integration design spec (sub-task 1) +``` + +(6 commits total, including the spec commit from brainstorming.) + +If any commit is missing or out of order, do NOT rewrite history — proceed to Task 9 and flag in PR body. + +--- + +## Task 9: Push branch and open PR + +**Files:** None — git/GitHub only. + +- [ ] **Step 1: Push branch** + +Run: +```bash +git push -u origin ai-development +``` + +Expected: branch pushed, upstream set. If push is rejected (already exists with different history), STOP and consult user before forcing. + +- [ ] **Step 2: Compose PR body** + +Use this template — fill in the actual file sizes and commit hashes: + +```markdown +## Summary +- Install `koni-docs` skill from `Koniverse/Koni-Skills` into the monorepo. +- Add `AGENTS.md` (canonical AI agent guide) and `CLAUDE.md` (thin pointer + integration block). +- Add `VERSION` file (`1.3.79`, synced with root `package.json`). +- Add `.active-context.example.md` (Pattern B template); ignore `.active-context.md` per-developer. + +This is **sub-task 1 of 3** on the `ai-development` umbrella. Out of scope (separate PRs): +- Sub-task 2: standardize `docs/` content (BRIEF / PRD / ARCH / CONTEXT / LESSONS / SETUP / sprints / CHANGELOG) +- Sub-task 3: migrate GitHub issues to stories/epics + +## Spec +- Design: `docs/superpowers/specs/2026-06-02-koni-docs-integration-design.md` +- Plan: `docs/superpowers/plans/2026-06-02-koni-docs-integration-sub-task-1.md` + +## Test plan +- [x] `npx skills experimental_install` reproduces `.agents/skills/koni-docs/` from `skills-lock.json` (round-trip MATCH) +- [x] `.active-context.md` is gitignored (touch + `git check-ignore` confirms) +- [x] No placeholder leakage in AGENTS.md / VERSION / .active-context.example.md +- [x] `yarn install --immutable` succeeds +- [x] `yarn workspace @subwallet/extension-base run build` succeeds +- [x] CLAUDE.md integration block parses as valid YAML + +## Known deferred items +- `koni-docs` RULE-1 / RULE-2 (VERSION + CHANGELOG same commit) enforcement is **deferred** until sub-task 2 creates `docs/CHANGELOG.md`. +- `packages/*` use version `1.3.79-1` (with `-1` suffix); VERSION uses `1.3.79` (root). Reconciliation deferred to sub-task 2. +- `active_sprint:` in CLAUDE.md is `` — no active sprint until sub-task 2 starts one. +``` + +- [ ] **Step 3: Create the PR** + +Run: +```bash +gh pr create \ + --base subwallet-dev \ + --title "chore: integrate koni-docs skill + AGENTS/CLAUDE.md (sub-task 1)" \ + --body "$(cat <<'EOF' +[PASTE THE BODY FROM STEP 2 HERE] +EOF +)" +``` + +Expected: PR URL printed. + +- [ ] **Step 4: Return PR URL to user** + +Print the PR URL so the user can review. + +--- + +## Risk mitigation (cross-cutting) + +| Risk | Trigger | Action | +|---|---|---| +| `npx skills add` requires GitHub auth | Task 1 Step 3 fails with 401/403 | Run `gh auth login`, ensure `repo` scope, retry | +| Installer pulls > 10 MB into `.agents/` | Task 1 Step 4 `du -sh` > 10M | Append `.agents/` to `.gitignore`, only commit `skills-lock.json`. Document in CONTEXT and update Task 1 Step 6 to skip `.agents/` from the commit. Re-test Task 8 Step 1 round-trip | +| AGENTS.md / CLAUDE.md conflicts existing AI instructions | Should not happen (SubWallet has neither) | If unexpectedly present, STOP and consult user | +| Pre-commit hook fails | Any `git commit` step | Fix the underlying issue (lint, format) — do NOT use `--no-verify` | +| `yarn build` breaks on `extension-base` | Task 8 Step 5 | Try a different smaller package (`extension-chains`, `extension-mocks`). If pre-existing failure unrelated to our changes, document in PR body | +| Push rejected (branch exists) | Task 9 Step 1 | Do NOT force-push. Consult user — may need to rename branch | + +--- + +## Self-review notes + +- **Spec coverage:** All 8 sections of the spec are addressed across Tasks 1-9 (skill install: T1; VERSION: T2; .active-context: T4; AGENTS.md sections 1-10: T5+T6; CLAUDE.md: T7; .gitignore: T3; verification: T8; PR: T9). +- **Placeholders:** Plan contains no `TBD/TODO/fill in/similar to Task N` — every step shows the exact content or command. The `` string in CLAUDE.md is itself literal content, flagged twice as intentional. +- **Type consistency:** Filenames consistent across tasks (AGENTS.md, CLAUDE.md, VERSION, .active-context.example.md, .gitignore). Commit message prefixes match RULE-14 throughout. From 29134db21ec6ead3e116621a29e2f307509115a3 Mon Sep 17 00:00:00 2001 From: AnhMTV Date: Tue, 2 Jun 2026 17:23:47 +0700 Subject: [PATCH 03/67] chore: install koni-docs skill from Koniverse/Koni-Skills --- .agents/skills/koni-docs/SKILL.md | 520 ++++++++++++++++ .../references/bmad-template-analysis.md | 266 ++++++++ .../koni-docs/references/frontmatter-spec.md | 288 +++++++++ .agents/skills/koni-docs/references/rules.md | 299 +++++++++ .../koni-docs/references/sprint-system.md | 160 +++++ .../skills/koni-docs/references/templates.md | 172 +++++ .../references/templates/architecture.md | 237 +++++++ .../koni-docs/references/templates/brief.md | 170 +++++ .../references/templates/changelog.md | 91 +++ .../koni-docs/references/templates/context.md | 119 ++++ .../references/templates/design-spec.md | 122 ++++ .../koni-docs/references/templates/epic.md | 516 +++++++++++++++ .../references/templates/integration.md | 374 +++++++++++ .../koni-docs/references/templates/lessons.md | 86 +++ .../koni-docs/references/templates/okr.md | 150 +++++ .../koni-docs/references/templates/prd.md | 491 +++++++++++++++ .../koni-docs/references/templates/setup.md | 106 ++++ .../koni-docs/references/templates/sprint.md | 236 +++++++ .../koni-docs/references/templates/story.md | 566 +++++++++++++++++ .../references/templates/test-cases.md | 588 ++++++++++++++++++ .../references/templates/test-report.md | 406 ++++++++++++ skills-lock.json | 11 + 22 files changed, 5974 insertions(+) create mode 100644 .agents/skills/koni-docs/SKILL.md create mode 100644 .agents/skills/koni-docs/references/bmad-template-analysis.md create mode 100644 .agents/skills/koni-docs/references/frontmatter-spec.md create mode 100644 .agents/skills/koni-docs/references/rules.md create mode 100644 .agents/skills/koni-docs/references/sprint-system.md create mode 100644 .agents/skills/koni-docs/references/templates.md create mode 100644 .agents/skills/koni-docs/references/templates/architecture.md create mode 100644 .agents/skills/koni-docs/references/templates/brief.md create mode 100644 .agents/skills/koni-docs/references/templates/changelog.md create mode 100644 .agents/skills/koni-docs/references/templates/context.md create mode 100644 .agents/skills/koni-docs/references/templates/design-spec.md create mode 100644 .agents/skills/koni-docs/references/templates/epic.md create mode 100644 .agents/skills/koni-docs/references/templates/integration.md create mode 100644 .agents/skills/koni-docs/references/templates/lessons.md create mode 100644 .agents/skills/koni-docs/references/templates/okr.md create mode 100644 .agents/skills/koni-docs/references/templates/prd.md create mode 100644 .agents/skills/koni-docs/references/templates/setup.md create mode 100644 .agents/skills/koni-docs/references/templates/sprint.md create mode 100644 .agents/skills/koni-docs/references/templates/story.md create mode 100644 .agents/skills/koni-docs/references/templates/test-cases.md create mode 100644 .agents/skills/koni-docs/references/templates/test-report.md create mode 100644 skills-lock.json diff --git a/.agents/skills/koni-docs/SKILL.md b/.agents/skills/koni-docs/SKILL.md new file mode 100644 index 00000000000..6132db67241 --- /dev/null +++ b/.agents/skills/koni-docs/SKILL.md @@ -0,0 +1,520 @@ +--- +name: koni-docs +description: > + Manages all documentation artifacts in the koni-docs framework: SETUP, + PRD, ARCHITECTURE, LESSONS, CHANGELOG, CONTEXT, DESIGN, and Sprints (epics / + stories / sprint files / STATUS). Use when the user asks to update docs, + create a story, record a decision, log a lesson, write a changelog entry, + document system architecture, run the pre-commit doc checklist, or when any + planning tool (BMad, GStack, Superpowers) produces artifacts that need + standardization into the docs/ structure. +--- +# koni-docs — Documentation Management + +> **One rule above all others**: every code-shipping commit updates docs in +> the SAME commit. Never defer documentation to a follow-up. + +--- + +## 0. Quick orientation — what lives where + +``` +docs/ +├── README.md ← doc hub + pre-commit checklist +├── SETUP.md ← dev environment (clone → npm run dev) +├── BRIEF.md ← product brief: executive summary, problem, solution, scope, vision +├── PRD.md ← product spec: Epics / User Stories / Tasks +├── ARCHITECTURE.md ← system architecture: tech stack, components, data, API, infra +├── CHANGELOG.md ← full release history (every version) +├── CONTEXT.md ← decision log (append-only, never rewrite) +├── LESSONS.md ← recurring traps + patterns +├── design/ ← per-story design specs (US-X.Y--design.md) +├── okr/ ← (optional) file-native quarterly OKR ledgers (YYYY-QN.md) +├── sprints/ +│ ├── README.md ← agile schema + workflow +│ ├── STATUS.md ← AUTO-GENERATED kanban (never hand-edit) +│ ├── epics/ ← EPIC-N.md +│ ├── stories/ ← US-X.Y-.md (canonical task source) +│ ├── sprint-YYYY-WNN.md ← active sprint +│ └── archive/ ← closed sprints +└── tests/ + ├── test-cases/ ← EPIC-N.md (epic-level scenarios: E2E + REG + SMK + matrix) + │ └── README.md + └── test-reports/ ← execution history (append-only) + ├── README.md + ├── runs/ ← YYYY-MM-DD-EPIC-N-runN.md (per-execution detail) + └── releases/ ← vX.Y.Z.md (per-release aggregate) + +DEPLOY.md ← production runbook (repo root) +VERSION ← current semver string (repo root) +DESIGN.md ← design system (repo root) +.env.example ← env var template (repo root) +``` + +--- + +## 1. Pipeline integration + +Koni-docs is the **final stage** and **output standardizer** in the Koniverse product development pipeline: + +``` +BRAINSTORM → BRIEF → PRD → ARCH → EPIC/US → DESIGN → REVIEW → QA → IMPLEMENT → COMMIT/DOCS + BMAD BMAD BMAD BMAD BMAD GSTACK GSTACK GSTACK SUPERPOWERS KONI-DOCS +``` + +**Key principle**: Tools process content. Koni-docs standardizes output. When BMad, GStack, or Superpowers produce planning artifacts in their own directories (e.g., `_bmad-output/`), koni-docs maps them to the canonical `docs/` structure and ensures they follow Koniverse templates. + +### Vietnamese counterpart convention (`*.vi.md`) + +Some Koniverse projects (e.g. senti_quant) ship Vietnamese translations +of canonical docs as `*.vi.md` siblings — e.g. `docs/PRD.vi.md` next to +`docs/PRD.md`. **English is canonical** (per RULE-13): all sync scripts, +grep checks, and verification commands operate on `*.md` (no `.vi` +infix). The `.vi.md` files are: + +- **Optional** — projects opt in per their team's language preference. +- **Never authoritative** — if `*.md` and `*.vi.md` disagree, `*.md` wins. +- **Skipped by sync scripts** — `npx koni-docs status` / + `npx koni-docs sync` filter to `.md`-only files that DON'T match + `*.vi.md`. Frontmatter parsing, AC counting, status propagation: all + English-only. +- **Per-story discretion** — translate the stories that need broad + cross-team review; leave engineering-detail stories English-only. + +| Pipeline Phase | Tool | What it produces | +| ----------------------- | ------------------- | -------------------------------------------------------------- | +| Brainstorm | BMad + GStack | Raw ideas, problem framing | +| Product Brief | BMad | Executive brief | +| PRD | BMad | Full PRD content | +| Architecture | BMad | Architecture decisions | +| EPIC/US Breakdown | BMad | Epics + User Stories | +| Design Review | GStack | Design review, interaction states | +| Plan Review | GStack | Architecture review, edge cases, test plan | +| QA | GStack | Systematic testing, bug reports | +| Implementation | Superpowers | Plan → code → tests | +| **docs Finalize** | **Koni-docs** | **Standardized docs, rules enforced, CLAUDE.md updated** | + +--- + +## 2. Core rules (summary) + +These 12 rules apply to ALL Koniverse projects. Full enforcement details in `references/rules.md`. + +| Rule | Summary | Group | +| ------- | ------------------------------------------------------------- | ---------- | +| RULE-1 | VERSION + CHANGELOG in same commit | Pre-commit | +| RULE-2 | CHANGELOG commit hash mandatory, never "pending" | Pre-commit | +| RULE-5 | STATUS.md auto-generated, never hand-edit | Post-gen | +| RULE-6 | Story id must match filename + PRD `Epics & User Stories` | During | +| RULE-7 | CONTEXT.md append-only, corrections via revision entry | During | +| RULE-10 | Mark tasks [x] as you complete them | During | +| RULE-11 | New env var → SETUP + DEPLOY + .env.example in same commit | Pre-commit | +| RULE-13 | English-only for code, comments, UI, errors, commits, docs | During | +| RULE-14 | Commit prefix: feat:/fix:/chore:/docs:/style:/refactor:/test: | Pre-commit | +| RULE-15 | `assignee:` is the GitHub login — never git user.name | During | +| RULE-16 | `version_shipped:` is bare semver — never `v`-prefixed | During | +| RULE-17 | Frontmatter ID fields = bare canonical IDs only, never prose | During | + +**Technology-specific rules** (Supabase, Next.js) live in plugin skills. When a project declares `koni-docs-plugins: [supabase, nextjs]` in its CLAUDE.md, load those plugin skills for the additional rules. + +--- + +## 3. Workflow — task lifecycle + +### 3a. Before writing any code + +1. **Read LESSONS.md** — skim all entry titles; full-read 2-4 entries matching your domain. +2. **Read DESIGN.md** if any UI is involved. +3. **Find or create the story** in `docs/sprints/stories/`: + - Flip `status:` → `in-progress` + - Set `sprint:` to the active sprint id + - If no story exists, create a stub using the full story template (`references/templates/story.md`) before starting. + - **Domain-skill consultation for sizing** (mandatory for non-engineering + projects — growth / marketing / sales-ops / content workspaces): before + assigning `points:`, invoke the domain-appropriate skill(s) to cross-check + the estimate. Skipping this leads to systematic 30-40% undersizing, + especially for work with external dependencies, multi-stakeholder review, + or repeat-batch output (validated by the koni-growth calibration analysis + 2026-05-23 — see [§3a-bis](#3a-bis-story-sizing--calibration-scale)). + Routing: + - **B2B sales work** (proposals, sales kits, RFP/RFI, POC plans, + enterprise onboarding, IB / partner programs, agency partner kits) → + invoke `/sales-engineer` + - **Marketing ops work** (email sequences, CRM tagging, landing CRO, + content production, analytics tracking, paid ads, attribution) → invoke + `/marketing-ops` + - **Cross-domain** (e.g., payment integration + onboarding emails + + tracking) → invoke both + - **Pure engineering / product code / docs-only tooling** — skip + (gut-feel + Fibonacci is fine for these; consultation overhead is not + justified) + - Apply the sizing calibration scale (see §3a-bis below). +4. **Update the sprint file** — ensure the story row exists in the active sprint scope table. + +### 3a-bis. Story sizing — calibration scale + +For 1 assignee / 1-week sprint, ~10-15 pt capacity baseline. Tune per-team +when actuals stabilize. + +| Pts | Effort | Scope signal | +|---|---|---| +| 1 | ~½ day | Single doc, 1 stakeholder, no external dep | +| 2 | 1 day | Single template/file, internal review only | +| 3 | 2 days | Multi-doc bundle OR 1 internal integration | +| 5 | 3-4 days | Production deliverable (HTML / video / email seq) OR 1 external system integration | +| 8 | 1 week | Multi-system integration OR multi-asset sales kit OR content batch ≥3 items | +| 13 | Multi-week | Cross-product, legal/compliance loop, unknown scope — **split if possible** | + +**Splitting rule** — if a story estimates > 8pt, split it. A 13pt single +story is a planning anti-pattern: it blocks a whole sprint, hides milestone +risk, and cannot be paused/handed-off mid-flight. Reference split pattern from +koni-growth (CONTEXT D15): the original "Ship payment + recurring billing" +(13pt) was split into US-1.1 "payment one-shot" (8pt) + US-1.6 "recurring + +dunning state machine" (5pt), sequenced — first story unblocks revenue, second +unblocks lifecycle automation. + +**External-dependency rule** — if a story waits on a third-party system, +partner, or legal review, populate the `external_deps:` frontmatter field +(see [story template](references/templates/story.md) §1.frontmatter). These +stories are the most commonly undersized because dev-time excludes calendar +wait time. Example values: `[payment_gateway, resend_api, legal_review, +sales_navigator_license, partner_signature]`. + +**Done-story recalibration rule** — sprint assignment of a done-story is +locked history (do not move done stories across sprints), but **points may be +recalibrated** to reflect actual effort after the fact. This is the only way +to build a real velocity baseline; leaving inflated-optimistic estimates in +place mis-calibrates every future story. Recalibration must be paired with a +CONTEXT.md decision entry naming the affected stories and reasoning. + +### 3b. During implementation + +- Mark tasks `[x]` in the story file **as you complete them**, not all at the end (RULE-10). +- If you make an architecture or scope decision, append a `CONTEXT.md` entry immediately (see `references/templates/context.md`). +- If you encounter a trap or discover a reusable pattern, append a `LESSONS.md` entry. + +### 3c. Pre-commit checklist + +Run through every item before committing: + +``` +[ ] VERSION bumped per semver rule +[ ] CHANGELOG.md — story's "Changelog entry" section copied in, commit SHA filled (RULE-1, RULE-2) +[ ] PRD.md story status updated if scope changed +[ ] BRIEF.md updated if product vision, scope, or success criteria changed +[ ] CONTEXT.md has new entry if a decision was made +[ ] SETUP.md + DEPLOY.md + .env.example updated if new env var (RULE-11) +[ ] LESSONS.md has new entry if a trap or pattern was discovered +[ ] Story file: status → done, version_shipped set, Tasks all [x] +[ ] npx koni-docs sync --docs-path docs/ (5-layer sync) +[ ] npx koni-docs status --docs-path docs/ (STATUS.md — RULE-5) +[ ] CLAUDE.md Active Context block updated (see §4) +``` + +--- + +## 4. CLAUDE.md/AGENTS.md auto-update + +Every Koniverse project must have an active context block. Agent updates that block at specific trigger points (T1–T7 below). + +There are **two valid patterns** for where the Active Context block lives. Pick one per project; do not mix. Full template + rationale lives in [`references/templates/integration.md`](references/templates/integration.md) §0. + +| Pattern | When to use | Active Context lives in | +|---|---|---| +| **A — Inline** | Solo developer, one active branch, low merge volume | `CLAUDE.md` between `koni-docs:auto-update` markers | +| **B — File-extracted (recommended for teams)** | 2+ developers, parallel branches, frequent sprint churn | `.active-context.md` (gitignored) — `CLAUDE.md` keeps a pointer; `.active-context.example.md` committed as template | + +**Why a separate file for teams**: the Active Context block changes on every story start, close, sprint roll, decision, and lesson — many times per week. Two devs editing it on parallel branches always merges as a conflict. Pattern B moves the volatile content into a gitignored snapshot; the durable record stays in `docs/sprints/`, `CHANGELOG.md`, `CONTEXT.md`, `LESSONS.md`. Conflicts go to zero. + +### CLAUDE.md integration block (config — common to both patterns) + +```markdown +## Koni-Docs Integration + +koni-docs: + plugins: [] # e.g. [supabase, nextjs] + docs_path: docs/ + active_sprint: sprint-YYYY-WNN + version_file: VERSION +``` + +### Active Context — Pattern A (inline in CLAUDE.md) + +```markdown +## Active Context +- Sprint: sprint-YYYY-WNN +- Active Stories: 🟡 US-X.Y +- Last Version: vX.Y.Z +- Recent Decisions: D<N> +- Recent Lessons: §N +<!-- /koni-docs:auto-update --> +``` + +### Active Context — Pattern B (file-extracted, recommended for teams) + +`CLAUDE.md` keeps only a pointer: + +```markdown +## Active Context + +> **Moved to `.active-context.md`** — see [`.active-context.example.md`](./.active-context.example.md) +> for the template and the gitignored-on-purpose rationale. +``` + +`.active-context.md` (gitignored) holds the live snapshot — both a `Local developer` block (GitHub login, git name/email, workspace, current branch) and the auto-update `Project sprint context` block. `.active-context.example.md` is committed as the team template; contributors copy it on first checkout. Full template in [`references/templates/integration.md`](references/templates/integration.md) §2. + +### Trigger points (same for both patterns) + +| # | Trigger | Action | +| -- | ---------------------- | ---------------------------------------------------- | +| T1 | Start a story | Add `🟡 US-X.Y <title>` to Active Stories | +| T2 | Close a story | Change `🟡 → ✅`, update Last Version | +| T3 | Start a sprint | Update Sprint ID | +| T4 | Add a LESSONS entry | Append LESSONS.md + add §N to Recent Lessons | +| T5 | Log a CONTEXT decision | Append CONTEXT.md + add D`<N>` to Recent Decisions | +| T6 | Add an env var | Update SETUP + DEPLOY + .env.example (RULE-11) | +| T7 | Pre-commit | Run full checklist, verify all doc layers consistent | + +**How to update**: Use the `Edit` tool targeting the block between `<!-- koni-docs:auto-update -->` and `<!-- /koni-docs:auto-update -->` markers. For Pattern A the markers live in `CLAUDE.md`; for Pattern B they live in `.active-context.md`. Either way, only the marker block changes — surrounding content stays untouched. + +--- + +## 5. Activation — how to use this skill + +Every document template lives in its own file under +[`references/templates/`](references/templates/). Load only the template +file matching the user's request. + +| User request | Action | Load | +| ----------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------ | +| "create a story for US-X.Y" | Verify US-X.Y exists in PRD `Epics & User Stories`, use full story template. For retroactive/codebase-discovered stories, set `assignee` from the commit AUTHOR (`git log -1 --format=%an <sha>`), never the session user | `templates/story.md` §1 | +| "start story US-X.Y" | §3a flow: read LESSONS → DESIGN.md → flip `status: in-progress` | `rules.md` §RULE-6 | +| "close / complete story US-X.Y" | §3c checklist + 5-layer check + run agile:status | `sprint-system.md` §5-layer | +| "log a decision" / "record architecture choice" | Find highest D`<N>`, append decision entry | `templates/context.md` | +| "revise / correct decision D`<N>`" | Append revision entry, never edit original (RULE-7) | `templates/context.md` §Revision | +| "add a lesson" / "log a lesson" | Find highest entry number, append LESSONS entry | `templates/lessons.md` | +| "write changelog for vX.Y.Z" | Append CHANGELOG entry, bump VERSION simultaneously | `templates/changelog.md` | +| "create / update architecture" | Create or update ARCHITECTURE.md with tech stack, components, data flow | `templates/architecture.md` | +| "create brief" / "update brief" / "product brief" | Create or update BRIEF.md from BMad brainstorm/brief output | `templates/brief.md` | +| "update PRD for [feature]" | Update both FR table row AND §11 story entry | `templates/prd.md` | +| "create design spec for US-X.Y" | Use design spec template | `templates/design-spec.md` | +| "create an epic" | Use full epic template | `templates/epic.md` | +| "create sprint file" | Use sprint template | `templates/sprint.md` | +| "create / update test-cases for EPIC-N" | Use test-cases template (10-section layout: Scope / Stories in scope / Goals / Env / Cadence / Quick reference / Detail / Coverage matrix / Open) | `templates/test-cases.md` | +| "record a test run for EPIC-N" | Use per-execution sub-template — write to `runs/YYYY-MM-DD-EPIC-N-runN.md` | `templates/test-report.md` §A | +| "create release test report for vX.Y.Z" | Use per-release sub-template — write to `releases/vX.Y.Z.md`, link from CHANGELOG | `templates/test-report.md` §B | +| "update setup for new env var" | RULE-11: update SETUP + DEPLOY + .env.example in same commit | `templates/setup.md` | +| "create OKR ledger" / "set up quarterly OKRs" | Use OKR template (file-native quarterly Markdown ledger) | `templates/okr.md` | +| "wire koni-docs into project" / "refresh Active Context" | Update CLAUDE.md + AGENTS.md (+ `.active-context.md` for Pattern B) integration blocks | `templates/integration.md` | +| "adopt active-context split" / "move active context out of CLAUDE.md" | Pattern B: create `.active-context.example.md` + `.active-context.md` + gitignore + CLAUDE.md pointer | `templates/integration.md` §2 | +| "make AGENTS.md canonical" / "slim CLAUDE.md" / "AGENTS-canonical convention" | Apply §3.1 convention: CLAUDE.md keeps only pointer + Koni-Docs Integration + Active Context; AGENTS.md absorbs project structure / docs links / conventions | `templates/integration.md` §3.1 | +| "what templates exist?" | Browse the index | `templates.md` (thin index) | +| "run doc checklist" / "pre-commit check" | Walk §3c checklist item by item | `rules.md` + `sprint-system.md` | +| "regenerate status" | `npx koni-docs status --docs-path docs/` → commit | `sprint-system.md` §Scripts | +| "sync stories to PRD" | `npx koni-docs sync --docs-path docs/` | `sprint-system.md` §5-layer | +| "inject tasks from AC" | `npx koni-docs inject-tasks --docs-path docs/ --story US-X.Y` | `sprint-system.md` §Scripts | +| "backfill changelog SHAs" | `npx koni-docs backfill-commits --docs-path docs/` | `sprint-system.md` §Scripts | +| "standardize output from [tool]" | Map tool output to canonical docs/ structure | §1 Pipeline | +| "fix prd_ref" / "what goes in prd_ref / arch_ref / depends_on" / "AD-N in story frontmatter" / "sync warns row not found" / "migrate frontmatter" | Apply the per-field contract; move AD-N to `arch_ref`, US-X.Y to `depends_on`, prose to body | `frontmatter-spec.md` + `rules.md` RULE-17 | + +--- + +## 6. Reference files + +Load these on demand based on user intent: + +| File | When to load | Contents | +| ------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------- | +| `references/rules.md` | User asks about rules, pre-commit check, or rule violation surfaces | 12 core rules with severity, compliance steps, grep checks | +| `references/frontmatter-spec.md` | Authoring / migrating story / epic / sprint frontmatter; debugging `sync` row-not-found warnings | Authoritative per-field contract for `prd_ref` / `arch_ref` / `depends_on` / etc. Per-namespace regex, anti-pattern catalog with real broken values, migration playbook for projects carrying prose-stuffed ref fields. Pair with RULE-17. | +| `references/templates.md` | User asks "what templates exist?" or needs to navigate templates | Thin index — names each template, when to use it, links to the canonical file. Also has quick frontmatter cheatsheet for Story/Epic/Sprint. | +| `references/templates/changelog.md` | Writing changelog entry / shipping a version | CHANGELOG entry template, rules (RULE-1/RULE-2), safe-insertion pattern (anchor on `[Unreleased]`), filled example | +| `references/templates/context.md` | Recording a decision or revision (append-only, RULE-7) | Phase header + decision entry + revision entry templates, anti-patterns table, filled example (D3 TAM pivot) | +| `references/templates/lessons.md` | Codifying a recurring trap / pattern | Entry template, maintenance rules, filled example (`next build` vs `tsc`) | +| `references/templates/brief.md` | Creating/updating product brief (precedes PRD Executive Summary) | 8-section template (Exec / Problem / Solution / Differentiator / Persona / Success / Scope / Vision), filled example (Koni ERP brief) | +| `references/templates/prd.md` | Creating/updating PRD (label-only H2 sections, FR row, story entry, Epics & User Stories index) | Heading convention + full template skeleton, update procedure, FR row format, story-in-PRD entry, condensed filled snippet, legacy-numbered-PRD migration steps | +| `references/templates/architecture.md` | Documenting tech stack / components / data / AD-N summary table | Full ARCHITECTURE template (overview / stack / components / data / API / security / deploy / integrations / ADs), filled example | +| `references/templates/design-spec.md` | A story has visual or interaction complexity warranting a dedicated spec | Header refs + screens/states + layout decisions + component inventory + open questions, filled example (US-3.7 pod project) | +| `references/templates/epic.md` | Creating/updating an epic | Full BMad-grade Epic template — per-section guidance, required-vs-optional matrix by epic size, Mermaid patterns for entity maps + happy-path sequence diagrams, filled mini-example | +| `references/templates/story.md` | Creating/stubbing/updating a story | Full BMad-grade Story template — per-section guidance, required-vs-optional matrix by story size (1-13 pts), AC numbering rules, verification-command table pattern, filled mini-example | +| `references/templates/sprint.md` | Opening or closing a sprint | Frontmatter + Sprint scope table + goal recap + phased plan + retrospective + cross-references, filled example (sprint-2026-W19) | +| `references/templates/setup.md` | Adding an env var (RULE-11 — all three files in same commit) | SETUP block format + .env.example format + DEPLOY env table + RULE-11 checklist, filled examples for all three | +| `references/templates/okr.md` | Project adopts file-native OKRs in `docs/okr/YYYY-QN.md` | File-naming rule, YAML schema, KR formula rules (SELECT-only, end-exclusive boundaries), weekly notes, permissions, filled example (2026-Q2.md) | +| `references/templates/integration.md` | Wiring koni-docs into a new project, refreshing Active Context | CLAUDE.md `Koni-Docs Integration` block + AGENTS.md reference block + 7 trigger points for Active Context updates, filled example | +| `references/templates/test-cases.md` | Creating / updating per-epic test scenarios (`docs/tests/test-cases/EPIC-N.md`) | 10-section skeleton — Scope / Stories in scope (emoji status) / Goals / Env / Cadence / Quick reference summary / Detail (Gherkin) / Coverage matrix (with "AC description" column) / Open. Per-section guidance + filled EPIC-02 mini-example | +| `references/templates/test-report.md` | Recording a test run or release-level report (`docs/tests/test-reports/{runs,releases}/...`) | Two sub-templates: A) per-execution detail (one file per run, append-only) — B) per-release master (aggregate linked from CHANGELOG). Result symbols, append-only discipline, cross-link contract | +| `references/sprint-system.md` | User asks about sprints, agile workflow, scripts, 5-layer consistency, or test artifacts | Naming conventions, scripts, consistency check, setup guide, **§Test artifacts** (10-section test-cases structure + reports lifecycle) | +| `references/migration-from-bmad.md` | User asks to migrate from BMad to koni-docs | Architecture comparison, artifact mapping, step-by-step procedure | +| `references/bmad-template-analysis.md` | User asks about BMad template standards, or mapping BMad artifacts to koni-docs | Full BMad pipeline → koni-docs mapping, template differences, update recommendations | + +**Plugin skills**: If the project's CLAUDE.md declares `koni-docs-plugins`, load those skills for technology-specific rules that extend the core rule set. + +--- + +## 7. CLI tool — `@koniverse/koni-docs` + +This skill ships with a companion CLI binary published as `@koniverse/koni-docs` (current: **v0.7.0**). Provides 7 subcommands for the doc-maintenance work this skill prescribes, plus a reusable lib for programmatic use. + +> **Source of truth**: VERSION in this repo (`/Volumes/MacData/Workspace/AI/Koni-Skills/VERSION`) matches the latest npm version. When numbers diverge, the repo is the canonical pre-release; npm is the canonical published version. + +### 7.1 Install + +Pick the mode that fits the consumer repo: + +| Mode | Command | When to use | +|---|---|---| +| **devDep (recommended)** | `npm install --save-dev @koniverse/koni-docs` | Most consumer repos. Pinned in `package.json`, reproducible CI. Invoke via `npx koni-docs <cmd>`. | +| **Global** | `npm install -g @koniverse/koni-docs` | Cross-project use, one-off audits, ad-hoc preview. Invoke via `koni-docs <cmd>` (no `npx`). | +| **Local-tarball (pre-publish)** | From this repo: `cd packages/koni-docs && npm run build && npm pack` then `npm install -g ./koniverse-koni-docs-0.7.0.tgz` | Testing an unpublished version end-to-end, dogfooding a release candidate. Matches the v0.6.x / v0.7.0 ship workflow. | +| **`npm link` (active development)** | From this repo: `cd packages/koni-docs && npm run build && npm link` | Iterating on the CLI itself with a global `koni-docs` bin that always tracks `dist/`. Re-run `npm run build` after each source edit. | + +> If a project's CLAUDE.md / AGENTS.md says the CLI is installed in a specific way (e.g. devDep with `npm run agile:status` aliases), match that — don't switch modes silently. + +### 7.2 Update + +| Install mode | Update command | +|---|---| +| devDep | `npm install --save-dev @koniverse/koni-docs@latest` (or pin a specific version) | +| Global | `npm install -g @koniverse/koni-docs@latest` | +| Local-tarball | Re-pack from this repo and re-install: `npm uninstall -g @koniverse/koni-docs && npm install -g ./koniverse-koni-docs-<v>.tgz` | +| npm link | `git pull && npm run build` from `packages/koni-docs/` — the linked bin picks up the new `dist/`. | + +After upgrading, verify: + +```bash +koni-docs --version # global mode +npx koni-docs --version # devDep mode +``` + +Should report the version you just installed. If `--version` shows an older number, the install didn't take — re-run install and re-check. + +### 7.3 Global flags (every subcommand accepts these) + +- `--docs-path <path>` — override the default `docs/` root (useful for monorepos) +- `--dry-run` — preview changes without writing files +- `--json` — machine-readable output (pipe to `jq`) +- `--verbose` — extra logging + +### 7.4 Subcommand inventory + +| Subcommand | Since | Purpose | Example | +|---|---|---|---| +| `status` | v0.4 | Regenerate `STATUS.md` kanban from story frontmatter (RULE-5) | `koni-docs status` | +| `sync` | v0.4 | Propagate story status through doc layers (Epic / PRD `Functional Requirements` / Sprint / STATUS); column-by-NAME addressing (W23 BLOCKER fix); PRD section lookup uses label (`## Functional Requirements`) with legacy `## 8.` fallback (v0.7.2) | `koni-docs sync --story US-X.Y` | +| `inject-tasks` | v0.4 | Regenerate `## Tasks` checklist from `## Acceptance criteria` items in a story | `koni-docs inject-tasks --story US-X.Y` | +| `backfill-fields` | v0.4 | Add missing standard frontmatter keys to story files via `STORY_DEFAULTS` | `koni-docs backfill-fields` | +| `backfill-commits` | v0.4 | Replace `pending` commit SHAs in CHANGELOG with real SHAs from `git log` | `koni-docs backfill-commits` | +| `preview` | v0.6.0 | Launch the Astro SSR docs viewer (dashboard / per-doc / `/project` tracker). `--watch` enables chokidar + SSE live-reload (v0.7.0). | `koni-docs preview docs --port 4321 --watch` | +| `validate` | v0.7.0 | L3 ID-graph integrity check + FR-ref reachability (each story's `prd_ref` resolves to a real FR row in PRD `Functional Requirements`). Exits non-zero on any error. | `koni-docs validate --json` | + +### 7.5 Real-world usage — the four common loops + +**(A) After editing a story file** (start, close, change AC): + +```bash +koni-docs sync --story US-X.Y # propagate status across 5 doc layers +koni-docs status # regen STATUS.md (RULE-5) +``` + +**(B) Pre-commit checklist** (full §3c sweep): + +```bash +koni-docs inject-tasks --story US-X.Y # only if AC changed +koni-docs sync --story US-X.Y +koni-docs status +koni-docs validate # fails CI on broken refs +git add docs/ && git commit -m "..." # CHANGELOG SHA still "pending" +koni-docs backfill-commits # backfill SHA → write change +git add docs/CHANGELOG.md && git commit -m "docs: backfill ..." +``` + +**(C) Doc audit on a new repo or after a long pause**: + +```bash +koni-docs validate --include-warnings --json | jq # find broken refs +koni-docs backfill-fields --dry-run # see what's missing +koni-docs backfill-fields # fill defaults +koni-docs status # regen kanban +``` + +**(D) Browse the docs visually** (dashboard + per-doc + project tracker + live-reload): + +```bash +koni-docs preview docs --watch # opens http://localhost:4321/ +# / dashboard (KPIs + epic grid) +# /docs/<slug> any markdown doc rendered with shiki + mermaid +# /project full story tracker (filter/group; needs v0.7.0+) +``` + +`--watch` watches `docs/**/*.md` (chokidar) and pushes SSE events to the browser; edit a story file and the open tab reloads automatically. + +### 7.6 When to use which subcommand (mapping from user intent) + +| User says... | Run | +|---|---| +| "regenerate STATUS" / "refresh kanban" | `koni-docs status` | +| "sync US-X.Y" / "propagate story X status" | `koni-docs sync --story US-X.Y` | +| "rebuild tasks for US-X.Y" | `koni-docs inject-tasks --story US-X.Y` | +| "story X is missing fields" / "fix story frontmatter" | `koni-docs backfill-fields` (add `--dry-run` first to preview) | +| "fill in commit SHAs" / "backfill changelog" | `koni-docs backfill-commits` | +| "show me the docs in a browser" / "open docs viewer" | `koni-docs preview docs --watch` | +| "check docs integrity" / "find broken refs" / "ID graph audit" | `koni-docs validate` | +| "run doc checklist before commit" | full loop (B) above | +| "audit this new repo's docs" | full loop (C) above | + +### 7.7 Library API for programmatic use + +Other Koniverse products can import the typed lib without the CLI. v0.7.0 surface: + +```ts +// Corpus + I/O +import { + loadCorpus, readDoc, writeDoc, parseDoc, serializeDoc, updateFrontmatter, + getStories, getEpics, getSprints, getActiveSprint, resolveById, +} from '@koniverse/koni-docs/lib'; + +// Markdown primitives +import { + findSection, findSectionStartingWith, // ← prefix matcher (v0.7.0) + replaceSection, appendToSection, removeSection, + findTable, parseTable, findRow, updateCell, appendRow, removeRow, + parseCheckboxes, setCheckboxState, appendCheckbox, replaceCheckboxes, +} from '@koniverse/koni-docs/lib'; + +// Schemas +import { Schemas } from '@koniverse/koni-docs/lib'; +// → Schemas.storySchema, Schemas.epicSchema, Schemas.sprintSchema, Schemas.changelogEntrySchema + +// Validators +import { + validateRefs, // L3 ID graph (story→epic, story→sprint, story→PRD Epics & User Stories) + validateFrRefs, // ← prd_ref reachability into PRD Functional Requirements (v0.7.0) +} from '@koniverse/koni-docs/lib'; + +// Changelog + git +import { + parseChangelog, findEntryByVersion, formatVersionHeader, updateCommitSha, + isGitRepo, findCommitForVersion, findCommitByTag, listVersionBumps, +} from '@koniverse/koni-docs/lib'; +``` + +Subpath exports: `@koniverse/koni-docs/lib`, `@koniverse/koni-docs/lib/markdown`, `@koniverse/koni-docs/lib/schemas`. + +The lib has zero CLI dependencies. Composes `gray-matter` (frontmatter) + `unified` / `remark-parse` / `remark-stringify` / `remark-gfm` (markdown AST) + `zod` (schemas). **Mutation contract**: every export is pure — functions starting with `update*` return a new value, never mutate inputs. Sole exception: `parseTable(...).node` returns a reference to the underlying mdast Table node (intentional, documented at call site). + +### 7.8 Troubleshooting + +| Symptom | Cause | Fix | +|---|---|---| +| `koni-docs --version` reports an older number than `package.json` | Build/install drift after editing source | `cd packages/koni-docs && npm run build && npm pack && npm install -g ./koniverse-koni-docs-<v>.tgz` | +| `sync` warns `PRD Functional Requirements FR <id>: section "## Functional Requirements" not found` | PRD has no `## Functional Requirements` heading and no legacy `## 8.` heading either | Rename the H2 to `## Functional Requirements` (canonical label form). Legacy numbered headings (`## 8. Functional Requirements`, with or without `(FR)` suffix) are still matched by the v0.7.2 fallback, but new PRDs should use the label form | +| `validate` exits non-zero with `(not_found)` warnings | Story references a sprint / epic file that doesn't exist | Either create the missing file or fix the story's `sprint:` / `epic:` frontmatter | +| `preview` shows 500 SyntaxError on `/` | Stale `dist/` shipped with v0.6.0 shebang leak | Upgrade to v0.6.1+ — `npm install -g @koniverse/koni-docs@latest` | +| `preview --watch` browser doesn't auto-reload | Browser cached page from before `--watch` was passed | Open DevTools, disable cache, reload once; afterwards SSE works | +| `writeDoc` adds/removes quotes in git diff | gray-matter normalization (fixed in v0.7.0 — preserves the original quote style per key) | Upgrade to v0.7.0+ | + +### 7.9 Skill ↔ CLI relationship + +This skill (the `SKILL.md` you are reading) and the `koni-docs` CLI evolve together. **When the SKILL.md says "run X"**, X is one of the subcommands above. **When the CLI gains a new subcommand**, this §7 inventory is the authoritative reference — `references/sprint-system.md` mirrors only the agile-related subset (`status`, `sync`, `inject-tasks`, `backfill-fields`, `backfill-commits`). + +Skill files at `skills/koni-docs/` in this repo are the canonical source. Consumer projects link to this skill (preferred: symlink each agent's `.<agent>/skills/koni-docs/` → `../../skills/koni-docs`); the `skills-lock.json` `sourceType: "github"` mechanism is for projects that can't or won't host the file locally. diff --git a/.agents/skills/koni-docs/references/bmad-template-analysis.md b/.agents/skills/koni-docs/references/bmad-template-analysis.md new file mode 100644 index 00000000000..ace3d9b5c48 --- /dev/null +++ b/.agents/skills/koni-docs/references/bmad-template-analysis.md @@ -0,0 +1,266 @@ +# BMad → Koni-Docs Template Standardization + +> **Purpose**: Maps every stage of the BMad product development pipeline to the koni-docs `docs/` structure. Use this to update koni-docs templates to match BMad output standards. +> +> **Based on**: `examples/bmad-raw-sample/` — a complete PlantCare Pro example executed through the full BMad pipeline. +> +> **Date**: 2026-05-07 + +--- + +## 1. BMad Pipeline → Koni-Docs Mapping + +``` +BMAD STAGE BMAD OUTPUT PATH KONI-DOCS TARGET PRIORITY +────────────────────────────────────────────────────────────────────────────────────────────────── +Brainstorm _bmad-output/brainstorming/ (NOT in docs/ — ephemeral) 🟡 Optional + brainstorming-session-{date}.md + +Product Brief _bmad-output/planning-artifacts/ (NOT in docs/ — reference 🟡 Optional + brief.md only; extracts into PRD §1) + +PRD _bmad-output/planning-artifacts/ docs/PRD.md 🔴 Required + prd.md + +Architecture _bmad-output/planning-artifacts/ docs/ARCHITECTURE.md 🔴 Required + architecture.md + +Epics & Stories _bmad-output/planning-artifacts/ docs/sprints/epics/ 🔴 Required + epics.md EPIC-N.md (one per epic) + + docs/PRD.md §7 index + +Individual Story _bmad-output/implementation-artifacts/ docs/sprints/stories/ 🔴 Required + stories/{N}-{M}-{slug}.md US-X.Y-{slug}.md + +Sprint Status _bmad-output/implementation-artifacts/ docs/sprints/STATUS.md 🔴 Required + sprint-status.yaml (auto-generated YAML → MD) + +CHANGELOG (embedded in story files) docs/CHANGELOG.md 🔴 Required + +Decision Log (not present in BMad raw — added by docs/CONTEXT.md 🔴 Required + koni-docs as decisions are made) + +Lessons Learned (not present in BMad raw — added by docs/LESSONS.md 🔴 Required + koni-docs as patterns emerge) +``` + +## 2. BMad Template Structure (Extracted from Raw Sample) + +### 2a. PRD Structure (BMad Standard) + +``` +PRD.md +├── Frontmatter (YAML) +│ ├── stepsCompleted: [] +│ ├── inputDocuments: [] +│ └── workflowType: 'prd' +├── §1 Product Overview (2-3 paragraphs) +├── §2 User Personas (table) +├── §3 User Journey — Happy Path (numbered steps) +├── §4 Functional Requirements (table: ID | Req | Priority | Status | Epic) +├── §5 Non-Functional Requirements (table: ID | Req | Priority | Category) +├── §6 Technical Constraints (bullet list) +└── §7 Epics & Stories Index (per-epic story summary table) +``` + +### 2b. Architecture Structure (BMad Standard) + +``` +ARCHITECTURE.md +├── Frontmatter (YAML) +│ ├── stepsCompleted: [] +│ └── inputDocuments: [] +├── §1 Architecture Overview (diagram + description) +├── §2 Tech Stack (table: Layer | Technology | Version | Rationale) +├── §3 Core Data Models (per-entity: table with Column | Type | Notes) +├── §4 API Endpoints (table: Endpoint | Method | Auth | Purpose) +├── §5 Key Flows (sequence diagrams for critical paths) +├── §6 Architecture Decisions (table: Decision | Topic | Summary) +└── §7 Deployment Architecture (CI/CD pipeline diagram) +``` + +### 2c. Epics & Stories Structure (BMad Standard) + +``` +EPICS.md +├── Frontmatter (YAML) +│ ├── stepsCompleted: [] +│ └── inputDocuments: [prd.md, architecture.md] +├── Overview (1 paragraph) +├── §1 Requirements Inventory +│ ├── Functional Requirements (FR1, FR2, ...) +│ ├── Non-Functional Requirements (NFR1, NFR2, ...) +│ ├── Additional Requirements (from Architecture) +│ └── UX Design Requirements (if UX doc exists) +├── §2 FR Coverage Map (FR1: Epic N - Description) +├── §3 Epic List (summary: name, goal, FRs covered) +└── §4 Epic Details (repeat per epic) + └── Epic N: {Title} + ├── Epic goal statement + └── Story N.M: {Title} + ├── As a / I want / So that + └── Acceptance Criteria (Given/When/Then) +``` + +### 2d. Individual Story File Structure (BMad Standard) + +``` +STORY {N}.{M}.md +├── Title: Story {N}.{M}: {title} +├── Status: ready-for-dev +├── Story (As a / I want / so that) +├── Acceptance Criteria (numbered list) +├── Tasks / Subtasks +│ ├── Task X (AC: #) +│ └── Subtask X.Y +├── Dev Notes +│ ├── Architecture patterns & constraints +│ ├── Project Structure Notes +│ └── References (links to PRD, Architecture, NFRs) +└── Dev Agent Record + ├── Agent Model Used + ├── Debug Log References + ├── Completion Notes List + └── File List +``` + +### 2e. Sprint Status Structure (BMad Standard) + +```yaml +SPRINT-STATUS.YAML +├── Metadata (generated, project, project_key, tracking_system, story_location) +├── Status Definitions +│ ├── Epic: backlog | in-progress | done +│ ├── Story: backlog | ready-for-dev | in-progress | review | done +│ └── Retrospective: optional | done +└── development_status: + ├── epic-N: {status} + ├── N-M-{story-slug}: {status} + └── epic-N-retrospective: optional +``` + +## 3. Key Differences: BMad vs Current Koni-Docs Templates + +### 3a. Story Frontmatter + +| Field | BMad | Current Koni-Docs | Action | +|-------|------|-------------------|--------| +| `id` | Implicit from filename (N.M) | Explicit `US-X.Y` | **Keep koni-docs format** (more machine-parseable) | +| `status` | `ready-for-dev` / `in-progress` / `review` / `done` | `backlog` / `ready` / `in-progress` / `review` / `done` / `blocked` | **Add `backlog` and `blocked`** to BMad status list | +| `priority` | Not in story file frontmatter | `P0`-`P3` | **Keep in koni-docs** (useful for sprint planning) | +| `points` | Not in story file frontmatter | Fibonacci 1/2/3/5/8/13 | **Keep in koni-docs** (useful for sprint capacity) | +| `sprint` | Tracked in sprint-status.yaml | In story frontmatter | **Keep koni-docs** (explicit link is better than implicit) | +| `version_shipped` | Not in BMad story | In koni-docs frontmatter | **Keep koni-docs** (needed for CHANGELOG) | +| `prd_ref` | Not in BMad (implicit via epic) | In koni-docs frontmatter | **Keep koni-docs** (explicit FR traceability) | +| `commit` | Not in BMad story | In koni-docs frontmatter | **Keep koni-docs** (needed by RULE-2) | +| `created` / `updated` | Not in BMad story | In koni-docs frontmatter | **Keep koni-docs** (useful for auditing) | + +### 3b. Story Body + +| Section | BMad | Current Koni-Docs | Action | +|---------|------|-------------------|--------| +| Goal | Not explicit (covered by "As a/I want/So that") | Separate §Goal | **Merge**: Make "So that" the goal statement | +| Background | Not in BMad story | §Background (2-3 paragraphs) | **Keep koni-docs** (valuable context for dev) | +| AC Format | Numbered list (from epics) | Checklist `- [ ] AC-N:` | **Adopt BMad Given/When/Then** format within AC | +| Tasks | Grouped with subtasks, AC cross-ref | Flat checklist with file paths | **Adopt BMad grouping** (Task → Subtask hierarchy) | +| Dev Notes | Architecture + structure + references | Implementation notes § | **Adopt BMad format** (structured, source-linked) | +| Changelog entry | Separate story section | Embed in story | **Keep koni-docs** (embedded changelog is RULE-1 requirement) | +| Files modified | In Dev Agent Record → File List | Separate §Files modified | **Keep koni-docs** (more explicit) | +| Cross-references | In Dev Notes → References | Separate §Cross-references | **Keep koni-docs** (more prominent) | + +### 3c. Epic File + +| Aspect | BMad | Current Koni-Docs | Action | +|--------|------|-------------------|--------| +| Location | All epics in one `epics.md` | One file per epic: `EPIC-N.md` | **Keep koni-docs** (one-per-file scales better) | +| Content | Stories inline with full AC | Summary table + link to story files | **Keep koni-docs** (stories in own files per koni rule) | +| FR Coverage | In epics.md overview | In frontmatter `prd_ref` + §FR coverage | **Add FR coverage section** to koni-docs epic template | + +### 3d. Sprint File + +| Aspect | BMad | Current Koni-Docs | Action | +|--------|------|-------------------|--------| +| Format | YAML (`sprint-status.yaml`) | Markdown (`sprint-YYYY-WNN.md`) | **Keep koni-docs** (YAML auto-gen, MD sprint file complementary) | +| Scope table | Not in YAML (implicit from status) | Explicit scope table in MD | **Keep koni-docs sprint MD** for human-readable scope | +| Retrospective | Per-epic retrospective flag | In sprint file on close | **Adopt BMad retro pattern** — per-epic retro optional flag | +| Sprint goal | Not in YAML | In sprint MD frontmatter + recap | **Keep koni-docs** | + +## 4. Template Update Recommendations + +### 4a. PRD Template — Add §7 Epics & Stories Index + +Current koni-docs PRD template has §4 (FR table with EPIC-N column) but lacks a §7 index. Add: + +```markdown +## §7. Epics & Stories Index + +### EPIC-1 — {Epic Title} +| Story | Title | Status | Version | +|-------|-------|--------|---------| +| US-1.1 | {title} | {status} | — | +``` + +### 4b. Story Template — Enhancements + +1. **Add BMad-style AC format option**: Support Given/When/Then alongside declarative ACs +2. **Add Tasks grouping**: Allow `TASK-X.Y.Z` with subtask indentation and AC cross-reference +3. **Add structured Dev Notes**: Include `Project Structure Notes` and `References` sub-sections +4. **Keep koni-docs frontmatter**: The koni-docs frontmatter is richer — keep all fields +5. **Keep koni-docs Changelog entry section**: This is critical for RULE-1 (same-commit docs) + +### 4c. Epic Template — Add FR Coverage + +Add to current koni-docs epic template: + +```markdown +## FR Coverage +| FR | Story | Status | +|----|-------|--------| +| FR-N | US-X.Y | {status} | +``` + +### 4d. Architecture Template — Add Architecture Decisions Table + +Current koni-docs ARCHITECTURE template links to CONTEXT.md for decisions. Add a summary table: + +```markdown +## Architecture decisions +| Decision | Topic | Date | Version | CONTEXT Ref | +|----------|-------|------|---------|-------------| +| AD-1 | {topic} | YYYY-MM-DD | vX.Y.Z | D{N} | +``` + +## 5. BMad Naming Convention → Koni-Docs Mapping + +| BMad Convention | Koni-Docs Convention | Mapping Rule | +|----------------|---------------------|--------------| +| `Epic N` | `EPIC-N` | Same numbering, UPPERCASE in koni-docs | +| `Story N.M` | `US-X.Y` | N.M → X.Y (same numbering; X=epic, Y=story) | +| `{N}-{M}-{slug}` | `US-{X}.{Y}-{slug}` | Prefix with `US-` | +| `epic-N: {status}` | `EPIC-N.md` frontmatter `status:` | YAML → Markdown frontmatter | +| `development_status:` | `STATUS.md` (auto-generated) | YAML → kanban table | +| `sprint-status.yaml` | `sprint-YYYY-WNN.md` + `STATUS.md` | Complementary: YAML machine, MD human | + +## 6. Complete BMad → Koni-Docs Workflow + +``` +INPUT PROCESS OUTPUT +─────────────────────────────────────────────────────────────────────────────── +BMad brainstorm session → koni-docs extract → (discard or archive) +BMad brief.md → koni-docs extract §1,§2 → docs/PRD.md §1 +BMad prd.md → koni-docs standardize → docs/PRD.md (full) +BMad architecture.md → koni-docs standardize → docs/ARCHITECTURE.md +BMad epics.md (all epics+stories) → koni-docs split+standardize → docs/sprints/epics/EPIC-N.md + → docs/PRD.md §7 index + → docs/sprints/stories/US-X.Y-slug.md +BMad story files (per-story) → koni-docs add frontmatter → docs/sprints/stories/US-X.Y-slug.md +BMad sprint-status.yaml → koni-docs auto-generate → docs/sprints/STATUS.md + → koni-docs manual → docs/sprints/sprint-YYYY-WNN.md +``` + +## 7. NOTES + +- BMad's `planning_artifacts` path = `_bmad-output/planning-artifacts/` +- BMad's `implementation_artifacts` path = `_bmad-output/implementation-artifacts/` +- Koni-docs canonical path = `docs/` +- The `references/migration-from-bmad.md` file already covers the migration procedure — this analysis provides the **template-level structural mapping** to complement that file. diff --git a/.agents/skills/koni-docs/references/frontmatter-spec.md b/.agents/skills/koni-docs/references/frontmatter-spec.md new file mode 100644 index 00000000000..6ec80bed5cd --- /dev/null +++ b/.agents/skills/koni-docs/references/frontmatter-spec.md @@ -0,0 +1,288 @@ +# Frontmatter Reference Spec — single source of truth for ID fields + +> **Audience**: every koni-docs consumer project (Koni-Skills, Koni-Finance-Final, +> Koni-ERP-02, future Koniverse projects). When a project's docs disagree with +> this spec, the spec wins. +> +> **Scope**: defines exactly what each frontmatter field on `stories/*.md`, +> `epics/*.md`, and `sprints/*.md` may contain — value format, parser +> behaviour, where prose belongs instead. +> +> **Why**: the koni-docs CLI (`sync`, `validate`, `backfill-fields`, +> `inject-tasks`) cross-references documents by ID. Prose or partial IDs in +> ID-typed fields break those cross-references silently — the script either +> looks up a non-existent row, or fans out a sentence into garbage tokens. + +--- + +## 1. Iron Law + +> **An ID-typed field contains ONLY canonical bare IDs. Prose, parenthetical +> notes, scope qualifiers, dependency narratives, and version ranges belong +> in the document body — never in frontmatter.** + +If a value is supposed to identify a thing the script will look up, it MUST +match the canonical regex for that thing. Period. The "I'll add a small +clarifying note next to the ID" instinct is the bug — every such note +becomes a parser failure the moment the field gets split. + +--- + +## 2. The four canonical ID spaces + +The koni-docs framework distinguishes four ID namespaces. Each lives in +exactly one document and has exactly one canonical regex. + +| Namespace | Lives in | Canonical regex | Example | +|---|---|---|---| +| **Functional Requirement** | `docs/PRD.md` → `## Functional Requirements` table, `ID` column | `^FR-\d+$` | `FR-12`, `FR-127` | +| **Non-Functional Requirement** | `docs/PRD.md` → `## Non-Functional Requirements` table, `ID` column | `^NFR-\d+$` | `NFR-3` | +| **Architecture Decision** | `docs/ARCHITECTURE.md` → `## Architecture decisions` table, `ID` column | `^AD-\d+$` | `AD-06`, `AD-33` | +| **User Story** | `docs/sprints/stories/US-X.Y-*.md` filename + frontmatter `id` | `^US-\d+\.\d+$` | `US-1.16`, `US-4.29` | + +Plus three structural IDs used as container references: + +| Namespace | Lives in | Canonical regex | Example | +|---|---|---|---| +| **Epic** | `docs/sprints/epics/EPIC-N.md` | `^EPIC-\d+$` | `EPIC-4` | +| **Sprint** | `docs/sprints/sprint-YYYY-WNN.md` | `^sprint-\d{4}-W\d{2}$` | `sprint-2026-W22` | +| **Version** | `VERSION` file + `CHANGELOG.md` headers | `^\d+\.\d+\.\d+$` (bare, no `v`) | `0.7.3` | + +**Anything that is not one of these IDs does not belong in an ID-typed +frontmatter field.** Prose, partial IDs (`FR-X.1`, `FR-12..FR-15`), +qualifiers (`FR-10 (partial — accept path)`), and cross-doc narratives +(`extends US-1.3 (Better Auth org plugin) + US-1.17`) all go in the +document body — under Background, Cross-story dependencies, or +Implementation notes. + +--- + +## 3. Per-document frontmatter contract + +### 3.1 Story (`docs/sprints/stories/US-X.Y-*.md`) + +| Field | Type | Required? | Pattern / Allowed values | Notes | +|---|---|---|---|---| +| `id` | scalar string | **YES** | `^US-\d+\.\d+$` | MUST match filename prefix (RULE-6). | +| `title` | scalar string | **YES** | free text, quoted if it contains `:` | One sentence. No trailing period. | +| `epic` | scalar string | **YES** | `^EPIC-\d+$` | MUST resolve to an existing `EPIC-N.md`. | +| `status` | enum | **YES** | `backlog \| ready \| in-progress \| review \| done \| blocked \| deprecated` | | +| `priority` | enum | recommended | `P0 \| P1 \| P2 \| P3` | | +| `points` | scalar | recommended | `1 \| 2 \| 3 \| 5 \| 8 \| 13 \| ''` (Fibonacci) | `''` = unsized. | +| `sprint` | scalar string | conditional | `^sprint-\d{4}-W\d{2}$` or `''` | Set when committed to a sprint. | +| `version_shipped` | scalar string | conditional | `^\d+\.\d+\.\d+$` (bare semver, no `v`) | MANDATORY when `status: done` (RULE-16). | +| `prd_ref` | **list of strings** | optional | every entry MUST match `^FR-\d+$` or `^NFR-\d+$` | FR rows synced by `koni-docs sync`. **Do not put AD-N here** — use `arch_ref`. | +| `arch_ref` | **list of strings** | optional | every entry MUST match `^AD-\d+$` | Architecture Decisions this story materializes. | +| `depends_on` | **list of strings** | optional | every entry MUST match `^US-\d+\.\d+$` | Stories whose artifacts this story consumes. | +| `assignee` | scalar string | **YES** when shipping | GitHub login (`saltict`), not display name | RULE-15. | +| `commit` | scalar string | **YES** when shipping | git SHA (7-char or full); CSV of SHAs for multi-commit stories | Never `pending` (RULE-2). | +| `created` | scalar string | recommended | `^\d{4}-\d{2}-\d{2}$` | | +| `updated` | scalar string | recommended | `^\d{4}-\d{2}-\d{2}$` | | +| `external_deps` | list of slugs | optional | `^[a-z0-9_-]+$` per entry | Third-party / legal-review waits — populates STATUS risk flag. | + +### 3.2 Epic (`docs/sprints/epics/EPIC-N.md`) + +| Field | Type | Required? | Pattern / Allowed values | Notes | +|---|---|---|---|---| +| `id` | scalar string | **YES** | `^EPIC-\d+$` | MUST match filename. | +| `title` | scalar string | **YES** | free text | | +| `status` | enum | **YES** | `backlog \| in-progress \| done` | | +| `prd_ref` | **list of strings** | optional | every entry MUST match `^FR-\d+$` | FR rows this epic *owns* (not just touches). List every FR explicitly — **no `..` range syntax** (`FR-28 .. FR-45` is invalid). | +| `arch_ref` | **list of strings** | optional | every entry MUST match `^AD-\d+$` | ADs this epic anchors. | +| `created` | scalar string | recommended | `^\d{4}-\d{2}-\d{2}$` | | +| `updated` | scalar string | recommended | `^\d{4}-\d{2}-\d{2}$` | | + +### 3.3 Sprint (`docs/sprints/sprint-YYYY-WNN.md`) + +| Field | Type | Required? | Pattern / Allowed values | Notes | +|---|---|---|---|---| +| `id` | scalar string | **YES** | `^sprint-\d{4}-W\d{2}$` | MUST match filename. | +| `status` | enum | **YES** | `planned \| in-progress \| closed` | | +| `start` | scalar string | **YES** | `^\d{4}-\d{2}-\d{2}$` | Inclusive start date. | +| `end` | scalar string | **YES** | `^\d{4}-\d{2}-\d{2}$` | Inclusive end date. | +| `goal` | scalar string | **YES** | One sentence | The sprint's headline goal. | + +Sprint frontmatter intentionally has no ID-list fields — sprint scope +lives in the body's `## Sprint scope` table. + +--- + +## 4. YAML form — list vs CSV string + +The script accepts **both** forms today: + +```yaml +# Canonical — preferred +prd_ref: + - FR-04 + - FR-10 + - FR-127 + +# Inline flow form — equally valid, shorter for ≤3 items +prd_ref: [FR-04, FR-10, FR-127] + +# Comma-separated string — backwards-compatible +prd_ref: FR-04, FR-10, FR-127 +``` + +**Use the list form.** The CSV string form is the legacy escape hatch that +*invites* prose contamination — once authors see a string, they start +appending parenthetical notes. The list form makes it visibly wrong to +write a sentence inside `[...]`. + +--- + +## 5. Anti-patterns (real examples + the fix) + +These are pulled from the Koni-Finance-Final / Koni-Skills corpora and +are the exact kinds of values that break `koni-docs sync`. + +### 5.1 Prose-in-`prd_ref` + +```yaml +# ❌ BROKEN — parser splits on comma, produces fake IDs +prd_ref: ARCH §External Services (Resend, MVP) — proposes AD-33 (Email service abstraction); supports FR-04 / FR-10 / FR-127 deferred to EPIC-2 / EPIC-7 + +# ✅ FIXED +prd_ref: [FR-04, FR-10, FR-127] +arch_ref: [AD-33] +# Move the prose ("proposes AD-33 — Email service abstraction; deferred to EPIC-2 / EPIC-7") +# into Background or Architecture constraints in the body. +``` + +### 5.2 Parenthetical qualifier dangling off the last ID + +```yaml +# ❌ BROKEN — parser sees "FR-94 (shared with EPIC-5)" as a single ID +prd_ref: AD-04, AD-06, FR-09, FR-90, FR-91, FR-93, FR-94 (shared with EPIC-5) + +# ✅ FIXED +prd_ref: [FR-09, FR-90, FR-91, FR-93, FR-94] +arch_ref: [AD-04, AD-06] +# The "(shared with EPIC-5)" qualifier moves into Background: +# > FR-94 is co-owned with EPIC-5 — see EPIC-5 for the consumer side. +``` + +### 5.3 Story-as-PRD-ref + +```yaml +# ❌ BROKEN — US-1.3 is not a PRD requirement, it's another story +prd_ref: extends US-1.3 (Better Auth org plugin) + US-1.17 (real-email invitation wiring); no dedicated FR + +# ✅ FIXED +prd_ref: [] # or omit the field entirely +depends_on: [US-1.3, US-1.17] +# Add a Cross-story dependencies section in the body explaining the relationship. +``` + +### 5.4 Slash-separated IDs + +```yaml +# ❌ BROKEN — "FR-93 / FR-94" is one token, not two +prd_ref: AD-06 (pg-boss event bus), FR-93 / FR-94 (audit two-tier + redaction); follow-up to US-1.6 + +# ✅ FIXED +prd_ref: [FR-93, FR-94] +arch_ref: [AD-06] +depends_on: [US-1.6] +``` + +### 5.5 Range syntax in epic `prd_ref` + +```yaml +# ❌ BROKEN — "FR-28 .. FR-45" is one literal string, matches no FR row +prd_ref: FR-28 .. FR-45, FR-82 .. FR-89, FR-114, FR-117 .. FR-120 + +# ✅ FIXED — enumerate explicitly +prd_ref: + - FR-28 + - FR-29 + # ... every FR through FR-45 + - FR-82 + # ... through FR-89 + - FR-114 + - FR-117 + - FR-118 + - FR-119 + - FR-120 +``` + +Yes, that's verbose. It's also accurate, greppable, and the only form the +script can validate. If the epic owns 30+ FRs and the list is unwieldy, +that's a signal the epic itself is too broad — split it. + +--- + +## 6. Migration playbook (for a project carrying broken data) + +1. **Audit**. Grep every story / epic for non-conformant `prd_ref` values: + + ```bash + # In the project root: + rg --no-heading -n '^prd_ref:' docs/sprints | \ + grep -vE '^[^:]+:\s*(\[[A-Z, -]+\]|[A-Z]+-[0-9]+(,\s*[A-Z]+-[0-9]+)*)$' | \ + head -50 + ``` + + Anything that prints has prose / parens / slashes / ranges / wrong namespace. + +2. **For each offender**, decide per-token: + - Token matches `^FR-\d+$` → keep in `prd_ref`. + - Token matches `^NFR-\d+$` → keep in `prd_ref` (NFR also lives in PRD). + - Token matches `^AD-\d+$` → move to `arch_ref`. + - Token matches `^US-\d+\.\d+$` → move to `depends_on`. + - Token is prose → move to body (Background / Cross-story dependencies / Architecture constraints) and delete from frontmatter. + - Token is a range (`FR-X .. FR-Y`) → enumerate every member. + +3. **Convert all `prd_ref` to YAML list form** at the same time. The list + syntax makes future prose contamination physically uglier to write. + +4. **Run** `npx koni-docs sync --docs-path docs/ --dry-run` and + `npx koni-docs validate --docs-path docs/`. Iterate until both are + warning-free (modulo true data issues like a story citing a + non-existent FR-99 — those are real bugs, not format problems). + +5. **Commit per-epic** (one commit per epic's stories). Keeps the diff + reviewable and the blame readable. + +--- + +## 7. Parser behaviour (current koni-docs script) + +For implementers / debuggers — exactly what `koni-docs sync` and +`validateFrRefs` do today (as of `koni-docs@0.7.3`): + +- **Reading**: `frontmatter.prd_ref` is read as either a string or an + array. String form is split on `,` and each fragment is trimmed. +- **Filtering** (sync): only entries matching `/^FR-/` are looked up in + the PRD Functional Requirements table. Other entries (currently + including `AD-N`) are silently skipped at the table-lookup step but + surface as "row with ID … not found" warnings when fed to `updateCell`. +- **Filtering** (validate): same `/^FR-/` filter; the set of valid FR + IDs is extracted from the PRD FR table's `ID` column. +- **No automatic awareness** of `arch_ref` / `depends_on` yet — these + fields are read by humans / AI agents at planning time and validated + by future tooling. Adopting them today is forward-compatible and gets + prose out of `prd_ref` immediately. + +A future `koni-docs` may tighten the parser to reject malformed entries +at sync time and to sync `arch_ref` into ARCHITECTURE.md's AD table. +Data that follows this spec keeps working unchanged when that lands. + +--- + +## 8. Quick decision tree + +``` +Is the value a thing the script needs to LOOK UP in a table? +├── YES → it's an ID. It must match the canonical regex in §2. +│ No parens, no prose, no ranges, no slashes inside the ID. +│ Use list form. Wrong namespace? Move to the right field. +└── NO → it's context. Put it in the body, not in frontmatter. + (Background, Cross-story dependencies, Architecture constraints, + Implementation notes — pick the section that fits.) +``` + +If you find yourself reaching for `(`, `;`, `/`, `..`, `and`, `extends`, +`supports`, `deferred`, `partial`, `shared with`, `follow-up to`, or any +free-text inside an ID-typed field — **stop**. That belongs in the body. diff --git a/.agents/skills/koni-docs/references/rules.md b/.agents/skills/koni-docs/references/rules.md new file mode 100644 index 00000000000..dd1dd05e8b8 --- /dev/null +++ b/.agents/skills/koni-docs/references/rules.md @@ -0,0 +1,299 @@ +# Core Rules — Detailed Reference + +> These 12 rules apply to ALL Koniverse projects regardless of technology stack. +> Technology-specific rules live in plugin skills (koni-docs-supabase, koni-docs-nextjs, etc.) + +## Rule Groups + +| Group | When enforced | +|-------|---------------| +| Pre-commit | Before `git commit` | +| During work | While writing code/docs | +| Post-generation | After running scripts | + +--- + +## Pre-commit Rules + +### RULE-1: VERSION + CHANGELOG in same commit + +**Severity**: BLOCKER — violations block merge + +**What**: Every code-shipping commit must update both the `VERSION` file AND `Docs/CHANGELOG.md` in the SAME commit. Never defer documentation to a follow-up commit. + +**Why**: Keeps version tracking and changelog atomically linked to the code change. A follow-up commit can be missed; a `git bisect` won't find the changelog entry. + +**How to comply**: +1. Bump `VERSION` file according to semver rules +2. Add new CHANGELOG entry at top (below `[Unreleased]`) +3. Commit both together with the code changes + +**Semver rules**: +| Change type | Bump | +|---|---| +| Breaking schema / public API change | MAJOR | +| New backward-compatible feature (default) | MINOR | +| Bug fix, no new feature | PATCH | + +**Grep check**: `git diff --cached --name-only | grep -E "VERSION|CHANGELOG" | wc -l` — must be 2 when code files are staged. + +**See**: `templates.md` §CHANGELOG entry, §CHANGELOG safe insertion + +--- + +### RULE-2: Commit hash in CHANGELOG mandatory + +**Severity**: BLOCKER + +**What**: Every CHANGELOG entry must end with `**Commit**: <7-char SHA>`. The SHA must be real — `pending` is NEVER acceptable. + +**Why**: Allows `git log --grep` to find which commit shipped which version. A placeholder SHA breaks bisectability and erodes trust. + +**How to comply**: +1. Write the CHANGELOG entry with all content +2. Commit everything +3. Note the 7-char SHA from `git log -1 --format=%h` +4. `git commit --amend` to fill in the SHA +5. Push + +**Grep check**: `grep -n "Commit.*pending" Docs/CHANGELOG.md` — must return empty. + +**See**: `templates.md` §CHANGELOG entry + +--- + +### RULE-11: New env var → update all three files + +**Severity**: BLOCKER + +**What**: Adding a new environment variable requires updating ALL three files in the same commit: `Docs/SETUP.md` + `DEPLOY.md` + `.env.example`. + +**Why**: A missing env var in SETUP.md blocks new developers. Missing in DEPLOY.md causes production outages. Missing in .env.example makes it undiscoverable. + +**How to comply** — all three in same commit: +1. `Docs/SETUP.md` — add to the `.env.local` example block + one-line description +2. `DEPLOY.md` — add to the production env vars table +3. `.env.example` — add the key with placeholder value + +**Format for .env.example**: +```bash +# <Category / Feature name> (added in vX.Y.Z) +# <One sentence: what this controls, where to get the value> +NEW_ENV_VAR=<placeholder_or_description> +``` + +**Format for SETUP.md env block**: +```markdown +# <Category name> (added in vX.Y.Z) +# <What it does — 1 line> +NEW_ENV_VAR=<example_value_or_instructions> +``` + +**See**: `templates.md` §SETUP.md + DEPLOY.md + .env.example + +--- + +### RULE-14: Commit message prefix + +**Severity**: WARNING + +**What**: Every commit message must use a conventional prefix: `feat:`, `fix:`, `chore:`, `docs:`, `style:`, `refactor:`, `test:`. + +**Why**: Enables automatic changelog generation and makes `git log --oneline` scannable by intent. + +**Note**: Doc-only commits (typo, formatting) do NOT bump VERSION. + +--- + +## During-Work Rules + +### RULE-6: Story ID must match across all docs + +**Severity**: BLOCKER + +**What**: A story's `id:` in frontmatter must exactly match: +1. The filename prefix (e.g., `US-3.7` in `US-3.7-pod-project-management.md`) +2. The PRD §7 entry identifier + +One canonical ID per story across all documentation layers. + +**Why**: Prevents ID drift between the story file, the sprint board, and the PRD. Mismatched IDs break the 5-layer consistency system. + +**How to comply**: +1. Before creating a story, check PRD §7 to confirm the ID +2. If the story doesn't exist in PRD §7, add it first +3. Use the exact same ID in filename, frontmatter `id:`, and PRD reference + +**Grep check**: `grep -rn "US-X.Y" Docs/sprints/stories/ Docs/PRD.md` — all references to a story ID must be consistent. + +**See**: `templates.md` §Story file, `sprint-system.md` §Naming conventions + +--- + +### RULE-7: CONTEXT.md is append-only + +**Severity**: BLOCKER + +**What**: Never edit or delete a past CONTEXT.md entry. Corrections get a new `D<N> (revision of D<M>)` entry appended at the end of the current phase. + +**Why**: The decision log is a historical record. Rewriting history destroys the "why did we choose X?" trail that future contributors depend on. + +**How to comply**: +- **Correction needed?** → Append `### D<N>. <Title> (revision of D<M>)` with `What changed`, `New decision`, `Rationale` +- **Wrong entry?** → Add correction entry; never delete the original +- **Missing rationale?** → "because Y" is mandatory in every entry + +**Anti-patterns**: +| Wrong | Correct | +|---|---| +| Edit body of past entry D<M> | Add new D<N> (revision of D<M>) | +| Delete a wrong decision | Add correction entry | +| Leave rationale blank | Always include "because Y" | +| One huge entry for 10 decisions | One entry per decision | + +**See**: `templates.md` §CONTEXT.md, §CONTEXT revision entry + +--- + +### RULE-10: Mark tasks as you complete them + +**Severity**: WARNING + +**What**: Mark story Tasks `[x]` individually as each task is completed, not all at once when the story finishes. Same rule applies to Acceptance Criteria checkboxes. + +**Why**: Incremental checkmarks give visibility into progress. Batch-marking at the end hides blockers and makes sprint status inaccurate. + +**How to comply**: After completing each Task or AC, immediately update its checkbox from `[ ]` to `[x]` using the Edit tool. + +**See**: `sprint-system.md` §Story status flow + +--- + +### RULE-13: English-only for all deliverables + +**Severity**: WARNING + +**What**: All code, comments, UI strings, error messages, commit messages, and documentation must be in English. Vietnamese is reserved for user chat prompts only. + +**Why**: English is the lingua franca of software. Non-English strings in code or docs create barriers for international contributors and tooling. + +--- + +### RULE-15: `assignee:` is the GitHub login — never git `user.name`, never a display name + +**Severity**: BLOCKER + +**What**: Every `assignee:` value in koni-docs artifacts MUST be the contributor's **GitHub login** (the `username` half of `github.com/<username>`). Not their git `user.name`. Not their display name. Not their email handle. Same convention everywhere a person is named: + +- Story frontmatter `assignee:` (`docs/sprints/stories/US-*.md`) +- Sprint scope-table assignee columns (`docs/sprints/sprint-*.md`) +- Epic frontmatter / sprint frontmatter owner / lead fields (where present) +- `.active-context.md` "Local developer.GitHub login" field +- CONTEXT.md decision authorship (if recorded) +- LESSONS.md attribution (if recorded) + +**Why**: GitHub login is the only identifier that survives across **@-mentions, PR reviewer assignments, `gh api users/<login>`, CODEOWNERS lookups, and audit attribution**. Git `user.name` is per-machine and per-developer — one maintainer's `user.name = AnhMTV` while their GitHub login is `saltict`, so a mismatched `assignee:` silently breaks every downstream lookup (PR ping never fires, CODEOWNERS skips them, status reports route to the wrong person). + +**How to comply**: +1. **Get your own login**: `gh api user --jq .login` returns it exactly. Copy that string verbatim into `assignee:`. +2. **Get a teammate's login**: prefer the value they already use in past stories or `.active-context.example.md`. Otherwise `gh api users/<guess>` returns HTTP 200 only if `<guess>` is the real login. +3. **Set once, reuse**: write your login to `.active-context.md` `Local developer.GitHub login` field on first checkout. Every subsequent `assignee:` you set in any story copies from there. +4. **Never substitute git config**: `git config user.name` is for commit attribution, not assignee routing. They can disagree, and when they do the GitHub-side wins for every tool that matters. + +**Grep checks**: +- Story files using your machine's git `user.name` instead of GitHub login: + ```bash + grep -lE "^assignee: $(git config user.name)$" docs/sprints/stories/*.md + ``` + Should return zero files **unless** your git `user.name` happens to equal your GitHub login. +- Cross-check that every non-empty `assignee:` resolves via `gh`: + ```bash + grep -hE "^assignee: \S+$" docs/sprints/stories/*.md | awk '{print $2}' \ + | sort -u | xargs -I{} sh -c 'gh api users/{} > /dev/null 2>&1 && echo "{}: ok" || echo "{}: NOT A REAL LOGIN"' + ``` + Every line should print `ok`. + +**See**: `templates/story.md` §1 Frontmatter, `templates/sprint.md` §Sprint scope table, `templates/integration.md` §2 (`.active-context.md` Local developer block). + +--- + +### RULE-16: `version_shipped:` is bare semver — never `v`-prefixed + +**Severity**: BLOCKER + +**What**: Every `version_shipped:` value in story frontmatter MUST be **bare semver** — `0.7.0`, NEVER `v0.7.0`. Same rule applies to: +- Story frontmatter `version_shipped:` (`docs/sprints/stories/US-*.md`) +- Repo-root `VERSION` file content (`0.7.0\n`, not `v0.7.0\n`) +- `docs/CHANGELOG.md` section anchors (`## [0.7.0]`, not `## [v0.7.0]`) +- Any `version:` / `released_version:` field in epic / sprint / PRD frontmatter + +The `v` prefix IS still used for narrative / convention surfaces: +- Git tags (`v0.7.0` — git tradition) +- CHANGELOG narrative titles after the dash chain (`## [0.7.0] — date — title — v0.7.0`) +- Active Context summary lines (`Last Version: v0.7.0`) +- Body prose in stories / decisions / lessons (`shipped in v0.7.0`) + +**Why**: Tooling that joins on version strings — `agile-sync-up.mjs` Stories-table writer, CHANGELOG-anchor lookup, semver `compare()`, sort order — needs a single canonical key. Mixing `v0.7.0` and `0.7.0` in structured fields silently breaks equality comparisons and produces double-`v` corruption like `vv0.7.0` in synced output (the script prepends `v` to the bare convention). Real-world trap: caught during Koni-Skills v0.2.0 dogfood when US-1.1's `version_shipped: v0.1.0` produced `vv0.1.0` in EPIC-1 Stories table ([LESSONS §4](LESSONS.md)). Same split that git itself uses: tag `v0.7.0`, but `package.json` `"version": "0.7.0"`. + +**How to comply**: +1. **In story frontmatter**: `version_shipped: 0.7.0` — no `v`. +2. **In VERSION file**: bare `0.7.0` (one line, no `v`). +3. **In CHANGELOG section anchors**: `## [0.7.0] — 2026-MM-DD — title — v0.7.0` — `[0.7.0]` is the bare anchor; the trailing `v0.7.0` is narrative. +4. Prose elsewhere uses `v`-prefix freely. + +**Grep checks**: +- Story frontmatter no-`v`: `grep -lE '^version_shipped: v' docs/sprints/stories/*.md` → must return zero files. +- VERSION file: `head -1 VERSION | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$'` → must match (no `v`). +- CHANGELOG anchors: `grep -E '^## \[v' docs/CHANGELOG.md` → must return zero lines. + +**See**: `templates/story.md` §1 Frontmatter, `templates/changelog.md` §template skeleton, [LESSONS §4](../../docs/LESSONS.md). + +--- + +### RULE-17: Frontmatter ID fields are bare canonical IDs only — never prose + +**Severity**: BLOCKER + +**What**: Every frontmatter field that holds an ID the tooling will look up — `prd_ref`, `arch_ref`, `depends_on`, `epic`, `sprint`, `version_shipped`, `id` — MUST contain only bare canonical IDs matching the regex for that namespace ([`frontmatter-spec.md`](frontmatter-spec.md) §2). Parenthetical notes, scope qualifiers ("(partial — accept path)"), dependency narratives ("extends US-1.3 …"), version ranges ("FR-28 .. FR-45"), slash-joined IDs ("FR-93 / FR-94"), and cross-namespace mixing (putting `AD-N` into `prd_ref`) are all forbidden. + +The canonical YAML form is a **list of strings**: `prd_ref: [FR-04, FR-10]`. The legacy comma-string form (`prd_ref: FR-04, FR-10`) is still accepted by the parser but **must not contain anything except IDs and commas**. + +**Why**: `koni-docs sync` reads ID-typed fields, splits CSV strings on `,`, then looks each fragment up in a canonical table (FR row in PRD, story row in epic, etc.). A single qualifier like `FR-94 (shared with EPIC-5)` becomes the literal lookup key `FR-94 (shared with EPIC-5)` — guaranteed table miss, surfaced as a noisy "row not found" warning every sync run. A prose-stuffed value like `ARCH §External Services (Resend, MVP) — proposes AD-33 …` shatters into half-a-dozen junk tokens. This recurring class of bug was diagnosed during the US-4.29 PRD-label cleanup on Koni-Finance-Final's 204-story corpus. + +**How to comply**: +1. Use **list form** for every ID-typed field: `prd_ref: [FR-04, FR-10]`. +2. **One namespace per field**: `prd_ref` is FR / NFR only; `arch_ref` is AD only; `depends_on` is US only. See [`frontmatter-spec.md`](frontmatter-spec.md) §3 for the per-document contract. +3. **Prose moves to the body** — Background / Cross-story dependencies / Architecture constraints / Implementation notes. Frontmatter is for tooling; bodies are for humans. +4. **Enumerate ranges** — `[FR-28, FR-29, …, FR-45]`, never `FR-28 .. FR-45`. If the list is unwieldy, the owning epic / story is too broad — split it. +5. When migrating an existing project, run the audit grep from [`frontmatter-spec.md`](frontmatter-spec.md) §6 to surface offenders, then fix per-epic. + +**Grep checks**: +- Story `prd_ref` is well-formed (list, flow-list, or pure CSV of IDs): + ```bash + rg --no-heading -n '^prd_ref:' docs/sprints/stories | \ + grep -vE '^[^:]+:\s*(\[[A-Z, 0-9-]+\]|\s*$|[A-Z]+-[0-9]+(,\s*[A-Z]+-[0-9]+)*)$' + ``` + → must return zero matches. +- Same for `arch_ref` and `depends_on` — substitute the field name. +- `koni-docs sync --dry-run` → zero `section "..." not found` / `row with ID="<garbage>" not found` warnings. +- `koni-docs validate` → exits 0. + +**See**: [`frontmatter-spec.md`](frontmatter-spec.md) (the authoritative spec — per-field contract, anti-pattern catalog, migration playbook), `templates/story.md` §1 Frontmatter, `templates/epic.md` §1 Frontmatter. + +--- + +## Post-Generation Rules + +### RULE-5: STATUS.md is auto-generated + +**Severity**: BLOCKER + +**What**: `Docs/sprints/STATUS.md` is auto-generated by `npm run agile:status`. Never hand-edit it. + +**Why**: Hand-edits to STATUS.md will be overwritten by the next script run. The file is a derived artifact from story frontmatter — the source of truth is the story files. + +**How to comply**: Always run `npm run agile:status` before committing any story status change. If STATUS.md looks wrong, fix the story frontmatter, not STATUS.md. + +**Grep check**: N/A — this is a process rule. The script regeneration is the enforcement mechanism. + +**See**: `sprint-system.md` §Scripts reference diff --git a/.agents/skills/koni-docs/references/sprint-system.md b/.agents/skills/koni-docs/references/sprint-system.md new file mode 100644 index 00000000000..6eea2e2f4e3 --- /dev/null +++ b/.agents/skills/koni-docs/references/sprint-system.md @@ -0,0 +1,160 @@ +# Sprint System — Conventions & Workflow + +## Naming conventions (canonical — single source of truth for sprint artifact IDs) + +| Artifact | Pattern | Example | +|---|---|---| +| Story file | `US-<EPIC>.<N>[.<SUB>]-<slug>.md` | `US-3.7-pod-project-management.md` | +| Story `id:` frontmatter | `US-<EPIC>.<N>[.<SUB>]` | `US-3.7` | +| Task ID (inside story) | `TASK-<US-id>.<n>` | `TASK-3.7.1` | +| Epic file | `EPIC-<N>.md` or `EPIC-DS.md` | `EPIC-3.md` | +| Sprint file | `sprint-YYYY-WNN.md` | `sprint-2026-W19.md` | + +## Story status flow + +``` +backlog → ready → in-progress → review → done + ↓ + blocked ← document reason in Implementation notes +``` + +**WIP limit**: at most **3 stories** `in-progress` simultaneously **by default**. + +The limit is team-configurable via the `Koni-Docs Integration` block in +`CLAUDE.md`: + +```yaml +koni-docs: + agile: + wip_limit: 3 # default 3; raise for atomic-ship sprints, lower for strict flow +``` + +When unset, the convention defaults to 3 (best for solo / small teams). +Larger teams may raise to 5–7. Atomic single-session ships (e.g. +agent-assisted sprints that close everything in one commit) sometimes +exceed the limit transiently — `npx koni-docs status` flags WIP violations +but does not block. + +**`done` requires**: `version_shipped` set + CHANGELOG entry exists + all AC `[x]`. + +### Hybrid EPIC numbering (BMad legacy + post-koni-docs) + +Some Koniverse projects (e.g. senti_quant) carry **zero-padded epic IDs +from the BMad era** (`EPIC-01`..`EPIC-13`) alongside **plain epic IDs +added after koni-docs adoption** (`EPIC-14`+). Both are valid; sync +scripts treat the number as an opaque identifier. + +To minimize confusion: + +- **New projects**: use plain `EPIC-N` (no zero-padding) for all epics. +- **Migrated projects**: keep existing padded IDs as-is for backward + compatibility; only new epics need plain numbering. `findEpicFile` + matches via `startsWith(${id}.)` so file naming must match frontmatter + `id:` exactly (`EPIC-08.md` ↔ `id: EPIC-08`, not `id: EPIC-8`). +- When `npx koni-docs backfill-fields` infers epic from story id, it emits + plain `EPIC-N`. On a padded-ID project, hand-correct after backfill. + Filed as followup for a future story (auto-detect pad-style from + existing epic files). + +## Scripts reference + +The `@koniverse/koni-docs` CLI provides all automation. Install once per project: + +```bash +npm install --save-dev @koniverse/koni-docs +``` + +| Command | What it does | When to run | +|---|---|---| +| `npx koni-docs status --docs-path docs/` | Regenerate `STATUS.md` from all story frontmatter | Before every commit that changes story status | +| `npx koni-docs sync --docs-path docs/` | Propagate story status upward — updates EPIC table, PRD `Functional Requirements` row, and sprint scope | After story status changes | +| `npx koni-docs inject-tasks --docs-path docs/ --story US-X.Y` | Regenerate Tasks section from Acceptance Criteria (AC is canonical) | When AC changes | +| `npx koni-docs backfill-fields --docs-path docs/` | Backfill `assignee`/`commit`/`sprint` on existing stories | When setting up sprint system in existing project | +| `npx koni-docs backfill-commits --docs-path docs/` | Backfill "pending" commit SHAs in CHANGELOG with real SHAs from git | When SHAs are missing | + +All subcommands accept `--dry-run` for safe preview mode. + +**Always run `npx koni-docs status` before committing any story status change.** +STATUS.md is auto-generated — never hand-edit it (RULE-5). + +## 5-layer consistency check (before merging) + +The five layers must be consistent after every story ships. Run `npx koni-docs sync` to propagate automatically. + +| Layer | File | What to verify | +|---|---|---| +| 1 — Story | `Docs/sprints/stories/US-X.Y-*.md` | `status: done`, `version_shipped` set, all AC + Tasks `[x]` | +| 2 — Epic | `Docs/sprints/epics/EPIC-N.md` | Story row checked off; epic `status` updated if all stories done | +| 3 — PRD | `Docs/PRD.md` | `Functional Requirements` row `✅ shipped (vX.Y.Z)`; `Epics & User Stories` entry `✅ Done (vX.Y.Z)` | +| 4 — Sprint | `Docs/sprints/sprint-YYYY-WNN.md` | Story row shows done + version | +| 5 — STATUS | `Docs/sprints/STATUS.md` | Regenerated by `npx koni-docs status` | + +Inconsistency between any two layers = documentation debt. Fix in same commit as the feature. + +## Pre-commit checklist + +Run through every item before committing: + +``` +[ ] VERSION bumped per semver rule +[ ] CHANGELOG.md has a new entry — same commit, real SHA, never "pending" (RULE-1, RULE-2) +[ ] PRD.md story status updated if scope changed +[ ] CONTEXT.md has new entry if a decision was made +[ ] SETUP.md + DEPLOY.md + .env.example updated if new env var (RULE-11) +[ ] LESSONS.md has new entry if a trap or pattern was discovered +[ ] Story file: status → done, version_shipped set, Tasks all [x] +[ ] npx koni-docs sync --docs-path docs/ (propagates AC to EPIC + PRD) +[ ] npx koni-docs status --docs-path docs/ (regenerates STATUS.md — RULE-5) +[ ] CLAUDE.md Active Context block updated (T1-T7 as applicable) +``` + +## Test artifacts + +Per-story Acceptance Criteria (`stories/US-X.Y-<slug>.md` §4) + Verification commands (§11) remain the source of truth for what each individual story must prove. Two additional artifact types capture what AC cannot: + +| Artifact | Location | Owns | +|---|---|---| +| Test cases | `docs/tests/test-cases/EPIC-N.md` (one per epic) | End-to-end scenarios spanning ≥2 stories; regression scenarios for cross-story invariants; smoke; coverage matrix (AC → TC) | +| Test report — per-execution | `docs/tests/test-reports/runs/YYYY-MM-DD-EPIC-N-runN.md` | Execution log: who ran which TCs in which env against which commit, pass/fail per TC, failure reproduction detail | +| Test report — per-release | `docs/tests/test-reports/releases/vX.Y.Z.md` | Release-level aggregate of run files; outstanding risks; named ship-decision sign-off | + +**Promotion rule** — keep a scenario inside the story file unless one of: + +- it spans ≥2 stories in the same epic (E2E), +- it guards an epic-level invariant or past bug (REG), +- it runs on a different cadence than per-PR (smoke / nightly perf / security). + +**Test-cases file structure (audience: tester / reviewer)** — every `EPIC-N.md` file follows this 10-section skeleton (see [`templates/test-cases.md`](templates/test-cases.md) §2 Section index): + +1. Frontmatter — YAML metadata, no `status` +2. Overview — Scope (paragraph + "Out of scope" bullets) +3. Overview — Stories in scope (table `| Story | Short name | Status |` with emoji) +4. Overview — Goals (3-5 bullets stating high-level invariants the suite proves) +5. Overview — Environment & test data +6. Overview — Cadence & ownership +7. Quick reference — scenarios summary (table `| # | ID | Type | Priority | Short description | Stories | Mode |` — single-row scan of every TC) +8. Test scenarios (H3 per TC with YAML + Gherkin + Preconditions + Test data + Notes) +9. Coverage matrix (table `| Story | AC | AC description | Covered by | Type |` — Story column inlines short name, AC description distills story AC text in ≤80 chars) +10. Open / deferred scenarios + +The Stories-in-scope / Goals / Quick-reference triad up front lets a tester understand scope + run sequence in ≤2 minutes without scrolling through Gherkin. The Coverage matrix's inline short name + AC description columns mean each row is self-explanatory — no story-file lookup needed. + +**Lifecycle (per-TC, implicit — no frontmatter status)** — `draft` until §Coverage matrix row + `maps_to.ac` are populated → `ready`. Execution state lives only in `test-reports/runs/*.md`. Deprecate by replacing the H3 body with `**Deprecated YYYY-MM-DD** — <reason>` and keeping the ID intact. + +**Append-only discipline (reports)** — never edit a past run's results; re-runs create new files (`-run2`, `-run3`, …). A release file's `ship_status` may flip `held → shipped` or `shipped → rolled-back`, recorded as a dated paragraph under §Ship decision. Reports are never deleted. + +**Templates**: [`test-cases.md`](templates/test-cases.md) · [`test-report.md`](templates/test-report.md) (two sub-templates: per-execution + per-release). + +**Folder READMEs**: [`docs/tests/test-cases/README.md`](../../../docs/tests/test-cases/README.md) · [`docs/tests/test-reports/README.md`](../../../docs/tests/test-reports/README.md). + +**Phase 1 is manual-only.** A sync script (`agile-sync-tests.mjs`), a RULE (epic must have test-cases before close), and a Playwright → markdown converter for CI are planned for phase 2 once the manual pattern stabilizes. + +## How to set up in a new project + +1. Create `Docs/` directory structure per the orientation in SKILL.md §0 +2. Add the CLAUDE.md integration block (see `templates.md` §CLAUDE.md) +3. Add the AGENTS.md reference block (see `templates.md` §AGENTS.md) +4. Create initial `VERSION` file (e.g., `0.1.0`) +5. Create initial `CHANGELOG.md` with `[Unreleased]` section +6. If using sprints, create `Docs/sprints/` with `stories/`, `epics/`, `archive/` subdirectories +7. Run `npx koni-docs status` to generate initial STATUS.md diff --git a/.agents/skills/koni-docs/references/templates.md b/.agents/skills/koni-docs/references/templates.md new file mode 100644 index 00000000000..ae82aec124b --- /dev/null +++ b/.agents/skills/koni-docs/references/templates.md @@ -0,0 +1,172 @@ +# Document Templates — Index + +> Each Koniverse document type has its own template file under +> [`templates/`](templates/). This index lists what exists, when to use +> each, and where the canonical content lives. Load only the template +> file the user's request needs. + +--- + +## Template files + +| Template | File | Use when | +|---|---|---| +| **CHANGELOG entry** | [templates/changelog.md](templates/changelog.md) | Writing a changelog entry, shipping a version, closing a story | +| **CONTEXT.md — Decision Log** | [templates/context.md](templates/context.md) | Recording a product/architecture decision or revision (append-only, RULE-7) | +| **LESSONS.md — Lessons Learned** | [templates/lessons.md](templates/lessons.md) | Codifying a recurring trap, library quirk, or pattern that would save someone 30 minutes | +| **BRIEF.md — Product Brief** | [templates/brief.md](templates/brief.md) | Creating/updating the executive brief (precedes PRD §1) | +| **PRD — Product Requirements Document** | [templates/prd.md](templates/prd.md) | Creating/updating PRD §1–§11; FR table rows; PRD §11 epic/story index | +| **ARCHITECTURE.md — System Architecture** | [templates/architecture.md](templates/architecture.md) | Documenting tech stack, components, data flow, AD-N summary table | +| **DESIGN Spec for a Story** | [templates/design-spec.md](templates/design-spec.md) | A story has visual/interaction complexity warranting a dedicated spec | +| **Epic File — Full Template** | [templates/epic.md](templates/epic.md) | Creating/updating an epic (BMad-grade: Mermaid maps, invariants, budgets) | +| **Story File — Full Template** | [templates/story.md](templates/story.md) | Creating/stubbing/updating a story (AC + Tasks + Verification commands) | +| **Sprint File** | [templates/sprint.md](templates/sprint.md) | Opening a sprint, planning scope, closing with retrospective | +| **SETUP.md + DEPLOY.md + .env.example** | [templates/setup.md](templates/setup.md) | Adding an env var (RULE-11 — all three files in same commit) | +| **OKR — File-Native Quarterly Ledger** | [templates/okr.md](templates/okr.md) | Project adopts file-native OKRs in `docs/okr/YYYY-QN.md` | +| **CLAUDE.md + AGENTS.md integration blocks** | [templates/integration.md](templates/integration.md) | Wiring koni-docs into a new project, refreshing Active Context | +| **Test Cases — per-epic scenarios** | [templates/test-cases.md](templates/test-cases.md) | Capturing end-to-end + regression + smoke scenarios at the EPIC level (complements per-story AC) | +| **Test Report — per-execution + per-release** | [templates/test-report.md](templates/test-report.md) | Recording an execution run (`runs/`) or aggregating a release (`releases/`) | + +--- + +## Conventions every template follows + +- **Frontmatter** (where applicable) lives at the top in YAML. `id` MUST + match the filename. Status emojis are stable across the system: + `📋 backlog / 🚧 in-progress / ✅ done / ⏪ reverted / 🗑️ deprecated`. +- **English-only** (RULE-13). Templates, prose, and generated content + are all English even on Vietnamese-led projects. +- **Cross-references use markdown links** (`[text](path)`) — not inline + backticks for file paths. Reviewers must be able to click through. +- **Filled examples are condensed**, not raw copies. The point is to + show shape and tone; for a full real-world reference, link to the + upstream repo (Koni-Finance-Final / Koni-ERP-02). +- **Templates are loaded on demand.** Never pre-load every template; + load the single file that matches the user's intent. + +--- + +## Activation table (mirrors SKILL.md §5) + +| User request | Template file | +| ----------------------------------------------- | -------------------------------------- | +| "write changelog for vX.Y.Z" | [templates/changelog.md](templates/changelog.md) | +| "log a decision" / "record architecture choice" | [templates/context.md](templates/context.md) | +| "add a lesson" / "log a lesson" | [templates/lessons.md](templates/lessons.md) | +| "create brief" / "product brief" | [templates/brief.md](templates/brief.md) | +| "update PRD" / "PRD for <feature>" | [templates/prd.md](templates/prd.md) | +| "create / update architecture" | [templates/architecture.md](templates/architecture.md) | +| "create design spec for US-X.Y" | [templates/design-spec.md](templates/design-spec.md) | +| "create an epic" | [templates/epic.md](templates/epic.md) | +| "create a story for US-X.Y" | [templates/story.md](templates/story.md) | +| "create sprint file" | [templates/sprint.md](templates/sprint.md) | +| "update setup for new env var" | [templates/setup.md](templates/setup.md) | +| "create OKR ledger" / "quarterly OKRs" | [templates/okr.md](templates/okr.md) | +| "wire koni-docs into project" / "refresh Active Context" | [templates/integration.md](templates/integration.md) | +| "create test-cases for EPIC-N" | [templates/test-cases.md](templates/test-cases.md) | +| "record test run for EPIC-N" | [templates/test-report.md](templates/test-report.md) (per-execution sub-template) | +| "create release test report" | [templates/test-report.md](templates/test-report.md) (per-release sub-template) | + +--- + +## Quick frontmatter cheatsheet + +For agents that just need the frontmatter shape without loading the full +template: + +### Story (`docs/sprints/stories/US-X.Y-<slug>.md`) + +```yaml +--- +id: US-X.Y +title: "<Story title>" +epic: EPIC-X +status: backlog # backlog | ready | in-progress | review | done | blocked | deprecated +priority: P1 # P0 | P1 | P2 | P3 +points: 5 # Fibonacci: 1 / 2 / 3 / 5 / 8 / 13 +sprint: # sprint-YYYY-WNN once committed +version_shipped: # set when status → done +prd_ref: FR-N # PRD §8 FR ID(s) and/or AD-N IDs +assignee: # GitHub login (optional) +commit: # full SHA of landing commit (never "pending") +created: YYYY-MM-DD +updated: YYYY-MM-DD +--- +``` + +### Epic (`docs/sprints/epics/EPIC-N.md`) + +```yaml +--- +id: EPIC-X +title: "<Epic title>" +status: backlog # backlog | in-progress | done +prd_ref: FR-X.1 .. FR-X.N # also list AD-N IDs if architecture-heavy +created: YYYY-MM-DD +updated: YYYY-MM-DD +--- +``` + +### Sprint (`docs/sprints/sprint-YYYY-WNN.md`) + +```yaml +--- +id: sprint-YYYY-WNN +status: planned # planned | in-progress | closed +start: YYYY-MM-DD +end: YYYY-MM-DD +goal: "<one sentence naming the deliverable, not the activity>" +--- +``` + +### Test cases (`docs/tests/test-cases/EPIC-N.md`) + +```yaml +--- +id: EPIC-N-tests +epic: EPIC-N +created: YYYY-MM-DD +updated: YYYY-MM-DD +--- +``` + +Each TC inside the file carries its own YAML metadata block: + +```yaml +id: TC-N.E2E-1 +type: e2e # smoke | regression | e2e | integration | unit | performance | security +mode: manual # manual | automated | hybrid +priority: P0 # P0 | P1 | P2 | P3 +maps_to: + fr: [FR-N] + ac: [US-X.Y/AC-1, US-X.Z/AC-2] + ad: [AD-N] # optional + rule: [RULE-N] # optional +``` + +### Test report — per-execution (`docs/tests/test-reports/runs/YYYY-MM-DD-EPIC-N-runN.md`) + +```yaml +--- +epic: EPIC-N +run_id: YYYY-MM-DD-EPIC-N-runN +run_at: YYYY-MM-DDTHH:MM:SS+07:00 +env: staging # local | staging | production +version: vX.Y.Z +commit: <full SHA> +executor: <GitHub login | "CI"> +trigger: per-release # per-PR | per-release | per-sprint | nightly | on-demand +--- +``` + +### Test report — per-release (`docs/tests/test-reports/releases/vX.Y.Z.md`) + +```yaml +--- +version: vX.Y.Z +released_at: YYYY-MM-DD +commit: <SHA of the release commit> +epics_covered: [EPIC-N, EPIC-M] +ship_status: shipped # shipped | held | rolled-back +--- +``` diff --git a/.agents/skills/koni-docs/references/templates/architecture.md b/.agents/skills/koni-docs/references/templates/architecture.md new file mode 100644 index 00000000000..a8348e495fa --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/architecture.md @@ -0,0 +1,237 @@ +# ARCHITECTURE.md — System Architecture Template + +> **File location**: `docs/ARCHITECTURE.md` +> +> **Use when**: User asks to create/update system architecture, document +> tech stack, record component architecture, or after BMad produces +> architecture artifacts that need standardization. +> +> **Relationship to CONTEXT.md**: ARCHITECTURE.md is the *synthesized* +> structured reference — updated in-place. CONTEXT.md is the *append-only* +> decision log. Individual architecture decisions are recorded in +> CONTEXT.md (with rationale); ARCHITECTURE.md gets the AD-N summary row +> + the architectural state-of-the-world they describe. + +--- + +## 1. Template skeleton + +````markdown +# ARCHITECTURE — <Project Name> + +> Last updated: YYYY-MM-DD (vX.Y.Z) +> Maintainer: <team or lead> + +## System overview + +<3-5 sentence executive summary: what the system does at a high level, +the primary architectural style (monolith, microservices, serverless, etc.), +and the key architectural drivers (scale, latency, compliance, cost).> + +## Tech stack + +| Layer | Technology | Version | Rationale | +|-------|-----------|---------|-----------| +| Runtime | Node.js / Python / Go | X.Y | <one sentence why> | +| Framework | Next.js / FastAPI / etc. | X.Y | <one sentence why> | +| Database | PostgreSQL / etc. | X.Y | <one sentence why> | +| Cache | Redis / etc. | X.Y | <one sentence why> | +| Queue | — | — | — | +| Hosting | Vercel / Fly.io / AWS | — | <one sentence why> | +| Auth | NextAuth.js / Clerk / etc. | X.Y | <one sentence why> | +| Monitoring | Sentry / Datadog / etc. | — | <one sentence why> | + +## Component architecture + +``` +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ Web App │────▶│ API Layer │────▶│ Database │ +│ (Next.js) │ │ (tRPC/REST) │ │ (PostgreSQL) │ +└──────────────┘ └──────────────┘ └──────────────┘ + │ │ + ▼ ▼ +┌──────────────┐ ┌──────────────┐ +│ Auth │ │ Queue │ +│ (NextAuth) │ │ (optional) │ +└──────────────┘ └──────────────┘ +``` + +| Component | Responsibility | Tech | Key files | +|-----------|---------------|------|-----------| +| Web App | UI rendering, client state, routing | Next.js 14 (App Router) | `app/`, `components/` | +| API Layer | Business logic, validation, DB queries | tRPC / REST | `server/api/`, `app/api/` | +| Database | Persistent storage, migrations | PostgreSQL + Prisma | `prisma/schema.prisma` | +| Auth | Session management, OAuth flows | NextAuth.js | `server/auth.ts` | + +## Data architecture + +### Core models + +| Model | Table | Key fields | Indexes | +|-------|-------|------------|---------| +| User | `users` | id, email, name, created_at | `email (unique)` | +| Task | `tasks` | id, user_id, title, status, priority, created_at | `user_id`, `status` | + +### Data flow + +``` +Client (browser) + │ POST /api/tasks { title, description } + ▼ +API Route (Next.js Route Handler) + │ validate input (zod) + │ check auth (getServerSession) + ▼ +Prisma Client + │ INSERT INTO tasks ... + ▼ +PostgreSQL + │ returns new row + ▼ +API Route + │ return JSON response + ▼ +Client (optimistic update + revalidate) +``` + +## API architecture + +| Endpoint | Method | Auth | Purpose | +|----------|--------|------|---------| +| `/api/tasks` | GET | Required | List user's tasks | +| `/api/tasks` | POST | Required | Create task | +| `/api/tasks/[id]` | PATCH | Required | Update task | +| `/api/tasks/[id]` | DELETE | Required | Delete task | + +### Error response format + +```json +{ + "error": { + "code": "VALIDATION_ERROR", + "message": "Title is required", + "field": "title" + } +} +``` + +## Security architecture + +| Concern | Approach | Detail | +| ---------------- | ------------------------ | ------------------------------------------------------ | +| Authentication | NextAuth.js JWT sessions | HttpOnly cookie, 30-day expiry | +| Authorization | Row-level ownership | All queries filter by `user_id = session.user.id` | +| Input validation | Zod schemas | Every API input validated at the boundary | +| CSRF | NextAuth built-in | Double-submit cookie pattern | +| Secrets | Environment variables | `.env.local` (never committed), validated at startup | + +## Deployment architecture + +``` +GitHub (main branch) + │ git push + ▼ +GitHub Actions (CI) + │ lint → typecheck → test → build + ▼ +Vercel (hosting) + ├── Production (main) → app.koni.app + └── Preview (PR branches) → *.vercel.app + │ + ▼ +Supabase (PostgreSQL) + └── Production database +``` + +| Environment | URL | Branch | DB | +| ----------- | ------------------- | --------- | ---------------- | +| Production | app.koni.app | main | Supabase prod | +| Preview | `<pr>.vercel.app` | feature/* | Supabase staging | + +## Integration architecture + +| External service | Purpose | Auth method | Fallback | +| ------------------ | ------------------ | --------------- | -------------------------- | +| `<Service name>` | `<what it does>` | API key / OAuth | `<graceful degradation>` | + +## Architecture decisions + +<BMad standard: each architecture decision gets an AD-N identifier. +Summary table here; full rationale in CONTEXT.md D<N> entries. +Use AD-N IDs in story Dev Notes to reference which decisions apply.> + +| ID | Topic | Summary | Version | CONTEXT Ref | +| --- | ----------- | ------------------------------ | ------- | ---------------- | +| AD-1 | `<title>` | <one sentence summary> | vX.Y.Z | [D1](CONTEXT.md) | +| AD-2 | `<title>` | <one sentence summary> | vX.Y.Z | [D2](CONTEXT.md) | + +Individual decisions that shaped this architecture are recorded in [CONTEXT.md](CONTEXT.md). +Link new architecture decisions from CONTEXT.md here as they are recorded. + +## Open architecture questions + +- [ ] <unresolved architecture question> +- [ ] <tradeoff being evaluated> +```` + +--- + +## 2. Updating ARCHITECTURE.md + +- **When**: After any architecture decision (CONTEXT.md entry), tech stack + change, or new integration. +- **How**: Edit the relevant section in-place. Update the `Last updated` + date and version. +- **Cross-reference**: Always link the relevant CONTEXT.md entry in the + AD table. +- **Omit empty sections**: If a section (e.g., Queue, Integration) has no + content, remove it. Add it back when needed. + +--- + +## 3. Filled example (condensed) + +> Full ARCHITECTURE files run 600–1000 lines. The snippet below shows the +> shape of `System overview` + `Tech stack` + `Architecture decisions` — +> the three sections most often consulted by reviewers. + +```markdown +# ARCHITECTURE — Koni-ERP-02 + +> Last updated: 2026-05-07 (v0.76.0) +> Maintainer: founder / @jindo9986 + +## System overview + +Koni-ERP-02 is a single Next.js App Router monolith deployed on Vercel, +with Supabase for Postgres + Auth + RLS, Inngest for scheduled syncs + +event-driven jobs, and per-workspace BYOK LLM providers (Anthropic / +OpenAI / Gemini / Qwen) for the NL→SQL pipeline. Multi-tenant from day +one via row-level security (every table carries `workspace_id`, every +policy filters by `user_workspaces()`). Architectural drivers: self-serve +onboarding (10-minute time-to-first-answer), self-host friendliness, +sub-1s NL→SQL latency on warm cache. + +## Tech stack + +| Layer | Technology | Version | Rationale | +|-------|-----------|---------|-----------| +| Runtime | Node.js | ≥20 LTS | Vercel + Inngest both target Node | +| Framework | Next.js | 15 (App Router) | RSC + Server Actions; OAuth + chat fit cleanly | +| Database | PostgreSQL via Supabase | 15 | RLS upfront cost <2 days; rebuild past 5 tenants takes weeks | +| Auth | Supabase Auth | 2.x | Email + OAuth; SSR cookie helper | +| Jobs | Inngest | 3.x | Cron 6h + event-driven; local dev server | +| LLM | Anthropic Claude (default) | claude-sonnet-4-6 | BYOK lets workspaces swap providers | +| Hosting | Vercel | — | Edge + CDN; preview deploys on PR | + +## Architecture decisions + +| ID | Topic | Summary | Version | CONTEXT Ref | +| --- | ----- | ------- | ------- | ----------- | +| AD-1 | Multi-tenant RLS from day 1 | Every table `workspace_id NOT NULL`; policy `workspace_id IN (SELECT user_workspaces())` | v0.1.0 | [D4](CONTEXT.md) | +| AD-2 | Hardcoded connectors → abstract after Stripe (rule-of-three) | GitHub = #1, Linear = #2, Stripe = #3 will trigger refactor | v0.1.0 | [D4](CONTEXT.md) | +| AD-3 | Editable SQL preview UX | Confidence badge + SQL collapsed but editable; revised in v0.7.0 to auto-run | v0.7.0 | [D4](CONTEXT.md) | +| AD-4 | BYOK key encryption via AES-256-GCM | Master encryption key in env; same scheme reused for OAuth tokens (v0.20.0) | v0.20.0 | [D38](CONTEXT.md) | + +Individual decisions are recorded in [CONTEXT.md](CONTEXT.md). +``` diff --git a/.agents/skills/koni-docs/references/templates/brief.md b/.agents/skills/koni-docs/references/templates/brief.md new file mode 100644 index 00000000000..0f2f9786558 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/brief.md @@ -0,0 +1,170 @@ +# BRIEF.md — Product Brief Template + +> **File location**: `docs/BRIEF.md` +> +> **Use when**: User asks to create/update product brief, executive brief, +> or after BMad brainstorm produces a brief that needs standardization. +> +> **Source**: Maps directly from BMad `brief.md` (planning artifact). The +> Brief is the executive-facing document that defines the product vision +> *before* detailed requirements are written. PRD §1 Executive Summary +> derives from this file. + +--- + +## 1. Template skeleton + +```markdown +# Product Brief: {Product Name} + +## Executive Summary + +[2-3 paragraph narrative: What is this? What problem does it solve? +Why does it matter? Why now? This should be compelling enough to stand +alone — if someone reads only this section, they should understand the +vision.] + +## The Problem + +[What pain exists? Who feels it? How are they coping today? What's the +cost of the status quo? Be specific — real scenarios, real frustrations, +real consequences.] + +## The Solution + +[What are we building? How does it solve the problem? +Focus on the experience and outcome, not the implementation.] + +## What Makes This Different + +[Key differentiators vs alternatives. Why this approach? What's the +unfair advantage? Be honest — if the moat is execution speed, say so.] + +| Competitor | What They Do | Our Advantage | +|-----------|-------------|---------------| +| {name} | {description} | {why we're better} | + +## Who This Serves + +**Primary user: {persona name}** +- {demographics, context, behavior} +- {core need they're trying to satisfy} + +**Secondary user: {persona name}** (if applicable) +- {demographics, context, behavior} + +## Success Criteria + +| Metric | Target (Month 6) | Target (Month 12) | +|--------|------------------|-------------------| +| {metric 1} | {target} | {target} | +| {metric 2} | {target} | {target} | + +## Scope + +### In Scope (MVP) +- {feature / capability included in first release} + +### Out of Scope (v1.0) +- {feature explicitly deferred to future versions} + +## Vision + +[Where does this go if it succeeds? What does it become in 2-3 years? +Inspiring but grounded — 1 paragraph.] +``` + +--- + +## 2. Updating BRIEF.md + +- **When**: During initial product definition, or after BMad brainstorm → brief + pipeline produces output. +- **How**: Write the full brief. On major pivots, update in-place and record + the change in CONTEXT.md. +- **Cross-reference**: PRD §1 Executive summary derives from BRIEF.md. Link + to BRIEF.md from the PRD header. + +--- + +## 3. Filled example (condensed) + +```markdown +# Product Brief: Koni ERP + +## Executive Summary + +Koni ERP is a self-serve AI data layer for tech teams under 1000 employees +— a chat box that answers business questions in plain English, drawing +data from the SaaS tools the team already uses (GitHub, Linear, Stripe). +The wedge V1 is GitHub + Linear for founders and Heads of Engineering, +with multi-tenant Row-Level Security from day one. + +The opportunity: companies <1000 employees can't afford fully-integrated +ERPs and end up running a SaaS combo, which fragments their data. +Glean-style products solve adjacent problems but require an 8-week +procurement cycle. We ship self-serve, BYOK, and self-host-friendly +from day one. + +## The Problem + +A founder running 5 SaaS tools (GitHub, Linear, Stripe, HubSpot, Notion) +cannot answer "how many PRs did we merge last week and what % shipped +features customers paid for". Each tool has its own dashboard; none of +them speaks to the others; a SQL-fluent analyst is still 18 months of +headcount away. The status quo is "open 5 tabs and eyeball it" — which +means most teams never ask the question. + +## The Solution + +A chat box at `erp.koni.studio`. Connect tools via OAuth (5-click +self-serve), the system ingests into a multi-tenant Postgres with RLS, +and natural-language questions get answered by an NL→SQL pipeline with +the SQL preview shown for power users. BYOK so workspace owners bring +their own Anthropic / OpenAI / Gemini / Qwen key. + +## What Makes This Different + +| Competitor | What They Do | Our Advantage | +|-----------|-------------|---------------| +| Glean | Enterprise search + AI assistant | Self-serve, no 8-week procurement | +| Looker / Metabase | BI on warehoused data | No ETL — direct connector ingest | +| ChatGPT + custom GPT | LLM Q&A | Multi-tenant RLS + BYOK + connector framework | + +## Who This Serves + +**Primary user: technical founder (V1 PLG)** +- 10-100 employees, tech-comfortable, already running GitHub + Linear +- Wants weekly insight cadence without hiring a data team + +**Secondary user: Head of Data (V2 sales-led)** +- 100-1000 employees, needs SOC 2 + self-host + SSO +- Today running a Looker / Metabase stack against ETL'd warehouses + +## Success Criteria + +| Metric | Target (Month 6) | Target (Month 12) | +|--------|------------------|-------------------| +| Active workspaces | 100 | 1000 | +| Weekly query volume / workspace | 30 | 100 | +| Self-serve OAuth completion rate | 60% | 80% | + +## Scope + +### In Scope (MVP) +- GitHub + Linear connectors +- NL→SQL with confidence badge + editable preview +- BYOK Anthropic / OpenAI / Gemini / Qwen + +### Out of Scope (v1.0) +- Stripe connector (triggers connector-abstraction refactor, rule-of-three) +- Self-host / on-prem (V2 sales-led tier) +- Custom dashboards (chart-pinning ships in a later release) + +## Vision + +In 24 months: every tech company under 1000 employees has a Koni ERP +workspace as their default "ask the business a question" surface. The +connector framework supports 20+ SaaS tools, and a small but defensible +enterprise tier ships self-host + SSO for the V2 segment. +``` diff --git a/.agents/skills/koni-docs/references/templates/changelog.md b/.agents/skills/koni-docs/references/templates/changelog.md new file mode 100644 index 00000000000..a7e85907fa7 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/changelog.md @@ -0,0 +1,91 @@ +# CHANGELOG Entry — Template + +> **File location**: `docs/CHANGELOG.md` (or `Docs/CHANGELOG.md` — match +> the project's existing casing). +> +> **Use when**: User asks to write a changelog entry, ship a version, or +> close a story. +> +> **One rule above all others**: every code-shipping commit bumps `VERSION` +> AND adds a new entry to CHANGELOG.md IN THE SAME COMMIT (RULE-1). The +> commit hash goes into the entry at pre-commit time — `pending` is never +> acceptable (RULE-2). + +--- + +## 1. Template skeleton + +```markdown +## [X.Y.Z] — YYYY-MM-DD — <short descriptive title> — vX.Y.Z + +<1-3 sentence description: what shipped and why. Include root cause for bug fixes.> + +### Added +- <Feature / component added> + +### Changed +- <Behavior or API changed — old vs new> + +### Fixed +- <Bug description + root cause in one sentence> + +### Removed +- <What was dropped and why> + +### Security +- <CVE or hardening detail> + +**Commit**: <7-char or full SHA> +``` + +--- + +## 2. Rules + +- Only include sections that have content. Omit empty sections. +- `**Commit**: pending` is NEVER acceptable (RULE-2). Use the full landing + SHA, set at pre-commit. +- Entries in reverse-chronological order — newest at top. +- Never reorder or edit past entries. +- Version tag appears twice: `[X.Y.Z]` in header AND `— vX.Y.Z` inline — + both required for `git log --grep`. + +--- + +## 3. Safe CHANGELOG insertion + +**WRONG** (eats previous version header): + +``` +oldString = "## [0.63.3] — ..." +newString = "## [0.63.4] ...\n\n## [0.63.3] — ..." +``` + +**CORRECT** — anchor on `[Unreleased]` section: + +``` +oldString = "## [Unreleased]\n\n(empty — track here while in dev but not yet shipped)\n\n---" +newString = "## [Unreleased]\n\n(empty...)\n\n---\n\n## [X.Y.Z] — ...\n\n...content..." +``` + +--- + +## 4. Filled example + +```markdown +## [0.63.4] — 2026-01-15 — Add pod project management — v0.63.4 + +Shipped pod-based project grouping with drag-and-drop reordering. Users can now +organize projects into custom pods for better workspace navigation. + +### Added +- Pod creation and deletion UI in workspace settings +- Drag-and-drop project-to-pod assignment +- Pod filter chips in project list + +### Fixed +- Project list not updating after workspace switch (missing `revalidatePath` in + workspace change handler) + +**Commit**: a1b2c3d +``` diff --git a/.agents/skills/koni-docs/references/templates/context.md b/.agents/skills/koni-docs/references/templates/context.md new file mode 100644 index 00000000000..bf007041f26 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/context.md @@ -0,0 +1,119 @@ +# CONTEXT.md — Decision Log Template + +> **File location**: `docs/CONTEXT.md` +> +> **Use when**: User asks to record a product / architecture decision, +> revise an earlier decision, or log "why we picked X over Y". +> +> **One rule above all others**: CONTEXT.md is append-only (RULE-7). Never +> rewrite a past decision; record a revision entry that references the +> original by ID. Future-you reads this when wondering "why did we pick +> X over Y" — silently editing history breaks that contract. + +--- + +## 1. Phase header + +Group decisions chronologically by phase. Each phase gets a single header. + +```markdown +--- + +## Phase N — <Phase name> (YYYY-MM-DD, shipped vX.Y.Z) +``` + +--- + +## 2. Decision entry template (D`<N>`) + +```markdown +### D<N>. <Short decision title> + +**Context**: <1-2 sentences: what problem triggered this decision> + +**Decision**: <what was decided, specific and concrete> + +**Rationale**: <why this option over alternatives — the "because" is mandatory> + +**Alternatives considered** (optional): +- Option A — why rejected +- Option B — why rejected + +**Impact**: <what this changes in the codebase or product> + +**Date**: YYYY-MM-DD +**Version**: vX.Y.Z +``` + +### Finding the next D`<N>` number + +```bash +grep -n "^### D[0-9]" docs/CONTEXT.md | tail -5 +``` + +Increment by 1. Never reuse a number, even if a decision is deleted. + +--- + +## 3. Revision entry template (revision of D`<M>`) + +When a prior decision needs to change, add a NEW entry that points back +at the original. Never edit the body of the original. + +```markdown +### D<N>. <Title> (revision of D<M>) + +**What changed**: <what was wrong or outdated in D<M>> + +**New decision**: <the corrected or updated decision> + +**Rationale**: <why the revision was needed> + +**Date**: YYYY-MM-DD +**Version**: vX.Y.Z +``` + +--- + +## 4. Anti-patterns (RULE-7) + +| Wrong | Correct | +| ------------------------------------ | ------------------------------------- | +| Edit body of past entry D`<M>` | Add new D`<N>` referencing D`<M>` | +| Delete a wrong decision | Add correction entry | +| Leave rationale blank ("we chose X") | Always include "because Y" | +| One huge entry covering 10 decisions | One entry per decision | + +--- + +## 5. Filled example + +```markdown +## Phase 0 — Brainstorm & PRD (2026-04-29 morning) + +### D3. TAM expansion (IMPORTANT PIVOT) + +**Context**: Initial brainstorm assumed TAM = companies with <30 employees +(very early stage). Founder pushed back during the /office-hours review. + +**Decision**: TAM = companies <1000 employees, with a 2-tier persona model: +- **V1 (PLG)**: <100 employees, founder / Head of Engineering +- **V2 (sales-led)**: 100-1000 employees, Head of Data, mid-market + +**Rationale**: companies under 1000 employees all hit the "data scattered +across many tools" problem, not just <30. Sub-1000 companies cannot afford +fully-integrated ERPs and end up running a SaaS combo, which fragments +their data. Targeting only <30 leaves the wedge customer intact while +expanding the addressable market 30×. + +**Alternatives considered**: +- Stay at <30 employees — rejected: too narrow to defend a venture-scale outcome. +- Jump straight to enterprise — rejected: V1 product-market fit needs PLG motion first. + +**Impact**: V2 requires compliance (SOC 2, regional data-residency), self-host +option, SSO. Ship V1 first, V2 follows. Updates `BRIEF §Who This Serves` and +PRD §5 Personas. + +**Date**: 2026-04-29 +**Version**: pre-v0.1.0 +``` diff --git a/.agents/skills/koni-docs/references/templates/design-spec.md b/.agents/skills/koni-docs/references/templates/design-spec.md new file mode 100644 index 00000000000..a97df082968 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/design-spec.md @@ -0,0 +1,122 @@ +# DESIGN Spec for a Story — Template + +> **File location**: `docs/design/US-X.Y-<slug>-design.md` +> +> **Use when**: A story has significant visual complexity or interaction +> nuance that warrants a dedicated design spec. Skip for stories that fit +> comfortably inside the story file's AC list. +> +> **Relationship to DESIGN.md (the design system)**: `DESIGN.md` at the +> repo root is the project-wide design system (typography, color, spacing, +> primitives). Per-story design specs live in `docs/design/` and +> *reference* the system. Always cite the §-section of DESIGN.md being +> applied — and document any deviation with rationale. + +--- + +## 1. Template skeleton + +```markdown +# US-X.Y — <Story title> — Design Spec + +> **Status**: 📋 Backlog / 🚧 In Progress / ✅ Done (vX.Y.Z) +> **PRD ref**: [FR-N](../PRD.md), [§11 EPIC-X US-X.Y](../PRD.md) +> **Story**: [stories/US-X.Y-<slug>.md](../sprints/stories/US-X.Y-<slug>.md) +> **Epic**: [EPIC-X](../sprints/epics/EPIC-X.md) +> **DESIGN refs**: [§N <section name>](../../DESIGN.md) +> **LESSONS refs**: [§N <related trap>](../LESSONS.md) + +## Context + +<why this story warrants a design spec — 1 paragraph. State what's +visually or interactionally complex enough that the story file's AC +list cannot capture it.> + +## Screens / states + +<List every screen this story touches, and every state each screen +must handle. Empty / loading / populated / error are the default four; +add more (e.g. "expired token", "rate limited") if the story surfaces them.> + +| Screen | State | Notes | +|---|---|---| +| <route or component name> | empty / loading / populated / error | <any constraint> | + +## Layout decisions + +<Which DESIGN.md sections apply (§X) and any deviations from them with +rationale. Reviewers reject deviations without a stated reason.> + +- **DESIGN.md §N <section>**: applied to <surface>. +- **Deviation**: <what differs and why> — accepted because <reason>. + +## Component inventory + +<Every component this story uses. Mark new components vs reused. For +reused, cite the source path so reviewers can verify the variant.> + +| Component | Source | Notes | +|---|---|---| +| `<Button>` | shadcn/ui primitive | — | +| `<ProjectShell>` | new (this story) | <responsibility> | + +## Open questions + +- [ ] <unresolved design choice — assign a decision owner if blocking> +- [ ] <tradeoff being evaluated> +``` + +--- + +## 2. Filled example (condensed) + +```markdown +# US-3.7 — Per-pod project management view (Projects v2-style) — Design Spec + +> **Status**: 📋 Backlog (V1 design approved 2026-05-02). Awaiting build. +> **PRD ref**: [FR-18](../PRD.md), [§7 EPIC-3 US-3.7](../PRD.md) +> **Story**: [stories/US-3.7-pod-project-management.md](../sprints/stories/US-3.7-pod-project-management.md) +> **DESIGN refs**: [§11.8 two-column shell](../../DESIGN.md), [§11.7 loading-transition](../../DESIGN.md) +> **LESSONS refs**: [§22 public route trust boundary](../LESSONS.md), [§29 unstable_cache + workspace-scoped key](../LESSONS.md) + +## Context + +The agile-MD convention (`Docs/sprints/` markdown parsed into Stories + +Epics + Sprints) already works for the Koni dev team. Hypothesis: any pod +team that follows the same convention should get the same agile UI +inside Koni without building a separate DB-backed tracker. V1 reads each +pod's connected GitHub repos via Octokit; no migration, no new storage. + +## Screens / states + +| Screen | State | Notes | +|---|---|---| +| `/dashboard/data-sources/pods/[slug]/project` | empty (no path configured) | CTA → Settings → Pod → Project path | +| `/dashboard/data-sources/pods/[slug]/project` | loading (initial fetch) | Skeleton table, nav progress bar (DESIGN §11.7) | +| `/dashboard/data-sources/pods/[slug]/project` | populated (Table) | Sortable, click-row → GitHub external link | +| `/dashboard/data-sources/pods/[slug]/project` | populated (Board) | Status columns; mobile stacks | +| `/dashboard/data-sources/pods/[slug]/project` | error (Octokit 401) | "Token expired — reconnect GitHub" + CTA | + +## Layout decisions + +- **DESIGN.md §11.8 two-column shell** applied: left sidebar = pod nav, + right pane = view toolbar + table/board. +- **DESIGN.md §11.7 loading-transition** applied: `<NavProgressBar>` for + view switches; `<PageTransition>` fade-in on first render. +- **Deviation**: Board view uses *full-width* kanban (not the standard + §11.8 two-column inset) to fit 5 status columns on a 1440px viewport. + +## Component inventory + +| Component | Source | Notes | +|---|---|---| +| `<ProjectShell>` | new (this story) | View toolbar (Table | Board | Roadmap) | +| `<ProjectTableView>` | new | Wraps shadcn `<Table>` with sort + group-by | +| `<ProjectBoardView>` | new | DnD via react-dnd-kit (read-only V1) | +| `<StatusBadge>` | shared `src/components/ui/status-badge.tsx` | Per DESIGN.md §11.5 | + +## Open questions + +- [ ] Should sprint filter default to "current sprint" or "all"? Tracking telemetry needed. +- [ ] Drag-write back (commit to repo) — V2 or V3? +``` diff --git a/.agents/skills/koni-docs/references/templates/epic.md b/.agents/skills/koni-docs/references/templates/epic.md new file mode 100644 index 00000000000..58b8e245722 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/epic.md @@ -0,0 +1,516 @@ +# Epic File — Full Template + +> **File location**: `docs/sprints/epics/EPIC-N.md` +> +> **Use when**: User asks to create or update an epic, or when a planning tool +> (BMad / GStack / Superpowers) produces an epic-shaped artifact that needs +> standardization into the koni-docs structure. +> +> **One rule above all others**: an epic is the *contract* between the PRD's +> functional requirements and the stories that implement them. It owns the +> cross-cutting invariants, performance budgets, and shared test infrastructure +> that no single story can own alone. Stories *reuse* what the epic publishes; +> they never rebuild it. + +--- + +## 1. Section index — what's required vs optional + +Not every epic needs every section. Pick depth by epic size; small epics (≤5 +stories, ≤2 weeks) can skip the optional blocks. Medium and large epics MUST +include the recommended blocks — they are the difference between an epic that +holds the line and an epic that quietly drifts. + +| § | Section | Small (≤5 stories) | Medium (6–12) | Large (13+) | +| --- | ---------------------------------------- | ------------------ | ------------- | ----------- | +| 1 | Frontmatter | required | required | required | +| 2 | Goal | required | required | required | +| 3 | Overview — Business context | recommended | required | required | +| 4 | Overview — Feature pillars | optional | required | required | +| 5 | Out of scope | required | required | required | +| 6 | FR Coverage | required | required | required | +| 7 | AD Coverage | optional | recommended | required | +| 8 | Stories table | required | required | required | +| 9 | Object map & user-story interactions | optional | recommended | required | +| 10 | Detailed object specifications | optional | optional | recommended | +| 11 | Cross-cutting invariants | recommended | required | required | +| 12 | RBAC additions | optional | optional | recommended | +| 13 | Cross-story testing requirements | optional | recommended | required | +| 14 | Performance budgets & invariants | optional | recommended | required | +| 15 | Acceptance criteria (propagated) | required | required | required | + +--- + +## 2. Full template skeleton + +````markdown +--- +id: EPIC-X +title: "<Epic title>" +status: backlog # backlog | in-progress | done +prd_ref: # FR-N this epic OWNS (not just touches). Enumerate every entry (RULE-17 — no `..` range syntax, no AD-N here, no prose). + - FR-X + - FR-Y +arch_ref: # OPTIONAL — AD-N this epic anchors. Enumerate; omit field entirely if none. + - AD-N +created: YYYY-MM-DD +updated: YYYY-MM-DD +--- + +## Goal + +<1-3 sentences: the user outcome this epic delivers end-to-end. State the +*value*, not the mechanism. For platform / foundation epics, state what +downstream epics get to *stop worrying about*.> + +## Overview + +### Business context + +<2-4 paragraphs explaining: (a) what state the product is in *before* this +epic; (b) what *kind* of capability this epic adds (read path / write path / +governance / runtime / cross-cutting); (c) the architectural distinction +this epic preserves (i.e. what it explicitly does NOT do, deferred to +which other epic). Link to BRIEF.md / PRD §1 / ARCHITECTURE for shared +context — do not repeat them.> + +### Feature pillars + +<Group stories into 3-7 capability pillars. Each pillar is a coherent +slice of user value or shared infra. Use this table to give a reviewer +"the map" before they dive into the 20-row stories table below.> + +| # | Pillar | Stories | Purpose | +|---|---|---|---| +| 1 | **<Pillar name>** | [US-X.1](../stories/US-X.1-<slug>.md), [US-X.2](../stories/US-X.2-<slug>.md) | <One-line purpose> | +| 2 | **<Pillar name>** | [US-X.3](../stories/US-X.3-<slug>.md) | <One-line purpose> | + +### Out of scope + +<Bullet list of capabilities that *sound like* they belong here but +explicitly do not. For each, name the owning epic and the rationale. This +is the single highest-leverage section for preventing scope creep.> + +- **<Capability A>** — owned by [EPIC-N](EPIC-N.md). <Why it lives there.> +- **<Capability B>** — deferred to <phase / version>. <Why deferred.> + +## FR Coverage + +<Map every functional requirement this epic owns to the story that +implements it. Status reflects the *story's* current state. When a FR is +shared across epics, note which other epic co-owns it.> + +| FR | Story | Status | +|----|-------|--------| +| FR-N | [US-X.1](../stories/US-X.1-<slug>.md) | ✅ done (vX.Y.Z) / 🚧 in-progress / 📋 backlog | +| FR-N (shared with EPIC-M) | [US-X.2](../stories/US-X.2-<slug>.md) | ✅ done (vX.Y.Z) | + +> Note any FR explicitly *not* owned here (e.g. "FR-N is owned by EPIC-M, +> listed here only as a downstream dependent"). + +## AD Coverage + +<For architecture-heavy epics: map every Architecture Decision (AD-N from +ARCHITECTURE.md / CONTEXT.md) this epic loads on to the story that +materializes it. Skip the table for product-feature epics that don't +introduce new architectural choices.> + +| AD | Title | Story | +|----|-------|-------| +| AD-N | <decision title> | [US-X.Y](../stories/US-X.Y-<slug>.md) | + +> Call out ADs that are *referenced* but whose primary implementation +> lives in another epic, so reviewers don't expect them here. + +## Stories + +| ID | Title | Goal | Status | Version | +|---|---|---|---|---| +| [US-X.1](../stories/US-X.1-<slug>.md) | <title> | <one-line user outcome> | ✅ done | v0.X.0 | +| [US-X.2](../stories/US-X.2-<slug>.md) | <title> | <one-line user outcome> | 🚧 in-progress | — | + +## Object map & user-story interactions + +> Reference: [DOMAIN-ENTITIES.md](../../DOMAIN-ENTITIES.md) (if the project +> maintains one). Existing entities are reused as-is; planned entities are +> introduced by stories in this epic. + +### Entity & subsystem map + +```mermaid +graph LR + classDef existing fill:#e8f4f8,stroke:#2980b9,color:#000 + classDef planned fill:#fff3cd,stroke:#f39c12,color:#000,stroke-width:2px + classDef future fill:#f5e6e6,stroke:#c0392b,color:#000,stroke-dasharray:4 3 + classDef external fill:#fef6e4,stroke:#8e44ad,color:#000 + classDef audit fill:#ede7f6,stroke:#5e35b1,color:#000 + + subgraph EXISTING["Delivered by upstream epics"] + A["<Entity A>"] + B["<Entity B>"] + end + + subgraph THIS["This epic (MVP)"] + X["<b>Entity X</b><br/><i>US-X.1, US-X.2</i>"] + Y["<b>Entity Y</b><br/><i>US-X.3</i>"] + end + + subgraph FUTURE["This epic — future scope"] + Z["<Entity Z> · US-X.N"] + end + + A --> X + X --> Y + Y -.-> Z + + class A,B existing + class X,Y planned + class Z future +``` + +#### Diagram legend + +- **Blue** = entities/subsystems delivered by upstream epics. +- **Yellow** = entities/subsystems this epic owns (MVP). +- **Dashed red** = future scope, deferred. +- **Purple** = external providers / audit substrate. + +### US ↔ entity / subsystem matrix + +<For each story in the epic, name the primary entity or subsystem it ships +and the FR it fulfills. This matrix is the canonical "what does this story +touch" view and is reused by reviewers and onboarding.> + +| US | Primary entity / subsystem | FR | +|---|---|---| +| [US-X.1](../stories/US-X.1-<slug>.md) | <Entity / subsystem> | FR-N | + +### End-to-end happy path + +<One sequence diagram showing the canonical user flow that exercises this +epic. Branches (failure modes, fallbacks) are listed in prose below the +diagram — do not clutter the happy path.> + +```mermaid +sequenceDiagram + actor U as User + participant Web + participant API + participant DB + + U->>Web: <trigger> + Web->>API: <request> + API->>DB: <write inside withTx> + API-->>Web: <typed response> + Web-->>U: <state update> +``` + +**Branches not shown:** <failure modes>, <fallback paths>, <degraded states>. + +## Detailed object specifications + +<For each *load-bearing* entity introduced by this epic, list the columns +that drive acceptance criteria across multiple stories. This is not a +schema dump — link to `prisma/schema.prisma` (or equivalent) for the full +shape. Cite the owning story for each column.> + +### <EntityName> — <role in the epic> + +`<EntityName>` is the <central object / read-side mirror / write-side +registry> for <what>. Schema lives in <path>. + +**Columns load-bearing for AC across stories:** + +| Column | Purpose | Owner story | +|---|---|---| +| `<column>` | <what it represents / why nullable / which invariant> | [US-X.Y](../stories/US-X.Y-<slug>.md) | + +**Lifecycle (non-formal):** `<state1> → <state2> → <state3>` — and what's +*not* a state (e.g. "no `executed` state because this epic does not +move funds"). + +## Cross-cutting invariants + +<The contracts every story in this epic must uphold and every review must +protect. Each invariant cites the FR / AD it operationalizes and the story +that primarily enforces it.> + +- **<Invariant 1> ([FR-N](../../PRD.md#4-functional-requirements)):** <statement of the rule.> Enforced by [US-X.Y](../stories/US-X.Y-<slug>.md). +- **<Invariant 2>:** <statement>. <How violations are caught (lint / test / runtime).> + +## RBAC additions + +<Only include if this epic introduces new resources, actions, or role +assignments. Each entry must land alongside its story — no story may add +an action without updating the access-control statement + seed-role maps.> + +| Resource | New action | Story | Default roles | +|---|---|---|---| +| `<resource>` | `<action>` | [US-X.Y](../stories/US-X.Y-<slug>.md) | <comma-separated roles> | + +**Convention:** <naming convention for resource/action names — match the +existing pattern in the codebase.> + +## Cross-story testing requirements + +<Test patterns that are *set up once* in this epic and *reused* by every +later story. Stories never rebuild these — they import the harness. Iron +rule: if a story implementation touches one of these patterns, the +infrastructure in the right column must already exist.> + +| Pattern | Stories that apply | Shared infra | +|---|---|---| +| **<Pattern name>** | [US-X.1](../stories/US-X.1-<slug>.md), [US-X.2](../stories/US-X.2-<slug>.md) | <path to fixture / helper / test file> | + +> **Cross-reference**: Executable scenarios for this epic live in +> [`docs/tests/test-cases/EPIC-N.md`](../../../docs/tests/test-cases/) (see +> [`templates/test-cases.md`](test-cases.md)). The patterns in the table +> above declare the *harness*; the test-cases file owns the *scenarios* +> that exercise it (E2E / regression / smoke) plus the AC coverage matrix. +> Execution history lives in [`docs/tests/test-reports/`](../../../docs/tests/test-reports/). + +## Performance budgets & invariants + +<Hard budgets the epic publishes so story implementations cannot quietly +trade latency / throughput for shipping speed. Each budget must cite the +story that primarily defends it. Reviewers reject story PRs that do not +explicitly state in dev notes or PR description that they meet these.> + +| Concern | Budget | Story | Rationale | +|---|---|---|---| +| **<Operation>** | <p95 / throughput / size cap> | [US-X.Y](../stories/US-X.Y-<slug>.md) | <one sentence why this number, not a different one> | + +## Acceptance criteria (propagated from stories) + +<Roll-up of the *epic-level* acceptance: which story ACs collectively prove +the epic is done. Status checkboxes mirror story status. This is the +top-of-funnel checklist for "is the epic shippable".> + +- [x] <AC from US-X.1 — one-line summary> +- [ ] <AC from US-X.2 — one-line summary> ([US-X.2](../stories/US-X.2-<slug>.md) — backlog) +```` + +--- + +## 3. Per-section guidance + +### §1 Frontmatter + +- `id` matches the filename: `EPIC-3.md` → `id: EPIC-3`. +- `prd_ref` is a **YAML list** of every FR-N this epic *owns* (not just + touches) — bare IDs only, enumerated (no `FR-28 .. FR-45` range + syntax, no parentheticals). Architecture-heavy epics list AD-N in a + separate `arch_ref` field, NOT mixed into `prd_ref`. See RULE-17 and + [`frontmatter-spec.md`](../frontmatter-spec.md) for the contract + + enumeration rationale. If the FR list grows beyond ~30 entries, the + epic is too broad — split it. +- `status` transitions: `backlog → in-progress → done`. Set `in-progress` + the moment the first story in the epic flips to `in-progress`; set + `done` only after every story is `done` AND the epic-level ACs are + ticked. + +### §2 Goal + +- 1-3 sentences. State the user-visible outcome and the architectural + posture (e.g. "epic ships *no end-user feature* on its own; its + deliverable is a runnable platform"). For product epics, name the + primary persona who benefits. + +### §3 Business context + +- Open with *what's missing before this epic*. Reviewers should be able + to answer "what was broken?" from the first paragraph. +- Name the architectural distinction: what this epic does NOT do, and + which sibling/future epic owns it. This is the most-cited section in + story Dev Notes. + +### §4 Feature pillars + +- 3-7 pillars. Each pillar maps 1-N stories to a coherent slice. If you + cannot draw a pillar boundary cleanly, the epic is probably two epics. +- Pillar names are *capabilities*, not file paths. "Wallet onboarding" + ✅ — "Update OrganizationWallet.prisma" ❌. + +### §5 Out of scope + +- One bullet per excluded capability. Always name the owning epic. +- This section is the single highest-leverage place to prevent scope + creep. If a reviewer asks "why isn't X here?" and X isn't listed — + add it on the spot. + +### §6 FR Coverage + +- Every FR-N in `prd_ref` must appear here, mapped to a story. +- Co-owned FRs note "(shared with EPIC-M)" — reviewers chase the joint + story across both epics. +- Deprecated FRs stay in the table with `🗑️ deprecated YYYY-MM-DD` and + no story link. + +### §7 AD Coverage + +- Only for architecture-heavy epics (EPIC-1 / platform refactors). +- "Referenced but not implemented" ADs go in a callout below the table, + not in the table itself. + +### §8 Stories table + +- Title is short (≤60 chars). The full descriptive name lives in the + story file's frontmatter `title:`. +- Goal column is *one line* — the user outcome from the story's `## Goal` + section, condensed. Reviewers use this to navigate the epic without + opening each story. +- Status emoji + version match the story frontmatter exactly. Use + `agile-sync-up.mjs` to keep this in sync. + +### §9 Object map & US ↔ entity matrix + +- Mermaid diagram covers entities the epic *creates* + their relations to + existing entities (upstream) and future entities (deferred). +- US-X.Y labels inside Mermaid subgraph cells stay plain text — Mermaid + does not render markdown links inside subgraph nodes. Always provide + the US ↔ entity matrix table below the diagram for navigation. +- The end-to-end happy-path sequence diagram covers the canonical user + flow. Branches (failures, fallbacks) go in prose below — never + clutter the happy path with alt branches. + +### §10 Detailed object specifications + +- Only for epics introducing 2+ entities with cross-story coupling. +- Each entity gets a "columns load-bearing for AC across stories" table. + Do not dump the entire schema — link to `prisma/schema.prisma`. +- Cite the *owning story* for each column. A column with no owner is + either dead weight or a missing story. + +### §11 Cross-cutting invariants + +- One bullet per invariant. Each cites the FR / AD it operationalizes. +- Invariants are *enforceable* — name how (test / lint / runtime guard). + "We will be careful" is not an invariant. + +### §12 RBAC additions + +- Required when the epic introduces new resource/action pairs. +- Rule: **no story may add an access-control action without updating + this table in the same PR**. Reviewers reject otherwise. + +### §13 Cross-story testing requirements + +- Shared test infrastructure that lives once and is consumed by every + story. Example: a property-based RLS test that every storage-touching + story extends; a fixture for SIWE signatures every wallet story reuses. +- Iron rule: if a later story touches a pattern listed here, the harness + in the right column must *already exist*. The first story in the epic + to use the pattern is the one that sets it up. + +### §14 Performance budgets & invariants + +- Reviewers reject story PRs that do not explicitly state in dev notes + or PR description that they meet these budgets. +- Budgets are *numbers*, not adjectives. "Fast" is not a budget; "p95 + ≤ 800 ms warm cache" is. + +### §15 Acceptance criteria (propagated) + +- Roll-up of epic-level success. Each item maps to a story's primary AC. +- Check items only when the underlying story is `done` AND its commit is + in `main` AND the version has shipped. + +--- + +## 4. Filled mini-example (condensed) + +```markdown +--- +id: EPIC-3 +title: "Treasury Visibility" +status: in-progress +prd_ref: + - FR-28 + - FR-29 + - FR-30 + # ... enumerate every FR through FR-45 + - FR-82 + - FR-83 + # ... through FR-89 + - FR-114 + - FR-117 + - FR-118 + - FR-119 + - FR-120 +created: 2026-04-11 +updated: 2026-05-09 +--- + +## Goal + +Organizations connect their wallets (EOA + Safe multi-sig), classify them, +see real-time balances / PnL / transaction history sourced from external +providers, and govern the Safe estate — all under the same RLS + audit +invariants the platform layer (EPIC-1) and identity layer (EPIC-2) already +enforce. + +## Overview + +### Business context + +Before EPIC-3, an organization has members and roles but zero visibility into +the treasury. EPIC-3 closes this gap: it owns the **treasury read path** (the +dashboard, asset table, PnL chart, transaction timeline) and the **Safe +governance read path** (signer set, threshold, hierarchy, deploy). It does +**not** move funds — that is owned by [EPIC-4](EPIC-4.md). + +The architectural distinction the epic preserves: **Koni does not run an +indexer**. Balances and transactions are sourced from external providers +through 5 Hexagonal capability ports (AD-32) with backend proxy + cache + +stale-if-error degradation. + +### Feature pillars + +| # | Pillar | Stories | Purpose | +|---|---|---|---| +| 1 | **Wallet onboarding & taxonomy** | US-3.1, US-3.2, US-3.6 | EOA SIWE-verified connect, mandatory entity + purpose classification, soft-delete disconnect | +| 2 | **Treasury read surface** | US-3.3, US-3.4, US-3.5 | Aggregated token holdings, Zerion-backed PnL, Blockscout-backed tx history | +| 3 | **Safe estate governance** | US-3.7, US-3.8, US-3.9 | Connect existing Safe, designate ROOT, deploy new Safe from app | + +### Out of scope + +- **Funds movement / payment requests** — owned by [EPIC-4](EPIC-4.md). EPIC-3 only exposes wallets and balances. +- **Internal balance indexer** — explicitly deprecated by FR-32. All balance reads go through external provider ports. +- **Hash-chain audit verification** — owned by [EPIC-5](EPIC-5.md). EPIC-3 only writes audit entries. + +## FR Coverage + +| FR | Story | Status | +|----|-------|--------| +| FR-28 | [US-3.1](../stories/US-3.1-...md) | ✅ done (v0.3.0) | +| FR-32 | — | 🗑️ deprecated 2026-04-22 | +| FR-117 | [US-3.9](../stories/US-3.9-...md) | ✅ done (v0.3.0) | + +## Stories + +| ID | Title | Goal | Status | Version | +|---|---|---|---|---| +| [US-3.1](../stories/US-3.1-...md) | Connect EOA Wallet (SIWE) | SIWE-verified EOA registry write | ✅ done | v0.3.0 | +| [US-3.3](../stories/US-3.3-...md) | Token Holdings with Price | Aggregate holdings + USD weights via Blockscout + CoinGecko | 🚧 in-progress | — | + +## Cross-cutting invariants + +- **Mandatory classification before activation (FR-29):** every newly connected wallet must complete classification (entity + purpose) before it is visible in the treasury aggregate. +- **Cached signer set is never trusted for execution (FR-87):** SafeConfig.signers is for display only; EPIC-4 must re-fetch on-chain at preflight. +- **Audit-first for governance writes (AD-04):** `wallet.connect`, `wallet.classify`, `safe.deploy` all write inside `withTx`. Audit failure ⇒ action failure. + +## Performance budgets & invariants + +| Concern | Budget | Story | Rationale | +|---|---|---|---| +| **Treasury aggregate p95** | ≤ 800ms warm, ≤ 2.5s cold | US-3.3 | Home-page latency masks vendor regressions otherwise | +| **External provider rate limits** | Per-host token bucket; CI asserts maxRps | US-3.3 | Vendor 429s cascade into L2 staleness for the whole org | + +## Acceptance criteria (propagated from stories) + +- [x] User can connect EOA wallet via EIP-1193 + SIWE (US-3.1) +- [x] Connected wallets are classified (entity + purpose) with per-user primary (US-3.2) +- [ ] Treasury Assets page shows all tokens with USD value + freshness indicators (US-3.3 — in-progress) +``` + +See [EPIC-1.md](https://github.com/Koniverse/Koni-Finance-Final/blob/main/docs/sprints/epics/EPIC-1.md) and [EPIC-3.md](https://github.com/Koniverse/Koni-Finance-Final/blob/main/docs/sprints/epics/EPIC-3.md) in the Koni-Finance-Final repository for full reference epics (370–430 lines each). diff --git a/.agents/skills/koni-docs/references/templates/integration.md b/.agents/skills/koni-docs/references/templates/integration.md new file mode 100644 index 00000000000..ac4ec7e0fa1 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/integration.md @@ -0,0 +1,374 @@ +# CLAUDE.md + AGENTS.md + .active-context — Koni-Docs Integration Blocks + +> **File locations** (project repo root): +> - `CLAUDE.md` — Claude Code agent instructions +> - `AGENTS.md` — agent-agnostic project guide (Codex / Cursor / Gemini) +> - `.active-context.md` (gitignored) — per-developer running snapshot +> - `.active-context.example.md` (committed) — template that contributors copy +> +> **Use when**: Setting up koni-docs in a new project, or refreshing the +> Active Context block after a sprint roll / story close. +> +> **Why both CLAUDE.md and AGENTS.md**: `CLAUDE.md` is consumed by Claude +> Code at session start; `AGENTS.md` is consumed by other tools. Many +> projects symlink one to the other so the same content drives both. The +> Koni-Docs blocks below differ slightly because the audiences differ. + +--- + +## 0. Pick a pattern — inline vs. file-extracted + +There are **two valid integration patterns** for the Active Context block. +Pick one per project; do not mix. + +| Pattern | When to use | Active Context lives in | +|---|---|---| +| **A — Inline (legacy)** | Solo developer, one active branch, low merge volume | `CLAUDE.md` between `koni-docs:auto-update` markers | +| **B — File-extracted (recommended for teams)** | 2+ developers, parallel branches, frequent sprint churn | `.active-context.md` (gitignored) — `CLAUDE.md` keeps only a pointer | + +**Why a separate file for teams:** the Active Context block changes on +every story start, close, sprint roll, decision, and lesson — many +times per week. When two developers update it in parallel on different +branches, the merge is always a conflict, always trivial, and always +annoying. The file-extracted pattern moves the volatile content into a +gitignored snapshot; the durable record stays in `docs/sprints/`, +`CHANGELOG.md`, `CONTEXT.md`, `LESSONS.md`. Conflicts go to zero. + +> **Reference implementation**: Koni-Finance-Final adopted the +> file-extracted pattern in 2026-05; Koni-Skills adopted it in v0.2.0 +> (sprint-2026-W22). Both treat `.active-context.md` as the single +> mutable session-start snapshot. + +--- + +## 1. Pattern A — Inline CLAUDE.md Integration Block + +### CLAUDE.md + +```markdown +## Koni-Docs Integration + +koni-docs: + plugins: [] # e.g. [supabase, nextjs] + docs_path: docs/ # where docs live + active_sprint: sprint-YYYY-WNN # current sprint ID + version_file: VERSION # path to semver file + +## Active Context <!-- koni-docs:auto-update --> +- Sprint: sprint-YYYY-WNN +- Active Stories: 🟡 US-X.Y <title> +- Last Version: vX.Y.Z +- Recent Decisions: D<N> +- Recent Lessons: §N +<!-- /koni-docs:auto-update --> +``` + +The agent updates the block between the `<!-- koni-docs:auto-update -->` +and `<!-- /koni-docs:auto-update -->` markers using `Edit`. + +--- + +## 2. Pattern B — File-extracted active-context (recommended for teams) + +### Step 1 — `CLAUDE.md` keeps the config block + a pointer + +```markdown +## Koni-Docs Integration + +koni-docs: + plugins: [] + docs_path: docs/ + active_sprint: sprint-YYYY-WNN + version_file: VERSION + +## Active Context + +> **Moved to `.active-context.md`** — see [`.active-context.example.md`](./.active-context.example.md) +> for the template and the gitignored-on-purpose rationale. The auto-update block +> (sprint / active stories / decisions / lessons) and the per-developer block +> (GitHub login, git name/email, current branch, workspace path) both live there. +> +> When you start working in this repo, copy the example to `.active-context.md` +> and fill in your local-developer details. Koni-docs T1-T7 triggers update the +> sprint block inside `.active-context.md`, not here. +``` + +### Step 2 — Commit `.active-context.example.md` as the team template + +```markdown +# Active Context — example / template + +> Copy this file to `.active-context.md` and fill in your own values. +> `.active-context.md` is **gitignored** — it captures *your local* development context +> (current user, current branch, in-flight work) and should not be committed. +> +> Source-of-truth doc artifacts (sprints, decisions, lessons) still live under `docs/`; +> this file is just the per-developer running snapshot the agent reads at the start of +> each session. + +## Local developer + +- **GitHub login**: <your-github-username> <!-- `gh api user --jq .login` --> +- **Git name**: <Your Name> <!-- `git config user.name` --> +- **Git email**: <you@example.com> <!-- `git config user.email` --> +- **Workspace**: <absolute path to this repo on your machine> +- **Current branch**: <your-current-branch> <!-- `git rev-parse --abbrev-ref HEAD` --> +- **Last updated**: YYYY-MM-DD + +## Project sprint context <!-- koni-docs:auto-update --> + +- **Sprint**: sprint-YYYY-WNN +- **Active Stories**: 🟡 US-X.Y <title> +- **Recently Shipped (main)**: ✅ US-A.B <title> (vX.Y.Z) — <one-line summary> +- **Last Version**: vX.Y.Z +- **Recent Decisions**: D<N> = <one-line summary> +- **Recent Lessons**: §N = <one-line summary> + +<!-- /koni-docs:auto-update --> + +## How this file is maintained + +- The **Local developer** block is set once when you copy the example, refreshed only + when your branch / git identity changes. Agents should not edit it unless asked. +- The **Project sprint context** block (between the `koni-docs:auto-update` markers) is + updated by the agent on koni-docs T1-T7 triggers (story start/close, sprint start, + CONTEXT decision, LESSONS entry, env-var change, pre-commit) — same as the old + `CLAUDE.md` block, just relocated to this file. + +## Why this file is gitignored + +The sprint context block does contain repo-shared facts (active stories, last version, +recent decisions) — those are also captured durably in `docs/sprints/`, `CHANGELOG.md`, +`CONTEXT.md`, and `LESSONS.md`. The agent uses this file as a fast-access summary at +session start; the *authoritative* state lives under `docs/`. Keeping the snapshot +local avoids merge churn when multiple developers update active stories in parallel. +``` + +### Step 3 — Add `.active-context.md` to `.gitignore` + +``` +# Per-developer running snapshot of sprint + local identity context. +# `.active-context.example.md` is the committed template; copy it to +# `.active-context.md` and fill in your local values. Gitignoring the +# local copy avoids merge churn when multiple developers update active +# stories in parallel. +.active-context.md +``` + +### Step 4 — Each developer copies the example on first checkout + +```bash +cp .active-context.example.md .active-context.md +# Edit `.active-context.md` and fill in the Local developer block: +# gh api user --jq .login → GitHub login +# git config user.name → Git name +# git config user.email → Git email +# pwd → Workspace +# git rev-parse --abbrev-ref HEAD → Current branch +``` + +After this, koni-docs T1–T7 triggers update the sprint block inside +`.active-context.md` — not in `CLAUDE.md`. `CLAUDE.md` becomes a stable, +team-shared file with minimal write churn. + +--- + +## 3. AGENTS.md ⇄ CLAUDE.md — project file architecture + +### 3.1 Convention: AGENTS.md is canonical, CLAUDE.md is pointer (recommended) + +Koniverse projects use **`AGENTS.md` as the single source of truth for +all AI instructions** — project structure, conventions, skill catalog, +workflow guides, commit discipline, behavioral rules. **`CLAUDE.md` is +a thin pointer** that defers to AGENTS.md for everything except the +small surface area that is Claude-Code-specific OR session-volatile +(see exception list below). + +The same idea: **agent-agnostic content goes in AGENTS.md** (Codex, +Cursor, Gemini, Copilot, Claude Code all read it); **Claude-Code-only +or session-bound content stays in CLAUDE.md**. Don't duplicate. When +duplicated content drifts, AGENTS.md wins by convention. + +#### What stays in `CLAUDE.md` + +1. **One-line pointer** at the top stating AGENTS.md is canonical. +2. **`## Koni-Docs Integration` config block** — `plugins`, `docs_path`, + `active_sprint`, `version_file`. Agents read these at session start + to know where docs live, before they've opened any file. +3. **`## Active Context`** — either inline (Pattern A from §1) or a + pointer to `.active-context.md` (Pattern B from §2). This is + session-volatile by design and changes many times per week. +4. **Claude-Code-specific routing/behavioral blocks** if your project + uses them (e.g. agentcohort, custom slash-command routing). + Anything that ONLY applies to Claude Code, not to other agents. + +Everything else — project purpose, structure, conventions, skill +catalog, quick-reference table, docs/ links — lives in `AGENTS.md`. + +#### What goes in `AGENTS.md` + +1. **Canonical preamble** at the top reminding readers AGENTS.md is + the single source of truth. +2. **Project purpose / structure / conventions** — the durable content. +3. **`## Koni-Docs` section** — short reference block pointing at + `docs/README.md` and the koni-docs skill (template in §3.2 below). +4. **Documentation links** — direct pointers to BRIEF, PRD, + ARCHITECTURE, CONTEXT, LESSONS, SETUP, sprints/STATUS, VERSION, + CHANGELOG. Live in AGENTS.md, NOT CLAUDE.md. +5. **Skill catalog tables** (Current skills + Installed helper skills). +6. **Quick reference / common tasks**. + +#### Why this hierarchy + +- **Multi-agent reach.** Cursor, Gemini, Codex CLI, Copilot CLI all read + `AGENTS.md` natively. Claude Code reads BOTH `AGENTS.md` and + `CLAUDE.md`. Putting durable content in AGENTS.md means every agent + gets it; putting it in CLAUDE.md means only Claude Code does. +- **Single source of truth.** When the project structure changes, you + update AGENTS.md and CLAUDE.md still works (because it points at + AGENTS.md). Drift between two copies of the same content is the + recurring papercut this convention prevents. +- **Smaller CLAUDE.md = less merge churn.** With Active Context already + moved to `.active-context.md` (Pattern B from §2), CLAUDE.md becomes + near-static. Fewer parallel-branch edits = fewer conflicts. + +#### Filled example — `CLAUDE.md` thin pointer + +```markdown +# CLAUDE.md — <Project Name> + +This project uses **[AGENTS.md](AGENTS.md)** as the single source of truth +for all AI instructions — project structure, conventions, skill catalog, +documentation map, and behavioral guidelines. + +## Koni-Docs Integration + +koni-docs: + plugins: [] + docs_path: docs/ + active_sprint: sprint-YYYY-WNN + version_file: VERSION + +## Active Context + +> **Moved to `.active-context.md`** — see [`.active-context.example.md`](./.active-context.example.md). +``` + +That's the whole file. Optionally add Claude-Code-only routing blocks +below if the project uses agentcohort, custom slash commands, etc. + +#### Filled example — `AGENTS.md` preamble + +```markdown +# AGENTS.md — <Project Name> + +> **This file is the single source of truth for all AI agent instructions +> in this project.** Cursor, Gemini, Codex CLI, Copilot CLI, and Claude +> Code all read it. `CLAUDE.md` is a thin pointer back to this file plus +> a Claude-Code-specific Koni-Docs Integration block; on any conflict, +> AGENTS.md wins. + +## Project purpose +... + +## Documentation +- [BRIEF.md](docs/BRIEF.md) · [PRD.md](docs/PRD.md) · [ARCHITECTURE.md](docs/ARCHITECTURE.md) +- [CONTEXT.md](docs/CONTEXT.md) · [LESSONS.md](docs/LESSONS.md) · [SETUP.md](docs/SETUP.md) +- [sprints/](docs/sprints/) (with [STATUS.md](docs/sprints/STATUS.md) auto-generated kanban) +- [VERSION](VERSION) + [CHANGELOG.md](CHANGELOG.md) — semver + release history + +## Koni-Docs +This project uses koni-docs for documentation management ... (see §3.2 below for full block). +``` + +--- + +### 3.2 AGENTS.md — Koni-Docs Reference Block + +For agent-agnostic project guides, a minimal pointer is enough. Keep it +short — agents read AGENTS.md for orientation, not for full reference. + +```markdown +## Koni-Docs + +This project uses koni-docs for documentation management. All docs follow the +structure defined in `docs/README.md`. See the koni-docs skill for templates, +rules, and workflows. +``` + +The AGENTS.md block is the same for both Active Context patterns A and +B — agents that read AGENTS.md discover the project's koni-docs adoption +via the pointer; they then read `.active-context.md` (Pattern B) or the +inline CLAUDE.md block (Pattern A) for the live snapshot. + +--- + +## 4. Trigger points (when to update Active Context) + +Same triggers for both patterns. Only the *file* the agent writes to differs. + +| # | Trigger | Action | +| -- | ---------------------- | ---------------------------------------------------- | +| T1 | Start a story | Add `🟡 US-X.Y <title>` to Active Stories | +| T2 | Close a story | Change `🟡 → ✅`, update Last Version | +| T3 | Start a sprint | Update Sprint ID | +| T4 | Add a LESSONS entry | Append LESSONS.md + add §N to Recent Lessons | +| T5 | Log a CONTEXT decision | Append CONTEXT.md + add D`<N>` to Recent Decisions | +| T6 | Add an env var | Update SETUP + DEPLOY + .env.example (RULE-11) | +| T7 | Pre-commit | Run full checklist, verify all doc layers consistent | + +**How to update**: +- **Pattern A**: target the block in `CLAUDE.md` between the + `<!-- koni-docs:auto-update -->` markers with `Edit`. +- **Pattern B**: target the same marker block inside `.active-context.md`. + The markers keep updates precise without touching surrounding content. + +--- + +## 5. Filled example — `.active-context.md` (Pattern B, recommended) + +```markdown +# Active Context — local + +## Local developer + +- **GitHub login**: saltict +- **Git name**: AnhMTV +- **Git email**: maithachvietanh@gmail.com +- **Workspace**: /Volumes/MacData/Workspace/AI/Koni-Skills +- **Current branch**: feat/us-2-2-wire-integration +- **Last updated**: 2026-05-27 + +## Project sprint context <!-- koni-docs:auto-update --> + +- **Sprint**: sprint-2026-W22 +- **Active Stories**: 🟡 US-1.2 Active-context split · 🟡 US-2.1 Bootstrap docs · 🟡 US-2.2 Wire integration · 🟡 US-2.3 VERSION+CHANGELOG seed +- **Recently Shipped (main)**: ✅ US-1.1 Koni-docs initial release (v0.1.0) +- **Last Version**: v0.1.0 +- **Recent Decisions**: D7 (active-context split), D6 (dogfood koni-docs), D4 (template split) +- **Recent Lessons**: §4 (`version_shipped` bare semver), §3 (fresh clone needs experimental_install), §1 (sync-script word boundaries) + +<!-- /koni-docs:auto-update --> +``` + +--- + +## 6. Filled example — `CLAUDE.md` Active Context (Pattern A, legacy) + +```markdown +## Koni-Docs Integration + +koni-docs: + plugins: [supabase, nextjs] + docs_path: docs/ + active_sprint: sprint-2026-W19 + version_file: VERSION + +## Active Context <!-- koni-docs:auto-update --> +- Sprint: sprint-2026-W19 +- Active Stories: ✅ US-3.7 Per-pod project view · ✅ US-3.8 Pod doc tabs · 🟡 US-1.8 Invitation email +- Last Version: v0.76.0 +- Recent Decisions: D60 (per-pod read-only project view), D59 (BMAD-agile activation) +- Recent Lessons: §29 (unstable_cache + workspace-scoped key), §28 (key= unmounts nested layouts) +<!-- /koni-docs:auto-update --> +``` diff --git a/.agents/skills/koni-docs/references/templates/lessons.md b/.agents/skills/koni-docs/references/templates/lessons.md new file mode 100644 index 00000000000..9d4682e2d73 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/lessons.md @@ -0,0 +1,86 @@ +# LESSONS.md — Lessons Learned Template + +> **File location**: `docs/LESSONS.md` +> +> **Use when**: A bug required a revert, a library had a non-obvious quirk, +> a pattern emerged that will recur, or something would save someone 30 +> minutes. +> +> **One rule above all others**: a lesson earns its keep if it saves the +> *next contributor* time. Delete entries that are no longer true (library +> upgraded, framework behavior changed) — stale advice is worse than no +> advice. + +--- + +## 1. Entry template + +```markdown +## <N>. <Short title — pattern or trap name> + +**What happened (vX.Y.Z → vX.Y+1.Z)**: <concrete incident — 2-4 sentences> + +**Why**: <root cause explanation — the mechanism, not just the symptom> + +**How to avoid**: +- Rule 1 +- Rule 2 +- Rule 3 + +**Pattern** (optional): +```code example if useful``` + +See [CONTEXT.md D<N>](CONTEXT.md) — link to the related decision entry if applicable. +``` + +### Finding the next entry number + +```bash +grep -n "^## [0-9]" docs/LESSONS.md | tail -5 +``` + +--- + +## 2. Maintenance rules + +- Number sequentially. Never reuse a number even if an entry is deleted. +- Delete entries only when the library was upgraded or behavior fundamentally + changed. Don't delete for stylistic reasons. +- Reference from commit messages: "Per LESSONS §N". +- Cross-reference from CONTEXT.md and CHANGELOG where relevant. +- A LESSONS entry that needs to *change* should be deleted + re-added with + a new number, not edited in place — preserves the citation contract. + +--- + +## 3. Filled example + +```markdown +## 1. `next build` catches what `tsc --noEmit` cannot + +**What happened (v0.19.1 → v0.19.2)**: Added `formatSessionTitle` to +`src/lib/sessions.ts` and imported it from a Client Component +(`chat-layout.tsx`). Local `tsc --noEmit` passed. Vercel's `next build` +errored: `next/headers` cannot be imported from a Client Component. Lost +~10 minutes of debug + a hotfix release. + +**Why**: TypeScript validates *types*. It does not enforce the React Server +Components / Client Components boundary or care about webpack's bundle +topology. Only `next build` does both. + +**How to avoid**: +- For any change that touches a module imported across the RSC/Client line, + run `next build` (not just `tsc --noEmit`) before pushing. +- If a Client Component needs a helper that lives next to server code, + move the helper to its own pure module (no `next/headers`, no + `getServerSupabase`). Re-export from the original module if needed for + backward compat with server callers. +- Sniff test: would `grep -r "next/headers"` traverse this import? If yes, + it can't go in a Client Component bundle. + +**Pattern**: see `src/lib/session-title.ts` — pure helpers isolated; +`src/lib/sessions.ts` re-exports for server callers. + +See [CONTEXT D45](CONTEXT.md) — the decision to add `next build` to the +pre-commit checklist on the back of this incident. +``` diff --git a/.agents/skills/koni-docs/references/templates/okr.md b/.agents/skills/koni-docs/references/templates/okr.md new file mode 100644 index 00000000000..9fcb7ed31f4 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/okr.md @@ -0,0 +1,150 @@ +# OKR — File-Native Quarterly Ledger Template + +> **File locations**: +> - `docs/okr/YYYY-QN.md` — one file per quarter +> - `docs/okr/README.md` — convention doc (this template's prose form) +> +> **Use when**: A project adopts the file-native OKR pattern (quarterly +> Markdown ledgers checked into the repo, parsed by a dashboard via the +> GitHub Contents API). Skip this template if the project tracks OKRs in +> a SaaS tool (Lattice, Mooncamp, etc.) instead. +> +> **Why file-native**: the repo is already the source of truth for code + +> specs + decisions. OKRs that live in the same repo get the same review, +> diff, and audit affordances. Permissions and check-in writes are +> managed via a small DB table (`okr_owners`), not by file-permission +> gymnastics. + +--- + +## 1. File-naming rule + +`YYYY-QN.md` exactly. Parser regex: `(\d{4})-Q([1-4])\.md$`. Examples: +`2026-Q1.md`, `2026-Q2.md`, `2027-Q3.md`. Other filenames are ignored. + +--- + +## 2. YAML schema (per quarter file) + +Each MD file contains one or more fenced ` ```yaml ... ``` ` code +blocks. Top-level keys are `team_<slug>`. Required fields: + +```yaml +team_<slug>: + function: <string> # required — sales / marketing / engineering + owner: <string> # required — IS the SQL `WHERE owner = '..'` literal + description: <string> # optional + KR_<slug>: # ≥1 KR per team + target: <number> # required + unit: <string> # required — USD / % / count / days / hours / NPS + measure: | # required — SELECT-only SQL against `data_tables.<table>` + SELECT ... + source: <string> # required — `data_tables.<table>` validation hint + description: <string> # optional + threshold: <number> # optional — yellow-zone boundary + stretch: <number> # optional — 10× / aspirational target +``` + +--- + +## 3. KR formula rules + +- **SELECT-only.** `INSERT / UPDATE / DELETE / DROP / CREATE / ALTER` + fail at parse time AND at the RPC level (belt + braces). +- **Single statement.** No semicolon-separated multi-statements. +- **End-exclusive boundaries.** Use `closed_at >= '2026-04-01' AND + closed_at < '2026-07-01'` for Q2 — half-open intervals avoid + double-counting on July 1. +- **Owner string matches YAML `owner`.** `WHERE owner = 'salesperson_1'` + expects the same literal as the team's `owner:` key. +- **Qualify `data_tables.<table>` in `source:`** so the validator can + look up the schema. Bare `data_tables` (no table) is rejected. + +--- + +## 4. Weekly notes + +The MD file ends with a single `## Weekly notes` H2. The check-in +Server Action appends new notes BENEATH this heading, chronological +(newest at bottom), never edits prior notes. + +Per-note format: + +```markdown +### YYYY-WNN / <owner_slug> +_Posted YYYY-MM-DDTHH:MM:SSZ by <Display Name> (<auth-user-uuid>)_ + +<note body, free prose, max 5000 chars> +``` + +ISO-8601 week numbering. Timestamps stored in UTC. + +--- + +## 5. Permissions (`okr_owners` table) + +Replaces any in-repo `owners.json` static file. Maps auth user UUID → +`<owner_slug>` string per workspace. Managed under +`/dashboard/settings/okr` (Owner + Admin only). RLS exposes each Member +their own row so the check-in form can resolve their identity at +submit time. + +--- + +## 6. Filled example — `docs/okr/2026-Q2.md` + +````markdown +# 2026-Q2 OKRs + +```yaml +team_sales_a: + function: sales + owner: salesperson_1 + description: Enterprise pipeline, US East coast + KR_pipeline_value: + target: 500000 + unit: USD + threshold: 350000 + stretch: 1000000 + source: data_tables.deals + description: Sum of `amount` on `stage IN ('proposal','negotiation','closed_won')` + measure: | + SELECT COALESCE(SUM(amount), 0) AS actual + FROM data_tables.deals + WHERE owner = 'salesperson_1' + AND closed_at >= '2026-04-01' + AND closed_at < '2026-07-01' + AND stage IN ('proposal','negotiation','closed_won'); + KR_logos_closed_won: + target: 6 + unit: count + source: data_tables.deals + measure: | + SELECT COUNT(*) AS actual + FROM data_tables.deals + WHERE owner = 'salesperson_1' + AND stage = 'closed_won' + AND closed_at >= '2026-04-01' + AND closed_at < '2026-07-01'; +``` + +## Weekly notes + +### 2026-W18 / salesperson_1 +_Posted 2026-05-04T10:30:00Z by Hieu Dao (00000000-0000-0000-0000-000000000001)_ + +Closed Acme ($85k) on Friday; two stalled deals re-engaged after the +pricing-page refresh shipped Wed. Next week: push Beta Corp through +procurement, expect close 2026-W19. +```` + +--- + +## 7. Cross-references + +- Settings UI: `/dashboard/settings/okr` lets workspaces choose the + repo + folder (`docs/okr/` by default). +- Dashboard: `/dashboard/okr/<year>/q/<N>` renders target-vs-actual + per KR with weekly check-in timeline. +- LESSONS entry recommended whenever a parser quirk bites — e.g. YAML + multi-line `measure:` indentation, end-exclusive boundary off-by-one. diff --git a/.agents/skills/koni-docs/references/templates/prd.md b/.agents/skills/koni-docs/references/templates/prd.md new file mode 100644 index 00000000000..2a75242a25a --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/prd.md @@ -0,0 +1,491 @@ +# PRD — Product Requirements Document Template + +> **File location**: `docs/PRD.md` +> +> **Use when**: User asks to create/update PRD, product spec, or after +> BMad produces PRD artifacts that need standardization. +> +> **Source**: Maps from BMad `prd.md` + extracts the Executive Summary +> from `brief.md`. The PRD is the canonical product specification — it +> absorbs the Brief's executive summary and expands it with detailed +> requirements, personas, and the epic/story index. +> +> **Scope boundary**: This PRD contains business requirements only. +> Implementation details (schema, framework names, API endpoint paths, +> infrastructure mechanisms) belong in `ARCHITECTURE.md` and downstream +> task descriptions. + +--- + +## 1. Heading convention (canonical — must be followed) + +**H2 headings in `PRD.md` MUST use label-only form, not numeric prefixes.** + +| ✅ Use | ❌ Do not use | +| ------------------------------- | -------------------------------------- | +| `## Functional Requirements` | `## 8. Functional Requirements (FR)` | +| `## Epics & User Stories` | `## 11. Epics & User Stories` | +| `## Executive Summary` | `## 1. Executive Summary` | + +Rationale: +- Labels are stable across reorderings — adding/removing a section + doesn't renumber every cross-reference downstream. +- Tooling (`koni-docs sync`, `koni-docs validate`) locates sections by + label (e.g. `Functional Requirements`) and falls back to legacy + numbered headings only for backwards-compat. New PRDs MUST emit the + label form. +- Cross-references in other docs use the label too: + `PRD Functional Requirements row FR-12`, not `PRD §8 FR-12`. + +--- + +## 2. Full template skeleton + +````markdown +--- +stepsCompleted: [] +inputDocuments: [] +classification: + projectType: '{SaaS B2B / Mobile B2C / Web3 / Marketplace / API Platform / ...}' + domain: '{primary business domain}' + complexity: '{Low / Medium / High}' + projectContext: '{Greenfield / Brownfield}' +workflowType: 'prd' +lastEdited: 'YYYY-MM-DD' +editHistory: + - date: 'YYYY-MM-DD' + changes: '{summary of what changed in this edit}' +--- + +# {Project Name} — Product Requirements Document + +**Version:** X.Y.Z +**Date:** YYYY-MM-DD +**Status:** {Live at URL / In development / Regenerated vX.Y} +**Dual-Audience:** Human stakeholders + LLM implementation agents + +> **Scope boundary:** This PRD contains business requirements only. Implementation +> details (schema, framework names, API endpoint paths, infrastructure mechanisms) +> belong in `architecture.md` and downstream task descriptions. + +--- + +## Executive Summary + +### Vision + +[1 paragraph: The long-term vision. What does this product become? +What's the north star?] + +**Positioning Statement:** {Product Name} — {one-line value proposition}. + +### What Makes It Special + +[2-3 paragraphs: Key differentiators vs alternatives. What strategic +advantages does this product have? Why this approach over others?] + +1. **{Advantage 1}** — {explanation} +2. **{Advantage 2}** — {explanation} +3. **{Advantage 3}** — {explanation} + +### Core Philosophies + +| # | Philosophy | Implication | +|---|-----------|-------------| +| 1 | {principle name} | {what this means in practice} | +| 2 | {principle name} | {what this means in practice} | +| 3 | {principle name} | {what this means in practice} | + +### Project Classification + +| Dimension | Value | +|-----------|-------| +| Project Type | {type} | +| Domain | {domain} | +| Complexity | {complexity} | +| Context | {greenfield / brownfield} | +| Target Users | {who this is for} | + +### Why Now + +- {Market trend or regulatory pressure driving urgency} +- {Technology enabler that makes this possible now} +- {Competitive window or execution advantage} + +--- + +## Success Criteria + +### User Success Metrics + +| ID | Metric | Target | Measurement | +|----|--------|--------|-------------| +| US-1 | {user-facing metric} | {quantified target} | {how measured} | +| US-2 | {user-facing metric} | {quantified target} | {how measured} | + +### Business Success Metrics + +| ID | Metric | Target | Measurement | +|----|--------|--------|-------------| +| BS-1 | {business metric} | {quantified target} | {how measured} | +| BS-2 | {business metric} | {quantified target} | {how measured} | + +### Technical Success Metrics + +| ID | Metric | Target | Measurement | +|----|--------|--------|-------------| +| TS-1 | {technical metric} | {quantified target} | {how measured} | +| TS-2 | {technical metric} | {quantified target} | {how measured} | + +### Aha Moment Targets + +1. **Aha Moment #1 — {Name}:** {What the user experiences. Why it's magical.} + Target: {quantified time or action}. +2. **Aha Moment #2 — {Name}:** {What the user experiences.} + Target: {quantified time or action}. + +--- + +## Product Scope + +### Phase 1: MVP — {MVP theme / goal} + +**Goal:** {1 sentence — what MVP proves} + +| Area | Included | Excluded | +|------|----------|----------| +| {Area 1} | {MVP scope} | {Deferred} | +| {Area 2} | {MVP scope} | {Deferred} | + +**MVP Exit Criteria:** +- {Measurable criterion} +- {Measurable criterion} + +### Phase 2: {Beta / Growth} — {Phase theme} + +**Goal:** {1 sentence — what this phase achieves} + +| Area | Additions | +|------|-----------| +| {Area 1} | {What's added beyond MVP} | +| {Area 2} | {What's added beyond MVP} | + +**{Phase 2} Exit Criteria:** +- {Measurable criterion} +- {Measurable criterion} + +### Phase 3: {Release / Scale} — {Phase theme} + +**Goal:** {1 sentence — what this phase achieves} + +| Area | Additions | +|------|-----------| +| {Area 1} | {What's added beyond previous phase} | +| {Area 2} | {What's added beyond previous phase} | + +### Scope Boundaries (All Phases) + +**Permanently Out of Scope:** +- {Feature / capability that will never be in this product} +- {Rationale — brief} + +--- + +## User Journeys + +### Journey 1: {Journey Name} ({Primary Persona}) + +**Persona:** {1-2 sentences describing who this is and what they need} + +**Trigger:** {What prompts this journey — an event, a notification, a task} + +1. **{Step name}:** {What the user does. Key interaction detail.} +2. **{Step name}:** {What the user does. Key interaction detail.} +3. **{Step name}:** {What the user does. Key interaction detail.} +4. **{Step name}:** {What the user does. Key interaction detail.} +5. **Outcome:** {What the user achieves at the end} + +**Success Metric:** {Quantified measure of journey success} + +### Journey 2: {Journey Name} ({Persona}) + +[... repeat pattern for 3-7 journeys covering all major user flows] + +--- + +## Personas + +### P1 — {Primary persona name} ({user segment}) +- **Trigger**: {what prompts them to seek a solution} +- **Pain**: {concrete pain points — what hurts today} +- **Uses the product**: {how they interact, key jobs-to-be-done} +- **Won't pay if**: {deal-breakers — what would make them walk away} + +### P2 — {Secondary persona name} ({user segment}) +- **Trigger**: {what prompts them} +- **Pain**: {concrete pain points} +- **Uses**: {how they interact} +- **Won't pay if**: {deal-breakers} + +--- + +## Background & Strategic Decisions + +[Key decisions that shaped the product direction. Each decision gets +an ID, description, date, and rationale. Maps from BMad Architecture +ADs and CONTEXT.md entries.] + +| ID | Decision | Date | Rationale | +|----|----------|------|-----------| +| A1 | {decision title} | YYYY-MM-DD | {1-2 sentence rationale} | +| A2 | {decision title} | YYYY-MM-DD | {1-2 sentence rationale} | + +--- + +## Domain-Specific Requirements + +> **Note:** This section is **optional**. Include it ONLY when the product domain +> has specific compliance, regulatory, or technical constraints (crypto, healthcare, +> fintech, legal, etc.). For generic SaaS/web apps, omit this entire section. + +### {Domain Category 1} + +| ID | Requirement | Rationale | +|----|-------------|-----------| +| D-1 | {domain-specific requirement} | {why this matters in this domain} | + +### {Domain Category 2} (if applicable) + +| ID | Requirement | Rationale | +|----|-------------|-----------| +| D-2 | {requirement} | {rationale} | + +--- + +## Functional Requirements + +| ID | Requirement | Priority | Status | Epic | +|----|-------------|----------|--------|------| +| FR-1 | {requirement description} | P0/P1/P2/P3 | {status} (vX.Y.Z) | EPIC-N | + +Priority: `P0` = must-ship/blocking, `P1` = high, `P2` = medium, `P3` = nice-to-have. +Status: `✅ shipped (vX.Y.Z)` / `🚧 in-progress` / `📋 backlog` / `⏪ reverted in vX.Y.Z` / `🗑️ deprecated vX.Y.Z`. + +--- + +## Non-Functional Requirements + +| ID | Requirement | Target | Status | +|----|-------------|--------|--------| +| NFR-1 | {requirement} | {measurable target} | {status} | + +--- + +## Glossary + +> **Note:** This section is **optional**. Include it when the domain has specialized +> terminology that both human stakeholders and AI agents need clarified. + +| Term | Definition | +|------|------------| +| {Term 1} | {1-2 sentence definition in business terms} | +| {Term 2} | {1-2 sentence definition in business terms} | + +--- + +## Epics & User Stories + +### EPIC-1: {Epic Title} + +**Goal**: {1 sentence — the user outcome this epic delivers} + +**Status**: {📋 backlog / 🚧 in-progress / ✅ Done (vX.Y.Z)} + +| Story | Title | Status | Version | +|-------|-------|--------|---------| +| [US-1.1](../sprints/stories/US-1.1-<slug>.md) | {title} | {status} | {version} | +| [US-1.2](../sprints/stories/US-1.2-<slug>.md) | {title} | {status} | {version} | + +### EPIC-2: {Epic Title} + +**Goal**: {1 sentence} + +**Status**: {status} + +| Story | Title | Status | Version | +|-------|-------|--------|---------| +| [US-2.1](../sprints/stories/US-2.1-<slug>.md) | {title} | {status} | {version} | + +[Repeat for all epics] +```` + +--- + +## 3. Updating PRD.md + +- **When**: After BMad produces PRD artifacts, when scope changes, or when + a new epic/story is added. +- **How**: Edit the relevant section in-place. Update `lastEdited` and + `editHistory` in frontmatter. +- **Executive Summary / Success Criteria / Product Scope / User Journeys / + Personas / Background**: Updated during initial PRD creation from BMad + output. Rarely change after. +- **Functional Requirements**: Updated every time a story ships or scope + changes (add rows, update status). +- **Epics & User Stories**: Updated when stories are created, status + changes, or new epics are added. +- **Cross-reference**: Link to `BRIEF.md` from the Executive Summary + header. Link to `ARCHITECTURE.md` from Background & Strategic Decisions. + Link to `CONTEXT.md` for individual decisions. + +--- + +## 4. Functional Requirements — row format + +```markdown +| FR-N | <Requirement description> | P0/P1/P2/P3 | 🚧 In progress / ✅ shipped (vX.Y.Z) / 📋 Backlog | EPIC-N | +``` + +Priority: `P0` = must-ship/blocking, `P1` = high, `P2` = medium, `P3` = nice-to-have. + +--- + +## 5. Story entry in Epics & User Stories + +```markdown +### US-X.Y — <Story title> + +**Status**: 🚧 In progress / ✅ Done (vX.Y.Z) / 📋 Backlog / ⏪ Reverted in vX.Y.Z + +**Epic**: EPIC-N + +**Goal**: <one sentence — what user outcome this delivers> + +**Acceptance criteria**: +- [ ] <criterion 1 — use "Given/When/Then" or declarative> +- [ ] <criterion 2> + +**Story file**: [docs/sprints/stories/US-X.Y-<slug>.md](sprints/stories/US-X.Y-<slug>.md) +``` + +### Removal / revert entry + +```markdown +**Status**: ⏪ Reverted in vX.Y.Z — see CONTEXT D<N> + +<One sentence why it was removed. Link to the CONTEXT entry.> +``` + +--- + +## 6. Epics & Stories Index + +This index lives in the `Epics & User Stories` section. Each story entry +links to its canonical story file in `docs/sprints/stories/`. The index +is updated when stories are created or their status changes. + +```markdown +## Epics & User Stories + +### EPIC-1 — {Epic Title} +| Story | Title | Status | Version | +|-------|-------|--------|---------| +| US-1.1 | {title} | 📋 Backlog | — | +| US-1.2 | {title} | 🚧 in-progress | — | + +### EPIC-2 — {Epic Title} +| Story | Title | Status | Version | +|-------|-------|--------|---------| +| US-2.1 | {title} | 📋 Backlog | — | +``` + +> **Sync rule**: When `npx koni-docs sync` runs, it propagates story +> status changes to this index and to the `Functional Requirements` +> table. The index is the single source of truth for "what stories exist." + +--- + +## 7. Filled example (Executive Summary + Epics & User Stories snippet) + +> The full PRD spans the sections listed above and grows to 1500+ lines +> on a mature project. The snippet below shows the *shape* of the +> Executive Summary and the Epics & User Stories index — the two +> sections most often updated. + +```markdown +# Koni-ERP-02 — Product Requirements Document + +> **Version**: 0.76.0 — see [VERSION](../VERSION) for the live value. +> Notable additions since v0.20.0: +> - Workspace → Pod → Foundation roadmap landed (v0.21–v0.25) +> - Notion connector reshaped via generic `data_tables` JSONB abstraction (v0.28) +> - Dashboard Studio MVP — pin chat answers as charts (v0.33+) +> - Per-pod product context surface — 6 tabs per pod (v0.44–v0.47) +> +> See [CHANGELOG.md](CHANGELOG.md) for the complete release history. +> **Status**: Live at https://erp.koni.studio. + +--- + +## Executive Summary + +**Koni ERP** = a self-serve AI data layer for tech teams under 1000 +employees — a chat box that answers business questions in plain English, +drawing data from the SaaS tools the team already uses (GitHub, Linear, +Stripe). + +**Wedge V1**: GitHub + Linear for founders / Heads of Engineering. NL→SQL +on top of ingested data, multi-tenant with Row-Level Security from day one. + +**Differentiators**: +- **Self-serve setup** (vs Glean's 8-week procurement cycle) +- **BYOK multi-provider** — Anthropic / OpenAI / Gemini / Qwen, no lock-in +- **Self-host friendly** (Supabase + Vercel + Inngest, all OSS-equivalent) + +### Project Classification + +| Dimension | Value | +|-----------|-------| +| Project Type | SaaS B2B | +| Domain | Internal analytics / ERP | +| Complexity | High | +| Context | Greenfield | +| Target Users | Tech-comfortable founders, Heads of Engineering / Data | + +--- + +## Epics & User Stories + +### EPIC-1 — Authentication, Workspaces, Permissions +| Story | Title | Status | Version | +|-------|-------|--------|---------| +| US-1.1 | Auth + auto-create workspace on sign-up | ✅ done | v0.2.0 | +| US-1.4 | Multi-workspace per user (1 user → N workspaces) | ✅ done | v0.21.0 | +| US-1.7 | Capability gating (Owner / Admin / Member) | ✅ done | v0.54.0 | +| US-1.8 | Invitation email auto-send via Resend | 🚧 in-progress | — | + +### EPIC-3 — GitHub Connector +| Story | Title | Status | Version | +|-------|-------|--------|---------| +| US-3.1 | Connect GitHub OAuth, sync repos/PRs/commits/issues | ✅ done | v0.5.0 | +| US-3.7 | Per-pod project management view (Projects v2-style) | ✅ done | v0.45.1 | +| US-3.10 | Project Table polish — full-width + group-by | ✅ done | v0.48.0 | +``` + +--- + +## 8. Migrating an older PRD with numbered headings + +`koni-docs sync` and `koni-docs validate` accept legacy numbered PRDs +(`## 8. Functional Requirements (FR)`, `## 11. Epics & User Stories`) +through a label-first / number-fallback lookup, so existing projects +keep working. To migrate to the canonical label form: + +1. Strip the leading `N.` from every H2 in `docs/PRD.md` + (e.g. `## 8. Functional Requirements (FR)` → `## Functional Requirements`, + `## 11. Epics & User Stories` → `## Epics & User Stories`). +2. Replace cross-references in sibling docs: + `PRD §8` → `PRD Functional Requirements`, + `PRD §11` → `PRD Epics & User Stories`. +3. Re-run `npx koni-docs sync --docs-path docs/`. If warnings are gone, + the migration is clean. diff --git a/.agents/skills/koni-docs/references/templates/setup.md b/.agents/skills/koni-docs/references/templates/setup.md new file mode 100644 index 00000000000..0cf223f52f2 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/setup.md @@ -0,0 +1,106 @@ +# SETUP.md + DEPLOY.md + .env.example — Template + +> **File locations**: +> - `docs/SETUP.md` — local dev environment (clone → `npm run dev`) +> - `DEPLOY.md` — production runbook (repo root) +> - `.env.example` — env var template (repo root) +> +> **Use when**: User asks to add a new env var, document a setup step, +> or update a deploy procedure. +> +> **One rule above all others (RULE-11)**: a new env var lands in ALL +> THREE files in the same commit. Skipping one of them causes silent +> deploy failures and onboarding friction. Use the checklist in §3. + +--- + +## 1. SETUP.md env block format + +```markdown +# <Category name> (added in vX.Y.Z) +# <What it does — 1 line> +NEW_ENV_VAR=<example_value_or_instructions> +``` + +Group env vars by category in SETUP.md (e.g. "Supabase", "OAuth — GitHub", +"BYOK encryption"). Within a category, order by criticality (required → optional). + +--- + +## 2. .env.example format + +```bash +# <Category / Feature name> (added in vX.Y.Z) +# <One sentence: what this controls, where to get the value> +NEW_ENV_VAR=<placeholder_or_description> +``` + +`.env.example` is the single source of truth for "what env vars exist". +Every key in `.env.local` must have a corresponding line in `.env.example` +— with a placeholder, never a real value. + +--- + +## 3. Env var update checklist (RULE-11) + +``` +[ ] docs/SETUP.md — add to the .env.local example block + one-line description +[ ] DEPLOY.md — add to the production env vars table +[ ] .env.example — add the key with placeholder value +``` + +Reviewers reject env-var commits that miss any of these three files. + +--- + +## 4. Filled example — SETUP.md env block + +```markdown +# Master encryption key (BYOK feature — used for both LLM API keys +# and OAuth tokens since v0.20.0). +# Generate: openssl rand -base64 32 +ANTHROPIC_KEY_ENCRYPTION_SECRET=<output_of_openssl> + +# OAuth state HMAC secret (added v0.20.0). Signs the `state` param on +# the GitHub + Linear OAuth flows. Separate from SUPABASE_SERVICE_ROLE_KEY +# so a state-signing leak isn't a DB compromise. v0.20.0 falls back to +# SUPABASE_SERVICE_ROLE_KEY if unset (rotation window); v0.21+ will hard- +# require this. Generate: openssl rand -base64 32 +OAUTH_STATE_SECRET=<output_of_openssl> +``` + +--- + +## 5. Filled example — .env.example + +```bash +# BYOK encryption (added v0.20.0) — encrypts LLM keys + OAuth tokens at rest. +# Generate locally: `openssl rand -base64 32` +ANTHROPIC_KEY_ENCRYPTION_SECRET= + +# OAuth state HMAC secret (added v0.20.0) — signs the `state` param. +OAUTH_STATE_SECRET= + +# GitHub OAuth — only needed to test the connect flow locally. +# Create at https://github.com/settings/developers +# Callback URL: http://localhost:3000/api/oauth/github/callback +GITHUB_OAUTH_CLIENT_ID= +GITHUB_OAUTH_CLIENT_SECRET= +``` + +--- + +## 6. DEPLOY.md production env vars table + +In `DEPLOY.md`, env vars live in a table that mirrors the production +secret store (Vercel / Fly.io / k8s Secret). Each row names where to +configure the value, not just what it is. + +```markdown +| Env var | Required | Source | Notes | +|---|---|---|---| +| `ANTHROPIC_KEY_ENCRYPTION_SECRET` | Required | Generated once per env via `openssl rand -base64 32` | Rotation procedure §<N> below | +| `OAUTH_STATE_SECRET` | Required | Generated once per env via `openssl rand -base64 32` | Falls back to `SUPABASE_SERVICE_ROLE_KEY` if unset (v0.20.0 only) | +| `GITHUB_OAUTH_CLIENT_ID` | Required | GitHub → Settings → Developer → OAuth Apps | Callback `https://erp.koni.studio/api/oauth/github/callback` | +| `GITHUB_OAUTH_CLIENT_SECRET` | Required | Same place; rotate every 12 months | Vercel env: production only | +``` diff --git a/.agents/skills/koni-docs/references/templates/sprint.md b/.agents/skills/koni-docs/references/templates/sprint.md new file mode 100644 index 00000000000..967d9602a0d --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/sprint.md @@ -0,0 +1,236 @@ +# Sprint File — Template + +> **File location**: `docs/sprints/sprint-YYYY-WNN.md` +> +> **Use when**: User asks to create a new sprint file, open a sprint, or +> close a sprint with a retrospective. ISO-8601 week numbering — `WNN` is +> two digits (`W04`, `W19`, `W47`). +> +> **One rule above all others**: AC + Tasks live inside each *story file*, +> not in the sprint file. The sprint file is a planning surface that lists +> committed stories at a glance and captures the goal + retrospective. If +> you find yourself copy-pasting AC into the sprint file, stop. + +--- + +## 1. Template skeleton + +```markdown +--- +id: sprint-YYYY-WNN +status: planned # planned | in-progress | closed +start: YYYY-MM-DD +end: YYYY-MM-DD +goal: "<Sprint goal — one sentence naming the deliverable, not the activity>" +--- + +## Sprint scope + +**Canonical 6-column shape** — most projects use this: + +| US | Title | Epic | Pri | Points | Status | Story file | +|---|---|---|---|---|---|---| +| US-X.Y | <title> | EPIC-X | P1 | 5 | 🚧 in-progress | [link](stories/US-X.Y-<slug>.md) | + +**Extended 7-column shape with `Carry` column** — Koni-Finance-Final pattern, useful for multi-week tracking: + +| US | Title | Epic | Pri | Points | Status | Carry | Story file | +|---|---|---|---|---|---|---|---| +| US-X.Y | <title> | EPIC-X | P1 | 5 | 🚧 in-progress | from W<N> | [link](stories/US-X.Y-<slug>.md) | + +`Carry` column values: +- `from W<N>` — carries from a prior sprint (most common case) +- `new` — first appearance in this sprint +- `substrate` — foundational story unblocking multiple others +- Empty cell — fresh in-scope story (when project mixes annotation styles) +- Descriptive prose — `from W20 → W21 → W22` for chained carries + +`agile-sync-up.mjs` writes the **Status** cell by header name, so it works on both 6-col and 7-col shapes without configuration. + +### Inline title annotations (senti_quant pattern) + +For mid-sprint scope changes, append parenthetical timestamps + versions directly to the Title cell: + +| Annotation | Meaning | +|---|---| +| `_(added 2026-05-25)_` | Story added to scope mid-sprint | +| `_(closed mid-sprint v0.1.12)_` | Story landed mid-sprint at the named version | +| `_(added + closed mid-sprint 2026-05-26)_` | Both — landed same day it was added | +| `_(carry W21←W20←W19)_` | Multi-sprint carry chain | +| `(EPIC-34, [D44](../CONTEXT.md))` | Decision reference embedded | + +These markers are prose only — sync scripts ignore them. They give reviewers an audit trail in the sprint file itself, complementary to the `Carry` column. + +> **Convention**: AC + Tasks live inside each story file. This sprint file lists planned +> stories at a glance only. Design + decision docs cross-linked at the bottom. + +## Sprint goal recap + +<1-2 paragraphs: why these stories were chosen for this window, any +dependencies or sequencing constraints. State the deliverable cut +(what's in, what's deferred), and why-this-now (which prior decision or +incident motivated the timing).> + +## Phased plan + +<Optional but recommended for sprints with 4+ stories. Sequence the +work into phases by dependency, not by calendar. State the rough day- +budget per phase against the sprint length so reviewers see whether +the scope fits.> + +1. **Phase 1 — <name>** (~<N> days): <what ships> +2. **Phase 2 — <name>** (~<N> days): <what ships> +3. **Phase N — QA + docs** (~<N> day): /qa-only, design-review, CHANGELOG, story flip to done, run sync scripts + +## Why <US-X.Y> in W<N> *(optional)* + +<For sprints with a single load-bearing mid-sprint commitment, narrate +why that story landed THIS week. 1-2 paragraphs. Reviewers reading the +sprint file 3 months later need this — story files don't capture +sprint-level timing. senti_quant pattern. Skip when the sprint goal +itself answers the question.> + +## Parked / deferred from W<N-1> *(optional, recommended when carry-over > 20%)* + +<Explicit carry-over audit at sprint open. Group prior-sprint +in-progress stories by what happened to them this week. senti_quant +pattern.> + +- ✅ **Closed in W<N-1>**: <story> — landed at vX.Y.Z +- 🚧 **Carried into W<N>**: <story> — <why still open> +- 🟢 **Carried into W<N> as `ready`**: <story> — scope-locked, awaiting pickup +- 🗑️ **Retired in W<N-1>**: <story> — superseded by <other story> / scope cancelled + +## Closed mid-sprint W<N> *(optional, filled as stories land)* + +<Date + version per mid-sprint landing. Lets reviewers reconstruct +exact ship sequence without crawling git log. senti_quant pattern.> + +- ✅ **2026-05-26** — US-X.Y, US-X.Z shipped in v0.1.12 (<one-line summary>) +- ✅ **2026-05-27** — US-A.B shipped in v0.1.13 + +## Risks & dependencies *(optional, recommended for sprints with cross-team blockers)* + +<Per-risk bullet with a mitigation. Not just dependency links — name +the mitigation that lets the sprint close even if the risk fires. +senti_quant pattern.> + +- **<Risk name>** — *Impact*: <what breaks if risk fires>. *Mitigation*: <what you do instead>. *Owner*: @<github-login>. + +## Per-Epic Retrospective + +<BMad pattern: each epic gets a lightweight retro. Optional per epic — flag as +"done" when completed, "optional" if skipped.> + +| Epic | Retro Status | Notes | +|------|-------------|-------| +| EPIC-X | optional | | +| EPIC-Y | done | See [retro notes](#retrospective) | + +## Retrospective + +<Filled on sprint close. Leave empty until close.> + +### What went well + +- TBD + +### What didn't + +- TBD + +### Followups + +- TBD + +## Carry-overs to W<N+1> *(optional, filled at sprint close)* + +<End-of-sprint accounting of what didn't close — each row names a +reason + sprint of origin so the receiving W<N+1> knows the history. +Koni-Finance-Final pattern.> + +| US | Reason | Sprint of origin | +|---|---|---| +| US-X.Y | <why still open — e.g., "blocked on US-8.0 substrate merge"> | sprint-2026-W<N-K> | + +## Cross-references + +- [Story US-X.Y](stories/US-X.Y-<slug>.md) — canonical AC + Tasks +- [Design doc](../design/US-X.Y-<slug>-design.md) — full architecture + decision log (if applicable) +- [EPIC-X](epics/EPIC-X.md) — parent epic +- [PRD §N](../PRD.md) — spec +- [CONTEXT D<N>](../CONTEXT.md) — motivating decision (if applicable) +- [README.md](README.md) — sprint schema + naming convention +- [STATUS.md](STATUS.md) — auto-rendered kanban board +``` + +--- + +## 2. Sprint lifecycle + +- **planned** — sprint file created, scope locked, no stories started yet. + Status flips when the first story moves to `in-progress`. +- **in-progress** — work has begun. Story status mirrors reality; sync via + `agile-sync-up.mjs`. +- **closed** — every story is `done` or `removed`. Retrospective filled. + `STATUS.md` regenerated. Sprint file may be moved to `docs/sprints/archive/`. + +--- + +## 3. Filled example (condensed) + +```markdown +--- +id: sprint-2026-W19 +status: in-progress +start: 2026-05-04 +end: 2026-05-10 +goal: "Ship US-3.7..3.12 (pod project surface — v0.45-v0.50) + Phase 1 permissions tier US-1.7 + US-1.8 (v0.54.0)" +--- + +## Sprint scope + +| US | Title | Epic | Pri | Points | Status | Story file | +|---|---|---|---|---|---|---| +| US-3.7 | Per-pod project management view (Projects v2-style) | EPIC-3 | P1 | 13 | ✅ done (v0.45.1) | [stories/US-3.7-pod-project-management.md](stories/US-3.7-pod-project-management.md) | +| US-3.8 | Per-pod doc tabs (PRD / Context / Lessons / Changelog) | EPIC-3 | P1 | 8 | ✅ done (v0.46.0) | [stories/US-3.8-pod-doc-tabs.md](stories/US-3.8-pod-doc-tabs.md) | +| US-3.10 | Project Table polish — group by EPIC/Sprint | EPIC-3 | P1 | 5 | ✅ done (v0.48.0) | [stories/US-3.10-project-table-polish.md](stories/US-3.10-project-table-polish.md) | +| US-1.7 | Capability gating Owner/Admin/Member | EPIC-1 | P0 | 8 | ✅ done (v0.54.0) | [stories/US-1.7-capability-gating.md](stories/US-1.7-capability-gating.md) | +| US-1.8 | Invitation email auto-send via Resend | EPIC-1 | P0 | 5 | 🚧 in-progress | [stories/US-1.8-invitation-email-resend.md](stories/US-1.8-invitation-email-resend.md) | + +## Sprint goal recap + +Ship V1 read-only per-pod project view. Reads `Docs/sprints/` from each +repo in the pod's `selected_repo_ids` via Octokit, parses YAML frontmatter +into typed Story / Epic / Sprint records, caches with `unstable_cache` +keyed `(workspace_id, pod_slug)`, renders Table + Board views. + +**Deliverable cut**: read-only. No drag-write, no inline edit, no GitHub +Projects v2 API sync. Defer to V2 once V1 demand is validated. + +**Why this sprint, why now**: the 2026-05-02 BMAD-agile activation +(CONTEXT D59) proved the agile-MD convention works for the Koni dev team. +Extending it as a UI inside Koni so other pods inherit the pattern is the +next logical step — minimal new code, zero data migration. + +## Phased plan + +1. **Phase 1 — Backend foundation** (~1 day): mig 0028, types, frontmatter parser, fetch + cache layer. +2. **Phase 2 — API + page scaffolding** (~0.5 day): GET / refresh route, page.tsx, shell. +3. **Phase 3 — Table view** (~1 day): shadcn Table, sortable, click-row external link. +4. **Phase 4 — Board view** (~1 day): kanban columns, status grouping, mobile stack. +5. **Phase 5 — Settings UI for path config** (~0.5 day): pod drawer field, validation. +6. **Phase 6 — Sync hook integration** (~0.5 day): extend `syncOneSource` to revalidateTag on every sync. +7. **Phase 7 — QA + docs** (~0.5 day): `/qa-only`, `/design-review`, CHANGELOG, CONTEXT D60, story flip to done. + +## Retrospective + +(filled on sprint close — 2026-05-10 EOD) + +## Cross-references + +- [Story US-3.7](stories/US-3.7-pod-project-management.md) — canonical AC + Tasks +- [Design doc](../design/US-3.7-pod-project-management-design.md) — full architecture +- [EPIC-3 GitHub connector](epics/EPIC-3.md) — parent epic +- [CONTEXT D59](../CONTEXT.md) — BMAD-agile activation rationale +``` diff --git a/.agents/skills/koni-docs/references/templates/story.md b/.agents/skills/koni-docs/references/templates/story.md new file mode 100644 index 00000000000..400b06f920a --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/story.md @@ -0,0 +1,566 @@ +# Story File — Full Template + +> **File location**: `docs/sprints/stories/US-X.Y-<slug>.md` +> +> **Use when**: User asks to create or update a user story, when a planning +> tool (BMad / GStack / Superpowers) produces a story-shaped artifact, or +> when stub-creating a story before starting implementation (per SKILL.md §3a). +> +> **One rule above all others**: a story is the *contract* between the epic's +> functional requirement and the code that ships. It owns its own acceptance +> criteria, task breakdown, and verification commands. It does NOT own +> cross-cutting concerns (those belong to the epic) or system-wide invariants +> (those belong to ARCHITECTURE.md + CONTEXT.md). + +--- + +## 1. Section index — what's required vs optional + +Stories differ wildly in size. A 1-point chore can skip most sections; a +13-point cross-cutting refactor needs them all. Reviewers should be able to +tell *story size* from which sections are filled in. + +| § | Section | Tiny (1-2 pts) | Standard (3-5 pts) | Large (8-13 pts) | +| --- | ------------------------------------ | -------------- | ------------------ | ---------------- | +| 1 | Frontmatter | required | required | required | +| 2 | Goal | required | required | required | +| 3 | Background | optional | required | required | +| 4 | Acceptance criteria | required | required | required | +| 5 | Tasks | required | required | required | +| 6 | Dev notes — Architecture constraints | optional | required | required | +| 7 | Dev notes — Cross-story dependencies | optional | recommended | required | +| 8 | Dev notes — Performance budget | optional | optional | required (if perf-sensitive) | +| 9 | Dev notes — What we did NOT do | optional | optional | recommended | +| 10 | Dev notes — References | required | required | required | +| 11 | Verification commands | optional | recommended | required | +| 12 | Changelog entry | required | required | required | +| 13 | Implementation notes | filled during impl | filled during impl | filled during impl | +| 14 | Files modified | filled during impl | filled during impl | filled during impl | +| 15 | Cross-references | optional | recommended | required | + +--- + +## 2. Full template skeleton + +````markdown +--- +id: US-X.Y +title: "<Story title>" +epic: EPIC-X +status: backlog # backlog | ready | in-progress | review | done | blocked | deprecated +priority: P1 # P0 | P1 | P2 | P3 +points: 5 # Fibonacci: 1 / 2 / 3 / 5 / 8 / 13 — see SKILL.md §3a-bis for scale + consult /sales-engineer or /marketing-ops on non-eng work +sprint: # nullable while backlog; set to sprint-YYYY-WNN when committed +version_shipped: # MANDATORY (RULE-16) when status → done; bare semver e.g. `0.3.1`, NEVER `v0.3.1` +prd_ref: [FR-N] # PRD Functional Requirements this story materializes — list of bare IDs only (RULE-17). FR-N / NFR-N here; AD-N goes in arch_ref. See references/frontmatter-spec.md. +arch_ref: [AD-N] # OPTIONAL — ARCHITECTURE.md Architecture Decisions this story materializes (list of AD-N). Omit if none. +depends_on: [US-X.Y] # OPTIONAL — other stories whose artifacts this story consumes (list of US-X.Y). Omit if none. Cross-story narrative belongs in §7. +assignee: # MANDATORY (RULE-15): commit AUTHOR — `git log -1 --format=%an <sha>`, NOT the session user (`gh api user`) +commit: # full SHA of landing commit (set at pre-commit). Multi-commit story: comma-separated SHAs, e.g. `47b4383, a76477c, 9a701de` +created: YYYY-MM-DD +updated: YYYY-MM-DD +external_deps: # optional list of third-party systems / partners / legal reviews this story waits on. Example: [payment_gateway, resend_api, legal_review, sales_navigator_license]. Populate when story waits on calendar-time outside dev control — these stories are the most commonly undersized (SKILL.md §3a-bis). +--- + +## Story refresh — YYYY-MM-DD *(optional, recurring)* + +<For mid-implementation re-scopes that don't warrant a brand-new story. +Append a dated block at the top of the body (after frontmatter, before +Goal) each time the story is re-scanned against new context. The +original Goal / AC numbering stays immutable; the refresh block records +what changed since the prior refresh and why. Koni-Finance-Final pattern. + +Skip entirely on first authoring — only add when the story actually +needs a refresh.> + +After re-scanning the source on YYYY-MM-DD against branch `<branch>`, +the following decisions were locked into this story: + +- <decision 1> +- <decision 2> + +## Goal + +<1 paragraph: what user outcome this delivers and why it matters now. +Maps to the BMad "So that..." clause — articulate the value, not the +mechanism. For platform / infrastructure stories, name the downstream +stories that get to *stop worrying about* what this delivers.> + +## Background + +<2-4 paragraphs: why now, what alternatives were considered, surrounding +context that informs the design. This is the section a future maintainer +reads to understand *why this story exists* — without it, the AC list +looks arbitrary. + +Cite: +- The PRD FR / AD this story materializes. +- The CONTEXT.md decision (D<N>) that authorized this approach, if any. +- The LESSONS.md entry that motivated this story, if any. +- Sibling stories this story is a continuation of, or correction to. + +For stories that are *scope-reduced replacements* of an earlier plan, +state explicitly: what was the original plan, what changed, why.> + +## Acceptance criteria + +<Use Given/When/Then (BMad standard) for behavioral ACs. Use declarative +form for data / constraint / schema ACs. Each AC must be: +- **Independently testable** — a single failing AC fails the story. +- **Bound to a verification command** in §11 — every AC has a runnable check. +- **Edge-case aware** — include error handling, empty states, race conditions. + +Number ACs `AC-N`. Story files NEVER renumber ACs after creation — even if +one is deleted, the gap stays so cross-references survive.> + +- [ ] **AC-1** — **Given** {precondition}, **When** {action}, **Then** {expected outcome} **And** {additional criteria} +- [ ] **AC-2** — <declarative criterion — for schema, constraints, or static properties> +- [ ] **AC-3** — **Given** {edge case precondition}, **When** {action}, **Then** {expected error handling} + +> **AC quality bar**: cover the unhappy path. If this story has a design +> spec, cross-reference relevant interaction states (empty, loading, +> error, populated). For storage-touching stories, an AC must cover RLS +> isolation. For audit-touching stories, an AC must cover the +> transactional contract (FR-93). + +## Tasks + +<Tasks are grouped with AC cross-references (BMad pattern). Each task +references which AC(s) it fulfills. Sub-tasks are indented and reference +specific file paths or symbol names. Prefer 4-8 top-level tasks per story; +if you have more than 10, the story is too big — split it.> + +- [ ] **TASK-X.Y.1** — <action> — <rationale> (AC: 1, 2) + - [ ] Subtask X.Y.1.1 — <specific sub-action with file path> + - [ ] Subtask X.Y.1.2 — <specific sub-action with file path> +- [ ] **TASK-X.Y.2** — <action> — <rationale> (AC: 3) + +## Dev notes + +### Architecture constraints + +<Relevant Architecture Decisions (AD-N) and patterns that constrain the +implementation. Reference ARCHITECTURE.md sections and CONTEXT.md entries +by ID. State here *what choices are foreclosed* — that prevents reviewers +asking "why didn't you do it differently".> + +- [AD-N](../../ARCHITECTURE.md#architecture-decisions): <one-line summary of the decision and how it applies to this story> +- This story does NOT introduce new AD entries. <Or: "Introduces AD-N — see CONTEXT D<N>".> + +### Cross-story dependencies + +<Bullets in three groups: +- **Builds on:** stories whose output this story consumes. +- **Required by:** stories that cannot start until this lands. +- **Sibling:** stories shipping concurrently that touch shared code. + +For each dependency, name the *artifact* (file, function, schema, fixture) +the dependency is on — not just the story ID.> + +- Builds on [US-X.Y](US-X.Y-<slug>.md) — uses `<helper>` from `<file path>`. +- Required by [US-X.Z](US-X.Z-<slug>.md) — that story extends `<entity>` added here. +- Sibling [US-X.W](US-X.W-<slug>.md) — both touch `<file path>`; coordinate review. + +### Performance budget + +<Only include for stories where the epic publishes a performance budget +(see epic §14) OR where the story sits on a hot path. State the budget, +state how it is measured, and link to the test that defends it.> + +- <Operation> overhead: ≤ <p95 number> ms p95. +- Measured by <test file> using <approach>. +- Story PR description must explicitly confirm this budget is met. + +### What we explicitly did NOT do + +<For stories that are scope-reduced or take a deliberate "do less now" +posture. List the alternatives that were considered and rejected, with the +trigger that would justify revisiting. This section prevents reviewers +from re-litigating the scope decision.> + +- No <abstraction> — the registry stays light. Trigger to revisit: <number / event>. +- No <feature> — deferred to <story / phase>. Reason: <one sentence>. + +### References + +<Cite every technical reference with source path and section. Reviewers +chase these — a broken or missing reference is a reject signal.> + +- [Issue #<N>](<URL>) +- [Source: PRD §8 FR-N](../../PRD.md#8-functional-requirements) +- [Source: ARCHITECTURE §<section>](../../ARCHITECTURE.md) +- [Source: CONTEXT D<N>](../../CONTEXT.md) <if a decision was recorded> +- [Source: LESSONS §<N>](../../LESSONS.md) <if a prior trap is being avoided> +- [Source: Design spec](../../design/US-X.Y-<slug>-design.md) <if applicable> + +## Verification commands + +<Every AC maps to a runnable command. Reviewers paste these into a +terminal — if they don't all pass, the story is not done. For declarative +ACs (e.g. "no file imports X"), use `rg` / `grep` and assert the line +count. For behavioral ACs, name the test file and case.> + +| AC | Command | +|---|---| +| AC-1 | `pnpm --filter @workspace/<pkg> test <test-path>` | +| AC-2 | `rg "<pattern>" packages/ apps/ --type ts -l \| grep -v __tests__` returns only <expected files> | +| AC-3 | `grep -l "<marker>" <dir>` returns <expected count> files | + +## Changelog entry + +> This is the *exact* text that goes into CHANGELOG.md when the story +> ships. Draft it when the story nears completion. On ship, copy this +> into CHANGELOG.md under the new version header (see +> [templates/changelog.md](changelog.md)). +> +> Only include sections that have content. Omit empty sections. + +### Added +- <Feature / component added> + +### Changed +- <Behavior or API changed — old vs new> + +### Fixed +- <Bug description + root cause in one sentence> + +### Deprecated +- <What is still exported but JSDoc-marked `@deprecated`, with the replacement> + +### Removed +- <What was dropped and why> + +**Commit**: <full SHA> + +## Implementation notes + +<Filled *during* implementation. Workarounds, design tradeoffs, library +quirks, security notes, performance observations discovered along the +way. This is the section that gets harvested into LESSONS.md when a +trap or pattern emerges.> + +<Initial draft: empty. Fill incrementally as you ship.> + +## Files modified + +<Filled *during* implementation. Group by package / app / area. One line +per file explaining what changed and why — not just what was added.> + +**Created (<package>):** +- `<path/to/file.ts>` — <purpose> + +**Modified (<package>):** +- `<path/to/file.ts>` — <what changed and why> + +**Deleted:** +- `<path/to/file.ts>` — <reason for deletion> + +## Cross-references + +- [PRD FR-N](../../PRD.md#8-functional-requirements) +- [Epic EPIC-X](../epics/EPIC-X.md) +- [CHANGELOG vX.Y.Z](../../CHANGELOG.md) +- [CONTEXT D<N>](../../CONTEXT.md) <if a decision was recorded> +- [LESSONS §<N>](../../LESSONS.md) <if a pattern was codified> +- [Design spec](../../design/US-X.Y-<slug>-design.md) <if applicable> +```` + +--- + +## 3. Per-section guidance + +### §1 Frontmatter + +- `id` MUST match the filename prefix: `US-1.16-...md` → `id: US-1.16`. + RULE-6 enforces this. +- `epic` MUST match an existing `EPIC-N.md` and the story MUST be listed + in that epic's Stories table. `agile-sync-up.mjs` validates this. +- `status` lifecycle: `backlog → ready → in-progress → review → done`. + `blocked` is a sub-state of `in-progress` (document the reason in + Implementation notes). `deprecated` is a terminal state for stories + retired before shipping (typically driven by a CONTEXT decision); + cross-reference the deciding `D<N>` in the story body. Set + `version_shipped` only on `done` transition; never before. +- `points`: Fibonacci only (1 / 2 / 3 / 5 / 8 / 13). A 13-pt story should + almost always be split — large stories merge code without ever being + reviewed in full. **For non-engineering stories (sales / marketing / + content / ops), consult `/sales-engineer` or `/marketing-ops` before + sizing** — gut-feel + Fibonacci alone systematically undersizes ~30-40% + on these tracks (see SKILL.md §3a + §3a-bis for the routing matrix and + calibration scale). +- `prd_ref` / `arch_ref` / `depends_on`: ID-typed list fields. Each + entry MUST be a bare canonical ID matching the regex for its namespace + (`^FR-\d+$` or `^NFR-\d+$` for `prd_ref`; `^AD-\d+$` for `arch_ref`; + `^US-\d+\.\d+$` for `depends_on`). **Use YAML list form** + (`prd_ref: [FR-04, FR-10]`) — the legacy CSV-string form invites + prose contamination that breaks `koni-docs sync`. Prose, qualifiers, + parenthetical scope notes, and ranges (`FR-28 .. FR-45`) all belong + in the body (Background, Cross-story dependencies, Architecture + constraints), NEVER in frontmatter. See RULE-17 and + [`frontmatter-spec.md`](../frontmatter-spec.md) for the full + contract and migration playbook. +- `commit`: stays empty until the landing commit exists. Filled at + pre-commit time (RULE-2). Never `pending`. +- `assignee`: the person who **authored the work**, not whoever is writing + the doc. For a **retroactive / codebase-discovered** story, derive it from + the git author of the story's `commit` SHA — run + `git log -1 --format='%an <%ae>' <sha>` — and **never** default to the + current session's git user (that silently mis-credits another contributor). + When the story spans commits by multiple authors (e.g. main work by one + person + a follow-up fix by another), set `assignee` to the primary-work + author and credit the others explicitly in Background / Tasks next to their + SHA. +- `external_deps` *(optional)*: list of third-party systems, partners, or + legal-review queues this story waits on. Populate when calendar wait time + (outside dev control) is in the critical path — e.g. + `[payment_gateway, resend_api, legal_review, sales_navigator_license, + partner_signature]`. These stories are the most commonly undersized; + surfacing the dep in frontmatter lets sprint planning account for the + buffer and lets STATUS dashboards flag blocking risk. Stories with no + external waits leave this field empty or omit it entirely. + +### §2 Goal + +- 1 paragraph, value-focused. Write it so a reviewer who knows nothing + about this story can answer "what does the user gain when this ships?" + in one sentence. + +### §3 Background + +- Write Background *before* the AC list. The AC list looks arbitrary + without it. +- For scope-reduced stories: state the original plan, why it was + rescoped, and where the deferred plan is preserved. Future maintainers + often re-find this section when the trigger to revisit fires. + +### §4 Acceptance criteria + +- AC numbering is *immutable* after creation. If AC-3 is deleted, AC-4 + does NOT become AC-3. Cross-references in commits, PR descriptions, + and downstream stories rely on stable AC IDs. +- Prefer Given/When/Then for behavioral ACs. Declarative form is fine + for schema, constraints, lint guards. +- Each AC must be defended by exactly one command in §11 Verification. +- The unhappy path is a first-class AC, not an afterthought. Empty + states, error responses, race conditions, RLS isolation, audit + rollback — pick the ones that apply. + +### §5 Tasks + +- Tasks have AC cross-references in `(AC: N, M)` form. A task with no AC + reference either fulfills no AC (cut it) or is missing an AC (add it). +- Subtasks name specific paths or symbols. "Update auth middleware" is + not a subtask; "Update `packages/api/src/middleware/auth.ts` to call + `permissionCache.get()` before DB fallback" is. +- Mark tasks `[x]` *as you complete them*, not all at the end (RULE-10). + Reviewers use this to gauge progress mid-sprint. + +### §6 Architecture constraints + +- One bullet per AD that constrains the implementation. State *how* the + AD applies, not just that it does. +- If the story introduces a *new* architecture decision (rare), name it + here and append a CONTEXT.md entry in the same commit. RULE-7. + +### §7 Cross-story dependencies + +- Three groups: builds-on / required-by / sibling. Each names the + *artifact* the dependency is on (file, function, schema, fixture) + — not just the story ID. + +### §8 Performance budget + +- Required when the epic publishes a budget for this story, or when the + story sits on a hot path (auth check, request-path middleware, dashboard + aggregate, etc.). +- Budgets are numbers, not adjectives. Name the test that defends them. + +### §9 What we explicitly did NOT do + +- For stories that take a deliberate "do less now" posture. List the + rejected alternatives and the trigger that would justify revisiting. +- This section prevents reviewers from re-litigating the scope decision + and protects future-you from re-discovering the same trade-off. + +### §10 References + +- Every reference has a source path. Bare URLs without context belong + in PR descriptions, not in story Dev Notes. + +### §11 Verification commands + +- Every AC has a runnable command. Reviewers paste them — if any + command does not pass, the story is not done. +- For "no X exists" ACs, use `rg` / `grep` with an explicit expected + output (file count, allowlist of paths). +- For "this test passes" ACs, name the test file *and the test case* + if the file has more than one. + +### §12 Changelog entry + +- Drafted when the story is nearing `done`. Reviewers copy it verbatim + into CHANGELOG.md at ship time — keep it ship-ready. +- Only include `### Added` / `### Changed` / `### Fixed` / `### Deprecated` + / `### Removed` / `### Security` sections that have content. +- `**Commit**: pending` is NEVER acceptable. Fill the full SHA at + pre-commit (RULE-2). + +### §13 Implementation notes + +- Filled *during* implementation, not before. Captures workarounds, + library quirks, security notes, perf observations. +- When a note describes a pattern that will recur, also append a + LESSONS.md entry and cross-reference. + +### §14 Files modified + +- Filled *during* implementation. Group by package; one line per file + describing what changed and why. +- This section is harvested for code-review PR descriptions — write it + for the reviewer, not for yourself. + +### §15 Cross-references + +- Link upward (PRD FR, Epic) and laterally (CHANGELOG, CONTEXT, LESSONS, + Design spec) so a reader landing on the story can navigate the + surrounding docs without re-grepping. + +--- + +## 4. Filled mini-example (condensed) + +```markdown +--- +id: US-1.16 +title: "Add Typed Per-Queue Helpers + Zod Validation Boundary on pg-boss" +epic: EPIC-1 +status: done +priority: P1 +points: 2 +sprint: sprint-2026-W19 +version_shipped: 0.3.1 +prd_ref: [FR-93, FR-94] +arch_ref: [AD-06] +assignee: +commit: a1b2c3d4e5f6... +created: 2026-05-09 +updated: 2026-05-09 +--- + +## Goal + +Close the producer-side typing gap on top of the existing pg-boss runtime +(US-1.6) without introducing a registry/`defineConsumer` abstraction. Ship +`packages/api/src/queues/` containing Zod schemas for every queue payload +plus typed `enqueue*` helpers. Add a defensive Zod parse at the top of every +consumer handler. The producer-side typing problem is solved; downstream +stories adding new queues add one schema + one helper, no abstraction work. + +## Background + +[US-1.6](US-1.6-...md) shipped the pg-boss lifecycle wrapper and the +`enqueueAuditAppend` helper. Since then the project grew to **five queues** +with three production gaps (duplicate consts, unsafe `as` cast, no runtime +validation). This is the **scope-reduced replacement** for the original +Option X plan ([spec](../../superpowers/specs/2026-05-09-typed-queue-service-design.md)); +the user pushed back ("we already have `getPgBoss` standardized") and we +re-scoped to fix only the highest-value gap (#4: no runtime validation) +without the larger abstraction. + +## Acceptance criteria + +- [x] **AC-1** — **Given** the new `packages/api/src/queues/` directory, + **When** the package is built, **Then** it exports `QUEUE_NAMES`, 5 Zod + payload schemas, and 4 new typed `enqueue*` helpers. +- [x] **AC-2** — **Given** any helper invoked with a payload missing a + required field, **When** the call runs, **Then** it throws `ApiError` + with `code === "VALIDATION_ERROR"`, and `getPgBoss().send` is **never** + invoked. +- [x] **AC-3** — **Given** every direct `getPgBoss().send` / `boss.send` + call site, **When** they are migrated to typed helpers, **Then** + `rg "(getPgBoss\(\)|boss)\.send\(" packages/ apps/ --type ts -l | grep -v __tests__` + returns only the helper implementation files. + +## Tasks + +- [x] **TASK-1.16.1** — `packages/api/src/queues/` scaffold (AC: 1) + - [x] Subtask 1.16.1.1 — Create `schemas.ts` with 5 Zod schemas + `QUEUE_NAMES`. + - [x] Subtask 1.16.1.2 — Create `helpers.ts` with 4 new typed helpers. +- [x] **TASK-1.16.2** — Migrate all direct producer call sites (AC: 3) + - [x] Subtask 1.16.2.1 — `routes/safes.route.ts`: use `enqueueSafeDeployMonitor`. + +## Dev notes + +### Architecture constraints + +- [AD-06](../../ARCHITECTURE.md#architecture-decisions) — pg-boss as event bus; this story does NOT change the substrate. +- Helper file location is centralized at `queues/helpers.ts`; **`enqueueAuditAppend` deliberately stays in `services/audit.service.ts`** because it owns the redaction wrap (FR-94). + +### Cross-story dependencies + +- Builds on [US-1.6](US-1.6-...md) — uses `getPgBoss()` and the `enqueueAuditAppend` precedent. +- Required by future EPIC-3 / 4 / 5 stories that introduce new queues — they add a schema + helper in one PR diff. + +### Performance budget + +- Helper overhead vs raw `boss.send`: ≤ 0.5 ms p95. +- Measured by `__tests__/queues/helpers.unit.test.ts`. + +### What we explicitly did NOT do (vs Option X) + +- No central typed registry — `QUEUE_NAMES` is the only registry, intentionally light. +- No `defineConsumer` / `defineBatchConsumer` wrappers — `boss.work` stays inline in each job file. +- No 100% coverage gate on `queues/` — ordinary unit tests + integration regression suite. + +### References + +- [Original spec (Option X, deferred)](../../superpowers/specs/2026-05-09-typed-queue-service-design.md) +- [Source: ARCHITECTURE §Component architecture](../../ARCHITECTURE.md) + +## Verification commands + +| AC | Command | +|---|---| +| AC-1, AC-2 | `pnpm --filter @workspace/api test packages/api/__tests__/queues` | +| AC-3 | `rg "(getPgBoss\(\)\|boss)\.send\(" packages/ apps/ --type ts -l \| grep -v __tests__` returns only `queues/helpers.ts` and `services/audit.service.ts` | + +## Changelog entry + +### Added +- `packages/api/src/queues/`: Zod schemas for all 5 pg-boss payload types; `QUEUE_NAMES` const; 4 typed `enqueue*` helpers. +- Defensive Zod parse at the entry of every consumer handler. + +### Changed +- `enqueueAuditAppend` now Zod-validates after redaction (drops unsafe `as unknown as object` cast). +- 5 direct `getPgBoss().send` call sites migrated to typed helpers. + +### Deprecated +- `AUDIT_APPEND_QUEUE`, `SAFE_DEPLOY_QUEUE`, ... consts — JSDoc-marked `@deprecated`, point at `QUEUE_NAMES.X`. + +**Commit**: a1b2c3d4e5f6... + +## Implementation notes + +Shipped Option Y (helpers + Zod) instead of Option X (full registry + +`defineConsumer`) after a YAGNI challenge revealed that 4 of 5 identified +gaps were low-severity. Implementation took ~150 LoC across 5 new files + +11 modified; 38 new tests pass; zero new typecheck errors. + +## Files modified + +**Created (api package):** +- `packages/api/src/queues/schemas.ts` +- `packages/api/src/queues/helpers.ts` + +**Modified (api package):** +- `packages/api/src/services/audit.service.ts` — Zod parse + drop cast. +- `packages/api/src/routes/safes.route.ts` — use helper. + +## Cross-references + +- [PRD AD-06](../../PRD.md) +- [Epic EPIC-1](../epics/EPIC-1.md) +- [CHANGELOG v0.3.1](../../CHANGELOG.md) +``` + +See [US-1.16](https://github.com/Koniverse/Koni-Finance-Final/blob/main/docs/sprints/stories/US-1.16-design-and-implement-typed-queue-service-thin-typed-producer-on-pg-boss.md) and [US-1.1](https://github.com/Koniverse/Koni-Finance-Final/blob/main/docs/sprints/stories/US-1.1-extend-docker-compose-dev-infrastructure-with-redis-config-and-optional-services.md) in the Koni-Finance-Final repository for full reference stories. diff --git a/.agents/skills/koni-docs/references/templates/test-cases.md b/.agents/skills/koni-docs/references/templates/test-cases.md new file mode 100644 index 00000000000..d87815bc384 --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/test-cases.md @@ -0,0 +1,588 @@ +# Test Cases File — Full Template + +> **File location**: `docs/tests/test-cases/EPIC-N.md` (one file per epic) +> +> **Use when**: User asks to create or update a test-cases file for an epic, +> or when a planning tool (BMad `bmad-testarch-test-design`, GStack `/qa`, +> Superpowers) produces a test-plan-shaped artifact that needs +> standardization into the koni-docs structure. +> +> **One rule above all others**: a test-cases file is the *epic-level* +> verification contract. It does NOT replace per-story Acceptance Criteria +> (those remain in `docs/sprints/stories/US-X.Y-<slug>.md`). It captures +> what AC cannot: end-to-end flows that span multiple stories, regression +> scenarios that guard cross-story invariants, smoke checks per release, +> and the execution log that tells reviewers when each scenario last passed. + +--- + +## 1. What this file owns vs. what it does not + +| Concern | Owner | +|---|---| +| Per-story acceptance contract (AC-1..N + Tasks) | Story file (`stories/US-X.Y-<slug>.md`) | +| Per-AC runnable verification command | Story file §11 Verification commands | +| End-to-end flow spanning ≥2 stories in the epic | **This file** | +| Regression scenarios protecting epic-level invariants | **This file** | +| Smoke check for the epic at release time | **This file** | +| Coverage matrix (AC → TC) | **This file** §Coverage matrix | +| Execution history (when/who/pass/fail) | `docs/tests/test-reports/runs/*.md` | +| Cross-story test infrastructure (fixtures, harness) | Epic file §13 Cross-story testing requirements | + +**Iron rule**: if a scenario can be expressed as a single story's AC, it +belongs in the story file. Promote to a test-case here only when it +exercises ≥2 stories or guards an epic-level invariant. + +--- + +## 2. Section index — what's required vs optional + +| § | Section | Small epic (≤5 stories) | Medium (6–12) | Large (13+) | +| --- | -------------------------------- | ----------------------- | ------------- | ----------- | +| 1 | Frontmatter | required | required | required | +| 2 | Overview — Scope | required | required | required | +| 3 | Overview — Stories in scope | required | required | required | +| 4 | Overview — Goals | recommended | required | required | +| 5 | Overview — Environment & test data | optional | required | required | +| 6 | Overview — Cadence & ownership | optional | recommended | required | +| 7 | Quick reference — scenarios summary | recommended | required | required | +| 8 | Test scenarios (detail) | required | required | required | +| 9 | Coverage matrix | recommended | required | required | +| 10 | Open / deferred scenarios | optional | recommended | required | + +**Audience principle**: §2-§7 are written for the **tester / reviewer** scanning the file (≤2 min to understand scope + run sequence). §8 onwards is the executable detail for the person running the suite. + +--- + +## 3. Full template skeleton + +````markdown +--- +id: EPIC-N-tests +epic: EPIC-N +created: YYYY-MM-DD +updated: YYYY-MM-DD +--- + +## Overview + +### Scope + +<2-4 sentences naming the user flows + invariants this file covers, and +explicitly the ones it does NOT (those owned by sibling epics' test files +or deferred). Cross-link to the epic file [§Goal](../epics/EPIC-N.md) for +the product framing — do not repeat it.> + +**Out of scope** (tested elsewhere): + +- <Capability A> → see [EPIC-M tests](EPIC-M.md) +- <Capability B> → deferred to <phase / story> + +### Stories in scope + +<Every story this epic touches — both shipped and not — in one table so +a reviewer scans the surface area before diving into TCs. `Short name` is +a 2-5 word capability label (consistent across this file). `Status` uses +emojis so the table renders at a glance.> + +| Story | Short name | Status | +|---|---|---| +| [US-X.Y](../../sprints/stories/US-X.Y-<slug>.md) | <2-5 word capability> | ✅ shipped | +| [US-X.Z](../../sprints/stories/US-X.Z-<slug>.md) | <2-5 word capability> | 🚧 in-progress | +| [US-X.W](../../sprints/stories/US-X.W-<slug>.md) | <2-5 word capability> | 📋 backlog | +| [US-X.V](../../sprints/stories/US-X.V-<slug>.md) | <2-5 word capability> | ⏸ out-of-scope (deferred / phase) | + +> Status emoji set: `✅ shipped` · `✅ shipped (partial)` · `🚧 in-progress` · `📋 backlog` · `⏸ out-of-scope`. + +### Goals — what running this suite proves + +<3-5 bullets stating the high-level invariants the tester confirms by +running every TC in this file. These are NOT individual TCs — they are +the *why*. Reviewers use this to judge whether the TC list below actually +covers the goals.> + +1. <Outcome 1 — the user-facing invariant or product property> +2. <Outcome 2> +3. <Outcome 3> + +### Environment & test data + +<Name the environments scenarios run against (`local`, `staging`, +`production`) and the seed data they assume. If scenarios need MinIO +fixtures, broker sandbox accounts, or Discord webhooks, list them here so +a future executor can reproduce without spelunking.> + +- **Environment(s):** `staging` (default), `local` for unit-level +- **Seed data:** `<path or script>` +- **External dependencies:** `<broker sandbox / vendor / mock server>` +- **Secrets/keys:** load from `.env.test` (never commit) + +### Cadence & ownership + +<Who runs what, when. Pre-PR for `smoke`, per-release for `e2e + regression`, +nightly for `performance`, etc.> + +| Cadence | Test types | Owner | +|---|---|---| +| Per-PR | smoke | dev on PR | +| Per-release | e2e + regression | QA lead | +| Nightly | performance | CI | + +## Quick reference — test scenarios summary + +<Every TC in this file as a single-row scan. Tester reads top-down to know +exactly which scenarios + what they prove + which stories they touch before +diving into Gherkin. The `Stories` cell uses `US-X.Y <short name>` format +matching §Stories in scope; multiple stories use `<br>` line breaks.> + +| # | ID | Type | Priority | Short description | Stories | Mode | +|---|----|------|----------|-------------------|---------|------| +| 1 | TC-N.E2E-1 | E2E | P0 | <one-line outcome the TC proves> | US-X.Y <short name><br>US-X.Z <short name> | manual | +| 2 | TC-N.REG-1 | Regression | P0 | <one-line outcome> | US-X.Y <short name> | automated | +| 3 | TC-N.SMK-1 | Smoke | P1 | <one-line outcome> | US-X.Z <short name> | automated | + +> **Read this table first.** Passing every row = epic still works at +> release time. Drill into [§Test scenarios](#test-scenarios) for full +> Gherkin detail. + +## Test scenarios + +<Every scenario is an H3 subsection with a YAML metadata block + Gherkin +body. Number TCs within the epic using `TC-N.<TYPE>-<num>`: +- `TC-02.E2E-1` — end-to-end flow +- `TC-02.REG-1` — regression +- `TC-02.SMK-1` — smoke +- `TC-02.PRF-1` — performance +- `TC-02.SEC-1` — security +- `TC-02.INT-1` — integration + +NEVER renumber TCs after creation. Even if one is removed, the number stays +deprecated so cross-references (in test-reports, lessons, PRs) survive.> + +### TC-N.E2E-1 — <Short scenario title> + +```yaml +id: TC-N.E2E-1 +type: e2e # smoke | regression | e2e | integration | unit | performance | security +mode: manual # manual | automated | hybrid +priority: P0 # P0 | P1 | P2 | P3 +maps_to: + fr: [FR-N, FR-M] + ac: [US-X.Y/AC-1, US-X.Y/AC-3, US-X.Z/AC-2] + ad: [AD-N] + rule: [RULE-N] # optional — security/compliance rules from CLAUDE.md +``` + +**Preconditions:** +- <state of the system before the scenario starts> +- <user / data prerequisites> + +**Scenario:** +```gherkin +Given <preconditions> +And <additional context> +When <user / system action> +Then <observable outcome> +And <invariant that must hold> +``` + +**Test data:** +- `<account / fixture / file>` + +**Notes:** +- <known flakiness, branch coverage, links to bug tickets> + +### TC-N.REG-<num> — <Regression title> + +<Same structure. Regression scenarios should explicitly cite the +LESSONS.md entry or CONTEXT.md decision they guard against re-regression.> + +## Coverage matrix + +<Roll-up: which TC covers which AC in which story. Reviewers use this to +spot AC gaps. The Story column inlines the short name from §Stories in +scope (gives a tester scanning a single row enough context to know what +the AC is about). AC description is a ≤80-char distillation of the +story's AC text — never copy paste the full AC. Generate by hand at first; +later phases may auto-fill via `agile-sync-tests.mjs`.> + +| Story | AC | AC description | Covered by | Type | +|---|---|---|---|---| +| [US-X.Y — <short name>](../../sprints/stories/US-X.Y-<slug>.md) | AC-1 | <≤80-char summary of what the AC asserts> | TC-N.E2E-1, TC-N.SMK-1 | e2e, smoke | +| [US-X.Y — <short name>](../../sprints/stories/US-X.Y-<slug>.md) | AC-2 | <summary> | TC-N.REG-1 | regression | +| [US-X.Z — <short name>](../../sprints/stories/US-X.Z-<slug>.md) | AC-3 | <summary> | — | **gap** | +| [US-X.W — <short name>](../../sprints/stories/US-X.W-<slug>.md) | — | <story-level purpose if AC list not relevant> | — | **gap (entire story)** | + +> **Gap rule:** any AC row with `—` in "Covered by" must appear in +> §Open / deferred scenarios with an owner and a target date. +> +> **Short name consistency:** use the exact same `<short name>` value as +> in §Stories in scope. Cross-file consistency (same US-X.Y → same short +> name across every test-cases file in the project) is enforced by the +> agent maintaining the docs. +> +> **AC description sourcing:** distill from the story file's `## Acceptance +> criteria` section: +> ```bash +> sed -n '/^## Acceptance criteria/,/^## [A-Z]/p' docs/sprints/stories/US-X.Y-*.md +> ``` +> Compress to ≤80 chars while preserving technical terms (function names, +> env vars, HTTP codes). For ACs still in `[ ]` status in the story file, +> include them in the matrix with `Covered by = —` and `Type = +> **gap (deferred)**`. + +## Open / deferred scenarios + +<Scenarios that should exist but do not yet. Each item names the owner + +the trigger (date / event) that will close the gap.> + +- **<Scenario title>** — covers `US-X.Z/AC-3` (currently no TC). + Owner: `<name>`. Target: `<sprint id or release tag>`. +- **<Scenario title>** — performance baseline for `<operation>`, + deferred until [EPIC-N performance budget](../epics/EPIC-N.md#performance-budgets--invariants) + is published. +```` + +--- + +## 4. Per-section guidance + +### §1 Frontmatter + +- `id` matches the filename's epic ID: `EPIC-02.md` → `id: EPIC-02-tests`. +- `epic` links back to the canonical epic file (1-to-1). +- `created` / `updated` follow the same convention as story frontmatter. +- Do NOT add `status:` to the file frontmatter — status lives **inside + each TC's metadata block** (because a single file can have a mix of + ready + draft + executed + deprecated scenarios). + +### §2 Scope + +- 2–4 sentences. Name the user flows + invariants covered, and the ones + explicitly NOT covered (cross-link to sibling epic test files when + passing the baton). +- If this file leaves a major area uncovered on purpose (e.g. "load + testing deferred to EPIC-08"), state it here — reviewers should not + have to guess. +- The "Out of scope" bullet list is mandatory when the epic has sibling + test files. Reviewers must not have to guess which epic test file owns + a flow they expected to find here. + +### §3 Stories in scope + +- One row per story the epic touches, including stories that have no + TC coverage in this file (mark them `📋 backlog` or `⏸ out-of-scope`). + A missing row is worse than a row with status `⏸` — it makes the + reviewer wonder if the story was forgotten. +- `Short name` is a 2-5 word capability label (≤25 chars). The same value + must be reused in: + - §Quick reference summary (Stories cell, as `US-X.Y <short name>`) + - §Coverage matrix (Story cell, as `[US-X.Y — <short name>](...)`) + This 3-place consistency is non-negotiable; the agent that maintains + the file enforces it. +- Status emoji set is fixed: `✅ shipped` · `✅ shipped (partial)` · + `🚧 in-progress` · `📋 backlog` · `⏸ out-of-scope`. No free-form text. + +### §4 Goals — what running this suite proves + +- 3–5 bullets. Each bullet names a *user-facing invariant* or *product + property* the suite confirms. These are NOT TC IDs. +- Goals are the bridge between "what the epic claims to do" (PRD / epic + file) and "what we actually test" (TCs below). If a goal has no TC + backing, it belongs in §Open / deferred with an owner. +- Reviewers check: do the goals here cover the FRs `prd_ref` in the epic + frontmatter? If a goal is absent for a covered FR, the file is + incomplete. + +### §5 Environment & test data + +- Required for medium / large epics. Without it, a future executor + cannot reproduce the scenarios. +- List ALL external dependencies (broker sandbox, MinIO bucket, Discord + webhook, mock server) — each one is a potential flake source. +- Never commit secrets. Reference `.env.test` or a sealed vault. + +### §6 Cadence & ownership + +- Required for large epics. The "who runs what when" matrix prevents + scenarios from rotting unowned. +- Cadence values: `per-PR`, `per-release`, `per-sprint`, `nightly`, + `on-demand`. Match these to the `trigger` field if you extend the YAML + metadata block in phase 2. + +### §7 Quick reference — scenarios summary + +- One row per TC in the file, ordered to match §Test scenarios (detail) + section order. A reader who only opens this section must learn enough + to know which TCs to run, in what order, and what each proves. +- `Short description` (≤80 chars) is a distilled version of the TC's H3 + title. Avoid mechanics ("calls function X") — name the *outcome*. +- `Stories` cell mirrors §Stories in scope short names. Multiple stories + separate with `<br>` for a vertical stack inside the cell. +- `Mode` matches the YAML metadata block (`manual` / `automated` / `hybrid`). +- This table is regenerated when scenarios are added/removed/renumbered. + It is NOT a separate source of truth — the detail section §8 is. + +### §8 Test scenarios — TC structure + +- Every TC needs the YAML metadata block + Gherkin scenario block. + Gherkin is mandatory even for `unit`/`integration` types — the + Given/When/Then framing forces precondition + observable clarity. +- `maps_to.ac` is the single most-important field for traceability. + Every TC must map to at least one AC; a TC that maps to zero AC is + either testing something that's not specified (gap in PRD/story) or + is dead weight. +- For automated TCs, include a Notes line linking to the test file: + `Notes: implemented at e2e-tests/tests/<file>.spec.ts::<test name>`. +- Separate consecutive TCs with `---` horizontal rule for visual scan. + +### §9 Coverage matrix + +- Required for medium / large epics. Reviewers use this to spot AC + gaps before signing off the epic. +- A row per `(story, AC)` pair, not per story. A story with 5 ACs + produces 5 rows. +- **Story column** inlines the short name from §Stories in scope: + `[US-X.Y — <short name>](../../sprints/stories/US-X.Y-<slug>.md)`. The + short name is repeated on every row for the same story — markdown has + no `rowspan`, and the repetition helps tester scan a single row + without going back to look up the story. +- **AC description column** (≤80 chars) distills the story's AC text. + Extract with: + ```bash + sed -n '/^## Acceptance criteria/,/^## [A-Z]/p' docs/sprints/stories/US-X.Y-*.md + ``` + Compress while preserving technical terms. For ACs still `[ ]` in the + story (not shipped), include the row with `Covered by = —` and `Type = + **gap (deferred)**` — never silently drop the AC. +- "**gap**" entries MUST appear in §10 Open / deferred scenarios with + an owner and a target. +- For stories with no AC tracked (entire-story gaps, out-of-scope), + represent as a single row: `| [US-X.Y — <name>](...) | — | <story + purpose> | — | **gap (entire story)** |`. + +### §10 Open / deferred scenarios + +- Append-only — when a deferred scenario is implemented, move it to + §8 Test scenarios but leave a one-line note here pointing to the new + TC ID. This preserves history of why a gap existed. + +--- + +## 5. Conventions + +### TC ID format + +`TC-<EPIC>.<TYPE>-<num>` where TYPE is uppercase 3-letter mnemonic: + +| Type | Mnemonic | Meaning | +|---|---|---| +| `smoke` | `SMK` | Quick post-deploy sanity check (≤2 min) | +| `regression` | `REG` | Guards a previously-fixed bug or invariant | +| `e2e` | `E2E` | End-to-end flow spanning ≥2 stories | +| `integration` | `INT` | Service-to-service contract test | +| `unit` | `UNT` | Logic-level test (rarely promoted here — usually lives in story AC) | +| `performance` | `PRF` | Latency / throughput / size budget | +| `security` | `SEC` | Auth / authz / data-exposure check | + +### Status (implicit in TC body, not frontmatter) + +A TC is implicitly `draft` until its Coverage matrix row is filled. +Once `maps_to.ac` is non-empty AND a runnable Notes line or manual steps +exist, treat it as `ready`. Use test-reports (`runs/*.md`) to track +`executed` state — never duplicate execution status into the test-cases +file itself. + +### Deprecation + +When a scenario is no longer relevant (story removed, FR deprecated): + +1. Replace the TC body with: `**Deprecated YYYY-MM-DD** — <one-line reason>. See [CONTEXT D<N>](../../CONTEXT.md).` +2. Keep the H3 heading + ID so cross-references survive. + +--- + +## 6. Filled mini-example (condensed) + +````markdown +--- +id: EPIC-02-tests +epic: EPIC-02 +created: 2026-05-15 +updated: 2026-05-15 +--- + +## Overview + +### Scope + +EPIC-02 (MT5 account linking + terminal provisioning) end-to-end flows, +credential-vault security regression, and per-release smoke. + +**Out of scope** (tested elsewhere): + +- Slot enforcement → see [EPIC-04 tests](EPIC-04.md) +- Partner verification → see [EPIC-06 tests](EPIC-06.md) + +### Stories in scope + +| Story | Short name | Status | +|---|---|---| +| [US-2.1](../../sprints/stories/US-2.1-link-mt5-account-with-encryption.md) | Link MT5 + vault | ✅ shipped | +| [US-2.6](../../sprints/stories/US-2.6-mt5-credential-verification.md) | Credential verify | ✅ shipped | +| [US-2.7](../../sprints/stories/US-2.7-terminal-provisioning.md) | Terminal provision | ✅ shipped | +| [US-2.8](../../sprints/stories/US-2.8-mt5-account-health-status.md) | Health status | ✅ shipped | +| [US-2.9](../../sprints/stories/US-2.9-unlink-mt5-account.md) | Unlink + decommission | 📋 backlog | + +### Goals — what running this suite proves + +1. A user can link an MT5 account with credentials encrypted at rest and never leaked in API/log. +2. The platform auto-provisions a terminal on an ONLINE node and surfaces health within 60s. +3. Credential vault is regression-safe — no code path returns plaintext. +4. The linked-accounts dashboard renders post-deploy (smoke). + +### Environment & test data + +- **Environment(s):** `staging` for e2e, `local` for security regression +- **Seed data:** `e2e-tests/fixtures/mt5-sandbox-accounts.json` +- **External dependencies:** Exness MT5 sandbox (`mt5-sandbox.exness.com:443`), Node Manager LAN endpoint, MinIO `ea-binaries` bucket +- **Secrets:** `.env.test` — `CREDENTIAL_ENCRYPTION_KEY`, `SERVICE_JWT_SECRET` + +### Cadence & ownership + +| Cadence | Test types | Owner | +|---|---|---| +| Per-PR | TC-02.SMK-* | dev on PR | +| Per-release | TC-02.E2E-*, TC-02.REG-* | QA | +| Nightly (post-MVP) | TC-02.PRF-* | CI | + +## Quick reference — test scenarios summary + +| # | ID | Type | Priority | Short description | Stories | Mode | +|---|----|------|----------|-------------------|---------|------| +| 1 | TC-02.E2E-1 | E2E | P0 | Link MT5 account → encrypted vault → terminal provision → health badge | US-2.1 Link MT5 + vault<br>US-2.6 Credential verify<br>US-2.7 Terminal provision<br>US-2.8 Health status | manual | +| 2 | TC-02.REG-1 | Regression | P0 | Credential vault never returns plaintext on any code path | US-2.1 Link MT5 + vault | hybrid | +| 3 | TC-02.SMK-1 | Smoke | P1 | Linked accounts list renders on dashboard | US-2.8 Health status | automated | + +## Test scenarios + +### TC-02.E2E-1 — Link MT5 account, provision terminal, verify health badge + +```yaml +id: TC-02.E2E-1 +type: e2e +mode: manual +priority: P0 +maps_to: + fr: [FR11, FR12, FR13, FR15] + ac: [US-2.1/AC-1, US-2.1/AC-3, US-2.6/AC-1, US-2.7/AC-1, US-2.8/AC-1] + ad: [AD-10] +``` + +**Preconditions:** +- Authenticated user with at least 1 free slot. +- Exness MT5 sandbox credentials available (`fixtures/mt5-sandbox-accounts.json`). +- At least 1 `NodeInstance` with `status=ONLINE` and free capacity. + +**Scenario:** +```gherkin +Given the user is on the MT5 accounts page +And they have 0 linked accounts and 2 free slots +When they submit valid sandbox credentials (login + password + server) +Then Mt5Account row is created with non-null encryptedPassword +And no API response or log line contains the plaintext password +And selectAvailableNode picks an ONLINE node with free capacity +And the terminal_manager.ex5 boots on the assigned port within 15s +And the account appears in the dashboard with terminalStatus=ACTIVE within 60s +``` + +**Test data:** `mt5-sandbox-accounts.json` → `account_a_demo` + +**Notes:** +- Last run 2026-05-14 — pass (link to runs/2026-05-14-EPIC-02-run1.md). +- Manual phase 1; will be automated in `e2e-tests/tests/mt5-link-flow.spec.ts` (TBD US-19.X). + +### TC-02.REG-1 — Credential vault never returns plaintext (security regression) + +```yaml +id: TC-02.REG-1 +type: regression +mode: hybrid +priority: P0 +maps_to: + fr: [FR11] + ac: [US-2.1/AC-2, US-2.1/AC-4] + rule: [SECURITY-1, SECURITY-2] +``` + +**Preconditions:** +- 1 linked Mt5Account in the DB with a known plaintext password. + +**Scenario:** +```gherkin +Given an Mt5Account exists with login=8000123 and a known plaintext password +When any OpenSaaS query, action, or REST endpoint returns the account +Then no response field contains the plaintext password +And no structured log line emitted during the action contains the plaintext password +And the response shape is { login, serverAddress, accountType, terminalStatus } with no password-shaped field +``` + +**Notes:** +- Implemented at `app/src/mt5/__security__/credential-leak.test.ts` (planned). +- Guards CLAUDE.md security rule #1 + #2. + +### TC-02.SMK-1 — Linked accounts list renders on dashboard + +```yaml +id: TC-02.SMK-1 +type: smoke +mode: automated +priority: P1 +maps_to: + fr: [FR15] + ac: [US-2.8/AC-1] +``` + +**Scenario:** +```gherkin +Given an authenticated user with ≥1 linked Mt5Account +When they navigate to /mt5/accounts +Then the linked accounts table renders within 2s +And each row shows login, broker, terminalStatus badge +``` + +**Notes:** +- Implemented at `e2e-tests/tests/mt5-accounts-smoke.spec.ts` (planned). + +## Coverage matrix + +| Story | AC | AC description | Covered by | Type | +|---|---|---|---|---| +| [US-2.1 — Link MT5 + vault](../../sprints/stories/US-2.1-link-mt5-account-with-encryption.md) | AC-1 | Submit valid credentials → Mt5Account row created with encryptedPassword | TC-02.E2E-1 | e2e | +| [US-2.1 — Link MT5 + vault](../../sprints/stories/US-2.1-link-mt5-account-with-encryption.md) | AC-2 | No API response or log contains plaintext password | TC-02.REG-1 | regression | +| [US-2.1 — Link MT5 + vault](../../sprints/stories/US-2.1-link-mt5-account-with-encryption.md) | AC-3 | Account routed to ONLINE node via selectAvailableNode | TC-02.E2E-1 | e2e | +| [US-2.1 — Link MT5 + vault](../../sprints/stories/US-2.1-link-mt5-account-with-encryption.md) | AC-4 | Vault uses AES-256-GCM, key from CREDENTIAL_ENCRYPTION_KEY | TC-02.REG-1 | regression | +| [US-2.1 — Link MT5 + vault](../../sprints/stories/US-2.1-link-mt5-account-with-encryption.md) | AC-5 | Duplicate-login advisory when same MT5 login used twice | — | **gap** | +| [US-2.6 — Credential verify](../../sprints/stories/US-2.6-mt5-credential-verification.md) | AC-1 | Node Manager validates credentials before persisting Mt5Account | TC-02.E2E-1 | e2e | +| [US-2.8 — Health status](../../sprints/stories/US-2.8-mt5-account-health-status.md) | AC-1 | terminalStatus=ACTIVE surfaced within 60s of provision | TC-02.E2E-1, TC-02.SMK-1 | e2e, smoke | +| [US-2.9 — Unlink + decommission](../../sprints/stories/US-2.9-unlink-mt5-account.md) | — | Soft-delete Mt5Account + decommission terminal cleanly | — | **gap (entire story)** | + +## Open / deferred scenarios + +- **Duplicate-login advisory check** — covers `US-2.1/AC-5`. Owner: `@qa-lead`. Target: sprint-2026-W21. +- **Unlink + decommission flow** — covers all `US-2.9` ACs. Owner: `@qa-lead`. Target: sprint-2026-W21. +- **TC-02.PRF-1 link p95 latency** — deferred until EPIC-02 publishes a perf budget (currently undefined). +```` + +--- + +## 7. Cross-references + +- [Story template](story.md) — §11 Verification commands stays the + source of truth for per-AC runnable checks. +- [Epic template](epic.md) — §13 Cross-story testing requirements + declares shared fixtures + harnesses that test-cases scenarios import. +- [Test report template](test-report.md) — execution log for these + scenarios (per-run + per-release). +- [Sprint system](../sprint-system.md) — where test-cases fit in the + agile flow (Test artifacts section). diff --git a/.agents/skills/koni-docs/references/templates/test-report.md b/.agents/skills/koni-docs/references/templates/test-report.md new file mode 100644 index 00000000000..eb2c5635edf --- /dev/null +++ b/.agents/skills/koni-docs/references/templates/test-report.md @@ -0,0 +1,406 @@ +# Test Report — Full Template + +> **File locations**: +> - Per-execution: `docs/tests/test-reports/runs/YYYY-MM-DD-EPIC-N-runN.md` +> - Per-release: `docs/tests/test-reports/releases/vX.Y.Z.md` +> +> **Use when**: A test-cases file ([template](test-cases.md)) has just been +> executed (manually or by CI), or a release is shipping and needs an +> aggregate test summary that ships alongside CHANGELOG.md. +> +> **One rule above all others**: a test report is **append-only history**. +> Once a run is recorded, never edit the result fields — if you re-run the +> same scenario, create a NEW run file (`-run2`, `-run3`, …). The reason a +> test failed and was then re-run is itself a signal worth preserving. + +--- + +## 1. Two sub-templates in this file + +| Sub-template | Path | Purpose | +|---|---|---| +| **A. Per-execution detail** | `test-reports/runs/YYYY-MM-DD-EPIC-N-runN.md` | One file per test run. Captures who ran what, in which env, against which commit, and the pass/fail of each TC. | +| **B. Per-release master** | `test-reports/releases/vX.Y.Z.md` | One file per shipped version. Aggregates run files into a release-level summary, linked from CHANGELOG.md. | + +Both share frontmatter conventions (date / commit / version) but differ in +body: A is granular (per-TC results + failure detail), B is aggregate +(epic-by-epic coverage + outstanding risks). + +--- + +## 2. Sub-template A — Per-execution detail + +### A.1 File location & naming + +`docs/tests/test-reports/runs/YYYY-MM-DD-EPIC-N-runN.md` + +- `YYYY-MM-DD` is the run date in local TZ. +- `EPIC-N` is the epic whose test-cases were executed. +- `runN` increments per (date, epic) pair starting at `run1`. If a 2nd run + happens on the same day for the same epic, it is `run2`. + +Examples: +- `2026-05-15-EPIC-02-run1.md` — first run of EPIC-02 tests on 2026-05-15 +- `2026-05-15-EPIC-02-run2.md` — re-run after fixing a flake +- `2026-05-15-EPIC-03-run1.md` — separate epic same day + +### A.2 Full skeleton + +````markdown +--- +epic: EPIC-N +run_id: YYYY-MM-DD-EPIC-N-runN +run_at: YYYY-MM-DDTHH:MM:SS+07:00 # ISO 8601 with timezone +env: staging # local | staging | production +version: vX.Y.Z # release / build tag at run time +commit: <full git SHA> +executor: <name or "CI"> +trigger: per-release # per-PR | per-release | per-sprint | nightly | on-demand +--- + +## Summary + +| Result | Count | +|---|---| +| Pass | N | +| Fail | N | +| Skip | N | +| **Total** | N | + +**Duration:** <human-readable, e.g. "12m 34s"> + +## Scope + +<1–3 sentences: which TC IDs were in scope this run, and which were +intentionally skipped (with reason). Link to the test-cases file.> + +- Source: [docs/tests/test-cases/EPIC-N.md](../../test-cases/EPIC-N.md) +- In scope: `TC-N.E2E-*`, `TC-N.SMK-*` +- Skipped: `TC-N.PRF-*` (perf budget not yet defined — see test-cases §7) + +## Results per test-case + +| TC ID | Result | Duration | Notes / log link | +|---|---|---|---| +| TC-N.E2E-1 | ✅ pass | 2m 15s | — | +| TC-N.E2E-2 | ❌ fail | 1m 02s | See §Failures TC-N.E2E-2 | +| TC-N.REG-1 | ✅ pass | 0m 30s | — | +| TC-N.SMK-1 | ⏭️ skip | — | env=staging; smoke runs only on local | + +## Failures (detail) + +<One subsection per failing TC. Include enough detail that a different +person can reproduce the failure without asking the executor. Attach +screenshots / logs via MinIO presigned URL when applicable.> + +### TC-N.E2E-2 + +- **Expected:** <observable outcome from the Gherkin Then-clause> +- **Actual:** <what happened instead> +- **Reproduction:** + 1. <step> + 2. <step> +- **Logs:** [minio://test-artifacts/runs/<run_id>/TC-N.E2E-2.log](minio://...) +- **Screenshots:** [minio://test-artifacts/runs/<run_id>/TC-N.E2E-2.png](minio://...) +- **Suspected cause:** <hypothesis> +- **Triage status:** open / re-run scheduled / bug filed (link issue) + +## Follow-up + +<Actions the run surfaced. Bug tickets opened, LESSONS.md entries +appended, scenarios moved to deferred, regressions to add to the +test-cases file.> + +- Bug filed: [#<issue>](<URL>) — root cause for TC-N.E2E-2. +- LESSONS.md §M appended — flaky retry pattern. +- TC-N.PRF-1 promoted from §7 deferred to §5 in test-cases file (now ready). +```` + +### A.3 Per-section guidance + +#### Frontmatter + +- `run_at` ISO 8601 with timezone — never bare local time. The run log is + used for forensics; "what time zone was this in?" is a question we + refuse to entertain. +- `commit` is the SHA of the deployed build, not of the test-cases file. + These can diverge. +- `executor` is a GitHub login or `"CI"`. Never anonymous. +- `trigger` matches one of the cadence values in the test-cases file + §Cadence & ownership. + +#### Summary + +- Total must equal Pass + Fail + Skip — sanity check by reviewers. +- Duration is the wall-clock end-to-end, not the sum of per-TC durations. + +#### Scope + +- Always link to the test-cases file. Reviewers need to navigate to the + Gherkin scenarios without grepping. +- Skipped TCs must state WHY (env mismatch, deferred, broken fixture). + An unexplained skip is a smell. + +#### Results per test-case + +- Result emojis are stable across the system: `✅ pass / ❌ fail / ⏭️ skip / ⚠️ flake`. +- Use `⚠️ flake` for tests that pass on retry — adds visibility to + flakiness without inflating fail count. +- "Notes / log link" — for pass, leave `—`. For fail, point to §Failures. + +#### Failures + +- Reproducibility is non-negotiable. A failure without reproduction steps + is not a useful report. +- Log + screenshot URLs should resolve from MinIO (preferred) or any + durable artifact store. Avoid local file paths. + +#### Follow-up + +- Append-only. If the bug is later fixed, the fix goes into the NEXT run + file's Summary, not by editing this report. + +--- + +## 3. Sub-template B — Per-release master + +### B.1 File location & naming + +`docs/tests/test-reports/releases/vX.Y.Z.md` + +- `vX.Y.Z` is the semver from [VERSION](../../../../VERSION) at release time. +- Created at the same commit as the CHANGELOG entry — link both ways. + +### B.2 Full skeleton + +````markdown +--- +version: vX.Y.Z +released_at: YYYY-MM-DD +commit: <SHA of the release commit> +epics_covered: [EPIC-N, EPIC-M] +ship_status: shipped # shipped | held | rolled-back +--- + +## Release scope + +<2–4 sentences naming the stories shipped in this version and the epics +they belong to. Link to CHANGELOG.md vX.Y.Z section for the canonical +list. Do not duplicate the changelog body here.> + +- See [CHANGELOG.md vX.Y.Z](../../../CHANGELOG.md#vxyz) +- Stories shipped: [US-X.Y](../../stories/US-X.Y-<slug>.md), [US-X.Z](../../stories/US-X.Z-<slug>.md) + +## Test coverage + +| Epic | Total TC | Pass | Fail | Skip | Coverage % | Notes | +|---|---|---|---|---|---|---| +| [EPIC-N](../../epics/EPIC-N.md) | N | N | 0 | N | M% | <one-line caveat> | +| [EPIC-M](../../epics/EPIC-M.md) | N | N | 0 | N | M% | — | + +> **Coverage % formula:** `pass / (total − skip)` rounded to integer. +> Skipped TCs are not counted as failures, but the Notes column must +> explain why they were skipped at release time. + +## Run history (links to per-execution detail) + +<Append every run that contributed to this release decision. Most +recent first.> + +- [2026-05-15-EPIC-02-run2](../runs/2026-05-15-EPIC-02-run2.md) — ✅ pass (re-run after fixing flake) +- [2026-05-15-EPIC-02-run1](../runs/2026-05-15-EPIC-02-run1.md) — ⚠️ 1 flake (TC-02.E2E-2) +- [2026-05-14-EPIC-03-run1](../runs/2026-05-14-EPIC-03-run1.md) — ✅ pass + +## Outstanding risks + +<Risks that DID NOT block this release but the team must track. Each +item names an owner + a target sprint or release to resolve.> + +- **TC-N.E2E-2 flaked once on Safari** — owner `@qa-lead`. Target: next sprint. See LESSONS §N. +- **TC-N.PRF-1 not run** — owner `@infra-lead`. Target: v0.X+1 once perf budget defined in [EPIC-N §14](../../epics/EPIC-N.md#performance-budgets--invariants). + +## Ship decision + +<One short paragraph: who signed off, what was the risk calculation, +any conditions attached to the release.> + +- **Signed off by:** `<name>` (`<role>`) on YYYY-MM-DD +- **Conditions:** <e.g. "monitor EPIC-02 health dashboards for 48h, rollback trigger: >2% terminalStatus=UNREACHABLE"> +```` + +### B.3 Per-section guidance + +#### Frontmatter + +- `ship_status` lifecycle: `held` while QA is running, `shipped` after + go-decision, `rolled-back` if reverted. Never delete the file — flip + the status. +- `epics_covered` lists ONLY the epics whose stories shipped in this + version. Epics whose tests were re-run but had no story change do not + belong here. + +#### Test coverage + +- One row per epic touched by the release. +- A row with 0 total TC indicates the epic has no test-cases file yet — + flag in Notes (`test-cases file pending`). +- Coverage % is a directional metric, not a contract. A release can ship + at <100% if Outstanding risks documents the gap and the ship decision + accepts it. + +#### Run history + +- Append-only, most-recent-first. The history is a forensic trail. +- A re-run after a flake is fine; do not delete the flake run. + +#### Outstanding risks + +- This section is the most-read by stakeholders. Be specific. "Some + tests flaky" is not a risk; "TC-02.E2E-2 flaked once on Safari due to + Dexie race; LESSONS §6" is. + +#### Ship decision + +- One named person signs off. "The team decided" is not accountability. +- Conditions are runtime triggers, not promises ("we'll watch closely" + is not a condition; "rollback if >2% terminalStatus=UNREACHABLE in 48h" + is). + +--- + +## 4. Conventions shared across both sub-templates + +### Result symbols + +| Symbol | Meaning | +|---|---| +| ✅ | pass | +| ❌ | fail (final) | +| ⏭️ | skip (with reason) | +| ⚠️ | flake (passed on retry within same run) | +| 🚫 | blocked (could not run — missing precondition, broken env) | + +### Append-only discipline + +- NEVER edit a past run's results. Errors? Add a new run. +- NEVER edit a past release's `ship_status` retroactively — except + `shipped → rolled-back` (which is itself an event worth dating in the + Ship decision paragraph). + +### Cross-link contract + +| From | To | Purpose | +|---|---|---| +| Run file `Scope` | `test-cases/EPIC-N.md` | Gherkin scenarios | +| Run file `Failures` | MinIO logs/screenshots | Reproducibility | +| Release file `Release scope` | `CHANGELOG.md#vX.Y.Z` | Story list | +| Release file `Run history` | Run files | Forensic trail | +| Release file `Outstanding risks` | `LESSONS.md §N`, `EPIC-N §14` | Risk context | +| `CHANGELOG.md#vX.Y.Z` | Release file | Reverse pointer (optional, phase 2) | + +### English-only (RULE-13) + +Both sub-templates are canonical docs and must be English-only, even +on Vietnamese-led projects. + +--- + +## 5. Filled mini-example — Per-execution + +````markdown +--- +epic: EPIC-02 +run_id: 2026-05-15-EPIC-02-run1 +run_at: 2026-05-15T14:30:00+07:00 +env: staging +version: v0.2.0 +commit: 1fe8d25a4b3c2e9f6d8a1b2c3d4e5f6a7b8c9d0e +executor: bluezdot +trigger: per-release +--- + +## Summary + +| Result | Count | +|---|---| +| Pass | 2 | +| Fail | 0 | +| Skip | 1 | +| **Total** | 3 | + +**Duration:** 4m 12s + +## Scope + +EPIC-02 release readiness for v0.2.0. All P0 e2e + regression TCs run. +Smoke skipped (only runs in PR CI). + +- Source: [docs/tests/test-cases/EPIC-02.md](../../test-cases/EPIC-02.md) +- In scope: `TC-02.E2E-1`, `TC-02.REG-1` +- Skipped: `TC-02.SMK-1` (env=staging; smoke is PR-only) + +## Results per test-case + +| TC ID | Result | Duration | Notes / log link | +|---|---|---|---| +| TC-02.E2E-1 | ✅ pass | 3m 42s | — | +| TC-02.REG-1 | ✅ pass | 0m 30s | — | +| TC-02.SMK-1 | ⏭️ skip | — | env mismatch (PR-only) | + +## Follow-up + +- None. Release proceeds. +```` + +--- + +## 6. Filled mini-example — Per-release + +````markdown +--- +version: v0.2.0 +released_at: 2026-05-15 +commit: 1fe8d25a4b3c2e9f6d8a1b2c3d4e5f6a7b8c9d0e +epics_covered: [EPIC-02] +ship_status: shipped +--- + +## Release scope + +v0.2.0 ships the EPIC-02 MT5 account linking + terminal provisioning +slice: link with AES-256 vault, credential verification, terminal +provisioning, health badge, unlink + decommission. + +- See [CHANGELOG.md v0.2.0](../../../CHANGELOG.md#v020) +- Stories shipped: [US-2.6](../../stories/US-2.6-mt5-credential-verification.md), [US-2.7](../../stories/US-2.7-terminal-provisioning.md), [US-2.8](../../stories/US-2.8-mt5-account-health-status.md), [US-2.9](../../stories/US-2.9-unlink-mt5-account.md) + +## Test coverage + +| Epic | Total TC | Pass | Fail | Skip | Coverage % | Notes | +|---|---|---|---|---|---|---| +| [EPIC-02](../../epics/EPIC-02.md) | 3 | 2 | 0 | 1 | 100% | Smoke skipped (PR-only); 2 gaps tracked in test-cases §7 | + +## Run history + +- [2026-05-15-EPIC-02-run1](../runs/2026-05-15-EPIC-02-run1.md) — ✅ pass + +## Outstanding risks + +- **TC-02 coverage gaps** — US-2.1/AC-5 (duplicate-login advisory) and US-2.9 (unlink flow) lack TCs. Owner `@qa-lead`. Target: sprint-2026-W21. +- **TC-02.PRF-1 deferred** — perf budget undefined. Target: EPIC-02 §14 update + sprint-2026-W22. + +## Ship decision + +- **Signed off by:** `bluezdot` (project owner) on 2026-05-15 +- **Conditions:** Monitor `terminalStatus=UNREACHABLE` rate on Grafana for 48h post-deploy. Rollback if >2% over baseline. +```` + +--- + +## 7. Cross-references + +- [Test cases template](test-cases.md) — source of scenarios run here. +- [Changelog template](changelog.md) — release file links from + CHANGELOG.md vX.Y.Z section. +- [Lessons template](lessons.md) — outstanding risks often spawn lessons. +- [Sprint system](../sprint-system.md) §Test artifacts — workflow context. diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 00000000000..4741eff2819 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "koni-docs": { + "source": "Koniverse/Koni-Skills", + "sourceType": "github", + "skillPath": "skills/koni-docs/SKILL.md", + "computedHash": "15c58d122bc47feaba6cc2b709d924be1568163abc0d038b19013eb75a4e47a8" + } + } +} From 094e5d5e883a5e8fb3419cd8e5b4b94d0c76a672 Mon Sep 17 00:00:00 2001 From: AnhMTV <maithachvietanh@gmail.com> Date: Tue, 2 Jun 2026 17:27:43 +0700 Subject: [PATCH 04/67] chore: add VERSION file and gitignore active-context --- .gitignore | 3 +++ VERSION | 1 + 2 files changed, 4 insertions(+) create mode 100644 VERSION diff --git a/.gitignore b/.gitignore index 4f5e345b3a4..0f547091acf 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ test.ts /packages/extension-koni-base/src/test-api/ /packages/extension-base/src/koni/test-api/ /packages/extension-base/src/services/chain-service/chain.spec.ts + +# Koni-docs Active Context (per-developer, Pattern B) +.active-context.md diff --git a/VERSION b/VERSION new file mode 100644 index 00000000000..d0e1d319de2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.3.79 From a1210d8f0d7d458590a26a29854b202c8606c848 Mon Sep 17 00:00:00 2001 From: AnhMTV <maithachvietanh@gmail.com> Date: Tue, 2 Jun 2026 17:31:14 +0700 Subject: [PATCH 05/67] docs: add active-context example template (koni Pattern B) --- .active-context.example.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .active-context.example.md diff --git a/.active-context.example.md b/.active-context.example.md new file mode 100644 index 00000000000..8230d4a9369 --- /dev/null +++ b/.active-context.example.md @@ -0,0 +1,28 @@ +# Active Context (Example Template) + +> **Per-developer file.** Copy this to `.active-context.md` (gitignored) on +> first checkout and update as you work. This `.example.md` version is the +> committed template — keep it minimal and generic. + +## Sprint +<!-- Active sprint id, e.g. sprint-2026-W23 — leave empty until sub-task 2 starts sprints --> +Sprint: + +## Active Stories +<!-- One bullet per story you're actively working on (US-X.Y-slug) --> +- + +## Last Version Shipped +<!-- Bare semver, no v-prefix (per RULE-16) --> +Last Version: 1.3.79 + +## Recent Decisions +<!-- Pointer to recent CONTEXT.md entries you authored or rely on --> +- + +## Recent Lessons +<!-- Pointer to recent LESSONS.md entries relevant to current work --> +- + +## Personal Notes +<!-- Free-form scratch space; not consumed by koni-docs sync --> From 3365b4a4c39b5dbf930e49de3796439922496306 Mon Sep 17 00:00:00 2001 From: AnhMTV <maithachvietanh@gmail.com> Date: Tue, 2 Jun 2026 17:34:46 +0700 Subject: [PATCH 06/67] docs: add AGENTS.md (canonical AI agent guide for SubWallet) --- AGENTS.md | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..4b9bde89bc7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,147 @@ +# AGENTS.md — SubWallet-Extension + +> **This file is the single source of truth for all AI agent instructions in this project.** +> Cursor, Gemini, Codex CLI, Copilot CLI, and Claude Code all read it. +> [`CLAUDE.md`](CLAUDE.md) is a thin pointer back to this file plus the +> Koni-Docs Integration block and an Active Context pointer. +> On any conflict between AGENTS.md and CLAUDE.md, AGENTS.md wins. + +## 1. Project purpose + +SubWallet-Extension is a non-custodial multi-chain wallet delivered as a +browser extension and web app. It supports Substrate (Polkadot / Kusama +ecosystem), EVM (Ethereum, Base, Arbitrum, …), Bitcoin, and TON. The +codebase is a Yarn 3 monorepo of TypeScript packages: background services +(account / balance / chain / earning / NFT / staking / transaction), a +React UI (extension popup + full-page web app), and shared message bus +layer between them. + +## 2. Monorepo layout + +| Package | Purpose | Top dependencies | +|---|---|---| +| `@subwallet/extension-base` | Functions, classes and other utilities used in @subwallet/extension | @acala-network/api, @apollo/client, @azns/resolver-core | +| `@subwallet/extension-chains` | Definitions for all known chains as exposed by the extension. | @babel/runtime, @polkadot/networks, @polkadot/util | +| `@subwallet/extension-compat-metamask` | Metamask compatibility layer | @babel/runtime, @metamask/detect-provider, @polkadot/types | +| `@subwallet/extension-dapp` | Provides an interfaces around the injected globals for ease of access by dapp developers. | @babel/runtime, @polkadot/util, @polkadot/util-crypto | +| `@subwallet/extension-inject` | A generic injector (usable to any extension), that populates the base exposed interfaces to be used by dapps. | @babel/runtime, @polkadot/rpc-provider, @polkadot/types | +| `@subwallet/extension-koni-ui` | A sample signer extension for the @polkadot/api | @babel/runtime, @coinbase/cbpay-js, @fortawesome/fontawesome-svg-core | +| `@subwallet/extension-koni` | A sample signer extension for the @polkadot/api | @babel/runtime, @emurgo/cardano-serialization-lib-browser, @subwallet/extension-base | +| `@subwallet/extension-mocks` | Definitions for all known chains as exposed by the extension. | @babel/runtime, sinon-chrome | +| `@subwallet/extension-web-ui` | A sample signer extension for the @polkadot/api | @babel/runtime, @coinbase/cbpay-js, @fortawesome/fontawesome-svg-core | +| `@subwallet/subsquare-api-sdk` | Subsquare API SDK for Subwallet | @polkadot/util, axios | +| `@subwallet/web-runner` | A web runner simulate background.js can be use on mobile or environment support web view | @babel/runtime, @emurgo/cardano-serialization-lib-browser, @subwallet/extension-base | +| `@subwallet/webapp` | A web runner simulate background.js can be use on mobile or environment support web view | @babel/runtime, @emurgo/cardano-serialization-lib-browser, @subwallet/extension-base | + +For full details, see each package's `README.md` (where present) and +`package.json`. + +## 3. Tech stack + +- **Node:** `.nvmrc` → 12 (build target). Local shell may be newer (Node 18+ recommended for dev). +- **Package manager:** Yarn 3 (berry), workspaces. Never use npm install. +- **Language:** TypeScript (strict mode in most packages). +- **UI:** React 18, styled-components, react-router. +- **Blockchain libs:** `@polkadot/api`, `@polkadot/keyring`, `ethers`, `web3`, `@ton/core`, `bitcoinjs-lib`. +- **Storage:** IndexedDB via `dexie`. +- **Build:** Webpack 5 for extension; separate build for web-runner and webapp. +- **Lint / format:** ESLint (config: `.eslintrc.js`), Prettier (`.prettierrc.cjs`). +- **CI:** GitHub Actions (`.github/workflows/`). + +## 4. Build / dev commands + +| Goal | Command | +|---|---| +| Install dependencies | `yarn install` | +| Build all packages | `yarn build` | +| Build extension only | `yarn webpack:build:extension` | +| Watch extension during dev | `yarn webpack:watch:extension` | +| Start web app dev server | `yarn webpack:dev:webapp` | +| Build webapp | `yarn webpack:build:webapp` | +| Run lint | `yarn lint` | +| Run tests | `yarn test` | + +See `package.json` (root) `scripts` for the full list, and `CONTRIBUTING.md` +for the contributor workflow. + +## 5. Conventions + +- **Branch naming:** `koni/dev/issue-<number>` for feature/bug branches + tied to GitHub issues; `koni/dev/<short-slug>` for branches without a + ticket. AI-driven branches use `ai-development` or `ai-<scope>`. +- **Commit prefix (RULE-14):** `feat:` / `fix:` / `chore:` / `docs:` / + `style:` / `refactor:` / `test:`. Subject in imperative mood, English. +- **Language (RULE-13):** All code, comments, UI strings, error messages, + commit messages, and docs are English. Localization happens via + `public/locales/` translation bundles, not inline. +- **PR template:** `.github/PULL_REQUEST_TEMPLATE/resolve-issue.md` — fill + every section, link the issue, attach screenshots for UI changes. +- **Versioning:** Root `package.json` carries the user-facing semver + (currently `1.3.79`). `packages/*` carry a per-monorepo internal version + with `-N` suffix (currently `1.3.79-1`). The canonical user-facing + version also lives in `VERSION` (repo root, per koni-docs §0). + +## 6. Documentation + +Current docs at the repo root: + +- `README.md` — quickstart + feature overview +- `CONTRIBUTING.md` — contributor workflow +- `CHANGELOG.md` — release history +- `BOUNTIES.md` — open bounties for contributors +- `LICENSE` — Apache-2.0 +- `VERSION` — canonical semver (= root `package.json` version) + +Canonical `docs/` content per koni-docs spec (BRIEF, PRD, ARCHITECTURE, +CONTEXT, LESSONS, SETUP, sprints/, CHANGELOG) is **pending sub-task 2**. +Until then, koni-docs `RULE-1` / `RULE-2` (VERSION + CHANGELOG in same +commit) enforcement is deferred for this repo. + +GitHub issue → story/epic migration is **pending sub-task 3**. + +## 7. Koniverse pipeline + +This repo follows the Koniverse product development pipeline: + +``` +BRAINSTORM → BRIEF → PRD → ARCH → EPIC/US → DESIGN → REVIEW → QA → IMPLEMENT → COMMIT/DOCS + BMAD BMAD BMAD BMAD BMAD GSTACK GSTACK GSTACK SUPERPOWERS KONI-DOCS +``` + +Koni-docs is the **final standardization stage**: it maps planning +artifacts produced by upstream tools into the canonical `docs/` structure +and enforces the 12 core rules. + +## 8. Active context pattern (Pattern B — file-extracted) + +Live per-developer state lives in `.active-context.md` (gitignored). The +committed template is `.active-context.example.md`. On first checkout: + +```bash +cp .active-context.example.md .active-context.md +``` + +Update `.active-context.md` as you work — sprint, active stories, recent +decisions, recent lessons. It is consumed by koni-docs status / sync +commands. + +## 9. Skill quick reference + +| Skill | Triggers on | +|---|---| +| `koni-docs` | Update docs, create story, record decision, log lesson, write changelog entry, document architecture, run pre-commit doc checklist | + +Additional skills (e.g. `koni-api`, plugin skills for Supabase / Next.js) +may be added to `skills-lock.json` over time. + +## 10. Out of scope for sub-task 1 + +The following are tracked in separate sub-tasks and **not** part of this +branch (`ai-development`): + +- Authoring `docs/BRIEF.md`, `docs/PRD.md`, `docs/ARCHITECTURE.md`, + `docs/CONTEXT.md`, `docs/LESSONS.md`, `docs/SETUP.md`, + `docs/CHANGELOG.md`, `docs/sprints/` — **sub-task 2**. +- Migrating GitHub issues to stories/epics — **sub-task 3**. +- Reconciling `packages/*` version suffix (`-N`) with root VERSION — + **sub-task 2**. From 76ff80b6f06c821a9412893d6ed50d8098ce7ab3 Mon Sep 17 00:00:00 2001 From: AnhMTV <maithachvietanh@gmail.com> Date: Tue, 2 Jun 2026 17:39:22 +0700 Subject: [PATCH 07/67] fix: correct yarn commands in AGENTS.md (use real package.json scripts) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- AGENTS.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4b9bde89bc7..d1d0e958b88 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,10 +54,12 @@ For full details, see each package's `README.md` (where present) and |---|---| | Install dependencies | `yarn install` | | Build all packages | `yarn build` | -| Build extension only | `yarn webpack:build:extension` | -| Watch extension during dev | `yarn webpack:watch:extension` | -| Start web app dev server | `yarn webpack:dev:webapp` | -| Build webapp | `yarn webpack:build:webapp` | +| Build extension UI | `yarn build:ui` | +| Watch extension during dev | `yarn watch-dev` | +| Start webapp dev server | `yarn webapp:dev` | +| Build webapp | `yarn webapp:build` | +| Start web-runner dev server | `yarn web-runner:dev` | +| Build web-runner | `yarn web-runner:build` | | Run lint | `yarn lint` | | Run tests | `yarn test` | @@ -103,7 +105,7 @@ GitHub issue → story/epic migration is **pending sub-task 3**. This repo follows the Koniverse product development pipeline: -``` +```text BRAINSTORM → BRIEF → PRD → ARCH → EPIC/US → DESIGN → REVIEW → QA → IMPLEMENT → COMMIT/DOCS BMAD BMAD BMAD BMAD BMAD GSTACK GSTACK GSTACK SUPERPOWERS KONI-DOCS ``` From 7a49a007a70f092ed3cc45cfeb2582095f25bdbf Mon Sep 17 00:00:00 2001 From: AnhMTV <maithachvietanh@gmail.com> Date: Tue, 2 Jun 2026 17:41:02 +0700 Subject: [PATCH 08/67] docs: add CLAUDE.md pointer with koni-docs integration block --- CLAUDE.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..dadd48e2a01 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,24 @@ +# CLAUDE.md + +> This file is a thin pointer. **AGENTS.md is canonical.** +> On any conflict between AGENTS.md and CLAUDE.md, AGENTS.md wins. + +See [AGENTS.md](AGENTS.md) for the full project guide. + +## Koni-Docs Integration +koni-docs: + plugins: [] + docs_path: docs/ + active_sprint: <TBD-after-sub-task-2> + version_file: VERSION + +## Active Context <!-- koni-docs:auto-update --> +- Sprint: (not started — pending sub-task 2) +- Active Stories: +- Last Version: 1.3.79 +- Recent Decisions: +- Recent Lessons: +<!-- /koni-docs:auto-update --> + +See `.active-context.md` (gitignored, per-developer) for live snapshot; +copy from `.active-context.example.md` on first checkout. From 821bb9e4efa7bfb63dd5ac502e93c8d851163b6c Mon Sep 17 00:00:00 2001 From: AnhMTV <maithachvietanh@gmail.com> Date: Wed, 3 Jun 2026 10:14:11 +0700 Subject: [PATCH 09/67] docs: add AI coding behavior guidelines as section 1 of AGENTS.md Adapted from multica-ai/andrej-karpathy-skills CLAUDE.md. Adds 4 rules (Think Before Coding, Simplicity First, Surgical Changes, Goal-Driven Execution) at the top so AI agents see them before project-specific context. Existing sections renumbered 1-10 -> 2-11; no content changes. --- AGENTS.md | 86 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d1d0e958b88..360393aee42 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,73 @@ > Koni-Docs Integration block and an Active Context pointer. > On any conflict between AGENTS.md and CLAUDE.md, AGENTS.md wins. -## 1. Project purpose +## 1. AI coding behavior guidelines + +> Adapted from [multica-ai/andrej-karpathy-skills · CLAUDE.md](https://github.com/multica-ai/andrej-karpathy-skills/blob/main/CLAUDE.md). +> Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. +> +> **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. + +### 1.1 Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them — don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. + +### 1.2 Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +### 1.3 Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it — don't delete it. + +When your changes create orphans: +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +### 1.4 Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" + +For multi-step tasks, state a brief plan: + +```text +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. + +**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes. + +## 2. Project purpose SubWallet-Extension is a non-custodial multi-chain wallet delivered as a browser extension and web app. It supports Substrate (Polkadot / Kusama @@ -16,7 +82,7 @@ codebase is a Yarn 3 monorepo of TypeScript packages: background services React UI (extension popup + full-page web app), and shared message bus layer between them. -## 2. Monorepo layout +## 3. Monorepo layout | Package | Purpose | Top dependencies | |---|---|---| @@ -36,7 +102,7 @@ layer between them. For full details, see each package's `README.md` (where present) and `package.json`. -## 3. Tech stack +## 4. Tech stack - **Node:** `.nvmrc` → 12 (build target). Local shell may be newer (Node 18+ recommended for dev). - **Package manager:** Yarn 3 (berry), workspaces. Never use npm install. @@ -48,7 +114,7 @@ For full details, see each package's `README.md` (where present) and - **Lint / format:** ESLint (config: `.eslintrc.js`), Prettier (`.prettierrc.cjs`). - **CI:** GitHub Actions (`.github/workflows/`). -## 4. Build / dev commands +## 5. Build / dev commands | Goal | Command | |---|---| @@ -66,7 +132,7 @@ For full details, see each package's `README.md` (where present) and See `package.json` (root) `scripts` for the full list, and `CONTRIBUTING.md` for the contributor workflow. -## 5. Conventions +## 6. Conventions - **Branch naming:** `koni/dev/issue-<number>` for feature/bug branches tied to GitHub issues; `koni/dev/<short-slug>` for branches without a @@ -83,7 +149,7 @@ for the contributor workflow. with `-N` suffix (currently `1.3.79-1`). The canonical user-facing version also lives in `VERSION` (repo root, per koni-docs §0). -## 6. Documentation +## 7. Documentation Current docs at the repo root: @@ -101,7 +167,7 @@ commit) enforcement is deferred for this repo. GitHub issue → story/epic migration is **pending sub-task 3**. -## 7. Koniverse pipeline +## 8. Koniverse pipeline This repo follows the Koniverse product development pipeline: @@ -114,7 +180,7 @@ Koni-docs is the **final standardization stage**: it maps planning artifacts produced by upstream tools into the canonical `docs/` structure and enforces the 12 core rules. -## 8. Active context pattern (Pattern B — file-extracted) +## 9. Active context pattern (Pattern B — file-extracted) Live per-developer state lives in `.active-context.md` (gitignored). The committed template is `.active-context.example.md`. On first checkout: @@ -127,7 +193,7 @@ Update `.active-context.md` as you work — sprint, active stories, recent decisions, recent lessons. It is consumed by koni-docs status / sync commands. -## 9. Skill quick reference +## 10. Skill quick reference | Skill | Triggers on | |---|---| @@ -136,7 +202,7 @@ commands. Additional skills (e.g. `koni-api`, plugin skills for Supabase / Next.js) may be added to `skills-lock.json` over time. -## 10. Out of scope for sub-task 1 +## 11. Out of scope for sub-task 1 The following are tracked in separate sub-tasks and **not** part of this branch (`ai-development`): From 410ac82aa122d990032c47c203cc46eca8d87737 Mon Sep 17 00:00:00 2001 From: AnhMTV <maithachvietanh@gmail.com> Date: Wed, 3 Jun 2026 14:27:28 +0700 Subject: [PATCH 10/67] feat(docs): add design spec for Koni-docs migration (sub-tasks 2 + 3) and remove logo image --- AGENTS.md | 19 +- docs/.nojekyll | 0 docs/extension-overview.png | Bin 199063 -> 0 bytes docs/favicon.ico | Bin 15086 -> 0 bytes docs/index.html | 215 ----- docs/logo.jpg | Bin 14434 -> 0 bytes .../2026-06-03-koni-docs-migration-design.md | 830 ++++++++++++++++++ 7 files changed, 838 insertions(+), 226 deletions(-) delete mode 100644 docs/.nojekyll delete mode 100644 docs/extension-overview.png delete mode 100644 docs/favicon.ico delete mode 100644 docs/index.html delete mode 100644 docs/logo.jpg create mode 100644 docs/superpowers/specs/2026-06-03-koni-docs-migration-design.md diff --git a/AGENTS.md b/AGENTS.md index 360393aee42..62bd0179f00 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,17 +2,12 @@ > **This file is the single source of truth for all AI agent instructions in this project.** > Cursor, Gemini, Codex CLI, Copilot CLI, and Claude Code all read it. -> [`CLAUDE.md`](CLAUDE.md) is a thin pointer back to this file plus the +> [CLAUDE.md](./CLAUDE.md) is a thin pointer back to this file plus the > Koni-Docs Integration block and an Active Context pointer. > On any conflict between AGENTS.md and CLAUDE.md, AGENTS.md wins. ## 1. AI coding behavior guidelines -> Adapted from [multica-ai/andrej-karpathy-skills · CLAUDE.md](https://github.com/multica-ai/andrej-karpathy-skills/blob/main/CLAUDE.md). -> Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. -> -> **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. - ### 1.1 Think Before Coding **Don't assume. Don't hide confusion. Surface tradeoffs.** @@ -85,7 +80,7 @@ layer between them. ## 3. Monorepo layout | Package | Purpose | Top dependencies | -|---|---|---| +| --- | --- | --- | | `@subwallet/extension-base` | Functions, classes and other utilities used in @subwallet/extension | @acala-network/api, @apollo/client, @azns/resolver-core | | `@subwallet/extension-chains` | Definitions for all known chains as exposed by the extension. | @babel/runtime, @polkadot/networks, @polkadot/util | | `@subwallet/extension-compat-metamask` | Metamask compatibility layer | @babel/runtime, @metamask/detect-provider, @polkadot/types | @@ -117,15 +112,17 @@ For full details, see each package's `README.md` (where present) and ## 5. Build / dev commands | Goal | Command | -|---|---| +| --- | --- | | Install dependencies | `yarn install` | | Build all packages | `yarn build` | | Build extension UI | `yarn build:ui` | -| Watch extension during dev | `yarn watch-dev` | | Start webapp dev server | `yarn webapp:dev` | -| Build webapp | `yarn webapp:build` | | Start web-runner dev server | `yarn web-runner:dev` | | Build web-runner | `yarn web-runner:build` | +| Build extension only | `yarn webpack:build:extension` | +| Watch extension during dev | `yarn watch-dev` | +| Start web app dev server | `yarn webpack:dev:webapp` | +| Build webapp | `yarn webapp:build` | | Run lint | `yarn lint` | | Run tests | `yarn test` | @@ -196,7 +193,7 @@ commands. ## 10. Skill quick reference | Skill | Triggers on | -|---|---| +| --- | --- | | `koni-docs` | Update docs, create story, record decision, log lesson, write changelog entry, document architecture, run pre-commit doc checklist | Additional skills (e.g. `koni-api`, plugin skills for Supabase / Next.js) diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/docs/extension-overview.png b/docs/extension-overview.png deleted file mode 100644 index 265237daaeca749d3067350b92bec1075d610128..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199063 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 g{r=BaxcTIuG+Z|Ct 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 X_U%-Ba8!5;3bHA^P?xKbF$?}5DeCp4 diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index f4cdb3ee02334dcd100dd673ff8da4254b338e7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 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 C;{t*J diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 79f6c0b69b8..00000000000 --- a/docs/index.html +++ /dev/null @@ -1,215 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Polkadot-js extension, manage accounts for substrate based chains - - - - - - - - - - - -
- -
-
- -

- This browser extension does one thing: it manages accounts and allows the signing of transactions with those accounts. It does not inject providers for use by dapps at this early point, nor does it perform wallet functions, - e.g send funds. -

- - - -

More info

-

This extension is open source and the code is available on GitHub.

-

For developers wanting to use the accounts from the extension in a Dapp, head to the developer documentation.

-
- - - diff --git a/docs/logo.jpg b/docs/logo.jpg deleted file mode 100644 index 5b28f46f65a3b643dbdde0d848724a8164d56d44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14434 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 export
Website scrape
Internal decks"] + REPO["📂 Repo files
packages/, README
CONTRIBUTING
package.json"] + GH["🐙 GitHub — ALL issues
open + closed + comments
+ linked PRs"] + GIT["📜 Git log — full history
commits + merges + tags"] + end + + subgraph STAGING["🗂 tmp/ (gitignored, never public)"] + direction TB + RAW["tmp/raw/
{notion, github, git}"] + INTAKES["⚙️ Intake-A (vision)
⚙️ Intake-B (operational, chunked)"] + CAN1(["tmp/canonical/vision-and-strategy.md"]) + CAN2(["tmp/canonical/product-history.md"]) + RECON["⚙️ Reconciliation"] + TAX(["tmp/canonical/taxonomy-v1.md
+ taxonomy-reconciliation-report.md"]) + RAW --> INTAKES + INTAKES --> CAN1 + INTAKES --> CAN2 + CAN1 --> RECON + CAN2 --> RECON + RECON --> TAX + end + + G1{"⚠ G1 — taxonomy review"} + + subgraph SA["🟦 Stream A — Foundation docs (Waves 1-3)"] + direction LR + A1["BRIEF"] + A2["PRD"] + A3["ARCH"] + A4["CONTEXT"] + A5["LESSONS"] + A6["SETUP"] + A7["CHANGELOG"] + end + + G2{"⚠ G2 — PRD review"} + + subgraph SB["🟩 Stream B — Issue → backlog (Waves 3-5)"] + direction LR + B1["B1 Triage"] + B2["B2 Epic"] + B3["B3 Story"] + end + + subgraph W6["🟪 Wave 6 — Sprint system bootstrap (interactive)"] + direction LR + I1["I1 + S6a:
packages/* version"] + I2["I2 + S6b + I2b + S6c:
past sprints"] + I3["I3 + S6d:
first active sprint"] + end + + G3{"⚠ G3 — final integration"} + + subgraph TGT["✅ TARGETS — committed to repo"] + direction LR + TDOCS["docs/{BRIEF,PRD,ARCH,
CONTEXT,LESSONS,SETUP}.md
CHANGELOG.md (root)"] + TSPR["docs/sprints/
{epics, stories, STATUS}
+ sprint-YYYY-WNN.md"] + end + + EXT --> RAW + GH --> RAW + GIT --> RAW + REPO --> SA + + TAX --> G1 + G1 --> SA + TAX --> SB + CAN1 --> SA + CAN2 --> SA + CAN2 --> SB + + A2 --> G2 + G2 --> B3 + + SA --> TDOCS + B3 --> W6 + W6 --> G3 + G3 --> TSPR + + classDef private fill:#ffe4e1,stroke:#d33,color:#000 + classDef public fill:#e0f2e0,stroke:#070,color:#000 + classDef pipeline fill:#fff3bf,stroke:#a80,color:#000 + classDef gate fill:#cfe2ff,stroke:#06c,color:#000 + classDef inputgate fill:#e9d5ff,stroke:#7c3aed,color:#000 + class EXT,RAW,CAN1,CAN2,TAX private + class REPO,TDOCS,TSPR public + class INTAKES,RECON,A1,A2,A3,A4,A5,A6,A7,B1,B2,B3 pipeline + class G1,G2,G3 gate + class I1,I2,I3,W6 inputgate +``` + +--- + +## 4. Source data + +Every downstream artifact must trace back to one of these five sources. No artifact may be invented without citation. + +```mermaid +flowchart LR + subgraph PRIV["🔒 Private sources"] + S1["Notion export"] + S2["Website scrape
(subwallet.app, koni.studio)"] + S3["Internal decks (optional)"] + end + + subgraph PUB["🟢 Public sources (repo + GH)"] + S4["Codebase
packages/*, configs"] + S5["Repo metadata
README, CONTRIBUTING
package.json, VERSION"] + S6["GitHub issues
open + closed + comments"] + S7["GitHub PRs linked
to issues"] + S8["Git log full history
commits + tags"] + end + + S1 --> N1["vision-and-strategy.md"] + S2 --> N1 + S3 --> N1 + + S6 --> N2["product-history.md"] + S7 --> N2 + S8 --> N2 + + S4 --> D1["ARCHITECTURE.md, SETUP.md"] + S5 --> D1 + S5 --> D2["CHANGELOG.md, PRD §FR"] + S8 --> D2 + + classDef priv fill:#ffe4e1,stroke:#d33 + classDef pub fill:#e0f2e0,stroke:#070 + class S1,S2,S3 priv + class S4,S5,S6,S7,S8 pub +``` + +Source authority ranking when conflicts arise (e.g., Notion says "we support Solana", codebase does not): + +```mermaid +flowchart LR + A["Codebase / git tag"] -->|"is reality"| B["product-history.md"] + C["Notion / website"] -->|"is intent"| D["vision-and-strategy.md"] + B -->|"feeds 'shipped' status"| E["PRD Functional Requirements"] + D -->|"feeds 'planned' status"| E + E -->|"if mismatch:
codebase wins for 'shipped',
vision wins for 'planned'"| F["Resolution rule"] +``` + +--- + +## 5. Storage convention — `tmp/` discipline + +All sensitive raw + intermediate artifacts stay in `tmp/`, which is **gitignored**. Public repo only ever sees `docs/*` and `CHANGELOG.md`. + +```mermaid +flowchart TB + subgraph TMP["tmp/ — gitignored"] + direction TB + subgraph RAW["raw/"] + R1["notion/
(exports, HTML/MD)"] + R2["github/
(issues.json, comments/, prs/)"] + R3["git/
(log.json, tags.json)"] + R4["website/
(scraped HTML/MD)"] + end + subgraph CAN["canonical/"] + C1["vision-and-strategy.md"] + C2["product-history.md"] + C3["taxonomy-v1.md"] + C4["taxonomy-reconciliation-report.md"] + end + subgraph LOG["agent-logs/ optional"] + L1["intake-b-chunk-NN.log"] + L2["recon-decisions.log"] + end + end +``` + +Rule: **no agent writes outside `tmp/` until its inputs are validated against the canonical set.** Any agent that needs to consume Notion content reads from `tmp/canonical/vision-and-strategy.md`, never from `tmp/raw/notion/*`. + +--- + +## 6. Intake Phase 0 — two parallel agents + +```mermaid +flowchart TB + subgraph IA["Intake-A — Strategic vision"] + direction LR + IA_in["tmp/raw/notion/
tmp/raw/website/
tmp/raw/decks/"] + IA_op["Operations:
1. dedupe across sources
2. classify (vision, persona,
metric, competitive, roadmap)
3. grade source authority
4. cite original location"] + IA_out["tmp/canonical/
vision-and-strategy.md"] + IA_in --> IA_op --> IA_out + end + + subgraph IB["Intake-B — Operational history (chunked)"] + direction TB + IB_pre["Pre-fetch:
gh issue list --state all --json …
gh pr list --state all --json …
git log --pretty=format:… > log.json"] + IB_chunk{"Chunk by area cluster
OR time bucket
~300-500 issues/chunk"} + IB_r1["Chunk 1 agent"] + IB_r2["Chunk 2 agent"] + IB_rN["Chunk N agent"] + IB_merge["Merge agent:
concat + dedupe
+ cross-link issue↔PR↔commit"] + IB_out["tmp/canonical/
product-history.md"] + IB_pre --> IB_chunk + IB_chunk --> IB_r1 + IB_chunk --> IB_r2 + IB_chunk --> IB_rN + IB_r1 --> IB_merge + IB_r2 --> IB_merge + IB_rN --> IB_merge + IB_merge --> IB_out + end + + classDef agent fill:#fff3bf,stroke:#a80 + class IA_op,IB_r1,IB_r2,IB_rN,IB_merge agent +``` + +Intake-A and Intake-B run **fully in parallel** (no dependency). Intake-B's chunks can run in parallel across multiple agent sessions if available. + +### 6.1 Schema fragment — `product-history.md` + +```yaml +# Each entry produced by Intake-B is one of: +- type: feature # shipped or proposed feature cluster + area: # finalized after Reconciliation + status: shipped | open | abandoned + related_issues: [#1234, #5678] + related_prs: [#999] + related_commits: [abc123] + shipped_in: v1.3.40 # if shipped + summary: "..." + +- type: decision # captured from issue comments or commit messages + area: + decided_at: 2024-08-15 + context: "Why this decision was made" + alternatives_considered: [...] + +- type: incident # bug + resolution + area: + severity: critical | high | medium | low + root_cause: "..." + lesson: "..." +``` + +--- + +## 7. Reconciliation Phase 0.5 + +```mermaid +flowchart LR + CAN_VIS[(vision-and-strategy.md)] + CAN_HIST[(product-history.md)] + + EX1["Extract strategic areas
top-down from vision"] + EX2["Cluster issues + commits
bottom-up"] + + PROP_A(["taxonomy-v0-strategic
top-down list"]) + PROP_B(["taxonomy-v0-empirical
bottom-up clusters"]) + + REC["🔀 Reconciliation agent
4 operations:
union • conflict • gap • granularity"] + DIFF["taxonomy-reconciliation-report.md"] + TAX(["taxonomy-v1.md"]) + + G1{"⚠ G1 Human review
approve taxonomy-v1"} + + CAN_VIS --> EX1 --> PROP_A --> REC + CAN_HIST --> EX2 --> PROP_B --> REC + REC --> DIFF + REC --> TAX + DIFF -.->|context for review| G1 + TAX --> G1 + G1 -->|approved| DOWN["Wave 3+: PRD, B1, B2"] + G1 -->|revise| REC + + classDef gate fill:#cfe2ff,stroke:#06c + class G1 gate +``` + +### 7.1 The 4 reconciliation operations + +```mermaid +flowchart TB + subgraph OPS["Reconciliation operations"] + O1["Union — items in A ∪ B"] + O2["Conflict resolution
same concept, different name
→ canonical name + aliases[]"] + O3["Gap surfacing
only in A → source: vision_primary
only in B → source: history_primary"] + O4["Granularity reconciliation
A coarse, B fine → pick level
+ sub_areas[]"] + end + + O1 --> R1["Each item: name, description"] + O2 --> R2["Each item: aliases list"] + O3 --> R3["Each item: source field"] + O4 --> R4["Each item: sub_areas list"] + + R1 --> TAX["taxonomy-v1.md schema"] + R2 --> TAX + R3 --> TAX + R4 --> TAX +``` + +### 7.2 `taxonomy-v1.md` schema (verbatim what reconciler outputs) + +```yaml +--- +version: 1 +generated_at: 2026-06-03 +source_strategic: tmp/canonical/vision-and-strategy.md +source_empirical: tmp/canonical/product-history.md +human_reviewed_by: +reconciliation_report: tmp/canonical/taxonomy-reconciliation-report.md +--- + +## Areas (product axis — reconciled) + +### swap +description: Cross-chain & same-chain token swap (routing, providers, slippage) +aliases: [Swap features, swapping, dex, exchange, cross-chain swap] +sub_areas: [xcm, hydration, optimex] +source: both +empirical_issue_count: 47 +example_issues: [#4936, #4567, #4209] + +### multisig +description: Multi-signature account creation, transfer, approval flows +aliases: [Multi-sig, multi sig, multisig wallet] +sub_areas: [creation, transfer, notification, detection] +source: vision_primary +empirical_issue_count: 9 +example_issues: [#4869, #4872, #4875] + +# … ~10-15 areas total + +## Types (work axis — fixed enum, NOT reconciled) +- bug +- feature +- integration +- refactor +- research +- chore +- ux +``` + +--- + +## 8. Stream A — Foundation docs dependency graph + +```mermaid +flowchart TB + CAN_VIS[(vision-and-strategy.md)] + CAN_HIST[(product-history.md)] + REPO[(Repo files + git log)] + TAX[(taxonomy-v1.md)] + + A1["A1 BRIEF"] + A2["A2 PRD"] + A3["A3 ARCHITECTURE"] + A4["A4 CONTEXT"] + A5["A5 LESSONS"] + A6["A6 SETUP"] + A7["A7 CHANGELOG"] + + CAN_VIS --> A1 + CAN_HIST --> A1 + + CAN_VIS --> A2 + CAN_HIST --> A2 + REPO --> A2 + TAX --> A2 + + REPO --> A3 + CAN_HIST --> A3 + + CAN_HIST --> A4 + REPO --> A4 + + CAN_HIST --> A5 + REPO --> A5 + + REPO --> A6 + + REPO --> A7 + + A1 --> T1["docs/BRIEF.md"] + A2 --> T2["docs/PRD.md"] + A3 --> T3["docs/ARCHITECTURE.md"] + A4 --> T4["docs/CONTEXT.md"] + A5 --> T5["docs/LESSONS.md"] + A6 --> T6["docs/SETUP.md"] + A7 --> T7["CHANGELOG.md root"] +``` + +### 8.1 Stream A agent contracts (one-line each) + +| Agent | Inputs | Output | Can parallelize with | +| --- | --- | --- | --- | +| A1 BRIEF | canonical-vis, canonical-hist | `docs/BRIEF.md` | A3, A4, A5, A6, A7 | +| A2 PRD | canonical-vis, canonical-hist, repo, **taxonomy-v1** | `docs/PRD.md` | B1 (both use taxonomy) | +| A3 ARCHITECTURE | repo, canonical-hist | `docs/ARCHITECTURE.md` | A1, A4, A5, A6, A7 | +| A4 CONTEXT | canonical-hist, repo | `docs/CONTEXT.md` | A1, A3, A5, A6, A7 | +| A5 LESSONS | canonical-hist, repo | `docs/LESSONS.md` | A1, A3, A4, A6, A7 | +| A6 SETUP | repo only | `docs/SETUP.md` | everything (no canonical dep) | +| A7 CHANGELOG | git log, existing `CHANGELOG.md` | `CHANGELOG.md` reformatted | everything (no canonical dep) | + +A6 and A7 are **early-runnable**: they start in Wave 1 alongside the intakes since they need only repo files. + +--- + +## 9. Stream B — Issue → backlog (no sprint allocation) + +```mermaid +flowchart LR + CAN_HIST[(product-history.md)] + TAX[(taxonomy-v1.md)] + PRD_FR[("PRD §Functional Requirements
area map")] + ARCH[("ARCHITECTURE
package map")] + + B1["B1 Triage
each open issue:
{area, type}"] + B2["B2 Epic synthesizer
group by area
→ EPIC-N"] + B3["B3 Story conversion
1 issue → 1 US-X.Y file
status: backlog"] + + CAN_HIST -->|filter status=open| B1 + TAX --> B1 + PRD_FR -.->|cross-check| B1 + TAX --> B2 + ARCH -.->|tech grouping| B2 + + B1 --> B2 --> B3 + + B2 --> T8["docs/sprints/epics/EPIC-N.md"] + B3 --> T9["docs/sprints/stories/US-X.Y-*.md
(all status: backlog)"] + B3 -.->|trigger| T11["docs/sprints/STATUS.md
auto via npx koni-docs status"] +``` + +Stream B produces a **classified backlog** only. No sprint allocation logic — that lives in Wave 6 (interactive, human-driven). + +--- + +## 10. Wave structure + critical path + +```mermaid +flowchart TB + subgraph W1["WAVE 1 — parallel, no deps"] + direction LR + W1A["Intake-A"] + W1B["Intake-B chunked"] + W1C["A6 SETUP"] + W1D["A7 CHANGELOG"] + end + + subgraph W2["WAVE 2 — after canonicals ready"] + direction LR + W2R["Reconciliation"] + W2A["A1 BRIEF"] + W2B["A3 ARCH"] + W2C["A4 CONTEXT"] + W2D["A5 LESSONS"] + end + + G1{"⚠ G1 taxonomy-v1"} + + subgraph W3["WAVE 3 — after G1"] + direction LR + W3A["A2 PRD"] + W3B["B1 Triage"] + end + + G2{"⚠ G2 PRD"} + + subgraph W4["WAVE 4 — after B1+ARCH"] + W4["B2 Epic synth"] + end + + subgraph W5["WAVE 5 — after B2"] + W5["B3 Story conversion"] + end + + subgraph W6["WAVE 6 — interactive bootstrap"] + direction TB + W6S["I1+S6a → I2+S6b+I2b+S6c → I3+S6d"] + end + + G3{"⚠ G3 final integration"} + DONE["PR ready for merge → master"] + + W1 --> W2 + W2R --> G1 --> W3 + W3A --> G2 + W3B --> W4 + G2 -.->|consumes| W5 + W4 --> W5 --> W6 --> G3 --> DONE + + classDef wave fill:#fff3bf,stroke:#a80 + classDef gate fill:#cfe2ff,stroke:#06c + classDef input fill:#e9d5ff,stroke:#7c3aed + class W1,W2,W3,W4,W5,W1A,W1B,W1C,W1D,W2R,W2A,W2B,W2C,W2D,W3A,W3B wave + class G1,G2,G3 gate + class W6,W6S input +``` + +### 10.1 Critical path + +```mermaid +flowchart LR + A["Intake-B
chunked (longest)"] --> B["Recon"] + B --> G1{"G1"} + G1 --> C["A2 PRD"] + C --> G2{"G2"} + G2 --> D["B2 Epic"] + D --> E["B3 Story"] + E --> F["Wave 6 bootstrap"] + F --> G3{"G3"} + G3 --> H["merge → master"] + + classDef gate fill:#cfe2ff,stroke:#06c + class G1,G2,G3 gate +``` + +Anything not on this path can run in parallel slots if compute / reviewer bandwidth allows. + +--- + +## 11. Gate taxonomy: review vs input + +```mermaid +flowchart LR + subgraph REV["🔵 Review gates (3)"] + direction TB + RA["AI produces artifact"] + RB["Human says yes/no/revise"] + RC["If no → AI revises
If yes → unblock downstream"] + RA --> RB --> RC + end + + subgraph INP["🟣 Input gates (4)"] + direction TB + IA["AI pauses, prompts user
(structured schema)"] + IB["Human provides info
(YAML / answers / files)"] + IC["AI parses input,
runs deterministic step,
resumes"] + IA --> IB --> IC + end + + classDef rev fill:#cfe2ff,stroke:#06c + classDef inp fill:#e9d5ff,stroke:#7c3aed + class RA,RB,RC rev + class IA,IB,IC inp +``` + +### 11.1 Review gates + +| ID | After | Reviews | Blocks if rejected | +| --- | --- | --- | --- | +| G1 | Reconciliation | `taxonomy-v1.md` + reconciliation report | Wave 3 (PRD + Triage) | +| G2 | A2 PRD | `docs/PRD.md` | Wave 5 (B3 final story output uses PRD §FR) | +| G3 | Wave 6 | Whole repo state — 5-layer consistency, no "TBD", STATUS.md correct | PR merge to master | + +### 11.2 Input gates + +| ID | Before | User provides | Agent then does | +| --- | --- | --- | --- | +| I1 | S6a | strategy to unify `packages/* -N` suffix with root VERSION | apply chosen rule across all `packages/*/package.json` | +| I2 | S6b | rule mapping commit/tag → sprint window (or explicit map) | derive `sprint-YYYY-WNN.md` skeletons from git tags + CHANGELOG | +| I2b | S6c | review of derived past sprint files, edits to story-to-sprint mapping | finalize past sprint files | +| I3 | S6d | (1) theme/focus for first active sprint, (2) 1-3 stories from backlog to promote, (3) sprint window ID (e.g. 2026-W23) | create `sprint-2026-WNN.md`, flip selected stories `backlog → ready`, run `npx koni-docs status` | + +--- + +## 12. Wave 6 — interactive bootstrap detail + +```mermaid +flowchart TB + START(["Enter Wave 6"]) + + I1{"🟣 I1
strategy: unify packages/* -N
vs keep -N suffix"} + S6a["S6a — apply version policy
edit packages/*/package.json"] + + I2{"🟣 I2
commit/tag → sprint mapping rule"} + S6b["S6b — derive past sprint files
from git tag dates + CHANGELOG"] + I2b{"🟣 I2b
review derived past sprint files,
edit story-to-sprint mapping"} + S6c["S6c — finalize past sprint files"] + + I3{"🟣 I3
theme + 3 stories + sprint window ID"} + S6d["S6d — create active sprint file
flip selected stories: backlog → ready
npx koni-docs status"] + + G3{"⚠ G3 final integration review"} + END(["Wave 6 done — PR ready"]) + + START --> I1 --> S6a --> I2 --> S6b --> I2b --> S6c --> I3 --> S6d --> G3 --> END + + classDef input fill:#e9d5ff,stroke:#7c3aed + classDef step fill:#fff3bf,stroke:#a80 + classDef gate fill:#cfe2ff,stroke:#06c + class I1,I2,I2b,I3 input + class S6a,S6b,S6c,S6d step + class G3 gate +``` + +### 12.1 Input gate schema (used by Plan) + +Each input gate in the Plan follows this structure: + +```yaml +INPUT_GATE_: + blocks: + prompt_to_user: | + + expected_input_format: + agent_action_on_receipt: | + + fallback_if_user_unavailable: pause # never silent-default + verification: +``` + +--- + +## 13. Branch & commit convention + +```mermaid +flowchart LR + M[master] + AID[ai-development] + M -.->|already branched| AID + + C0["...821bb9e docs: add AI coding behavior
(end of sub-task 1)"] + C1["intake-a: notion + website synthesis"] + C2["intake-b/batch-1: issues 1-500"] + C3["intake-b/batch-2: issues 501-1000"] + Cdot["..."] + C4["recon: taxonomy-v1 + report"] + C5["⚠ G1 review (commit comments)"] + C6["a1-brief: docs/BRIEF.md"] + C7["a2-prd: docs/PRD.md"] + Cdot2["..."] + C8["wave6-i1: packages version unify"] + Cdot3["..."] + PR["PR merged → master"] + + C0 --> C1 --> C2 --> C3 --> Cdot --> C4 --> C5 --> C6 --> C7 --> Cdot2 --> C8 --> Cdot3 --> PR +``` + +### 13.1 Rules + +- **Append-only.** No force-push, no rebase on `ai-development`. +- **One commit per agent run.** Conventional prefix: `: `. +- **Review per commit**, not per PR. Reviewer can revert any single commit if rejected. +- **PR description**: maintained incrementally — agents append a short bullet to the PR description after each commit, summarising what was added. +- **No commits to `tmp/`.** `tmp/` is gitignored end-to-end; if `git status` shows `tmp/` entries, that is a `.gitignore` bug to fix immediately. +- **Reviewer workflow when receiving handoff**: `git fetch && git diff ..HEAD` per commit, approve/revert, then signal next agent run. + +### 13.2 Conflict surfaces (need light serialization) + +```mermaid +flowchart LR + F1["CLAUDE.md
Active Context block"] -.->|"updated by A1, A2,
A3, Wave 6 steps"| Conflict + F2["tmp/canonical/taxonomy-v1.md"] -.->|"recon writes,
downstream reads only"| OK + F3["docs/sprints/STATUS.md"] -.->|"auto-gen,
multiple agents trigger"| MaybeConflict + Conflict["⚠ serialize edits"] + OK["✓ no conflict expected"] + MaybeConflict["⚠ regen idempotently
npx koni-docs status
at end of each commit"] +``` + +--- + +## 14. Canonical → target traceability matrix + +```mermaid +flowchart LR + subgraph PRIV["tmp/canonical/"] + P1["vision-and-strategy.md"] + P2["product-history.md"] + P3["taxonomy-v1.md"] + end + + subgraph PUB["docs/ (public)"] + D1["BRIEF.md"] + D2["PRD.md"] + D3["ARCHITECTURE.md"] + D4["CONTEXT.md"] + D5["LESSONS.md"] + D6["SETUP.md"] + D7["sprints/epics/EPIC-N.md"] + D8["sprints/stories/US-X.Y-*.md"] + end + + P1 --> D1 + P1 --> D2 + P2 --> D1 + P2 --> D2 + P2 --> D3 + P2 --> D4 + P2 --> D5 + P2 --> D7 + P2 --> D8 + P3 --> D2 + P3 --> D7 + P3 --> D8 + + REPO[("Repo + git")] --> D2 + REPO --> D3 + REPO --> D4 + REPO --> D5 + REPO --> D6 + REPO --> CHG["CHANGELOG.md"] +``` + +--- + +## 15. Out of scope (truly out) + +Not in this migration: + +- External system integration (Linear, Jira, Slack, etc.) — handled outside koni-docs entirely. +- New skill installation (the `koni-docs` skill is the only one needed; `koni-api` and others are deferred to separate sub-tasks). +- Tooling changes (CI workflows, lint rules) — koni-docs operates over existing CI conventions, not changes them. + +Items that **looked** out of scope but were reclassified as input gates in Wave 6 (per user clarification): + +- Sprint allocation weekly cadence → ongoing human process; Wave 6 bootstraps the first sprint as template +- Backfill past sprints → I2 + S6b + I2b + S6c +- Priority labeling, theme ordering → part of I3 and recurring weekly +- `packages/* -N` suffix vs root VERSION → I1 + S6a + +--- + +## 16. Success criteria + +```mermaid +flowchart TB + S1["1. All 7 foundation docs
present + reviewed"] + S2["2. ~200 open issues
converted to US-X.Y backlog"] + S3["3. EPIC-N files cover all
taxonomy areas (≥80% issue coverage)"] + S4["4. STATUS.md auto-gen passes
5-layer consistency check"] + S5["5. First active sprint file exists,
≥1 story in 'ready'"] + S6["6. packages/* -N suffix policy
documented in CONTEXT.md"] + S7["7. PR ai-development → master
green, mergeable"] + S8["8. No 'TBD' / placeholder
in any committed doc"] + + ALL["✅ Migration done"] + + S1 --> ALL + S2 --> ALL + S3 --> ALL + S4 --> ALL + S5 --> ALL + S6 --> ALL + S7 --> ALL + S8 --> ALL +``` + +--- + +## 17. Open risks & considerations + +| Risk | Mitigation | +| --- | --- | +| Notion content thin / weak vision section | Intake-A surfaces a `coverage report`; if < 60% of BRIEF template sections covered, escalate to user before Wave 2 | +| Intake-B token cost on full history | Chunking by area cluster (300-500 issues/chunk). Optionally parallelize across sessions. Budget cap per chunk. | +| Taxonomy drift between Intake-A and Intake-B | Reconciliation phase exists precisely for this; G1 human review is mandatory | +| Reviewer bottleneck on per-commit cadence | Per-commit review can batch up to 5 commits if they're independent (intake chunks); critical-path commits review one-at-a-time | +| `npx koni-docs status` failing in CI | Run locally before each commit; do not push commits that leave STATUS.md inconsistent | +| Long-living branch drift from master | Periodic `git merge master --no-ff` from master into ai-development; resolve conflicts in same commit | + +--- + +## 18. Next step + +After this spec is approved by user review: + +1. Invoke the `writing-plans` skill. +2. Output: `docs/superpowers/plans/2026-06-03-koni-docs-migration.md`. +3. The Plan encodes, per agent run: + - Prompt template (exact text agent receives) + - Inputs (files / canonicals to read) + - Outputs (files to write) + - Acceptance criteria + - Verification commands + - Commit prefix + message template +4. Input gates encoded with full schema per §12.1. +5. The Plan is the handoff deliverable. + +--- + +## Appendix A — Glossary + +| Term | Meaning | +| --- | --- | +| **Stream A** | Foundation docs work — BRIEF, PRD, ARCHITECTURE, CONTEXT, LESSONS, SETUP, CHANGELOG | +| **Stream B** | Issue → backlog conversion — Triage, Epic, Story (no Sprint) | +| **Wave** | Time-ordered batch of agent runs sharing the same dependency layer | +| **Review gate** | Human approves an AI-produced artifact before downstream proceeds | +| **Input gate** | Agent pauses to receive structured user input before continuing | +| **Canonical** | A `tmp/canonical/*.md` file — single source of truth for downstream agents | +| **Chunked execution** | Intake-B mode where the input is partitioned (~300-500 issues/run) and runs in parallel | +| **5-layer consistency** | Story ↔ Epic ↔ PRD ↔ Sprint ↔ STATUS.md must agree — per koni-docs skill | From eace53764c67b7855e3d2da1f46cd1d9ad756789 Mon Sep 17 00:00:00 2001 From: tunghp2002 Date: Wed, 3 Jun 2026 17:35:49 +0700 Subject: [PATCH 11/67] =?UTF-8?q?docs:=20a6-setup=20=E2=80=94=20add=20docs?= =?UTF-8?q?/SETUP.md=20(dev=20environment)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/SETUP.md | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/SETUP.md diff --git a/docs/SETUP.md b/docs/SETUP.md new file mode 100644 index 00000000000..2083f974caf --- /dev/null +++ b/docs/SETUP.md @@ -0,0 +1,133 @@ +# SETUP — SubWallet-Extension local dev environment + +> Local development setup: clone → install → build → run. Production +> runbook lives in `DEPLOY.md` (repo root, pending). Env var template is +> `.env.example` (repo root). New env vars land in SETUP + DEPLOY + +> `.env.example` in the same commit (koni-docs RULE-11). + +--- + +## 1. Prerequisites + +| Tool | Version | Notes | +|---|---|---| +| Node.js | **12** for the production build target (`.nvmrc`); **18+** (20 LTS recommended) for day-to-day dev | The Webpack build pins Node 12 for output compatibility; modern Node runs the dev tooling fine. Use `nvm` to switch. | +| Yarn | **3.x (berry)** — pinned via `packageManager` in `package.json` (`yarn@3.x`) and `.yarn/releases/` | **Never run `npm install`** — it corrupts the Yarn-managed workspace. | +| Git | any recent | Branch convention: `koni/dev/issue-` (see `AGENTS.md` §6). | +| Browser | Chrome / Chromium or Firefox | For loading the unpacked extension build. | + +Confirm your toolchain: + +```bash +node -v # 18.x / 20.x for dev (build target is 12 per .nvmrc) +yarn -v # 3.x +``` + +--- + +## 2. Clone & install + +```bash +git clone https://github.com/Koniverse/SubWallet-Extension.git +cd SubWallet-Extension +yarn install # Yarn 3 workspaces — installs all packages/* +``` + +`yarn install` resolves the full monorepo (12 workspace packages under +`packages/*`). The first install runs `postinstall` automatically. + +--- + +## 3. Environment variables + +Copy the template and fill values (obtain real values from the SubWallet +team — the committed `.env.example` ships working defaults for some +public services): + +```bash +cp .env.example .env +``` + +Variables consumed at build time, by category: + +```bash +# Fiat on-ramp providers +TRANSAK_API_KEY= # Transak buy-crypto widget +COINBASE_PAY_ID= # Coinbase Pay on-ramp +MELD_WIZARD_KEY= # Meld fiat on-ramp + +# SubWallet backend services +SUBWALLET_API=http://localhost:3000/api # core SubWallet API base URL +SW_EXTERNAL_SERVICES_API=http://localhost:8787 # external services API base URL + +# Feature endpoints +NFT_MINTING_HOST= # NFT minting service host +PATCH_CHAIN_LIST_URL=# runtime chain-list override URL +BTC_SERVICE_TOKEN= # Bitcoin service auth token + +# Build metadata +BRANCH_NAME=master # branch tag baked into the build +``` + +> `.env.example` is the single source of truth for which env vars exist. +> Do not commit a populated `.env`. + +--- + +## 4. Build + +| Goal | Command | +|---|---| +| Build all packages | `yarn build` | +| Build extension UI bundle | `yarn build:ui` | +| Build the browser extension (dev) | `yarn build:koni-dev` | +| Build the web app | `yarn webapp:build` | +| Build the web-runner | `yarn web-runner:build` | +| Produce distributable zips | `yarn build:zip` | + +The browser extension is compiled from `packages/extension-koni` +(background + popup + injected scripts). The web app and web-runner are +separate build outputs of the same `extension-base` core. + +--- + +## 5. Run / develop + +| Goal | Command | +|---|---| +| Watch + rebuild the extension during dev | `yarn watch-dev` | +| Start the web app dev server | `yarn webapp:dev` | +| Start the web-runner dev server | `yarn web-runner:dev` | +| Generic dev entry | `yarn dev` | + +### Load the unpacked extension (Chrome) + +1. `yarn build:koni-dev` (or `yarn watch-dev` for live rebuilds). +2. Open `chrome://extensions`, enable **Developer mode**. +3. **Load unpacked** → select the build output directory produced by the + extension build (under `packages/extension-koni/build`). +4. Reload the extension after each rebuild (or use `watch-dev`). + +--- + +## 6. Lint & test + +```bash +yarn lint # ESLint (.eslintrc.js) + Prettier (.prettierrc.cjs) +yarn lint:changes # lint only changed files +yarn test # test suite +yarn test:one # run a single test +``` + +Run `yarn lint` and `yarn test` before opening a PR. See `CONTRIBUTING.md` +for the full contributor workflow (no `--force` pushes; non-master +branches; all changes via PR; CI must pass before merge). + +--- + +## 7. Reference + +- `AGENTS.md` — canonical project guide (monorepo layout, tech stack, conventions). +- `CONTRIBUTING.md` — contributor rules and PR/review process. +- `README.md` — architecture concept (background / popup / injected scripts). +- `package.json` (root) `scripts` — the complete command list. From c6465368019a55047442efe8ac2181196f44caf0 Mon Sep 17 00:00:00 2001 From: tunghp2002 Date: Wed, 3 Jun 2026 17:36:08 +0700 Subject: [PATCH 12/67] =?UTF-8?q?docs:=20a7-changelog=20=E2=80=94=20add=20?= =?UTF-8?q?[Unreleased]=20anchor=20to=20CHANGELOG.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b69d166f363..daa09ec1db9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## [Unreleased] + +(empty — track unreleased changes here; new version sections are inserted above the previous version, anchored on this block, per koni-docs changelog template) + +--- + ## 1.3.79 Build date: May 21, 2026. From 2d336e5a7167fdd6c31f926c7bfd3d622712023a Mon Sep 17 00:00:00 2001 From: tunghp2002 Date: Wed, 3 Jun 2026 18:50:07 +0700 Subject: [PATCH 13/67] =?UTF-8?q?docs:=20a3-arch=20=E2=80=94=20add=20docs/?= =?UTF-8?q?ARCHITECTURE.md=20with=20AD=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/ARCHITECTURE.md | 259 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 docs/ARCHITECTURE.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000000..990a7b37a9f --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,259 @@ +# ARCHITECTURE — SubWallet-Extension + +> Last updated: 2026-06-03 (v1.3.79) +> Maintainer: Koniverse team + +## System overview + +SubWallet-Extension is a non-custodial multi-chain cryptocurrency wallet +delivered as a Chromium/Firefox browser extension and a standalone web +application. It supports four blockchain ecosystems: Substrate/Polkadot +(relay chains and parachains), EVM (Ethereum and EVM-compatible chains), +Bitcoin, and TON. The architecture is a message-passing monolith: a +persistent background service owns all state, key material, and on-chain +communication, while multiple UI surfaces (extension popup, full-page +web app) communicate with it exclusively over the browser's message-bus +API. The codebase is organised as a Yarn 3 monorepo of twelve TypeScript +packages with explicit dependency boundaries between background logic, +UI, and shared protocol layers. Architectural drivers are: self-custody +(keys never leave the background environment), multi-chain breadth (100+ +networks from a single install), and portability (the same background +logic runs inside the browser extension service worker and inside a +web-runner iframe for mobile webview contexts). + +## Tech stack + +| Layer | Technology | Version | Rationale | +|-------|-----------|---------|-----------| +| Build target (Node) | Node.js | 12 (`.nvmrc`) | Lowest-common-denominator for extension bundling; dev requires Node 18+ | +| Package manager | Yarn 3 (berry), workspaces | 3.x | Monorepo workspace linking; deterministic lock file | +| Language | TypeScript | 5.x (per tsconfig) | Strict typing across background and UI code | +| UI framework | React | 18.2 | Concurrent mode; hooks-based state; shared between popup and web app | +| UI components | `@subwallet/react-ui`, styled-components | 5.1.2-b77 / ^5.3.6 | In-house component library + CSS-in-JS theming | +| State management | Redux Toolkit + redux-persist | ^1.9.1 / ^6.0.0 | Slice-based reducers; persisted to chrome storage via redux-persist | +| Routing | react-router-dom | ^6.8.2 | Client-side navigation for popup and web app | +| Substrate connectivity | `@polkadot/api`, `@polkadot/rpc-provider` | ^16.4.2 | Full Substrate node API plus lightweight WsProvider for balance queries | +| Substrate crypto | `@polkadot/keyring`, `@polkadot/util-crypto`, `@subwallet/keyring`, `@subwallet/ui-keyring` | ^13.5.3 / ^0.1.14 | Key derivation, signing, address encoding for Substrate and EVM | +| EVM connectivity | `ethers`, `web3` | ^6.4.2 / ^1.10.4 | EVM RPC, contract calls, and MetaMask-compatible provider injection | +| TON connectivity | `@ton/core`, `@ton/crypto`, `@ton/ton` | ^0.56.3 / ^3.2.0 / ^15.0.0 | TON blockchain client and cryptographic primitives | +| Bitcoin support | `bitcoinjs-lib` | 6.1.5 | Transaction construction and address generation for Bitcoin | +| Cardano support | `@emurgo/cardano-serialization-lib-browser` | ^13.2.0 | Cardano serialization for browser environments | +| Local database | `dexie` + `dexie-export-import` | ^3.2.2 / ^4.0.7 | IndexedDB abstraction for structured background storage | +| GraphQL client | `@apollo/client` | ^3.7.14 | SubSquid / SubQuery history and governance data queries | +| HTTP client | `axios` | ^1.13.2 | REST API calls (middleware services, SubSquid, chain-list updates) | +| Phishing protection | `@polkadot/phishing` | ^0.25.15 | Automatically updated phishing site and address list | +| Build tool | Webpack 5 | ^5.102.1 | Extension bundle with code-splitting for Firefox file-size limits | +| Lint / format | ESLint, Prettier | per config | Enforced via `yarn lint` pre-commit check | +| CI | GitHub Actions | — | Automated build, lint, test, and release packaging | + +## Component architecture + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Browser Extension (Manifest V3) │ +│ │ +│ ┌──────────────────────────────────────────────────────────────┐ │ +│ │ Background Service Worker │ │ +│ │ (packages/extension-koni → background.ts) │ │ +│ │ │ │ +│ │ KoniState ──► ChainService ──► Substrate ApiPromise/WS │ │ +│ │ │ ──► BalanceService ──► EVM Web3/Ethers │ │ +│ │ │ ──► EarningService ──► TON @ton/ton │ │ +│ │ │ ──► NFTService ──► Bitcoin bitcoinjs-lib │ │ +│ │ │ ──► StakingService │ │ +│ │ │ ──► TransactionService │ │ +│ │ └──► KeyringService (@subwallet/keyring) │ │ +│ │ │ │ +│ │ KoniExtension (pri handlers) ◄── Extension pages │ │ +│ │ KoniTabs (pub handlers) ◄── Injected dApp scripts │ │ +│ │ KoniCron (scheduled jobs) │ │ +│ └─────────────────┬────────────────────────────────────────────┘ │ +│ │ Chrome message bus (chrome.runtime.sendMessage) │ +│ ┌──────────┴──────────────────────┐ │ +│ │ │ │ +│ ┌──────▼──────────┐ ┌─────────▼──────────┐ │ +│ │ popup.html │ │ portfolio.html │ │ +│ │ extension- │ │ (expand view) │ │ +│ │ koni-ui │ │ extension-koni-ui │ │ +│ └─────────────────┘ └────────────────────┘ │ +│ │ +│ content-script (extension-inject) injected into every browser tab │ +│ └── exposes window.injectedWeb3 for dApp Substrate access │ +│ └── exposes window.ethereum for MetaMask-compatible EVM dApps │ +└─────────────────────────────────────────────────────────────────────┘ + +Alternate runtimes (no extension): + web-runner ──► background logic in iframe/WebView (mobile webview) + webapp ──► standalone web app (extension-web-ui + extension-base) +``` + +### Package map + +| Package | Purpose | Key dependencies | +|---------|---------|-----------------| +| `@subwallet/extension-base` | Core background services: account management, balance, chain connectivity, transaction, earning, NFT, staking, message-bus handlers, storage, cron jobs, inject scripts | `@polkadot/api`, `ethers`, `web3`, `@ton/core`, `bitcoinjs-lib`, `dexie`, `@subwallet/keyring`, `@subwallet/chain-list`, `@walletconnect/sign-client`, `rxjs`, `@apollo/client` | +| `@subwallet/extension-chains` | Static chain definitions (metadata, genesis hashes, SS58 prefixes) exposed by the extension | `@polkadot/networks`, `@polkadot/util`, `@polkadot/util-crypto`, `@subwallet/extension-inject` | +| `@subwallet/extension-compat-metamask` | MetaMask-compatible EVM provider shim injected into dApp pages | `@metamask/detect-provider`, `@polkadot/types`, `web3`, `@subwallet/extension-inject` | +| `@subwallet/extension-dapp` | Convenience wrapper around injected globals for dApp developers (Substrate side) | `@polkadot/util`, `@polkadot/util-crypto`, `@subwallet/extension-inject` | +| `@subwallet/extension-inject` | Generic injector that populates `window.injectedWeb3` and related interfaces for any conforming extension | `@polkadot/rpc-provider`, `@polkadot/types`, `@subwallet/keyring`, `web3-core` | +| `@subwallet/extension-koni` | Main extension compile entry: wires background service worker and popup bundles; Webpack build lives here | `@subwallet/extension-base`, `@subwallet/extension-inject`, `@subwallet/extension-koni-ui`, `@emurgo/cardano-serialization-lib-browser` | +| `@subwallet/extension-koni-ui` | React UI for the browser extension popup and expand view | `react` 18, `styled-components`, `react-router` v6, `@reduxjs/toolkit`, `@subwallet/react-ui`, `@subwallet/extension-base`, `@polkadot/hw-ledger`, `@ledgerhq/hw-app-eth` | +| `@subwallet/extension-mocks` | Test fixtures and chrome API stubs (sinon-chrome) for unit tests across packages | `sinon-chrome` | +| `@subwallet/extension-web-ui` | React UI for the standalone web application (webapp / web-runner); mirrors extension-koni-ui with web-specific adaptations | `react` 18, `styled-components`, `react-router-dom` ~6.9, `@reduxjs/toolkit`, `@subwallet/react-ui`, `@subwallet/extension-base`, `axios` | +| `@subwallet/subsquare-api-sdk` | HTTP SDK for Subsquare governance and referendum APIs | `@polkadot/util`, `axios` | +| `@subwallet/web-runner` | Hosts the background service worker logic inside a WebView iframe so the wallet can run in mobile or non-extension web environments; uses extension-koni-ui for UI | `@subwallet/extension-base`, `@subwallet/extension-inject`, `@subwallet/extension-koni-ui`, `@emurgo/cardano-serialization-lib-browser` | +| `@subwallet/webapp` | Standalone web application bundle; combines extension-web-ui with background logic for a fully self-contained browser web app | `@subwallet/extension-base`, `@subwallet/extension-inject`, `@subwallet/extension-web-ui`, `@emurgo/cardano-serialization-lib-browser` | + +### Runtime split + +All API calls and key operations must be processed in the background +environment. Extension pages and inject scripts hold no private key +material and do not call chain APIs directly. + +``` + UI Surface (extension-koni-ui / extension-web-ui) + │ + │ messaging.ts — typed request/response wrappers + │ chrome.runtime.sendMessage (extension) / postMessage (web-runner) + ▼ + Message Bus (extension-base: KoniExtension / KoniTabs) + │ routes by message type prefix: + │ pri(…) — privileged, only extension pages + │ pub(…) — public, injectable by dApps via tabs + ▼ + Background Services (KoniState, ChainService, BalanceService, …) + │ + ├── Substrate: @polkadot/api ApiPromise / WsProvider + ├── EVM: ethers JsonRpcProvider / web3 Web3 + ├── TON: @ton/ton TonClient + └── Bitcoin: bitcoinjs-lib +``` + +## Data layer + +| Store | Technology | Scope | Persistence | +|-------|-----------|-------|-------------| +| Structured extension data | IndexedDB via `dexie` (^3.2.2) | Background process | Survives browser restarts; dexie-export-import enables backup/restore | +| Key-value extension state | `chrome.storage.local` | Background process | Survives browser restarts; used for settings, account metadata, network state | +| UI Redux state | `redux-persist` backed by `chrome.storage.local` | UI process | Persisted slice snapshots for fast rehydration on popup open | +| In-memory subscription state | RxJS Subjects (`SubscribableStore`) | Background process | Volatile; republished to UI subscribers on reconnect | + +State managed by `KoniState` (in `extension-base`) is the authoritative +source. UI components receive state updates via subscription messages +from the background; they do not maintain independent chain-query +subscriptions. Cronjobs (`KoniCron`) run in the background to refresh +balances, staking data, prices, and NFT metadata on schedule. + +## Cross-chain support + +Chain definitions live in `@subwallet/chain-list` (an external monorepo +package, version `0.2.127`). The extension bundles and auto-updates this +list to add new chains, tokens, and logos without a code release. + +The `extension-chains` package wraps the static genesis hash and SS58 +prefix metadata used for account validation and address encoding, sourced +from `@polkadot/networks`. + +`ChainService` (in `extension-base`) manages per-chain API objects: + +| Ecosystem | API object | Library | +|-----------|-----------|---------| +| Substrate (Polkadot, Kusama, parachains) | `SubstrateApi` (wraps `@polkadot/api` ApiPromise + light-client fallback via `@substrate/connect`) | `@polkadot/api`, `@substrate/connect` | +| EVM (Ethereum, Base, Moonbeam, …) | `EvmApi` (wraps `web3` Web3 and `ethers` JsonRpcProvider) | `web3`, `ethers` | +| TON | TON client | `@ton/ton` | +| Bitcoin | UTXO builder | `bitcoinjs-lib` | + +Each enabled chain gets one API instance managed by `ChainService`. RAM +usage is controlled by creating lightweight WsProvider-only connections +for balance queries and instantiating the full `ApiPromise` only when +complex extrinsic construction is needed (see AD-07). + +XCM cross-chain transfers are coordinated in `extension-base` using +Paraspell-compatible `MultiLocation` v3 formatting. XCM route +availability is configurable per chain pair so individual routes can be +toggled without a code release in response to partner-chain incidents +(see AD-09). + +## Security model + +| Concern | Approach | Detail | +|---------|---------|--------| +| Key custody | Non-custodial; keys never transmitted | `@subwallet/keyring` / `@subwallet/ui-keyring` hold all private key material exclusively in the background service worker | +| Key encryption at rest | `browser-passworder` (AES-256-GCM via master password) | Raw key bytes are never stored; only encrypted blobs written to `chrome.storage.local` | +| Master password | Single password unlocks all accounts | Unified unlock flow; password-derived key held in memory while unlocked, dropped on auto-lock | +| Message bus isolation | `pri(…)` messages only reachable from extension pages; `pub(…)` messages available to injected content scripts | Background enforces prefix-based routing; inject scripts cannot call privileged handlers | +| Phishing protection | `@polkadot/phishing` + ChainPatrol API | Phishing list auto-updates online; flagged sites and addresses surfaced with a blocking warning screen | +| Hardware wallets | Ledger (via `@polkadot/hw-ledger`, `@ledgerhq/hw-app-eth`) and QR signers (Parity Signer, Keystone) | Signing payloads sent to device; private keys never enter the extension | +| Seed phrase display | Input elements only, never textarea | Prevents "demonic vulnerability" autocomplete/autofill leaks (fixed in v1.1.10, issue #1798) | +| dApp injection scope | Content script injected into all tabs; authorization stored per dApp origin | Unauthorized dApps see the provider object but receive rejection until user explicitly approves | + +## Build and deploy + +### Extension build + +``` +packages/extension-koni/ + ├── webpack.config.cjs ← entry point for extension build + ├── src/background.ts ← compiled to background service worker + ├── popup.html / portfolio.html ← extension popup and expand view + └── manifest.json ← Manifest V3 descriptor +``` + +Build command: `yarn webpack:build:extension` +Dev watch: `yarn watch-dev` + +The Webpack config splits the output into many smaller chunks (see AD-06) +to satisfy Firefox's per-file size limits on extension submissions. +WASM modules (`@polkadot` crypto, Cardano serialization, etc.) are +loaded via `wasm-unsafe-eval` CSP in the manifest, required for MV3 +compliance (resolved Chrome 102+, see AD-08). + +### Web app build + +``` +packages/webapp/ + ├── webpack.config.cjs ← entry for standalone web app + └── src/index.tsx ← mounts extension-web-ui + background bridge +``` + +Build command: `yarn webapp:build` +Dev server: `yarn webapp:dev` / `yarn webpack:dev:webapp` + +### Web-runner build + +``` +packages/web-runner/ + └── webpack.config.cjs ← entry for mobile WebView host +``` + +Build command: `yarn web-runner:build` +Dev server: `yarn web-runner:dev` + +### Release versioning + +The canonical user-facing version is `1.3.79`, recorded in: +- Root `package.json` (`"version": "1.3.79"`) +- `VERSION` file (repo root) + +Internal monorepo packages carry the suffix `-1` (`1.3.79-1`). +CI (GitHub Actions) generates dev versions automatically; stable releases +require manual version bump in root `package.json` and `CHANGELOG.md` +update before tagging. + +## Architecture decisions + +| ID | Topic | Decision | Rationale | Citation | +|----|-------|---------|-----------|---------| +| AD-01 | IndexedDB via dexie for background storage | Use `dexie` (IndexedDB abstraction) as the primary structured store in the background service worker | Provides async schema migrations, a clean query API, and export/import support; outlasted PouchDB evaluation (issue #782) as sufficient without added complexity | issue #782; `extension-base` `dexie` dep | +| AD-02 | ChainService per-chain API objects | Each supported chain gets a dedicated API object (`SubstrateApi` or `EvmApi`) managed by a central `ChainService` | Encapsulates connect/disconnect lifecycle, retry logic, and metadata caching per chain; replaces ad-hoc chain lookups (issues #894, #926, #1222) | issues #894, #1222; shipped v0.7.6 | +| AD-03 | Background / UI message-bus isolation | All chain calls and key operations run exclusively in the background; UI communicates only via typed messages (`pri(…)` / `pub(…)`) | Prevents direct key or RPC exposure in the UI process; `pri` prefix enforces privileged access for extension pages only | `extension-base` README; README.md message-passing docs | +| AD-04 | Non-custodial keyring confined to background | `@subwallet/keyring` and `@subwallet/ui-keyring` are instantiated only in the background service worker; no private key bytes flow to UI or inject scripts | Eliminates key exfiltration via XSS or compromised dApp pages; hardware-wallet signing delegates to device | issues #433; security area entries | +| AD-05 | Yarn 3 monorepo package boundaries | Codebase split into twelve packages with explicit peer/runtime dependencies | Enables code reuse across extension and mobile/web contexts (`extension-base` shared by extension-koni, web-runner, webapp); prevents coupling between UI and background logic | issues #276, #169, #594; AGENTS.md §3 | +| AD-06 | Webpack 5 bundle splitting | Extension output split into many smaller chunks rather than one monolithic bundle | Avoids Firefox extension submission file-size limits (~4 MB per file cap); reduces memory overhead from large monolithic JS files | issues #48, #80, #131; shipped v0.3.6 | +| AD-07 | Lightweight WsProvider for balance queries; full ApiPromise deferred | Use a custom lightweight connector for balance/token queries; instantiate full `@polkadot/api` ApiPromise only for extrinsic construction | Full ApiPromise consumed ~137 MB for 4 chains / ~264 MB for 20 chains; WsProvider-only mode requires only ~72 MB regardless of chain count | issues #217, #232; PR #3024; shipped v1.1.64 | +| AD-08 | Manifest V3 migration with service worker background | Rebuild background layer around MV3 event-driven service worker rather than polyfilling the MV2 persistent-page model | Chrome enforcement timeline left no viable alternative; WASM support resolved via `wasm-unsafe-eval` CSP from Chrome 102; service worker shutdown/wake lifecycle required new state-persistence strategy | issues #349, #413, #412, #707, #782 | +| AD-09 | Per-chain XCM route toggle | XCM transfer routes are individually configurable per chain pair and can be disabled at runtime without a code release | Enables rapid response to partner-chain security incidents (e.g., Acala 2022 incident, issue #667) without requiring a full extension release cycle | issues #667, #695; bridge-xcm decision entries | +| AD-10 | Polkadot-js fork with upstream rebase strategy | Fork `polkadot-js/extension` and maintain `origin` remote so upstream changes can be rebased at any time | Minimises divergence risk and keeps the extension compatible with polkadot-js tooling; avoids full-rewrite maintenance burden | issue #15; decided 2022-03-15 | + +Individual decisions are recorded in [CONTEXT.md](CONTEXT.md). +Link new architecture decisions from CONTEXT.md here as they are recorded. From 47d0cc814f354bb2d9ff4a02f9ca4fdf6ff597e7 Mon Sep 17 00:00:00 2001 From: tunghp2002 Date: Wed, 3 Jun 2026 18:50:07 +0700 Subject: [PATCH 14/67] =?UTF-8?q?docs:=20a4-context=20=E2=80=94=20seed=20d?= =?UTF-8?q?ocs/CONTEXT.md=20decision=20log=20from=20history?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/CONTEXT.md | 632 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 632 insertions(+) create mode 100644 docs/CONTEXT.md diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md new file mode 100644 index 00000000000..416d543404f --- /dev/null +++ b/docs/CONTEXT.md @@ -0,0 +1,632 @@ + + +--- + +## Phase 0 — Foundation & Early Architecture (2022-03-15, shipped v0.3.x–v0.5.x) + +### D1. Fork polkadot-js extension rather than copying code + +**Context**: The team needed to start from the polkadot-js browser extension codebase and faced a choice between copying the source files and maintaining a git fork with a tracked remote. + +**Decision**: Fork the official polkadot-js extension repository and maintain a git remote `origin` pointing to upstream, so that improvements can be rebased at any time. + +**Rationale**: Keeping a live remote minimises divergence risk and ensures the extension stays compatible with polkadot-js tooling. Copying code independently would make future upstream merges manual and error-prone. + +**Alternatives considered**: +- Copy code over and maintain independently — rejected: divergence would accumulate quickly. +- Full rewrite without polkadot-js dependency — rejected: too high a risk for the initial release. + +**Impact**: Upstream polkadot-js bug-fixes and API changes can be rebased rather than ported manually. + +**Date**: 2022-03-15 +**Version**: pre-v0.3.x + +**Citations**: [#15](https://github.com/Koniverse/SubWallet-Extension/issues/15) + +--- + +### D2. Replace ApiPromise with a lightweight connector for balance/token queries + +**Context**: Full `polkadot.js ApiPromise` consumed approximately 137 MB RAM for 4 chains, growing to ~264 MB for 20 chains, making the extension unusable on modest hardware. + +**Decision**: Replace `ApiPromise` with a custom lightweight `WsProvider`-only connector for balance and token queries, deferring full `ApiPromise` only for complex operations such as extrinsic construction. + +**Rationale**: Lightweight connections required only ~71–72 MB regardless of chain count — a >3× saving at 20 chains. Keeping `ApiPromise` would have made the extension impractical for users with many networks enabled. + +**Alternatives considered**: +- Keep `ApiPromise` but limit concurrent connections — rejected: still RAM-intensive and limits network support. +- Use SubQuery/SubSquid APIs for all data — rejected: introduces off-chain dependency risk and latency. + +**Impact**: Dramatically reduced per-session RAM; separate `Dotsama connector` / `ChainService` architecture created to manage lightweight connections. + +**Date**: 2022-05-10 +**Version**: pre-v0.5.x + +**Citations**: [#217](https://github.com/Koniverse/SubWallet-Extension/issues/217), [#232](https://github.com/Koniverse/SubWallet-Extension/issues/232), [PR #3024](https://github.com/Koniverse/SubWallet-Extension/pull/3024) + +--- + +### D3. Rebuild the background layer for Chrome Manifest V3 (initial research) + +**Context**: Chrome announced its Manifest V3 migration roadmap. Research identified two major blockers: WASM support (resolved via `wasm-unsafe-eval` CSP from Chrome 102) and the service-worker model replacing persistent background pages. + +**Decision**: Rebuild the background layer around MV3's event-driven service worker rather than polyfilling the old MV2 behaviour. + +**Rationale**: Polyfilling MV2 semantics in MV3 would create a fragile compatibility shim that would break at each Chrome update; rebuilding natively produces a maintainable codebase aligned with browser vendor direction. + +**Alternatives considered**: +- Polyfill MV2 background-page behaviour in MV3 service worker — rejected: fragile and not future-proof. +- Delay migration and monitor Chrome enforcement timeline — rejected: timeline risk was too high. + +**Impact**: Service-worker lifecycle (sleep/wake), DApp connection, `fetch` API, and storage patterns were all redesigned. + +**Date**: 2022-07-01 +**Version**: pre-v0.6.x + +**Citations**: [#349](https://github.com/Koniverse/SubWallet-Extension/issues/349), [#413](https://github.com/Koniverse/SubWallet-Extension/issues/413), [#412](https://github.com/Koniverse/SubWallet-Extension/issues/412) + +--- + +### D4. Create a modified @subwallet/keyring that exposes private key after unlock + +**Context**: EVM signing required exporting the full account and converting to a private key on every signing operation, making the flow slow and requiring repeated password entry. + +**Decision**: Create a modified `@subwallet/keyring` package that exposes the private key once the keyring is unlocked, removing the repeated export step for EVM operations. + +**Rationale**: The repeated export-then-sign pattern degraded UX significantly for EVM-heavy users. Exposing the key once per unlocked session is an acceptable security trade-off compared to caching it in memory after first unlock. + +**Alternatives considered**: +- Cache private key in memory after first unlock — rejected: higher security risk without clear benefit over the chosen approach. +- Keep existing export-then-sign pattern — rejected: too slow and password-heavy for normal use. + +**Impact**: New `@subwallet/keyring` package; EVM signing flow no longer requires per-transaction key export. + +**Date**: 2022-08-01 +**Version**: pre-v0.6.x + +**Citations**: [#433](https://github.com/Koniverse/SubWallet-Extension/issues/433) + +--- + +### D5. Expose both re-nominate and unbond-then-rebond flows for validator switching + +**Context**: Users requested the ability to switch validators without waiting for the full unstaking period. Two flows exist: (1) unbond → wait → restake, and (2) direct re-nomination without unbonding. + +**Decision**: Expose both flows and improve UX for re-nomination to reduce waiting-time confusion. + +**Rationale**: Forcing users through the unbond path unnecessarily locks funds for the full unbonding period (28 days on Polkadot). Direct re-nomination is safe and already supported by the pallet. + +**Alternatives considered**: +- Only support unbond-then-rebond flow for simplicity — rejected: creates unnecessary wait and confusion. +- Auto-compound rewards as default — rejected: user should opt in. + +**Impact**: Staking UX updated to present both paths; re-nomination shortcut added. + +**Date**: 2022-08-01 +**Version**: pre-v0.6.x + +**Citations**: [#504](https://github.com/Koniverse/SubWallet-Extension/issues/504) + +--- + +## Phase 1 — ChainService Architecture & New UI (2022-10-14, shipped v0.7.x–v1.0.x) + +### D6. Scope MV3 migration storage to chrome.storage (defer PouchDB) + +**Context**: Chrome Manifest V3 migration required rethinking persistent storage. Service workers terminate after ~5 minutes, have no DOM access, and previously in-memory state had to be persisted. PouchDB was initially proposed for multi-platform storage. + +**Decision**: Use `chrome.storage` directly for all persistent extension state; defer PouchDB as not yet necessary. + +**Rationale**: `chrome.storage` was sufficient for the current extension and web-runner use case. Adding PouchDB would introduce complexity and a dependency before the use case was proven. + +**Alternatives considered**: +- Use PouchDB for all persistent data — rejected at this stage; deferred (see D7). +- Keep MV2 and delay migration — rejected: timeline risk. +- Use `chrome.storage` directly for all state — chosen approach. + +**Impact**: MV3 migration proceeded without a shared DB layer; storage model remained `chrome.storage`-based. + +**Date**: 2022-10-14 +**Version**: pre-v1.0.x + +**Citations**: [#707](https://github.com/Koniverse/SubWallet-Extension/issues/707), [#782](https://github.com/Koniverse/SubWallet-Extension/issues/782) + +--- + +### D7. Scope custom network support to pure EVM or pure Substrate only + +**Context**: ChainService JSON schema and UI were designed for single-type networks. Mixed-mode (both EVM and Substrate) custom networks were requested. + +**Decision**: Limit custom network support to pure EVM or pure Substrate networks only; defer mixed-mode support. + +**Rationale**: The ChainService schema was not yet designed for dual-mode networks; implementing them immediately would require a premature schema extension and UI redesign. + +**Alternatives considered**: +- Support all mixed networks immediately — rejected: too invasive. +- Defer all custom network support — rejected: pure EVM/Substrate custom networks were already valuable. + +**Impact**: Users can add custom networks of a single type; mixed-type custom chains require built-in support. + +**Date**: 2022-12-15 +**Version**: pre-v0.8.x + +**Citations**: [#926](https://github.com/Koniverse/SubWallet-Extension/issues/926) + +--- + +### D8. Deprioritise PouchDB for cross-platform storage + +**Context**: Following D6, PouchDB adoption was re-evaluated as the web-runner use case matured (see [#782](https://github.com/Koniverse/SubWallet-Extension/issues/782)). + +**Decision**: Formally deprioritise PouchDB; the current `chrome.storage` approach is sufficient and the web-runner does not yet require a shared DB layer. + +**Rationale**: The engineering cost of PouchDB outweighed the benefit at the current stage. No concrete cross-platform storage requirement existed that `chrome.storage` could not meet. + +**Alternatives considered**: +- Adopt PouchDB immediately — rejected: premature. +- Use IndexedDB directly — rejected: no advantage over `chrome.storage` for the current use case. + +**Impact**: Storage architecture remains `chrome.storage`-only through at least the v1.0 milestone. + +**Date**: 2023-04-20 +**Version**: pre-v1.1.x + +**Citations**: [#782](https://github.com/Koniverse/SubWallet-Extension/issues/782) + +--- + +### D9. Defer step 3 of online chain-list auto-update (immediate provider change) + +**Context**: The three-step auto-update from chain-list feature was implemented in steps. Step 3 — applying provider changes immediately after fetching from chain-list — was identified as too risky because it could break running connections. + +**Decision**: Do not apply provider URL changes immediately on chain-list fetch; defer step 3. + +**Rationale**: Interrupting live WS connections to switch providers could break active transactions and subscriptions. The benefit (fresher RPC endpoints) does not outweigh the reliability risk. + +**Alternatives considered**: +- Immediate provider update on chain-list change — rejected: breaks running connections. +- Versioned chain-list with `olderVersions` field for compatibility tracking — evaluated but not implemented. + +**Impact**: RPC provider changes from chain-list are only applied on next extension start, not live. + +**Date**: 2023-05-11 +**Version**: v1.0.3 + +**Citations**: [#1320](https://github.com/Koniverse/SubWallet-Extension/issues/1320) + +--- + +### D10. Do not auto-select validators; users should research or use pools + +**Context**: Auto-select validator was requested. The team lead evaluated two obvious strategies (highest APR, lowest commission) and found both to be potentially misleading. + +**Decision**: Do not implement auto-selection of validators; users should do their own research or stake via a nomination pool instead. + +**Rationale**: There is no sound wallet-level rationale for recommending a specific validator. Auto-selection by APR or commission could steer users toward validators with adverse behaviour (e.g., 100% commission briefly lowered to attract nominations). + +**Alternatives considered**: +- Auto-select by highest APR — rejected: misleading and manipulable. +- Auto-select by lowest commission — rejected: commission alone is not a reliable quality signal. + +**Impact**: No auto-validator-select feature shipped; nomination-pool path highlighted as the simpler option. + +**Date**: 2023-05-30 +**Version**: v1.0.x + +**Citations**: [#1417](https://github.com/Koniverse/SubWallet-Extension/issues/1417) + +--- + +### D11. Temporarily hide Substrate private-key export (no wallet supports re-import yet) + +**Context**: No wallet, including SubWallet, supported re-importing a Substrate account by raw private key. Exposing the export would mislead users into believing they could restore the account elsewhere. + +**Decision**: Hide the Substrate private-key export option until a corresponding import path exists. + +**Rationale**: Exporting a key that cannot be re-imported is a footgun. A clear warning was considered insufficient given the severity of potential fund loss. + +**Alternatives considered**: +- Keep export visible with a warning — rejected: warning insufficient for a footgun that causes irreversible fund loss. +- Implement import-by-private-key first — deferred; chosen as the pre-condition to re-enabling export. + +**Impact**: Export button hidden in account settings until import support ships. + +**Date**: 2023-04-18 +**Version**: v0.6.7 + +**Citations**: [#1207](https://github.com/Koniverse/SubWallet-Extension/issues/1207), [#1142](https://github.com/Koniverse/SubWallet-Extension/issues/1142) + +--- + +### D12. WalletConnect initial integration defers eth_signTransaction and eth_signTypedData + +**Context**: The initial WalletConnect integration was scoped. `eth_signTransaction` was not supported by the WC Core library at the time; `eth_signTypedData` had known stability problems. + +**Decision**: Omit `eth_signTransaction` and `eth_signTypedData` from the initial WalletConnect integration; return an `unsupported method` error to callers. + +**Rationale**: Shipping partial or unstable implementations of these methods would cause silent or confusing failures for dApps that depend on them. A clear `unsupported` error is better than an unreliable implementation. + +**Alternatives considered**: +- Implement with partial support and show unsupported error for missing methods — this was the chosen approach. + +**Impact**: Initial WalletConnect integration covers session creation/disconnect/sign for the core method set; callers get an explicit error for unsupported methods. + +**Date**: 2023-07-03 +**Version**: v1.1.1 + +**Citations**: [#1497](https://github.com/Koniverse/SubWallet-Extension/issues/1497) + +--- + +### D13. Fix Ledger HID conflicts by disconnecting device on session close (not by splitting HTML files) + +**Context**: Ledger HID transport conflicts arose between the expand view and popup view sharing the same domain. A proposed fix was to serve them from separate HTML files. + +**Decision**: Disconnect the Ledger device on session close instead of splitting `index.html` into separate files for expand and popup views. + +**Rationale**: Separating HTML files would require a large, invasive refactor of the routing architecture. The disconnect-on-close approach solves the conflict with minimal code change. + +**Alternatives considered**: +- Separate `index.html` files for expand and popup views — rejected: too invasive. + +**Impact**: Ledger device is disconnected when the popup or expand view is closed, preventing HID conflicts. + +**Date**: 2023-06-30 +**Version**: v1.0.9 + +**Citations**: [#1573](https://github.com/Koniverse/SubWallet-Extension/issues/1573) + +--- + +### D14. Adopt EIP-6963 multi-provider discovery alongside window.ethereum injection + +**Context**: SubWallet's EVM provider conflicted with MetaMask and other injected wallets because all wallets competed to set `window.ethereum`. + +**Decision**: Implement EIP-6963 `announce`/`request-provider` events alongside the existing injected provider, enabling multiple wallets to coexist on the same page. + +**Rationale**: Continuing with single `window.ethereum` injection breaks multi-wallet UX and causes SubWallet to override or be overridden by other wallets. EIP-6963 is the browser-standardised coexistence mechanism. + +**Alternatives considered**: +- Continue with single `window.ethereum` injection — rejected: breaks multi-wallet UX and caused Uniswap load failures. + +**Impact**: EIP-6963 RDNS registered; SubWallet visible as a distinct provider in dApps supporting multi-wallet discovery. + +**Date**: 2023-10-16 +**Version**: v1.1.12 + +**Citations**: [#2021](https://github.com/Koniverse/SubWallet-Extension/issues/2021), [#2328](https://github.com/Koniverse/SubWallet-Extension/issues/2328) + +--- + +### D15. Defer custom derived-path feature to avoid Milestone 6 scope creep + +**Context**: Custom derived-path was requested during Milestone 6 development. Implementing it immediately would risk delaying the milestone. + +**Decision**: Close the custom derived-path issue and resolve it in a follow-up issue (#3556) in a later milestone. + +**Rationale**: Scope discipline is required to ship on time. The feature is useful but not critical for the current milestone's core deliverables. + +**Alternatives considered**: +- Implement immediately within Milestone 6 scope — rejected: scope creep risk. + +**Impact**: Custom derived path deferred; implementation tracked in [#3556](https://github.com/Koniverse/SubWallet-Extension/issues/3556). + +**Date**: 2023-11-01 +**Version**: v1.1.x + +**Citations**: [#2135](https://github.com/Koniverse/SubWallet-Extension/issues/2135) + +--- + +### D16. Complete MV3 migration (service-worker lifecycle, DApp connection, fetch, storage) + +**Context**: Chrome's roadmap required MV3 compliance. The full migration had been researched since D3 (2022-07-01) but execution was deferred while the architecture was stabilised. + +**Decision**: Upgrade the extension from MV2 to MV3, redesigning service-worker lifecycle (sleep/full mode), DApp connection (reconnect on wake), `fetch` API (moved to service worker), and storage. + +**Rationale**: Chrome's enforcement deadline left no further room for delay. A full architectural migration is cleaner than maintaining both MV2 and MV3 code paths. + +**Alternatives considered**: +- Delay MV3 migration and maintain MV2 branch until Chrome forced migration — rejected: timeline risk too high. + +**Impact**: Extension shipped as MV3 on Chrome and Firefox; a new MV3 UI design also shipped alongside. + +**Date**: 2023-11-18 +**Version**: v1.2.1 + +**Citations**: [#2205](https://github.com/Koniverse/SubWallet-Extension/issues/2205), [#2120](https://github.com/Koniverse/SubWallet-Extension/issues/2120) + +--- + +### D17. Implement full OpenGov voting (not read-only) for Polkadot governance + +**Context**: Polkadot switched to OpenGov (governance v2). SubWallet needed to decide the scope of governance support. + +**Decision**: Implement full voting (standard, abstain/split, delegation) rather than read-only referendum listing, requiring significant API integration work. + +**Rationale**: Read-only governance display provides minimal value; users expect to vote in-app. Full voting is the differentiating feature. + +**Alternatives considered**: +- Read-only referendum listing without vote submission — rejected: insufficient value. + +**Impact**: Full OpenGov voting shipped in extension and WebApp, including track metadata, vote counting, and delegation logic. + +**Date**: 2023-11-20 +**Version**: v1.1.x (shipped later via [PR #4301](https://github.com/Koniverse/SubWallet-Extension/pull/4301)) + +**Citations**: [#2216](https://github.com/Koniverse/SubWallet-Extension/issues/2216), [#2222](https://github.com/Koniverse/SubWallet-Extension/issues/2222), [#2271](https://github.com/Koniverse/SubWallet-Extension/issues/2271) + +--- + +### D18. Scope ZK assets (Manta) to extension only — exclude WebApp and mobile + +**Context**: Integrating ZK metadata from Manta's WASM bundle would increase the extension size by 3× and caused incorrect `chrome.runtime.onInstalled` firing. + +**Decision**: Do not include ZK assets in WebApp and mobile builds; scope ZK asset support to the extension only. + +**Rationale**: A 3× extension size increase is unacceptable; it would slow startup and exceed store size guidelines. The web/mobile use case for ZK assets was not yet proven. + +**Alternatives considered**: +- Include ZK assets in WebApp and mobile with lazy loading — rejected: size increase persists even with lazy loading at the bundle level. + +**Impact**: ZK-asset (Manta) functionality is extension-only; WebApp and mobile do not expose this feature. + +**Date**: 2023-07-04 +**Version**: v1.1.1 + +**Citations**: [#1608](https://github.com/Koniverse/SubWallet-Extension/issues/1608), [#1628](https://github.com/Koniverse/SubWallet-Extension/issues/1628) + +--- + +### D19. Refactor balance service to new service architecture (centralise cache invalidation) + +**Context**: Balance functions were scattered across subscription and state modules, leading to stale cache bugs — balances shown after account removal, incorrect All-accounts totals. + +**Decision**: Refactor balance service to match the new service architecture used by other services, centralising balance logic and handling cache invalidation explicitly on account mutations. + +**Rationale**: Patching individual stale-cache bugs in place would not prevent future occurrences. A structural refactor ensures cache invalidation is always triggered by account mutations through a single code path. + +**Alternatives considered**: +- Patch individual stale-cache bugs without architectural refactor — rejected: symptom treatment, not root-cause fix. + +**Impact**: `BalanceService` class created; cache invalidated deterministically on account add/remove. + +**Date**: 2023-12-27 +**Version**: v1.1.27 + +**Citations**: [#2416](https://github.com/Koniverse/SubWallet-Extension/issues/2416) + +--- + +## Phase 2 — Earning, Swap, and Multi-chain Expansion (2024-02-01, shipped v1.1.x–v1.2.x) + +### D20. Rename "Staking" feature to "Earning" across all platforms + +**Context**: The Staking feature had expanded to cover native staking, liquid staking, and lending — mechanisms that do not align with the word "staking". + +**Decision**: Rename the feature from "Staking" to "Earning" across Extension, WebApp, and Mobile, and display a dismissible notification popup to existing users explaining the change. + +**Rationale**: "Earning" better represents the full range of yield mechanisms and avoids confusion for users who have liquid staking or lending positions. The notification mitigates user confusion from the sudden rename. + +**Alternatives considered**: +- Keep "Staking" name — rejected: misleading for liquid staking and lending users. +- Use "Yield" instead of "Earning" — rejected: "Earning" tested better with the target audience. + +**Impact**: All Staking labels renamed to Earning; user notification shipped. + +**Date**: 2024-02-01 +**Version**: v1.1.34 + +**Citations**: [#2599](https://github.com/Koniverse/SubWallet-Extension/issues/2599) + +--- + +### D21. Adopt Texterify as the multilingual management platform + +**Context**: Translation management was fragmented — files were manually managed in source control with no cross-team tooling, making updates and coordination difficult. + +**Decision**: Adopt Texterify as the centralised multilingual management platform. + +**Rationale**: Texterify enables cross-team coordination and easier updates compared to ad-hoc translation file management. It provides a dedicated workflow for translators and reduces the risk of key drift between source and translated strings. + +**Alternatives considered**: +- Continue with ad-hoc translation files managed in source control — rejected: not scalable as language count grows. + +**Impact**: Translation workflow moved to Texterify; keys synced from the CMS rather than manually in-repo. + +**Date**: 2024-09-09 +**Version**: v1.1.x + +**Citations**: [#2132](https://github.com/Koniverse/SubWallet-Extension/issues/2132) + +--- + +### D22. Integrate Meld All-in-One Wizard directly (supersedes standalone Meld research task) + +**Context**: A Milestone 8 research task for a standalone Meld on-ramp integration was open. During development, the Meld All-in-One Wizard became available, covering the same use case. + +**Decision**: Integrate the Meld All-in-One Wizard directly via [#4085](https://github.com/Koniverse/SubWallet-Extension/issues/4085), closing the standalone research task. + +**Rationale**: The Wizard provides the desired fiat on-ramp functionality immediately without requiring a custom integration. It reduces engineering effort and time-to-ship. + +**Alternatives considered**: +- Standalone Meld on-ramp research — superseded by Wizard availability. +- Integrate Meld Wizard directly — chosen. + +**Impact**: Meld on-ramp shipped via Wizard; Transak off-ramp also integrated in the same milestone. + +**Date**: 2025-03-10 +**Version**: v1.3.25 + +**Citations**: [#3842](https://github.com/Koniverse/SubWallet-Extension/issues/3842), [#4085](https://github.com/Koniverse/SubWallet-Extension/issues/4085) + +--- + +### D23. GRC-20 token type renamed to VFT (Vara Fungible Token) + +**Context**: The upstream Vara ecosystem renamed the GRC-20 token standard to VFT (Vara Fungible Token). Existing GRC-20 tokens in user wallets had the old type stored. + +**Decision**: Rename the token type from GRC-20 to VFT across Extension and WebApp, and migrate existing GRC-20 token entries to the new type field value. + +**Rationale**: Keeping the GRC-20 label while the ecosystem uses VFT would confuse users and developers. The migration ensures existing user tokens continue to work without manual intervention. + +**Alternatives considered**: +- Keep GRC-20 label with an internal alias — rejected: creates a two-name problem for the same standard. + +**Impact**: Token type renamed; migration job converts stored GRC-20 entries to VFT. + +**Date**: 2024-08-01 +**Version**: v1.1.66 + +**Citations**: [#3268](https://github.com/Koniverse/SubWallet-Extension/issues/3268), [#3290](https://github.com/Koniverse/SubWallet-Extension/issues/3290), [#3291](https://github.com/Koniverse/SubWallet-Extension/issues/3291), [PR #3271](https://github.com/Koniverse/SubWallet-Extension/pull/3271) + +--- + +### D24. Abandon SnowBridge SDK in favour of ParaSpell API for XCM bridge protection + +**Context**: A SnowBridge SDK implementation was underway to provide safety guards against asset loss on the Polkadot↔Ethereum bridge. + +**Decision**: Abandon the SnowBridge SDK implementation and rely on the ParaSpell API (already integrated) to provide the same protection. + +**Rationale**: ParaSpell was already integrated and provides equivalent protection against asset loss without adding a new SDK dependency. Maintaining two bridge SDKs for overlapping functionality increases complexity. + +**Alternatives considered**: +- Implement SnowBridge SDK directly (`@snowbridge/api`) — rejected: redundant with ParaSpell. + +**Impact**: SnowBridge SDK not shipped; ParaSpell API used for all XCM bridge operations. + +**Date**: 2024-10-01 +**Version**: v1.2.4 + +**Citations**: [#3416](https://github.com/Koniverse/SubWallet-Extension/issues/3416) + +--- + +### D25. XCM Transfer Max formula: reserve 1.2× ED + 2× fee to prevent dust loss + +**Context**: Transfer Max via XCM could leave the source account with less than the existential deposit (ED), causing account reaping and balance loss. + +**Decision**: When transferring the maximum amount via XCM, reserve `1.2 × ED + 2 × fee` rather than sending the full transferable balance. For regular (non-XCM) transfers, show a warning on "Transfer All" rather than blocking. + +**Rationale**: The multipliers provide a safety buffer against fee estimation variance and ED rounding. Blocking entirely for non-XCM transfers is overly restrictive; a warning is sufficient. + +**Alternatives considered**: +- Block transfer-max entirely for XCM — rejected: too restrictive. +- Use a flat ED multiplier — rejected: less precise than the 1.2× formula. + +**Impact**: XCM Transfer Max capped at `transferable − 1.2 × ED − 2 × fee`; warning modal shown for non-XCM transfer-all. + +**Date**: 2024-07-01 +**Version**: v1.1.48 + +**Citations**: [#2798](https://github.com/Koniverse/SubWallet-Extension/issues/2798) + +--- + +## Phase 3 — Bitcoin, Cardano, Multi-chain Ecosystem (2025-01-01, shipped v1.3.x) + +### D26. Cancel dynamic swap pair support (Milestone 8 Cancel) + +**Context**: Dynamic swap pair discovery was planned for Milestone 8 to automatically enumerate valid swap pairs from providers. + +**Decision**: Cancel dynamic swap pair support and retain static pair configuration that is updated manually. + +**Rationale**: Dynamic pair management adds complexity in filtering, ranking, and validating pairs across multiple providers. Static lists are simpler to audit for correctness and safety, and the maintenance overhead is acceptable at the current scale. + +**Alternatives considered**: +- Implement dynamic swap pair discovery — rejected: complexity not justified by benefit at current scale. +- Keep static pair lists — chosen approach. + +**Impact**: Swap pairs remain statically configured; engineering effort redirected to other Milestone 8 priorities. + +**Date**: 2025-01-01 +**Version**: v1.3.x + +**Citations**: [#4078](https://github.com/Koniverse/SubWallet-Extension/issues/4078), [PR #4079](https://github.com/Koniverse/SubWallet-Extension/pull/4079) + +--- + +### D27. Cancel 1inch DEX aggregator integration (economically unviable pricing) + +**Context**: 1inch DEX aggregator integration was planned as a swap provider. During contract negotiations, 1inch's pricing mechanism made the integration economically unviable for SubWallet's model. + +**Decision**: Cancel 1inch integration. + +**Rationale**: 1inch's pricing structure would not be sustainable. Other swap providers (Chainflip, HydraDX, Uniswap, KyberSwap, SimpleSwap) cover the same user need without the economic blocker. + +**Alternatives considered**: +- Integrate 1inch as planned — rejected: economically unviable. +- Negotiate different pricing tier — insufficient progress to unblock the integration. + +**Impact**: 1inch removed from swap provider roadmap; no code shipped. + +**Date**: 2025-04-07 +**Version**: v1.3.x + +**Citations**: [#4105](https://github.com/Koniverse/SubWallet-Extension/issues/4105) + +--- + +### D28. Remain on ParaSpell API for XCM (do not build in-house or self-host) + +**Context**: As XCM complexity grew (Snowbridge, Across, Asset Hub Migration), the team evaluated whether to build XCM transfer logic in-house or fork/self-host ParaSpell. + +**Decision**: Continue using the ParaSpell API, upgrading through version milestones (v4, v5, v1 API) as they release. + +**Rationale**: Building XCM transfer logic in-house would require significant ongoing maintenance to track every Polkadot runtime upgrade. Forking ParaSpell creates a self-maintenance burden without the upstream improvements. Staying on the official API ensures the team benefits from ParaSpell's own ecosystem tracking. + +**Alternatives considered**: +- Build XCM transfer logic in-house — rejected: high maintenance cost. +- Fork and self-host the ParaSpell XCM API — rejected: maintenance burden without benefit. +- Stay on ParaSpell and upgrade versions as they release — chosen. + +**Impact**: XCM bridge logic remains delegated to ParaSpell; three version upgrades (v4, v5, v1 API) shipped during this phase. + +**Date**: 2025-07-01 +**Version**: v1.3.55–v1.3.79 + +**Citations**: [#4405](https://github.com/Koniverse/SubWallet-Extension/issues/4405), [#4606](https://github.com/Koniverse/SubWallet-Extension/issues/4606), [#4908](https://github.com/Koniverse/SubWallet-Extension/issues/4908), [#4979](https://github.com/Koniverse/SubWallet-Extension/issues/4979) + +--- + +### D29. Remove the Crowdloans tab (Polkadot Agile Coretime deprecates slot auctions) + +**Context**: Polkadot launched Agile Coretime, permanently deprecating parachain slot auctions and crowdloans. No future crowdloans will occur. + +**Decision**: Remove the Crowdloans tab from the extension and web app entirely. + +**Rationale**: Displaying a tab for a feature that can never be used again adds UI noise without value. Historical locked-fund visibility can be accessed via balance screens. + +**Alternatives considered**: +- Keep the Crowdloans tab for historical locked-fund visibility — rejected: adds noise for a permanently deprecated feature. +- Remove it entirely from all platforms — chosen. + +**Impact**: Crowdloans tab removed; a community PR targeting the wrong branch (master instead of dev) was also rejected. + +**Date**: 2025-10-01 +**Version**: post-v1.3.x + +**Citations**: [#4920](https://github.com/Koniverse/SubWallet-Extension/issues/4920), [PR #4949](https://github.com/Koniverse/SubWallet-Extension/pull/4949) + +--- + +### D30. Migrate ParaSpell from V4/V5 to v1 API (docs v12-to-v13) + +**Context**: ParaSpell released a new v1 API (corresponding to docs v12-to-v13 migration), changing base URLs and request payload structures across all XCM functions. + +**Decision**: Migrate the SubWallet ParaSpell integration to v1 API, refactoring all request payloads and parameters. + +**Rationale**: Staying on V4/V5 would eventually lose upstream support and miss new features. The v1 API is the stable forward path for all new ParaSpell ecosystem features (per D28). + +**Alternatives considered**: None documented — migration is a maintenance requirement of the D28 stay-on-ParaSpell decision. + +**Impact**: All XCM-related ParaSpell calls updated; two PRs shipped the migration. + +**Date**: 2025-07-01 +**Version**: v1.3.72–v1.3.79 + +**Citations**: [#4908](https://github.com/Koniverse/SubWallet-Extension/issues/4908), [#4979](https://github.com/Koniverse/SubWallet-Extension/issues/4979), [PR #4909](https://github.com/Koniverse/SubWallet-Extension/pull/4909), [PR #4982](https://github.com/Koniverse/SubWallet-Extension/pull/4982) From e9ccbab914d25e9b7a3f4ec48628ea50032ccd71 Mon Sep 17 00:00:00 2001 From: tunghp2002 Date: Wed, 3 Jun 2026 18:50:07 +0700 Subject: [PATCH 15/67] =?UTF-8?q?docs:=20a5-lessons=20=E2=80=94=20seed=20d?= =?UTF-8?q?ocs/LESSONS.md=20from=20incident=20history?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/LESSONS.md | 538 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 docs/LESSONS.md diff --git a/docs/LESSONS.md b/docs/LESSONS.md new file mode 100644 index 00000000000..378eee59bde --- /dev/null +++ b/docs/LESSONS.md @@ -0,0 +1,538 @@ +# LESSONS.md — SubWallet Extension: Lessons Learned + +> **Canonical guide**: AGENTS.md +> **One rule above all others**: a lesson earns its keep if it saves the *next contributor* time. +> Delete entries only when the underlying library or behavior has fundamentally changed — stale advice is worse than no advice. +> Reference from commit messages: "Per LESSONS §N". + +--- + +## 1. Transferable balance formula must track upstream Substrate changes + +**What happened**: Multiple releases shipped incorrect "transferable" amounts — users saw wrong send-max values, some triggered account reaping, and negative balances appeared in transaction screens. The root cause was a stale or inconsistent formula for computing transferable balance. + +**Why**: Substrate's balance pallet changed how `frozen` and `reserved` interact. The correct formula after polkadot-sdk PR #12951 is `transferable = free - max(frozen - reserved, ED)`. Code that hard-coded an earlier approximation (`free - frozen`) produced wrong results after runtime upgrades. Additional complexity arose from the `flags` field on system accounts (Polkadot Collectives, Bridge Hub) inflating locked values, and from non-standard pallets (Equilibrium's tuple-based balance storage, Asset Hub pallet-assets). + +**How to avoid**: +- Pin the transferable formula to the upstream Substrate definition and update it whenever a polkadot-sdk balance-pallet PR merges. +- Each chain with a non-standard balance pallet (Equilibrium, Gear/Vara, Cardano) requires an explicit adapter — never fall through to the generic Substrate path. +- Unit-test the formula against all pallet variants: `system v1`, `pallet-assets`, `nomination-pool`, and `local token`. +- Send-max logic must subtract **both** fee and ED in a single, consistent calculation; do not apply ED subtraction in multiple places. + +See [#303](https://github.com/Koniverse/SubWallet-Extension/issues/303), [#353](https://github.com/Koniverse/SubWallet-Extension/issues/353), [#1163](https://github.com/Koniverse/SubWallet-Extension/issues/1163), [#2079](https://github.com/Koniverse/SubWallet-Extension/issues/2079), [#2118](https://github.com/Koniverse/SubWallet-Extension/issues/2118), [#3246](https://github.com/Koniverse/SubWallet-Extension/issues/3246), [#3440](https://github.com/Koniverse/SubWallet-Extension/issues/3440). + +--- + +## 2. XCM routes break silently after runtime upgrades — always re-validate + +**What happened**: XCM transfers failed silently or produced confusing error pages after Substrate runtime upgrades on partner chains. Causes included: MultiLocation v2/v3 destination format mismatches, Paraspell major-version API changes breaking multiple chains at once, XCM delivery fees introduced in polkadot-sdk v1.3.0 being absent from fee estimation, and the Polkadot Asset Hub Migration (AHM) invalidating entire routing paths. + +**Why**: XCM is a versioned protocol and both the pallet (v2→v3→v4) and the fee model evolve with each runtime upgrade. SubWallet's XCM layer depended on Paraspell as an abstraction, but Paraspell itself breaks on major versions. The AHM moved DOT from the relay chain to Asset Hub, making pre-migration reserve/teleport paths incorrect. + +**How to avoid**: +- Run a full XCM regression sweep against all active channels after every relay-chain or major parachain runtime upgrade. +- Pin Paraspell upgrades to a tested release; schedule a migration PR before the partner chain's enactment date. +- XCM fee estimation must include **delivery fees** (introduced in polkadot-sdk v1.3.0) and **destination-chain ED** for max-transfer cases. +- Each active XCM channel must have a toggle so it can be disabled instantly without a code release (learned from the Acala security incident). +- Max XCM transfer must subtract the source-chain ED to prevent dust-loss scenarios on the source. + +See [#333](https://github.com/Koniverse/SubWallet-Extension/issues/333), [#667](https://github.com/Koniverse/SubWallet-Extension/issues/667), [#1218](https://github.com/Koniverse/SubWallet-Extension/issues/1218), [#1505](https://github.com/Koniverse/SubWallet-Extension/issues/1505), [#2792](https://github.com/Koniverse/SubWallet-Extension/issues/2792), [#3617](https://github.com/Koniverse/SubWallet-Extension/issues/3617), [#4416](https://github.com/Koniverse/SubWallet-Extension/issues/4416), [#4632](https://github.com/Koniverse/SubWallet-Extension/issues/4632). + +--- + +## 3. Balance cache must be invalidated on account mutations and network toggles + +**What happened**: Stale balances appeared after send/receive operations, after account create/remove, after switching tokens in the send-fund screen, and in All-Accounts mode after account imports. NFTs and earning positions also continued to display after their backing network was disabled. + +**Why**: Balance subscriptions were keyed on network only (not on `(network, token)` tuples) and were not torn down on account mutation events. The balance service scattered logic across subscription and state modules without a central cache-invalidation strategy. + +**How to avoid**: +- Force-refresh balance after any successful transfer; do not rely on the periodic polling interval alone. +- Balance subscriptions in send-fund and confirmation screens must re-subscribe when **either** the network or the selected token changes. +- Invalidate the All-Accounts balance cache on every account add/remove event. +- Network-dependent UI state (balances, NFTs, earning positions) must be cleared when the backing network is disabled. +- The balance service should be a single service with explicit cache-invalidation hooks, not scattered subscriptions. + +See [#463](https://github.com/Koniverse/SubWallet-Extension/issues/463), [#968](https://github.com/Koniverse/SubWallet-Extension/issues/968), [#1161](https://github.com/Koniverse/SubWallet-Extension/issues/1161), [#2323](https://github.com/Koniverse/SubWallet-Extension/issues/2323), [#1151](https://github.com/Koniverse/SubWallet-Extension/issues/1151), [#2612](https://github.com/Koniverse/SubWallet-Extension/issues/2612). + +--- + +## 4. All substrate fee and amount values must use arbitrary-precision integers + +**What happened**: DOT staking fee estimation returned a value larger than JavaScript's safe integer range, causing a BigInt overflow that looped the confirmation screen indefinitely. EVM gas estimation also fluctuated, causing submit failures on Astar EVM and other chains. + +**Why**: Substrate RPC responses for fees and balances can exceed `Number.MAX_SAFE_INTEGER`. JavaScript's native `number` type silently rounds large values, producing incorrect arithmetic. EVM gas estimation is inherently volatile and lacks a floor value. + +**How to avoid**: +- All fee, balance, and amount values received from Substrate RPC must be parsed as `BigNumber` or `BigInt` **before** any arithmetic or display conversion — never pass them through `Number()`. +- EVM gas estimation must use a retry/fallback strategy with a configurable floor value; guard against `undefined` fee responses before rendering. +- Fee display on the confirmation screen must **always** be shown; estimation errors should display a warning, not silently hide the fee field. + +See [#641](https://github.com/Koniverse/SubWallet-Extension/issues/641), [#770](https://github.com/Koniverse/SubWallet-Extension/issues/770), [#2255](https://github.com/Koniverse/SubWallet-Extension/issues/2255), [#2336](https://github.com/Koniverse/SubWallet-Extension/issues/2336), [#2146](https://github.com/Koniverse/SubWallet-Extension/issues/2146). + +--- + +## 5. Staking extrinsics are chain-specific — parachain pallets differ from relay-chain + +**What happened**: Withdrawal, stake, and unstake operations failed on parachains (Moonbeam/Moonriver, GLMR, Turing Network) because the code reused relay-chain extrinsic signatures. `withdrawUnbonded` on relay-chain requires a `slashing_span` count parameter that was omitted. Moonbeam's `parachain-staking` pallet has entirely different call signatures. The Polkadot/Kusama runtime upgrade in 2023 deprecated controller accounts in favour of proxy accounts, breaking `bond()` and `set_controller()`. + +**Why**: The team initially treated parachain staking pallets as variations of the relay-chain `staking` pallet. Each parachain ships its own staking pallet with different extrinsic names, parameter order, and business logic. + +**How to avoid**: +- Maintain a per-chain staking-pallet mapping that specifies which extrinsic names, parameter sets, and response shapes apply. +- `withdrawUnbonded` always requires a `slashing_span` count query before submission. +- QR-signer flows must be tested against each supported parachain staking pallet, not just relay-chain staking. +- Monitor Polkassembly and Kusama/Polkadot runtime upgrade proposals; schedule a compatibility update before the enactment block. +- APY calculations must be verified against on-chain data after any staking API update. + +See [#809](https://github.com/Koniverse/SubWallet-Extension/issues/809), [#837](https://github.com/Koniverse/SubWallet-Extension/issues/837), [#717](https://github.com/Koniverse/SubWallet-Extension/issues/717), [#1350](https://github.com/Koniverse/SubWallet-Extension/issues/1350), [#4026](https://github.com/Koniverse/SubWallet-Extension/issues/4026), [#4224](https://github.com/Koniverse/SubWallet-Extension/issues/4224). + +--- + +## 6. Multi-step / liquid staking flows need idempotency and subscription cleanup + +**What happened**: Liquid staking with Bifrost Polkadot showed duplicate "Mint vDOT" steps because incorrect state management in the multi-step earn flow allowed the same step to be queued twice. XCM balance subscriptions were not cancelled after XCM actions on the earning screen, generating redundant background calls. Swapping-rate defaults were set excessively high. vDOT and vMANTA unstaking failed due to bugs in the liquid staking handler. + +**Why**: Multi-step flows accumulate state across async operations. Without idempotency checks at each step boundary and explicit subscription teardown after completion, steps can be re-triggered and subscriptions leak. + +**How to avoid**: +- Each step in a multi-step liquid staking or swap flow must be guarded with an idempotency key; duplicate step enqueueing must be rejected. +- Unsubscribe all balance and status listeners immediately after an XCM or liquid staking operation completes. +- Default slippage values for each swap/liquid staking provider must be explicitly reviewed during integration; never ship with a provider default of 0% or maximum. +- Derivative token unstake paths (vDOT, vMANTA, etc.) require dedicated regression tests. + +See [#2537](https://github.com/Koniverse/SubWallet-Extension/issues/2537), [#2089](https://github.com/Koniverse/SubWallet-Extension/issues/2089), [#2155](https://github.com/Koniverse/SubWallet-Extension/issues/2155), [#4054](https://github.com/Koniverse/SubWallet-Extension/issues/4054). + +--- + +## 7. Manifest V3 service-worker lifecycle breaks always-on patterns + +**What happened**: After MV3 migration, WalletConnect connection popups stopped appearing because service-worker sleep interrupted event listener registration. Outbound fetch calls to online resources (chain configs, swap quotes, price data) were blocked by MV3 service-worker fetch restrictions, leaving the extension operating on stale/empty data after a restart. Pending transactions held in memory indefinitely froze sessions when the service worker was interrupted. + +**Why**: MV3 replaces persistent background pages with event-driven service workers that can terminate after ~5 minutes of inactivity. Any pattern that assumed a persistent in-memory state or always-available fetch context breaks silently. + +**How to avoid**: +- WalletConnect (and similar always-on listeners) must re-register their event handlers on every service-worker wake event. +- All online-resource fetching (chain configs, price data, swap quotes) must use fetch patterns compatible with MV3 service-worker restrictions; test explicitly after every MV3 build. +- Pending transaction state must have a timeout; after expiry, mark as failed so the session is unblocked. +- Every online resource endpoint must have a static fallback bundled with the extension. + +See [#2407](https://github.com/Koniverse/SubWallet-Extension/issues/2407), [#2949](https://github.com/Koniverse/SubWallet-Extension/issues/2949), [#2992](https://github.com/Koniverse/SubWallet-Extension/issues/2992), [#1308](https://github.com/Koniverse/SubWallet-Extension/issues/1308), [#2322](https://github.com/Koniverse/SubWallet-Extension/issues/2322). + +--- + +## 8. EVM provider injection must implement EIP-6963 and avoid tab pollution + +**What happened**: SubWallet's EVM provider conflicted with MetaMask and other wallets because it was injected into all pages (not just authorized dApp pages) and did not implement EIP-6963 multi-provider discovery. Uniswap failed to load when only SubWallet was installed. The inject script was not removed on tab close, causing provider leaks across unrelated browser tabs. + +**Why**: Before EIP-6963, wallets competed for `window.ethereum`, causing the last-loaded wallet to win. Without EIP-6963 announce/request-provider events, multi-wallet coexistence is broken by design. + +**How to avoid**: +- Implement EIP-6963 (`eip6963:announceProvider` / `eip6963:requestProvider`) alongside the legacy `window.ethereum` injection; this is now a baseline requirement. +- Scope content-script injection to explicitly authorized dApp URLs; auto-remove provider on tab unload. +- Keep SubWallet's RDNS and EIP-6963 metadata up to date when bumping WalletConnect or connector library versions. + +See [#591](https://github.com/Koniverse/SubWallet-Extension/issues/591), [#2588](https://github.com/Koniverse/SubWallet-Extension/issues/2588), [#2021](https://github.com/Koniverse/SubWallet-Extension/issues/2021). + +--- + +## 9. dApp connection: provider init timing and account-type mismatches + +**What happened**: DApps that polled for the EVM provider immediately on page load (e.g., Moonbase App) missed the provider because the background service worker was still starting. Substrate accounts appeared on EVM-only dApp authorization requests and vice versa. After version upgrades, accounts on the dApp allowlist were assigned to the wrong dApp type. SubWallet auto-connected when MetaMask was the active wallet. + +**Why**: The EVM provider was initialized late in the startup sequence, and the dApp authorization store did not persist the account's dApp type (Substrate vs EVM) alongside the authorization entry. + +**How to avoid**: +- Inject the provider synchronously in the content script before `DOMContentLoaded`; use a deferred promise pattern so the provider is available immediately even if the background is still starting. +- Persist dApp type alongside each authorized account entry; run a migration on extension upgrade. +- Wallet-detection logic must check whether SubWallet is the intended injected provider before initiating a connection handshake. +- dApp permission requests must pre-select the network matching the dApp's `chainId`, not the first active EVM network. + +See [#444](https://github.com/Koniverse/SubWallet-Extension/issues/444), [#71](https://github.com/Koniverse/SubWallet-Extension/issues/71), [#473](https://github.com/Koniverse/SubWallet-Extension/issues/473), [#789](https://github.com/Koniverse/SubWallet-Extension/issues/789), [#3180](https://github.com/Koniverse/SubWallet-Extension/issues/3180). + +--- + +## 10. Ledger signing failures are often upstream in polkadot-js or derivation-path conflicts + +**What happened**: Transactions signed via Ledger produced bad signatures due to a polkadot-js API issue (#5555). Ledger constrains one asset per derivation path; users who sent tokens to the wrong derivative account lost UI access. The Polkadot app and Avail Recovery app used different derivation paths, so assets sent to the Polkadot app path could not be recovered with the Avail app. Extension crashed when a dApp supplied a V5 XCM Location as `assetId` in a `SignerPayload` because the signing code didn't handle V5 format. + +**Why**: The Ledger hardware signing path is a long chain involving hardware firmware, the Ledger app, the transport library, and the polkadot-js API. Any link in this chain can produce an incorrect signature or derivation. + +**How to avoid**: +- Monitor the polkadot-js API repo for Ledger-related bug fixes; pin to validated versions. +- When supporting multiple Ledger apps for the same chain ecosystem, verify address derivation compatibility between apps **before** offering both to users. +- Clearly communicate Ledger derivation-path constraints in UI; maintain an offline recovery tool for assets stranded in wrong derivative accounts. +- Signer payload handling must defensively parse and validate `assetId` types including future XCM Location versions (V4, V5). +- Each Ledger app/chain combination requires its own signed-extension matrix to be validated. + +See [#1096](https://github.com/Koniverse/SubWallet-Extension/issues/1096), [#2200](https://github.com/Koniverse/SubWallet-Extension/issues/2200), [#3145](https://github.com/Koniverse/SubWallet-Extension/issues/3145), [#4645](https://github.com/Koniverse/SubWallet-Extension/issues/4645), [#4989](https://github.com/Koniverse/SubWallet-Extension/issues/4989). + +--- + +## 11. Transaction history parsers need format-specific test coverage + +**What happened**: Transaction history was recorded incorrectly for dApp-originated transactions because the history writer used the extension's internal account context instead of the dApp-supplied transaction parameters. EVM history and NFT transfer history disappeared after a receipt format change. Duplicate entries appeared when both local state and Subscan indexer returned the same transaction. + +**Why**: The transaction history subsystem has multiple data sources (local state, Subscan, SubSquid, in-extension recording). Each source has its own data shape and each chain type (Substrate, EVM, XCM, NFT) has different record formats. + +**How to avoid**: +- History entries must be constructed from on-chain extrinsic data, not inferred from UI context — especially for dApp-originated transactions. +- History parsers need regression tests covering EVM receipt format updates and NFT transfer record shapes. +- Deduplicate entries from local state and external indexers before rendering; use extrinsic hash as the canonical key. +- NFT transfer amount formatting must use a dedicated path, not the fungible token amount formatter. + +See [#675](https://github.com/Koniverse/SubWallet-Extension/issues/675), [#2362](https://github.com/Koniverse/SubWallet-Extension/issues/2362), [#2373](https://github.com/Koniverse/SubWallet-Extension/issues/2373), [#2613](https://github.com/Koniverse/SubWallet-Extension/issues/2613). + +--- + +## 12. Transaction nonce collisions and signing-queue ordering + +**What happened**: Submitting two transactions in quick succession before the first was included in a block caused the second to fail with a nonce collision error. Transaction signing failed when the chain API was not connected — the signing handler did not wait for API initialization. + +**Why**: Substrate nonces are per-account and must be monotonically increasing. Without a transaction queue that serializes submission, the extension can submit two transactions with the same nonce. + +**How to avoid**: +- Implement a transaction queue that holds subsequent transactions until the preceding one receives block inclusion confirmation. +- Transaction signing must queue or prompt chain reconnection rather than failing silently when the API is uninitialized. +- Never allow the confirmation screen to progress to signing when the account balance is insufficient to cover gas fees; validate before showing the popup. + +See [#403](https://github.com/Koniverse/SubWallet-Extension/issues/403), [#419](https://github.com/Koniverse/SubWallet-Extension/issues/419), [#4300](https://github.com/Koniverse/SubWallet-Extension/issues/4300). + +--- + +## 13. Metadata hash and signed extension compatibility must be validated per network + +**What happened**: AVL token transfers produced bad signatures because client-side metadata hash calculation was done server-side, causing version skew. Transaction signing for Moonbase Alpha earning failed after the `CheckMetadataHash` signed extension rollout due to metadata hash mismatch. Aleph Zero Ledger signing failed on some tokens due to missing or incorrect signed extension handling. + +**Why**: Substrate's `CheckMetadataHash` signed extension requires the client to compute and include a hash of the chain's current metadata in every signed payload. If the hash is computed on a different version (or on the server) than the chain's runtime, the signature is invalid. + +**How to avoid**: +- Move metadata shortening and `metadataHash` calculation to the client using `@polkadot-api/merkleize-metadata`; do not delegate to a server. +- `CheckMetadataHash` compatibility must be validated per-network before shipping, including on testnets (Moonbase Alpha, Paseo). +- When bumping `@polkadot/api`, run a signing smoke test on all actively supported chains. + +See [#3300](https://github.com/Koniverse/SubWallet-Extension/issues/3300), [#3305](https://github.com/Koniverse/SubWallet-Extension/issues/3305), [#3669](https://github.com/Koniverse/SubWallet-Extension/issues/3669), [#3145](https://github.com/Koniverse/SubWallet-Extension/issues/3145). + +--- + +## 14. iOS / WKWebView storage is ephemeral — mobile wallet data needs native backup + +**What happened**: iOS 17.1 silently reset WKWebView indexed storage, causing wallet data loss for iOS mobile app users. The extension team was caught without a backup/restore mechanism. + +**Why**: WKWebView's IndexedDB is stored in a process-specific cache that iOS can purge at any time without warning, especially after system updates. + +**How to avoid**: +- Mobile web-view storage must be treated as ephemeral; implement a periodic export/backup to native device storage. +- Add a `backup/restore IndexedDB` API in the web runner and expose it to the native shell so data can be preserved across OS updates. +- Send push notifications to users when a backup is available and when a data reset is detected. + +See [#2230](https://github.com/Koniverse/SubWallet-Extension/issues/2230), [#2272](https://github.com/Koniverse/SubWallet-Extension/issues/2272), [#2276](https://github.com/Koniverse/SubWallet-Extension/issues/2276). + +--- + +## 15. NFT startup blocking and IPFS gateway reliability + +**What happened**: Extension took a very long time to open (or failed entirely) if the user had previously closed it on the NFT tab while NFT data was loading — background IPFS/API requests blocked startup. An IPFS gateway (`ipfs-gateway.cloud`) triggered browser deceptive-site warnings. IPFS URI parsing failed for `ipfs://`, `/ipfs/`, and bare CID forms. NFT CORS issues arose when the same gateway was used across extension, WebApp, and mobile environments. + +**Why**: NFT data loading was synchronous at startup and tied to IPFS gateways that can be slow, flagged by browsers, or subject to CORS restrictions. + +**How to avoid**: +- Lazy-load NFT data; never block extension startup on in-flight NFT requests from a previous session. +- IPFS gateway selection must be environment-aware (extension vs. WebApp vs. mobile) to avoid CORS. +- NFT IPFS URI parsing must handle all formats: `ipfs://`, `/ipfs//`, and bare CID. +- Use a trusted, SubWallet-controlled IPFS gateway with a fallback list; avoid third-party gateways that may be flagged by browser security filters. +- Wrap NFT metadata parsing in try/catch; display a "metadata unavailable" fallback card rather than dropping the NFT. + +See [#97](https://github.com/Koniverse/SubWallet-Extension/issues/97), [#611](https://github.com/Koniverse/SubWallet-Extension/issues/611), [#779](https://github.com/Koniverse/SubWallet-Extension/issues/779), [#1602](https://github.com/Koniverse/SubWallet-Extension/issues/1602). + +--- + +## 16. Wallet reset must iterate every data service — nothing self-clears + +**What happened**: NFT data was not cleared when resetting the wallet because the NFT service was missing from the reset hook. Earned positions, chain connections, and token preferences also persisted across resets in various incidents. A bug caused `Erase All` not to fully reset wallet state to defaults, and auto-detected balance detection fired again after reset. + +**Why**: Each service independently manages its storage, and reset logic was implemented ad-hoc. Adding a new service without adding it to the reset sequence left orphaned data. + +**How to avoid**: +- Maintain an explicit registry of all data services; the reset routine must iterate the registry and call a `reset()` method on every service. +- Write an integration test that performs a full wallet reset and asserts that every storage key returns its default value. +- `Erase All` must be covered by a regression test on both Extension and WebApp. + +See [#2106](https://github.com/Koniverse/SubWallet-Extension/issues/2106), [#3035](https://github.com/Koniverse/SubWallet-Extension/issues/3035). + +--- + +## 17. Predefined token/network lists must honour user deletion flags + +**What happened**: Default EVM tokens re-added themselves after a user deleted them because the predefined token list was reapplied on every extension startup without checking the user's deletion flag. Currency settings were not persisted correctly across extension version upgrades, causing user preferences to reset to default on each update. + +**Why**: Startup initialisation code conflated "apply defaults for new install" with "apply defaults on every startup". User deletions stored in the preference layer were not consulted before re-injecting predefined tokens. + +**How to avoid**: +- Store user deletion flags persistently; skip re-injection of deleted predefined tokens on startup. +- Any setting that must survive upgrades needs explicit migration logic in the upgrade handler, not just a default-value initialiser. +- Test upgrade paths: install version N, change a preference, upgrade to version N+1, assert the preference is preserved. + +See [#490](https://github.com/Koniverse/SubWallet-Extension/issues/490), [#2977](https://github.com/Koniverse/SubWallet-Extension/issues/2977). + +--- + +## 18. Token slug normalisation — case mismatches cause silent activation failures + +**What happened**: USDT (Bifrost) and similar tokens failed auto-activation checks because the wallet stored slugs in uppercase while the indexer produced lowercase. Custom tokens were also excluded from the asset registry when applying an online chainlist patch because the merge step was missing. + +**Why**: Token slugs flowed from multiple sources (chain-list package, indexers, dApp injections) with inconsistent casing. String comparison without normalisation produced silent mismatches. + +**How to avoid**: +- Normalise token slugs to lowercase at ingestion time on all paths (chain-list, indexer, dApp API). +- When applying online chainlist patches, always merge custom user tokens into the resulting registry — do not overwrite. +- Token deletion must operate on local storage directly and not require a live chain connection. + +See [#1500](https://github.com/Koniverse/SubWallet-Extension/issues/1500), [#687](https://github.com/Koniverse/SubWallet-Extension/issues/687), [#3101](https://github.com/Koniverse/SubWallet-Extension/issues/3101). + +--- + +## 19. Swap provider SDKs must handle RPC reconnection and MV3 context loss + +**What happened**: Hydration SDK held a stale API promise after an RPC interruption, causing persistent swap quote errors even after the user changed providers. Swap fees displayed incorrectly on the Swap Confirmation screen when reopening after a background service restart (MV3 context loss). ChainFlip default slippage was 0%, meaning users could receive significantly less than expected without warning. + +**Why**: Swap provider SDKs are initialized once at startup and assume a stable RPC connection. MV3's service worker can be terminated between user interactions, wiping in-memory SDK state. + +**How to avoid**: +- Swap handler SDKs must support reconnection and API promise refresh on RPC failure; guard SDK init against unstable connections. +- Fee data must be refetched or re-validated when a confirmation screen is restored from a sleeping service worker. +- Default slippage for every provider must be explicitly set and reviewed during integration — never rely on the provider's library default. +- Price impact must be applied to the received-amount calculation, not only to the display warning. + +See [#3993](https://github.com/Koniverse/SubWallet-Extension/issues/3993), [#3630](https://github.com/Koniverse/SubWallet-Extension/issues/3630), [#3634](https://github.com/Koniverse/SubWallet-Extension/issues/3634), [#4241](https://github.com/Koniverse/SubWallet-Extension/issues/4241). + +--- + +## 20. Network reconnection must be debounced and centralised + +**What happened**: A reconnect loop occurred when the network connection was lost and restored: the reconnect handler fired per-network subscription, creating O(n) simultaneous reconnect attempts for n active networks. The background service worker ran indefinitely after a browser restart if the user never opened the popup because the sleep/shutdown mechanism had uncovered edge cases. + +**Why**: Each subscription independently triggered a reconnect rather than delegating to a single coordinator. The shutdown logic was not triggered on extension reload events. + +**How to avoid**: +- Network reconnection must be debounced and centralised in a single coordinator; individual subscription handlers must not independently trigger reconnect. +- The background service worker shutdown logic must fire on extension reload/unload events, not only on explicit user inactivity. +- Rate-limit external API polling; batch requests where the API supports it. + +See [#702](https://github.com/Koniverse/SubWallet-Extension/issues/702), [#802](https://github.com/Koniverse/SubWallet-Extension/issues/802), [#912](https://github.com/Koniverse/SubWallet-Extension/issues/912), [#4623](https://github.com/Koniverse/SubWallet-Extension/issues/4623). + +--- + +## 21. UI must not block on non-critical service initialisation at startup + +**What happened**: The WebApp was very slow to load for the first time because the `ROOT` component required all services (including network-dependent NFT and earning services) to resolve before rendering anything. This caused blank screens on slow connections and was reproduced in the extension on slow networks. + +**Why**: The root component waited synchronously for all async service data, conflating "critical for first paint" data with "nice to have" background data. + +**How to avoid**: +- Separate required-for-first-paint data (account list, master password status) from background service data (balances, NFTs, staking). +- Show progressive loading states; never block paint on network-dependent services. +- Non-critical service data should load and update after the initial paint, not before. + +See [#1902](https://github.com/Koniverse/SubWallet-Extension/issues/1902), [#2207](https://github.com/Koniverse/SubWallet-Extension/issues/2207). + +--- + +## 22. WebApp and extension share background logic but have distinct lifecycle assumptions + +**What happened**: A large batch of WebApp-specific bugs was discovered during initial QA because the WebApp had not been tested as a standalone product. Using the WebApp and extension simultaneously caused state conflicts: the WebApp cached state locally and became out of sync with the extension background. Extension-specific patterns (popup auto-close, window focus) caused regressions in the WebApp. + +**Why**: The WebApp shares the background service layer with the extension but has different navigation lifecycle, connection timing, and rendering environment. The shared codebase masked the WebApp's distinct requirements. + +**How to avoid**: +- Create and maintain a dedicated WebApp regression checklist before each release. +- WebApp must treat the extension background as the single source of truth; re-subscribe to all state on every connection, not cache state locally. +- Extension-specific lifecycle assumptions (popup close, expand view) must be gated behind environment detection before any shared code references them. + +See [#1680](https://github.com/Koniverse/SubWallet-Extension/issues/1680), [#1838](https://github.com/Koniverse/SubWallet-Extension/issues/1838). + +--- + +## 23. Build environment variables must be explicitly scoped in extension bundles + +**What happened**: The extension's `content.js` was embedded in the page with sensitive build variables exposed because the Webpack config did not strip them from page-context scripts. + +**Why**: Webpack's `DefinePlugin` or `EnvironmentPlugin` replaced env-var references in all bundles by default. Page scripts and content scripts run in a less-trusted context and should receive only the minimum necessary configuration. + +**How to avoid**: +- Explicitly enumerate which environment variables are allowed in `page.js` and `content.js` bundles. +- Production and dev environment parity for backend proxies must be validated; API health checks should cover production endpoints explicitly. +- Hardcoded API keys in source code must be moved to environment variables before shipping. + +See [#1823](https://github.com/Koniverse/SubWallet-Extension/issues/1823), [#4977](https://github.com/Koniverse/SubWallet-Extension/issues/4977), [#4929](https://github.com/Koniverse/SubWallet-Extension/issues/4929). + +--- + +## 24. Phishing detection must be stateless with respect to account presence and tested against high-traffic sites + +**What happened**: Phishing detection crashed when no account existed in the wallet because the detection function assumed at least one account was present. A separate incident saw the advanced phishing detection feature flag legitimate high-traffic sites (YouTube, X/Twitter) as phishing, alarming users. The feature was temporarily disabled entirely. + +**Why**: The detection function used account data as a filter input without guarding against the empty-account case. The heuristics were not tested against a broad whitelist of high-traffic legitimate sites before default-enabling. + +**How to avoid**: +- Phishing detection must be stateless with respect to account presence; guard against empty account lists before checking page URLs. +- Before default-enabling any phishing detection heuristic, test it against a curated whitelist of the top 500 high-traffic sites. +- Provide a clear user-visible bypass ("I trust this site") so users are not permanently blocked on false positives. + +See [#2372](https://github.com/Koniverse/SubWallet-Extension/issues/2372), [#4889](https://github.com/Koniverse/SubWallet-Extension/issues/4889), [#4891](https://github.com/Koniverse/SubWallet-Extension/issues/4891). + +--- + +## 25. Account migration routines must be tested under high account-count load + +**What happened**: Extension was unable to open when updating from v1.3.7 to v1.3.8 for wallets with more than 100 accounts because the migration logic blocked on large account sets. Navigation stack was not explicitly reset when the active account set became empty, leaving the user on a blank screen after removing the last account. + +**Why**: Migration routines were developed and tested with small account sets. The O(n) or synchronous blocking patterns that are acceptable for 5 accounts become unacceptable for 100+. + +**How to avoid**: +- Account migration routines must be benchmarked with ≥100 accounts before shipping. +- Always process migration in chunks with async yield points to avoid blocking the event loop. +- Navigation stack must be explicitly reset when the active account set becomes empty. +- After removing the last account, redirect to the onboarding/create-account screen. + +See [#3890](https://github.com/Koniverse/SubWallet-Extension/issues/3890), [#3054](https://github.com/Koniverse/SubWallet-Extension/issues/3054), [#3148](https://github.com/Koniverse/SubWallet-Extension/issues/3148). + +--- + +## 26. Online content endpoints must always have a static fallback bundled with the extension + +**What happened**: The fiat on-ramp token list became empty after the online content endpoint became unavailable (missing fallback). Price history API returned empty data on production due to a misconfigured proxy while dev worked correctly. Online chainlist patch load order caused earning positions to appear for networks not yet enabled, and new-install users saw incorrect state until manual reload. + +**Why**: The extension relies on online resources (chain configs, token lists, price data) that can become unavailable due to network issues, proxy misconfigurations, or infrastructure outages. Without a bundled fallback, users see empty or broken UI. + +**How to avoid**: +- Every online resource endpoint must have a static fallback bundled with the extension and used when the online fetch fails. +- Online patch application must fire a "ready" event after the chain API is initialised before earning/position views subscribe to it. +- Validate production and dev API endpoint parity explicitly in CI. + +See [#2322](https://github.com/Koniverse/SubWallet-Extension/issues/2322), [#4977](https://github.com/Koniverse/SubWallet-Extension/issues/4977), [#3132](https://github.com/Koniverse/SubWallet-Extension/issues/3132). + +--- + +## 27. XCM destination validation: always check existential deposit and sufficient-asset status + +**What happened**: Users lost tokens when XCM transferring MYTH to Polkadot Asset Hub for accounts without native DOT — the recipient was not validated for minimum DOT balance on the destination. XCM transfer from PAH to Polkadot left users with less than ED on the source chain because max-transferable did not account for source-chain ED reservation. + +**Why**: XCM transfers silently succeed on-chain even if the destination account does not meet ED requirements — the funds are simply trapped. The extension's max-transfer logic computed the full transferable amount without reserving source-chain ED. + +**How to avoid**: +- Always validate destination chain ED / sufficient-asset status before allowing XCM transfer submission. +- Max XCM transfer formula: `transferable − source_ED − estimated_fee`. +- Show a warning when the recipient has zero native balance on the destination chain. +- Use ParaSpell's dry-run API to pre-validate XCM routing before submission where the node supports it. + +See [#3895](https://github.com/Koniverse/SubWallet-Extension/issues/3895), [#3617](https://github.com/Koniverse/SubWallet-Extension/issues/3617). + +--- + +## 28. WalletConnect and dApp signing flows must be tested after every MV3 or SDK update + +**What happened**: WalletConnect connection popup stopped appearing after MV3 migration. Some required EVM JSON-RPC methods were missing from SubWallet's WalletConnect implementation, causing dApps to fail detection. Aleph Zero EVM dApp connection succeeded but transactions failed because SubWallet's all-network inject behavior conflicted with Remix's network-change model. + +**Why**: WalletConnect integrates deeply with the extension lifecycle. MV3 changes the service-worker event model; each WalletConnect SDK version bump can change required method sets or namespace formats. + +**How to avoid**: +- After every WalletConnect SDK version bump or MV3 lifecycle change, run a full dApp signing regression covering: session creation, message signing, transaction signing, disconnect. +- Audit the WalletConnect EVM session against the current WC EVM spec for required methods. +- dApps that disallow network changes (Remix-style) need a manual network selection option; automatic all-network injection can break EIP-1193 flows. + +See [#2407](https://github.com/Koniverse/SubWallet-Extension/issues/2407), [#2860](https://github.com/Koniverse/SubWallet-Extension/issues/2860), [#4330](https://github.com/Koniverse/SubWallet-Extension/issues/4330). + +--- + +## 29. Seed phrase and private key inputs must use `` not `