Phase 3.1 — VM Score Card (0–100):
- 6 weighted dimensions: steal time, RAM, disk, service health,
maintenance hygiene, LLM readiness (matching roadmap scoring)
- Color-coded gauge + per-dimension progress bars with detail text
- Computed from health + cron + unhealthy data; degrades gracefully
when any source is unavailable
Phase 1.3 — Unhealthy Container Detail Panel:
- Loads independently from GET /api/vm/containers/unhealthy
- Per-container: name, unhealthy since, restart count, last health logs
- Expandable row for health check output
- One-click restart with spinner, feedback toast, auto-refresh after 3s
Phase 1.1 — Cron Status Panel:
- Loads from GET /api/vm/cron-status
- Table: 4 managed jobs × schedule | last run | freed MB | status | next
- Collapsible run history (last 10) with step-by-step log expansion
Phase 3.4 — Ollama/LLM Panel:
- Loads from GET /api/vm/ollama/models
- Currently-loaded section with RAM pressure warning (<4 GB free)
- RAM bar visualisation showing model footprint
- Model list with size + last-used time
- One-click unload button
Other improvements:
- All data fetched in parallel (Promise.allSettled) — any panel failure
does not block the rest of the page
- Add steal, failed_units, cron_missing_paths to CHECK_META/CHECK_ORDER
- Refresh now updates all 5 data sources atomically
- web/package-lock.json regenerated (was stale, caused build failure)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- code-quality/repository.ts: fix tsErrorMatch[3] → [4] for type field (group 3 is column, 4 is error|warning)
- code-quality/repository.ts: fix ESLint regex to make rule brackets optional (not all formatters include them)
- code-quality/repository.ts: fix Vitest test count — parse 'Tests' line (individual tests) instead of 'Test Files' (file count); improve Jest regex to capture pass/fail independently
- env/repository.ts: replace raw process.env.ENCRYPTION_KEY with config.ENCRYPTION_KEY so the validated default flows through a single source of truth
- config.ts: add startup console.warn when CSRF_SECRET or ENCRYPTION_KEY are using insecure defaults
- deployments/orchestrator.ts: refactor runDeploymentScript to use try/catch/finally — deployment record is now always written in the finally block, preventing zombie 'running' states if updateDeployment itself throws
- auth.tsx: remove dead 'user &&' guard (user is always truthy after the !user check above); remove debug console.log calls, keep console.error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Primitives.tsx (TS2339):
- asChild branch read children.props.className before the cast applied,
making props typed as unknown. Extract typedChild first, then read props.
hermes/page.tsx + agents/page.tsx + tasks/page.tsx + tasks/[id]/page.tsx (TS2322):
- Badge.variant accepts 'neutral'|'success'|'warning'|'error'|'info' but
callers were passing 'danger' (should be 'error') and 'default' (should
be 'neutral'). MetricCard.tone is a separate type and is correct as-is.
Changes:
- statusTone map in hermes/page.tsx: 'danger' → 'error', 'default' → 'neutral'
- getTaskTone fallback: 'default' → 'neutral'; explicit return type added
- levelTone in tasks/[id]/page.tsx: 'danger' → 'error'; explicit return type added
- Inline Badge variants: all remaining 'danger' → 'error' across 3 files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Security (backend):
- env/routes: add requireAdmin to all 6 env endpoints — GET /env was
fully open, exposing all secret values to unauthenticated requests
- deployments/routes: add requireAdmin to all 4 GET endpoints (deployment
history and logs were publicly readable)
- health/routes: remove duplicate requireAdmin call from DELETE /health/cache
handler body (was already enforced via preHandler)
Frontend — auth/api:
- system/page: replace raw fetch + localStorage token with apiRequest
(mutations now go through CSRF flow)
- vm/page: same — replace raw fetch with vmApi from api.ts
- api.ts: add vmApi (getHealth, getCleanupLog, runCleanup) + shared
VmHealthResult / VmCheck / VmCheckLevel types
Shared utilities:
- utils.ts: add formatBytes() and getStatusColor() shared helpers
- system/page: remove duplicate formatBytes, import from utils
- health/page: remove duplicate getStatusColor, import from utils
- page.tsx (home): remove duplicate getStatusColor, import from utils
UX improvements:
- page.tsx: remove Seed Services button from normal header (debug tool)
- page.tsx: deploy button now always enabled; shows inline warning banner
when service is not 'up' instead of silently disabling the button
- metrics: fix bar chart — bars now grow from bottom (flex-col-reverse),
add empty state, fix date parsing timezone edge case
- sidebar-nav: theme toggle now functional — persists to localStorage and
toggles document.documentElement class 'dark'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 07:00 daily cron was incorrectly pointing to vm-cleanup.sh instead
of vm-health-check.sh. Health check is read-only; cleanup is not.
Also add --notify so Telegram alerts fire when WARNING/CRITICAL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A prior rebase merged the v13/v13.1 edits into \xc2\xa710 with mangled text
(steps 11\xe2\x80\x9320 out of order; step 10 garbled). Rebuilt the section
cleanly from v12 base + appended the new v13/v13.1 steps:
11. Phase E1/E2/E5
12. Phase B
13. Phase B4 + E3/E4/E6
14. Phase C (8/9; C5 partial)
15. Phase D.1
16. Phase D.2
17. B7-4 AGENTS.md warnings
18. Phase D extension (MindLyst, LysnrAI, talk2obsidian)
19. Phase D.3 advisory cleanup
20. C5 partial validation (this session)
Restored the lost "ported back to clock" trailing line for step 9.
No content changes beyond what was already documented in v13/v13.1.
Findings from dummy check-in attempt:
- Pilot workflow YAML parses cleanly (6 jobs on clock incl. docker-lint)
- Local simulation of docker-lint job (gitea-doctor + docker-doctor)
exits 0 on both pilots
- Pilot repos are NOT hosted on Gitea (`git push gitea` returns 404).
Only `learning_ai_uxui_web` exists at localhost:3300
- Until pilot repos are mirrored to Gitea, the .gitea/workflows/ci.yml
file ships but the runner never fires
- C5 marked as partial; gap recorded explicitly in \xc2\xa7Phase C and \xc2\xa710
Final-state summary:
- All 12 consumer repos now PASS docker-doctor with zero errors
- MindLyst + LysnrAI + talk2obsidian onboarded (was previously out of scope)
- docker-doctor learned Python Dockerfile detection
- 10 repos received advisory-warning cleanup commits (compose build.args
+ healthcheck.start_period)
- C5 (CI green confirmation) is the only remaining follow-up
The roadmap is now in a fully landed state for in-scope repos.
- B7-4 AGENTS.md warnings landed in all 9 repos
- C9 web smoke test (Playwright) landed on clock to guard F11 regression
- D.2 per-repo Dockerfile/compose fixes applied to all 7 consumer repos
via idempotent fixer; docker-doctor PASS on every consumer repo
- 3 non-consumer repos (MindLyst KMP, LysnrAI multi-target, talk2obsidian)
remain out of scope; documented as follow-up
- C5 confirmation pending next Gitea CI run
Final status: 18 of 18 in-scope items complete.
- Marked E1, E2, E5 complete in Phase E checklist
- Added step 11 to \xc2\xa710 execution order with commit refs
- Renumbered remaining steps; deferred E3/E4/E6 to after Phase B