// Audit and install a self-correcting guardrail stack in any TypeScript project. Four-phase workflow: understand the guardrail system from this repo, inventory the target project, generate a gap analysis, then apply changes — creating missing configs and merging missing sections into existing ones.
Audit and install a self-correcting guardrail stack in any TypeScript project. Four-phase workflow: understand the guardrail system from this repo, inventory the target project, generate a gap analysis, then apply changes — creating missing configs and merging missing sections into existing ones.
Overview
Install or update a self-correcting guardrail stack in a TypeScript project. After setup,
every git commit runs quality checks in parallel (~3s) and rejects bad code. You (the agent)
see the errors, fix them, and retry.
Four-phase workflow:
Understand — read this repo's reference configs to know what "correct" looks like
Inventory — inspect the target project's current state (configs, deps, code health)
Gap analysis — compare current vs. desired; present a structured report; get confirmation
It does NOT create agent instruction files (CLAUDE.md, GEMINI.md, etc.)
It does NOT copy template files — all configs are generated based on YOUR project
Skill Selection Guide
This skill is always the starting point. Run it once per project to install the toolchain. After setup, you have several optional skills to extend the stack:
Goal
Skill
When
Install the full guardrail stack
setup-guardrails (this skill)
Any new or existing TypeScript project — run first
After setup-guardrails — essential for AI-heavy projects
Deep-dive on architecture tier rules
enforce-architecture
When debugging boundary violations or onboarding to tier rules
Handle a rejected commit
self-correcting-loop
When git commit fails after guardrails are installed
Add a new workspace package
adding-a-package
In a monorepo, after the root stack is set up
Typical setup sequence for an AI-assisted project:
setup-guardrails ← installs toolchain and pre-commit hooks (this skill)
└─ enforce-code-discipline ← adds LLM-specific rules and coverage gates
└─ enforce-architecture ← (monorepo only) deep-dives on tier boundaries
Not sure whether you need enforce-code-discipline?
If human engineers are the primary authors, setup-guardrails alone is sufficient.
If LLMs frequently write or refactor code in this project, add enforce-code-discipline next —
it catches complexity creep, naming drift, and coverage regressions that type-checking alone misses.
See docs/tool-reference.md for a deep dive on each guardrail tool and why it was chosen over alternatives.
Prerequisites
Node.js 20+ installed
A TypeScript project with a package.json
Git initialized (git init)
Phase 0: Understand the Guardrail System
Do this before reading the target project. Phase 0 builds your mental model of what
"correct" looks like, so you can do accurate gap analysis in Phase 2.
Important: The reference files show the complete end-state after BOTH
setup-guardrails AND enforce-code-discipline have run. Discipline-specific sections
(unicorn, sonarjs, jsdoc, complexity limits, coverage thresholds) are added by
enforce-code-discipline, not by this skill. In Phase 2, use the inline content in THIS
file — not the reference files — as your source of truth for what this skill installs.
0a. Quick project type detection
Read the target project's package.json before fetching anything:
Monorepo if: workspaces field exists, or turbo.json / packages/ / apps/ directory present
Single-package otherwise
This determines which reference directory to fetch next.
0b. Fetch reference configs from GitHub
Fetch and read each file fully using raw GitHub URLs.
Single-package — from https://raw.githubusercontent.com/Avinava/agentic-guardrail-ts/main/reference/single-package/:
eslint.config.js
lefthook.yml
tsconfig.json
vitest.config.ts
commitlint.config.ts
package.json
Monorepo — from https://raw.githubusercontent.com/Avinava/agentic-guardrail-ts/main/reference/monorepo/:
eslint.config.js
lefthook.yml
tsconfig.base.json
knip.json
turbo.json
.syncpackrc.json
commitlint.config.ts
vitest.config.ts
package.json
Also fetch docs/known-conflicts.md:
https://raw.githubusercontent.com/Avinava/agentic-guardrail-ts/main/docs/known-conflicts.md
If a fetch fails (no network access), continue using the inline templates in this skill
as your sole reference for what each config should contain.
0c. What to take away from the reference files
After reading, you should understand:
What pre-commit jobs a complete lefthook.yml has (including secrets and, for monorepo, lint-deps)
What TypeScript flags are required (isolatedModules, verbatimModuleSyntax, incremental, erasableSyntaxOnly)
Which ESLint rules belong to THIS skill (import-x rules, runtime safety, boundaries for monorepo) vs. enforce-code-discipline (unicorn, sonarjs, jsdoc, complexity limits)
What devDependencies the reference package.json lists
Any known tool conflicts (from docs/known-conflicts.md) — especially the ESLint Config Prettier ordering constraint
You are now ready to inspect the target project.
Phase 1: Inventory the Target Project
1a. Project type
Monorepo if ANY of these are true:
workspaces field exists in package.json
turbo.json exists at root
packages/ or apps/ directory exists
Single package otherwise.
1b. Package manager
Check which lockfile exists:
pnpm-lock.yaml → pnpm (use pnpm add -D)
yarn.lock → yarn (use yarn add -D)
package-lock.json or none → npm (use npm install -D)
1c. Org scope
Look at the name field in package.json:
If it starts with @something/, the org scope is @something
If no scope detected and this is a monorepo, ask the user
If single package with no scope, use @myorg as a fallback (only matters if they add workspaces later)
1d. Existing packages (monorepo only)
List the directories under packages/ and apps/. These become the tier assignments in ESLint config. Ask the user how to classify them (see Phase 3).
1e. Config file inventory
For each file below, record: (a) does it exist? (b) if it exists, is it outdated?
"Outdated" = file exists but is missing these literal strings:
File
Outdated if file exists but is MISSING any of these strings
(no outdated check — treat as present or missing only)
vitest.config.ts
(no outdated check)
knip.json (monorepo)
(no outdated check)
.syncpackrc.json (monorepo)
(no outdated check)
turbo.json (monorepo)
(no outdated check)
scripts/typecheck-staged.sh (monorepo)
(exists AND is executable?)
.editorconfig
(no outdated check)
.nvmrc
(no outdated check)
.prettierrc
(no outdated check)
.prettierignore
(no outdated check)
1f. devDependency audit
Read the target project's package.json. For each package below, record whether it is already in devDependencies. Phase 3 will install ONLY the missing ones.
Greenfield: No existing lint config, or existing config with <10 violations → proceed normally (all rules at error)
Retrofit: Existing codebase with >10 violations or non-strict TypeScript → use Wave mode
If retrofit, WARN the user before proceeding:
"This is an existing codebase with [N] lint violations and [M] type issues.
I'll install guardrails in Wave mode — rules start at warn so your
existing workflow isn't disrupted. You'll then drive each rule category to
zero violations via focused refactoring, and flip warn→error one category
at a time.
What to expect:
Pre-commit hooks will run but won't block commits initially (warnings, not errors)
A warning budget header in eslint.config.js tracks progress toward zero
Each rule category gets its own cleanup wave
Total adoption timeline: days to weeks depending on codebase size
The key principle: Tools become gates only when their baseline is exit-0.
A rule moves to error only when you have zero violations. Never use
--max-warnings=N — it decays over time and erodes trust in the tool."
If retrofit mode: When generating eslint.config.js in Phase 3, set these rules to warn instead of error:
import-x/default, import-x/named
@typescript-eslint/no-non-null-assertion
@typescript-eslint/consistent-type-assertions
no-restricted-syntax (double-cast)
boundaries/dependencies (if monorepo)
Rules that ALWAYS start at error even on retrofit (they're auto-fixable):
Add a warning budget header at the top of eslint.config.js:
// ── WARNING BUDGET (retrofit mode) ──────────────────────────// These rules are at 'warn' until the violation count reaches 0.// Drive each to zero, then flip to 'error' in the same commit.//// Rule Count Target// import-x/default 12 Wave 1// import-x/named 3 Wave 1// no-non-null-assertion 47 Wave 2// consistent-type-assertions 8 Wave 2// boundaries/dependencies 22 Wave 3//// Last audited: YYYY-MM-DD// ─────────────────────────────────────────────────────────────
Phase 2: Gap Analysis
Using the Phase 1 inventory, compile this report and present it to the user before making any changes:
GUARDRAIL GAP ANALYSIS
======================
Project type: [single-package | monorepo]
Package manager: [npm | pnpm | yarn]
Mode: [greenfield | retrofit]
CONFIG FILES
PRESENT AND CORRECT: [list files]
PRESENT BUT OUTDATED: [list files with specific gap, e.g. "lefthook.yml (missing: secrets job)"]
MISSING: [list files]
PACKAGE.JSON SCRIPTS
PRESENT AND CORRECT: [list scripts]
MISSING: [list scripts]
DEVDEPENDENCIES
ALREADY INSTALLED: [list packages]
MISSING (to install): [list packages]
[Retrofit only:]
EXISTING VIOLATIONS: [N lint violations, M @ts-ignore occurrences]
STRATEGY: Wave mode — auto-fixable rules at error immediately, others at warn
Then ask: "Confirm this plan? Reply YES to proceed, or list any items to SKIP."
Wait for user confirmation before proceeding to Phase 3. Record any exclusions.
This single checkpoint replaces the scattered "ask user" prompts in the old workflow — EXCEPT still ask separately about:
Tier assignments (monorepo only): after YES, ask how to classify each detected package into tiers (see Phase 3, eslint.config.js section)
Enum usage: ask before writing tsconfig — "Does this project use TypeScript enum or namespace keywords?"
Phase 3: Apply Changes
File treatment policy:
MISSING: Create the file from the inline template in this skill.
PRESENT BUT OUTDATED: Merge the missing sections into the existing file. Do NOT overwrite content that is already correct. Do NOT overwrite unrelated custom content.
PRESENT AND CORRECT: Skip silently. No warning needed.
Merge instructions by file type:
lefthook.yml — outdated (missing job):
Read the existing file. Append the missing job block inside pre-commit.jobs, immediately before the commit-msg section. Do not modify existing jobs or their order.
eslint.config.js — outdated (missing rule group):
Read the existing file. Locate the array passed to tseslint.config(...). Find the eslintConfigPrettier entry — it MUST remain the last element (see docs/known-conflicts.md, "ESLint Config Prettier × ESLint Rules"). Insert the missing config object(s) immediately BEFORE eslintConfigPrettier. Do not modify existing config objects. If the file doesn't use tseslint.config(), skip the merge and warn the user that the format is non-standard.
tsconfig.json / tsconfig.base.json — outdated (missing flags):
Add the missing key-value pairs to compilerOptions. Do not remove or modify existing options.
package.json scripts — outdated (missing scripts):
Add the missing script entries. Do not overwrite scripts that already exist unless the user confirms.
.gitignore — outdated (missing entries):
Append the missing entries. Do not remove existing entries.
3a. Foundation Configs
Create these files in the project root. Apply the file treatment policy (create if missing, skip if present).
{"semverGroups":[{"label":"Internal workspace packages use exact versions","packages":["DETECTED_SCOPE/**"],"dependencies":["DETECTED_SCOPE/**"],"dependencyTypes":["prod","dev"],"pinVersion":"*"}],"versionGroups":[{"label":"All third-party deps should use the same version","packages":["**"],"dependencies":["**"],"dependencyTypes":["prod","dev"]}]}
Replace DETECTED_SCOPE with the actual scope. For pnpm/yarn, change "pinVersion": "*" to "pinVersion": "workspace:*".
Coverage thresholds are intentionally omitted here. They are set by the enforce-code-discipline skill, which baselines them against your current coverage in retrofit mode, or sets hard gates (80% lines/functions/statements, 75% branches) in greenfield mode. Run that skill after this one.
3f. Helper Scripts (Monorepo Only)
Skip this step for single-package projects.
scripts/typecheck-staged.sh
#!/usr/bin/env bash# Type-check only the packages that have staged changes.set -euo pipefail
STAGED=$(git diff --cached --name-only --diff-filter=d | grep -oP 'packages/[^/]+' | sort -u)
if [ -z "$STAGED" ]; thenecho"No packages with staged changes — skipping typecheck."exit 0
fifor pkg in$STAGED; doif [ -f "$pkg/tsconfig.json" ]; thenecho"Type-checking $pkg..."
npx tsc -b "$pkg" --noEmit
fidone
Make it executable: chmod +x scripts/typecheck-staged.sh
scripts/publint-all.sh
#!/usr/bin/env bash# Run publint against every workspace package that has a dist/ folder.set -euo pipefail
EXIT_CODE=0
for pkg in packages/*/; doif [ -d "$pkg/dist" ]; thenecho"publint: $pkg"
npx publint "$pkg" || EXIT_CODE=1
fidoneexit$EXIT_CODE
Make it executable: chmod +x scripts/publint-all.sh
This creates .git/hooks/ symlinks that make pre-commit checks fire automatically. This command is idempotent — safe to run even if hooks were already installed.
3j. Merge or Create .gitignore
If .gitignore exists, append any missing entries. If it doesn't exist, create it:
eslint.config.js exists with import-x/named, import-x/default, no-non-null-assertion, TSAsExpression > TSAsExpression
For monorepo: eslint.config.js imports eslint-plugin-boundaries
.prettierrc exists
commitlint.config.ts exists with actual package scopes
vitest.config.ts exists with correct include pattern
tsconfig includes "isolatedModules", "verbatimModuleSyntax", "incremental" (and "erasableSyntaxOnly" if user confirmed no enums)
.editorconfig exists
.nvmrc exists
package.json has lint:unused, prettier:check, prettier:fix, prepare scripts
git commit --allow-empty passes without hook errors
Lockfile updated with new devDependencies (including eslint-plugin-import-x)
For monorepo: tsconfig.base.json, knip.json, .syncpackrc.json, turbo.json exist
For monorepo: scripts/typecheck-staged.sh exists and is executable
For retrofit: warning budget header present and honest; no rule at error with non-zero violations
4c. Adoption summary
After the verification commit succeeds, report:
SETUP COMPLETE
==============
Files created: [N] new files
Files updated: [Y] existing files with merged sections
Files unchanged: [Z] already correct
Deps installed: [W] new packages
Wave Sequencing (Retrofit Projects)
After initial setup in retrofit mode, drive each rule category to zero violations one wave at a time. Do NOT mix waves — complete one before starting the next.
Wave 1: Auto-fixable rules (usually same-day)
import-x/order → run npx eslint --fix → already at error
import-x/no-duplicates → run npx eslint --fix → already at error
Setting up an AI-assisted project from scratch, or adding guardrails to an existing codebase?
→ You're in the right place. Complete this skill, then run enforce-code-discipline.
LLMs frequently write or refactor code in this project?
→ After setup, run enforce-code-discipline to add complexity limits, naming conventions, and coverage gates.
These rules specifically target the patterns LLMs produce that type-checking alone misses.
Monorepo with multiple packages and you need to control which can import which?
→ After setup, read enforce-architecture to understand and configure your tier boundary rules.
It explains rule intentions, common violations, and how to handle legitimate exceptions.
git commit was rejected after guardrails were installed?
→ See self-correcting-loop — it walks through reading hook output, fixing the issue, and retrying
without fighting the tools.
Adding a new workspace package to an existing monorepo?
→ See adding-a-package — it wires up the tsconfig, ESLint boundaries entry, and Turborepo task
for the new package without breaking the existing ones.