integrate generalized quality-rails into mosaic bootstrap
This commit is contained in:
30
README.md
30
README.md
@@ -103,6 +103,30 @@ Inside any compatible repository:
|
|||||||
|
|
||||||
Wrapper commands call local repo scripts under `scripts/agent/`.
|
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)
|
## Bootstrap Any Repo (Slave Linkage)
|
||||||
|
|
||||||
Attach any repository/workspace to the master layer:
|
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
|
~/.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:
|
This creates/updates:
|
||||||
|
|
||||||
- `.mosaic/` (repo-specific hook/config surface)
|
- `.mosaic/` (repo-specific hook/config surface)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ Master/slave model:
|
|||||||
- Pull before edits when collaborating in shared repos.
|
- Pull before edits when collaborating in shared repos.
|
||||||
- Run validation checks before claiming completion.
|
- Run validation checks before claiming completion.
|
||||||
- Apply quality rails from `~/.mosaic/rails/` when relevant (review, QA, git workflow).
|
- 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.
|
- Avoid hardcoded secrets and token leakage in remotes/commits.
|
||||||
- Do not perform destructive git/file actions without explicit instruction.
|
- Do not perform destructive git/file actions without explicit instruction.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
TARGET_DIR="$(pwd)"
|
TARGET_DIR="$(pwd)"
|
||||||
FORCE=0
|
FORCE=0
|
||||||
|
QUALITY_TEMPLATE=""
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -10,6 +11,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
FORCE=1
|
FORCE=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--quality-template)
|
||||||
|
QUALITY_TEMPLATE="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
TARGET_DIR="$1"
|
TARGET_DIR="$1"
|
||||||
shift
|
shift
|
||||||
@@ -48,6 +53,7 @@ copy_file() {
|
|||||||
|
|
||||||
copy_file "$TEMPLATE_ROOT/.mosaic/README.md" "$TARGET_DIR/.mosaic/README.md"
|
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/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
|
for file in "$TEMPLATE_ROOT"/scripts/agent/*.sh; do
|
||||||
base="$(basename "$file")"
|
base="$(basename "$file")"
|
||||||
@@ -90,3 +96,17 @@ fi
|
|||||||
|
|
||||||
echo "[mosaic] Repo bootstrap complete: $TARGET_DIR"
|
echo "[mosaic] Repo bootstrap complete: $TARGET_DIR"
|
||||||
echo "[mosaic] Next: edit $TARGET_DIR/.mosaic/repo-hooks.sh with project workflows"
|
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
|
||||||
|
|||||||
@@ -115,12 +115,15 @@ echo "[mosaic-doctor] Mosaic home: $MOSAIC_HOME"
|
|||||||
expect_file "$MOSAIC_HOME/STANDARDS.md"
|
expect_file "$MOSAIC_HOME/STANDARDS.md"
|
||||||
expect_dir "$MOSAIC_HOME/guides"
|
expect_dir "$MOSAIC_HOME/guides"
|
||||||
expect_dir "$MOSAIC_HOME/rails"
|
expect_dir "$MOSAIC_HOME/rails"
|
||||||
|
expect_dir "$MOSAIC_HOME/rails/quality"
|
||||||
expect_dir "$MOSAIC_HOME/profiles"
|
expect_dir "$MOSAIC_HOME/profiles"
|
||||||
expect_dir "$MOSAIC_HOME/templates/agent"
|
expect_dir "$MOSAIC_HOME/templates/agent"
|
||||||
expect_dir "$MOSAIC_HOME/skills"
|
expect_dir "$MOSAIC_HOME/skills"
|
||||||
expect_dir "$MOSAIC_HOME/skills-local"
|
expect_dir "$MOSAIC_HOME/skills-local"
|
||||||
expect_file "$MOSAIC_HOME/bin/mosaic-link-runtime-assets"
|
expect_file "$MOSAIC_HOME/bin/mosaic-link-runtime-assets"
|
||||||
expect_file "$MOSAIC_HOME/bin/mosaic-sync-skills"
|
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).
|
# Claude runtime file checks (copied, non-symlink).
|
||||||
for rf in CLAUDE.md settings.json hooks-config.json context7-integration.md; do
|
for rf in CLAUDE.md settings.json hooks-config.json context7-integration.md; do
|
||||||
|
|||||||
65
bin/mosaic-quality-apply
Executable file
65
bin/mosaic-quality-apply
Executable 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
52
bin/mosaic-quality-verify
Executable 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
189
rails/quality/PHILOSOPHY.md
Normal 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
166
rails/quality/README.md
Normal 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.
|
||||||
174
rails/quality/docs/CI-SETUP.md
Normal file
174
rails/quality/docs/CI-SETUP.md
Normal 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
|
||||||
164
rails/quality/docs/TYPESCRIPT-SETUP.md
Normal file
164
rails/quality/docs/TYPESCRIPT-SETUP.md
Normal 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.
|
||||||
53
rails/quality/scripts/install.ps1
Normal file
53
rails/quality/scripts/install.ps1
Normal 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"
|
||||||
75
rails/quality/scripts/install.sh
Executable file
75
rails/quality/scripts/install.sh
Executable 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 ""
|
||||||
57
rails/quality/scripts/verify.ps1
Normal file
57
rails/quality/scripts/verify.ps1
Normal 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
92
rails/quality/scripts/verify.sh
Executable 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
|
||||||
64
rails/quality/templates/monorepo/.eslintrc.strict.js
Normal file
64
rails/quality/templates/monorepo/.eslintrc.strict.js
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
2
rails/quality/templates/monorepo/.husky/pre-commit
Normal file
2
rails/quality/templates/monorepo/.husky/pre-commit
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
npx lint-staged
|
||||||
|
npx git-secrets --scan || echo "Warning: git-secrets not installed"
|
||||||
29
rails/quality/templates/monorepo/.lintstagedrc.js
Normal file
29
rails/quality/templates/monorepo/.lintstagedrc.js
Normal 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',
|
||||||
|
],
|
||||||
|
};
|
||||||
67
rails/quality/templates/monorepo/.woodpecker.yml
Normal file
67
rails/quality/templates/monorepo/.woodpecker.yml
Normal 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
|
||||||
96
rails/quality/templates/monorepo/README-STRUCTURE.md
Normal file
96
rails/quality/templates/monorepo/README-STRUCTURE.md
Normal 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).
|
||||||
30
rails/quality/templates/monorepo/package.json.snippet
Normal file
30
rails/quality/templates/monorepo/package.json.snippet
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
packages:
|
||||||
|
- 'apps/*'
|
||||||
|
- 'packages/*'
|
||||||
39
rails/quality/templates/monorepo/tsconfig.base.json
Normal file
39
rails/quality/templates/monorepo/tsconfig.base.json
Normal 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"]
|
||||||
|
}
|
||||||
23
rails/quality/templates/monorepo/turbo.json.snippet
Normal file
23
rails/quality/templates/monorepo/turbo.json.snippet
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
npx lint-staged
|
||||||
|
npx git-secrets --scan || echo "Warning: git-secrets not installed"
|
||||||
@@ -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',
|
||||||
|
],
|
||||||
|
};
|
||||||
67
rails/quality/templates/typescript-nextjs/.woodpecker.yml
Normal file
67
rails/quality/templates/typescript-nextjs/.woodpecker.yml
Normal 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
|
||||||
@@ -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;
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
53
rails/quality/templates/typescript-node/.eslintrc.strict.js
Normal file
53
rails/quality/templates/typescript-node/.eslintrc.strict.js
Normal 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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
npx lint-staged
|
||||||
|
npx git-secrets --scan || echo "Warning: git-secrets not installed"
|
||||||
9
rails/quality/templates/typescript-node/.lintstagedrc.js
Normal file
9
rails/quality/templates/typescript-node/.lintstagedrc.js
Normal 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',
|
||||||
|
],
|
||||||
|
};
|
||||||
66
rails/quality/templates/typescript-node/.woodpecker.yml
Normal file
66
rails/quality/templates/typescript-node/.woodpecker.yml
Normal 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
|
||||||
22
rails/quality/templates/typescript-node/package.json.snippet
Normal file
22
rails/quality/templates/typescript-node/package.json.snippet
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
42
rails/quality/templates/typescript-node/tsconfig.strict.json
Normal file
42
rails/quality/templates/typescript-node/tsconfig.strict.json
Normal 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"]
|
||||||
|
}
|
||||||
@@ -13,3 +13,19 @@ This repository is attached to the machine-wide Mosaic framework.
|
|||||||
- Keep universal standards in `~/.mosaic`
|
- Keep universal standards in `~/.mosaic`
|
||||||
- Keep repo-specific behavior in this repo
|
- Keep repo-specific behavior in this repo
|
||||||
- Avoid copying large runtime configs into each project
|
- 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 .
|
||||||
|
```
|
||||||
|
|||||||
10
templates/repo/.mosaic/quality-rails.yml
Normal file
10
templates/repo/.mosaic/quality-rails.yml
Normal 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>
|
||||||
Reference in New Issue
Block a user