Files
thiessen.io/test/content.test.mjs
dennisthiessenandClaude Sonnet 5.5 61465cc67e
My Gatsby Deployment pipeline / build-and-deploy (push) Successful in 1m4s
Rebuild the site as a terminal-style agent on Astro
Replace the single Gatsby page with a dark, terminal-style agent that answers
questions about Dennis's work from one curated knowledge base. There is no
model and no network call, so answers cannot be invented. A plain "Classic
view" and an optional light theme sit one click away.

Site
- Terminal UI (React island): typed answers, tool-call lines, slash commands,
  autocomplete, history, deep links, a short /oncall game
- Content focused on what recruiters scan for; personal and employer-internal
  details are left out
- No profile photo anywhere, including the new link-preview card and icons
- Corrected facts from the old page (dates, titles, tenses, skill tiers)

Tooling
- Migrate Gatsby 5 to Astro 7 (Node 22.23+); builds in about 1 second
- Self-host fonts, so visitors make no request to third parties
- Link-preview card, favicon set, web manifest and a service-worker clean-up
  for returning visitors
- Deploy workflow builds with npm ci on Node 22 and uploads dist/
- Track package-lock.json

Quality
- Tests for routing and for markup injection from visitor input
- A content guard scans every reachable answer; the list of things that must
  never be published lives in a git-ignored local file

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 22:45:25 +01:00

143 lines
4.4 KiB
JavaScript

// Guards the public wording against claims the resume kit rules out. Every answer the
// terminal can reach by clicking is generated and scanned, so a new sentence in
// profile.mjs cannot slip a banned claim onto the site.
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { answerToText, COMMANDS, INTRO, respond } from '../src/agent/engine.mjs'
import * as profile from '../src/data/profile.mjs'
import { privatePatterns } from './private-banned.mjs'
// Generic rules. The specifics that must stay hidden live in the git-ignored
// test/banned.private.json, so this file never spells them out.
const BANNED = [
[
/typescript|terraform|azure|\bgcp\b|snowflake/i,
'skill that is not on the record',
],
[/langchain|langgraph|llamaindex|llama-index/i, 'agent framework never used'],
[/fine-?tun/i, 'model fine-tuning'],
[/petabyte|\bPB-scale/i, 'scale claim'],
[
/own the (aws )?data platform|built the data mesh|data mesh/i,
'org-scale ownership',
],
[
/customer-embedded|formal model evaluation|agent orchestration|production llm/i,
'forbidden pattern',
],
[
/led the migration|sole technical lead|owned the full migration|managed a team|led engineers/i,
'ownership overreach',
],
[/24\/7 on-?call|on-?call 24\/7/i, 'on-call hours are not on the record'],
[/\bmttr\b|\buptime\b.*\d+\s?%/i, 'operational metric'],
[/\+\d{2}[\s\d().-]{8,}/, 'phone number'],
[/\bgrade\b/i, 'degree grades stay off the page'],
[
/permit|sponsorship|authori[sz]ed to work|work authori[sz]ation/i,
'work authorization stays off the page',
],
...privatePatterns,
]
const scan = (label, value) => {
for (const [pattern, why] of BANNED) {
assert.doesNotMatch(value, pattern, `${label}: ${why}`)
}
}
test('static content avoids banned claims', () => {
const { NON_CLAIMS, ...content } = profile
scan('profile.mjs', JSON.stringify(content))
})
test('every answer reachable from the intro avoids banned claims and dead ends', () => {
const seen = new Set()
const queue = [
...INTRO.followups.map((f) => [f.cmd, null]),
...COMMANDS.filter((c) => !['clear', 'theme'].includes(c.name)).map((c) => [
`/${c.name}`,
null,
]),
]
let visited = 0
while (queue.length) {
const [cmd, scene] = queue.shift()
const key = `${cmd}|${JSON.stringify(scene)}`
if (seen.has(key)) continue
seen.add(key)
const out = respond(cmd, scene)
if (!out.answer) continue
visited += 1
const body = answerToText(out.answer)
scan(cmd, body)
assert.doesNotMatch(
body,
/Unknown command|No (role|project) matches|outside what I can answer/,
`${cmd} led to a dead end`
)
for (const f of out.answer.followups) queue.push([f.cmd, out.scene])
}
assert.ok(visited > 30, `crawled ${visited} answers`)
})
test('every role and project opens', () => {
for (const role of profile.ROLES) {
assert.match(
answerToText(respond(`/experience ${role.id}`).answer),
new RegExp(role.company)
)
}
for (const project of profile.PROJECTS) {
const body = answerToText(respond(`/projects ${project.id}`).answer)
assert.ok(body.includes(project.name), project.id)
scan(project.id, body)
}
})
test('every skill can be asked about by name', () => {
for (const skill of profile.SKILLS) {
const body = answerToText(
respond(`Does he know ${skill.aliases[0]}?`).answer
)
assert.ok(
body.includes(skill.note),
`${skill.name} should answer with its own note`
)
}
})
test('job titles and dates match the record', () => {
const byId = Object.fromEntries(profile.ROLES.map((r) => [r.id, r]))
assert.equal(byId.bosch.period, '2020 to 2022')
assert.equal(byId.fraunhofer.period, '2018 to 2019')
assert.equal(byId.vizrt.title, 'Test Automation / DevOps Engineer')
assert.equal(byId.generali.title, 'IT Consultant')
assert.equal(
byId.swisscom.titleHistory,
'Senior from Oct 2023, Staff since Apr 2025'
)
})
test('the not-in-stack list holds only mainstream technologies', () => {
const allowed = new Set([
'TypeScript',
'Terraform',
'Azure',
'GCP',
'Snowflake',
'LangChain',
'LangGraph',
'LlamaIndex',
'Model fine-tuning',
])
for (const [label] of profile.NON_CLAIMS) {
assert.ok(
allowed.has(label),
`${label} should not be published in the not-in-stack list`
)
}
})