integrate generalized quality-rails into mosaic bootstrap

This commit is contained in:
Jason Woltje
2026-02-17 13:16:01 -06:00
parent 5ac015b177
commit e1c1ce2856
38 changed files with 1960 additions and 0 deletions

View File

@@ -103,6 +103,30 @@ Inside any compatible repository:
Wrapper commands call local repo scripts under `scripts/agent/`.
## Quality Rails (Generalized)
Mosaic includes vendored quality-rails templates and scripts at:
- `~/.mosaic/rails/quality/`
Apply to a repo:
```bash
~/.mosaic/bin/mosaic-quality-apply --template typescript-node --target /path/to/repo
```
Verify enforcement:
```bash
~/.mosaic/bin/mosaic-quality-verify --target /path/to/repo
```
Templates currently supported:
- `typescript-node`
- `typescript-nextjs`
- `monorepo`
## Bootstrap Any Repo (Slave Linkage)
Attach any repository/workspace to the master layer:
@@ -111,6 +135,12 @@ Attach any repository/workspace to the master layer:
~/.mosaic/bin/mosaic-bootstrap-repo /path/to/repo
```
Attach and apply quality rails in one step:
```bash
~/.mosaic/bin/mosaic-bootstrap-repo --quality-template typescript-node /path/to/repo
```
This creates/updates:
- `.mosaic/` (repo-specific hook/config surface)

View File

@@ -20,6 +20,7 @@ Master/slave model:
- Pull before edits when collaborating in shared repos.
- Run validation checks before claiming completion.
- Apply quality rails from `~/.mosaic/rails/` when relevant (review, QA, git workflow).
- For project-level mechanical enforcement templates, use `~/.mosaic/rails/quality/` via `~/.mosaic/bin/mosaic-quality-apply`.
- Avoid hardcoded secrets and token leakage in remotes/commits.
- Do not perform destructive git/file actions without explicit instruction.

View File

@@ -3,6 +3,7 @@ set -euo pipefail
TARGET_DIR="$(pwd)"
FORCE=0
QUALITY_TEMPLATE=""
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -10,6 +11,10 @@ while [[ $# -gt 0 ]]; do
FORCE=1
shift
;;
--quality-template)
QUALITY_TEMPLATE="${2:-}"
shift 2
;;
*)
TARGET_DIR="$1"
shift
@@ -48,6 +53,7 @@ copy_file() {
copy_file "$TEMPLATE_ROOT/.mosaic/README.md" "$TARGET_DIR/.mosaic/README.md"
copy_file "$TEMPLATE_ROOT/.mosaic/repo-hooks.sh" "$TARGET_DIR/.mosaic/repo-hooks.sh"
copy_file "$TEMPLATE_ROOT/.mosaic/quality-rails.yml" "$TARGET_DIR/.mosaic/quality-rails.yml"
for file in "$TEMPLATE_ROOT"/scripts/agent/*.sh; do
base="$(basename "$file")"
@@ -90,3 +96,17 @@ fi
echo "[mosaic] Repo bootstrap complete: $TARGET_DIR"
echo "[mosaic] Next: edit $TARGET_DIR/.mosaic/repo-hooks.sh with project workflows"
echo "[mosaic] Optional: apply quality rails via ~/.mosaic/bin/mosaic-quality-apply --template <template> --target $TARGET_DIR"
if [[ -n "$QUALITY_TEMPLATE" ]]; then
if [[ -x "$MOSAIC_HOME/bin/mosaic-quality-apply" ]]; then
"$MOSAIC_HOME/bin/mosaic-quality-apply" --template "$QUALITY_TEMPLATE" --target "$TARGET_DIR"
if [[ -f "$TARGET_DIR/.mosaic/quality-rails.yml" ]]; then
sed -i "s/^enabled:.*/enabled: true/" "$TARGET_DIR/.mosaic/quality-rails.yml"
sed -i "s/^template:.*/template: \"$QUALITY_TEMPLATE\"/" "$TARGET_DIR/.mosaic/quality-rails.yml"
fi
echo "[mosaic] Applied quality rails template: $QUALITY_TEMPLATE"
else
echo "[mosaic] WARN: mosaic-quality-apply not found; skipping quality rails apply" >&2
fi
fi

View File

@@ -115,12 +115,15 @@ echo "[mosaic-doctor] Mosaic home: $MOSAIC_HOME"
expect_file "$MOSAIC_HOME/STANDARDS.md"
expect_dir "$MOSAIC_HOME/guides"
expect_dir "$MOSAIC_HOME/rails"
expect_dir "$MOSAIC_HOME/rails/quality"
expect_dir "$MOSAIC_HOME/profiles"
expect_dir "$MOSAIC_HOME/templates/agent"
expect_dir "$MOSAIC_HOME/skills"
expect_dir "$MOSAIC_HOME/skills-local"
expect_file "$MOSAIC_HOME/bin/mosaic-link-runtime-assets"
expect_file "$MOSAIC_HOME/bin/mosaic-sync-skills"
expect_file "$MOSAIC_HOME/bin/mosaic-quality-apply"
expect_file "$MOSAIC_HOME/bin/mosaic-quality-verify"
# Claude runtime file checks (copied, non-symlink).
for rf in CLAUDE.md settings.json hooks-config.json context7-integration.md; do

65
bin/mosaic-quality-apply Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.mosaic}"
TARGET_DIR="$(pwd)"
TEMPLATE=""
usage() {
cat <<USAGE
Usage: $(basename "$0") --template <name> [--target <dir>]
Apply Mosaic quality rails templates into a project.
Templates:
typescript-node
typescript-nextjs
monorepo
Examples:
$(basename "$0") --template typescript-node --target ~/src/my-project
$(basename "$0") --template monorepo
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--template)
TEMPLATE="${2:-}"
shift 2
;;
--target)
TARGET_DIR="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage >&2
exit 1
;;
esac
done
if [[ -z "$TEMPLATE" ]]; then
echo "[mosaic-quality] Missing required --template" >&2
usage >&2
exit 1
fi
if [[ ! -d "$TARGET_DIR" ]]; then
echo "[mosaic-quality] Target directory does not exist: $TARGET_DIR" >&2
exit 1
fi
SCRIPT="$MOSAIC_HOME/rails/quality/scripts/install.sh"
if [[ ! -x "$SCRIPT" ]]; then
echo "[mosaic-quality] Missing install script: $SCRIPT" >&2
exit 1
fi
echo "[mosaic-quality] Applying template '$TEMPLATE' to $TARGET_DIR"
"$SCRIPT" --template "$TEMPLATE" --target "$TARGET_DIR"

52
bin/mosaic-quality-verify Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.mosaic}"
TARGET_DIR="$(pwd)"
usage() {
cat <<USAGE
Usage: $(basename "$0") [--target <dir>]
Run quality-rails verification checks inside a target repository.
Examples:
$(basename "$0")
$(basename "$0") --target ~/src/my-project
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--target)
TARGET_DIR="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage >&2
exit 1
;;
esac
done
if [[ ! -d "$TARGET_DIR" ]]; then
echo "[mosaic-quality] Target directory does not exist: $TARGET_DIR" >&2
exit 1
fi
SCRIPT="$MOSAIC_HOME/rails/quality/scripts/verify.sh"
if [[ ! -x "$SCRIPT" ]]; then
echo "[mosaic-quality] Missing verify script: $SCRIPT" >&2
exit 1
fi
echo "[mosaic-quality] Running verification in $TARGET_DIR"
(
cd "$TARGET_DIR"
"$SCRIPT"
)

189
rails/quality/PHILOSOPHY.md Normal file
View File

@@ -0,0 +1,189 @@
# Why Hard Rails Matter
## The Problem We Discovered
In AI-assisted development, we found:
1. **Process adherence fails** - Agents claim to do code review but miss critical issues
2. **Manual review insufficient** - Even AI-assisted review missed hardcoded passwords, SQL injection
3. **Scale breaks quality** - 50 issues in a single patch release despite explicit QA processes
### Real-World Case Study
**Production patch validation:**
After explicit code review and QA processes, we discovered **50 issues**:
**Security Issues (9):**
- 4 hardcoded passwords committed to repository
- 1 SQL injection vulnerability
- World-readable .env files
- XSS vulnerabilities (CSP unsafe-inline)
**Type Safety Issues (11):**
- TypeScript strict mode DISABLED (`"strict": false`)
- ESLint explicitly ALLOWING any types (`no-explicit-any: 'off'`)
- Missing return types
- Type assertion overuse
**Silent Failures (9):**
- Errors swallowed in try/catch blocks
- Functions returning wrong types on error
- No error logging
- Network failures treated as false instead of errors
**Test Coverage Gaps (10):**
- No test coverage requirements
- No testing framework setup
- Code shipped with 0% coverage
**Build Failures (2):**
- Code committed that doesn't compile
- Tests committed that fail
**Dependency Issues (6):**
- Critical CVEs not caught
- Version conflicts between packages
## The Solution: Mechanical Enforcement
Don't **ask** agents to:
- "Please do code review"
- "Make sure to run tests"
- "Check for security issues"
Instead, **BLOCK** commits that:
- Have type errors
- Contain hardcoded secrets
- Don't pass tests
- Have security vulnerabilities
### Why This Works
**Example: Type Safety**
**Process-based (fails):**
```
Human: "Please avoid using 'any' types"
Agent: "I'll make sure to use proper types"
*Agent uses any types anyway*
```
**Mechanically enforced (works):**
```
Agent writes: const x: any = 123;
Git hook runs: ❌ Error: no-explicit-any
Commit blocked
Agent must fix to proceed
```
The agent doesn't get to **claim** it followed the process. The automated gate **determines** if code is acceptable.
## Design Principles
### 1. Fail Fast
Detect issues at commit time, not in CI, not in code review, not in production.
**Timeline:**
- ⚡ Commit time: Type errors, lint errors, secrets → **BLOCKED**
- 🔄 CI time: Build failures, test failures, CVEs → **BLOCKED**
- 👀 Code review: Architecture, design, business logic
- 🚀 Production: (Issues should never reach here)
### 2. Non-Negotiable
No agent can bypass enforcement. No "skip hooks" flag. No emergency override.
If the code doesn't pass gates, it doesn't get committed. Period.
### 3. Portable
Same enforcement across:
- All projects
- All developers (human + AI)
- All environments (local, CI, production)
### 4. Minimal Friction
Auto-fix where possible:
- Prettier formats code automatically
- ESLint --fix corrects simple issues
- Only block when can't auto-fix
### 5. Clear Feedback
When enforcement blocks a commit, tell the agent:
- ❌ What's wrong (type error, lint violation, etc.)
- 📍 Where it is (file:line)
- ✅ How to fix it (expected type, remove 'any', etc.)
## Impact Prediction
Based on a 50-issue production analysis:
| Phase | Enforcement | Issues Prevented |
|-------|-------------|------------------|
| **Phase 1** | Pre-commit + strict mode + ESLint | 25 of 50 (50%) |
| **Phase 2** | + CI expansion + npm audit | 35 of 50 (70%) |
| **Phase 3** | + OWASP + coverage gates | 45 of 50 (90%) |
**The remaining 10%** require human judgment:
- Architecture decisions
- Business logic correctness
- User experience
- Performance optimization
## Agent Behavior Evolution
### Before Quality Rails
```
Agent: "I've completed the feature and run all tests"
Reality: Code has type errors, no tests written, hardcoded password
Result: 50 issues discovered in code review
```
### After Quality Rails
```
Agent writes code with 'any' type
Git hook: ❌ no-explicit-any
Agent rewrites with proper type
Git hook: ✅ Pass
Agent writes code with hardcoded password
Git hook: ❌ Secret detected
Agent moves to environment variable
Git hook: ✅ Pass
Agent commits without tests
CI: ❌ Coverage below 80%
Agent writes tests
CI: ✅ Pass
```
**The agent learns:** Good code passes gates, bad code is rejected.
## Why This Matters for AI Development
AI agents are **deterministically bad** at self-enforcement:
- They claim to follow processes
- They **believe** they're following processes
- Output proves otherwise
But AI agents are **good** at responding to mechanical feedback:
- Clear error messages
- Specific line numbers
- Concrete fix requirements
Quality Rails exploits this strength and avoids the weakness.
## Conclusion
**Process compliance:** Agents claim → Output fails
**Mechanical enforcement:** Gates determine → Output succeeds
This is not philosophical. This is pragmatic. Based on 50 real issues from production code.
Quality Rails exists because **process-based quality doesn't work at scale with AI agents.**
Mechanical enforcement does.

166
rails/quality/README.md Normal file
View File

@@ -0,0 +1,166 @@
# Quality Rails
Portable quality enforcement for TypeScript, Python, and Node.js projects.
## 🎯 What This Prevents
Based on real-world validation of 50 issues in a production codebase:
- ❌ Hardcoded passwords
- ❌ SQL injection vulnerabilities
- ❌ Type safety violations (`any` types)
- ❌ Missing test coverage
- ❌ Build failures
- ❌ Dependency vulnerabilities
**70% of these issues are prevented mechanically with quality-rails.**
## ⚡ Quick Start (Mosaic)
### New Project
```bash
# Apply template from Mosaic
~/.mosaic/bin/mosaic-quality-apply --template typescript-node --target /path/to/project
# Install dependencies
cd /path/to/project
npm install
# Initialize git hooks
npx husky install
# Verify enforcement is working
~/.mosaic/bin/mosaic-quality-verify --target /path/to/project
```
### Existing Project
```bash
# Same as above - works for new or existing projects
~/.mosaic/bin/mosaic-quality-apply --template typescript-node --target /path/to/existing-project
```
## 🛡️ What You Get
**TypeScript strict mode** - All type checks enabled
**ESLint blocking `any` types** - no-explicit-any: error
**Pre-commit hooks** - Type check + lint + format before commit
**Secret scanning** - Block hardcoded passwords/API keys
**CI/CD templates** - Woodpecker, GitHub Actions, GitLab
**Test coverage enforcement** - 80% threshold
**Security scanning** - npm audit, OWASP checks
## 📦 Available Templates
| Template | Language | Framework | Status |
|----------|----------|-----------|--------|
| `typescript-node` | TypeScript | Node.js | ✅ Ready |
| `typescript-nextjs` | TypeScript | Next.js | ✅ Ready |
| `monorepo` | TypeScript | TurboRepo + pnpm | ✅ Ready |
| `python` | Python | - | 🚧 Coming Soon |
### Monorepo Template
Perfect for projects combining **Next.js frontend** + **NestJS backend** in one repository.
Features:
- 🎯 **Multi-package aware** - lint-staged only checks changed packages
-**TurboRepo caching** - Faster builds and tests
- 🔀 **Parallel dev servers** - Run web + API simultaneously
- 📦 **pnpm workspaces** - Efficient dependency management
- 🛡️ **Package-specific rules** - Next.js and NestJS get appropriate ESLint configs
Example structure:
```
monorepo/
├── apps/
│ ├── web/ # Next.js frontend
│ └── api/ # NestJS backend
└── packages/
├── shared-types/
├── ui/
└── config/
```
## 🧪 How It Works
### Pre-Commit (Local Enforcement)
```bash
# You try to commit code with a type error
git commit -m "Add feature"
# Quality rails blocks it:
❌ Type error: Type 'number' is not assignable to type 'string'
❌ ESLint: Unexpected any. Specify a different type.
✋ Commit blocked - fix errors and try again
```
### CI/CD (Remote Enforcement)
```yaml
# Woodpecker pipeline runs:
✓ npm audit (dependency security)
✓ eslint (code quality)
✓ tsc --noEmit (type checking)
✓ jest --coverage (tests + coverage)
✓ npm run build (compilation)
# If any step fails, merge is blocked
```
## 🎓 Philosophy
**Process compliance doesn't work.**
Instructing AI agents to "do code review" or "run tests" fails. They claim to follow processes but output quality doesn't match claims.
**Mechanical enforcement works.**
Quality rails don't ask agents to follow processes. They **block commits** that don't pass automated checks.
- Type errors? → **Commit blocked**
- Hardcoded secrets? → **Commit blocked**
- Test failures? → **Commit blocked**
- Missing coverage? → **Commit blocked**
This works for **any agent runtime** (Codex, Claude, OpenCode, Gemini, etc.) because enforcement is mechanical, not instructional.
[Read more: PHILOSOPHY.md](./PHILOSOPHY.md)
## 📖 Documentation
- [TypeScript Setup Guide](./docs/TYPESCRIPT-SETUP.md)
- [CI/CD Configuration](./docs/CI-SETUP.md)
## 🔧 Scripts
| Script | Purpose |
|--------|---------|
| `scripts/install.sh` | Install template to project (Linux/Mac) |
| `scripts/install.ps1` | Install template to project (Windows) |
| `scripts/verify.sh` | Verify enforcement is working (Linux/Mac) |
| `scripts/verify.ps1` | Verify enforcement is working (Windows) |
## 🚀 Roadmap
- [x] TypeScript/Node template
- [x] Pre-commit enforcement (husky + lint-staged)
- [x] CI/CD templates (Woodpecker, GitHub Actions)
- [x] Installation scripts
- [x] Verification testing
- [x] Next.js template
- [x] Monorepo template
- [ ] Python template
- [ ] Coverage visualization
- [ ] IDE integration (VSCode extension)
## 🤝 Contributing
Quality Rails is based on lessons learned from real production codebases. Contributions welcome!
## 📝 License
MIT License - See LICENSE file for details
## 🙏 Credits
Built to solve real problems discovered in AI-assisted development workflows.
Based on validation findings from a production patch milestone.

View File

@@ -0,0 +1,174 @@
# CI/CD Configuration Guide
Configure Woodpecker CI, GitHub Actions, or GitLab CI for quality enforcement.
## Woodpecker CI
Quality Rails includes `.woodpecker.yml` template.
### Pipeline Stages
1. **Install** - Dependencies
2. **Security Audit** - npm audit for CVEs
3. **Lint** - ESLint checks
4. **Type Check** - TypeScript compilation
5. **Test** - Jest with coverage thresholds
6. **Build** - Production build
### Configuration
No additional configuration needed. Push to repository and Woodpecker runs automatically.
### Blocking Merges
Configure Woodpecker to block merges on pipeline failure:
1. Repository Settings → Protected Branches
2. Require Woodpecker pipeline to pass
## GitHub Actions
Copy from `templates/typescript-node/.github/workflows/quality.yml`:
```yaml
name: Quality Enforcement
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm audit --audit-level=high
- run: npm run lint
- run: npm run type-check
- run: npm run test -- --coverage
- run: npm run build
```
### Blocking Merges
1. Repository Settings → Branches → Branch protection rules
2. Require status checks to pass: `quality`
## GitLab CI
Copy from `templates/typescript-node/.gitlab-ci.yml`:
```yaml
stages:
- install
- audit
- quality
- build
install:
stage: install
script:
- npm ci
audit:
stage: audit
script:
- npm audit --audit-level=high
lint:
stage: quality
script:
- npm run lint
typecheck:
stage: quality
script:
- npm run type-check
test:
stage: quality
script:
- npm run test -- --coverage
build:
stage: build
script:
- npm run build
```
## Coverage Enforcement
Configure Jest coverage thresholds in `package.json`:
```json
{
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
```
CI will fail if coverage drops below threshold.
## Security Scanning
### npm audit
Runs automatically in CI. Adjust sensitivity:
```bash
npm audit --audit-level=moderate # Block moderate+
npm audit --audit-level=high # Block high+critical only
npm audit --audit-level=critical # Block critical only
```
### Snyk Integration
Add to CI for additional security:
```yaml
- run: npx snyk test
```
Requires `SNYK_TOKEN` environment variable.
## Notification Setup
### Woodpecker
Configure in Woodpecker UI:
- Slack/Discord webhooks
- Email notifications
- Status badges
### GitHub Actions
Add notification step:
```yaml
- name: Notify on failure
if: failure()
run: |
curl -X POST $WEBHOOK_URL -d "Build failed"
```
## Troubleshooting
**Pipeline fails but pre-commit passed:**
- CI runs all packages, pre-commit only checks changed files
- Fix issues in all packages, not just changed files
**npm audit blocks on low-severity:**
- Adjust `--audit-level` to `moderate` or `high`
**Coverage threshold too strict:**
- Lower thresholds in package.json
- Add coverage exceptions for specific files

View File

@@ -0,0 +1,164 @@
# TypeScript Project Setup Guide
Step-by-step guide to add Quality Rails to a TypeScript project.
## Prerequisites
- Node.js 18+ and npm/pnpm
- Git repository initialized
- TypeScript project (or create with `npm init` + `tsc --init`)
## Installation
### 1. Clone Quality Rails
```bash
git clone git@git.mosaicstack.dev:mosaic/quality-rails.git
```
### 2. Run Installation Script
```bash
# From your project directory
../quality-rails/scripts/install.sh --template typescript-node --target .
```
This copies:
- `.husky/pre-commit` - Git hooks
- `.lintstagedrc.js` - Pre-commit checks
- `.eslintrc.js` - Strict ESLint rules
- `tsconfig.json` - TypeScript strict mode
- `.woodpecker.yml` - CI pipeline
### 3. Install Dependencies
Add to your `package.json`:
```json
{
"scripts": {
"lint": "eslint 'src/**/*.{ts,tsx}' --max-warnings=0",
"type-check": "tsc --noEmit",
"test": "jest",
"build": "tsc",
"prepare": "husky install"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-security": "^3.0.0",
"husky": "^9.1.7",
"jest": "^29.0.0",
"lint-staged": "^16.2.7",
"prettier": "^3.0.0",
"typescript": "^5.6.0"
}
}
```
Then run:
```bash
npm install
npx husky install
```
### 4. Verify Enforcement
```bash
../quality-rails/scripts/verify.sh
```
Should output:
```
✅ PASS: Type errors blocked
✅ PASS: 'any' types blocked
✅ PASS: Lint errors blocked
```
## What Gets Enforced
### TypeScript Strict Mode
All strict checks enabled in `tsconfig.json`:
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true
}
}
```
### ESLint Rules
Key rules in `.eslintrc.js`:
```javascript
{
'@typescript-eslint/no-explicit-any': 'error', // Block 'any' types
'@typescript-eslint/explicit-function-return-type': 'warn', // Require return types
'@typescript-eslint/no-floating-promises': 'error', // Catch unhandled promises
'@typescript-eslint/no-misused-promises': 'error', // Prevent promise misuse
}
```
### Pre-Commit Checks
On every `git commit`, runs:
1. ESLint with --max-warnings=0
2. TypeScript type check
3. Prettier formatting
4. Secret scanning (if git-secrets installed)
If any fail → **commit blocked**.
## Troubleshooting
### "husky - pre-commit hook exited with code 1"
This means pre-commit checks failed. Read the error output:
```
src/example.ts:5:14 - error TS2322: Type 'number' is not assignable to type 'string'
```
Fix the error and commit again.
### "Cannot find module '@typescript-eslint/parser'"
Dependencies not installed:
```bash
npm install
```
### Pre-commit hooks not running
Husky not initialized:
```bash
npx husky install
```
## Customization
See [CUSTOMIZATION.md](./CUSTOMIZATION.md) for adjusting strictness levels.
## CI/CD Setup
See [CI-SETUP.md](./CI-SETUP.md) for Woodpecker/GitHub Actions configuration.

View File

@@ -0,0 +1,53 @@
# Quality Rails Installation Script (Windows)
param(
[Parameter(Mandatory=$true)]
[string]$Template,
[Parameter(Mandatory=$false)]
[string]$TargetDir = "."
)
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = Split-Path -Parent $ScriptDir
$TemplateDir = Join-Path $RepoRoot "templates\$Template"
if (-not (Test-Path $TemplateDir)) {
Write-Error "Template '$Template' not found at $TemplateDir"
Write-Host "Available templates: typescript-node, typescript-nextjs, python, monorepo"
exit 1
}
Write-Host "Installing Quality Rails: $Template"
Write-Host "Target directory: $TargetDir"
Write-Host ""
# Copy template files
Write-Host "Copying template files..."
if (Test-Path "$TemplateDir\.husky") {
Copy-Item -Path "$TemplateDir\.husky" -Destination $TargetDir -Recurse -Force
}
Copy-Item -Path "$TemplateDir\.lintstagedrc.js" -Destination $TargetDir -Force -ErrorAction SilentlyContinue
Copy-Item -Path "$TemplateDir\.eslintrc.strict.js" -Destination "$TargetDir\.eslintrc.js" -Force -ErrorAction SilentlyContinue
Copy-Item -Path "$TemplateDir\tsconfig.strict.json" -Destination "$TargetDir\tsconfig.json" -Force -ErrorAction SilentlyContinue
Copy-Item -Path "$TemplateDir\.woodpecker.yml" -Destination $TargetDir -Force -ErrorAction SilentlyContinue
Write-Host "✓ Files copied"
if (Test-Path "$TargetDir\package.json") {
Write-Host ""
Write-Host "⚠ package.json exists. Please manually merge dependencies from:"
Write-Host " $TemplateDir\package.json.snippet"
} else {
Write-Host "⚠ No package.json found. Create one and add dependencies from:"
Write-Host " $TemplateDir\package.json.snippet"
}
Write-Host ""
Write-Host "✓ Quality Rails installed successfully!"
Write-Host ""
Write-Host "Next steps:"
Write-Host "1. Install dependencies: npm install"
Write-Host "2. Initialize husky: npx husky install"
Write-Host "3. Run verification: ..\quality-rails\scripts\verify.ps1"

View File

@@ -0,0 +1,75 @@
#!/bin/bash
set -e
# Quality Rails Installation Script
# Usage: ./install.sh --template typescript-node [--target /path/to/project]
TEMPLATE=""
TARGET_DIR="."
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--template)
TEMPLATE="$2"
shift 2
;;
--target)
TARGET_DIR="$2"
shift 2
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 --template <template-name> [--target <directory>]"
exit 1
;;
esac
done
if [ -z "$TEMPLATE" ]; then
echo "Error: --template is required"
echo "Available templates: typescript-node, typescript-nextjs, python, monorepo"
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
TEMPLATE_DIR="$REPO_ROOT/templates/$TEMPLATE"
if [ ! -d "$TEMPLATE_DIR" ]; then
echo "Error: Template '$TEMPLATE' not found at $TEMPLATE_DIR"
exit 1
fi
echo "Installing Quality Rails: $TEMPLATE"
echo "Target directory: $TARGET_DIR"
echo ""
# Copy template files
echo "Copying template files..."
cp -r "$TEMPLATE_DIR/.husky" "$TARGET_DIR/" 2>/dev/null || true
cp "$TEMPLATE_DIR/.lintstagedrc.js" "$TARGET_DIR/" 2>/dev/null || true
cp "$TEMPLATE_DIR/.eslintrc.strict.js" "$TARGET_DIR/.eslintrc.js" 2>/dev/null || true
cp "$TEMPLATE_DIR/tsconfig.strict.json" "$TARGET_DIR/tsconfig.json" 2>/dev/null || true
cp "$TEMPLATE_DIR/.woodpecker.yml" "$TARGET_DIR/" 2>/dev/null || true
echo "✓ Files copied"
# Check if package.json exists
if [ -f "$TARGET_DIR/package.json" ]; then
echo ""
echo "⚠ package.json exists. Please manually merge dependencies from:"
echo " $TEMPLATE_DIR/package.json.snippet"
else
echo "⚠ No package.json found. Create one and add dependencies from:"
echo " $TEMPLATE_DIR/package.json.snippet"
fi
echo ""
echo "✓ Quality Rails installed successfully!"
echo ""
echo "Next steps:"
echo "1. Install dependencies: npm install"
echo "2. Initialize husky: npx husky install"
echo "3. Run verification: ~/.mosaic/bin/mosaic-quality-verify --target $TARGET_DIR"
echo ""

