# Changelog

All notable changes to Lycato. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).

## [Unreleased]

Audit remediation (2026-07-27/28), then the GTM FIX cycle (2026-07) which
closes the three build items recorded in the repository-only
`gtm/GAP_REMEDIATION.md`. Continuous-improvement loop notes remain below.

### Added — full story and UX audit (2026-08-09)

- **Exact 50-story verification ledger** at
  `docs/testing/USER_STORY_AUDIT_2026-08-09.md`, with a regression test that
  requires every canonical Lycato story exactly once, requires its status to
  match, and keeps the suite snapshot totals derived from the rows.
- **Generated-site link integrity gate** for local files, clean URLs, rewrite
  routes, and fragments. Public docs can no longer ship broken internal links.
- **Installed-artifact acceptance breadth** in the clean-machine gate: morning,
  search, JSON logs, migration dry-run, backup/restore, and a real loopback
  `lycato serve` health/static-page probe now run from the packed CLI.

### Fixed — full story and UX audit (2026-08-09)

- **Verified-email second-tab handoff** now force-refreshes the Firebase ID
  token before protected Firestore access. A newly verified founder no longer
  sees a false setup pause caused by the pre-verification token cache.
- **Public lead-deletion requests** no longer crash after the write: their live
  result region now belongs to the submitting form, with a shipped-shell
  regression contract.
- **Hosted native form validation** now leaves a persistent plain-language
  message, `aria-invalid`, and `aria-describedby` relationship, then clears the
  relationship as soon as the field becomes valid.
- **Public documentation canon** now matches the current master brand and no
  longer links to repository-only ADR/GTM files from the generated site.

### Added — audit remediation

- **`lycato operator approvals` / `approve <digest>` / `deny <digest>`** — the
  create→approve→consume action-digest flow now has call sites. A scheduled
  agent that writes company truth with no branch and no diff (today:
  `daily-digest`) records a pending digest and does not run until one is
  approved. An unapproved slot is a failed run, so it stays eligible for retry.
- **Missed-minute catch-up in `lycato serve`** — a late tick replays every
  elapsed minute instead of dropping the slot.

### Changed — audit remediation

- **`pnpm docs:build` clears its output first**, and the CI docs job runs
  `scripts/ci/check-docs-public-tree.mjs`, which fails the build if the tree
  about to be deployed holds any path outside the public allowlist. The
  currently-deployed site predates this and still serves the internal
  directories; removing them needs a deploy.
- **`lycato init` creates `main`**, and `lycato review approve`'s provenance gate
  resolves its base via `origin/HEAD` and **blocks** when no base resolves,
  rather than passing the change through.
- **The notifier marks only channels that actually delivered.** A failed
  webhook no longer banks the events into the once-per-day ledger, and the run
  reports failure.
- **Operator access moved into `firestore.rules`.** The browser-side
  `isOperatorAdmin` / `elevateOperatorAdmin` / `assertTenantIsolation` helpers
  are deleted.
- **Nine unimported hosted modules moved to `packages/hosted/unshipped/`** —
  the plan-gate and billing subsystem, the provisioning saga, the company
  switcher, the mailbox mediator, the margin report. They are not shipped and
  not enforcing anything; see `packages/hosted/unshipped/README.md`.
  *(Superseded 2026-08-23: the `apps/` move, commit `65ee343`, relocated
  `packages/hosted` → `apps/hosted` and **deleted** the `unshipped/` tree and its
  README rather than carrying them across. Neither path resolves in the current
  checkout; the nine modules exist only in git history.)*
- **Production CSP no longer whitelists `127.0.0.1`**, and
  `/.well-known/{ucp,security.txt}` are excluded from the SPA rewrite.

### Added

- **Diff risk scoring** — `scoreRisk` now scores deletions vs appends, annotates
  ledger net deltas and decision amendments, treats `decisions/` as high-risk,
  and sets `requiresExplicitRead` for high-risk diffs. Cockpit Review sorts by
  risk and withholds Approve until a high-risk diff is opened; CLI `lycato review`
  matches.
