Shared development tools and repository standards for Ankhorage TypeScript projects.
@ankhorage/devtools is the single source of truth for these separate concerns:
src/
├── cli/
└── tools/
├── eslint/
├── prettier/
├── knip/
├── package/
├── workflows/
└── vscode/
eslint: shared flat ESLint configuration, automatic project profiles, and the bundled ESLint runnerprettier: shared Prettier configuration and the bundled Prettier runnerknip: shared Knip configuration helpers and the bundled Knip runnerpackage: merge-aware synchronization of the sharedpackage.jsontooling contractworkflows: canonical.github/workflows/ci.ymlandrelease.ymlvscode: canonical.vscode/settings.jsonandextensions.json
The package owns the supported ESLint, TypeScript ESLint, Prettier, Knip, security, React, React Hooks, React Native, import/sort, unused-import, and formatting-plugin versions used by consuming repositories.
For a repository that does not yet depend on the shared toolchain:
bun add -D @ankhorage/devtools
bunx @ankhorage/ankh devtools sync .
bun installAfter the first install, the normal workflow is:
ankh devtools syncThe target path is optional and defaults to the current working directory.
Synchronization ensures @ankhorage/devtools is declared using the version of the provider performing the sync, installs the standard package scripts, and removes direct devDependencies for tools/plugins owned by devtools. Unrelated package metadata, dependencies, and scripts are preserved.
The package is discovered under the devtools category and exposes these capabilities:
devtools.lintdevtools.formatdevtools.knipdevtools.syncdevtools.statusdevtools.eslint.syncdevtools.eslint.statusdevtools.prettier.syncdevtools.prettier.statusdevtools.knip.syncdevtools.knip.statusdevtools.package.syncdevtools.package.statusdevtools.workflows.syncdevtools.workflows.statusdevtools.vscode.syncdevtools.vscode.status
The canonical command prefix is always:
ankh devtools ...ankh devtools lint -- --max-warnings=0 .
ankh devtools format -- --check .
ankh devtools knip -- --productionThese delegate to the same bundled tools as the package binaries:
ankh devtools lint→ankhorage-eslintankh devtools format→ankhorage-prettierankh devtools knip→ankhorage-knip
The synchronized package scripts are:
{
"scripts": {
"lint": "ankhorage-eslint . --max-warnings=0",
"lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
"format": "ankhorage-prettier --write .",
"format:check": "ankhorage-prettier --check .",
"knip": "ankhorage-knip"
}
}Synchronize or inspect every managed concern:
ankh devtools sync .
ankh devtools status .Synchronize one concern:
ankh devtools eslint sync .
ankh devtools prettier sync .
ankh devtools knip sync .
ankh devtools package sync .
ankh devtools workflows sync .
ankh devtools vscode sync .Report one concern:
ankh devtools eslint status .
ankh devtools prettier status .
ankh devtools knip status .
ankh devtools package status .
ankh devtools workflows status .
ankh devtools vscode status .Preview synchronization without writing:
ankh devtools sync . --dry-run
ankh devtools eslint sync . --dry-run
ankh devtools package sync . --dry-runA dry run reports would create and would update actions without mutating files. status exits with code 1 when managed state has drifted and 0 when it is current.
Synchronization is deterministic and idempotent:
- missing managed artifacts are created
- outdated centrally owned artifacts are updated
- current artifacts are left untouched
- unrelated files and package fields are preserved
- repeated sync produces only
unchangedresults - invalid target paths and write failures return a non-zero exit code
- create-only repository extension files are never overwritten after creation
The canonical workflow and VS Code files are packaged with @ankhorage/devtools; synchronization does not fetch mutable files from GitHub at runtime.
createConfig() defaults to profile: 'auto'.
Automatic detection reads the consuming repository's package.json and delegates project trait detection to @ankhorage/utility/project. Dependency signals are considered across dependencies, devDependencies, and peerDependencies.
Profile precedence is:
React Native / Expo
↓
react-native
↓ includes
react
↓ includes
base
A React or Next.js project selects react. A React Native or Expo project selects react-native. Everything else selects base.
An unusual repository can opt out of automatic selection:
import { createConfig } from '@ankhorage/devtools/eslint';
export default createConfig({
files: ['src/**/*.ts'],
profile: 'base',
project: ['./tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
});The base profile enforces the shared TypeScript policy plus:
- maximum 50 effective lines per function
- maximum 300 effective lines per file
- modified cyclomatic complexity maximum 15
- security review for dynamic object access
- rejection of non-literal
require()calls
The React profile adds React and React Hooks correctness rules. The React Native profile composes the React profile and adds the selected React Native style rules.
ankh devtools eslint sync centrally owns eslint.config.mjs and creates eslint.local.config.mjs once.
The canonical wrapper uses automatic profile detection and appends repository-owned flat-config entries:
import { createConfig } from '@ankhorage/devtools/eslint';
import localConfig from './eslint.local.config.mjs';
const localEntries = Array.isArray(localConfig) ? localConfig : [localConfig];
export default [
...createConfig({
files: ['src/**/*.{ts,tsx}'],
project: ['./tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
}),
...localEntries,
];Use eslint.local.config.mjs for narrow repository-specific flat-config overrides, including temporary file-specific migration overrides. On first synchronization, an existing non-canonical eslint.config.mjs is preserved as the initial local config before the canonical wrapper is installed. Synchronization never overwrites that local file afterward.
ankh devtools prettier sync owns .prettierrc.js and emits the correct ESM or CommonJS delegate based on the repository's package.json module type.
The consumer delegates formatting policy to:
@ankhorage/devtools/prettier
ankh devtools knip sync bootstraps knip.config.ts with:
import { createKnipConfig } from '@ankhorage/devtools/knip';
export default createKnipConfig();knip.config.ts is create-only after bootstrap so repositories can retain narrow local entries, projects, ignores, binaries, dependencies, or switch to createKnipMonorepoConfig() without synchronization overwriting those extensions.
ankh devtools package sync merge-updates package.json rather than replacing it.
It owns:
- the
@ankhorage/devtoolsdevDependency version range lintlint:fixformatformat:checkknip
It also removes direct devDependencies for tools and ESLint plugins already provided by @ankhorage/devtools. Unrelated scripts, dependencies, metadata, and repository-specific configuration remain unchanged.
workflows owns exactly:
.github/workflows/ci.yml
.github/workflows/release.yml
The CI workflow installs the pinned Bun version with the frozen lockfile, builds before repository-provider validation, runs bunx @ankhorage/ankh doctor validate ., and conditionally runs lint, formatting, Knip, tests, typecheck, and Changesets checks.
vscode owns exactly:
.vscode/settings.json
.vscode/extensions.json
Unknown workflow and VS Code files are never deleted.
A new concern should:
- live in its own sibling directory under
src/tools - define only the files and behavior it owns
- expose deterministic status and synchronization
- add provider commands under
ankh devtools - include dry-run, status, and idempotence coverage
- document its central ownership and repository-owned extension points