# 2026-07-17 — Codebase review, lint audit, and documentation maintenance

Status: **Report delivered** — doc/tooling fixes applied in this commit; code
refactors listed below are actionable recommendations pending approval.

Scope: full review of Go (`internal/`, `cmd/`), Svelte SPA (`web/`), and all
documentation (`README`, `AGENTS.md`, `.agents/**`, `docs/**`, `plans/**`,
`seeds/**`). Research-only review followed by targeted doc-maintainability
fixes. No production code was refactored in this pass.

Method: three parallel research passes (Go, web, docs) plus `go vet`, `go
build`, `go test -race`, and `npm run build`. `go vet` is clean; all tests
pass; the SPA builds with Svelte 5 warnings (listed in §B.4).

## A. Headline findings

| # | Area | Finding | Severity |
| - | ---- | ------- | -------- |
| A1 | Go | `internal/httpapi/phase3.go` is a 2627-line god file holding 12+ unrelated resource domains, misnamed after a project phase | High |
| A2 | Go | `internal/mcp/server.go:67` `newServer` is a 708-line function registering 33 tools inline; no registry pattern | High |
| A3 | Go | 17 sqlc queries are defined but never called; ~50% of DB access bypasses sqlc with raw inline SQL in `httpapi/` | High |
| A4 | Go | Test coverage violates the documented gates: `learning` (0%, gate 80%), `actuator`, `scheduler`, `domain`, `notifier`, `knowledge` all 0% | High |
| A5 | Web | Entire tool-renderer registry is dead — 21 files (~1.5k lines): `tool-renderers.ts`, `renderers/index.ts`, 10 `.ts` + 10 `.svelte` registrars; `getToolRenderer` is never called | High |
| A6 | Web | No `lint`/`check`/`test` scripts in `package.json`; zero test files; `any` is pervasive in the SSE/event payload plumbing | High |
| A7 | Docs | `.agents/domains/knowledge/schema.md` and `.agents/shared/llm-wiki.md` describe the deleted Python substrate (`bin/homelab`, `oikos/cards/`, `oikos/ledger.py`, root `inventory.yaml`) — they contradict the DB-native model in AGENTS.md / ADR 0003 | High |
| A8 | Docs | Brittle hardcoded counts in 5 docs: "33 tools", "15 tools", "36 documents", "20 migrations", "001–011" — rot on every seed regen | Medium |
| A9 | Build | Desktop version hardcoded `0.1.0` in `cmd/desktop/main.go:39` and `Makefile:70` while repo is at `0.7.6` — breaks the auto-update comparison | Medium |
| A10 | Docs | 4 broken markdown links + `plans/index.md` out of sync with filesystem (4 done plans not moved, 4 entries missing) | Low |

## B. Go codebase

`go vet ./...` clean. `go build` clean. `go test -race` passes for all packages
that have tests. 387 `.go` files, ~33k LOC.

### B.1 Naming & conventions — mostly idiomatic

- All packages lowercase single words; no casing/abbreviation inconsistency.
- `internal/httpapi/phase3.go` — **temporal naming** (named after a project
  phase, not a domain). Contents span checks, executions, approvals, patterns,
  skills, policy, metrics, trends, agent-activity, relationships, entity-types,
  autonomy, risk-classes. Should be split into ~12 resource files.
- `internal/httpapi/stubs.go` — 5-line file, comment-only, no declarations.
  Orphan. **Delete.**
- `cmd/desktop/main.go` uses stdlib `log` while the rest of the codebase
  standardizes on `slog` via `internal/observability/logging.go:11`.

### B.2 Dead code

No TODO/FIXME/XXX/HACK/DEPRECATED comments anywhere. No commented-out blocks.
No panics in non-test code. No global mutable state.

Dead exported symbols:
- `internal/notifier/notifier.go:286` — `VerifyApprovalToken` has **zero call
  sites**. Truly dead. **Delete.**
- `internal/checkdefaults/defaults.go:22,60,125,133` — `ResolveHost`,
  `ForEntityType`, `ShortSlug`, `DefaultInterval` are exported but only called
  within their own package. **Unexport.**

Dead file:
- `internal/httpapi/stubs.go` — comment-only orphan. **Delete.**

