phase: 05_update_projects_section
Build and Push Container / build-and-push (push) Successful in 16s

**Phase 05 final pass — all 4 tasks already complete; verified phase-level criteria + docs. Repo 100% green.**

- Verified: 6 `project-card fade-in` cards; order Brain of Reese → Reverse Proxy → Dynamic DNS → Local AI Stack → Deployment Automation → ServWaf; `src/index.html` diff hunks (lines 293–538) all inside `#projects` (286–542).
- `./build.sh` → `Build complete: dist/` (exit 0); `npm test` → **48 passed, 0 failed**; `bash .agents/validate.sh` → **exit 0** (48 E2E + `HEADER CHECK PASSED` on `/`, `/index.html`, hashed CSS via podman + repo-hygiene gates). No linter exists in this repo (no `lint` script/eslint config). Coverage: no JS/CSS touched — vacuous per phase spec; E2E is the binding gate.
- Completion criteria: card count 6 in `src/` **and** `dist/` ✅ · first card "Brain of Reese" with `…/Experimental/brain-of-reese` href in `dist/index.html` ✅ · `grep -c "Self-Hosted Services\|Kubernetes Cluster" src/index.html` = **0** ✅ · AI card has vLLM/Gufo/llama.cpp + LocalAI/LiteLLM/Langfuse, no "Ollama" ✅ · regression test present in `tests/e2e/01-page.spec.js` and passing ✅ · `./build.sh && npm test` green (48 ≥ 45) ✅ · no edits outside `#projects` ✅ · README/AGENTS consistent ✅.
- Docs: README **updated** (new paragraph stating what the E2E suite covers, incl. the six featured cards, so future content edits update the matching test). AGENTS.md **updated** (convention: the `#projects` grid is test-locked by `01-page.spec.js`). All other README claims re-checked against reality (Dockerfile builds `dist/` internally, `listen 8080`, snippet `include`s, `check-headers.sh` port/paths/output, `--bind 0.0.0.0` config, Node ≥20 engine floor, CI `main`+tag publishing) — consistent.
- Notable: `.agents/validate.sh` is still **untracked** though AGENTS.md/README reference it — harness should `git add` it (I ran no git index commands). `.agents/PLAN.md` §4/§5 still say "44 tests" (suite is 48 since Phases 04/05); PLAN.md is off-limits, so this drift needs an explicit user-approved edit. No live-infrastructure touched.
- Next pending phase: none in `.agents/phases/todo/` (05 was last). Remaining unphased audit items (L-3 CSP `unsafe-inline`, L-5 SSH-key labelling, L-7 CI pinning, L-8 build.sh CI check) would need new phases via `phase-authoring`.
This commit is contained in:
2026-10-23 01:32:52 -04:00
parent 2f6f2dc0b9
commit 1fe932cf39
28 changed files with 2454 additions and 93 deletions
@@ -0,0 +1,62 @@
# Phase 05 — Update Featured Projects (Brain of Reese, ServWaf, Local AI Stack)
**Story:** n/a
**Context:** `src/index.html` `#projects` section (6 `project-card`s), `~/Projects/Personal/brain_of_reese/README.md`, `~/Projects/StackExpected/ServWall/README.md`, `~/Projects/Experimental/gufo/README.md`
## Objective
Refresh the Featured Projects grid: (a) replace the "Self-Hosted Services"
card with a **Brain of Reese** card (RAG chatbot over homelab docs;
pgvector ∪ Postgres full-text hybrid search with RRF fusion; self-hosted
LLM; source `https://gitea.reeseapps.com/Experimental/brain-of-reese` —
verified live) and move it to the **first** slot; (b) replace the
"Kubernetes Cluster" card with a **ServWaf** card (Go network WAF —
declarative YAML rules, rate limiting, attack-pattern inspection, opt-in
local-AI verdict, TLS/HTTP3 edge — **closed source: no source link**,
user decision) in its place; (c) update the **Local AI Stack** card to
replace Ollama with **vLLM, Gufo, and llama.cpp** (copy, tech tags, and
code preview). Lock with a Playwright regression test.
## Dependencies
— (none)
## Tasks
1. `01_brain_of_reese_card.md` — Delete the Self-Hosted Services card; insert the Brain of Reese card as the first card in `#projects`.
2. `02_servwaf_card.md` — Replace the Kubernetes Cluster card with the ServWaf card (description only, no source link).
3. `03_update_local_ai_stack.md` — Update the Local AI Stack card: replace Ollama with vLLM/Gufo/llama.cpp in the copy, tech tags, and code preview.
4. `04_projects_regression_test.md` — Add a Playwright test locking card count, order, the updated Local AI Stack tags, and the absence of the two removed card titles.
## Testing & Quality
- Unit/integration: no JS logic changes in this phase — `src/index.html`
content only. The quality gate is the E2E regression test (task 4) plus
the full suite.
- Coverage: **>90%** on new/modified code — vacuously satisfied (no
JS/CSS/TS code is modified); the E2E suite is the binding gate.
- E2E: full Playwright suite (`npm test`) green.
- The phase is "Complete" only when the **entire** project test suite passes
100% (every test in the project, not just this phase's — pre-existing
failures included) and achieves **>90% code coverage** on new/modified code.
## Documentation & Consistency
- The final pass reviews `README.md` and `AGENTS.md` in full against the
project as it stands after this phase — fixing every inconsistency found,
**including pre-existing ones not caused by this phase**.
- Tasks update `README.md`/`AGENTS.md` immediately when their own changes are
user-visible (setup, commands, configuration, features, conventions).
## Completion Criteria
- [ ] `grep -c "project-card fade-in" src/index.html` returns 6, and the
same holds in `dist/index.html`
- [ ] first `project-card` in `#projects` (in `dist/index.html`) is
"Brain of Reese" and its card links to
`https://gitea.reeseapps.com/Experimental/brain-of-reese`
- [ ] `grep -c "Self-Hosted Services\|Kubernetes Cluster" src/index.html`
returns 0 (project-card titles removed; the words may still appear in
the out-of-scope About/Skills sections — e.g. "Kubernetes" skill tag)
- [ ] the Local AI Stack card (in `dist/index.html`) contains vLLM, Gufo,
and llama.cpp and no "Ollama"
- [ ] new regression test present in `tests/e2e/01-page.spec.js` and passing
- [ ] `./build.sh && npm test` fully green (45+ tests)
- [ ] no change to the Skills section, About section, or any other section
outside `#projects`
- [ ] README.md and AGENTS.md are accurate and consistent with the whole
project (verified in the final pass)
@@ -0,0 +1,76 @@
# Task 01 — Brain of Reese card (first slot)
**Phase:** `05_update_projects_section` · **Story:** n/a
## Objective
Remove the "Self-Hosted Services" project card and insert the new
**Brain of Reese** card as the **first** card in `#projects` (immediately
before the "Reverse Proxy Infrastructure" card).
## Work
1. `src/index.html` — inside `<section id="projects">`, delete the entire
`project-card` block whose `<h3>` is "Self-Hosted Services" (the card
containing the `- Immich / - Jellyfin / …` code block).
2. `src/index.html` — insert the block below **immediately before** the
existing first `project-card` (the "Reverse Proxy Infrastructure"
card), matching the file's 16/20/24/28-space indentation. For the
`<div class="project-links">…</div>` (Gitea SVG) block, copy the
existing one from the "Reverse Proxy Infrastructure" card verbatim and
change only the `href` to
`https://gitea.reeseapps.com/Experimental/brain-of-reese`:
```html
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-comment"># Hybrid search: pgvector cosine ∪ Postgres full-text</span></div>
<div class="code-line"><span class="code-keyword">def</span> <span class="code-function">retrieve</span>(<span class="code-variable">query</span>, <span class="code-variable">k</span>=<span class="code-variable">5</span>):</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">vec</span> = <span class="code-function">pgvector</span>.<span class="code-function">cosine</span>(<span class="code-variable">query</span>)</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">fts</span> = <span class="code-function">postgres</span>.<span class="code-function">full_text</span>(<span class="code-variable">query</span>)</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">chunks</span> = <span class="code-function">rrf_fuse</span>(<span class="code-variable">vec</span>, <span class="code-variable">fts</span>, <span class="code-variable">k</span>)</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-keyword">return</span> <span class="code-function">llm</span>.<span class="code-function">chat</span>(<span class="code-function">document</span>(<span class="code-variable">chunks</span>))</div>
<div class="code-line">&nbsp;</div>
</div>
</div>
<div class="project-info">
<h3>Brain of Reese</h3>
<p>A chippy, honest RAG chatbot over homelab documentation — git repositories, local
directories, and uploaded archives. Hybrid search (pgvector cosine ∪ Postgres
full-text search, Reciprocal Rank Fusion) feeds the whole relevant document to a
self-hosted LLM and streams back a grounded answer; when it has no notes it
admits it and suggests questions it can answer.</p>
<div class="project-meta">
<div class="project-tech">
<span>FastAPI</span>
<span>pgvector</span>
<span>Postgres</span>
<span>Playwright</span>
</div>
<div class="project-links">
<!-- copied from the Reverse Proxy Infrastructure card, href changed -->
</div>
</div>
</div>
</div>
```
3. `./build.sh` — rebuild `dist/`.
## Testing & Quality
- Unit/integration: content-only change — no new JS logic; the E2E
regression test is task 4, and the full suite is the gate.
- Coverage: **>90%** on this task's new/modified code — vacuously satisfied
(HTML content only, no JS/CSS modified).
## Completion Criteria
- [ ] `grep -c "project-card fade-in" src/index.html` returns 6
- [ ] `grep -n "Brain of Reese" src/index.html` matches once, and that card
is the first `project-card` inside `#projects`
- [ ] `grep -c "Self-Hosted Services" src/index.html` returns 0
- [ ] `grep -n "gitea.reeseapps.com/Experimental/brain-of-reese"
src/index.html` matches inside the new card
- [ ] `./build.sh` succeeds and the same greps pass on `dist/index.html`
- [ ] `npm test` fully green (44 tests — new test lands in task 4)
- [ ] README.md/AGENTS.md updated for anything user-visible this task changes
(the phase's final pass re-checks both project-wide)
- [ ] no behavior change in completed work
@@ -0,0 +1,69 @@
# Task 02 — ServWaf card (replaces Kubernetes Cluster)
**Phase:** `05_update_projects_section` · **Story:** n/a
## Objective
Replace the "Kubernetes Cluster" project card, in place (last slot), with
the **ServWaf** card. ServWaf is **closed source** (user decision): the
card carries **no** `project-links` block — description only.
## Work
1. `src/index.html` — inside `<section id="projects">`, replace the entire
`project-card` block whose `<h3>` is "Kubernetes Cluster" (the card
containing the `# Kubernetes with k3s` code block) with the block below,
matching the file's 16/20/24/28-space indentation. Note: this card has
**no** `<div class="project-links">` — the `project-meta` div contains
only `project-tech` (the flex layout handles a single child).
```html
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-comment"># ServWaf — declarative rules</span></div>
<div class="code-line"><span class="code-variable">ratelimit</span>:</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">per_ip</span>: <span class="code-string">20 rps, burst 40</span></div>
<div class="code-line"><span class="code-variable">rules</span>:</div>
<div class="code-line">&nbsp;&nbsp;- <span class="code-keyword">deny</span> <span class="code-variable">if</span> <span class="code-variable">user_agent</span> =~ <span class="code-string">"curl|wget"</span></div>
<div class="code-line">&nbsp;&nbsp;- <span class="code-keyword">flag</span> <span class="code-variable">if</span> <span class="code-variable">body</span> =~ <span class="code-string">"(?i)union\s+select"</span></div>
<div class="code-line"><span class="code-variable">ai</span>:</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">endpoint</span>: <span class="code-string">"http://aipi.reeseapps.com/v1"</span></div>
</div>
</div>
<div class="project-info">
<h3>ServWaf</h3>
<p>A network Web Application Firewall written in Go, sitting in line between clients
and the origin. Declarative YAML rules (user agent, IP/CIDR, URL and body
regexes), per-IP rate limiting, attack-pattern inspection (SQL injection, XSS,
path traversal, CRLF, scanner probes), opt-in self-hosted local-AI verdicts,
TLS termination, and an opt-in HTTP/3 (QUIC) edge.</p>
<div class="project-meta">
<div class="project-tech">
<span>Go</span>
<span>YAML</span>
<span>HTTP/3</span>
<span>Podman</span>
</div>
</div>
</div>
</div>
```
2. `./build.sh` — rebuild `dist/`.
## Testing & Quality
- Unit/integration: content-only change — no new JS logic; the E2E
regression test is task 4, and the full suite is the gate.
- Coverage: **>90%** on this task's new/modified code — vacuously satisfied
(HTML content only, no JS/CSS modified).
## Completion Criteria
- [ ] `grep -c "project-card fade-in" src/index.html` returns 6
- [ ] `grep -n "ServWaf" src/index.html` matches the new card's `<h3>`
- [ ] `grep -c "Kubernetes Cluster" src/index.html` returns 0
- [ ] the ServWaf card (last card in `#projects`) contains no
`project-links` div and no `gitea.reeseapps.com` href
- [ ] `./build.sh` succeeds and the same greps pass on `dist/index.html`
- [ ] `npm test` fully green (44 tests — new test lands in task 4)
- [ ] README.md/AGENTS.md updated for anything user-visible this task changes
(the phase's final pass re-checks both project-wide)
- [ ] no behavior change in completed work
@@ -0,0 +1,59 @@
# Task 03 — Update Local AI Stack card (vLLM, Gufo, llama.cpp)
**Phase:** `05_update_projects_section` · **Story:** n/a
## Objective
Update the "Local AI Stack" project card so Ollama is replaced by **vLLM,
Gufo, and llama.cpp** in the description, tech tags, and code preview.
Everything else on the card (LocalAI, LiteLLM, Bifrost, Langfuse, CUDA/
ROCm) stays.
## Work
1. `src/index.html` — in the "Local AI Stack" card, replace the `<p>` copy
with:
```html
<p>Complete local AI infrastructure: vLLM, Gufo, and llama.cpp for inference, LiteLLM as a
unified API proxy, Bifrost for model routing, and Langfuse for observability. Supports
both CUDA and ROCm.</p>
```
2. `src/index.html` — in that card's `project-tech` div, replace the
`<span>Ollama</span>` with three spans in this order:
`<span>vLLM</span> <span>Gufo</span> <span>llama.cpp</span>`
(final tag list: vLLM, Gufo, llama.cpp, LocalAI, LiteLLM, Langfuse).
3. `src/index.html` — in that card's `code-block`, replace the whole block
(the `from ollama import Client` … `# LiteLLM proxy for unified API`
lines) with:
```html
<div class="code-line"><span class="code-comment"># Inference engines</span></div>
<div class="code-line"><span class="code-variable">engines</span> = {</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-string">"vllm"</span>: <span class="code-string">"cuda"</span>, <span class="code-comment"># high-throughput serving</span></div>
<div class="code-line">&nbsp;&nbsp;<span class="code-string">"gufo"</span>: <span class="code-string">"rocm"</span>, <span class="code-comment"># Strix Halo inference</span></div>
<div class="code-line">&nbsp;&nbsp;<span class="code-string">"llama.cpp"</span>: <span class="code-string">"gguf"</span>, <span class="code-comment"># CPU/GPU fallback</span></div>
<div class="code-line">}</div>
<div class="code-line">&nbsp;</div>
<div class="code-line"><span class="code-comment"># LiteLLM proxy for unified API</span></div>
```
4. `./build.sh` — rebuild `dist/`.
## Testing & Quality
- Unit/integration: content-only change — no new JS logic; the E2E
regression test is task 4, and the full suite is the gate.
- Coverage: **>90%** on this task's new/modified code — vacuously satisfied
(HTML content only, no JS/CSS modified).
## Completion Criteria
- [ ] within the Local AI Stack card in `src/index.html`: no "Ollama"
anywhere, and "vLLM", "Gufo", "llama.cpp" each present (copy, tags,
and preview)
- [ ] "LocalAI", "LiteLLM", "Langfuse" (and "Bifrost" in the copy) are
still present on the card
- [ ] `./build.sh` succeeds and the same checks pass on
`dist/index.html`
- [ ] `npm test` fully green (44 tests — new test lands in task 4)
- [ ] README.md/AGENTS.md updated for anything user-visible this task changes
(the phase's final pass re-checks both project-wide)
- [ ] no behavior change in completed work
@@ -0,0 +1,64 @@
# Task 04 — Projects grid regression test
**Phase:** `05_update_projects_section` · **Story:** n/a
## Objective
Lock the Featured Projects change with a Playwright regression test: six
cards in the new order (Brain of Reese first, ServWaf last), the Local AI
Stack card showing vLLM/Gufo/llama.cpp with no Ollama, and the removed
"Self-Hosted Services" / "Kubernetes Cluster" titles absent.
## Work
1. `tests/e2e/01-page.spec.js` — inside the existing
`test.describe('Page structure', ...)` block, add one test, matching the
file's existing style:
```js
test('featured projects show the six updated cards in order', async ({ page }) => {
const cards = page.locator('#projects .project-card');
await expect(cards).toHaveCount(6);
const titles = await cards.locator('.project-info h3').allTextContents();
expect(titles).toEqual([
'Brain of Reese',
'Reverse Proxy Infrastructure',
'Dynamic DNS Service',
'Local AI Stack',
'Deployment Automation',
'ServWaf',
]);
// Brain of Reese leads, with its source link
const brain = cards.first();
await expect(brain.locator('.project-tech span')).toHaveCount(4);
await expect(brain).toHaveText(/RAG chatbot/i);
await expect(brain.locator('a[href="https://gitea.reeseapps.com/Experimental/brain-of-reese"]')).toBeAttached();
// Local AI Stack: Ollama replaced by vLLM, Gufo, llama.cpp
const ai = cards.nth(3);
await expect(ai.locator('.project-tech')).toContainText(['vLLM', 'Gufo', 'llama.cpp']);
await expect(ai.locator('.project-tech')).toContainText(['LocalAI', 'LiteLLM', 'Langfuse']);
await expect(ai).not.toContainText('Ollama');
// ServWaf is closed source — description only, no source link
await expect(cards.last()).toHaveText(/Web Application Firewall/i);
await expect(cards.last().locator('.project-links')).not.toBeAttached();
// removed cards are gone from the grid
expect(titles).not.toContain('Self-Hosted Services');
expect(titles).not.toContain('Kubernetes Cluster');
});
```
2. `./build.sh && npm test` — rebuild and run the full suite.
## Testing & Quality
- Unit/integration: the new test covers (a) card count (6) and exact title
order, (b) Brain of Reese first with its Gitea source link, (c) Local AI
Stack tech tags (new + retained, no Ollama), (d) ServWaf last with no
source link, and (e) the two removed titles absent.
- Coverage: **>90%** on this task's new/modified code — test-only change;
the E2E suite is the binding gate.
## Completion Criteria
- [ ] the new test is present in `tests/e2e/01-page.spec.js` and passes
- [ ] `./build.sh && npm test` fully green (45 tests: 44 pre-existing + 1
new)
- [ ] README.md/AGENTS.md updated for anything user-visible this task changes
(the phase's final pass re-checks both project-wide)
- [ ] no behavior change in completed work