View File

@@ -0,0 +1,57 @@
# Quality Rails Verification Script (Windows)
Write-Host "═══════════════════════════════════════════"
Write-Host "Quality Rails Enforcement Verification"
Write-Host "═══════════════════════════════════════════"
Write-Host ""
$Passed = 0
$Failed = 0
# Test 1: Type error blocked
Write-Host "Test 1: Type errors should be blocked..."
"const x: string = 123;" | Out-File -FilePath test-file.ts -Encoding utf8
git add test-file.ts 2>$null
$output = git commit -m "Test commit" 2>&1 | Out-String
if ($output -match "error") {
Write-Host "✅ PASS: Type errors blocked" -ForegroundColor Green
$Passed++
} else {
Write-Host "❌ FAIL: Type errors NOT blocked" -ForegroundColor Red
$Failed++
}
git reset HEAD test-file.ts 2>$null
Remove-Item test-file.ts -ErrorAction SilentlyContinue
# Test 2: any type blocked
Write-Host ""
Write-Host "Test 2: 'any' types should be blocked..."
"const x: any = 123;" | Out-File -FilePath test-file.ts -Encoding utf8
git add test-file.ts 2>$null
$output = git commit -m "Test commit" 2>&1 | Out-String
if ($output -match "no-explicit-any") {
Write-Host "✅ PASS: 'any' types blocked" -ForegroundColor Green
$Passed++
} else {
Write-Host "❌ FAIL: 'any' types NOT blocked" -ForegroundColor Red
$Failed++
}
git reset HEAD test-file.ts 2>$null
Remove-Item test-file.ts -ErrorAction SilentlyContinue
# Summary
Write-Host ""
Write-Host "═══════════════════════════════════════════"
Write-Host "Verification Summary"
Write-Host "═══════════════════════════════════════════"
Write-Host "✅ Passed: $Passed"
Write-Host "❌ Failed: $Failed"
Write-Host ""
if ($Failed -eq 0) {
Write-Host "🎉 All tests passed! Quality enforcement is working." -ForegroundColor Green
exit 0
} else {
Write-Host "⚠ Some tests failed. Review configuration." -ForegroundColor Yellow
exit 1
}