- **`@lycato/agent-decision-draft`** — `lycato operator decision-draft "…?"` frames
  options/trade-offs into `decisions/{id}.md` as `proposed`. Frames; never decides.
- **`@lycato/agent-notifier`** + `@lycato/core` notify — opt-in high-signal interrupts
  (obligation due, runway low, stale decision, stale review) via
  `.founder/notify.yml`. Delivers to `.founder/notifications/` and optional
  webhook. Wired into `lycato sync` / `lycato serve` (no-op when disabled).
- **`@lycato/agent-kpi-snapshot`** — `lycato operator kpi-snapshot` drafts
  `kpis/{yyyy-mm-dd}.md` from the money ledger + run-ledger sync health.
- **`lycato doctor --json`** plus a Wiring report (configured adapters + telemetry
  posture).
- **Telemetry instrumentation** — `recordRun` / `recordUsage` wired into both run
  paths + an oclif `postrun` hook; `lycato init` opt-in consent prompt. Still
  default-off, local-only.
- **Static doc-site** — `pnpm docs:build` renders the public subset of `docs/`
  to a self-contained HTML site (one dep, `marked`), plus a brand-true marketing
  home at `docs/site/home.html` (site root). The subset is the `PUBLIC_DOC_DIRS`
  allowlist in `scripts/build-docs.mjs`; internal directories are not emitted.
- **Cockpit** — `manualChunks` code-split + lazy money chart (~176→86KB gz first
  paint), global `:focus-visible` ring + CmdK dialog focus-trap, an
  `ErrorBoundary`, a ⌘K search affordance, and a first-run onboarding card.

### Changed

- **Dependencies upgraded to latest across the workspace**, including major
  bumps: Tailwind **4** (CSS-first via `@tailwindcss/vite`; `postcss.config.js` +
  `tailwind.config.js` removed, fonts moved to an `@theme` block), Vite **8**
  (Rolldown), Vitest **4** (+ jsdom 26), TypeScript **6** (`types: ["node"]` in
  `tsconfig.base.json`), Biome **2** (migrated `biome.json`), Zod **4**,
  octokit **5**, recharts **3**, lucide-react **1**, `@inquirer/prompts` **8**,
  `@fastify/cors` **11**. `engines.node` raised to `>=22.13`. Code migrations were
  small: Zod 4 `z.record(z.string(), …)` + `ZodType<T>`, two lucide icon renames
  (`CheckCircle2`→`CircleCheckBig`, `XCircle`→`CircleX`), and a Vitest-4 class
  mock for octokit. All 316 tests + build + typecheck + Biome stay green.
- **`cockpit-devtools-smoke`** made Windows-portable (platform-aware Chrome path
  via `CHROME_PATH`, `pnpm` spawned through a shell, `localhost` web host, and a
  retrying Chrome-profile cleanup).
- LF line endings enforced via `.gitattributes`.
- `plain` / `visible` / `stripe-atlas` adapters now run under `lycato sync`.
- Test suite 216 → 300+: a jsdom cockpit render harness (every page),
  per-adapter pull tests, and core `money` / `paths` coverage.

### Fixed

- Windows-only `pnpm build` / `pnpm typecheck` silent no-op (POSIX-quoted
  workspace filter ran via `cmd.exe`).
- Date-dependent run-ledger timestamp bug in `runSync` (used wall clock instead
  of the injected `now`).

## [0.1.1] — 2026-05-21

### Added

- **Provider-agnostic LLM client.** `@lycato/core/llm` exports
  `LlmClient`, `createAnthropicClient`, `createOpenAIClient`,
  `createGeminiClient`, `createLlmClient`, `resolveLlmClient`. Direct
  `fetch`-based — no SDK dep. Default models pinned per provider
  (`claude-opus-4-7`, `gpt-4o-mini`, `gemini-2.5-flash`).
