Files
oikos/plans/2026-07-17-codebase-review-and-cleanup.md
dtoro 69964abe2e
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
chore: reconcile on-client path + add golangci-lint config (R13+R14)
R13 — on-client path reconciliation:
- AGENTS.md: 6 occurrences of /opt/homelab-context/ → /opt/homelab/
  (sections 1, 2, 5, 7)
- .agents/NOMOS.md: 2 occurrences of /opt/homelab-context/ → /opt/homelab/
- CLIENTS.md already used /opt/homelab/ — now consistent across all docs.
  The repo is still named 'homelab-context' (git remote), it just clones
  to /opt/homelab/ on enrolled clients per CLIENTS.md.

R14 — golangci-lint/staticcheck/govulncheck tooling:
- .golangci.yml (new): config enabling govet, staticcheck, ineffassign,
  unused, errcheck, gosimple, typecheck, misspell, revive. Excludes
  generated code (internal/httpapi/gen/, internal/db/sqlcgen/) and
  relaxes errcheck in test files.
- Makefile: split 'lint' target into vet, golangci, govulncheck subtargets.
  Each checks if the tool is installed and prints install instructions
  if not. 'make lint' runs all three.
- CI already had golangci-lint-action + govulncheck (both advisory);
  the action auto-discovers .golangci.yml.
2026-07-17 23:09:47 +02:00

464 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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", "001011" — 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 001020: 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/ (001011)" → "(001020, 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
- 00010015 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 00010010/0015
(plain) and 00110014 (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 R1R14 above): all code deletions,
refactors, test additions, and the substrate-doc rewrite.