Add Spec Kit, linting, formatting, coverage and a live sweep - #26
Merged
Merged
Conversation
Three things this repo had no way to catch, and now does.
**Spec Kit.** Design decisions were living in conversations and pull request
descriptions, where they are unfindable a month later. `.specify/` carries the
templates and `specs/` will carry the decisions. The constitution in
`.specify/memory/constitution.md` is not aspirational -- every principle is
written from a failure this repo actually had, or a boundary the team has
already decided (Reactome data through the public services; no Neo4j from a
deployed instance; analysis runs in the Analysis Service).
The Spec Kit skills under `.claude/skills/` are tracked deliberately. People
clone this repo and point an agent at it, so the workflow should come with it;
`.gitignore` now excludes only machine-local settings instead of all of
`.claude/`.
**Static analysis.** ESLint with type-aware rules, and Prettier. The unsafe-*
rules are warnings -- the honest local type for an unpinned Reactome field is
`any`, and a fixture test is the real guard against bad shapes. The rules that
catch silent wrongness are errors. Two genuine bugs fell out:
- neo4j.ts handed an async function to `process.once`, so a failed driver
close rejected with nobody listening -- an unhandled rejection on shutdown.
- http.ts rethrew `lastError`, which is `unknown`, so a non-Error rejection
propagated as something callers could not read `.message` off.
`require-await` is off by decision: MCP handlers are declared async uniformly
so every tool in a file has the same signature.
**Typechecking that covers the tests.** `tests/` was in no tsconfig at all, so
nothing checked it. `tsconfig.eslint.json` includes tests and config files;
`npm run typecheck` now uses it. Turning on `noUncheckedIndexedAccess` found
three unguarded array reads in src -- the same class as the field-path bugs it
follows. Tests get `textOf()` and `calledUrl()` helpers so the flag does not
just become a field of `!`.
**Coverage.** 44% statements, reported by `npm run test:coverage`. Thresholds
are set just under that as a ratchet, not a target.
**A live sweep.** `npm run sweep` calls all 53 tools against the live services
and flags output containing `undefined`, `[object Object]`, or nothing at all.
Unit tests pin shapes we already know; only this finds a tool that never worked.
It found one immediately: `search_diagram` shared the grouped-results helper,
but `/search/diagram/{id}` returns a flat `entries` array, so the tool threw
"result.results is not iterable" on every call. Fixed and pinned.
It runs weekly and on demand rather than in CI, because a red run there can
mean Reactome changed rather than this repo did.
**CI** now runs lint, format check, typecheck, build and coverage. The build
was never run in CI before. `npm audit` runs advisory-only in its own job, so a
new advisory against a transitive dev dependency does not block an unrelated PR.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Formatting only -- no behaviour change. Kept separate so it can be skipped in review and in blame, per the constitution's rule that broad reformatting rides with substantive change or not at all. Scope is the code this repo owns. The vendored Spec Kit assets under .specify/ and .claude/ are excluded so that a `specify` upgrade does not become a conflict, and markdown is excluded because reformatting prose churns documentation for no readability gain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 14, 2026
This was referenced Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three things this repo had no way to catch, and now does.
Spec Kit
Design decisions were living in conversations and PR descriptions, where they are unfindable a month later.
.specify/carries the templates;specs/will carry the decisions.The constitution (
.specify/memory/constitution.md) is not aspirational — every principle is written from a failure this repo actually had, or a boundary the team has already decided:The Spec Kit skills under
.claude/skills/are tracked deliberately — people clone this repo and point an agent at it, so the workflow should come with it..gitignorenow excludes only machine-local settings rather than all of.claude/.Static analysis
ESLint with type-aware rules, plus Prettier. The
no-unsafe-*rules are warnings — the honest local type for an unpinned Reactome field isany, and a fixture test is the real guard against bad shapes. The rules that catch silent wrongness are errors.Two genuine bugs fell out:
neo4j.tshanded anasyncfunction toprocess.once, so a failed driver close rejected with nobody listening — an unhandled rejection on shutdown.http.tsrethrewlastError, which isunknown, so a non-Errorrejection propagated as something callers could not read.messageoff.require-awaitis off by decision: MCP handlers are declaredasyncuniformly so every tool in a file has the same signature.Typechecking that covers the tests
tests/was in no tsconfig at all, so nothing checked it.tsconfig.eslint.jsonincludes tests and config files, andnpm run typechecknow uses it.Turning on
noUncheckedIndexedAccessfound three unguarded array reads insrc/— the same class as the field-path bugs in #25. Tests gettextOf()andcalledUrl()helpers so the flag does not just become a field of!.Coverage
44% statements, via
npm run test:coverage. Thresholds are set just under that as a ratchet, not a target — most tools still have no test.A live sweep
npm run sweepcalls all 53 tools against the live services and flags output containingundefined,[object Object], or nothing at all. Unit tests pin shapes we already know about; only this finds a tool that never worked.It found one immediately:
search_diagramshared the grouped-results helper, but/search/diagram/{id}returns a flatentriesarray — so the tool threwresult.results is not iterableon every call. It had never returned an answer. Fixed and pinned.It runs weekly and on demand rather than in CI, because a red run there can mean Reactome changed rather than this repo did.
CI
Now runs lint, format check, typecheck, build and coverage. The build was never run in CI before.
npm auditruns advisory-only in its own job, so a new advisory against a transitive dev dependency does not block an unrelated PR.Note on the lockfile
package-lock.jsonis regenerated. The committed one was stale enough thatnpm auditreported 8 vulnerabilities (5 high); a fresh resolve within the same semver ranges reports 0.npm cifrom the new lockfile passes the full check.Verification
npm cifrom a clean tree, thennpm run check(lint → format → typecheck → build → test): passesnpm audit: 0 vulnerabilitiesThe Prettier pass is its own commit (
d58da7c) and is listed in.git-blame-ignore-revs, so it can be skipped in review and in blame.🤖 Generated with Claude Code