Compact guidance for AI assistants working in this repository.
- vers-js — Runtime-agnostic TypeScript library for parsing and validating VERS (VErsion Range Specifier) declarations.
- Current scope is v0.1.0: canonical syntax validation, parsed declaration metadata, and canonical string projection. It is a conforming parser, not a full semantic engine.
- Apache 2.0 licensed, owned by Windlass.
- Runtime: Node.js 22 LTS for development only. The published library must remain runtime-agnostic (Node.js, Deno, Bun).
- Package manager: pnpm 11.17.0, pinned via
devEngines.packageManagerinpackage.json. - Language: TypeScript 7 (ES2023 target, ESM-only,
"moduleResolution": "Bundler"). - Lockfile:
pnpm-lock.yaml(frozen installs in CI viapnpm ci). - Editor (VS Code): Install and enable the official TypeScript 7 VS Code extension
(
TypeScriptTeam.native-preview) to use the TypeScript 7 native language server. Repository builds and type checks remain authoritative via the workspacetscthrough pnpm scripts. - Toolchain (decided, see ADR-0036 through ADR-0040, ADR-0049, and ADR-0053):
- Build:
tsconly — no bundler. Build script:tsc -p tsconfig.build.json. - Typecheck:
tsc --noEmit(authoritative; Oxlint does not replace this). - Test: Vitest. Run:
vitest run. Watch:vitest. - Lint: Oxlint with type-aware linting (configured in
.oxlintrc.json). - Format: Oxfmt (configured in
.oxfmtrc.json). - Markdown lint:
markdownlint-cli2(configured in.markdownlint-cli2.jsonc).
- Build:
Use the package.json scripts rather than bare CLI invocations. The scripts include flags that
match CI:
# Linting and formatting
pnpm run lint:md # markdownlint-cli2
pnpm run lint:md:fix # markdownlint-cli2 --fix (also run by lefthook)
pnpm run lint:ts # oxlint (reads .oxlintrc.json)
pnpm run lint:ts:fix # oxlint --fix (also run by lefthook)
pnpm run lint:ts:github # oxlint with GitHub Actions format
pnpm run fmt # oxfmt (also run by lefthook)
pnpm run fmt:check # oxfmt --check
# Type checking and build
pnpm run typecheck # tsc --noEmit
pnpm run build # tsc -p tsconfig.build.json
# Testing
pnpm run test # vitest run
pnpm run test:watch # vitest
pnpm run test:coverage # vitest run --coverage
# Package verification (requires build first; scripts run it for you)
pnpm run test:package # verify dist/ artifacts and package.json exports
pnpm run typecheck:package # type-check a package consumer
pnpm run typecheck:package:blocked # verify subpath imports are blocked
pnpm run smoke:package # runtime smoke test using the package name
pnpm run verify:package # all of the above in order
# Runtime smoke testing (requires build first)
pnpm run smoke:runtime:node # built package smoke under Node.js
pnpm run smoke:runtime:deno # built package smoke under Deno
pnpm run smoke:runtime:bun # built package smoke under Bun
pnpm run smoke:runtime # all three runtimes
pnpm run verify:runtime # build then run all runtime smoke testsCI uses pnpm ci for reproducible installs. For local development, pnpm install is fine.
lefthook.yml enforces auto-fix on commit:
lint:md:fix— auto-fix markdown issues and stagelint:ts:fix— auto-fix TS lint issues and stagefmt— run Oxfmt and stage
Commit-msg hook enforces DCO sign-off. If you bypass hooks, CI will still catch violations.
This project follows SDD methodology. Do not implement before reading the specs.
- ADRs first (
docs/decisions/): understand why architecture was chosen - Specs second (
docs/architecture/): define exact observable behavior - Implementation third: build against the specifications
Start with docs/architecture/index.md when returning to the project.
When drafting or reviewing architecture specs, follow this sequence:
scope-and-invariants.md— boundary firstpublic-api.md— callable surfacedata-model-and-canonical-output.md— success valuescharacter-encoding.md— input processing rulesparser-phases.md— execution orderdiagnostics.md— failure valuesfixtures.md— test expectationsresource-limits.md— resource boundariesbuild-and-test.md— scaffolding and verification- Add new documents as needed.
These invariants are defined in docs/architecture/scope-and-invariants.md and
docs/architecture/public-api.md. Agents must not violate them when proposing code changes:
- Public API is fixed: Only
parseVers(),validateVers(), andcanonicalizeVers()are public. Each accepts exactly onestringargument. Non-string input must throwTypeError. Malformed input returnsResultfailures, never repaired output. - No parser internals in public results: Public results must not expose tokens, scanner state, parser nodes, mutable state, or runtime-specific objects.
- ESM-only, root-only: Package consumers import only from
"@windlass/vers-js". No subpath imports (@windlass/vers-js/parser,@windlass/vers-js/errors). No CommonJS artifact. No default export. - Strict canonical validation: All public functions validate canonical VERS syntax. They do not trim whitespace, change casing, rewrite separators, reorder constraints, deduplicate, or repair percent escapes.
- Type validation is syntax-only: The parser validates
typecharacters and lowercase casing. It must not reject unknown types (e.g.,support.unknown_typeis reserved, not active). - Constraint order preserved: The parser preserves input constraint order. It does not sort, simplify, or normalize for containment.
- Error-only, no warnings: Public functions do not accept warning, advisory, loose, repair, recovery, or coercion modes. Successful results carry no warnings.
- Issue codes are machine-readable:
VersIssue.codeuses the core issue-code union. Human-readablemessagefields are convenience text, not the stable contract.
When implementing or validating changes, run checks in this order:
pnpm run fmt:check— formattingpnpm run lint:ts— linting (type-aware via.oxlintrc.json)pnpm run lint:md— markdown lintingpnpm run typecheck— type-checking (authoritative)pnpm run test— tests (unit, parser, fixture, diagnostic, resource, package-boundary)pnpm run test:coverage— coverage (also exercised in CI)pnpm run build— package buildpnpm run verify:package— package artifact, consumer type, blocked subpath, and package-name smoke checkspnpm run verify:runtime— built package smoke under Node.js, Deno, and Bun- Windlass supply-chain checks (Scorecard, OSV Scanner, Dependency Review)
Independent checks may be reordered for CI speed, but release readiness requires all to pass.
| Layer | Purpose |
|---|---|
| Unit | Small parser helpers with internal contracts |
| Parser success | Successful parseVers, validateVers, canonicalizeVers behavior |
| Official fixtures | Pinned upstream vers_canonical_parse_test.json through local disposition table |
| Project diagnostic fixtures | Active issue codes, severity, spans, fatality, ordering, metadata |
| Resource boundary | Input length (1024/1025 UTF-16 code-unit boundary), issue cap (16), truncation metadata |
| Package boundary | Root exports, declaration metadata, default export, blocked subpaths |
| Runtime smoke | Built package under Node.js, Deno, Bun |
Tests must not assert exact human-readable diagnostic message strings. They may assert that messages are non-empty strings.
- ADRs: Use MADR 4.0.0 format. Store in
docs/decisions/with sequential numbering (0001-title.md). - ADR immutability: Existing accepted ADRs are immutable. Never edit the body of an accepted ADR
after the fact. The only permitted post-acceptance change is updating the
statusfield (e.g., tosuperseded,deprecated). If a decision changes, write a new ADR rather than rewriting history. - Dates in documents: Use Holocene Era / Human Era year format (e.g.,
12026-06-07). - Bilingual README updates: When editing any
README.md, update the correspondingREADME.ko.mdin the same directory as part of the same change. - Dependency policy:
minimumReleaseAgecooldown configured inpnpm-workspace.yaml(4320 minutes / 3 days). Security updates bypass cooldown per Dependabot config. - CodeGraph MCP:
opencode.jsoncconfigures a local CodeGraph MCP server. Other AI tool configs (.cursor/,.claude/,.kiro/,.gemini/) also reference CodeGraph.
- DCO sign-off required: Every commit must include a
Signed-off-by:line. Usegit commit -s(orgit commit --signoff) for all commits. Lefthook enforces this in the commit-msg hook.
- Maintain
CHANGELOG.mdaccording to Keep a Changelog 2.0.0, but use the organization's Human Era date convention for release headings (for example,## [0.1.0] - 12026-06-13). - Changelog entries are for users and downstream integrators. Summarize notable upgrade-relevant behavior; do not generate changelog entries by dumping commit logs.
- For every PR, complete the organization PR template's
Changelogsection with:- Category:
Added,Changed,Deprecated,Removed,Fixed,Security, orNone - User-facing note: a short impact summary, or why no note is needed
- Category:
- Use
Nonefor changes with no direct user-facing impact, such as test cleanup, internal refactoring, formatting, CI-only maintenance, or routine development toolchain pin bumps (for exampledevEngines.packageManagerand devDependency updates). - Record a development toolchain bump only when it changes build-from-source requirements (for
example a package-manager major upgrade that changes the lockfile format) or when it is
security-driven; use the category that matches the effect (
ChangedorSecurity). - During development, update only the
[Unreleased]section when a PR has user-facing impact. Group entries byAdded,Changed,Deprecated,Removed,Fixed, andSecurity; do not create empty category sections. - For release PRs, move
[Unreleased]entries into the new version section, recreate an empty[Unreleased]section at the top, update comparison links at the bottom ofCHANGELOG.md, and use the finalized version section as the GitHub Release body.
- The canonical maintainer release runbook is
docs/release.md. Keep signed tag, npm Trusted Publishing, provenance, and GitHub Release process details there rather than duplicating the full workflow in README or AGENTS notes.
- Reusable workflows from
windlasstech/.github:- Scorecard supply-chain security
- OSV Scanner (full scan on schedule + push to main; PR scan on PRs + merge groups)
- Dependency Review (on PRs + merge groups)
- Do not add build/test CI that bypasses these security checks.
- Always reference
windlasstech/.githubmain branch security docs before making security-relevant changes:- Primary security policy: https://raw.githubusercontent.com/windlasstech/.github/refs/heads/main/SECURITY.md
- Artifact attestations: https://raw.githubusercontent.com/windlasstech/.github/refs/heads/main/docs/security/artifact-attestations.md
- Dependency security: https://raw.githubusercontent.com/windlasstech/.github/refs/heads/main/docs/security/dependency-security.md
- SLSA compliance framework: https://raw.githubusercontent.com/windlasstech/.github/refs/heads/main/docs/security/slsa-compliance-framework.md
- Workflow hardening: https://raw.githubusercontent.com/windlasstech/.github/refs/heads/main/docs/security/workflow-hardening.md
- Supply-chain baseline from the organization policy:
- SLSA Build L1/L2 are required; Build L3+ is the target wherever feasible.
- SLSA Source L1/L2 are required; Source L3 controls are followed where feasible; Source L4 is structurally blocked for a 1-person organization.
- Release source integrity uses GPG-signed annotated tags, GPG-signed commits on
main, protected branches/tags, linear history, and required CI gates. - Released artifacts that consumers run, install, deploy, or download must include signed
provenance attestations. Prefer SLSA GitHub Generator builders/generators; use reusable-workflow
attestations when practical; use direct
actions/attestonly as the baseline path when Build L3+ is not yet feasible. - Released binaries and container images must include signed SPDX and CycloneDX SBOM attestations when the build can generate them; public releases should publish the same SBOM files as release assets when possible.
- Registry-published release artifacts should upload linked artifacts storage metadata with
artifact-metadata: writewhen supported. - Dependency security is layered: committed lockfiles, Dependabot, cooldowns, Dependency Review, and OSV Scanner. Security updates bypass cooldowns; normal version updates use cooldowns.
- Workflow hardening requires SHA-pinned third-party actions, hardened runners, explicit minimal top-level permissions, job-level elevation only when required, OIDC instead of long-lived cloud credentials, and protected production environments.
- GitHub Actions permission reminders:
- Artifact attestations with
actions/attest:contents: read,id-token: write,attestations: write. - Linked artifacts storage records: add
artifact-metadata: writeand use registry artifact subjects by immutable digest. - Container registry pushes: add
packages: writeonly on the job that pushes images. - Release asset upload: add
contents: writeonly on the release job. - PR comments: add
pull-requests: writeonly for jobs that write comments.
- Artifact attestations with
- PRs must follow the template defined in
windlasstech/.github: - Always fetch the template content and write the PR body to match it. Do not rely on
gh pr createto auto-populate the template; if it does not, manually compose the body using the fetched template structure.
- Do not leak parser internals (
Parserclass,parseInput, scanner state) through the public API or exported types. - Do not add Node.js/Deno/Bun-specific globals or imports to
src/; Oxlint'sno-restricted-globalsandimport/no-nodejs-modulesrules enforce runtime agnosticism. - Do not implement comparison, containment, native range translation, resolver behavior, or vulnerability interpretation unless a new ADR explicitly expands scope.
- Do not expose a default export or subpath exports from
package.json. - Do not assert exact
VersIssue.messagestrings in tests. - Do not edit the upstream fixture at
tests/fixtures/upstream/vers_canonical_parse_test.json; local divergences are captured intests/fixtures/vers-canonical-disposition.json.
This project has a CodeGraph MCP server (codegraph_* tools) configured. CodeGraph is a
tree-sitter-parsed knowledge graph of every symbol, edge, and file. Reads are sub-millisecond and
return structural information grep cannot.
Use codegraph for structural questions — what calls what, what would break, where is X defined, what is X's signature. Use native grep/read only for literal text queries (string contents, comments, log messages) or after you already have a specific file open.
| Question | Tool |
|---|---|
| "Where is X defined?" / "Find symbol named X" | codegraph_search |
| "What calls function Y?" | codegraph_callers |
| "What does Y call?" | codegraph_callees |
| "How does X reach/become Y? / trace the flow from X to Y" | codegraph_trace (one call = the whole path, incl. callback/React/JSX dynamic hops) |
| "What would break if I changed Z?" | codegraph_impact |
| "Show me Y's signature / source / docstring" | codegraph_node |
| "Give me focused context for a task/area" | codegraph_context |
| "See several related symbols' source at once" | codegraph_explore |
| "What files exist under path/" | codegraph_files |
| "Is the index healthy?" | codegraph_status |
- Answer directly — don't delegate exploration. For "how does X work" / architecture questions,
answer with 2-3 codegraph calls:
codegraph_contextfirst, then ONEcodegraph_explorefor the source of the symbols it surfaces. For a specific flow ("how does X reach Y") start withcodegraph_tracefrom→to — one call returns the whole path with dynamic hops bridged — then ONEcodegraph_explorefor the bodies; don't rebuild the path withcodegraph_search+codegraph_callers. Codegraph IS the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer. - Trust codegraph results. They come from a full AST parse. Do NOT re-verify them with grep — that's slower, less accurate, and wastes context.
- Don't grep first when looking up a symbol by name.
codegraph_searchis faster and returns kind + location + signature in one call. - Don't chain
codegraph_search+codegraph_nodewhen you just want context —codegraph_contextis one call. - Don't loop
codegraph_nodeover many symbols — onecodegraph_explorecall returns several symbols' source grouped in a single capped call, while each separate node/Read call re-reads the whole context and costs far more. - Index lag: the file watcher debounces ~500ms behind writes; don't re-query immediately after editing a file in the same turn.
The MCP server returns "not initialized." Ask the user: "I notice this project doesn't have
CodeGraph initialized. Want me to run codegraph init -i to build the index?"
- VERS introduction: https://www.packageurl.org/docs/vers/introduction
- VERS spec: https://packageurl.org/docs/vers/specification
- VERS tests: https://packageurl.org/docs/vers/tests
- VERS parsing guide: https://github.com/package-url/vers-spec/blob/main/docs/how-to-parse.md
- Upstream canonical parse fixture (browse): https://github.com/package-url/vers-spec/blob/main/tests/vers_canonical_parse_test.json
- Upstream canonical parse fixture (raw): https://raw.githubusercontent.com/package-url/vers-spec/main/tests/vers_canonical_parse_test.json
- Local fixture disposition table:
tests/fixtures/vers-canonical-disposition.json - MADR template: embedded in
docs/decisions/0000-use-markdown-architectural-decision-records.md - Windlass dependency-security policy: https://github.com/windlasstech/.github/blob/main/docs/security/dependency-security.md