English
Run checks
When To Use
Use this task before a commit, before a pull request, or when you need to reproduce the repository's CI plan locally.
Prerequisites
- Run
repo doctorfrom the workspace root. - Install dependencies with the repository's pinned pnpm version.
- Decide whether you need a fast check or the full delivery gate.
Smallest Command
bash
repo check --dry-runExpected Output
repoctl prints the selected tasks and the exact commands that would run. Remove --dry-run only after the plan matches the repository policy.
Common Branches
- A staged file needs a focused check: use
repo check --staged. - CI requires an artifact: add
--json --out reports/check-plan.json. - You are preparing a release: use
repo check --fullafter the fast check passes.
repo check provides stable, task-oriented entrypoints for local and CI verification. It preserves the underlying workspace scripts and reports which commands will run before execution.
Modes
| Mode | Command | Purpose |
|---|---|---|
| Default | repo check | Lightweight pre-commit verification |
| Staged | repo check --staged | Lefthook plus workspace-aware type checking |
| Full | repo check --full | Root lint, typecheck, build, test, and tsd workflow |
| Commit message | repo check --edit-file <file> | Validate a commit message file |
Use --dry-run, --json, or --markdown to inspect the plan without running it. --out <file> persists the result and --redact removes local paths.
Pre-Commit
repo verify pre-commit runs the repository's staged-file policy. JavaScript, TypeScript, Vue, and style files are routed to the configured lint tasks.
Staged Type Checking
repo verify staged-typecheck <files...> maps changed TypeScript and Vue files to their owning workspace. Vue packages use their vue-tsc based typecheck script; TypeScript packages use tsc.
Pre-Push
repo verify pre-push discovers packages from pnpm-workspace.yaml, including private applications and respecting excluded patterns. It runs the owning package's available build, test, and tsd scripts for changed files, then always runs root lint and typecheck. Nested packages use the deepest matching directory; the workspace root is not also run as a child package.
Deleted files still trigger their former package. Moving a file between packages checks both directories. Root configuration changes and removed package manifests without a current owner also trigger root build, test, and tsd. This is file ownership selection, not a reverse dependency closure. A newly pushed ref is compared with the empty tree; deleting a ref adds no package tasks.
Programmatic callers can still pass workspaces to override discovery, including an empty array. Entries are directories relative to cwd, and their order does not affect ownership.
Automation
bash
repo check --full --dry-run --json --out reports/check-plan.json
repo check --staged --markdown --redact --out reports/check-plan.mdJSON field names and command IDs do not change with --lang; only human-readable descriptions do.
Affected Checks
bash
repo check --affected --base origin/main --head HEAD --json
repo check --affected --base HEAD~1 --filter @acme/web --dry-run
repo check --affected --global-input 'shared-config/**'
repo check --affected --base origin/main --report reports/affected.jsonAffected mode selects changed workspace packages and their direct/transitive consumers, including private apps. JSON schema version 1 records the Git range, file ownership, dependency paths, fallback reasons, package selection and skipped tasks. Repeatable --filter accepts exact names or workspace-relative directories and intersects the affected set. Empty intersections and missing scripts are explained; they never execute unfiltered recursive commands.
The default range is the merge base of origin/main and HEAD, through HEAD, plus staged, unstaged and untracked files. --head must resolve to the current checkout. Missing refs, shallow/incomplete history, a non-current head, unavailable Git or a workspace nested below the Git root explicitly fall back to full checks. Unsafe workspace discovery fails instead of returning an empty plan. Deleted files and both sides of renames retain their owners; package manifest changes, including new/deleted packages, trigger full checks because dependencies may have changed.
Default global inputs cover root package/workspace/lock/Turbo files, TypeScript/lint/test/commit configs, .npmrc, .pnpmfile.*, Node version files, .github/**, lefthook.yml, scripts/** and patches/**. Turbo globalDependencies and repeatable --global-input globs are additive. Unknown unowned files trigger full checks. Root Markdown, docs/** and license/notice documents are ignored unless a global rule matches; files inside a workspace remain package inputs. An unreadable Turbo config or unresolved graph diagnostics (including catalogs) also trigger full fallback when there are changes. Inspect globalInputs and fallback in JSON for the exact rules and reasons.
Execution follows build → lint → typecheck → tsd → test, skipping unavailable scripts. pnpm recursive commands receive the exact planned filters and handle dependency ordering. Build also includes dependencies outside the affected/filter set, recorded in prerequisiteTargets, so tests can use built artifacts. Full fallback uses each existing root script; when a root script is absent, it uses that script across all selected packages. Explicit filters limit checks even during fallback; build prerequisites may still lie outside them. Root scripts are responsible for their own Turbo/pnpm delegation.
--json, --markdown, --dry-run and --out remain previews. --report executes the same model and embeds it as affectedPlan. --affected cannot combine with --full, --staged or --edit-file; base/head/filter/global-input options require affected mode. Programmatic callers use resolveAffectedCheckPlan({ cwd, base, head, filters, globalInputs }) or runCheckWithReport({ cwd, affected: true, ... }).
Export this same plan as a GitHub Actions matrix with repo check --affected --matrix; use --shards 16 to group workspaces. See CI matrix setup for empty-result handling and safe argument-array execution.
Execution Reports
bash
repo check --full --report reports/check-result.json --redact
repo check --full --report reports/check-result.md --report-format markdown--report <file> runs checks and writes the result separately from live terminal logs. It cannot be combined with --dry-run, --json, --markdown, or --out, which continue to produce plans only. The default report format is JSON.
JSON schema version 1 records the mode, directory, start/end timestamps, duration in milliseconds, exit code, and each task's executable and argument array. Stable statuses are success, failed, skipped, and interrupted. Failed commands stop the sequence; later tasks retain null timestamps/exit codes and a skip reason. The CLI keeps the failing exit code and saves the report on SIGINT/SIGTERM where the operating system allows graceful handling. SIGKILL, abrupt power loss, and an unwritable report destination cannot guarantee a saved report.
Reports capture root script or verification stage results, not the internals of Turbo or Lefthook. In staged mode, workspace typechecks run once inside the Lefthook pre-commit pipeline; the report records pre-commit as one stage. Reports do not collect environment values or child output. --redact replaces workspace and home path prefixes, including those in command arguments.
The programmatic runCheckWithReport({ cwd, full: true, signal }) API returns the same report without exiting the caller. An optional AbortSignal cancels the active command.
Find available workspace tasks
repo workspace tasks lists actual package scripts, including root tasks and private applications. A package without scripts is shown explicitly. Search text matches names, paths, descriptions and task names literally; --script requires an exact script name. Neither discovery nor location runs scripts or writes files.
bash
repo workspace tasks client
repo workspace tasks --script test --json
repo workspace tasks --no-private --no-root
repo workspace locate @scope/client
repo workspace locate client --jsonlocate accepts an absolute directory path (including directory symlinks) as an exact query; an absolute path that is not a workspace root never falls back to text search. Other queries prefer an exact package name or workspace-relative path, then search names, paths and descriptions. Relative paths and . refer to the workspace root even when invoked from a child package. A unique match prints only its absolute path. Missing or ambiguous matches exit with code 1; ambiguity lists candidates instead of choosing one. --interactive enables a choice only in a TTY, with the prompt on stderr; JSON and CI always report candidates. Quote the resulting path when passing it to a shell command.
The public getWorkspaceTaskCatalog(cwd, options) and locateWorkspace(cwd, query) APIs return schemaVersion: 1 data. The catalog includes stable exclusion reasons and each task's existing script plus a pnpm executable/argument array (--dir, directory, run, task). Callers may explicitly execute that array to preserve pnpm and Turbo behavior; searching never launches it.
Per-Package Manifest Health
repo doctor discovers packages using pnpm workspace patterns (including private and root packages) and reads each manifest independently. An unreadable package.json produces a diagnostic while other packages are still checked. pnpm package.yaml and package.json5 are supported; multiple manifests in one directory require review. Discovery reads fresh files for each API call.
Manifest findings reuse the existing doctor JSON, Markdown, --strict, and --redact output. Each finding includes a stable id, workspace-relative path, and field. Rules cover missing/invalid/duplicate names, invalid versions and dependency sections, missing/ambiguous workspace targets, self-dependencies, and duplicate/conflicting declarations. Normal peer/dev and peer/runtime pairs are retained; peer compatibility is a separate check. An optional dependency overriding a regular dependency is a warning, since npm permits this explicitly.
Unpublished applications should declare private: true. Public package license, repository, repository.directory and publishConfig recommendations are warnings, not mandatory publishing policy; strict mode also fails on warnings. repository.directory is compared against the workspace root, so a workspace embedded in another repository or a separately hosted package requires manual review. Values from invalid JSON and registry credentials are never included in these findings. Doctor neither executes package scripts nor changes manifests. Actual tarball contents and type consumption remain separate package delivery checks.
Internal dependency boundaries
repo workspace boundaries --json inspects the shared manifest graph, including root and private packages. Configure boundaries in repoctl.config; configured policies also appear in repo doctor JSON/Markdown and its existing CI exit policy. The standalone command exits 1 on failures; --strict includes warnings. It never runs tasks, scans source imports, changes dependencies or enforces third-party admission.
ts
export default {
boundaries: {
tags: { shared: { paths: ['packages/**'] }, app: { paths: ['apps/**'] } },
rules: [
{ id: 'shared-layer', from: { tags: ['shared'] }, allow: [{ tags: ['shared'] }] },
{ id: 'public-packages', from: { private: false }, allow: [{ private: false }] },
],
cycles: { dependencyTypes: ['dependencies', 'optionalDependencies'], severity: 'fail' },
exceptions: [
{ rule: 'shared-layer', source: 'packages/adapter', target: 'apps/web', type: 'peerDependencies', reason: 'Temporary adapter during migration' },
],
},
}Selectors combine fields with AND and values within one field with OR. packages means exact names; paths supports exact workspace directories (root is .) or directory/** for descendants. ./** includes the entire workspace. Other glob syntax is rejected. Tags are named selectors, cannot reference other tags, and may overlap. A rule's allow array is a union; an empty array denies all internal dependencies. Every matching rule applies. Boundary rules include all four dependency fields by default, while cycle detection defaults to dependencies and optionalDependencies only. Configure dependencyTypes to include dev or peer edges; cycles: false disables cycles. Semver edges mean possible local relationships, not lockfile installation proof.
Reports contain stable rule IDs, the violated manifest field, endpoints and exact edges. Each cyclic strongly connected component produces one deterministic closed representative chain and its full member list, rather than enumerating every cycle. A cycle exception removes only the exact typed edge; remaining cycles are still checked. Reasons and waived edges remain visible. Unknown fields/tags, invalid paths, duplicate IDs and missing exception reasons fail validation. Unmatched selector alternatives and unused exceptions warn; incomplete internal graph resolution fails so missing relationships are not reported as healthy.
The public checkWorkspaceBoundaries(cwd, { config? }) returns schemaVersion 1 with findings, exceptions and summary. Explicit API config replaces project configuration for that call. Relative IDs and rule IDs do not change with output language. Put the CLI or repo doctor --strict into an existing pnpm CI script; repoctl adds no separate task runner.
Workspace ownership and CODEOWNERS
Declare exact package names or workspace-relative paths in repoctl.config.ts:
ts
export default {
codeowners: { owners: { '@acme/web': ['@acme/frontend'], 'libraries/sdk': ['@maintainer'] } },
}repoctl workspace owners [workspace] --json reports private and public packages, owners, configuration sources, and missing ownership. Owners accept GitHub users, teams, or email addresses. No network calls verify membership; actual access and review permissions remain managed by GitHub.
sh
repoctl workspace owners --file .github/CODEOWNERS --dry-run
repoctl workspace owners --file .github/CODEOWNERS --syncA file is always explicit: .github/CODEOWNERS, CODEOWNERS, or docs/CODEOWNERS, relative to the workspace root. Map . or the root package name to create a * default rule before the more specific child package rules. The default is read-only. --dry-run also prevents writes with --sync. A marked block preserves outside rules/comments byte for byte. The last matching GitHub rule wins: later rules that may shadow generated paths are reported, including ownerless rules. An existing higher-priority CODEOWNERS file is reported. Unsupported literal directory characters and invalid mappings block sync.
The public planCodeowners() / applyCodeownersPlan() APIs expose before/after content and a diff, revalidate configuration and workspace inputs, and reject stale plans. Writes replace one file atomically, refuse symlink/hardlink targets, and are idempotent. No messages, review requests, permissions, or branch protection are changed. Installation policy: see pnpm installation security for release waiting, trust and build approval diagnostics and optional reviewed presets.
Managed asset upgrade PRs
After a trusted default-branch dependency update, repo maintenance upgrade --base <full-before-sha> --head <full-current-sha> --out <empty-external-directory> compares the resolved root repoctl version in the pnpm lockfile. It understands pnpm 12's separate package-manager document, ignores unrelated dependency and peer-context changes, and blocks unsupported or ambiguous resolutions. The target version must already be installed from the frozen lockfile. Exactly pinned, directly configured organization presets can also trigger maintenance for existing owned assets. Exported workflows embed fixed package/source/target authorization; newly declared files require explicit adoption and workflow review.
Run preparation in a disposable clean checkout: it applies the complete root upgrade plan and three-way merges, refreshes the lockfile through pnpm, reinstalls with --frozen-lockfile --ignore-scripts, then runs declared build, lint, typecheck, tsd and test scripts in that order. The checkout retains the generated changes. Conflicts, changed source inputs, unsupported paths and failed checks leave report.json and logs with blocked status. No asset diff produces unchanged; neither state can create a PR. The report contains every planned file, skips/conflicts, source and target versions, validation results, exact file hashes/modes and the patch digest.
Report file hashes identify exact Git blob bytes. Preparation still checks the planned working-file bytes; publication verifies the patch index and Git-equivalent checkout contents, allowing built-in line-ending conversions. The isolated publisher disables hooks, executable clean/smudge/process filters and filesystem monitors before inspecting files, and retains those settings for PR creation. Preset reports also carry raw preconditions and restricted checkout settings, which Git uses to bind planned bytes to committed and patched blobs while preserving fixed source and ownership checks. Before bytes use the original attributes and after bytes use the authorized patched attributes, so a preset can update .gitattributes. Patch/report path mismatches fail before apply; final plan verification can reject the isolated checkout afterward, still before obtaining write credentials.
Automatic maintenance can complete the built-in Changesets migration and update only its exact .repoctl/migrations/ledger.json metadata path. Preparation and publication share the same fixed migration identity, committed legacy-source, journal and completed-history checks; the target template version comes from the committed root repoctl → monorepo → templates lockfile dependency chain, not the repoctl version number. The publisher binds every migration output to the verified Git patch and checks public-package prerelease lanes against committed workspace membership. Interrupted journals, ambiguous lockfiles, noncanonical workspace patterns, and YAML/JSON5 workspace manifests require a reviewed manual repo upgrade; no broader .repoctl path is authorized. Refresh the trusted exported workflow to adopt this migration policy.
Automatic maintenance can complete the built-in Changesets migration and update only its exact .repoctl/migrations/ledger.json metadata path. Preparation and publication share the same fixed migration identity, committed legacy-source, journal and completed-history checks; the target template version comes from the committed root repoctl → monorepo → templates lockfile dependency chain, not the repoctl version number. The publisher binds every migration output to the verified Git patch and checks public-package prerelease lanes against committed workspace membership. Interrupted journals, ambiguous lockfiles, noncanonical workspace patterns, and YAML/JSON5 workspace manifests require a reviewed manual repo upgrade; no broader .repoctl path is authorized. Refresh the trusted exported workflow to adopt this migration policy.
Export the opt-in recipe with repo maintenance workflow --out .github/workflows/repoctl-upgrade.yml; existing files are never overwritten. Configure a repository GitHub App through REPOCTL_APP_CLIENT_ID and REPOCTL_APP_PRIVATE_KEY, with Contents, Pull requests and Workflows: write permissions, then commit the workflow. The Workflows permission is needed for managed workflow updates; an App token also lets the proposed PR trigger normal CI. No registry token is required.
The first job only has read permissions and can run the trusted checkout's configuration, dependencies and validation scripts. It uploads an immutable artifact. The second job downloads that exact artifact ID, verifies its digest, originating run/attempt/repository, source SHA, allowed root paths, Git modes and before/after hashes, and applies only the reviewed patch. It executes no project scripts or installed dependencies. It then obtains the App token and uses a pinned create-pull-request action on the dedicated repoctl/managed-assets branch. Repeated events update that branch and PR; reserve it for automation. A moved default branch or missing/insufficient App permissions stops publication. Reports remain in the workflow artifacts for review.
Only default-branch pushes touching pnpm-lock.yaml and explicit default-branch manual runs are supported. For a manual retry, provide the full trusted ancestor SHA before the dependency update. Concurrency is serialized per repository/default branch. Fork PR events, automatic merges, registry publication, arbitrary dependency updates, organization asset providers and template instance upgrades are outside this recipe.
Diagnose Turbo cache misses and slow tasks
Capture evidence with Turbo's existing pnpm exec turbo run build --summarize, then analyze an explicit saved summary. The analyzer never invokes tasks, deletes caches or edits configuration:
sh
repo check cache .turbo/runs/current.json
repo check cache .turbo/runs/current.json .turbo/runs/previous.json --json
repo check cache current.json previous.json --markdown > cache-analysis.md--slowest 10 selects up to 1–100 tasks. JSON schema version 1 includes known hits/misses, unknown cache outcomes, a hit rate whose denominator excludes unknowns, actual task durations, duration deltas and comparisons keyed by stable task ID. Added and removed tasks are explicit. Missing/duplicate IDs, missing fields and unsupported summary schemas produce limitations; absent evidence never proves an input was removed. Native Turbo summary schema 1 is supported, including a real Turbo 2.11.6 fixture. Future schemas expose coarse recorded data only, with unknown comparisons.
Shared global differences are stored once in globalEvidence; task evidence contains only task-specific differences, so report size does not multiply global file count by task count. Comparison evidence covers files, global inputs, dependency task hashes, environment declarations/values, task commands/arguments and resolved task configuration. Every value, including untrusted input hashes, becomes a SHA-256 digest before output; only task IDs, filenames, environment names and allowlisted field names are retained as labels. Raw environment values, commands and arbitrary configuration are never printed. Digest equality is evidence comparison, not proof of a unique cache-miss cause. Unchanged hashes with a miss and opaque changed hashes have explicit limitations. Reports do not include the input summary path.
The dependency critical path is available only with unique tasks, complete dependencies, valid timings, an acyclic graph and no overlapping dependency execution. It reports summed task time separately from the observed span; scheduling gaps and contention prevent treating this as an optimization estimate. Inputs are limited to JSON files of 20 MiB and nesting depth 64. Analysis is read-only; redirect stdout to retain an artifact. Parent execution and plan-writing options (--full, --affected, --report, --out, etc.) are rejected; --json, --markdown and --dry-run work before or after cache. Normal check --json remains a check-plan preview.
Public API: analyzeTurboRuns(currentPath, { previous?: string, slowest?: number }) returns TurboRunAnalysis without changing files. JSON fields and limitation codes remain stable across languages.