Repository navigation
Weekly tech debt audit: dispatch - 2026-07-15 #616
Description
Weekly tech debt audit: dispatch - 2026-07-15
Repo health snapshot
- Open issues: 1 (Renovate Dashboard, Renovate Dashboard 🤖 #14)
- Open PRs: 1 (feat(deps)!: Update dependency typescript (6.0.3 → 7.0.2) - autoclosed #598 — TypeScript 7.0.2 upgrade, blocked by CI/lint failures)
- Merged PRs (30 days): 30 (14 Renovate deps, 6 fix/features, 6 CI digests, 3 releases, 1 lockfile maintenance)
- Latest release: v0.5.25
- CI: Main branch green; PR feat(deps)!: Update dependency typescript (6.0.3 → 7.0.2) - autoclosed #598 has Build, Lint, and Docker Build failures
- Test suite: 1913 tests total, 43 failures (11 failing test files)
Met findings
Test health — CI gap
The test suite has 43 known failures across 11 test files, including unit tests for github.ts, version.ts, the Dockerfile, the PR follow-up webhook, and 7 component test files (layout.tsx, auth-controls, github-link, issue-card, kanban-board, kanban-column, login/page). The component failures are pre-existing (layout rendering, Tailwind CSS class mismatch, event handling). The unit failures are caused by a missing @vitest-environment node directive on 4 test files that use node:* built-in modules — they run under the jsdom environment by default and fail with "No such built-in module: node:".
These failures are silent in CI because the test job evades detection — work to make CI surface them has not been completed. The public test count (1913 total, 1870 passed) does not break CI builds.
Evidence:
src/lib/github.test.ts— importsnode:crypto, no@vitest-environment nodesrc/lib/version.test.ts— importsnode:fsviaversion.ts, no directiveDockerfile.test.ts— importsnode:fs,node:path, no directivesrc/app/api/pr-followup/webhook/route.test.ts— importsnode:cryptoviaprisma.ts, no directive- 7 component test files fail under jsdom:
layout.test.tsx,auth-controls.test.tsx,github-link.test.tsx,issue-card.test.tsx,kanban-board.test.tsx,kanban-column.test.tsx,login/page.test.tsx
Relevant files (from merge #581 / commit de9a896):
src/lib/github.test.ts(line 1 — missing directive)src/lib/version.test.ts(line 1 — missing directive)Dockerfile.test.ts(line 1 — missing directive)src/app/api/pr-followup/webhook/route.test.ts(line 1 — missing directive)
Acceptance: All 1913+ tests pass in a single vitest run without environment errors.
Rate limiter is in-memory only
The rate limiter in src/lib/rate-limit.ts is a module-level Map<string, WindowEntry> with no persistence. A process restart resets all rate limits. The limiter uses a fixed-window algorithm (not sliding window), which allows burst traffic at window boundaries. Expired key pruning only occurs when the map exceeds 1000 keys.
Evidence:
src/lib/rate-limit.tsline 44:const windows = new Map<string, WindowEntry>();src/lib/rate-limit.tsline 56: simple fixed-window check:if (entry.count < limit)src/lib/rate-limit.tsline 82:PRUNE_THRESHOLD = 1000— no timer-based sweep
Acceptance: Document in accepted-risks.md as a single-node design tradeoff, or add a DB-backed sliding-window rate limiter.
Large module: src/lib/github.ts (1049 lines)
The GitHub client module now spans 1049 lines and mixes multiple responsibilities: GitHub App JWT auth, token management, issue CRUD, PR queries, CI job log fetching, code search, repository metadata, and check-run queries. This grew organically as new features were added (pr-followup CI logs, groomer features, automation sync). The module has one any-typed helper (fetchRepoJson returning Promise<any>) and a manual redirect-follow for log blob fetching.
Evidence:
src/lib/github.ts— 1049 lines totalsrc/lib/github.tsline 529:async function fetchRepoJson(repoFullName: string, errorPrefix: string): Promise<any>- Responsibilities: JWT signing (lines 34–100), token caching (lines 14–23), issue/PR CRUD (lines 150–500+), repo metadata (lines 987–1005), CI logs (lines 808–878), code search (lines 1006–1049)
Acceptance: Split into domain-specific files (e.g., github-auth.ts, github-issues.ts, github-ci.ts), or at minimum type the fetchRepoJson return with Record<string, unknown>.
projects/page.tsx uses pervasive as any casts
The projects page uses untyped any casts throughout the issue mapping and rendering, bypassing TypeScript strict mode checks.
Evidence:
src/app/projects/page.tsxline 76:(i: any) => getProjectIssueStatus(i as any) === column.idsrc/app/projects/page.tsxline 87:(statusIssues as any).map((issue: any) => (
Acceptance: Define and use the proper issue type instead of any.
Pending TypeScript 7 upgrade (PR #598)
PR #598 attempts to upgrade from TypeScript 6.0.3 to 7.0.2 but is blocked: GitHub releases don't show v7.0.2 as published, Build and Lint CI checks fail, and the AI reviewer requested changes. This PR has been stale for 7 days.
Evidence:
gh pr view 598 --repo misospace/dispatch— mergeStateStatus: BLOCKED, reviewDecision: CHANGES_REQUESTED- CI: Build FAILURE, Lint FAILURE, Docker Build FAILURE, Typecheck SUCCESS, Tests SUCCESS
- Dependencies modified:
package.json(typescript 6.0.3 → 7.0.2),package-lock.json(+349/-8)
Acceptance: Either resolve the CI failures and verify the TypeScript v7 release exists, or close the PR with a note that v7 is not yet available on npm/GitHub Releases.
Groomer LLM prompt embedded as comments
The groomer LLM system prompt lives as a multi-line TypeScript comment string in src/lib/groomer/llm.ts rather than a dedicated prompt file or template. This makes prompt versioning, diffing, and review harder than necessary.
Evidence:
src/lib/groomer/llm.tslines 18–205: multi-line template literal assigned toSYSTEM_PROMPTconstant- No prompt version or template reference in the module exports
Acceptance: Extract the system prompt to a standalone file (e.g., src/lib/groomer/prompts/classify.ts) and reference it by path.
Unanalyzed: long-running webhook handler path
The PR follow-up webhook handler (src/app/api/pr-followup/webhook/route.ts) processes pull_request_review, pull_request, and check_run events sequentially, calling multiple GitHub API and Dispatch API endpoints per event. There's no timeout guard per sub-operation, and a single slow GitHub API call could hold the webhook response past typical HTTP gateway timeouts (10–30s).
Evidence:
src/app/api/pr-followup/webhook/route.ts— no per-operation timeout wrapper- The main handler calls
ingestPrFollowupEvent, which performs GitHub API fetches and Dispatch internal mutations
Acceptance: Add per-operation timeouts or kick off async processing with an immediate 202 response.
Recommended Issue Breakdown
[P0] Fix 43 test failures across 11 test files
Problem: 43 tests fail in vitest run but do not break CI. Four unit test files are missing @vitest-environment node directives and fail with "No such built-in module: node:" errors. Seven component tests fail under jsdom due to rendering/styling/environment mismatches. This creates a false sense of test health — CI does not catch regressions in these files.
Evidence:
npx vitest runoutputs: "Test Files 11 failed | 103 passed | Tests 43 failed | 1870 passed"- Missing directive files:
src/lib/github.test.ts,src/lib/version.test.ts,Dockerfile.test.ts,src/app/api/pr-followup/webhook/route.test.ts - Failing component files:
layout.test.tsx,auth-controls.test.tsx,github-link.test.tsx,issue-card.test.tsx,kanban-board.test.tsx,kanban-column.test.tsx,login/page.test.tsx
Acceptance: All 1913+ tests pass in npx vitest run. CI includes the full test suite in its success/failure check.
[P1] Extract and type the GitHub client module
Problem: src/lib/github.ts is 1049 lines spanning JWT auth, issue CRUD, PR queries, CI log fetching, code search, and repo metadata. One helper (fetchRepoJson) returns Promise<any>. Module bloat makes it hard to reason about, test in isolation, and change without risk.
Evidence:
src/lib/github.ts— 1049 lines- Line 529:
async function fetchRepoJson(repoFullName: string, errorPrefix: string): Promise<any> - Mixed responsibilities: auth setup (lines 14–100), issue fetch (lines 150–250), PR logic (lines 260–520), log fetching (lines 808–878), code search (lines 1006–1049)
Acceptance: Split into github-auth.ts, github-issues.ts, github-ci.ts, github-code-search.ts (or equivalent domain files). fetchRepoJson typed with Record<string, unknown> instead of any. Each new file under 400 lines.
[P1] Resolve stale PR #598 (TypeScript 7.0.2 upgrade)
Problem: PR #598 (TypeScript 6.0.3 → 7.0.2) has blocked CI (Build, Lint, Docker Build fail), changes requested by AI reviewer, and references a version (v7.0.2) not confirmed published on GitHub Releases. The PR has been untouched for 7 days.
Evidence:
gh pr view 598 --repo misospace/dispatch— mergeStateStatus: BLOCKED, reviewDecision: CHANGES_REQUESTED- CI: Build FAILURE, Lint FAILURE, Docker Build FAILURE
- GitHub Releases API shows no v7.0.2 tag for microsoft/TypeScript
Acceptance: Either the PR is fixed and merged, or closed with a documented reason. The Renovate dashboard is updated to skip v7 if it's not yet available.
[P2] Remove pervasive as any casts in projects page
Problem: src/app/projects/page.tsx uses untyped any casts for issue iteration and status projection, bypassing TypeScript strict mode on that component.
Evidence:
src/app/projects/page.tsxline 76:(i: any) => getProjectIssueStatus(i as any) === column.idsrc/app/projects/page.tsxline 87:(statusIssues as any).map((issue: any) => (
Acceptance: Replace all any references with a properly typed IssueWithRepo or comparable interface, verified by npx tsc --noEmit.
[P2] Extract groomer LLM prompts to dedicated files
Problem: The groomer's LLM system prompt is embedded as a 188-line string in src/lib/groomer/llm.ts, making prompt versioning, diff review, and A/B testing harder than necessary.
Evidence:
src/lib/groomer/llm.tslines 18–205:const SYSTEM_PROMPT = ...template literal
Acceptance: Move the prompt content to a dedicated file under src/lib/groomer/prompts/ and reference it from llm.ts. Prompt content should be reviewable as a diff when changed.
[P3] Document in-memory rate limiter tradeoff
Problem: The rate limiter (src/lib/rate-limit.ts) is an in-memory fixed-window Map with no persistence, meaning a process restart resets all limits. This is acceptable for a single-node internal ops tool but not documented as an architectural tradeoff.
Evidence:
src/lib/rate-limit.tsline 44:const windows = new Map<string, WindowEntry>();src/lib/rate-limit.tsline 56: fixed-window check (allows burst at boundary)
Acceptance: Mention the single-node, in-memory rate-limiter tradeoff in docs/accepted-risks.md alongside the existing entries.
Decomposed into
- Fix 43 test failures across 11 test files #617 — Fix 43 test failures across 11 test files
- Extract and type the GitHub client module #618 — Extract and type the GitHub client module
- Resolve stale PR #598 (TypeScript 7.0.2 upgrade) #619 — Resolve stale PR feat(deps)!: Update dependency typescript (6.0.3 → 7.0.2) - autoclosed #598 (TypeScript 7.0.2 upgrade)
- Remove pervasive as any casts in projects page #620 — Remove pervasive as any casts in projects page
- Extract groomer LLM prompts to dedicated files #621 — Extract groomer LLM prompts to dedicated files
- Document in-memory rate limiter tradeoff #622 — Document in-memory rate limiter tradeoff
Activity
itsmiso-ai commentedon Jul 23, 2026
Already decomposed into #617, #618, #619, #620, #621, #622.
Closing as retired-workflow artifact — the umbrella + decomposer pipeline was replaced on 2026-07-16 with direct issue creation per finding.