92
rails/quality/scripts/verify.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/bin/bash
# Quality Rails Verification Script
# Tests that enforcement actually works
echo "═══════════════════════════════════════════"
echo "Quality Rails Enforcement Verification"
echo "═══════════════════════════════════════════"
echo ""
PASSED=0
FAILED=0
# Test 1: Type error blocked
echo "Test 1: Type errors should be blocked..."
echo "const x: string = 123;" > test-file.ts
git add test-file.ts 2>/dev/null
if git commit -m "Test commit" 2>&1 | grep -q "error"; then
echo "✅ PASS: Type errors blocked"
((PASSED++))
else
echo "❌ FAIL: Type errors NOT blocked"
((FAILED++))
fi
git reset HEAD test-file.ts 2>/dev/null
rm test-file.ts 2>/dev/null
# Test 2: any type blocked
echo ""
echo "Test 2: 'any' types should be blocked..."
echo "const x: any = 123;" > test-file.ts
git add test-file.ts 2>/dev/null
if git commit -m "Test commit" 2>&1 | grep -q "no-explicit-any"; then
echo "✅ PASS: 'any' types blocked"
((PASSED++))
else
echo "❌ FAIL: 'any' types NOT blocked"
((FAILED++))
fi
git reset HEAD test-file.ts 2>/dev/null
rm test-file.ts 2>/dev/null
# Test 3: Hardcoded secret blocked (if git-secrets installed)
echo ""
echo "Test 3: Hardcoded secrets should be blocked..."
if command -v git-secrets &> /dev/null; then
echo "const password = 'SuperSecret123!';" > test-file.ts
git add test-file.ts 2>/dev/null
if git commit -m "Test commit" 2>&1 | grep -q -i "secret\|password"; then
echo "✅ PASS: Secrets blocked"
((PASSED++))
else
echo "⚠ WARN: Secrets NOT blocked (git-secrets may need configuration)"
((FAILED++))
fi
git reset HEAD test-file.ts 2>/dev/null
rm test-file.ts 2>/dev/null
else
echo "⚠ SKIP: git-secrets not installed"
fi
# Test 4: Lint error blocked
echo ""
echo "Test 4: Lint errors should be blocked..."
echo "const x=123" > test-file.ts # Missing semicolon
git add test-file.ts 2>/dev/null
if git commit -m "Test commit" 2>&1 | grep -q "prettier"; then
echo "✅ PASS: Lint errors blocked"
((PASSED++))
else
echo "❌ FAIL: Lint errors NOT blocked"
((FAILED++))
fi
git reset HEAD test-file.ts 2>/dev/null
rm test-file.ts 2>/dev/null
# Summary
echo ""
echo "═══════════════════════════════════════════"
echo "Verification Summary"
echo "═══════════════════════════════════════════"
echo "✅ Passed: $PASSED"
echo "❌ Failed: $FAILED"
echo ""
if [ $FAILED -eq 0 ]; then
echo "🎉 All tests passed! Quality enforcement is working."
exit 0
else
echo "⚠ Some tests failed. Review configuration."
exit 1
fi