### B.3 Dead sqlc queries (17)

Defined in `internal/db/queries/*.sql`, generated into `internal/db/sqlcgen/`,
never called anywhere in the codebase:

| Query | File:line |
| ----- | --------- |
| `ListEntityRelations` | `internal/db/queries/relationships.sql:1` |
| `ListGraphEdges` | `internal/db/queries/relationships.sql:13` |
| `UpsertCurrentRelationship` | `internal/db/queries/relationships.sql:25` |
| `EndCurrentRelationship` | `internal/db/queries/relationships.sql:31` |
| `GetEntityBySlug` | `internal/db/queries/entities.sql:7` |
| `ListEntitiesCapped` | `internal/db/queries/entities.sql:30` |
| `GetEntityStatus` | `internal/db/queries/operations.sql:268` |
| `ListEntityStatus` | `internal/db/queries/operations.sql:17` |
| `UpdateSignalState` | `internal/db/queries/operations.sql:92` |
| `InsertApproval` | `internal/db/queries/operations.sql:212` |
| `InsertClassification` | `internal/db/queries/operations.sql:109` |
| `InsertFeedback` | `internal/db/queries/operations.sql:156` |
| `InsertSkill` | `internal/db/queries/operations.sql:204` |
| `ListEntityTypes` | `internal/db/queries/ontology.sql:1` |
| `ListRelationshipTypes` | `internal/db/queries/ontology.sql:4` |
| `ListLifecycleDefs` | `internal/db/queries/ontology.sql:7` |
| `WithTx` | `internal/db/sqlcgen/db.go` |

**Whole `relationships.sql` file is dead** — graph/relationship access is done
via raw inline SQL in `phase3.go` and `impl.go`. Either delete the queries or
migrate the inline SQL to use them.

### B.4 Pattern divergence — raw inline SQL vs sqlc

CONTRIBUTING §SQL says sqlc is the convention. ~50% of DB access bypasses it:

- `internal/httpapi/phase3.go:164,212,260,276,338,346,386,394,418,486,546,554,555,563,583` — raw `pool.Query/Exec` with inline SQL strings.
- `internal/httpapi/dashboard.go:21,39,59,96,117,123,144` — all raw inline SQL.
- `internal/httpapi/activity.go:81,148,185`, `learning_view.go:31,103` — raw inline SQL.
- `internal/httpapi/server.go:204`, `sse.go:112,142` — raw SQL (`LISTEN oikos_events`).