- **`--provider` flag** on `lycato morning --auto-sync`, `lycato operator
  investor-update`, `lycato operator content-queue`, `lycato close` to override
  the auto-picked provider when multiple keys are set.
- **`requiredAny`** semantics on sync steps — daily-digest now skips
  only when zero of `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` /
  `GEMINI_API_KEY` are set.

### Changed

- All four agents (`@lycato/agent-daily-digest`,
  `@lycato/agent-investor-update`, `@lycato/agent-content-queue`,
  `@lycato/agent-finance-close`) now accept `LlmClient` instead of
  `Pick<Anthropic, "messages">`.

### Removed

- `@anthropic-ai/sdk` dep dropped from `@lycato/cli` and all four agent
  packages. Install footprint reduced.

- **Run ledger.** `@lycato/core` exports `appendRun`, `readRuns`,
  `withRunRecord`. Every adapter/agent run writes a JSONL line to
  `.founder/runs/YYYY-MM.jsonl` (`at`, `kind`, `name`, `ok`, `ms`,
  `error?`, `summary?`). Wired into `runSync`, `morning --auto-sync`,
  `agent investor-update`, `agent content-queue`, `close`.
- **`lycato logs`.** New CLI `lycato logs [--limit N] [--kind adapter|agent]
  [--name X] [--since TS] [--json]` tails the run ledger with status,
  duration, and summary/error.
- **Cockpit Work page rewritten.** `/api/runs` now reads the run ledger
  instead of `journal/daily/`. Surface shows kind, name, latency,
  summary, error per entry.

### Changed (breaking — local cockpit only)

- `RunEntry` API contract reshaped: `{ at, kind, name, ok, ms, error?,
  summary? }`. Old `{ agent, date, file, preview }` removed.

### Added (Phase 3 — schema migrations)

- **Migration engine.** `@lycato/core/migrations` exports `Migration`
  type, `readRepoVersion`, `writeRepoVersion`, `runMigrations`,
  `CURRENT_REPO_VERSION`, `REGISTERED_MIGRATIONS`. Forward-only,
  idempotent, with `--dry-run` and `--to <ver>` support.
- **`lycato migrate`.** New CLI command. Walks `.co-md-version` →
  `CURRENT_REPO_VERSION` applying each pending migration in order.
- **Baseline migrations.** `0001_baseline_v0_1_0` backfills missing
  `.co-md-version` for v0.0.x repos; `0002_runs_gitignore` appends
  `.founder/runs/` to `.gitignore` (Phase 2 lock-in).
- **`lycato doctor`** now reports the repo schema version and recommends
  `lycato migrate` when behind.
- **`lycato init`** writes the current schema version from
  `CURRENT_REPO_VERSION` instead of hardcoding `0.1.0`.
- **Kernel template `.gitignore`** ships with `.founder/runs/` for
  fresh repos so the migration is a no-op there.

Repository-only rationale: ADR-0015, ADR-0016, and ADR-0017 in
`decisions/ADR_LOG.md`.

### Added (Phase 4 — cockpit cmd-K + global search)

- **`/api/search?q=`** Fastify route reads the existing
  `Lycato/cache/index.json`, classifies hits by path-prefix into
  `SearchHitKind`, returns `{ query, hits, indexBuiltAt, stale }`.
  Lazy-builds the index on first call.
- **`POST /api/reindex`** rebuilds the on-disk index.
- **Cmd-K modal** (`components/CmdK.tsx`) — debounced search,
  arrow-key nav, enter-to-open, hit-kind chips, footer reindex prompt
  when index is stale (>24h).
- **`Shell.tsx`** listens for `cmd/ctrl+K` to toggle cmd-K. Hit
  activation routes to the most-relevant page.

Repository-only rationale: ADR-0018 in `decisions/ADR_LOG.md`.

