@context-qb/cli
v2.5.0
Published
context.qb drift detector CLI — checks that context.qb.yaml matches the actual repo it describes. An Industrial Semiotics project.
Maintainers
Readme
@context-qb/cli
CLI tooling for the context.qb format. Currently provides one command — check-qb, the drift detector — with more on the way per the format ROADMAP.
What's here
packages/qb/cli/
├── package.json
├── tsconfig.json
├── README.md # this file
├── CHANGELOG.md # version history (Keep a Changelog format)
└── src/
├── types.ts # DriftFinding, DetectorContext, AdrStatus, etc.
├── loader.ts # loadContext, lineOf (reads paths.decisions: if set)
├── adapters.ts # thin re-export of three dispatchers
├── detectors.ts # detectTreeDrift, detectRoutesDrift, detectDecisionsDrift
├── cli.ts # parseArgs, main, isEntryPoint guard
├── index.ts # public surface re-exports
└── adapters/
├── workspaces/
│ ├── pnpm.ts # pnpm-workspace.yaml reader
│ ├── npm.ts # package.json#workspaces (npm + yarn classic + yarn berry)
│ └── index.ts # dispatcher: pnpm wins, npm fallback
├── routes/
│ ├── wrangler.ts # Cloudflare Workers (wrangler.jsonc)
│ ├── vercel.ts # Vercel (vercel.json)
│ ├── netlify.ts # Netlify (netlify.toml)
│ ├── fly.ts # Fly.io (fly.toml)
│ └── index.ts # dispatcher: aggregates all four
└── decisions/
└── index.ts # configurable path via paths.decisions:The drift detector — check-qb
check-qb reads context.qb.yaml at the repo root and compares three of its sections against authoritative sources of truth:
| Section in the qb file | Source of truth | What drift looks like |
| ---------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| tree: | pnpm (pnpm-workspace.yaml) or npm/yarn (package.json#workspaces) — pnpm wins when both exist | A new workspace exists without a tree: entry, or a tree: key has no matching directory |
| routes: | Cloudflare (wrangler.jsonc), Vercel (vercel.json), Netlify (netlify.toml), Fly (fly.toml) — aggregated | A config declares a route the qb file doesn't list, or vice-versa (with §5.2.1 exemptions for externally-managed routes) |
| decisions: | ADRs at docs/architecture/decisions/ or the path set in paths.decisions: | A new ADR exists without a decisions: entry, or the qb file's parenthesised status disagrees with the ADR's **Status:** line |
Adapter matrix
v2.0.0 ships adapters for the following sources of truth. If your setup isn't listed, the detector skips that section gracefully (no false positives).
| Section | Config / file | Package manager / platform | Notes |
| ------------ | ------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------ |
| tree: | pnpm-workspace.yaml | pnpm | Glob patterns evaluated against **/package.json |
| tree: | package.json#workspaces | npm / yarn classic / yarn berry | Falls back when no pnpm-workspace file; supports both array and object (packages:) forms |
| routes: | apps/*/wrangler.jsonc | Cloudflare Workers | Reads routes[] array |
| routes: | apps/*/vercel.json | Vercel | Reads routes[].src patterns |
| routes: | apps/*/netlify.toml | Netlify | Reads [[redirects]] table array |
| routes: | apps/*/fly.toml | Fly.io | Reads [[services.ports]] or [http_service] |
| decisions: | docs/architecture/decisions/*.md (default) | — | Matches NNNN-*.md naming convention |
| decisions: | Custom path via paths.decisions: in context.qb.yaml | — | Overrides the default; useful for repos with non-standard ADR locations |
Findings are emitted lint-style:
context.qb.yaml:32 error tree-missing-entry workspace "apps/billing" exists but is not declared in tree:
context.qb.yaml:71 error decisions-status-drift decisions["0010"] qb says "accepted" but ADR file says "superseded"
docs/architecture/decisions/0099-bad.md warning internal-adr-missing-status 0099-bad.md is missing a parseable `**Status:**` line; ADR omitted from decisions comparison
[check-qb] 3 findings: 2 errors, 1 warning, 0 infoOn a clean repo: [check-qb] no drift detected. and exit 0.
Findings are sorted by qbLine ascending (undefined last), then code alphabetical. Same sort in both human and JSON modes — diffing the two outputs is straightforward.
Exit codes
| Code | Meaning |
| ---- | ----------------------------------------------------------------- |
| 0 | No drift, OR only info-severity findings (wildcard routes etc.) |
| 1 | At least one error or warning finding |
| 2 | Malformed or missing qb file (loader-level abort) |
--json mode
pnpm check:qb --jsonEmits { findings: DriftFinding[], summary: { total, errors, warnings, info } } as a single line of compact JSON on stdout. Parseable by jq. Stable shape across v1.x. Useful for CI gates, IDE integrations, and pipelines.
Example:
pnpm check:qb --json | jq '.summary.errors'Installation
# Install as a dev dependency
pnpm add -D @context-qb/cli
# or with npm
npm install --save-dev @context-qb/cliThe package provides a contextqb binary that runs the drift detector.
Playbooks
Two operator-facing playbooks walk through the detector end-to-end:
- Set Up Drift Detection on Day One — for new projects authoring their first
context.qb.yaml. - Retrofit Drift Detection on an Existing Repo — for projects with accumulated state that predates the discipline.
Usage
# Run the drift detector
pnpm contextqb # detect drift against ./context.qb.yaml
pnpm contextqb --file path/to/other.yaml # detect drift in a different repo
pnpm contextqb --json # machine-readable output for CI / IDE / jq pipelines
pnpm contextqb --help # usage summaryOr add a script to your package.json:
{
"scripts": {
"check:qb": "contextqb"
}
}Note on --file: when you pass --file <path>, the file's parent directory becomes the implicit repo root for adapter reads (pnpm-workspace.yaml, apps/*/wrangler.jsonc, docs/architecture/decisions/*.md). This lets you point at a fully self-contained fixture or another repository without contaminating the comparison with the current repo's sources of truth.
Internal dogfooding
This repo (contextqb) consumes its own published @context-qb/cli from npm — the root package.json lists it as a devDependency and pnpm check:qb invokes the npm-installed binary, not the local source. This ensures the same code path is exercised in development as consumers will hit in production.
Library surface
If you want to drive the detector programmatically (e.g. from a test harness or a custom workflow), the public surface is the module's exports:
import {
loadContext,
lineOf,
detectTreeDrift,
detectRoutesDrift,
detectDecisionsDrift,
type DriftFinding,
type DetectorContext,
} from "@context-qb/cli";
const ctx = await loadContext(repoRoot);
const findings = [...detectTreeDrift(ctx), ...detectRoutesDrift(ctx), ...detectDecisionsDrift(ctx)];loadContext is import-safe — pulling it in does not trigger the CLI's main() (see the isEntryPoint guard in cli.ts).
Status
| Version | What | Status |
| ------- | ------------------------------------------------------------------------------------------------ | ------ |
| 1.0.0 | Initial drift detector (pnpm + Cloudflare + ADR default-path) | ✓ |
| 1.0.1 | Published to npm; dogfooded in this repo | ✓ |
| 2.0.0 | Multi-format adapter expansion (npm/yarn, Vercel/Netlify/Fly, paths.decisions:) | ✓ |
| 2.0.x | Security hardening — npm metadata, INV-PUB-1 stripping of private path leaks | ✓ |
| 2.1.0 | Telemetry completeness — event_kind, subcommand, adapter_coverage, schema v2 (ADR-0028 PR) | ✓ |
| 2.2.0 | Telemetry integrity model — signed requests, HMAC-SHA256 (ADR-0028, INV-INT-1) | ✓ |
| 2.3.0 | project_id support — per-project counting in cooperative aggregates (ADR-0032 / 0033) | ✓ |
| 2.4.0 | Always-on upgrade notice + contextqb upgrade subcommand (ADR-0034, INV-CLI-UPD-1) | ✓ |
| 2.4.1 | Documentation refresh (Status table, env-vars, first-run prompt clarity) | ✓ |
| 2.5.0 | project-id --accept flag + pending-suggestion guard rails (cli-upgrade-feedback-followup FB.2) | ✓ |
See the full changelog for release notes.
Design notes
- Adapters never emit findings. They surface per-file errors as
console.warnto stderr. Promoting those to realDriftFindings lands in Task 9 alongside JSON output. - Detectors are pure
(ctx) => DriftFinding[]. They never read the filesystem directly; the loader buildsctx.pathExistsas a bounded existence-check helper. - DNS hostnames are case-insensitive (RFC 4343). The routes detector lowercases on both sides of the comparison so a capitalised qb hostname matches a lowercase wrangler entry.
- The
index:synthetic key indecisions:is filtered out before comparison so it never firesdecisions-stale-entryfor lack of a0XXX-index.mdADR file. - No build step required during development.
tsxruns the TypeScript sources directly. Thetypecheckscript keeps the workspace covered bypnpm typecheck. A build step +dist/output can be added later when we want to publish to npm.
All Subcommands
The CLI provides five subcommands. check is the default if no subcommand is specified.
Usage: contextqb [subcommand] [options]
Subcommands:
check (default) Drift detector for context.qb.yaml
membership <cmd> Manage your membership (register|revoke|status|project-id)
mcp setup Generate MCP client config snippets
insights <topic> Query community insights (stack|structure|mistakes|deploy)
upgrade Print the install command for upgrading to the latest CLI version
Options:
--file <path> Use the qb file at <path>
--json Emit machine-readable JSON output
--no-telemetry Skip telemetry for this run
--telemetry-preview Print telemetry payload without sending
--telemetry-debug Show errors when telemetry fails
--help Show this message and exitcontextqb check (default)
Drift detector — documented in detail above.
contextqb membership
Membership management for the data cooperative (see the privacy & telemetry page for details).
contextqb membership register # Explicitly register (rare — auto-provision is the normal path)
contextqb membership revoke # Revoke membership and delete all server-side data (sticky opt-out)
contextqb membership status # Show current membership info
contextqb membership project-id # Show the current project_id
contextqb membership project-id --accept # Write the most recently suggested UUID
contextqb membership project-id --regenerate # Generate and write a fresh UUIDProject ID workflow: When you first run contextqb check on a project without a project_id, the CLI suggests a UUID and caches it locally. You can either copy it manually or run --accept to write it automatically. If you run --regenerate while a suggestion is pending, the CLI warns you and requires --force-fresh to proceed. Both --accept and --regenerate use yaml.Document.set, which preserves all comments and key ordering in context.qb.yaml.
Sticky opt-out (INV-6): When you revoke, the local credentials file is rewritten to { opted_out: true }. Subsequent CLI runs will not re-provision a token. To re-enable, delete the credentials file and run any contextqb command.
contextqb mcp setup
Generate a ready-to-paste MCP client config snippet with your membership token interpolated.
contextqb mcp setup # Default: emits both cursor and claude blocks as JSON
contextqb mcp setup --client cursor # Cursor-specific snippet only
contextqb mcp setup --client claude # Claude Desktop snippet only
contextqb mcp setup --client json # Both snippets in a single JSON object (same as default)contextqb insights
Query community-wide insights derived from anonymized telemetry.
contextqb insights stack # Defaults to --dim1 lang
contextqb insights stack --dim1 mono
contextqb insights structure # Defaults to --dim1 tree_entries
contextqb insights mistakes # Defaults to --dim1 validation_status
contextqb insights deploy
contextqb insights stack --json # Machine-readable outputcontextqb upgrade
Print the upgrade command for the version of the CLI that's currently installed. Instructional only — does not run any installer itself.
contextqb upgrade # Prints the right install command for your install method
contextqb upgrade --json # Machine-readable outputThe subcommand detects how the CLI was installed (npx, pnpm dlx, npm-global, pnpm-global, homebrew, local dev dependency) and prints the corresponding command. If the CLI is older than the latest published version, the upgrade notice (printed automatically on every check / membership / mcp / insights run) tells you to run contextqb upgrade for the install command.
Topics and default dimensions:
| Topic | Dimensions | Default dim1 |
| ----------- | ------------------------------------- | ------------------- |
| stack | lang, mono | lang |
| structure | tree_entries, routes, decisions | tree_entries |
| mistakes | validation_status | validation_status |
| deploy | platform | (none) |
Environment Variables
CI environments are auto-detected by default — the CLI skips auto-provisioning when it sees GITHUB_ACTIONS, GITLAB_CI, CIRCLECI, and similar signals. See the privacy page for the full list of probed env vars.
| Variable | Description |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| CONTEXTQB_NO_PROVISION=true | Manual opt-out: skip auto-provisioning of membership. Useful on dev machines where you want no telemetry at all. |
| CONTEXTQB_FORCE_PROVISION=true | Override CI auto-detect: force provisioning even when a CI environment is detected. Useful for long-lived self-hosted runners you want counted as cooperative members. |
| CONTEXTQB_HOME=<path> | Override the credentials directory (default resolved via env-paths v4 — see paths below). |
| CONTEXTQB_UPDATE_CHECK=npm | Telemetry-opt-out users only. Re-enables the upgrade notice by polling https://registry.npmjs.org/@context-qb/cli/latest at most once every 24 hours. The cache is local and is never transmitted. Without telemetry, the server-piggybacked cli_version_latest signal does not arrive, so this is the fallback path. Opt-in only — not the default. |
Credentials file location
The CLI uses env-paths v4 with project name contextqb. Defaults per OS:
| OS | Path |
| ------- | --------------------------------------------------------- |
| macOS | ~/Library/Preferences/contextqb-nodejs/credentials.json |
| Linux | ~/.config/contextqb-nodejs/credentials.json |
| Windows | %APPDATA%\contextqb-nodejs\Config\credentials.json |
The -nodejs suffix is added by env-paths to avoid clashing with native applications. When CONTEXTQB_HOME is set, the file inside that directory is still named credentials.json.
Token Rotation
v1 does not provide an in-place token rotation endpoint. To rotate a token:
- Run
contextqb membership revoke(deletes server-side data, writes sticky opt-out locally) - Delete the credentials file (path varies by OS — see the table above)
- Run any
contextqbcommand — a fresh membership is auto-provisioned
See the apps/mcp/README on GitHub for server-side documentation.
About
ContextQB is a methodology and tooling project for building software with AI agents — principles, playbooks, audits, prompts, and the context.qb format that gives agents a stable, machine-validated map of your repo. Made by the technologists at Industrial Semiotics. Learn more at contextqb.com.
License
MIT.