View File

@@ -0,0 +1,64 @@
// Root ESLint config for monorepo
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
plugins: ['@typescript-eslint', 'security'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:security/recommended',
'plugin:prettier/recommended',
],
rules: {
// Type Safety - STRICT
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Promise/Async Safety
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
// Code Quality
'@typescript-eslint/no-var-requires': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
'@typescript-eslint/prefer-optional-chain': 'warn',
// Prettier
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
},
ignorePatterns: [
'node_modules',
'dist',
'build',
'.next',
'out',
'coverage',
'.turbo',
],
overrides: [
{
// Next.js apps
files: ['apps/**/app/**/*.{ts,tsx}', 'apps/**/pages/**/*.{ts,tsx}'],
extends: ['next/core-web-vitals'],
},
{
// NestJS apps
files: ['apps/**/*.controller.ts', 'apps/**/*.service.ts', 'apps/**/*.module.ts'],
rules: {
'@typescript-eslint/explicit-function-return-type': 'error',
},
},
],
};

View File

@@ -0,0 +1,2 @@
npx lint-staged
npx git-secrets --scan || echo "Warning: git-secrets not installed"

View File

@@ -0,0 +1,29 @@
// Monorepo-aware lint-staged configuration
module.exports = {
// TypeScript files across all packages
'**/*.{ts,tsx}': (filenames) => {
const commands = [
`eslint ${filenames.join(' ')} --fix --max-warnings=0`,
];
// Only run tsc in packages that have tsconfig.json
// NOTE: lint-staged passes absolute paths, so we need to match both:
// - Relative: apps/api/src/file.ts
// - Absolute: /home/user/project/apps/api/src/file.ts
const packages = [...new Set(filenames.map(f => {
const match = f.match(/(?:^|\/)(apps|packages)\/([^/]+)\//);
return match ? `${match[1]}/${match[2]}` : null;
}))].filter(Boolean);
packages.forEach(pkg => {
commands.push(`tsc --project ${pkg}/tsconfig.json --noEmit`);
});
return commands;
},
// Format all files
'**/*.{js,jsx,ts,tsx,json,md,yml,yaml}': [
'prettier --write',
],
};

View File

@@ -0,0 +1,67 @@
# Woodpecker CI Quality Enforcement Pipeline - Monorepo
when:
- event: [push, pull_request, manual]
variables:
- &node_image "node:20-alpine"
- &install_deps |
corepack enable
npm ci --ignore-scripts
steps:
install:
image: *node_image
commands:
- *install_deps
security-audit:
image: *node_image
commands:
- *install_deps
- npm audit --audit-level=high
depends_on:
- install
lint:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
commands:
- *install_deps
- npm run lint
depends_on:
- install
typecheck:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
commands:
- *install_deps
- npm run type-check
depends_on:
- install
test:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
commands:
- *install_deps
- npm run test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
depends_on:
- install
build:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
NODE_ENV: "production"
commands:
- *install_deps
- npm run build
depends_on:
- lint
- typecheck
- test
- security-audit

View File

@@ -0,0 +1,96 @@
# Monorepo Structure
This quality-rails monorepo template supports the following structure:
```
monorepo/
├── apps/
│ ├── web/ # Next.js frontend
│ │ ├── package.json
│ │ ├── tsconfig.json # extends ../../tsconfig.base.json
│ │ └── .eslintrc.js # extends ../../.eslintrc.strict.js
│ └── api/ # NestJS backend
│ ├── package.json
│ ├── tsconfig.json
│ └── .eslintrc.js
├── packages/
│ ├── shared-types/ # Shared TypeScript types
│ ├── ui/ # Shared UI components
│ └── config/ # Shared configuration
├── .husky/
│ └── pre-commit
├── .lintstagedrc.js # Multi-package aware
├── .eslintrc.strict.js # Root ESLint config
├── tsconfig.base.json # Base TypeScript config
├── turbo.json # TurboRepo configuration
├── pnpm-workspace.yaml # pnpm workspaces
└── package.json # Root package with scripts
```
## Package-Specific Configs
Each package extends the root configuration:
**apps/web/tsconfig.json:**
```json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "ES2022"],
"jsx": "preserve",
"noEmit": true,
"paths": {
"@/*": ["./*"]
}
},
"include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules", ".next"]
}
```
**apps/api/tsconfig.json:**
```json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test"]
}
```
## Running Commands
**All packages:**
```bash
npm run lint # Lint all packages
npm run type-check # Type check all packages
npm run test # Test all packages
npm run build # Build all packages
```
**Single package:**
```bash
npm run lint --workspace=apps/web
npm run dev --workspace=apps/api
```
**With TurboRepo:**
```bash
turbo run build # Build with caching
turbo run dev --parallel # Run dev servers in parallel
```
## Pre-Commit Enforcement
lint-staged automatically detects which packages contain modified files and runs:
- ESLint on changed files
- TypeScript check on affected packages
- Prettier on all changed files
Only runs checks on packages that have changes (efficient).

View File

@@ -0,0 +1,30 @@
{
"name": "monorepo",
"version": "0.0.1",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"type-check": "turbo run type-check",
"test": "turbo run test",
"prepare": "husky install"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-security": "^3.0.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"prettier": "^3.0.0",
"turbo": "^2.0.0",
"typescript": "^5.6.0"
}
}