### Added (Phase 5 — WebSocket push)

- **`/api/ws` Fastify route** (`@fastify/websocket`) streams `{ kind,
  path, at }` events to all connected cockpits.
- **chokidar watcher** monitors `WHEN.md`, `money/ledger/*.md`,
  `journal/daily/*.md`, `.founder/reviews/*.yml`,
  `.founder/runs/*.jsonl`. Emits on add/change/unlink.
- **`useLive(kinds, handler)`** React hook with singleton WS +
  exponential reconnect backoff.
- **`useApi` extended** with a `refresh` function so any page can be
  wired to re-fetch on a live event.
- **Work page** re-fetches on `runs` events; **Review page**
  re-fetches on `review` events.
- **Feature flag** `cockpit.live_updates: false` in
  `.founder/config.yml` disables both watcher + WS route.

Repository-only rationale: ADR-0019 in `decisions/ADR_LOG.md`.

### Added (Phase 6 — cockpit inline diff viewer)

- **`/api/review/:branch/diff`** returns `{ branch, base, files: [{
  file, status, before, after, isBinary, truncated }] }`. Caps each
  file at 1MB; sniffs binary via NUL byte.
- **`DiffViewer.tsx`** renders an in-browser LCS-based line diff per
  file with status chips (added/removed/modified) and
  truncated/binary indicators. No new client deps.
- **Review page** branches now expand in place to show the diff on
  first click (lazy fetch).

Repository-only rationale: ADR-0020 in `decisions/ADR_LOG.md`.

### Added (Phase 7 — cockpit inline edit + commit)

- **`POST /api/file`** accepts `{ path, branch, content, approver?,
  message? }`. Writes the file on the target proposal branch and
  commits via `simple-git`. Restores the founder's prior `HEAD` in a
  `finally` block.
- **`policy.editable`** flag on `PolicyRule`. Default-editable paths:
  `journal/daily/`, `content/queue/`, `investors/`. Helpers
  `isPathEditable(policy, path)` and `riskForPath(policy, path)`
  exported from `@lycato/core`.
- **High-risk gate**: writes to high-risk paths (e.g. `investors/`)
  require a non-empty `approver` field; surfaces as the commit
  author.
- **DiffViewer Edit mode**: per-file Edit button swaps the after-pane
  to a textarea + approver input + Save & commit. Diff invalidates
  on success so the next render shows the new commit.

Repository-only rationale: ADR-0021 in `decisions/ADR_LOG.md`.

### Added (Phase 8 — SDK + module system)

- **`@lycato/sdk@1.0.0`** public package exposing `Adapter`, `Agent`,
  `Module` interfaces + `defineAdapter`, `defineAgent`,
  `defineModule` identity helpers + `LlmClient` re-export from core.
  Surface pinned at 1.0.0.
- **`@lycato/core/modules`** registry: `loadModules`, `addModule`,
  `removeModule`, `listModules`, `BUILT_IN_MODULES`, `findModule`.
  Reads/writes `.founder/modules.yml`.
- **Built-in modules**: `saas`, `services`, `d2c`. Each declares
  directory layout + recommended adapters/agents.
- **`lycato module list / add / remove`** oclif topic commands.

Repository-only rationale: ADR-0022 in `decisions/ADR_LOG.md`.

### Added (Phase 9 — self-hosted slice)

- **`lycato serve`** single-process supervisor — spawns cockpit + runs
  `runSync` on `--interval` (default 900s). Flags `--skip-cockpit`,
  `--skip-sync`. Replaces the need for an external cron in
  single-tenant installs.
- **`deploy/docker-compose.yml`** + **`deploy/Caddyfile`** reference
  stack with auto-TLS.
- **`docs/deployment/SELF_HOSTING.md`** runbook (install, upgrade,
  backup, hardening). Calls out KMS / Stripe / passkey / multi-tenant
  as deferred to hosted product launch.

