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

25 KiB
Raw Permalink Blame History

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.gotemporal 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:286VerifyApprovalToken has zero call sites. Truly dead. Delete.
  • internal/checkdefaults/defaults.go:22,60,125,133ResolveHost, 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.go2627 lines (split by resource).
  • internal/mcp/server.go1691 lines.
  • internal/httpapi/impl.go1639 lines.
  • cmd/nomos/store.go1472 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 newServer708 lines (33 tools inline).
  • cmd/nomos/agent.go:187 chatWith405 lines.
  • internal/httpapi/phase3.go:270 executeApprovedAction356 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,107ChatEvent.data: any, interface data: any
  • src/lib/stores/chat.ts:58-59ToolCallResult.args?: any; result?: any
  • src/lib/stores/activity.ts:73-74(t.args as any)?.seq
  • src/lib/stores/workspace.ts:68,83,104,152data: 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,47T extends { child?: any }
  • vite.config.ts:18-24proxy: 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:18let 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 missingreadFileSync('VERSION') throws and crashes vite build/dev silently. Add a fallback or a build-time check.
  • vite.config.ts:38-50server.proxy hardcodes localhost:8090 (API) and localhost:8092 (nomos). Not env-driven.
  • vite.config.ts:31-34define: { __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:117http://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-117ghost 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
R2 Delete dead web: 21-file tool-renderer registry, 5 dead components, 2 dead store exports, 2 dead npm deps S Low
R3 Decide sqlc vs raw SQL: delete 17 dead queries OR migrate inline SQL to use them M Medium
R4 Split phase3.go (2627 lines) into per-resource files; refactor newServer (708 lines) to a tool registry M Medium
R5 Rewrite .agents/domains/knowledge/schema.md + .agents/shared/llm-wiki.md for the DB-native model; delete/deprecate root inventory.yaml M Low
R6 Inject desktop version from VERSION via ldflags; fix Makefile BINARY colliding with oikos/ dir S Low
R7 Add tests for learning (80% gate), actuator, scheduler, domain, notifier, knowledge L Low
R8 Add eslint+prettier+vitest to web/; wire svelte-check+tsc into CI; add web/ CI job M Low
R9 Define OikosEvent discriminated union; eliminate ~15 any sites in web S Low
R10 Replace <svelte:component> in ActivityTimeline.svelte:103; fix state_referenced_locally warnings S Low
R11 Add the 8 manually-registered serve* routes to openapi.yaml (or document the carve-out) S Low
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
R14 Install golangci-lint/staticcheck/govulncheck locally + in CI S Low

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.