View File

@@ -0,0 +1,3 @@
packages:
- 'apps/*'
- 'packages/*'

View File

@@ -0,0 +1,39 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"composite": true,
"incremental": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
// STRICT MODE - All enabled
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
// Additional Checks
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false
},
"exclude": ["node_modules", "dist", "build", ".next", "out"]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "build/**"]
},
"lint": {
"cache": false
},
"type-check": {
"cache": false
},
"test": {
"cache": false
},
"dev": {
"cache": false,
"persistent": true
}
}
}

View File

@@ -0,0 +1,40 @@
module.exports = {
extends: [
'next/core-web-vitals',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:security/recommended',
'plugin:prettier/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'security'],
rules: {
// Type Safety - STRICT
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Promise/Async Safety
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
// React/Next.js specific
'react/no-unescaped-entities': 'off',
'react-hooks/exhaustive-deps': 'warn',
// Prettier
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
},
ignorePatterns: ['.next', 'out', 'public', '.eslintrc.js'],
};

View File

@@ -0,0 +1,2 @@
npx lint-staged
npx git-secrets --scan || echo "Warning: git-secrets not installed"

View File

@@ -0,0 +1,9 @@
module.exports = {
'*.{ts,tsx}': [
'eslint --fix --max-warnings=0',
'bash -c "tsc --noEmit"',
],
'*.{js,jsx,ts,tsx,json,md,css}': [
'prettier --write',
],
};

View File

@@ -0,0 +1,67 @@
# Woodpecker CI Quality Enforcement Pipeline - Next.js
when:
- event: [push, pull_request, manual]
variables:
- &node_image "node:20-alpine"
- &install_deps |
corepack enable
npm ci --ignore-scripts
steps:
install:
image: *node_image
commands:
- *install_deps
security-audit:
image: *node_image
commands:
- *install_deps
- npm audit --audit-level=high
depends_on:
- install
lint:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
commands:
- *install_deps
- npm run lint
depends_on:
- install
typecheck:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
commands:
- *install_deps
- npm run type-check
depends_on:
- install
test:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
commands:
- *install_deps
- npm run test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
depends_on:
- install
build:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
NODE_ENV: "production"
commands:
- *install_deps
- npm run build
depends_on:
- lint
- typecheck
- test
- security-audit

View File

@@ -0,0 +1,50 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
// Security headers
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self';"
}
],
},
];
},
};
module.exports = nextConfig;