Repository-only rationale: ADR-0023 in `decisions/ADR_LOG.md`.

### Added (Phase 10 — capture endpoint + PWA shell)

- **`POST /api/inbox`** accepts `{ title, body?, source? }` and
  writes `inbox/{yyyy-mm-dd}-{nanoid}.md` with full frontmatter.
  Title required, body capped 64KB, source regex-validated.
- **PWA manifest** at `public/manifest.webmanifest` + service worker
  at `public/sw.js` (network-first `/api/*`, cache-first
  `/assets/*`, HTML shell fallback). Registered from `index.html`.
- **`api.capture(...)`** frontend helper.

Repository-only rationale: ADR-0024 in `decisions/ADR_LOG.md`.

### Deferred to v0.4.0+ (hosted product)

- KMS-backed encrypted secret store.
- Stripe checkout, per-tenant billing, passkey/SSO auth.
- Multi-tenant orchestration.
- Inbound email capture (Postmark webhook → inbox/).
- Voice capture via Whisper transcription.
- Managed daily backups to founder-owned S3.

### Added (telemetry — local-side opt-in CLI)

- **`@lycato/core/telemetry`** helpers: `readTelemetryConfig`,
  `writeTelemetryConfig`, `getInstallId` (UUIDv4 via `node:crypto`),
  `rejectReason` (deny-list against provider-key shapes + path
  separators + length > 32), `recordEvent` (appends to
  `.founder/telemetry/YYYY-MM.jsonl` only when enabled; rejected
  events go to `rejected.jsonl`), `previewPayload` (rolling-window
  aggregate in upstream shape), `deleteTelemetryLocal`.
- **`lycato telemetry status / on / off / preview / delete`** oclif
  topic commands.
- Default **off**. No upstream endpoint wired yet; `delete` is
  local-only. Per `docs/security/TELEMETRY_OPT_IN.md`, call-site
  instrumentation + endpoint follow once justified.

## [0.1.0] — 2026-05-19

First publishable alpha. Built end-to-end agent-drafts → founder-approves
loop on a markdown+git substrate.

### Added

- **Memory primitive.** `memory/{people,companies,projects,topics}/` —
  file-per-entity addressable via `lycato mem <key>`, `lycato mem ls`,
  `lycato mem find`, `lycato mem write`. New cockpit `Memory` page with tree +
  viewer. Core exports: `readEntity`, `writeEntity`, `listEntities`,
  `findEntities`, `findOversizedEntities`. Doctor warns on entities
  >50KB.
- **Provenance & evidence-rail.** Convention: agent-authored lines
  carry `<!-- src: <path> -->`. Core exports `withProvenance`,
  `extractProvenance`, `checkProvenance`. `lycato audit --provenance` flags
  missing markers in `journal/`, `investors/`. Cockpit `/api/file` with
  path-traversal guard.
- **Investor-update agent.** `@lycato/agent-investor-update` —
  deterministic financial summary + Claude-drafted Overview /
  Performance / Economics / Highlights / Lowlights / Asks. CLI:
  `lycato operator investor-update`.
- **Stripe adapter + reconcile.** `@lycato/adapter-stripe` pulls balance
  transactions. `@lycato/core/reconcile` cross-matches Stripe charges
  with Mercury settlements. `lycato reconcile` writes
  `money/reconcile-YYYY-MM.md`.
- **Content-queue agent.** `@lycato/agent-content-queue` drafts 3-5
  LinkedIn / X / blog candidates into `content/queue/`.
- **Importers.** `lycato init --demo` seeds a realistic company (5 people,
  3 decisions, 60d ledger, 4 inbox items, 2 memory entries).
  `lycato import notion <dir>`, `lycato import gmail-mbox <file>`,
  `lycato import linear <json-export>`.
