Testing
This page describes safe local tests, linked tests, coverage, and release-readiness checks.
Tracked implementation updates: pm-52eh, pm-mcxr, pm-u42x, pm-atfm, pm-xmp5, pm-39cqqx, pm-5cgm2z, pm-avv3wx.
Agent Quick Context
- Unit and integration tests must not read or write real
.agents/pmdata. - Prefer
node scripts/run-tests.mjs ...because it creates sandboxedPM_PATHandPM_GLOBAL_PATH. - Linked tests added through
pm testshould use sandbox-safe commands. Package-manager scripts such aspnpm testare allowed because linked-test execution injects isolatedPM_PATHandPM_GLOBAL_PATH; direct runners such asviteststill neednode scripts/run-tests.mjs ...or inline sandbox env. - Run linked tests before closing the item that owns the work.
Tracked documentation work: pm-u9d0.
Local/hosted gate parity is tracked by pm-ei6x66.
Standard Local Checks
pnpm build
pnpm lint
pnpm typecheck
node scripts/run-tests.mjs test
node scripts/run-tests.mjs coverage
For the exact ordered local preflight used to make release-readiness claims, run the registry-owned entrypoint:
pnpm verify:preflight
scripts/release/gate-registry.json is the executable plan for this command:
each ordered step declares its command, arguments, environment, capture mode,
and whether an explicit skip flag is permitted. Receipts distinguish passed
steps from declared skips, and quality:gate-registry maps hosted workflow
claims to the same canonical gate IDs. Hosted-only environment isolation and
tracker-integrity steps remain explicit entries with reasons rather than
silently disappearing from local parity.
node scripts/run-tests.mjs wraps Vitest in temporary tracker roots, then cleans them up.
Public SDK changes additionally run semantic surface and import-cost contracts:
pnpm sdk:surface:check
pnpm benchmark:sdk-entrypoints:check
pnpm benchmark:transport:check
The surface gate detects exported signature, type-parameter, declaration-kind, and stable error-code drift across the aggregate and every narrow SDK entrypoint. The performance gates protect entrypoint import cost and one-item CLI cold-start overhead without touching the repository tracker.
pnpm lint is the local CodeFactor parity check. It layers ESLint rules for
shipped source, package, plugin, and script surfaces that match the CodeFactor
maintainability findings this repo tracks (complexity,
no-unsafe-optional-chaining, and the relevant eslint-plugin-unicorn
mechanical rules), jscpd duplicate detection across source and tests, and the
repo-specific quality:static gate. The dedicated quality:static gate remains
authoritative for source/exported docstring coverage, orphan-module checks,
directory-load caps, and the TypeScript-aware duplicate/complexity checks that
are tailored to pm's source layout; pnpm lint delegates to it instead of
running a second threshold profile. It also includes a changed-file
CodeFactor-parity complexity scan for shipped source, package, and script files
so PR-local CodeFactor maintainability annotations fail locally before commit or
push. Existing legacy high-complexity test fixtures are tracked separately and
must not be used as precedent for new changed production/script code.
The local parity rules catch analyzer classes before push. The mandatory hosted proof runs after the final commit is pushed and both apps have finished:
pnpm quality:hosted-analysis
This command reads commit-scoped GitHub results for git rev-parse HEAD. It
passes only when DeepScan explicitly reports 0 new issues and CodeFactor
completes successfully with No issues found. The exact commit is authoritative.
For GitHub merge and squash commits whose apps report only on the reviewed PR
head, the gate accepts that evidence only after proving the source and target
have the same immutable Git tree. Squash commits also require one unambiguous
GitHub PR association to a closed PR merged into main. Missing, pending,
failed, skipped, stale, ambiguous, and different-tree results all fail.
pnpm release:gates includes the same non-skippable verification, so run it
only after the pushed reviewed head's hosted analyzers are terminal.
Focused Test Runs
node scripts/run-tests.mjs test -- tests/unit/output.spec.ts
node scripts/run-tests.mjs test -- tests/integration/cli.integration.spec.ts
Use focused runs while iterating, then run coverage before closure when risk or scope warrants it.
Coverage Governance
Coverage gating now targets literal all-source coverage across runtime code families:
vitest.config.tsincludes canonical authoring sources acrosssrc,packages(.ts),scripts,plugins, anddocs/examples.- Generated package JavaScript mirrors are not separately gated; coverage is enforced on their TypeScript sources.
- Thresholds are strict
100/100/100/100for lines, branches, functions, and statements. - Avoid reintroducing curated coverage allowlists; keep the full all-source corpus measurable and gated.
- Prefer extending existing test files and shared helpers so new coverage remains fast and non-duplicative.
- Prefer extracting deterministic pure helpers (and unit-testing them) when an orchestration-heavy file is difficult to cover directly.
Static quality also enforces source documentation coverage through pnpm quality:static: every src/**/*.ts source file needs a module TSDoc block,
every exported declaration needs a non-module TSDoc block, and known generated
boilerplate summaries are rejected.
TOON Storage Round-Trip Gate
TOON item serialization is a fail-closed storage boundary. Before returning
bytes to any create, update, merge, migration, or package caller, pm decodes the
new @toon-format/toon output and compares it with the JSON-like canonical
payload. A decode failure or the first field-level mismatch raises
item_document_roundtrip_failed; no item bytes are written.
The property suite exercises generated and adversarial agent text, including array-header spellings, colons, quotes, backslashes, newlines, Unicode, comment prefixes, and bracketed endpoint-shaped strings:
node scripts/run-tests.mjs test -- tests/fuzz/project-boundaries.fuzz.spec.ts
Major codec upgrades additionally require a complete read/serialize/read sweep
over every tracked .toon item, strict history and storage validation, the
packed npx consumer smoke, and Bun SDK/bunx execution. A dependency-only green
unit suite is not sufficient evidence for changing the canonical storage
codec.
Directory-load cap and the tests/unit split
pnpm quality:static also caps each directory under src/, tests/, and
packages/ at 120 .ts files (--max-files-per-dir, default 120). This
keeps any single directory navigable and forces load to be partitioned by area
rather than piling into one folder. tests/unit/ is therefore split into
per-area subdirectories (tests/unit/commands/, tests/unit/core/,
tests/unit/cli/, tests/unit/mcp/, tests/unit/extensions/, …) instead of a
single flat directory.
When adding a unit test, place it in the matching tests/unit/<area>/
subdirectory (or merge into an existing spec there); never add a file directly
to tests/unit/. The
static-quality-gate directory-load contract
test asserts the live repository stays at or below the cap and that the magic
number matches the gate default, so drift is caught before CI's static gate
fails.
Search Quality Evaluation
search-advanced exposes an advisory golden-query harness for relevance drift checks.
Fixture source:
tests/search-eval/golden-queries.json
Local run:
pm install search-advanced --project
pm reindex --mode keyword --eval --eval-fixtures tests/search-eval/golden-queries.json --json
Fixture authoring notes:
- Each fixture must include
query,expected_top_ids, and optionallymode(keyword|semantic|hybrid) andmin_ndcg_at_5(0..1). - Keep expected IDs deterministic and scoped to stable seed data so CI does not flap.
- Add new fixtures for regressions before tuning search defaults.
The repository-native pm eval corpus is a required CI and release gate:
pnpm build
pnpm quality:retrieval-eval
tests/search-eval/retrieval-gate-baseline.json enforces query count, nDCG,
MRR, precision, and recall. The corpus includes at least one intentionally
non-saturated judgment set so recall regressions remain observable. The
required context-quality command also runs an impossible perfect-score negative
control and fails if pm eval --fail-under stops returning a non-zero exit.
Refresh the baseline only with pnpm quality:retrieval-eval:update after
reviewing query-level ranking changes.
Context Quality Evaluation
The required context relevance gate proves that pm context and pm next
assemble the right bounded working set, not only that search returns relevant
documents.
Fixture and baseline sources:
tests/context-eval/golden-scenarios.json— reviewable scratch, real-shaped, synthetic-scale, and returning-agent continuity judgments.tests/context-eval/baseline.json— committed aggregate and per-scenario metrics from the accepted structural/scorer behavior.
Run the required gate locally:
pnpm build
pnpm quality:context-eval
The gate creates isolated temporary trackers through PmClient, reads them only
through the public context() / next() SDK primitives, and reports nDCG,
reciprocal rank, required-item recall, continuity coverage, token-budget
adherence, and served-item signal attribution. It fails when an explicit corpus
threshold is missed or any aggregate metric regresses below the committed
baseline.
Benchmark and evaluation populations that need realistic project structure
should use the public SDK shapes documented in
Portable Corpus Shapes. pnpm benchmark:corpus-shapes
remeasures the same SDK operations across equal-count scratch and
representative populations and records their measured profiles.
When a deliberate ranking change improves or intentionally redefines the golden judgments, review the scenario-level diff first, then refresh the baseline:
pnpm quality:context-eval -- --update
pnpm quality:context-eval
Do not update the baseline merely to make CI green. Change judgments and rationales in the corpus when product intent changes, and commit the corpus, baseline, scorer tests, and SDK documentation together.
Hosted Gate Registry
Tracked by pm-k6t4yb and pm-b2hc4x.
Every named workflow step that makes a build, test, quality, security, package,
or release claim is discovered and matched exactly against
scripts/release/gate-registry.json. Each registry entry declares:
- a canonical pm owner;
- the enforced workflow steps it owns;
- actionable failure taxonomy;
- explicit bypass policy and audit rationale;
- an executable negative-control test and assertion.
Run the fail-closed inventory locally:
pnpm quality:gate-registry
node scripts/release/gate-registry.mjs --inventory
pnpm quality:static includes the registry. A newly named workflow gate fails
until it has an owner and negative-control proof; a removed or renamed workflow
step also fails until stale policy is reconciled. Public source claims are
mapped to exact evidence strings and an enforced registry entry so
documentation cannot silently advertise advisory behavior.
Tracker Context-Quality Ratchets
Tracked by pm-ips23h and pm-kpftft.
scripts/release/tracker-measurements.json turns measured tracker populations
into reviewed, shrinking-only ceilings. Each declaration names its canonical
pm owner and a selector over stored dependency kinds, validator warnings,
graph-profile fields, or health-check severity. Open owners enforce their
ceiling; terminal owners retire it. Validator warnings and health checks are
exhaustive, so a newly observed class fails until it has a canonical owner and
reviewed declaration.
Run the same gate used by hosted CI:
pnpm quality:tracker-measurements
node scripts/release/tracker-measurement-gate.mjs --negative-control
--update can only lower a ceiling. It refuses to write while any population
has regressed or remains undeclared, so updating the baseline cannot absorb a
failure. Dependency-kind regressions include the post-measurement item, target,
author, timestamp, and mutation-source rows in the JSON receipt.
Agent Output Token Budgets
Run the required PR gate locally:
pnpm build
pnpm quality:token-budget
The isolated fixture contains a medium-scale linked workspace. Discovery
surfaces (--help and contract projections) use reviewable ratcheted byte
baselines. Answer surfaces including activity, deps, graph, duplicates, events,
health, list, get, context, next, search, and validate are measured against
their live SDK command contracts. A deliberately full, unbounded activity query
must exceed the default contract, proving the gate can fail when a default
becomes accidentally unbounded.
Refresh scripts/release/token-budgets.json only for an intentional discovery
surface change:
node scripts/release/token-budget-gate.mjs --update
pnpm quality:token-budget
The manifest records baseline bytes and estimated tokens for visible review
deltas. Updating it cannot raise answer ceilings, which remain owned by
PM_COMMAND_OUTPUT_BUDGET_CONTRACTS.
Linked Tests
Add tests to the item that owns the work:
pm test <item-id> --add command="node scripts/run-tests.mjs test -- tests/unit/output.spec.ts",timeout_seconds=240
pm test <item-id> --run --progress
For broader sweeps:
pm test-all --status in_progress --progress
Do not link pm test-all itself as an item-level test command. It creates recursive orchestration.
Use --fail-on-empty-test-run for release/readiness gates where selecting zero
linked tests should fail instead of producing an inconclusive pass.
Use --progress for long foreground sweeps; it prints parent-level
pm test-all selection, per-item start/end, and final summary lines in addition
to the linked-test command progress emitted by pm test.
Package Ecosystem Smoke
After pnpm build, external package compatibility can be checked without
touching the repository tracker:
pnpm smoke:external-packages -- --limit 10
pnpm smoke:external-packages -- --package pm-changelog
The harness creates one temporary project per package, sets sandboxed PM_PATH
and PM_GLOBAL_PATH, installs the package with pm install npm:<name> --project, runs pm package doctor --project --detail deep --trace, and probes
runtime contracts with pm contracts --runtime-only --availability-only. Use
--discover-only for the npm package list and --keep-temp only when debugging
a failing package root.
PM Context Modes
Linked PM commands default to schema context: settings and extensions are seeded, but tracker item data stays isolated.
When a linked command is a PM tracker-read such as pm validate, the default mismatch error suggests
--auto-pm-context, which keeps schema isolation for ordinary commands and routes only tracker-read PM commands
through seeded tracker data.
Use explicit modes when needed:
pm test <item-id> --run --pm-context schema
pm test <item-id> --run --pm-context tracker
pm test <item-id> --run --pm-context auto --check-context --auto-pm-context
For complex linked-test commands, prefer JSON input so shell syntax survives unchanged:
pm test <item-id> --add-json '{"command":"node scripts/run-tests.mjs test -- tests/unit/output.spec.ts","timeout_seconds":240}'
To rerun a focused subset without editing linked-test metadata:
pm test <item-id> --run --match output
pm test <item-id> --run --only-index 2
pm test <item-id> --run --only-last
Linked-command stdout and stderr are captured through temporary regular files,
then read back with the 20 MiB per-stream retention bound. This keeps inherited
descriptors blocking for Bun, Rust, and other runtimes that treat EAGAIN on a
non-blocking stdout pipe as fatal. Output beyond the bound is drained to disk,
the child still completes normally, and the result records a truncation notice
instead of misclassifying a transport failure as a test assertion.
Strict governance flags:
pm test <item-id> --run \
--fail-on-context-mismatch \
--fail-on-skipped \
--fail-on-empty-test-run \
--require-assertions-for-pm
Structured quantitative evidence can be recorded and queried without parsing logs or comments:
pm test <item-id> --run \
--measure coverage=100,unit=percent,threshold=100 \
--measure p95_latency=42,unit=ms,threshold=50
pm test <item-id> --metric-below coverage=100 --metric-diff p95_latency
Measurements are stored on the producing test_runs row, retained with the
bounded run history, and exposed consistently by CLI, SDK, MCP, and contracts.
Linked-Test Assertions
Linked tests can include assertion metadata:
pm test <item-id> --add \
command="pm list-open --json",timeout_seconds=120,assert_json_field_gte=count:0
Common assertion keys include:
assert_stdout_containsassert_stdout_regexassert_stderr_containsassert_stderr_regexassert_stdout_min_linesassert_json_field_equalsassert_json_field_gte
Background Runs
pm test <item-id> --run --background
pm test-all --status in_progress --background
pm test-runs
pm test-runs status <run-id>
pm test-runs logs <run-id> --tail 100
pm test-runs stop <run-id>
pm test-runs resume <run-id>
Background run fingerprints prevent duplicate parallel runs for the same linked-test set.
For long test-all runs, pm test-runs status <run-id> --json includes the
latest aggregate item coordinates (item_index, item_total, item_id) plus
linked-test coordinates and current_command when the child emits progress.
Use pm test-runs logs <run-id> --stream stderr only when the compact status
message is not enough.
The bundled pm-linked-test-adapters package is the first-party package
exemplar for background run management. Install it in an isolated project when
validating package-provided test-run surfaces:
pm install linked-test-adapters --project
pm package doctor --project --detail deep --trace
pm test-runs list --json
The package activates the test-runs command family and keeps subprocess
handling behind an explicit package permission declaration, so it is a useful
smoke target for package, permission, and command-contract changes.
Release-Readiness Checks
For substantial changes:
pnpm build
pnpm typecheck
node scripts/run-tests.mjs coverage
pm validate --check-resolution --check-history-drift
pm health --check-only
When release readiness requires external GitHub security telemetry in addition to local checks, run:
gh issue list --state open --limit 100 --json number,title,updatedAt,url
gh pr list --state open --limit 50 --json number,title,headRefName,reviewDecision,url
gh api "repos/unbraind/pm-cli/dependabot/alerts?state=open&per_page=100"
gh api "repos/unbraind/pm-cli/secret-scanning/alerts?state=open&per_page=100"
gh api "repos/unbraind/pm-cli/code-scanning/alerts?state=open&per_page=100"
code-scanning/alerts can return 404 no analysis found until at least one
CodeQL run has completed.
For documentation-only changes, at minimum run:
pnpm build
rg -n "forbidden-private-token-or-path" README.md docs
Replace the placeholder pattern with the actual sensitive term being guarded in the current task.
Contract Snapshot Gate
Tracked by pm-d6kq.
pm contracts --full --json is a public machine-readable SDK and agent surface.
Keep its committed golden snapshot current when command contracts, schemas,
aliases, or extension-provided command contracts intentionally change:
pnpm build
pnpm contracts:update
pnpm contracts:check
CI runs pnpm contracts:check in the static gate. Snapshot diffs should be
reviewed like an API change and paired with the package-owned changelog flow
when the contract surface changes intentionally.
The agent-facing output-size gate is separate from schema drift. Run
pnpm quality:token-surface after changing help, contracts, or MCP tool
registration. It measures root help, every advertised command help page, the
summary/default/full contracts family, and MCP tools/list against
scripts/agent-token-surface-baseline.json. Use
pnpm quality:token-surface:update only for an intentional reviewed change;
the baseline stores explicit byte ceilings with headroom rather than volatile
timestamps or package versions.
The CLI contract snapshot is separate from the TypeScript SDK surface snapshot. When public SDK declarations or SDK error codes change, also run:
pnpm sdk:surface:check
Use pnpm sdk:surface:update only for reviewed additive changes. Breaking
changes require the explicit --acknowledge-breaking "<release rationale>"
argument documented in SDK.