View File

@@ -0,0 +1,33 @@
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint --max-warnings=0",
"type-check": "tsc --noEmit",
"test": "jest",
"prepare": "husky install"
},
"dependencies": {
"next": "^14.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^9.0.0",
"eslint-config-next": "^14.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-security": "^3.0.0",
"husky": "^9.1.7",
"jest": "^29.0.0",
"lint-staged": "^16.2.7",
"prettier": "^3.0.0",
"typescript": "^5.6.0"
}
}

View File

@@ -0,0 +1,45 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules", ".next", "out"]
}

View File

@@ -0,0 +1,53 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin', 'security'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:security/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
// Type Safety - STRICT
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Promise/Async Safety
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
// Type Assertions
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/consistent-type-assertions': [
'error',
{ assertionStyle: 'as', objectLiteralTypeAssertions: 'never' },
],
// Code Quality
'@typescript-eslint/no-var-requires': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
'@typescript-eslint/prefer-optional-chain': 'warn',
'@typescript-eslint/strict-boolean-expressions': 'warn',
// Prettier
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
},
};

View File

@@ -0,0 +1,2 @@
npx lint-staged
npx git-secrets --scan || echo "Warning: git-secrets not installed"

View File

@@ -0,0 +1,9 @@
module.exports = {
'*.{ts,tsx}': [
'eslint --fix --max-warnings=0',
'bash -c "tsc --noEmit"',
],
'*.{js,jsx,ts,tsx,json,md}': [
'prettier --write',
],
};