- **Risk-aware approvals.** `.founder/policy.yml` (with sensible
  defaults). Core: `loadPolicy`, `scoreRisk`. `lycato review` lists
  proposal branches with risk + reasons + auto-merge eligibility.
  Cockpit `/api/review` returns risk per branch and approval status.
  Founder one-click approve via Cockpit button or
  `lycato review approve <branch>` — writes
  `.founder/reviews/<branch>.yml`. Merge stays manual.
- **BYO API key.** `.founder/keys.env` (gitignored, chmod 600). CLI:
  `lycato keys`, `lycato keys set NAME VALUE`. `loadKeysIntoEnv` wired into
  `lycato sync`, `lycato doctor`, agent runners. README "Your tokens. Your
  bill." positioning.
- **Derived index.** `Lycato/cache/index.json` rebuilt by `lycato reindex`.
  Core exports `buildIndex`, `readIndex`, `searchIndex`.
- **3 new adapters: Plain, Visible, Stripe Atlas.** Each follows the
  `client / transform / pull / index` layout.
- **Cockpit shell + routing.** Hash-based router, sidebar nav, six
  pages (Today / Work / Memory / Company / Review / Settings). All
  pages use `useApi` hook for typed data loading. Evidence rail on
  every panel.
- **CLI gaps filled.** `lycato inbox`, `lycato inbox view`, `lycato inbox triage`,
  `lycato sync`, `lycato audit`, `lycato reconcile`, `lycato reindex`, `lycato mem*`,
  `lycato keys*`, `lycato operator investor-update`, `lycato operator content-queue`,
  `lycato import notion`, `lycato import gmail-mbox`, `lycato import linear`,
  `lycato review`, `lycato review approve`.
- **CI matrix.** `ubuntu / macos / windows` × Node 22.
- **Docs site stub.** `docs/site/index.md` with command table +
  architecture + trust boundaries.
- **Demo script.** `docs/demo/script.md` (5 min on-camera path).

### Changed

- All 15 workspace packages bumped from `0.0.0` to `0.1.0`. `lycato init`
  writes `.co-md-version: 0.1.0`.
- Cockpit API extracted from single `server.ts` into per-route files
  under `api/`. Typed response shapes in `contracts.ts`. Structured
  error envelope `{ error: { code, message, path? } }` with `ApiError`.
- CORS locked to `http://localhost:5173` by default; override via
  `LYCATO_CORS_ORIGIN`.
- `lycato audit` walks `people/`, `decisions/`, `journal/daily/` for
  schema validation; new `--provenance` flag adds journal/investor
  marker checks.
- Numeric formatting in money commands forced to `en-US` so output is
  locale-independent.

### Fixed

- Pre-existing Windows-path fragility in `core/test/search.test.ts`
  (normalise `path.sep` → `/`).
- Pre-existing locale-dependent number formatting in
  `cap-table.ts`, `money/runway.ts`, `money/summary.ts`.

### Security

- `.founder/keys.env` is gitignored and written with `0o600`.
- Cockpit `/api/file` rejects path traversal and absolute paths.
- Approval records are local files; the cockpit never merges branches.

### Notes

- 154/154 tests pass (46 files) on the local matrix. CI matrix
  configured for ubuntu/macos/windows; first run pending push.
- `npm publish` not run by the agent. Manual: see Phase 10 deliverable
  in `docs/superpowers/plans/2026-05-19-co-md-research-driven-10-phase.md`.

## [0.0.4] — 2026-05-10

Web cockpit v1: React 19 + Recharts dashboard. Local Fastify API.
`lycato cockpit` launches the dashboard.

## [0.0.3] — 2026-05-08

`lycato people`, `lycato decide`, `lycato equity`, `lycato money` subcommands.

## [0.0.2] — 2026-05-08

`lycato doctor`, `lycato search`, `lycato obligations`, `lycato close`; GitHub adapter
and finance-close agent.

## [0.0.1] — 2026-05-05

Backbone: kernel template + `lycato init` + Mercury adapter + daily-digest
agent.