This is why the 17 queries above are dead — the equivalent logic is hand-written
inline. **Pick one DB-access pattern.** Recommendation: migrate inline SQL to
sqlc queries (deletes the dead queries' replacements and centralizes SQL).

### B.5 God files & functions (>800 lines / >100 lines)

Files (excluding generated):
- `internal/httpapi/phase3.go` — **2627 lines** (split by resource).
- `internal/mcp/server.go` — **1691 lines**.
- `internal/httpapi/impl.go` — **1639 lines**.
- `cmd/nomos/store.go` — **1472 lines**.
- `cmd/nomos/main.go` — 914 lines.
- `cmd/nomos/agent.go` — 861 lines.
- `internal/httpapi/server.go` — 842 lines.
- `cmd/desktop/main.go` — 784 lines.
- `internal/scheduler/scheduler.go` — 761 lines.

Functions (>100 lines, worst):
- `internal/mcp/server.go:67` `newServer` — **708 lines** (33 tools inline).
- `cmd/nomos/agent.go:187` `chatWith` — **405 lines**.
- `internal/httpapi/phase3.go:270` `executeApprovedAction` — **356 lines**, 5+
  levels of nested switch/if, 8 duplicated `UPDATE executions SET
  status=failed` error-bail blocks.
- `cmd/nomos/main.go:168` `handleChat` — 193 lines.
- `cmd/desktop/main.go:124` `startOIDCServer` — 182 lines.
- `internal/httpapi/dashboard.go:13` `GetDashboardSummary` — 160 lines.
- `internal/httpapi/impl.go:855` `CreateEntity` — 158 lines.
- `internal/httpapi/phase3.go:1366` `DecideApproval` — 156 lines.
- `internal/mcp/server.go:1264` `classifyAndGate` — 154 lines.

### B.6 `interface{}` vs `any`

Module is `go 1.26.3`; `any` is preferred. 409 `any` uses vs 11 `interface{}`.
The 11 are in `internal/mcp/server.go:1123,1131,1133,1581`,
`internal/httpapi/phase3.go:169,182`, and tests — all untyped-JSON unmarshaling.
**Replace with `any`** for consistency.

### B.7 Test coverage

CONTRIBUTING §Testing gates: policy + learning ≥ 80%, others ≥ 60%.

| Package | Tests | Status |
| ------- | ----- | ------ |
| `internal/learning` | 0 | ❌ violates 80% gate |
| `internal/actuator` | 0 | ❌ mutation code, untested |
| `internal/scheduler` | 0 | ❌ 761 lines of check logic |
| `internal/domain` | 0 | ❌ core types |
| `internal/notifier` | 0 | ❌ Matrix approval flow |
| `internal/knowledge` | 0 | ❌ seed ingestion |
| `internal/observability` | 0 | ❌ |
| `internal/checkdefaults` | 0 | ❌ |
| `internal/policy` | 1 | ⚠️ covers `classify.go` only |
| `internal/db`, `httpapi`, `mcp`, `secrets`, `config`, `ontology`, `safego` | ✅ | OK |
| `cmd/nomos` | 3 | ✅ |

### B.8 Generated code & migrations — clean

- `internal/httpapi/gen/api.gen.go` and `internal/db/sqlcgen/*.go` all carry
  `DO NOT EDIT` headers. No hand-edits detected.
- Migrations 001–020: sequential, no gaps, no down migrations, `embed.go`
  present. ✅

### B.9 OpenAPI vs implementation drift

- `api/openapi.yaml` defines 46 paths.
- `internal/httpapi/` implements ~40 strict handlers + ~8 manually-registered
  `chi.Get` routes (`serveRecentActivity`, `serveSessionDigest`,
  `serveKnowledgeContent`, `serveRecentKnowledge`, `serveLearningTimeline`,
  `serveLearningTrend`, `serveOIDC*`, `serveSSE`) that are **not in
  `openapi.yaml`**.
- OpenAPI is therefore not the source of truth for ~8 routes (violates
  CONTRIBUTING §OpenAPI codegen). **Add them to `openapi.yaml`** or document
  the carve-out.

## C. Web SPA (`web/`)

`npm run build` succeeds with Svelte 5 warnings. 722 KB JS bundle (222 KB
gzip), no code splitting.

### C.1 Tooling gaps — fixed in this pass + R8

- `package.json` had only `dev`/`build`/`preview`. **Added** `check`
  (`svelte-check`), `typecheck` (`tsc --noEmit`), and `lint` scripts, plus
  `svelte-check` + `typescript` devDeps. Run `npm install` to pick them up.
- **R8 added:** `eslint` (flat config) + `eslint-plugin-svelte` +
  `typescript-eslint` + `globals`; `prettier` + `prettier-plugin-svelte`;
  `vitest` (jsdom env) with a sample test (`src/lib/utils.test.ts`, 6 tests).
  New scripts: `lint`, `lint:fix`, `format`, `format:check`, `test`,
  `test:watch`. Vitest config wired into `vite.config.ts` via
  `/// <reference types="vitest/config" />`.
- **CI:** new `web` job in `.gitea/workflows/ci.yml` runs `npm ci`,
  `npm run check` (advisory), `npm run lint` (advisory),
  `npm run format:check` (advisory), `npm run test` (gate),
  `npm run build` (gate). Advisory steps use `continue-on-error: true`
  until the lint/check baseline is clean — matching the existing
  `golangci-lint` advisory pattern.
- **Known baseline:** svelte-check reports 154 pre-existing errors (133
  files, "No Svelte configuration found in vite config" cascade — not
  caused by R8); eslint reports 126 errors + 12 warnings (unused vars,
  `@html` XSS, unused CSS); prettier reports 175 unformatted files. These
  are real findings surfaced by the new toolchain — fixing them is a
  follow-up cleanup task.

### C.2 Dead code — the tool-renderer registry (21 files, ~1.5k lines)

`src/main.ts:25` lazy-imports `./lib/renderers`, which runs `renderers/index.ts`
calling 10 `init*()` functions that each `registerToolRenderer(...)`. But
**`getToolRenderer` is never called anywhere**. The whole subsystem is dead:

- `src/lib/tool-renderers.ts`
- `src/lib/renderers/index.ts`
- `src/lib/renderers/{blast-radius,change-log,entity-card,entity-table,execution-status,fleet-snapshot,health-summary,knowledge-results,lxc-list,metric-chart}.ts` (10)
- `src/lib/renderers/{BlastRadius,ChangeLog,EntityCard,EntityTable,ExecutionStatus,FleetSnapshot,HealthSummary,KnowledgeResults,LXCList,MetricChart}.svelte` (10)

**Either wire it up or delete all 21 files.** Note: `HealthSummary.svelte:30`
emits a `state_referenced_locally` Svelte 5 warning — dead code generating
lint noise.

### C.3 Dead components, stores, deps

Dead Svelte components (never imported outside self/comments):
- `src/lib/components/ToolCallGroup.svelte`
- `src/lib/components/PlanProgress.svelte`
- `src/lib/components/GoalHeader.svelte` (only in a comment)
- `src/lib/components/InlineApproval.svelte` (only in a comment)
- `src/lib/components/SessionDigest.svelte` + its API fn `fetchSessionDigest`
  (`src/lib/api.ts:354,363`) — dead chain.

Dead store exports (written, never read):
- `src/lib/stores/context.ts:10` `pendingApprovals`
- `src/lib/stores/events.ts:18` `connectionState`

Dead npm deps:
- `mode-watcher` (`package.json:16`) — 0 imports; superseded by
  `src/lib/stores/theme.svelte.ts`.
- `@internationalized/date` (`package.json:12`) — 0 imports.

Naming collision: `src/lib/components/EntityTable.svelte` (live) vs
`src/lib/renderers/EntityTable.svelte` (dead) — same filename, easy to grab
the wrong one.

### C.4 Type safety

No `@ts-ignore`/`@ts-expect-error`. But `any` is pervasive in the SSE/event
plumbing — defining an `OikosEvent` discriminated union would eliminate ~15
`any` sites:

- `src/lib/api.ts:32,107` — `ChatEvent.data: any`, interface `data: any`
- `src/lib/stores/chat.ts:58-59` — `ToolCallResult.args?: any; result?: any`
- `src/lib/stores/activity.ts:73-74` — `(t.args as any)?.seq`
- `src/lib/stores/workspace.ts:68,83,104,152` — `data: any`, `as any`, `s: any`
- All 10 dead renderers use `(tool.result as any).data` + `as any[]`
- `src/pages/Config.svelte:57,74` — `(window as any).wails`, `catch (e: any)`
- `src/lib/utils.ts:45,47` — `T extends { child?: any }`
- `vite.config.ts:18-24` — `proxy: any`, `proxyReq: any`

Missing return types on exported functions: `src/lib/utils.ts:4` (`cn`),
`src/lib/config.ts:23,42,50`, `src/lib/tool-renderers.ts:11`,
`src/lib/stores/context.ts:18`, `src/lib/stores/events.ts:23,46`,
`src/lib/stores/chat.ts:98,185,422,448,469`, `src/lib/oidc.ts:270`.

### C.5 Svelte 5 idioms — mostly clean

- `export let`: 0. `$:` labels: 0. `on:click`: 0. `createEventDispatcher`: 0.
  `<slot>`: 0 real usage. ✅ App is cleanly on runes.
- Mix of `svelte/store` classic stores (`stores/{activity,chat,context,events,workspace}.ts`) and `.svelte.ts` runes modules (`theme`, `is-mobile`, sidebar context). Deliberate but could be unified.
- `src/lib/components/ActivityTimeline.svelte:103` — `<svelte:component>` is
  **deprecated in runes mode**; components are dynamic by default. Replace with
  direct `{@const Comp = icon}{<Comp .../>}` or inline.
- `src/lib/components/DetailSection.svelte:18` — `let open = $state(defaultOpen)`
  triggers `state_referenced_locally`; wrap in `$derived`/init via `$effect` if
  reactivity to `defaultOpen` is intended.

### C.6 Build/config

- `vite.config.ts:6-14` — reads `../VERSION` or `./VERSION`; **no fallback if
  both missing** — `readFileSync('VERSION')` throws and crashes `vite
  build`/`dev` silently. Add a fallback or a build-time check.
- `vite.config.ts:38-50` — `server.proxy` hardcodes `localhost:8090` (API) and
  `localhost:8092` (nomos). Not env-driven.
- `vite.config.ts:31-34` — `define: { __OIKOS_VERSION__: ... }` global is used
  in `src/lib/version.ts:1` but its declaration in `vite-env.d.ts` should be
  verified.
- Bundle warning: single 722 KB JS chunk. Add `build.rollupOptions.output.
  manualChunks` or route-level dynamic imports.

### C.7 Hardcoded values

- `src/lib/oidc.ts:117` — `http://127.0.0.1:18901/oidc/start` (desktop OIDC
  broker port). Magic number, no constant.
- No tokens/secrets in `src/`. Auth via `localStorage`/OIDC. ✅
- 4 `fetch()` calls, all via `apiBase(...)`. No hardcoded hosts in fetch. ✅

### C.8 Accessibility

Generally decent (aria-label, role="button", tabindex, keyboard handlers).
Gaps:
- `src/pages/Chat.svelte:182` — bare `×` dismiss button missing `type="button"`.
- `src/lib/components/{SessionGraph,EntityGraph}.svelte` — SVG `<g role="button">`
  nodes keyboard-activatable but no `aria-label` (node identity not announced).
- `src/pages/Chat.svelte:107` — scroll container has no `role="log"`/`aria-live`
  for streamed messages.

## D. Documentation

### D.1 Stale references — fixed in this pass

- `AGENTS.md:115-117` — **ghost of retired `request_execution`**. Second `run`
  bullet listed the retired enum actions and contradicted the retire notice
  above it. Deleted.
- `AGENTS.md:148` — referenced `knowledge/wiki/` (does not exist); corrected to
  `archive/knowledge/`.
- `README.md:116` — broken link to `plans/2026-07-12-wails-desktop-app.md`
  (moved to `plans/done/`). Fixed.
- `.agents/OIKOS.md:103` — broken link to
  `../plans/2026-07-06-consolidate-oikos-control-plane-onto-mac-mini.md`
  (in `plans/done/`). Fixed.
- `.agents/operations/commands.md:52` — broken link with extra `/archive/`
  segment. Fixed.
- `.agents/operations/commands.md:85` — broken link to wails plan. Fixed.
- `.agents/shared/page-templates.md:12` — listed `HERMES.md` (renamed to
  `NOMOS.md` per ADR 0012). Fixed.

### D.2 Brittle counts — fixed in this pass

Replaced hardcoded rot-prone numbers with pointers to the source of truth:
- `AGENTS.md:45-46` — "36 documents, 6 investigations, 12 runbooks" → pointer
  to `seeds/knowledge.yaml`.
- `AGENTS.md:165` — "33 MCP tools" → "see §3 for the current tool list".
- `AGENTS.md:195,203` — "as of 2026-07-12" point-in-time dates removed.
- `.agents/OIKOS.md:108` — "migrations/ (001–011)" → "(001–020, forward-only)".
- `.agents/OIKOS.md:111-112` — duplicate brittle counts → pointer.
- `.agents/OIKOS.md:142` — "15 MCP tools" → pointer to AGENTS.md §3.
- `README.md:52` — "15 tools" → pointer.

Remaining brittle numbers (left as-is, intrinsic to evidence trail):
- `docs/mbse/README.md` carries many counts/dates as part of its audited
  evidence trail. Recommend adding a "Last verified: YYYY-MM-DD" header to that
  file and a scheduled re-verification (see §F).

### D.3 Substrate docs describing deleted Python architecture — fixed in R5

`.agents/domains/knowledge/schema.md` and `.agents/shared/llm-wiki.md` previously described
`bin/homelab`, `oikos/cards/`, `oikos/ledger.py`, root `inventory.yaml`, `knowledge/sources/`,
`get_page`/`search_docs` MCP tools — none of which exist. **Rewritten** for the DB-native model
(ADR 0003): DB is the single source of truth for both structured data and narrative knowledge;
`seeds/*.yaml` are the bootstrap+DR manifests; `archive/knowledge/` is the frozen legacy wiki;
MCP `search_knowledge`/`get_entity_knowledge` replace `get_page`/`search_docs`. Substrate refs in
`.agents/shared/{writing-style,page-templates}.md` and
`.agents/domains/operations/schema.md` swept clean. Root `inventory.yaml` marked deprecated
(stub points to `seeds/inventory.yaml` + DB; full on-client path reconciliation deferred to R13).

### D.4 ADR format

- 0001–0015 sequential, no gaps, indexed in `docs/adr/README.md`. ✅
- Template drift: `0013-signal-triggers.md` uses `## Overview` (no
  Context/Decision/Consequences); `0014-entity-model.md` uses numbered
  sections, no MADR template. Status-line format differs between 0001–0010/0015
  (plain) and 0011–0014 (bold split). **Normalize** (low priority — ADRs are
  immutable history; consider a formatting pass only).

### D.5 Plans — fixed in this pass

- Moved 4 "Done" 2026-07-14 plans from `plans/` to `plans/done/`
  (session-reliability-and-ux-audit, tool-timeline-sidebar,
  unified-agent-indicator, post-fix-session-remainders).
- Added 2 missing 2026-07-14 plans + 2 missing 2026-07-15 `done/` plans to
  `plans/index.md`.
- Updated `plans/index.md` Done table to reflect the moves.

### D.6 Missing docs — fixed in this pass

- Created `docs/index.md` (top-level docs index, per `writing-style.md` §folder
  READMEs).
- Created `docs/operations/README.md` (operations docs index).

### D.7 On-client path inconsistency

`AGENTS.md` uses `/opt/homelab-context/`; `CLIENTS.md` and `AGENTS.md:200`
itself use `/opt/homelab/`. **Pick one and use consistently** (recommend
`/opt/homelab/` per `CLIENTS.md:70-71`). Deferred — touches many lines and
the actual deployed path needs confirming against an enrolled client.

### D.8 Legacy root `inventory.yaml` — deprecated in R5

20387-byte Python-era file superseded by `seeds/inventory.yaml` on 2026-07-07. **Replaced with a
deprecation stub** pointing to `seeds/inventory.yaml` and the DB (ADR 0003). Kept as a stub rather
than deleted because AGENTS.md §1/§2 still point clients at `/opt/homelab-context/inventory.yaml`
(the on-client clone path); full path reconciliation is R13. `.agents/shared/*` and
`.agents/domains/*` references to bare `inventory.yaml` swept to `seeds/inventory.yaml` or
qualified as `archive/knowledge/` history.

## E. Build & tooling

### E.1 `Makefile`

- `make build` (`BINARY := oikos`) writes to `oikos/oikos` because `oikos/`
  exists as a directory. Functionally works (gitignored) but confusing — the
  gitignore comment says `bin/oikos`. **Recommend `BINARY := bin/oikos`** or
  rename the directory.
- `Makefile:70` `desktop-package` hardcodes `sed 's/$$(VERSION)/0.1.0/'`. Fixed
  in this pass to read from the `VERSION` file.
- `lint` target only runs `go vet` + optional `golangci-lint`. **Recommend
  installing golangci-lint + staticcheck + govulncheck** in CI (none are
  installed locally; CI config at `.gitea/workflows/ci.yml` should be checked).

### E.2 Desktop version hardcode — fixed in R6

`cmd/desktop/main.go:39` had `version = "0.1.0"` as a const while the repo is at `0.7.8`. Per
`CONTRIBUTING.md:54`, the auto-update feature compares against this value — so every release tag >
0.1.0 triggered a spurious update prompt. **Fixed:** `version` is now a `var` (default
`"0.1.0-dev"` fallback for bare `go build`), injected from the `VERSION` file at link time via
`make desktop` (`-ldflags "-X main.version=$(cat VERSION)"`). `CONTRIBUTING.md` updated to match.

### E.3 `Makefile` `BINARY` collision — fixed in R6

`BINARY := oikos` wrote to `./oikos`, which collided with the `oikos/` directory (Go's `-o oikos`
into a directory named `oikos` created `oikos/oikos`). **Fixed:** `BINARY := bin/oikos` (matches
the gitignore comment); `build` target ensures `bin/` exists; `clean` removes `bin/`. Stale
`oikos/` cruft directory removed.

## F. Recommendations (actionable, ordered)

| ID | Action | Effort | Risk |
| -- | ------ | ------ | ---- |
| R1 | Delete dead Go: `notifier.VerifyApprovalToken`, `httpapi/stubs.go`; unexport 4 `checkdefaults` symbols | S | Low | ✅ done (c3973e7) |
| R2 | Delete dead web: 21-file tool-renderer registry, 5 dead components, 2 dead store exports, 2 dead npm deps | S | Low | ✅ done (c3973e7+1) |
| R3 | Decide sqlc vs raw SQL: delete 17 dead queries OR migrate inline SQL to use them | M | Medium | ✅ done (hybrid: 8 deleted, 9 migrated) |
| R4 | Split `phase3.go` (2627 lines) into per-resource files; refactor `newServer` (708 lines) to a tool registry | M | Medium | ✅ done |
| R5 | Rewrite `.agents/domains/knowledge/schema.md` + `.agents/shared/llm-wiki.md` for the DB-native model; delete/deprecate root `inventory.yaml` | M | Low | ✅ done |
| R6 | Inject desktop `version` from `VERSION` via ldflags; fix `Makefile` `BINARY` colliding with `oikos/` dir | S | Low | ✅ done |
| R7 | Add tests for `learning` (80% gate), `actuator`, `scheduler`, `domain`, `notifier`, `knowledge` | L | Low | ✅ partial — pure unit tests added for all 6 packages; remaining coverage needs integration tests (`make test-db`) |
| R8 | Add `eslint`+`prettier`+`vitest` to `web/`; wire `svelte-check`+`tsc` into CI; add `web/` CI job | M | Low | ✅ done |
| R9 | Define `OikosEvent` discriminated union; eliminate ~15 `any` sites in web | S | Low | ✅ done |
| R10 | Replace `<svelte:component>` in `ActivityTimeline.svelte:103`; fix `state_referenced_locally` warnings | S | Low | ✅ done |
| R11 | Add the 8 manually-registered `serve*` routes to `openapi.yaml` (or document the carve-out) | S | Low | ✅ done — documented the carve-out |
| R12 | Add `docs/mbse/README.md` "Last verified" header + scheduled re-verification; normalize ADR 0013/0014 template | S | Low |
| R13 | Reconcile on-client path (`/opt/homelab/` vs `/opt/homelab-context/`) across AGENTS.md + CLIENTS.md | S | Low | ✅ done |
| R14 | Install `golangci-lint`/`staticcheck`/`govulncheck` locally + in CI | S | Low | ✅ done — `.golangci.yml` config + split Makefile targets |

## G. Verification

- `go vet ./...` — clean.
- `go build -tags timetzdata ./cmd/oikos` — clean.
- `go test -race -short ./...` — all tested packages pass.
- `npm run build` — succeeds with Svelte 5 warnings (listed §C.5).
- Doc fixes: all link targets verified to exist.

## H. What this commit changed

Applied (low-risk, reversible):
- Created this plan.
- Fixed 7 stale/broken doc references (AGENTS.md, README.md, OIKOS.md,
  commands.md, page-templates.md).
- Removed 8 brittle hardcoded counts/dates; replaced with pointers to source.
- Created `docs/index.md` and `docs/operations/README.md`.
- Moved 4 done plans to `plans/done/`; reconciled `plans/index.md`.
- Added `check`/`typecheck`/`lint` scripts + `svelte-check` devDep to
  `web/package.json`.
- Fixed `Makefile:70` `desktop-package` version substitution.
- Bumped `VERSION` 0.7.6 → 0.7.7.

Deferred (listed as recommendations R1–R14 above): all code deletions,
refactors, test additions, and the substrate-doc rewrite.