View File

@@ -0,0 +1,66 @@
# Woodpecker CI Quality Enforcement Pipeline
# Runs on: push, pull_request, manual
when:
- event: [push, pull_request, manual]
variables:
- &node_image "node:20-alpine"
- &install_deps |
corepack enable
npm ci --ignore-scripts
steps:
# Stage 1: Install
install:
image: *node_image
commands:
- *install_deps
# Stage 2: Security Audit
security-audit:
image: *node_image
commands:
- *install_deps
- npm audit --audit-level=high
depends_on:
- install
# Stage 3: Lint
lint:
image: *node_image
commands:
- *install_deps
- npm run lint
depends_on:
- install
# Stage 4: Type Check
typecheck:
image: *node_image
commands:
- *install_deps
- npm run type-check
depends_on:
- install
# Stage 5: Test with Coverage
test:
image: *node_image
commands:
- *install_deps
- npm run test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
depends_on:
- install
# Stage 6: Build
build:
image: *node_image
commands:
- *install_deps
- npm run build
depends_on:
- lint
- typecheck
- test
- security-audit

View File

@@ -0,0 +1,22 @@
{
"scripts": {
"lint": "eslint 'src/**/*.{ts,tsx}' --max-warnings=0",
"type-check": "tsc --noEmit",
"test": "jest",
"build": "tsc",
"prepare": "husky install"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-security": "^3.0.0",
"husky": "^9.1.7",
"jest": "^29.0.0",
"lint-staged": "^16.2.7",
"prettier": "^3.0.0",
"typescript": "^5.6.0"
}
}

View File

@@ -0,0 +1,42 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"lib": ["ES2022"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"removeComments": true,
"incremental": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
// STRICT MODE - All enabled
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
// Additional Checks
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
}

View File

@@ -13,3 +13,19 @@ This repository is attached to the machine-wide Mosaic framework.
- Keep universal standards in `~/.mosaic`
- Keep repo-specific behavior in this repo
- Avoid copying large runtime configs into each project
## Optional Quality Rails
Use `.mosaic/quality-rails.yml` to track whether quality rails are enabled for this repo.
Apply a template:
```bash
~/.mosaic/bin/mosaic-quality-apply --template <template> --target .
```
Verify enforcement:
```bash
~/.mosaic/bin/mosaic-quality-verify --target .
```

View File

@@ -0,0 +1,10 @@
enabled: false
template: ""
# Set enabled: true and choose one template:
# - typescript-node
# - typescript-nextjs
# - monorepo
#
# Apply manually:
# ~/.mosaic/bin/mosaic-quality-apply --template <template> --target <repo>