{"_id":"@andygo.dev/nestjs-harness","_rev":"3-a32d36d503422f8bfd66c7c91e2f029d","name":"@andygo.dev/nestjs-harness","dist-tags":{"latest":"1.1.1"},"versions":{"1.0.0":{"name":"@andygo.dev/nestjs-harness","version":"1.0.0","keywords":["nestjs","mcp","claude","claude-code","documentation","ai","typescript"],"license":"MIT","_id":"@andygo.dev/nestjs-harness@1.0.0","maintainers":[{"name":"andygo.dev","email":"andygo.develop+npmjs-andygo-dev@gmail.com"}],"homepage":"https://github.com/andygo-develop/nestjs-harness#readme","bugs":{"url":"https://github.com/andygo-develop/nestjs-harness/issues"},"bin":{"nestjs-harness":"bin/nestjs-harness.js"},"dist":{"shasum":"1a771039fe3c26f5aae45144b361cf742c3aa73a","tarball":"https://registry.npmjs.org/@andygo.dev/nestjs-harness/-/nestjs-harness-1.0.0.tgz","fileCount":231,"integrity":"sha512-gP/EnMAjZJ/C235CSBSAE+A2F3idbLrmXf3RUjv7UcIoxddj+mWeCW33CwpheqhW1vOGw6rsq0g5y0P5sb6duQ==","signatures":[{"sig":"MEQCIFpj5fAhzNNKrWeHgcXj6t59xx9IjYgG725UA/Xlt/33AiBCFmjNx4OU138aBW55zppYMgXoz+XRb0/yJ8S5+M/Ryw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":733891},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.13.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"2316fc6c2f77594dc3e57a1be24b2ff54d350042","scripts":{"lint":"eslint \"src/**/*.ts\" --max-warnings=0","test":"vitest run","build":"tsc","clean":"rm -rf dist","release":"[ \"$(git rev-parse --abbrev-ref HEAD)\" = \"main\" ] || { echo 'Error: not on main branch'; exit 1; } && [ -z \"$(git status --porcelain)\" ] || { echo 'Error: uncommitted changes'; exit 1; } && [ -z \"$(git log @{u}..HEAD --oneline)\" ] || { echo 'Error: unpushed commits'; exit 1; } && [ -z \"$(git log HEAD..@{u} --oneline)\" ] || { echo 'Error: local main is behind origin/main'; exit 1; } && npm publish --access public","lint:fix":"eslint \"src/**/*.ts\" --max-warnings=0 --fix","typecheck":"tsc --noEmit","bump:major":"npm version major --no-git-tag-version","bump:minor":"npm version minor --no-git-tag-version","bump:patch":"npm version patch --no-git-tag-version","test:watch":"vitest","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"andygo.dev","email":"andygo.develop+npmjs-andygo-dev@gmail.com"},"repository":{"url":"git+https://github.com/andygo-develop/nestjs-harness.git","type":"git"},"_npmVersion":"11.17.0","description":"Local AI development harness for NestJS projects: version-aware manual sync, SQLite/FTS5 search, a NestJS Claude Code Skill, and an MCP server.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"tar":"^7.5.22","zod":"^4.4.3","commander":"^15.0.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.5","vitest":"^4.1.10","typescript":"^5.5.3","@types/node":"^22.10.0","typescript-eslint":"^8.67.0"},"optionalDependencies":{"@huggingface/transformers":"^4.2.0"},"_npmOperationalInternal":{"tmp":"tmp/nestjs-harness_1.0.0_1786619484910_0.5864703512040585","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@andygo.dev/nestjs-harness","version":"1.1.0","keywords":["nestjs","mcp","claude","claude-code","documentation","ai","typescript"],"license":"MIT","_id":"@andygo.dev/nestjs-harness@1.1.0","maintainers":[{"name":"andygo.dev","email":"andygo.develop+npmjs-andygo-dev@gmail.com"}],"homepage":"https://github.com/andygo-develop/nestjs-harness#readme","bugs":{"url":"https://github.com/andygo-develop/nestjs-harness/issues"},"bin":{"nestjs-harness":"bin/nestjs-harness.js"},"dist":{"shasum":"1c3ef4a4aa7037115c92773e700f4bce6d5df69d","tarball":"https://registry.npmjs.org/@andygo.dev/nestjs-harness/-/nestjs-harness-1.1.0.tgz","fileCount":231,"integrity":"sha512-UX7QxMFKsHoxvwYKJyxPKi3GoFtPM3E+wSqc/Gh+wFtKD3LDiQeANA7++g5XnTT4iyHqMPAlqrkQ+IwiCaLTlg==","signatures":[{"sig":"MEQCIF+EzN/wsaS5nfX7Q0ppSUVyz4ECrxJoVzcRdi9D3brfAiBPAXSCytXjnOuQxNOt9NkL0web2apOKgjj+sZ64vK2uw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":735635},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.13.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"d56fe29df6788aec4795ab219b77a412e3da22f6","scripts":{"lint":"eslint \"src/**/*.ts\" --max-warnings=0","test":"vitest run","build":"tsc","clean":"rm -rf dist","release":"[ \"$(git rev-parse --abbrev-ref HEAD)\" = \"main\" ] || { echo 'Error: not on main branch'; exit 1; } && [ -z \"$(git status --porcelain)\" ] || { echo 'Error: uncommitted changes'; exit 1; } && [ -z \"$(git log @{u}..HEAD --oneline)\" ] || { echo 'Error: unpushed commits'; exit 1; } && [ -z \"$(git log HEAD..@{u} --oneline)\" ] || { echo 'Error: local main is behind origin/main'; exit 1; } && npm publish --access public","lint:fix":"eslint \"src/**/*.ts\" --max-warnings=0 --fix","typecheck":"tsc --noEmit","bump:major":"npm version major --no-git-tag-version","bump:minor":"npm version minor --no-git-tag-version","bump:patch":"npm version patch --no-git-tag-version","test:watch":"vitest","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"andygo.dev","email":"andygo.develop+npmjs-andygo-dev@gmail.com"},"repository":{"url":"git+https://github.com/andygo-develop/nestjs-harness.git","type":"git"},"_npmVersion":"11.17.0","description":"Local AI development harness for NestJS projects: version-aware manual sync, SQLite/FTS5 search, a NestJS Claude Code Skill, and an MCP server.","directories":{},"_nodeVersion":"24.16.0","dependencies":{"tar":"^7.5.22","zod":"^4.4.3","commander":"^15.0.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.5","vitest":"^4.1.10","typescript":"^5.5.3","@types/node":"^22.10.0","typescript-eslint":"^8.67.0"},"optionalDependencies":{"@huggingface/transformers":"^4.2.0"},"_npmOperationalInternal":{"tmp":"tmp/nestjs-harness_1.1.0_1786621613904_0.3526337972589084","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@andygo.dev/nestjs-harness","version":"1.1.1","description":"Local AI development harness for NestJS projects: version-aware manual sync, SQLite/FTS5 search, a NestJS Claude Code Skill, and an MCP server.","keywords":["nestjs","mcp","claude","claude-code","documentation","ai","typescript"],"homepage":"https://github.com/andygo-develop/nestjs-harness#readme","bugs":{"url":"https://github.com/andygo-develop/nestjs-harness/issues"},"repository":{"type":"git","url":"git+https://github.com/andygo-develop/nestjs-harness.git"},"author":{"name":"Andy Kramar","email":"andygo.develop@gmail.com"},"license":"MIT","type":"module","bin":{"nestjs-harness":"bin/nestjs-harness.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"main":"./dist/index.js","types":"./dist/index.d.ts","engines":{"node":">=22.13.0"},"scripts":{"build":"tsc","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","lint":"eslint \"src/**/*.ts\" --max-warnings=0","lint:fix":"eslint \"src/**/*.ts\" --max-warnings=0 --fix","release":"[ \"$(git rev-parse --abbrev-ref HEAD)\" = \"main\" ] || { echo 'Error: not on main branch'; exit 1; } && [ -z \"$(git status --porcelain)\" ] || { echo 'Error: uncommitted changes'; exit 1; } && [ -z \"$(git log @{u}..HEAD --oneline)\" ] || { echo 'Error: unpushed commits'; exit 1; } && [ -z \"$(git log HEAD..@{u} --oneline)\" ] || { echo 'Error: local main is behind origin/main'; exit 1; } && npm publish --access public","bump:patch":"npm version patch --no-git-tag-version","bump:minor":"npm version minor --no-git-tag-version","bump:major":"npm version major --no-git-tag-version"},"dependencies":{"@modelcontextprotocol/sdk":"^1.30.0","commander":"^15.0.0","tar":"^7.5.22","zod":"^4.4.3"},"optionalDependencies":{"@huggingface/transformers":"^4.2.0"},"devDependencies":{"@types/node":"^22.10.0","eslint":"^9.39.5","typescript":"^5.5.3","typescript-eslint":"^8.67.0","vitest":"^4.1.10"},"gitHead":"369d601f9f76d01bc7546a329d7112cb8507f7c3","_id":"@andygo.dev/nestjs-harness@1.1.1","_nodeVersion":"24.16.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-qid4LNGOz1HutIDCszXG2aqBM4SKqjlZ6K8GINggdJFt/CVqIQWDp/uklz90xvE1ngYJdl+Fzz9paSsFNwDa1w==","shasum":"fd073bdafd1a8b18d2988e70c7df9969d5262099","tarball":"https://registry.npmjs.org/@andygo.dev/nestjs-harness/-/nestjs-harness-1.1.1.tgz","fileCount":231,"unpackedSize":737961,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAb0EjU7I2HsQhFfilVS6OktWIQkfO1w/fOmU7N0jCN+AiBMvfnupwmX6/ta3V7IU608WqApArYVxNFcZRv+UM6O/Q=="}]},"_npmUser":{"name":"andygo.dev","email":"andygo.develop+npmjs-andygo-dev@gmail.com"},"directories":{},"maintainers":[{"name":"andygo.dev","email":"andygo.develop+npmjs-andygo-dev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nestjs-harness_1.1.1_1787132072198_0.13281027651747968"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-13T11:11:24.709Z","modified":"2026-08-19T09:34:32.516Z","1.0.0":"2026-08-13T11:11:25.075Z","1.1.0":"2026-08-13T11:46:54.086Z","1.1.1":"2026-08-19T09:34:32.359Z"},"bugs":{"url":"https://github.com/andygo-develop/nestjs-harness/issues"},"license":"MIT","homepage":"https://github.com/andygo-develop/nestjs-harness#readme","keywords":["nestjs","mcp","claude","claude-code","documentation","ai","typescript"],"repository":{"type":"git","url":"git+https://github.com/andygo-develop/nestjs-harness.git"},"description":"Local AI development harness for NestJS projects: version-aware manual sync, SQLite/FTS5 search, a NestJS Claude Code Skill, and an MCP server.","maintainers":[{"name":"andygo.dev","email":"andygo.develop+npmjs-andygo-dev@gmail.com"}],"readme":"# @andygo.dev/nestjs-harness\n\nA local AI development harness for NestJS projects.\n\nIt gives AI coding agents **version-aware, local, authoritative NestJS\nknowledge**, while keeping *how to write NestJS code* separate from *what the\nframework actually does*:\n\n- **Guidance** — development conventions, architecture and testing practice\n- **Roles** — `nestjs-expert`, `nestjs-code-reviewer`, `nestjs-test-writer`\n  and `nestjs-planner`, which verify APIs against the docs instead of\n  recalling them\n- **Manuals + MCP** — the official NestJS documentation for *your* version\n- **Project specs** *(optional)* — your own docs and specs, in a separate corpus\n- **Config** — how this specific project should be developed\n\nThe problem it solves: an agent confidently inventing a NestJS API, or\nanswering an NestJS 11 question from NestJS 10 memory.\n\nIt works with **Claude Code, Cursor, OpenAI Codex CLI, Gemini CLI and\nOpenCode** — one source of guidance, rendered into whatever each tool reads, so\na team on mixed tooling cannot end up with two versions of \"how we write NestJS\nhere\".\n\n```\nDeveloper\n    │  npx @andygo.dev/nestjs-harness setup\n    ▼\nNestJS Harness ── detects version ── installs guidance ── syncs manuals ── indexes ── serves MCP\n                                                                                        │\n                                                                                        ▼\n                                                      Claude Code · Cursor · Codex · Gemini CLI · OpenCode\n```\n\n## Requirements\n\n- Node.js **>= 22.13** (uses the built-in `node:sqlite`)\n- A NestJS project with a `package.json` depending on `@nestjs/core`\n\nNo native modules, no compilation, no database server.\n\n## Quick start\n\n```bash\ncd my-nestjs-project\nnpx @andygo.dev/nestjs-harness setup\n```\n\nOr install the CLI once and run the binary:\n\n```bash\nnpm install -g @andygo.dev/nestjs-harness\ncd my-nestjs-project\nnestjs-harness setup\n```\n\nThat detects your NestJS version, detects which coding agents the project\nalready uses, installs the guidance and roles for each of them, downloads and\nindexes the matching manuals, and offers to register the MCP server with each\nagent.\n\nTo choose the agents yourself:\n\n```bash\nnestjs-harness setup --target claude-code --target cursor\n```\n\nThen:\n\n```bash\nnestjs-harness manuals search \"guards and route access control\"\n```\n\n```\n1. Guards › Authorization guard\n   Section: (root)\n   Version: 11.0.0 (project: 11.1)\n   URL: https://docs.nestjs.com/guards#authorization-guard\n   Id:  11.0.0:en:guards.md#authorization-guard\n\n   …the CanActivate interface implemented by every guard. Each guard has a…\n```\n\n## Checking your setup\n\n```bash\nnestjs-harness doctor\n```\n\n```\n✓ NestJS project detected\n✓ NestJS version: 11.1\n✓ Manuals synchronized\n✓ Documentation index available\n✓ NestJS Skill installed\n✓ Cursor guidance installed\n✓ MCP server available\n✓ search_nestjs_manual available\n✓ get_nestjs_manual available\n✓ search_nestjs_api available\n\nAvailable commands:\n  nestjs-harness setup                       Run the full idempotent setup flow\n  nestjs-harness doctor                      Check setup health and list available commands\n  nestjs-harness init                        Detect the project and create .nestjs-harness/\n  nestjs-harness manuals sync                Download the official NestJS manuals\n  nestjs-harness manuals index               Build the manual search index\n  nestjs-harness manuals update              Sync manuals and update the index\n  nestjs-harness manuals search <query>      Search the NestJS manuals\n  nestjs-harness manuals status              Show synchronized and indexed manual status\n  nestjs-harness manuals versions            List documentation lines and local status\n  nestjs-harness specs index                 Index this project's own specs\n  nestjs-harness specs search <query>        Search this project's own specs\n  nestjs-harness specs status                Show project spec search status\n  nestjs-harness targets list                List coding agents and their setup state\n  nestjs-harness targets add <agent>         Set this project up for another coding agent\n  nestjs-harness targets remove <agent>      Stop maintaining files for a coding agent\n  nestjs-harness targets install             Reinstall files for every configured agent\n  nestjs-harness skill install                Install the NestJS guidance\n  nestjs-harness skill update                 Update the NestJS guidance\n  nestjs-harness agent install                Install the NestJS roles\n  nestjs-harness agent update                 Update the NestJS roles\n  nestjs-harness mcp start                    Run the MCP server on stdio\n  nestjs-harness mcp status                   Show MCP registration and readiness\n```\n\nThe MCP checks are not assertions — `doctor` stands the server up over an\nin-memory transport, lists its tools and calls them, so a tool that is\nregistered but broken (stale index, version drift) is reported as broken.\nFailures print the command that fixes them, and the exit code is non-zero,\nwhich makes it usable as a CI gate. `doctor` also prints the complete command\ncatalog so it doubles as command discovery. `--json` emits the full report,\nincluding the command catalog.\n\n## Commands\n\n| Command | What it does |\n|---|---|\n| `setup` | Everything below, in one idempotent command |\n| `doctor` | Check setup health and list available commands (`--json`) |\n| `init` | Detect the project and create `.nestjs-harness/` (no downloads) |\n| `manuals sync` | Download the official manuals for your version |\n| `manuals index` | Build the SQLite/FTS5 search index |\n| `manuals update` | Sync, then incrementally reindex — the everyday command |\n| `manuals search <query>` | Search the manuals (`--limit`, `--full`, `--version`) |\n| `manuals status` | What is synced and indexed (`--json`) |\n| `manuals versions` | Documentation lines and their local status |\n| `specs index` | Index this project's own specs (opt-in; enables spec search) |\n| `specs search <query>` | Search this project's own specs (`--limit`, `--full`) |\n| `specs status` | Whether project spec search is enabled and current (`--json`) |\n| `targets list` | Coding agents, and whether each is set up (`--json`) |\n| `targets add <agent>` | Set the project up for another agent and install its files |\n| `targets remove <agent>` | Stop maintaining an agent's files (deletes nothing) |\n| `targets install` | Reinstall guidance, roles and MCP for every configured agent |\n| `skill install` / `skill update` | Install or refresh the guidance, for every agent |\n| `agent install` / `agent update` | Install or refresh the roles, for every agent |\n| `mcp start` | Run the MCP server on stdio (your coding agent launches this) |\n| `mcp status` | Registration per agent and documentation readiness (`--json`) |\n\nAdd `--verbose` to any command for diagnostics and stack traces. `setup`,\n`targets install`, `skill install` and `agent install` accept `--target <agent>`\n(repeatable) to work on one agent at a time.\n\nEvery command is safe to run repeatedly. `manuals update` detects that nothing\nchanged and does no work; nothing will clobber your local edits.\n\n## What gets created\n\nAlways:\n\n```\n.nestjs-harness/\n├── config.json                       project configuration\n├── manuals/nestjs-11.0.0/            synced Markdown + .meta.json\n├── index/docs.sqlite                 FTS5 search index (framework manual)\n├── index/specs.sqlite                FTS5 search index (project specs, optional)\n├── targets/<agent>.json              what the harness installed, per agent\n└── cache/                            download cache\n```\n\nThen, per coding agent — only for the ones your project is set up for:\n\n```\nClaude Code       .claude/skills/nestjs/        SKILL.md + references/\n                  .claude/agents/*.md           four subagents\n                  .mcp.json\n\nCursor            .cursor/rules/nestjs.mdc      rule, auto-attached to **/*.ts\n                  .cursor/commands/*.md         four role playbooks\n                  .cursor/mcp.json\n\nOpenAI Codex CLI  AGENTS.md                     a marked-off block, merged in\n                  .codex/config.toml\n\nGemini CLI        GEMINI.md                     a marked-off block, merged in\n                  .gemini/commands/nestjs/*.toml   /nestjs:expert, …\n                  .gemini/settings.json\n\nOpenCode          AGENTS.md                     a marked-off block, merged in\n                  .opencode/agent/*.md          four subagents\n                  opencode.json\n```\n\nAgents that do not keep guidance in a self-contained directory share one copy of\nthe reference documents at `.nestjs-harness/instructions/references/`, which\ntheir guidance file links to.\n\nMCP registration files are only written if you approve the prompt.\n\nThe npm package contains the tooling. Documentation is downloaded locally by\n`manuals sync`, never bundled.\n\nPackage name:\n[`@andygo.dev/nestjs-harness`](https://www.npmjs.com/package/@andygo.dev/nestjs-harness).\nThe installed CLI binary is still `nestjs-harness`.\n\n## Coding agents\n\nWhich agents a project is set up for is recorded in `config.json` as `targets`,\nand `init` proposes what it can detect (`.cursor/`, `.codex/`, `GEMINI.md`,\n`opencode.json`, …). Detection only ever informs a *new* config — once the list\nis recorded it is your decision, and adding `.cursor/` to a repository will not\nsilently start writing Cursor files.\n\n```bash\nnestjs-harness targets list\n```\n\n```\nAgent        Set up   Detected   MCP\nclaude-code  yes      yes        yes\ncursor       yes      yes        yes\ncodex        —        yes        —\ngemini       —        —          —\nopencode     —        —          —\n```\n\n```bash\nnestjs-harness targets add codex\nnestjs-harness targets remove cursor\n```\n\n`targets remove` stops maintaining an agent's files; it never deletes them. They\nare in your repository, possibly committed and possibly edited, and quietly\ndeleting them because a config list changed is not a trade this tool makes. It\nprints exactly what was left behind.\n\n### One source of guidance\n\nThe conventions are written once, in this package's Skill templates, and\nrendered per agent. The four roles are written once as subagent definitions and\nre-expressed as whatever the tool actually supports:\n\n| Agent | Roles become | Permissions |\n|---|---|---|\n| Claude Code | subagents in `.claude/agents/` | native `tools:` list |\n| OpenCode | subagents in `.opencode/agent/` | translated to `tools: {write: false, …}` |\n| Gemini CLI | commands — `/nestjs:expert`, … | not expressible |\n| Cursor | commands in `.cursor/commands/` | not expressible |\n| OpenAI Codex CLI | playbook documents it is pointed at | not expressible |\n\nTwo details there are load-bearing. Claude Code namespaces MCP tools as\n`mcp__<server>__<tool>` and other clients do not, so the prefix is stripped for\nthem — a role telling Gemini CLI to call `mcp__nestjs-docs__search_nestjs_manual`\nwould simply never look anything up. And the reviewer's read-only restriction is\ntranslated rather than dropped where the syntax differs; where a tool cannot\nexpress it at all, that is stated rather than assumed.\n\n`AGENTS.md` is shared by Codex and OpenCode, so a project set up for both gets\n**one** block describing both, rather than each overwriting the other's on every\nrun.\n\n## MCP tools\n\nOnce registered, your coding agent gains these tools:\n\n| Tool | Purpose |\n|---|---|\n| `search_nestjs_manual` | Ranked search; returns compact excerpts + `documentId` |\n| `get_nestjs_manual` | Full document text for a `documentId` |\n| `search_nestjs_api` | Look up a class, decorator or method |\n| `search_project_specs` | Search this project's own specs (optional, see below) |\n| `get_project_spec` | Full text of one of this project's spec documents |\n\nResults are deliberately small — title, section, version, URL, excerpt,\n`documentId` — so a search never floods the context window. The agent fetches\nfull documents only when it needs them.\n\n## Project specs (optional)\n\nBeyond the framework manual, the harness can index **your project's own** specs,\ndesign notes and ADRs — the knowledge that explains how *this* application is\nmeant to behave.\n\nIt is opt-in. Nothing scans your repository until you run:\n\n```bash\nnestjs-harness specs index\nnestjs-harness specs search \"invoice numbering\"\n```\n\n```\n1. Billing Rules › Invoice Numbering\n   Section: docs\n   File: docs/billing.md#invoice-numbering\n   Id:   spec:docs/billing.md#invoice-numbering\n\n   Invoice numbers use the prefix ACME- followed by a zero-padded sequence…\n```\n\nWhich files count is configurable:\n\n```json\n\"specs\": {\n  \"enabled\": true,\n  \"include\": [\"docs/**/*.md\", \"specs/**/*.md\", \"*.md\"],\n  \"exclude\": [\"vendor/**\", \"node_modules/**\", \".nestjs-harness/**\", \".claude/**\",\n              \".cursor/**\", \".codex/**\", \".gemini/**\", \".opencode/**\",\n              \"AGENTS.md\", \"CLAUDE.md\", \"GEMINI.md\"]\n}\n```\n\nDiscovery prunes excluded directories rather than walking them, skips symlinks\nso it cannot escape the project, and indexes incrementally by content hash like\nthe manual does.\n\nThe excludes cover every file the harness installs for a coding agent. Those\nhold *framework* guidance, and indexing them here would let NestJS conventions\ncome back out of `search_project_specs` dressed as this project's own\nrequirements.\n\n**The two corpora never mix.** Project specs live in their own SQLite database\n(`index/specs.sqlite`) with their own tools, so a project design note cannot be\nreturned by `search_nestjs_manual` — that separation is structural, not a\nfilter that could be got wrong. The tool descriptions and the server\ninstructions both state which corpus is which, so an agent does not present\nyour internal ADR as NestJS framework behaviour.\n\n## The NestJS roles\n\n`setup` installs four roles for every coding agent the project is set up for:\n\n| Role | Does | Tools |\n|---|---|---|\n| `nestjs-expert` | Implements and refactors NestJS code | full (reads, edits, runs) |\n| `nestjs-code-reviewer` | Reviews NestJS code for defects | **read-only** + MCP lookups |\n| `nestjs-test-writer` | Writes and repairs tests | read/write + Bash + MCP lookups |\n| `nestjs-planner` | Plans features, refactors and migrations before implementation | **read-only** + MCP lookups |\n\nIn Claude Code and OpenCode they are subagents:\n\n```\n> use the nestjs-expert agent to add rate limiting to the auth module\n> use the nestjs-planner agent to plan the billing refactor\n> use the nestjs-code-reviewer agent on my changes\n> use the nestjs-test-writer agent to cover UsersService\n```\n\nIn Gemini CLI they are commands (`/nestjs:expert`, `/nestjs:code-reviewer`,\n`/nestjs:test-writer`, `/nestjs:planner`); in Cursor, commands in\n`.cursor/commands/`; in Codex, playbook documents its `AGENTS.md` block points\nat.\n\n### Forcing a role\n\nOnly `nestjs-expert`'s description says `Use PROACTIVELY`, so it is the one\nrole a coding agent may reach for on its own for NestJS implementation work.\nThe other three — `nestjs-planner`, `nestjs-code-reviewer`, `nestjs-test-writer`\n— only run when you ask for them by name; left unnamed, the agent is free to\nhandle planning, review or tests inline itself instead of delegating.\n\nTo force a specific role rather than leaving that choice to the agent, invoke\nit explicitly:\n\n- **Claude Code / OpenCode** — name the subagent in your prompt, as in the\n  examples above (`use the nestjs-code-reviewer agent to review this`). Naming\n  it dispatches the whole task to that subagent instead of the top-level agent\n  answering inline.\n- **Gemini CLI** — run its command directly: `/nestjs:expert`,\n  `/nestjs:planner`, `/nestjs:code-reviewer`, `/nestjs:test-writer`.\n- **Cursor** — run the matching command from `.cursor/commands/`.\n- **Codex** — there is no separate role to invoke. Its playbook is folded into\n  the ambient `AGENTS.md` block and applies on every turn, so there is nothing\n  to force on.\n\nThose are all per-prompt. For a standing rule, edit `CLAUDE.md` — the harness\nnever writes to it (Claude Code's guidance lives in `.claude/skills/nestjs/`\nand `.claude/agents/*.md` instead, see the layout above), so it is a clean\nplace to add project-wide delegation policy without colliding with anything\n`skill update`/`agent update` maintain. For example:\n\n```markdown\n## Subagent policy\n\n- Always use the nestjs-code-reviewer subagent to review NestJS changes\n  before reporting a task done.\n- Always use the nestjs-test-writer subagent when adding or fixing tests\n  for NestJS code.\n```\n\nThat turns delegation into the default for that kind of work project-wide,\ninstead of something asked for each time — effectively a project-scoped\n`Use PROACTIVELY` for a role that doesn't carry it by default. The same idea\napplies to the other targets' own ambient files (`AGENTS.md` for Codex/\nOpenCode, `GEMINI.md` for Gemini CLI, `.cursor/rules/` for Cursor) — but\nthose already carry a harness-managed block, so add project-specific policy\nlike this outside of it, not inside the marked-off section `agent\nupdate`/`skill update` own.\n\nAll four share one defining rule: **verify framework APIs against the\ndocumentation before asserting them**. For the expert that means searching\nbefore writing; for the planner it means grounding implementation steps in this\nproject's actual NestJS version; for the reviewer it means confirming an API\nreally is wrong before flagging it — a review that confidently flags correct\ncode is worse than no review; for the test writer it means checking that a\ntesting utility actually exists in this version before relying on it.\n\nThe **reviewer** is restricted to `Read, Grep, Glob, Bash` plus the three MCP\ntools, so it cannot rewrite the code it is reviewing — translated to\n`write: false, edit: false` for OpenCode, and stated in the prose for tools that\ncannot enforce it. It reports findings as\nCritical / Warning / Suggestion with `file:line` and a concrete fix, covering\nNestJS-specific defects: DTOs accepted without a `ValidationPipe`, guards\nordered after the logic they are meant to protect, providers reaching for\n`any` instead of constructor injection, N+1 queries from an eagerly-loaded\nrelation inside a loop, secrets read directly from `process.env` instead of\n`ConfigService`, and missing `@nestjs/testing` coverage on a new endpoint.\n\nThe **test writer** carries two hard rules that tool permissions cannot express:\nit never edits production code to make a test pass (a failing test it wrote is a\nbug found, and it reports it instead), and it never claims a suite passes\nwithout actually running it. It knows the NestJS testing surface —\n`Test.createTestingModule()`, `overrideProvider()`, Supertest against the HTTP\nserver for e2e, and mocking a provider rather than the module under test — and\nis told to cover failure paths, not just happy paths.\n\nIn Claude Code, tool names are namespaced by your MCP server name\n(`mcp__nestjs-docs__search_nestjs_manual`), so all four roles are rendered with\nthe `mcp.serverName` from your config at install time — rename the server and\n`agent update` rewires them.\n\nUnlike the ambient guidance, a subagent runs in a separate context with its own\ntool budget. Use the guidance for everyday NestJS work; reach for a role on\nlarger, self-contained tasks.\n\n### Manual registration\n\n`setup` asks before touching any MCP configuration file. To do it yourself, in\n`.mcp.json` (Claude Code), `.cursor/mcp.json` (Cursor) or `.gemini/settings.json`\n(Gemini CLI):\n\n```json\n{\n  \"mcpServers\": {\n    \"nestjs-docs\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@andygo.dev/nestjs-harness\", \"mcp\", \"start\"]\n    }\n  }\n}\n```\n\nIn `opencode.json` (OpenCode):\n\n```json\n{\n  \"mcp\": {\n    \"nestjs-docs\": {\n      \"type\": \"local\",\n      \"command\": [\"npx\", \"-y\", \"@andygo.dev/nestjs-harness\", \"mcp\", \"start\"],\n      \"enabled\": true\n    }\n  }\n}\n```\n\nIn `.codex/config.toml` (Codex CLI — it also reads `~/.codex/config.toml`):\n\n```toml\n[mcp_servers.nestjs-docs]\ncommand = \"npx\"\nargs = [\"-y\", \"@andygo.dev/nestjs-harness\", \"mcp\", \"start\"]\n```\n\nExisting servers in these files are never modified, and an entry for our own\nserver that you have customised is left alone.\n\n## Version safety\n\nThis is the point of the tool, so it is strict.\n\n```\nClaude → MCP → project config → NestJS version → version-specific index → search\n```\n\nUnlike frameworks that publish a single living documentation branch per major\nline, `docs.nestjs.com` (built from `nestjs/docs.nestjs.com`) gets a frozen\nsnapshot branch the day each major ships — `10.0.0`, `11.0.0`, and so on —\nand that branch never moves again. The harness pins every known major to its\nown frozen branch on purpose: the corpus a project syncs today is the same\ncorpus it syncs next month, not something that can silently drift out from\nunder an already-built index. So the harness maps:\n\n- the current major (**11**) → `11.0.0`, the frozen snapshot cut when NestJS\n  11 shipped;\n- the previous major (**10**) → `10.0.0`, the only documentation upstream\n  still has for it;\n- anything older → no documentation at all — upstream does not keep it;\n- a future major newer than anything the harness knows about yet → `master`\n  as a stopgap, since that is the only documentation upstream has for it\n  until a frozen branch exists.\n\nAnd it **never crosses a major-version boundary**: an 11.x project is never\nserved 10.x documentation, or vice versa.\n\nIf the right documentation is not available, you get an error, not a guess:\n\n```\n✖ NestJS 10.1 documentation has not been synchronized (corpus: nestjs-10.0.0, language: en).\n\nRun:\n\n  nestjs-harness manuals sync\n  nestjs-harness manuals index\n\nIndexed documentation for other versions is present but will not be used:\n  nestjs-11.0.0 (en, 210 documents)\n```\n\nVersion detection prefers `package-lock.json` (exact, `11.1.29`) and falls back\nto the `package.json` range (`^11.0.0`).\n\n## How it works\n\n**Sync** asks GitHub for the head commit of the documentation branch for your\nmajor line (`11.0.0` for the current major, `10.0.0` for the previous one). If\nit matches what you have, nothing is downloaded. Otherwise it pulls the branch\ntarball once and extracts only `content/**/*.md`.\n\n**Indexing** splits each page into one document per `##` section — a section is\nthe unit a developer actually wants back, and whole pages rank badly and blow up\ncontext. Each chunk is content-hashed, so re-indexing only touches what changed.\n\n**Search** is BM25 via SQLite FTS5 by default, with title and heading weighted\nabove body text. Queries are tokenised and re-quoted before they reach FTS5, so\n`@Injectable()`, `canActivate()` and `this.usersService` work rather than\nthrowing syntax errors. The search widens in stages: all terms → any term →\nprefix.\n\n**Hybrid search** *(optional)* blends that BM25 ranking with semantic\nsimilarity from local embeddings, combined by reciprocal rank fusion — a query\nphrased nothing like the manual's own wording (`\"how do I stop a request body\nfrom having extra unexpected fields\"`) can still surface the right section. See\n[Hybrid search](#hybrid-search-optional) below.\n\n**Storage** is behind a repository interface so another backend can be added\nlater.\n\n## Hybrid search (optional)\n\nBy default, search is BM25 only — lexical, offline, no extra dependency. Set\n`index.searchStrategy` to `\"hybrid\"` in `.nestjs-harness/config.json` to also\nrank by semantic similarity from a local embedding model, blended with BM25 by\n[reciprocal rank fusion](https://en.wikipedia.org/wiki/Learning_to_rank#Ranking_SVM):\n\n```json\n\"index\": {\n  \"searchStrategy\": \"hybrid\",\n  \"embeddingModel\": \"Xenova/all-MiniLM-L6-v2\"\n}\n```\n\nThen reindex — hybrid search needs embeddings to search *against*, not just\nthe FTS5 index:\n\n```bash\nnestjs-harness manuals update\nnestjs-harness specs index\n```\n\nThis works on an index you already have: nothing needs to change on disk for\nthe embeddings to be filled in, and neither command re-downloads or re-parses\nanything it does not have to. It is also resumable — if the run is interrupted,\neverything embedded so far is kept and the next run picks up exactly what is\nstill missing.\n\n`manuals status` reports readiness (`210/210 documents embedded`), and\n`manuals search` / `search_nestjs_manual` refuse to run hybrid search with a\nmessage telling you to reindex, rather than silently falling back to bm25,\nunless *every* document in the corpus has an embedding for the configured\nmodel. Reading a document by id (`get_nestjs_manual`) and `doctor` never\nrequire embeddings, so neither is affected while a corpus is still filling in.\n\nWhy bother: BM25 only ever matches vocabulary that is actually in the query.\nA query phrased in the developer's own words — `\"how do I stop a request body\nfrom having extra unexpected fields\"` — has almost no token overlap with the\nmanual's own heading, *Whitelisting*, but hybrid search still ranks it first,\nbecause the embeddings capture that they mean the same thing.\n\n**What's actually running:** a small sentence-embedding model\n(`Xenova/all-MiniLM-L6-v2` by default) via\n[`@huggingface/transformers`](https://github.com/huggingface/transformers.js) —\ntransformers.js, a local WASM/ONNX runtime. No API key, no server, no\noutbound calls per query. The model downloads once on first use\n(a few tens of MB), reporting progress as it goes, and is cached after that.\n\n**Trade-offs worth knowing before you opt in:**\n\n- It is the one path in this package that is not \"no native modules\": in\n  Node, transformers.js runs its ONNX graph through `onnxruntime-node`, a\n  small **prebuilt** (not compiled) native addon. The default `bm25` strategy\n  is entirely unaffected — this only loads if `searchStrategy` is `hybrid`.\n- `@huggingface/transformers` is listed as an `optionalDependencies` entry\n  specifically so a `bm25`-only install never has to carry it. It pulls in\n  `onnxruntime-node` and `sharp` (image handling the text-embedding path here\n  never uses), both of which currently have open, unpatched high-severity\n  advisories in their dependency chains at the time of writing — check\n  `npm audit` before deciding whether that is acceptable for your project.\n- Indexing a full manual corpus (~200 chunks) takes tens of seconds longer\n  than bm25-only, since every added or changed chunk needs an embedding. A\n  chunk that is unchanged *and* already has a vector for the configured model\n  is never re-embedded.\n- Similarity is a brute-force cosine scan over stored vectors at query time —\n  fine at the corpus sizes this tool deals with (low thousands of documents),\n  deliberately not a dedicated ANN index for a problem this size does not have.\n\n## Security\n\nDownloaded documentation is untrusted input:\n\n- extracted entries must be regular Markdown files under `content/`\n- absolute paths, `..` segments, symlinks and hardlinks are rejected\n- every destination is verified to resolve inside the manuals directory\n- content is only ever stored and displayed — never executed, never\n  interpolated into a shell command, never able to influence control flow\n\n## Programmatic use\n\n```ts\nimport { detectNestJsVersion, openCorpus, searchManuals } from '@andygo.dev/nestjs-harness';\n```\n\nThe version model, config, sync, index, search, MCP server and the target\ninstallers are all exported. To set a project up for an agent from your own\ntooling:\n\n```ts\nimport { installTargets, registerTargetServer, TARGETS } from '@andygo.dev/nestjs-harness';\n\nawait installTargets(['claude-code', 'cursor'], { root, serverName: 'nestjs-docs' });\nawait registerTargetServer(root, TARGETS.cursor, 'nestjs-docs');\n```\n\n## Development\n\n```bash\nnpm install\nnpm run build\nnpm test          # fully offline (hybrid search is tested against a fake embedding provider)\nnpm run typecheck\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md","author":{"name":"Andy Kramar","email":"andygo.develop@gmail.com"}}