@unbrained/pm-lint
v2026.10.31
Published
Authoring-quality lint rules for useful pm item context
Maintainers
Readme
pm-lint
Project management = context management. Vague work items waste agent time because they cannot explain what to do, how to verify it, or what already happened. pm-lint judges authoring quality. Use it alongside pm validate for structure and references, pm health for workspace health, and pm duplicates for duplicate discovery.
The npm package is @unbrained/pm-lint. The pm extension identity, catalog display name and command names remain pm-lint: existing pm lint commands do not change. The public package homepage remains https://pm-cli.unbrained.dev/packages/pm-lint.
This package is unreleased and under local review. Registry commands become available after maintainer-approved publication. Minimum host SDK: 2026.10.23; Node >=22.18.0.
With a pm host already installed, activate the registry package in your tracker:
pm install npm:@unbrained/pm-lint
# Equivalent current spelling:
pm package install npm:@unbrained/pm-lint
pm lintThe pinned pm host requires the npm: source prefix for registry packages, including scoped names. A bare pm install @unbrained/pm-lint is interpreted as a local source. Installation activates the manifest named pm-lint in the tracker; npm and Bun installation alone do not activate it.
For a local SDK dependency and tracker activation:
npm install --save-dev @unbrained/pm-lint @unbrained/pm-cli
npx pm package install ./node_modules/@unbrained/pm-lint
npx pm lint
bun add --dev @unbrained/pm-lint @unbrained/pm-cli
bunx --bun pm package install ./node_modules/@unbrained/pm-lint
bunx --bun pm lintGlobal package-manager installation is also available:
npm i -g @unbrained/pm-lint
bun add -g @unbrained/pm-lintThis extension exports an SDK and has no standalone executable. npx @unbrained/pm-lint and bunx @unbrained/pm-lint cannot run it directly; invoke the pm host instead:
npx --package @unbrained/pm-cli pm package install npm:@unbrained/pm-lint
bunx --bun --package @unbrained/pm-cli pm package install npm:@unbrained/pm-lintFor review before publication, build a tarball in the package checkout:
npm ci
npm run build
npm packIn a fresh directory outside any Git repository, install the host and initialize a tracker, then install the packed artifact (copy it into that directory first):
npm install @unbrained/[email protected]
npx pm init lint-consumer --defaults --agent-guidance skip
npx pm install ./unbrained-pm-lint-2026.10.13.tgz
npx pm lint --jsonThe same packed artifact works with bun add and bunx --bun pm. Other lint commands include:
pm lint rules --json
pm lint --output-include item_id,rule,title,excerpt --output-budget 2000
pm lint --rule PML002 --rule PML004 --fail-on warn
pm lint --disable PML006 --severity error --type Feature --status open
pm lint --id project-abcd --id project-efgh --since 2026-10-01
pm --pm-path path/to/tracker/.agents/pm lint --json--rule, --disable and --id are repeatable. --rule selects exactly those rules and can re-enable rules disabled in settings; --disable wins when both flags name the same rule. --severity overrides every enabled rule's severity. --fail-on defaults to error and is inclusive: warn fails on warnings and errors, info on every finding. --since selects items updated on or after a calendar date (UTC) or explicitly zoned ISO timestamp. Types match exact SDK metadata values. --status accepts comma-separated statuses and overrides each rule's default scope for the selected items. Item IDs must exist in the certified corpus.
The commands use the host renderer: TOON by default and global --json for automation. Every finding has item_id, rule, severity, field, hint, the item title, and an excerpt of the offending value trimmed to at most 160 characters. Missing-field findings use an empty excerpt. PML003 excerpts the first failing criterion; PML009 excerpts the unresolvable blocker reference. Treat diagnostic output as source context: it may contain private item text.
summary counts findings for every rule and severity, including zeros. scanned is the selected item count; corpus_count and complete_list prove the entire tracker was read before selection. Lint never edits an item or records test results in the scanned tracker. Both pm lint and pm lint rules declare the SDK read_output contract, so host projection (--output-include / --output-exclude), row limits, output budgets and --output-cursor continuations apply. Bounded output can contain fewer rows than count; read the host truncation receipt and follow every cursor, or use --output-budget unbounded --output-limit unbounded for a full report. Exit status and summary describe the full evaluation even when rendered rows are bounded.
pm lint rules lists each rule's code, default severity, default scope, field, description, hint and settings key.
Incremental complete lint
pm lint incremental --state lint.local.json --rebuild --json
# After editing an item:
pm lint incremental --state lint.local.json --jsonThe first scan requires --rebuild. Later scans retain the complete result,
including findings on unchanged items. Each rule/item pair records its declared
native-field dependencies and outcome. An unchanged local predicate and its
bounded excerpt are reused; titles and full source digests come from the current
SDK read. incremental.by_rule reports actual predicate calls and reused pairs.
The receipt and findings support the host's TOON/JSON projection, output budgets
and continuation controls. The checkpoint itself is never printed.
| Rules | Declared inputs | | --- | --- | | PML001 | title | | PML002 | type, acceptance criteria | | PML003 | title, acceptance criteria | | PML004 | title, resolution, actual result, close reason/time, comments | | PML006 | type, files, tests | | PML008 | title, description, notes | | PML005, PML007, PML009, PML010 | Global: clock/activity, hierarchy, local blocker references, SDK duplicate clusters |
Status affects every pair's applicability. Global predicates run on every scan
within their configured scope; duplicate analysis remains owned by the SDK.
Every scan still certifies the complete corpus, hashes native metadata and
drains SDK event pages. This reduces rule evaluation work, while retaining full
read and certification costs. It is separate from pm lint --since, which
selects a partial set of items by time.
Cursor loss, missing history anchors, unrecorded native imports, rule/dependency
version changes and policy/status-role drift refuse explicitly. Review the
typed refusal.code and refusal.remedy, then use --rebuild. Foreign state is
skipped even with --rebuild; choose a separate file. A malformed checkpoint
must be preserved or removed deliberately before rebuilding. Limits are 50,000
items, 1,000,000 event rows per invocation (including anchor verification) and
64 MiB per checkpoint. No scan silently truncates.
Keep this derived cache in a trusted, ignored file: it includes item ids,
dependency hashes, outcome bits and bounded finding excerpts. Its checksum
detects accidental damage; it is not an authenticity signature. The file adapter
uses a core token-owned lease, checks current checkpoint ownership and re-reads
all source revisions through the SDK before fenced atomic publication. Peers'
checkpoints and foreign records are preserved. Native item state is read only.
Checkpoint and baseline publication finishes all awaited work before a final
synchronous generation/byte/access-policy check and rename under its held lease
or lock. New sidecars are owner-only (0600 or stricter under the umask);
existing access bits, owner and group are retained. Staging files stay private
until commit, and cleanup verifies staging identity, owned bytes and access
policy before unlinking. Foreign data is retained even when it overwrites the
same staging inode.
The typed SDK exposes lintIncremental, IncrementalLintSource,
IncrementalCheckpoint, strict checkpoint/request parsers, RULE_DEPENDENCIES
and IncrementalRefusal. A source injects native SDK complete reads, event pages
and configured policy/roles, so hosted adapters can use their own compare-and-set
checkpoint storage. The optional filesystemLintSource / lintIncrementalFile
adapter implements the CLI's local cache. See the SDK example
and measured validation.
Install and release approval
The first npm release is pending the maintainer's interactive bootstrap. After
that first publish, the maintainer configures npm trusted publishing for
release.yml and sets the repository variable PM_RELEASE_APPROVED to true.
The daily workflow and manual dispatch both require this variable and main.
Publishing uses OIDC with provenance; no stored npm token is needed.
Once published, install the package and compatible pm host, then activate the extension in an initialized pm project:
npm install @unbrained/pm-lint @unbrained/pm-cli
npx --no-install pm package install ./node_modules/@unbrained/pm-lint
bun add @unbrained/pm-lint @unbrained/pm-cli
bunx --bun --no-install pm package install ./node_modules/@unbrained/pm-lint
pm package install npm:@unbrained/pm-lint
pm lint --helpThe package exports its SDK through main and types and registers commands
with pm. It has no standalone bin: use npx pm or bunx --bun pm through
the installed host. npm/Bun dependency installation alone does not activate it.
Evidence-backed baselines
Use a baseline to keep specific old findings visible while failing every new finding. Each finding now includes rule_version, a SHA-256 source_digest of its full canonical source item, and disposition (new or debt). Digests include all item metadata and full text, including text beyond the displayed excerpt. Any source-item change invalidates its acknowledgments; unrelated new items preserve existing debt. Rule semantics changes require a RULE_VERSION increment and invalidate older evidence.
pm lint --json --output-include item_id,rule,rule_version,source_digest
# Copy one observed finding's identity, version and digest; review its full source.
pm lint acknowledge --baseline lint-baseline.json \
--id project-abcd --rule PML002 --reason 'Legacy criteria debt; repair scheduled' \
--rule-version <observed-version> --source-digest <observed-sha256>
pm lint --baseline lint-baseline.json --json
pm lint --baseline lint-baseline.json \
--output-include item_id,rule,disposition --output-budget 2000Acknowledgment creates or updates only the named JSON file. It rechecks the complete tracker and refuses missing findings or changed evidence. A nonempty reason of at most 1,000 characters is required. There is no bulk acknowledgment or wildcard. Commit the reviewable baseline file alongside the policy decision. Cooperating CLI writers use an exclusive lock and atomic replacement; a busy lock returns a retry remedy. Do not edit the file concurrently with a command.
The baseline gate requires the complete tracker and all ten default rules. It refuses selection flags, severity/failure-threshold overrides, disabled rules and nondefault lint settings with unsafe_policy; restore default settings before using it. Output retains all findings and the ordinary per-rule summary, adds debt_count, new_count, stale_count, and labels each row. stale_count includes resolved findings and obsolete evidence; stale entries never count as debt. Exit 0 means no new findings, 1 means new findings at any severity (fail_on: info), and 2 is a typed baseline refusal with code, message and remedy. Malformed or missing baseline files fail closed. Host projection and continuation controls apply to baseline checks too.
The SDK exports LintBaseline, Acknowledgment, BaselineRefusal, parseBaseline, sourceDigest, RULE_VERSION and acknowledgeFinding. lintItems(client, pmRoot, { baseline }) uses the same complete gate on any SDK tracker backend. SDK baseline refusals throw BaselineRefusal. CLI command handlers adapt them with createPmCliExpectedError: exit 2, the same domain code and message, and the remedy in context.nextSteps. The host owns error rendering and expected-error classification. This replaces the former successful-output { ok: false, refusal: { code, message, remedy } } envelope; CLI consumers must read the native error diagnostic instead (on the error stream). Successful lint and acknowledgment receipts are unchanged. See the SDK example, public-data validation and SDK adoption evidence. A baseline records an explicit authoring-debt decision; it does not certify that the finding or its reason is true.
Acceptance-criterion witnesses
pm lint witnesses --contract criterion-witnesses.json checks the declared link
from every stored criterion to a linked test, a file observation, or explicitly
pending proof. It never runs code, reads a Git history, fetches URLs, or guesses
that prose establishes support. Core SDK criteria parsing and certified complete
reads apply to every lifecycle status, including closed and canceled items.
pm lint witnesses --contract criterion-witnesses.json --json
pm lint witnesses --contract criterion-witnesses.json \
--output-include item_id,criterion,state,citation --output-budget 2000The strict version 1 JSON contract contains bindings, each with item_id, exact
criterion text, and a nonempty witnesses array. Unknown keys and duplicate
item/criterion bindings refuse. Criteria use core's semicolon boundaries; exact
text identity survives reordering and unrelated item edits. Every criterion
without a binding reports missing, even if its item contains tests or comments.
| Witness kind | Required fields beyond kind | Meaning |
| --- | --- | --- |
| test | command and/or path, sha256, outcome | Match one exact linked-test declaration; sourceDigest(test) binds all metadata, including provenance and assertions |
| observation | path, sha256, outcome | Hash the exact local artifact bytes with observationDigest(bytes) |
| pending | reason | Explicitly unfinished proof with a nonblank reason |
Test and observation outcomes are explicitly declared pass or fail. A
supported row means the reference and digest match the declaration. It does
not certify that a test ran, that an observation proves the criterion, or
that a claimed outcome is true. Digest refreshes require evidence review.
pending is visible unfinished work and always fails the gate.
| State | Interpretation |
| --- | --- |
| supported | Evidence reference and complete digest match a declared pass |
| pending | Proof is explicitly deferred |
| missing | Current criterion has no declared binding |
| dangling | Referenced item, linked test, or file does not exist |
| stale | Criterion text was removed/changed, or referenced evidence digest changed |
| contradictory | Outcome declares failure against the criterion, or test identity is ambiguous |
Each witness produces a row. A failing outcome plus stale/dangling evidence produces both findings so neither condition hides the other. Mixed pass/fail witnesses remain contradictory; passing evidence cannot suppress any failure or pending proof. Removed-item bindings are dangling; removed-criterion bindings are stale, and replacement criteria remain missing until explicitly bound.
Observation paths are relative to the evidence workspace. They retain exact
citation spelling, including :line[:column] or #Lline[-Lline]; the digest
covers the whole file. Traversal, absolute observation paths and symlinks escaping
the workspace refuse. Missing files are dangling; unreadable artifacts refuse.
Test paths are exact tracker identities, with no filesystem lookup.
The host renders TOON by default or JSON with --json. findings rows carry
item_id, criterion, state, exact citation, and a remedy. Rows sort by exact
UTF-16 item_id, criterion, citation, then state, independently of host locale
and declaration order. Unicode spellings are never normalized. Counts and summary
cover the full evaluation; host projection, budgets, row limits and cursor
continuations apply. count counts finding rows, criteria_count counts stored
criteria, and corpus_count counts certified items. Exit 0 means every finding is
supported, including when the tracker has no criteria (a contract binding for a
removed item still emits dangling and exits 1), 1 means any
unfinished/invalid proof, and 2 means a typed refusal with code, message and
remedy. SDK input/I/O refusals throw WitnessRefusal; incomplete tracker reads
remain SDK errors and never become a passing empty corpus.
SDK exports include lintWitnesses(client, contract, evidenceRoot),
parseWitnessContract, readWitnessContract, observationDigest,
WitnessRefusal, and typed contract/result interfaces. The sidecar and artifacts
are caller-owned local inputs; the client can target any supported tracker
backend. No witness command changes tracker data. See the
SDK and JSON example and
public tracker validation.
Without git
Linting, incremental checkpoints, rule listing, acknowledgment, baseline checks and witness checks work in plain directory trackers without a git executable. These commands do not require repository history. The SDK baseline model is independent of git and tracker storage; CLI baseline and checkpoint files use the local filesystem.
| Code | Default severity | Default scope | Field | Checks | | --- | --- | --- | --- | --- | | PML001 | warn | non_terminal | title | Placeholder, under three words, or ticket-only title | | PML002 | error | non_terminal | acceptance_criteria | Feature, Issue or Task lacks criteria | | PML003 | warn | non_terminal | acceptance_criteria | Placeholder criterion, title restatement, or fewer than three words without an observable noun or verb | | PML004 | error | closed | resolution | Missing closure evidence beyond the title | | PML005 | warn | active | updated_at | Stale in-progress work or stale ownership without recent activity | | PML006 | info | all | files | Feature has neither linked files nor linked tests | | PML007 | warn | non_terminal | parent | Epic has no children in the complete tracker | | PML008 | info | non_terminal | description | Description or notes only repeat the title | | PML009 | error | blocked | blocked_reason | Missing blocker reason or unresolvable local blocker | | PML010 | warn | active | title | Active titles form SDK duplicate clusters |
Authoring rules skip terminal history by default. non_terminal and active cover unfinished statuses, including blocked and draft. PML010 uses statuses carrying the SDK registry's active or blocked roles. Scope restricts which items emit findings; hierarchy and duplicate context still use the complete corpus. A per-rule scope setting changes that default; explicit --status takes precedence over settings and evaluates every enabled rule on the named lifecycle. Rule-specific conditions still apply (for example, PML002 checks only work types and PML005 checks working or owned work). Working statuses carry the active role without default-open; custom status names retain those semantics.
PML003 deliberately flags vague assertions rather than requiring every fragment to match a verb whitelist. works, done, fixed, complete, ok, as expected, looks good, should work, TBD, N/A, and see description are rejected, as are assertions identical to the title. Concrete examples such as pm-cli-data.json regenerated, Any drifted snippet is corrected in all locales, and Publication and deployment state distinguished from candidate validation pass. Semicolons and newlines delimit assertions; the first vague fragment produces one item-level finding. This conservative heuristic does not prove that an outcome is true or sufficiently specified.
Each rule is individually configurable. Heuristics are authoring prompts and do not certify the truth of item evidence. PML010 uses the public SDK analyzeDuplicateItems capability on the certified active corpus. It preserves the SDK scorer and candidate-pair safety bound. The SDK caps output at 1,000 clusters; reaching that cap causes an error because complete duplicate findings cannot be proven. A refused SDK analysis fails the command rather than reporting a passing gate.
Place a lint section in the tracker's settings.json:
{
"lint": {
"enable": ["PML001"],
"disable": ["PML006"],
"failOn": "error",
"rules": {
"PML003": { "severity": "info", "scope": "all" },
"PML005": { "parameters": { "staleDays": 14 } },
"PML010": { "enabled": true, "parameters": { "threshold": 0.92 } }
}
}
}All rules start enabled. Settings apply enable/disable lists, then per-rule entries, then CLI overrides. Supported severities are error, warn, and info. Supported scopes are all, non_terminal, active, terminal, closed, canceled, open, in_progress, and blocked. Lifecycle scopes use configured SDK registry roles: closed means terminal-done, canceled means terminal-canceled, open means default-open, and in-progress means active without default-open. staleDays must be finite and positive; duplicate threshold must be greater than zero and at most one. Unknown codes, parameters, keys and invalid values are errors. The current SDK strips custom sections from readSettings, so the package validates core settings through that API and retrieves the custom section using public SDK settings-path and storage helpers.
Exit code 0 means no finding met the failure threshold. Exit code 1 means one or more findings met it. Invalid arguments, settings, incomplete storage, or refused duplicate analysis cause a host error and nonzero exit, rather than a clean lint receipt.
A tracker quality gate can start with errors and progressively adopt warnings:
- run: npm ci
- run: pm lint --fail-on error --jsonRun the host in a workspace where the package is activated. The package's own release:check covers identity/privacy, TypeScript, ESLint, jscpd, docstrings, all authored source coverage, production audit, package contents, changelog, release attestation and packed npm/native-Bun consumers. Scheduled and manual GitHub releases require PM_RELEASE_APPROVED=true on the canonical repository's main branch. Local npm publication remains a maintainer-authenticated action with prepublish checks.
Dogfood receipts, heuristic tuning decisions, coverage and behavioural revert proofs are recorded in the package tracker. The aggregate before/after corpus comparison and hand-checked PML003 sample are in the dogfood report. Public evidence contains only aggregate counts and generic verdicts; raw titles, criteria and tracker configuration stay out of the repository.
Portable organization authoring rules
pm lint pack --pack authoring-rules.json checks semantic authoring conventions
through the same certified complete reader as the built-in rules. It reads every
status, then applies the pack's explicit selectors. It does not change items or
settings. pm-presets can distribute configuration and templates; core owns field
types; this contract adds organization-specific assertions over canonical text.
See the shipped example pack.
Measured public-data and adapter evidence is in the
validation report.
pm lint pack --pack examples/authoring-rules.json
pm lint pack --pack examples/authoring-rules.json --jsonThe strict version 1 contract is { "version": 1, "rules": [...] }. Each rule
requires id, field, predicate, severity, explanation, and remedy.
Rule ids are unique, case-sensitive ASCII identifiers beginning with a letter;
subsequent characters may be letters, digits, _, . or -. Unknown keys fail
at every level, including predicates and selectors; unknown versions fail closed.
| Property | Version 1 semantics |
| --- | --- |
| field | tags, priority, title, description, acceptance_criteria, definition_of_ready, resolution, blocked_reason, outcome, value, why_now |
| predicate | { "op": "present" }, or { "op": "contains" / "not_contains" / "starts_with", "value": "literal" }, or { "op": "min_length" / "max_length", "value": integer } |
| severity | info, warn, error; only failed error assertions fail the gate |
| explanation, remedy | Required nonblank organization-authored diagnostic text, at most 256 characters each |
| when | Optional object with statuses and/or types exact string arrays; OR within each array, AND between them; no selector means all items |
Matching is case-sensitive and literal, including characters such as * and +.
Missing text fields behave as empty strings. present tests trimmed nonemptiness;
other predicates use the original text. Length counts UTF-16 code units.
Regex, glob and executable expression modes refuse with unsafe_pattern and a
literal-predicate remedy. There are no imports, nested expressions or commands.
Hard ceilings are 64 KiB of UTF-8 JSON, 64 rules, 64 characters per id/selector,
32 values per selector list, 512 characters per literal, 65,536 characters per
checked item field or length threshold, 100,000 potential item/rule pairs before
selection, and 1,000 findings. Empty rule/selector arrays refuse. Exceeding a
ceiling returns limit_exceeded; no partial result can pass. For SDK input the
byte ceiling applies to the detached canonical pack; for files it also applies
to the raw bytes, including whitespace. Regular files only are accepted.
Literal ceilings count UTF-16 code units before and after SDK tag normalization;
case mappings that expand a tag past 512 units return limit_exceeded on every
SDK and CLI path. Accepted canonical packs remain valid when parsed again.
Rows contain item_id, rule, field, severity, explanation and remedy.
They sort by exact item id then rule id, independent of locale, declaration order
and storage order. Receipts include corpus/check/finding counts, severity counts
and core's complete-list certificate. Item titles and text are not copied into
rows. Host output budgets and continuation controls apply to findings in TOON
and JSON. Exit codes are 0 for no error findings, 1 for error findings, and 2 for
typed pack refusals (invalid_pack, unsafe_pattern, limit_exceeded, pack_io, conflicting_pack).
Core read/certification failures retain core's diagnostic instead of becoming a
successful or partial pack result. Baseline acknowledgments do not suppress pack
rules; they belong to the separate built-in lint command.
import { PmClient } from '@unbrained/pm-cli/sdk';
import { lintRulePack, readRulePack, RulePackRefusal } from '@unbrained/pm-lint';
import type { RulePack } from '@unbrained/pm-lint';
const pack: RulePack = await readRulePack('authoring-rules.json');
try {
const result = await lintRulePack(new PmClient({ pmRoot: '.agents/pm' }), pack);
console.log(result.summary, result.exit_code);
} catch (error) {
if (error instanceof RulePackRefusal) console.error(error.code, error.remedy);
else throw error;
}parseRulePack(unknown) validates SDK-supplied data. lintRulePack accepts the
listAllComplete portion of the typed PmClient contract so adapters can supply
the same certified canonical metadata without filesystem access in the evaluator.
Tests compare real TOON and legacy JSON Markdown reader results; the pinned host
migrates legacy Markdown during reading. These tests do not attest hosted database
or network deployment. Pack semantics never inspect storage paths or invoke git.
Conflicting rule-pack witnesses
Run pm lint conflicts --pack examples/conflicting-rules.json (or add --json)
before applying an organization policy. The typed analyzeRulePack SDK returns
the smallest conflicting rule-ID set for required/forbidden tags, inclusive
native priority ranges and text-length bounds. Rule ordering never changes
identities or witness selection. Selectors must overlap for rules to conflict.
Literal text predicates outside the proof fragment return unknown with rule
IDs and a remedy. Proven contradictions stop pm lint pack before its tracker
read. See the grammar, example and exit contracts
and the shipped conflicting pack. These
observational commands also work in plain directory trackers without git.
Without git
All lint commands, baselines, witness validation and portable packs work in a plain tracker directory. No lint command requires a git repository. Package release changelog generation uses git tags separately from tracker linting.
Minimal authoring repair previews
pm lint repair --id pm-example-abcd --json returns one revision-bound patch
and explicit questions. --revision <sha256> refuses saved context after a
source edit. TOON is the default; JSON uses the same compact receipt. A successful
preview exits 0 even when questions remain. Typed refusals exit 2 with a code and
remedy; incomplete SDK reads remain errors.
The only patches are title-edge whitespace and SDK tag normalization (including
duplicates). Blank titles, missing or vague acceptance criteria, absent Issue
expected behavior, and other lint findings stay questions. Markdown body,
criteria, relationships, status, ownership and evidence are never authored.
Patches are limited to 16 KiB and one target per invocation. remaining_mechanical
is the projected mechanical count; validation carries core's disposable semantic
validation and its unresolved commit constraints. This does not certify substantive
answers or future application.
previewAuthoringRepairs(pmRoot, { id, revision? }) exposes the same typed SDK
receipt and throws RepairRefusal with code and remedy. Revisions bind original
item bytes and all SDK-read native fields; plan keys also bind the certified
corpus and lint configuration. Every invocation reads fresh state. There is no
apply, saved-plan import, sync, journal recovery or background worker. A consumer
that applies a patch must revalidate the target revision under its SDK mutation
preconditions immediately before writing.
SDK reads can create caches and migrate legacy documents, so all reads and core
mutation validation run in an owned temporary snapshot. Source fingerprints
reject concurrent edits; preview leaves source files, caches, history and journals
unchanged. Foreign runtime, lock, journal, checkpoint, search and executable extension state is excluded with
reasons in excluded_state; no peer record is replayed or deleted. Scratch files
are removed when the invocation finishes. Referenced schema must stay in retained tracker directories (for example schema/); references into excluded operational state refuse rather than falling back to defaults. Configuration and regular native
tracker files are required; unsafe filesystem entries refuse with a remedy.
Repair previews work in plain directory trackers without git. They require local tracker storage for byte-bound evidence; hosted database/network execution is not attested. See the SDK example and validation evidence.
Every workflow job granted write permissions, including CodeQL, Dependabot and
reusable workflow calls, requires the exact repository unbraind/pm-lint.
The approval variable controls GitHub releases; it does not impose a local npm
publishing hold. Maintainers run the prepublish checks and authenticate with npm
for local publication. The first local publish uses --provenance=false; daily
GitHub OIDC releases keep provenance enabled.
