Test & quality alignment plan
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Refactor
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- bun, eslint, nextjs, react, tailwindcss, typescript
Research direction
Start with the referenced root package.json, apps/web/package.json, and bun.lock, then inspect the phased gates and the existing apps/web/src/lib files. Run the Phase 0 checks first, followed by the local verification commands described in the plan; the work is done when the planned test, lint, type-check, build, and report-only quality gates pass without introducing CI.
Written by the indexing model from the issue text.
Description
Test & Quality Alignment Plan — dbuild
- Project:
Dbuild.dev(/home/didi/workspace/dbuild, originDimitriGilbert/dbuild) - Date: 2026-09-05
- Generated by the fleet test-and-quality alignment initiative (28-repo audit + synthesis)
- Audit report:
../.test-alignment-audit/reports/dbuild.md - Fleet synthesis / decisions:
../.test-alignment-audit/synthesis-decisions.md - Stack reality: BTS v2.22.10
next-onlymonorepo — Next.js 15.1.3 / React 19 / Tailwind v4 / Turborepo, Bun-managed (packageManager: [email protected],bun.lock), statically exported to GitHub Pages (dbuild.dev). One app (apps/web), emptypackages/. ~9k LOC, 0 test files, no hooks, deadlintscript (no CI — by owner design, D5; the gate is local commands).
Revised 2026-09-05: CI removed by owner decision — quality gates are local commands run by the owner/agents; no GitHub Actions.
1. Adopted decisions (synthesis D1–D7, adapted to a Bun-managed Next.js static site)
| Decision | Adoption for dbuild |
|---|---|
| D1 — Runner & layout | bun:test (Bun's built-in runner), NOT vitest. D1 explicitly rules: "Bun repos: bun:test stays … do not introduce vitest" (precedent: email-sdk + FeedElity, 806 cases combined). Adding vitest would bolt a Vite toolchain onto a repo with zero test infra for no benefit. Tests colocated as *.test.ts(x) next to source. Root one-command entry bun run test via turbo. Environment: node-only — no jsdom, no RTL. This is the explicit pick synthesis §5 demands from Next-variant repos: v1 has zero component tests (static site; the value is pure logic), so the DOM stack would be dead weight. |
| D2 — Coverage | Report-only. Bun's coverage reporter is explicitly not threshold-grade (synthesis §5). Run bun test --coverage, record the measured baseline in AGENTS.md, never gate on it. Revisit thresholds when Bun coverage matures. |
| D3 — Lint | ESLint 9 flat config + typescript-eslint ^8 + eslint-plugin-react-hooks (fleet standard). This retires the dead lint: "next lint" script (no ESLint dep or config exists — audit P1) and un-dangles the lint task in turbo.json. Prettier deferred in v1 per D3's allowance — stated in AGENTS.md rather than pretended. |
| D4 — Quality metrics | knip + jscpd, report-only, local report scripts run on demand by the owner/agents (writing a git-ignored local report). No CRAP (requires threshold-grade coverage Bun can't give), no mutation testing (dbuild is not one of the five nominated cores), no circular-dep tooling in v1. jscpd uses the launch-mommy canonical config (minLines: 8, minTokens: 80, exitCode: 0). |
| D5 — no CI by design (owner decision 2026-09-05) | GitHub Actions rejected fleet-wide: no .github/workflows, no reusable-workflow convergence target, no scheduled jobs. Enforcement = the root-level headless verification entrypoint (bun run verify: lint → check-types → test → build, composed in Phase 4) run by the owner and by agents; the plan's phase validation gates are run by implementer/validator agents before any phase is declared done. GH Pages deploys stay manual, guarded by a green bun run verify. |
| D6 — Shared config | Adopt @dg/config presets opportunistically (D6: "when it touches lint/tsconfig/vitest anyway") — Phase 1 touches lint, so if @dg/config is published at execution time, the ESLint config extends it; otherwise write the local flat config and note it for later convergence. Non-blocking either way. |
| D7 — Hooks | No git hooks. Local gates run by the owner/agents are the only authoritative gate. AGENTS.md states it explicitly: "the headless local verification command (bun run verify) is the quality gate; hooks are local convenience." A repo with no hooks and a green local run is compliant. |
Deviations from the synthesis (declared, not silent)
- Runner: the dbuild delta block (§4) says "Seed vitest (Bun-compatible)"; the core D1 decision and §5 non-goals say Bun repos keep
bun:testand must not introduce vitest. Follow D1:bun:test. Rationale above; zero install, native TS +tsconfigpathssupport, fleet precedent. CI workflowRemoved by the 2026-09-05 revision: no workflow is created (D5); the Bun-vs-pnpm reusable-workflow mismatch that motivated this deviation is moot — the gate is the localbun run verifyentrypoint.- Package manager: Bun stays. Switching to the pnpm fleet default would mean regenerating the lockfile, re-verifying the GH Pages build chain, and retraining habits for zero test-quality gain — synthesis §5 explicitly lists dbuild among "stay Bun". Kept.
- No
packages/config: dbuild has no shared-config package (emptypackages/) — the fleet cross-constant doesn't apply;apps/web/tsconfig.jsonstays hand-maintained. Optional tightening (noUncheckedIndexedAccess) is deliberately not in scope: it would churn 9k LOC with no test payoff in v1. - Renovate: the
local>DimitriGilbertpreset reference requires the org preset to exist first (fleet-level task). If it doesn't at execution time, land the one-liner anyway and expect a Renovate warning until the preset lands — or temporarily extendconfig:recommendedand switch in a follow-up.
2. Test-runner decision in one line
bun test over src/lib/* pure logic in node env; no jsdom/RTL; coverage report-only. The testable core (grounded in the audit + read of the code):
apps/web/src/lib/seo.ts(375 LOC) — fully pure:formatCanonicalUrl,formatTitle,formatAbsoluteImageUrl,formatOpenGraphLocale,serializeJsonLd,parseTaxonomySlug/formatTaxonomySlug,create*JsonLd,createStaticPageMetadata,createNotFoundMetadata. Highest-value, zero-mock target.apps/web/src/lib/xml-sitemap.ts(58 LOC) — pure string builders (createUrlSet,createSitemapIndex);createXmlResponseuses the globalResponse, which Bun provides.apps/web/src/lib/blog.ts(598 LOC) — exportedslugify; module-private pure helpers that must be exported (behavior-preserving, mechanical):readString,readDateString,readStringArray,readFrontmatter,calculateReadTime,generateTOC,chooseCanonicalTagName. fs-backed getters (getAllPostsetc.) get one deterministic integration test over the in-repocontent/blogcorpus (55 files, static data — hermetic enough; assert structure, not exact counts).apps/web/src/lib/projects.ts(62 LOC) — reads in-repodata/projects.json: cheap deterministic tests.apps/web/src/lib/mindmap.ts(440 LOC) — fs + dagre heavy, single re-export surface: deferred to a later ratchet; not worth a refactor in v1.apps/web/scripts/validate-content.js— the de-facto content suite (audit: "a genuine validation program"); predicates are pure but the script exports nothing and self-invokes. Phase 3 splits it so it becomes testable and build-gated.
3. Phased plan
Orchestration model per the subagent-orchestration skill: every phase = 1 implementer dispatch → implementer runs gatekeeping commands → 1 validator dispatch (different agent; must read the code, not just run commands) → fix loop (fixer repairs ALL validator findings at once; re-validate; max 3 attempts) → phase done. All phases are sequential (each builds on the previous gate). Every implementer/fixer dispatch carries the NO-SLOP policy verbatim (no any/as any, no TODO/FIXME, no unused imports, import type for type-only imports, no dev servers — all of which AGENTS.md also mandates). No commits/pushes unless the owner asks; the fleet initiative's rule against destroying uncommitted work applies (the tree currently has uncommitted content/source edits — phases must build around them, never reset them).
Phase 0 — Resolve the Next.js two-major drift + workspace hygiene
Goal: one Next major in the lockfile; root stops shadowing the app's dependency versions; no npm in a Bun repo.
Tasks
package.json(root): delete thedependenciesblock entirely — it hoistsnext ^16.1.3,react ^19.2.3,react-dom ^19.2.3,@types/node ^25.0.9whileapps/webpinsnext 15.1.3/react ^19.0.3/@types/node ^22.19.7(two Next majors and two@types/nodemajors confirmed inbun.lock). Nothing at root imports them (root has no source; onlyturbodevDep +scripts/optimize-image.sh). Keep theresolutionsblock (correct place for Bun overrides). Optionally rename root package fromDbuild.devtodbuildto kill the root/app name collision (nothing references the root name).apps/web/package.json: fix thesetupscript —"setup": "bun run init && bun run setup-content && bun run validate-content"(currentlynpm run …in a Bun repo; audit P3).- From repo root:
bun installto regeneratebun.lock; verify exactly onenextversion (15.1.3) and one@types/nodeversion remain. - Do NOT touch AGENTS.md/CLAUDE.md here — the docs sweep is Phase 5 (one coherent edit instead of churn).
Orchestration: implementer → validator. Validator must read both package.json files, diff-check no other script referenced the removed root deps, and confirm lockfile state.
Validation gate (from repo root): bun install --frozen-lockfile → bun run check-types → bun run build; plus grep -o '"next@[0-9.]*"' bun.lock | sort -u yields exactly [email protected].
Effort: S (small, but it unblocks everything else — the lockfile must be sane before any gated run from a clean checkout with --frozen-lockfile).
Phase 1 — Implement ESLint 9 flat config; retire the dead next lint
Goal: lint runs, lints, and passes; the turbo lint task stops being dangling; the "no linting" claim in AGENTS.md becomes false in the good direction.
Tasks
- Install (repo root, Bun workspace):
bun add -d -w eslint@^9 @eslint/js@^9 typescript-eslint@^8 eslint-plugin-react-hooks@^5 globals - Create
apps/web/eslint.config.mjs(flat config):@eslint/jsrecommended +typescript-eslinttype-checked (recommendedTypeCheckedwithprojectService: true— feasible: single tsconfig atapps/web/tsconfig.json) +eslint-plugin-react-hooksrecommended (it's a React repo). Ignores:node_modules/,.next/,out/,next-env.d.ts,content/. Plain-JSscripts/*.jsgetjs.configs.recommendedif type-checked parsing of JS proves noisy — implementer's call, requirement is zero errors, zero rule disables without cause. apps/web/package.json: replace"lint": "next lint"with"lint": "eslint .".package.json(root): add"lint": "turbo lint"(the turbolinttask already exists — it becomes real).apps/web/next.config.mjs: keepeslint.ignoreDuringBuilds: truedeliberately — lint is a standalone gate step (inbun run verify), not a build-time side effect; add nothing.- If
@dg/config(D6) is published by execution time, extend its ESLint preset instead of hand-rolling; note the choice in the config header comment (comment rule: AGENTS.md bans comments in production code — a one-line provenance note in config files is acceptable, or omit). - AGENTS.md: replace the "No Linting/Formatting Config" section with the reality (ESLint 9 flat,
bun run lint, Prettier deliberately deferred in v1 — D3's "say so rather than pretend").
Orchestration: implementer → validator. Validator must read the config and spot-check that the rules actually fire (e.g. introduce nothing; verify ignores don't swallow src/), and enforce NO-SLOP.
Validation gate: bun run lint (root, via turbo) exits 0 with zero errors → bun run check-types → bun run build. Any pre-existing lint errors are fixed, not suppressed (AGENTS.md: never silence; fix the cause — same doctrine as the fleet).
Effort: M (type-checked lint over 9k LOC can surface a tail of real findings to fix).
Phase 2 — Seed bun:test over src/lib pure logic
Goal: the repo's first real test suite; headless one-command entry from root.
Tasks
apps/web/package.json: add"test": "bun test"and"test:coverage": "bun test --coverage". Rootpackage.json: add"test": "turbo test".turbo.json: add a"test"task (nodependsOnbuild; cacheable).apps/web/bunfig.toml: coverage report-only config ([test]coverage settings; reporters text; no thresholds — D2 Bun exception).- Create colocated suites (bun:test, node env, static imports only — AGENTS.md forbids
await import/require):src/lib/seo.test.ts— canonical URL formatting, title suffixing, OG locale mapping, taxonomy slug parse/format roundtrip,serializeJsonLdoutput, eachcreate*JsonLd/create*Metadatafactory's required fields (~25–35 cases).src/lib/xml-sitemap.test.ts— URL-set rendering, sitemap index,createXmlResponse(Bun's globalResponse).src/lib/blog.test.ts—slugify(assert current behavior, e.g. non-ASCII stripping, as regression pins); newly exportedgenerateTOC,calculateReadTime,readFrontmatter,chooseCanonicalTagName(make them named exports — behavior-preserving, mechanical diff insrc/lib/blog.ts); one integration test over the realcontent/blogcorpus viagetAllPosts(): every non-draft post has title/date/tags/summary — assert structure, never exact counts (content churns weekly).src/lib/projects.test.ts—getAllProjectsnon-empty,getProjectByIdhit/miss→null, tag filter, unique tag list.
- Explicitly no jsdom / no
@testing-library/*/ no component tests in v1 (the pick synthesis §5 requires; static site, zero DOM logic under test). - NO-SLOP applies doubly: AGENTS.md itself bans
any/as any— tests use real types from the source modules.
Orchestration: this is the one phase worth two sub-phases, each with its own validator, plus a mandatory phase-wide validator reading both together (skill Rule 3): (a) infra (scripts, bunfig, turbo) + seo + xml-sitemap; (b) blog (incl. the export refactor) + projects + corpus integration. Phase-wide validator checks: import coherence, no duplicated fixtures, coverage config sane.
Validation gate: bun run test (root) green; bun run test:coverage produces a report (record the numbers, gate nothing); bun run check-types → bun run lint → bun run build.
Effort: M.
Phase 3 — Gate validate-content into the build; make it testable
Goal: invalid frontmatter can no longer ship (audit P2: validator is manual-only today, wired only into setup).
Tasks
- Split
apps/web/scripts/validate-content.js(behavior-preserving): extract the pure predicates/collectors —hasValue,readString,isDraft,isValidDate,isStringArray,isCategoryIndex,normalizeTranslationCandidate,hasFrenchSignals,likelyTranslatedContent,getTranslationCandidate,buildTranslationCandidateCounts,validatePost— intoapps/web/scripts/lib/content-rules.js(ESM static exports; norequire, noawait import).validate-content.jskeeps the fs walk, console output, andprocess.exitCodesemantics byte-identical (the build gate depends on the exit code). - Wire the build:
apps/web/package.json"build": "node scripts/validate-content.js && node scripts/generate-rss.js && next build"— validate first, so bad frontmatter fails before RSS is generated from it. - Create
apps/web/scripts/lib/content-rules.test.js(bun:test, plain JS): draft skip, category_indexwarnings-vs-errors, invalid date, tags array rule, empty body, French-heuristic detection, duplicate-translation-candidate ⇒translationKeywarning,ogImageAltwarning — table-driven against synthetic frontmatter objects (no fs). - Contingency: the live corpus has uncommitted edits; if
validate-contentfails on it during implementation, the implementer must stop and report — never weaken the validator or "fix" content silently. That's a real finding for the owner. - Add
bun run validate-contentto the AGENTS.md web-commands list (Phase 5 reconciles fully).
Orchestration: implementer → validator. Validator diffs old vs new script output on the real corpus (must be identical) and reads the new tests for tautologies (DoD item 9).
Validation gate: bun run validate-content exits 0 on the current corpus → bun test green → bun run build (now content-gated) → bun run check-types.
Effort: S.
Phase 4 — Local gate composition: bun run verify
Goal: DoD 2 under D5 (no CI by design): typecheck + lint + test + build become one headless root command the owner and agents run before any phase is declared done.
Tasks
- Root
package.json: add"verify": "bun run lint && bun run check-types && bun run test && bun run build"— the headless verification entrypoint (D5). Phases 0–3 already landed each ingredient; this composes them. - AGENTS.md: document
bun run verifyas the pre-deploy and pre-phase-done gate. - No
.github/directory is created and no workflow file is written anywhere (D5 — owner decision 2026-09-05).
Orchestration: implementer → validator. Validator reads the root package.json, confirms the chain is exactly lint → check-types → test → build, then runs bun run verify from a clean-install state; fail-fast proven once (deliberate failing test in a scratch copy → non-zero exit at the test step).
Validation gate: bun run verify exits 0 from repo root; every command in the chain exists in package.json files.
Effort: S.
Phase 5 — Dependency hygiene, quality report, docs truth
Goal: DoD items 5, 7, 8 closed; documentation stops contradicting reality.
Tasks
renovate.json(repo root):{"$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["local>DimitriGilbert"]}(deviation D5-5 ordering note applies). dbuild has no"latest"pins today — Renovate keeps it that way and watches thenext15→16 upgrade path deliberately instead of by lockfile accident.- Quality report (report-only, on demand — D4):
bun add -d -w knip jscpd; add.jscpd.jsonwith the launch-mommy canonical config (minLines: 8,minTokens: 80,exitCode: 0, exclude tests/generated — here:.next,out,content); minimalknip.jsonfor the Next app. Root scripts"quality:knip"/"quality:jscpd"write reports to a git-ignored local directory; run on demand by the owner or during a periodic agent pass — no workflow, no schedule, no artifacts (D5). - Coverage baseline: run
bun run test:coverage, record measured lines/branches in AGENTS.md's Testing section as the ratchet baseline (ratchet +5 whenever Bun's tooling becomes threshold-grade; never lower). - Docs reconciliation (audit P3, DoD item 5):
AGENTS.md:[email protected](says 1.2.15); Testing section = runner (bun test), locations (src/lib/*.test.ts,scripts/lib/*.test.js), commands (incl.bun run verify), coverage baseline + report-only status, the D7 sentence ("the headless local verification command (bun run verify) is the quality gate, run by the owner/agents; hooks are local convenience"); Package Management section consistent with the new scripts.CLAUDE.md: fix "No linting or type checking commands defined at root level" (false — rootcheck-types/lintexist via turbo), drop the stalebun run lintimplication, or better: reduce CLAUDE.md to a pointer at AGENTS.md to end the two-file drift permanently (owner's preference wins if they want both files).
- Optional (only if frictionless): adopt
@dg/configtsconfig/vitest presets per D6 — explicitly skippable; never a churn PR for its own sake.
Orchestration: implementer → validator. Validator greps both docs for every claim and verifies each against the repo (the exact failure mode this phase exists to kill: phantom docs).
Validation gate: full chain from root — bun install --frozen-lockfile && bun run verify && bun run validate-content; knip and jscpd run exit 0 (report-only) and produce their local reports.
Effort: M.
4. Alignment Definition of Done — per-item status for dbuild
| # | DoD item (synthesis §3) | Current status | Closed by |
|---|---|---|---|
| 1 | Root-level headless test command, documented in AGENTS.md | Missing — no test script anywhere |
Phase 2 + 5 |
| 2 | Headless verification entrypoint runs typecheck + test + lint in one command; agent records green before a phase is done | Missing — no root aggregate command | Phase 4 (bun run verify) |
| 3 | Coverage measured & reported; baseline recorded; no threshold ever lowered | Missing — no runner (Bun exception: report-only, no thresholds) | Phase 2 + 5 |
| 4 | Lint installed, zero errors, wired into the headless entrypoint; dead turbo lint task implemented or deleted |
Dead — next lint with no ESLint dep/config; turbo lint dangling; ignoreDuringBuilds: true |
Phase 1 + 4 |
| 5 | AGENTS.md "Testing" section matches reality; no phantom docs | Partial — honest about absence, but stale ([email protected], npm run in setup, CLAUDE.md false claims) |
Phase 5 |
| 6 | Dead scaffold test deps removed or used | Met (vacuously) — no RTL/jsdom scaffold deps present; nothing to remove | n/a (Phase 2 keeps it true: no DOM deps added) |
| 7 | Renovate one-liner; no "latest" deps |
Half — no "latest" pins exist; no Renovate config |
Phase 5 |
| 8 | Quality-report script (knip + jscpd) runnable locally, report-only | Missing | Phase 5 |
| 9 | Nothing satisfied by a fake pass (no passWithNoTests, no echo-tests, no tautologies) |
Met today (nothing exists to fake); enforced going forward by validators reading every test | All phases |
5. Risks & repo-specific notes
- Static-site scope discipline. No e2e, no component tests, no jsdom/RTL in v1 — value is pure-logic tests + content gating + the local build gate. Resist scope creep toward Playwright; the site has no backend to integration-test.
- GH Pages deploy coupling. Deploys are manual
gh-pagespushes from the owner's machine — that does not change under D5. The contract becomes: a greenbun run verifyimmediately before deploy ⇒ the deploy is safe. Risk: the deployed site drifts frommainif deploys lag — acceptable for a personal site; keep the pre-deploy verify habit documented in AGENTS.md. - Content corpus is live data. 55 markdown files, edited constantly (uncommitted edits in the tree right now). Corpus-integration tests assert structure, never counts; the Phase 3 build gate makes the validator an owner-facing contract — if it fails on real content, that's reported, not papered over.
- Bun coverage is experimental — report-only forever until Bun's reporter is threshold-grade (synthesis §5); do not let a later PR add thresholds on top of misreportable numbers.
- Export-refactor risk (blog.ts, Phase 2) is mechanical but touches a 598-line file with uncommitted edits — implementer must make the minimal diff (add
exportto existing pure functions, change nothing else) and the validator must diff behavior. validate-content.jsexit-code semantics are load-bearing once Phase 3 wires it intobuild— the refactor must not change output format or exit codes.- AGENTS.md hard bans (
await import,require,any) bind all test code too; bun:test + static imports comply naturally. - Uncommitted work in the tree: all phases proceed on top of it; no stashing/resetting (fleet rule). Suggest the owner land or set aside current edits before Phase 0 touches the lockfile —
bun installwill rewritebun.lock, which already has uncommitted modifications.
6. Effort summary
| Phase | Goal | Effort |
|---|---|---|
| 0 | Next-drift resolution + workspace hygiene | S |
| 1 | ESLint 9 flat config; kill dead next lint |
M |
| 2 | Seed bun:test over src/lib (2 sub-phases + phase-wide validation) |
M |
| 3 | validate-content build-gated + unit-tested |
S |
| 4 | Local gate composition (bun run verify) |
S |
| 5 | Renovate + quality report + docs truth | M |
Total: ~2 focused days of implementer/validator cycles. Sequencing rule: no phase starts before the previous gate is green; every gate is runnable from the repo root with the commands listed above.
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Getting set up
We have not checked this project's setup files yet. Start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Similar issues
-
needs:triage
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
ai-discovered
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
jessepollak/home#1627 ·
Maintainers usually reply within 1 day
-
agent-canvas bug llm priority:low ready-for-dev
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
OpenHands/OpenHands#17806 · 3 comments ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
radius-project/ai-extensions#923 ·
Maintainers usually reply within 1 day