
Dx Harness
- 79 installs
- 191 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
dx-harness is a Claude Code skill for ai & agent building.
About
dx-harness is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- dx-harness
- AI & Agent Building
- AI-coding skill
Dx Harness by the numbers
- 79 all-time installs (skills.sh)
- +6 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #5,292 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pproenca/dot-skills --skill dx-harnessAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 79 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do I helps with ai & agent building tasks during AI-assisted development.?
Helps with ai & agent building tasks during AI-assisted development.
Who is it for?
Best when you're working on ai & agent building and need structured help with dx harness.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks during AI-assisted development., or when dx-harness is a claude code skill for ai & agent building.
What you get
Structured output aligned to dx-harness: dx-harness, AI & Agent Building.
Files
DX Harness
Audits, scaffolds, and maintains the developer-experience harness of a repository. The harness is the meta-tooling that makes a dev loop fast: one-command bootstrap, one-command reset, one-command seed, discoverable conventions, agent-friendly entry points.
This skill exists because dev-experience attrition is real: harnesses rot, manual steps creep back in, conventions drift, and the cost is paid in micro-friction every day. The skill detects attrition systematically and applies canonical fixes.
When to Apply
Trigger this skill when:
- A user describes a repeated manual chore ("every time I reset the db I have to re-register", "I always run these three commands before working")
- A user mentions slow onboarding or asks "how do I get this running"
- A new repo is being bootstrapped and needs a harness from day one
- A user explicitly asks to audit DX or measure time-to-first-commit (TTFC)
- A user asks to fix missing bootstrap/reset/seed scripts or AGENTS.md
- A periodic maintenance pass is needed (e.g., after a release, after onboarding a new engineer)
Workflow Overview
┌────────────────────────────────────────────────────────────┐
│ 1. DISCOVER scripts/discover.sh → repo-fingerprint │
│ 2. AUDIT scripts/audit.sh → dx-audit.json │
│ 3. PRIORITIZE scripts/prioritize.sh → ranked findings │
│ 4. SCAFFOLD scripts/scaffold-*.sh → scratch dir │
│ 5. CONFIRM show diff to user; user picks what to apply│
│ 6. VERIFY scripts/verify.sh → assertions + timing │
│ 7. TRACK scripts/track-attrition.sh → audit log │
└────────────────────────────────────────────────────────────┘All scaffolded edits land in a scratch directory first (${TMPDIR}/dx-harness-<timestamp>/). The user reviews the diff and chooses what to copy back into the repo. No surprise writes to the working tree.
Tool Requirements
Required: bash, git, node, jq.
Detected at runtime (the skill adapts — it never forces a migration):
| Toolchain | Detection signal |
|---|---|
| npm/pnpm/yarn/bun | package.json, pnpm-lock.yaml, yarn.lock, bun.lockb |
| cargo | Cargo.toml |
| go modules | go.mod |
| Python (uv/poetry/pip) | pyproject.toml, uv.lock, poetry.lock, requirements.txt |
| just | Justfile or justfile |
| make | Makefile |
| docker compose | docker-compose.yml, compose.yaml |
| database (postgres) | docker-compose.yml services, DATABASE_URL env reference |
Risk Level
Write. The skill writes files but does not delete user data, force-push, or run destructive ops. Default mode stages everything in a scratch directory; the user explicitly approves each application. Verification runs in a separate git worktree so timing measurements never touch the working tree.
Setup
On first invocation, the skill checks config.json and prompts via AskUserQuestion for any empty required fields. See config.json for fields and defaults.
Key defaults:
ttfc_target_seconds: 60— the time-to-first-commit goal in secondsapply_mode: "scratch"— scaffolded edits go to scratch dir first; user copies inaudit_log_path: "${CLAUDE_PLUGIN_DATA}/dx-harness/audits.log"— persistent attrition history
Quick Reference
Run a full audit + scaffold pass
bash scripts/discover.sh > /tmp/fingerprint.json
bash scripts/audit.sh /tmp/fingerprint.json > /tmp/audit.json
bash scripts/prioritize.sh /tmp/audit.jsonScaffold one specific harness piece
bash scripts/scaffold-bootstrap.sh /tmp/fingerprint.json
bash scripts/scaffold-reset.sh /tmp/fingerprint.json
bash scripts/scaffold-seed.sh /tmp/fingerprint.json
bash scripts/scaffold-agents-md.sh /tmp/fingerprint.json
bash scripts/scaffold-justfile.sh /tmp/fingerprint.jsonVerify the harness works end-to-end
bash scripts/verify.sh
# runs bootstrap in a scratch worktree, times it, asserts TTFC < targetTrack attrition over time
bash scripts/track-attrition.sh /tmp/audit.json
# appends to audits.log, prints diff vs previous runHow to Use
The workflow scripts produce JSON between steps so they compose. The agent should read references/workflow.md for full step-by-step orchestration, including error handling and when to stop and ask the user.
For deeper context:
| File | Read When |
|---|---|
| references/workflow.md | Executing the workflow (always start here) |
| references/audit-checklist.md | Understanding what audit.sh checks and why |
| references/fix-recipes.md | Choosing which scaffold script applies for a finding |
| references/attrition-patterns.md | Reading git history for DX-rot signals |
Gotchas
See gotchas.md — accumulated failure points discovered over time. Always append new ones rather than rewriting.
Related Skills
dev-skill:*— for creating new agent-friendly skills (often the output of an AGENTS.md scaffolding pass references skills)bug-review— pairs naturally: bad DX often surfaces as repeat bug categories
# Agent & Developer Conventions
This file is the table of contents to the dev loop. If you're an agent (or a new human), start here.
Languages detected: **{{LANGUAGES}}**
Task runner: **{{TASK_RUNNER}}**
## Dev Loop
| Action | Command |
|-------------|------------------|
| Bootstrap | `{{BOOTSTRAP_CMD}}` |
| Dev server | `{{DEV_CMD}}` |
| Run tests | `{{TEST_CMD}}` |
| Watch tests | `{{WATCH_CMD}}` |
| Reset state | `{{RESET_CMD}}` |
| Re-seed | `{{SEED_CMD}}` |
Run `{{BOOTSTRAP_CMD}}` once after cloning. Use `{{RESET_CMD}}` whenever you want a clean slate — it re-runs bootstrap and re-seeds.
{{DB_SECTION}}
## Where things live
- Source code: see top-level directories
- Tests: alongside source or under a `tests/`/`test/`/`__tests__` directory
- Configuration: `.env.example` shows what env vars are needed
## Conventions for Agents
1. **Run `{{TEST_CMD}}` before declaring a task complete.** If tests fail, fix them or surface the failure.
2. **Use `{{RESET_CMD}}` instead of manual cleanup.** Don't re-register, re-login, or hand-edit the DB.
3. **Read existing patterns before introducing new ones.** Match the file naming, module structure, and import style already in the codebase.
4. **Update this file when you add new harness commands.** If you wire a new common command, add a row to the Dev Loop table.
## When the harness drifts
Run the dx-harness skill again. It will diff the current state against the audit log and surface regressions.
#!/usr/bin/env bash
# bootstrap.sh — bring a fresh checkout to a runnable state
# Generated by dx-harness. Idempotent: safe to run twice.
#
# Goal: under 60 seconds from clone to working dev loop.
set -euo pipefail
# --- 1. Tooling check ---
missing=()
for cmd in git; do
command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd")
done
if [[ ${#missing[@]} -gt 0 ]]; then
echo "Missing required tools: ${missing[*]}" >&2
echo "Install them, then re-run ./bootstrap.sh" >&2
exit 1
fi
# --- 2. Env file ---
if [[ ! -f .env && -f .env.example ]]; then
cp .env.example .env
echo "Copied .env.example → .env"
fi
# --- 3. Install dependencies ---
{{INSTALL_CMD}}
{{PY_INSTALL}}
{{RUST_BUILD}}
{{GO_INSTALL}}
# --- 4. Start required services ---
{{DB_START}}
# --- 5. Run migrations + seed (if scripts exist) ---
if [[ -x ./scripts/migrate.sh ]]; then
./scripts/migrate.sh
elif [[ -x ./migrate.sh ]]; then
./migrate.sh
fi
if [[ -x ./seed.sh ]]; then
./seed.sh
elif [[ -x ./scripts/seed.sh ]]; then
./scripts/seed.sh
fi
# --- 6. Summary ---
cat <<EOF
Bootstrap complete. Next steps:
- Dev server: (see AGENTS.md or Justfile)
- Run tests: (see AGENTS.md or Justfile)
- Reset state: ./reset.sh
EOF
# Justfile fragment generated by dx-harness.
# Append to your existing Justfile (do not overwrite).
#
# If your Justfile has no `default:` target yet, add the two lines below
# to the top of your Justfile so `just` (no args) lists all targets:
#
# default:
# @just --list
#
# These targets do NOT include `default:` so this fragment is append-safe.
# Bring a fresh checkout to a runnable state
bootstrap:
./bootstrap.sh
# Return the dev environment to a known-clean state
reset:
./reset.sh
# Re-seed dev data (test user, fixtures)
seed:
./seed.sh
# Run the dev server with hot reload
dev:
{{DEV_CMD}}
# Run the test suite once
test:
{{TEST_CMD}}
# Run tests in watch mode
test-watch:
{{WATCH_CMD}}
# Makefile fragment generated by dx-harness.
# Append to your existing Makefile (do not overwrite).
.PHONY: bootstrap reset seed dev test test-watch help
# Default target: list everything
help:
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
bootstrap: ## Bring a fresh checkout to a runnable state
./bootstrap.sh
reset: ## Return the dev environment to a known-clean state
./reset.sh
seed: ## Re-seed dev data (test user, fixtures)
./seed.sh
dev: ## Run the dev server with hot reload
{{DEV_CMD}}
test: ## Run the test suite once
{{TEST_CMD}}
test-watch: ## Run tests in watch mode
{{WATCH_CMD}}
#!/usr/bin/env bash
# reset.sh — return the dev environment to a known-clean state
# Generated by dx-harness.
#
# After this script + ./bootstrap.sh, the dev should NOT need to re-register
# or re-login. seed.sh (run as part of bootstrap) provides credentialed test
# users — see AGENTS.md for the canonical email/password.
set -euo pipefail
# --- Safety: never run against prod ---
if [[ "${NODE_ENV:-}" == "production" ]] || [[ "${ENVIRONMENT:-}" == "production" ]]; then
echo "Refusing to run reset.sh with NODE_ENV/ENVIRONMENT=production" >&2
exit 1
fi
# --- 1. Preserve a modified .env ---
if [[ -f .env ]]; then
if [[ -f .env.example ]] && ! cmp -s .env .env.example; then
cp .env .env.bak
echo "Backed up modified .env → .env.bak"
fi
fi
# --- 2. Stop services + wipe data volumes ---
{{DOWN_SERVICES}}
# --- 3. Clean build artifacts ---
{{CLEAN_ARTIFACTS}}
# --- 4. Re-run bootstrap (which re-seeds) ---
if [[ -x ./bootstrap.sh ]]; then
./bootstrap.sh
else
echo "No bootstrap.sh found — reset cannot complete the loop." >&2
echo "Generate one with dx-harness, then re-run ./reset.sh" >&2
exit 1
fi
echo "Reset complete. Test user is re-seeded (see AGENTS.md)."
#!/usr/bin/env bash
# seed.sh — populate the dev database with a canonical test user + fixtures
# Generated by dx-harness. Idempotent: re-running is safe.
#
# Test credentials (mirrored in AGENTS.md):
# Email: {{TEST_EMAIL}}
# Password: {{TEST_PASSWORD}}
set -euo pipefail
# --- Safety: never seed prod ---
if [[ "${NODE_ENV:-}" == "production" ]] || [[ "${ENVIRONMENT:-}" == "production" ]]; then
echo "Refusing to seed against production." >&2
exit 1
fi
# --- Wait for DB to be reachable (max 30s, network URLs only) ---
# Only TCP-style URLs (postgres://, mysql://, mongodb://) with user@host:port
# are probed. SQLite file paths, unix sockets, and other schemes are skipped —
# the seed body itself will surface a real connection error if anything's off.
if [[ -n "${DATABASE_URL:-}" ]] && [[ "${DATABASE_URL}" =~ ^(postgres|postgresql|mysql|mongodb|redis)://[^/]*@[^/]+ ]]; then
db_host=$(echo "$DATABASE_URL" | sed -E 's|.*://[^@]*@([^:/]+).*|\1|')
db_port=$(echo "$DATABASE_URL" | sed -E 's|.*://[^@]*@[^:]+:([0-9]+).*|\1|')
if [[ -n "$db_host" && -n "$db_port" && "$db_port" =~ ^[0-9]+$ ]]; then
echo "Waiting for database at ${db_host}:${db_port}..."
for i in $(seq 1 30); do
if (echo > "/dev/tcp/${db_host}/${db_port}") 2>/dev/null; then
break
fi
sleep 1
done
fi
fi
# --- Seed body (idempotent) ---
{{SEED_BODY}}
echo "Seed complete. Login with {{TEST_EMAIL}} / {{TEST_PASSWORD}}"
{
"ttfc_target_seconds": 60,
"apply_mode": "scratch",
"audit_log_path": "${CLAUDE_PLUGIN_DATA}/dx-harness/audits.log",
"scratch_root": "${TMPDIR}",
"repo_root": "",
"preferred_task_runner": "auto",
"skip_checks": [],
"_setup_instructions": {
"ttfc_target_seconds": "Target seconds for time-to-first-commit (clone → first runnable change). Default 60 — the audit treats anything over this as a P1 finding.",
"apply_mode": "How scaffolded edits are applied: 'scratch' (write to scratch dir, user copies in — default and safest), 'stage' (write into working tree but don't commit), 'pr' (open a PR via gh CLI).",
"audit_log_path": "Where attrition history is persisted across runs. Defaults to plugin data dir so it survives skill upgrades.",
"scratch_root": "Parent dir for ephemeral worktrees and scaffold output. Defaults to $TMPDIR.",
"repo_root": "Absolute path to the repo being audited. Empty means use $PWD at invocation time.",
"preferred_task_runner": "When the repo has no existing runner: 'just', 'make', 'npm', or 'auto' (detect-or-skip). When the repo already has a runner, the skill always extends the existing one regardless of this value.",
"skip_checks": "List of audit check IDs to skip. See references/audit-checklist.md for IDs. Use sparingly — skipping checks defeats the point."
}
}
Gotchas
Failure points discovered while using this skill. Append new entries with the date — never rewrite.
No known gotchas yet. Append as you discover them.
Template
### {Short title — what surprised the agent or user}
{1-3 sentences: what went wrong, why, how to detect, what to do instead.}
Fix: {the actionable correction}
Added: {YYYY-MM-DD}Anti-Gotchas (things people THINK are gotchas but aren't)
These are recorded so the skill doesn't waste cycles "fixing" them.
None yet.
{
"version": "1.0.3",
"organization": "dot-skills",
"technology": "developer experience / dev harness",
"discipline": "composition",
"type": "automation",
"date": "May 2026",
"abstract": "Audits, scaffolds, and maintains a repository's developer-experience harness — the meta-tooling (bootstrap, reset, seed, test commands, AGENTS.md) that determines time-to-first-commit and ongoing dev velocity. Detects DX attrition (manual chores creeping back, slowing bootstrap, undocumented conventions), then applies canonical fixes and verifies them in a scratch worktree. Targets <60s time-to-first-commit and one-command flows for every routine action.",
"references": [
"https://vercel.com/blog/the-future-of-developer-experience",
"https://just.systems/",
"https://increment.com/development/what-is-developer-experience/",
"https://leerob.io/blog/developer-experience-examples"
]
}
Attrition Patterns
DX rot leaves fingerprints in git history. This document lists the patterns scripts/audit.sh scans for, and what each pattern usually means.
These patterns are heuristic — they surface candidates, not verdicts. The skill always presents the matched commits to the user before treating them as findings.
Pattern Catalog
"again" / "every time" / "always"
Grep: git log --all --oneline -i -E "(again|every time|always need|i always)"
What it usually means: a manual chore the dev has done multiple times and is annoyed about. The commit message itself is often the venting.
Mapped fix recipe: depends on what the commit touched. The audit reads the commit's files and matches:
- DB schema / migration →
scaffold-seedorscaffold-reset - env / dotfiles →
scaffold-bootstrap - README →
scaffold-agents-md
---
"manual" / "by hand"
Grep: git log --all --oneline -i -E "(manual(ly)?|by hand|hand-run)"
What it usually means: a step that should be in a script but isn't.
Mapped fix recipe: open the commit, see what was being done manually, route to the matching scaffold recipe.
---
"re-seed" / "reset db" / "wipe db"
Grep: git log --all --oneline -i -E "(re-seed|reseed|reset.{0,5}db|wipe.{0,5}db|drop.{0,5}db)"
What it usually means: dev had to interact with the database directly to recover state. Strong signal seed.sh / reset.sh are missing or broken.
Mapped fix recipe: scaffold-reset + scaffold-seed.
---
"register again" / "re-login" / "test user"
Grep: git log --all --oneline -i -E "(register again|re-?login|re-?register|test user|create.{0,10}account|signup)"
What it usually means: the user's specific example — every reset wipes the user account so they have to re-register. The fix is a seeded test user.
Mapped fix recipe: scaffold-seed (with explicit credentialed test user).
---
"wip: setup" / "fix: bootstrap" / "fix: getting started"
Grep: git log --all --oneline -i -E "(wip.{0,5}setup|fix.{0,5}bootstrap|getting.{0,5}started|onboard)"
What it usually means: repeated fixes to the setup process. Even if each fix is small, frequency means the harness is fragile.
Mapped fix recipe: scaffold-bootstrap (regenerate with idempotency + offline-friendly defaults).
---
"flaky" / "retry" / "intermittent"
Grep: git log --all --oneline -i -E "(flaky|retry|intermittent|sometimes fails)"
What it usually means: tests or harness steps that aren't deterministic. This isn't a DX harness fix exactly — it's a code-fix — but it's worth surfacing because flaky tests destroy trust in the dev loop.
Mapped fix recipe: manual (the skill doesn't fix flakes; it just notes them).
---
"TODO" / "HACK" near the dev loop
Grep over files (not commits): grep -rE "(TODO|HACK|FIXME).*(bootstrap|setup|seed|reset|test)" {scripts,Justfile,Makefile,package.json,docs/} 2>/dev/null
What it usually means: known harness debt left in comments. Counts toward attrition score.
Mapped fix recipe: depends on TODO content.
---
Time-Window Scope
The audit defaults to scanning the last 200 commits or 90 days, whichever is smaller. Wider scans are noisy; narrower miss patterns. The user can override via:
DX_HARNESS_HISTORY_DAYS=180 bash scripts/audit.sh ...
DX_HARNESS_HISTORY_COMMITS=500 bash scripts/audit.sh ...What's NOT in the catalog
Patterns we considered but excluded:
- "sorry, my bad" — too generic; doesn't isolate DX.
- "oops" — same.
- "final final v2" — file/versioning hygiene issue, not DX harness.
- "build failed" — that's a CI issue; the audit's
ci-statuscheck covers it separately.
The catalog stays focused on signals that map cleanly to harness-level fixes. Patterns that surface generic frustration but don't have a clean fix recipe go in gotchas.md instead, where the user can capture them with context.
Trend Analysis
scripts/track-attrition.sh reads the audit log and tracks attrition over time:
- Recurrence count: same pattern appearing in N consecutive audits → severity bump
- TTFC drift: TTFC trending up over recent audits → P1 even if still under target
- Score-of-scores: total normalized score over time → headline "is the dev loop getting better or worse"
The trend output is short — three lines max — because trend-fatigue is itself an attrition pattern.
Audit Checklist
The canonical list of DX checks scripts/audit.sh runs. Each check has an ID, a question it answers, a detection method, a severity, and a fix recipe.
The audit's headline metric is time-to-first-commit (TTFC): clone → setup → make a change → run tests. Target: 60 seconds. Anything over is P1.
Severity Levels
| Severity | Meaning | Example |
|---|---|---|
| P1 | Blocks the dev loop or causes daily friction | Missing bootstrap, TTFC > target, no test command |
| P2 | Slows the dev loop but workarounds exist | Manual seeding after reset, slow watch mode |
| P3 | Polish — costs minutes per week, not hours | Missing badges, no editor config |
| info | Observation, not a finding | "Uses pnpm" |
Checks
ttfc — Time-to-first-commit
Question: Can a new dev clone this repo and run the tests within ttfc_target_seconds?
Detection:
- Clean git worktree in scratch dir
- Run the detected bootstrap command (
./bootstrap.sh→just bootstrap→make bootstrap→npm install→ manual fallback) - Time wall-clock from clone-end to test-command-success
Severity: P1 if TTFC > target, P2 if > 2×target, info if under target.
Fix recipe: scaffold-bootstrap (and possibly scaffold-justfile to wire it up).
False negatives to avoid: TTFC measured on a warm machine is lower than cold. The verify script uses a fresh worktree but cannot reset the user's package cache — note this in the report.
---
bootstrap-exists — One-command bootstrap
Question: Is there a single command that brings a fresh checkout to a runnable state?
Detection (in priority order): 1. Executable bootstrap.sh / scripts/bootstrap.sh / bin/bootstrap 2. just bootstrap or just setup in Justfile 3. make bootstrap or make setup in Makefile 4. README contains a single fenced shell block titled "Setup" or "Bootstrap" with one command 5. package.json script named bootstrap, setup, or prepare
Severity: P1 if none found.
Fix recipe: scaffold-bootstrap + scaffold-justfile (if a task runner exists).
---
reset-exists — One-command reset
Question: Is there a single command that returns the dev environment to a known-clean state (drop DB, clear caches, kill background processes)?
Detection:
reset.sh/scripts/reset.shjust reset/make resetnpm run reset
Severity: P1 if repo has a database and no reset; P2 otherwise.
Fix recipe: scaffold-reset.
Gotcha: "reset" is a strong word. A correct reset should reset BOTH data and state — dropping the DB but leaving a stale Redis cache is a half-reset and counts as missing.
---
seed-exists — Idempotent seed
Question: Can the dev environment be filled with realistic-enough data with a single command, and is it safe to run twice?
Detection:
seed.sh/scripts/seed.shjust seed/make seednpm run seed/npm run db:seed- Framework-specific (
prisma db seed,rails db:seed)
Severity: P1 if repo has a database and the reset-then-seed flow requires manual login/registration; P2 if seed exists but isn't idempotent (running twice errors or duplicates).
Fix recipe: scaffold-seed. The recipe explicitly bakes in a test user with known credentials so "I have to register every time" goes away.
---
test-command — One-command tests
Question: Is there a single command that runs the test suite?
Detection:
just test/make test/npm test/pytest/cargo test/go test ./...package.jsonhas atestscript- README explicitly documents it
Severity: P1 if missing.
Fix recipe: manual — the skill cannot invent tests. Produces guidance: "Add at least one test and wire test script in package.json".
---
test-watch — Hot test feedback
Question: Can the dev run a test watcher that re-runs on file change?
Detection:
just test-watch,npm run test:watch,vitest,jest --watch,pytest-watch,cargo watch -x test
Severity: P2 if missing.
Fix recipe: scaffold-justfile adds a test-watch entry calling the appropriate watcher for the detected runner.
---
dev-server — Hot dev loop
Question: Is there a single command to start the dev server with HMR / autoreload?
Detection:
just dev,npm run dev,cargo run,go run,python manage.py runserver,uvicorn ... --reload
Severity: P2 if the repo is an app (has any server entrypoint) and no dev command exists.
Fix recipe: scaffold-justfile adds the canonical dev entry for the detected stack.
---
agents-md — Agent-discoverable conventions
Question: Is there an AGENTS.md (or CLAUDE.md) that lists the harness commands and key conventions?
Detection:
AGENTS.mdorCLAUDE.mdor.cursorrulesat repo root
Severity: P1 if missing (agents must guess the bootstrap, which wastes time every session).
Fix recipe: scaffold-agents-md. The generated AGENTS.md lists detected commands by name and points to canonical files (no hallucinated paths).
Quality bar: A passing AGENTS.md must include at least: bootstrap command, test command, reset command (if exists), and "where the conventions live". A stub one-liner is not passing.
---
ci-status — CI is wired
Question: Does the repo have a CI workflow that runs tests on every push?
Detection:
.github/workflows/*.ymlcontainingteststep.circleci/config.yml.gitlab-ci.ymlazure-pipelines.yml
Severity: P2 if missing; P3 if exists but doesn't run tests.
Fix recipe: manual — CI generation is out of scope (different providers, secrets, etc.). The audit surfaces this so the human knows.
---
repeated-manual-steps — Attrition signals in git history
Question: Does git history reveal repeated manual chores that should be scripted?
Detection: see attrition-patterns.md. Greps git log for phrases like "again", "every time", "manual", "wip: setup", "fix: re-seed", "reset db", "register again".
Severity: P2 per pattern, capped at P1 if more than 5 distinct patterns found.
Fix recipe: depends on the pattern. The audit emits one finding per detected pattern with the right recipe attached.
---
secrets-bootstrap — Bootstrap is offline-friendly
Question: Does bootstrap require secrets the new dev doesn't have?
Detection:
- Bootstrap script greps for env vars (
$AWS_*,$STRIPE_*, etc.) without a fallback .env.exampleexists but bootstrap doesn't copy it to.env
Severity: P2.
Fix recipe: scaffold-bootstrap is regenerated to copy .env.example → .env if missing, and to skip steps that require real secrets when running in --offline mode.
---
dependency-pin-drift — Lockfile present and committed
Question: Is the lockfile committed?
Detection:
- Has package.json but no pnpm-lock.yaml / package-lock.json / yarn.lock / bun.lockb in git
- Has Cargo.toml but no Cargo.lock committed (for binaries) — note: libraries shouldn't commit Cargo.lock
Severity: P2.
Fix recipe: manual — emit guidance to commit the lockfile.
---
Scoring
The prioritize step combines:
| Factor | Source | Range |
|---|---|---|
frequency_score | How often this friction hits a dev per week | 1-10 |
pain_score | How much friction each occurrence costs | 1-10 |
fix_cost_score | How expensive the fix is | 1-10 (higher = more expensive) |
Final score: (frequency × pain) / fix_cost, normalized 0-100.
Each check declares its own default scores in audit.sh. The user can override via config.json.
Fix Recipes
For each friction type the audit can find, this document specifies the canonical fix the skill produces.
A recipe is more than "run this script". It's: what's the principle behind the fix, what does the output look like, and what NOT to do.
Recipe: scaffold-bootstrap
Friction it fixes: missing or slow one-command setup.
Principle: one script, idempotent, offline-friendly, prints what it's doing.
Output: an executable bootstrap.sh at repo root that:
1. Checks toolchain prerequisites (node, cargo, python — whichever the fingerprint detected) and prints actionable install hints if missing 2. Copies .env.example to .env if .env doesn't exist 3. Installs dependencies via the detected package manager 4. Starts any required services (docker compose up -d for db, if present) 5. Runs migrations (if framework detected) 6. Runs seed (if seed.sh exists) 7. Prints a one-line success summary with next-step commands
Idempotency rule: running bootstrap twice in a row should be a no-op on the second run (apart from "starting" already-running services).
What NOT to do:
- Don't generate a bootstrap that runs tests at the end. Tests belong in
verify/ CI / dev loop, not bootstrap. Bootstrap should be < 60s; tests inflate this and obscure failures. - Don't generate bootstrap that requires interactive prompts. Bootstrap must be unattended.
- Don't generate bootstrap that creates a global state (modifying ~/.bashrc, installing global tools). Use project-local installs.
---
Recipe: scaffold-reset
Friction it fixes: "I have to do X manually every time I want to start clean."
Principle: reset returns the project to "fresh clone + bootstrap" state without re-cloning.
Output: an executable reset.sh at repo root that:
1. Stops any running services (docker compose down) 2. Wipes ephemeral data (DB volumes, caches, build artifacts) 3. Re-runs bootstrap
Critical property: after reset.sh, the dev should NOT need to re-register or re-login — seed.sh (run as part of bootstrap) provides credentialed test users.
What NOT to do:
- Don't reset the user's
.envif it has been modified — back it up to.env.bakinstead. - Don't
rm -rf node_modulesunless the audit specifically found stale-dep issues; it's slow and rarely needed. - Don't drop production databases. The script must fail loudly if
NODE_ENV=productionor equivalent.
---
Recipe: scaffold-seed
Friction it fixes: "every time I reset I have to register/login/click around to get to a usable state."
Principle: seed creates the data a dev needs to start working immediately — a logged-in test user, sample records, anything that would otherwise require manual clicking.
Output: an executable seed.sh that:
1. Detects the database (DATABASE_URL env, framework conventions) 2. Inserts a canonical test user (e.g., dev@local.test / password) — credentials match what's in AGENTS.md 3. Inserts minimal but realistic fixture data (workspace, org, sample records) 4. Is idempotent: running twice doesn't error, doesn't duplicate
Critical property: the credentials seeded by seed.sh must be documented in AGENTS.md and .env.example so the dev (and any agent) finds them without asking.
What NOT to do:
- Don't use
INSERTwithoutON CONFLICT DO NOTHING(or framework equivalent). Idempotency matters more than perfect data. - Don't seed production-like volumes (thousands of records). Seed should be fast.
- Don't seed real-looking data that could be confused with prod. Use obvious test names ("Acme Test Co", "user-1@local.test").
---
Recipe: scaffold-agents-md
Friction it fixes: agents (and humans) waste a session figuring out the harness because nothing tells them.
Principle: AGENTS.md is the table of contents to the dev loop. Short, factual, links to canonical files. Not a tutorial.
Output: an AGENTS.md at repo root with sections:
# Project Conventions
## Dev Loop
- Bootstrap: `./bootstrap.sh`
- Dev server: `just dev`
- Tests: `just test`
- Watch tests: `just test-watch`
- Reset: `./reset.sh`
## Test User
- Email: dev@local.test
- Password: password
- Seeded by: `./seed.sh` (run as part of bootstrap)
## Where things live
- Source: `src/`
- Tests: `tests/` (or wherever fingerprint detected)
- Database migrations: `migrations/` (if applicable)
## Conventions
- Lint: `just lint`
- Format: `just fmt`
- {detected framework-specific conventions}Critical property: every command mentioned in AGENTS.md must actually exist. The audit re-runs after AGENTS.md is generated to verify.
What NOT to do:
- Don't write prose explaining what each file does. Link, don't narrate.
- Don't repeat content from README. AGENTS.md is for agents/devs onboarding; README is for users.
- Don't hallucinate paths. The skill only writes paths it verified exist in the fingerprint.
---
Recipe: scaffold-justfile
Friction it fixes: commands exist as scripts but aren't discoverable. just --list should answer "what can I run here?".
Principle: extend the existing task runner (Justfile, Makefile, package.json scripts). Never migrate to a different runner without explicit user request.
Output:
If Justfile exists: append missing entries. If Makefile exists: append missing entries. If package.json is the only manifest: add npm scripts. If none exist: create a Justfile (or Makefile if preferred_task_runner: make in config) with bootstrap/dev/test/reset/seed entries.
Canonical entries:
bootstrap: ./bootstrap.sh
dev: {detected dev command}
test: {detected test command}
test-watch: {detected watch command}
reset: ./reset.sh
seed: ./seed.sh
lint: {detected lint}
fmt: {detected formatter}Critical property: the runner becomes the canonical discovery surface — just --list (or make help) prints all available commands.
What NOT to do:
- Don't overwrite existing entries. Append-only; show the diff if entries differ.
- Don't migrate from one runner to another. Adapt to what exists.
- Don't add entries that call non-existent files. Verify each target's script exists before adding.
---
Recipe: manual
For findings the skill cannot auto-fix (missing test suite, broken CI, secret rotation), the audit emits guidance but does not scaffold. The output is a markdown bullet under "Manual follow-ups" in the report.
These are tracked in the audit log too — repeated manual findings without action surface as a trend ("you've had 'no test suite' as a P1 finding for 6 audits").
Workflow
Complete step-by-step orchestration of a DX audit + scaffold pass. The agent should follow this exactly, branching only where the workflow explicitly says so.
Preconditions
Before starting, verify:
1. pwd is a git repo (git rev-parse --is-inside-work-tree returns true) — if not, ask the user which repo to audit 2. Working tree is clean — if not, ask the user to stash or proceed at their own risk (audit is read-only, but scaffolding may be confusing on top of uncommitted changes) 3. jq is on PATH — required for JSON pipeline between steps
Step 1 — Discover
Run scripts/discover.sh to produce repo-fingerprint.json. The fingerprint is the single source of truth for what the repo IS — every downstream step reads it. Do not re-detect things in later scripts.
The fingerprint includes:
| Field | Example | Used by |
|---|---|---|
languages | ["typescript", "python"] | audit, scaffold-bootstrap |
package_manager | "pnpm" | scaffold-bootstrap |
task_runner | "justfile" \ | "makefile" \ |
test_runner | "vitest" \ | "jest" \ |
ci_provider | "github-actions" \ | "none" |
has_database | true/false | scaffold-seed, scaffold-reset |
db_kind | "postgres" \ | null |
agents_md_present | true/false | audit, scaffold-agents-md |
existing_scripts | ["bootstrap.sh"] | audit, scaffold-bootstrap |
Failure mode: If the repo is genuinely unrecognizable (no manifests, no scripts), the fingerprint will be sparse and downstream steps will skip most checks. Tell the user the repo looks empty and ask if they're sure this is the right path.
Step 2 — Audit
Run scripts/audit.sh <fingerprint> to produce dx-audit.json. The audit runs each check in audit-checklist.md against the fingerprint and emits findings.
Each finding has shape:
{
"id": "ttfc",
"category": "time-to-first-commit",
"severity": "P1",
"title": "Time-to-first-commit exceeds 60s target",
"evidence": "bootstrap.sh took 87s in scratch worktree",
"fix_recipe": "scaffold-bootstrap",
"frequency_score": 10,
"pain_score": 8,
"fix_cost_score": 4
}Failure mode: If a check can't run (e.g., verify.sh can't clone), the audit emits a finding with severity: "unknown" and evidence: "check failed: <reason>" — do not silently skip.
Step 3 — Prioritize
Run scripts/prioritize.sh <audit> to rank findings. The score is frequency × pain ÷ fix_cost, normalized 0-100. The output sorts findings highest-impact first.
The agent should present the top 5-10 findings to the user in a table:
Rank Score Severity Finding Fix
1 95 P1 No one-command bootstrap scaffold-bootstrap
2 87 P1 Missing AGENTS.md (agents can't discover loop) scaffold-agents-md
3 72 P2 Manual db re-seeding after reset scaffold-seed
...Then ask the user which findings to address. Default suggestion: top 5 P1 items.
Step 4 — Scaffold
For each chosen finding, run the matching scaffold-*.sh script. The script:
1. Reads the fingerprint 2. Renders the relevant template (assets/templates/*.tmpl) with substitutions 3. Writes the rendered output to a scratch directory (${TMPDIR}/dx-harness-<timestamp>/) 4. Prints the scratch path
Multiple scaffolds for one finding are allowed (e.g., scaffold-justfile adds entries for whatever scaffold-bootstrap produced).
Never write directly to the working tree at this stage. The user reviews the scratch dir before anything moves.
Step 5 — Confirm
After scaffolding, show the user a unified diff:
diff -ruN <repo>/ <scratch>/dx-harness/Ask via AskUserQuestion which files to copy across. Per-file confirmation, not blanket. After confirmation, copy files in.
If the repo already has the file (e.g., an old bootstrap.sh), show the diff against the existing file and ask whether to merge, replace, or skip. Never silently overwrite.
Step 6 — Verify
Run scripts/verify.sh. This:
1. Creates a fresh git worktree at ${TMPDIR}/dx-harness-verify-<timestamp>/ 2. Runs ./bootstrap.sh (or just bootstrap / make bootstrap) in the worktree 3. Times the run 4. Runs ./reset.sh if present, asserts clean state 5. Runs the test command (just test / make test / npm test / pytest), asserts pass 6. Cleans up the worktree
Output is a PASS/FAIL report with timings. If TTFC exceeds ttfc_target_seconds from config, that's a FAIL — the audit's headline metric.
Failure mode: If bootstrap fails in the scratch worktree but works in the user's working dir, it's almost always because the user has uncommitted state (.env file, local DB) that the scratch worktree lacks. The script emits a specific hint when it detects this.
Step 7 — Track
Run scripts/track-attrition.sh <audit>. This:
1. Appends the audit to ${CLAUDE_PLUGIN_DATA}/dx-harness/audits.log (newline-delimited JSON) 2. Reads the previous audit for this repo 3. Diffs current findings vs previous — new findings = regressions, missing findings = wins 4. Prints a short trend summary
Trend data is what makes this skill more than a one-shot. Over time, repeated audits show whether DX is rotting or improving.
Error Recovery
| Step fails | Action |
|---|---|
| Discover crashes | Likely permission issue or non-git dir. Ask user, don't auto-retry. |
| Audit crashes on one check | Continue with other checks; emit severity: unknown for the broken one. |
| Scaffold can't render template | Stop. Print the missing fingerprint field. Ask user to fill in via config.json or rerun discover. |
| User declines all findings | Acknowledge, save the audit anyway so trend tracking still benefits next run. |
| Verify fails | Show the failure output. Do NOT auto-rollback — the user may still want to keep the partial scaffold. |
Idempotency
The whole workflow is safe to re-run. Discover always produces the same fingerprint for the same repo state. Audit always produces the same findings for the same fingerprint. Scaffold always produces the same scratch output for the same fingerprint.
track-attrition.sh appends every audit to the log (one NDJSON row per run). It does not deduplicate — repeat audits are intentional history, used for trend analysis. The trend summary it prints compares the current audit to the most recent prior audit for the same repo hash, so identical back-to-back audits produce a "no change" trend line, not an error.
When to Stop Early
Stop and ask the user if:
- Fingerprint shows multiple conflicting toolchains (e.g., both Cargo and package.json) — the scaffold needs guidance
- Audit produces zero findings — confirm the user actually wants to proceed (the answer is usually yes, just for the verify step)
- A finding's fix recipe is
manual(e.g., "your test suite is missing entirely") — the skill can't auto-fix; produce guidance instead
#!/usr/bin/env bash
# audit.sh — run DX checks against a repo fingerprint
# Part of: dx-harness
#
# Reads a fingerprint JSON, runs each check from references/audit-checklist.md,
# emits findings JSON to stdout. Findings have shape:
# { id, category, severity, title, evidence,
# fix_recipe, frequency_score, pain_score, fix_cost_score }
#
# Usage:
# bash audit.sh <fingerprint-json>
# bash audit.sh /tmp/fingerprint.json > /tmp/audit.json
#
# Env knobs:
# DX_HARNESS_HISTORY_DAYS (default 90)
# DX_HARNESS_HISTORY_COMMITS (default 200)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
[[ $# -eq 1 ]] || die "Usage: $0 <fingerprint-json>"
FP_FILE="$1"
[[ -f "$FP_FILE" ]] || die "Fingerprint file not found: $FP_FILE (run scripts/discover.sh first)"
ROOT=$(jq -r '.repo_root' "$FP_FILE")
[[ -d "$ROOT" ]] || die "Fingerprint repo_root is not a directory: $ROOT"
TTFC_TARGET=$(config_get "ttfc_target_seconds" 60)
HISTORY_DAYS="${DX_HARNESS_HISTORY_DAYS:-90}"
HISTORY_COMMITS="${DX_HARNESS_HISTORY_COMMITS:-200}"
# --- skip list from config ---
SKIP_RAW=$(jq -r '.skip_checks // [] | .[]?' "$DX_CONFIG_FILE" 2>/dev/null || true)
should_skip() {
local id="$1"
while read -r s; do
[[ -n "$s" && "$s" == "$id" ]] && return 0
done <<< "$SKIP_RAW"
return 1
}
# Findings are accumulated as JSON lines, then merged at the end.
FINDINGS_FILE=$(mktemp -t dx-audit-XXXXXX.jsonl)
trap 'rm -f "$FINDINGS_FILE"' EXIT
emit() {
# Usage: emit ID CATEGORY SEVERITY TITLE EVIDENCE FIX_RECIPE FREQ PAIN COST
jq -nc \
--arg id "$1" --arg category "$2" --arg severity "$3" \
--arg title "$4" --arg evidence "$5" --arg fix_recipe "$6" \
--argjson freq "$7" --argjson pain "$8" --argjson cost "$9" \
'{id:$id, category:$category, severity:$severity, title:$title,
evidence:$evidence, fix_recipe:$fix_recipe,
frequency_score:$freq, pain_score:$pain, fix_cost_score:$cost}' \
>> "$FINDINGS_FILE"
}
# ---------------- Checks ----------------
# bootstrap-exists
if ! should_skip bootstrap-exists; then
BOOTSTRAP=$(jq -r '.bootstrap_command' "$FP_FILE")
if [[ -z "$BOOTSTRAP" || "$BOOTSTRAP" == "null" ]]; then
emit bootstrap-exists harness P1 \
"No one-command bootstrap detected" \
"Did not find bootstrap.sh, just bootstrap, make bootstrap, or npm run bootstrap" \
scaffold-bootstrap 10 9 3
fi
fi
# reset-exists
if ! should_skip reset-exists; then
HAS_RESET=$(jq -r '.existing_scripts // [] | map(select(. == "reset.sh" or . == "scripts/reset.sh")) | length' "$FP_FILE")
HAS_DB=$(jq -r '.has_database' "$FP_FILE")
HAS_RUNNER_RESET="false"
if [[ -f "${ROOT}/Justfile" ]] && grep -qE '^reset:' "${ROOT}/Justfile" 2>/dev/null; then HAS_RUNNER_RESET="true"; fi
if [[ -f "${ROOT}/Makefile" ]] && grep -qE '^reset:' "${ROOT}/Makefile" 2>/dev/null; then HAS_RUNNER_RESET="true"; fi
if [[ "$HAS_RESET" == "0" && "$HAS_RUNNER_RESET" == "false" ]]; then
if [[ "$HAS_DB" == "true" ]]; then
emit reset-exists harness P1 \
"No reset command — repo has a database but no clean-slate workflow" \
"Database detected but no reset.sh / just reset / make reset" \
scaffold-reset 7 8 3
else
emit reset-exists harness P2 \
"No reset command — minor, repo has no database" \
"No reset.sh / just reset / make reset (no database detected)" \
scaffold-reset 4 4 3
fi
fi
fi
# seed-exists
if ! should_skip seed-exists; then
HAS_DB=$(jq -r '.has_database' "$FP_FILE")
if [[ "$HAS_DB" == "true" ]]; then
HAS_SEED=$(jq -r '.existing_scripts // [] | map(select(. == "seed.sh" or . == "scripts/seed.sh")) | length' "$FP_FILE")
HAS_RUNNER_SEED="false"
if [[ -f "${ROOT}/Justfile" ]] && grep -qE '^seed:' "${ROOT}/Justfile" 2>/dev/null; then HAS_RUNNER_SEED="true"; fi
if [[ -f "${ROOT}/Makefile" ]] && grep -qE '^seed:' "${ROOT}/Makefile" 2>/dev/null; then HAS_RUNNER_SEED="true"; fi
if [[ -f "${ROOT}/package.json" ]] && jq -e '.scripts.seed // .scripts["db:seed"] // empty' "${ROOT}/package.json" >/dev/null 2>&1; then
HAS_RUNNER_SEED="true"
fi
if [[ "$HAS_SEED" == "0" && "$HAS_RUNNER_SEED" == "false" ]]; then
emit seed-exists harness P1 \
"No idempotent seed — devs must register/configure manually after every reset" \
"Database detected, but no seed.sh / just seed / package.json seed script" \
scaffold-seed 9 9 3
fi
fi
fi
# test-command
if ! should_skip test-command; then
TEST_RUNNER=$(jq -r '.test_runner' "$FP_FILE")
if [[ -z "$TEST_RUNNER" || "$TEST_RUNNER" == "null" ]]; then
emit test-command harness P1 \
"No test command detected" \
"Neither package.json test script, Justfile test target, pytest config, cargo, nor go test detected" \
manual 10 10 8
fi
fi
# agents-md
if ! should_skip agents-md; then
HAS_AGENTS=$(jq -r '.agents_md_present' "$FP_FILE")
if [[ "$HAS_AGENTS" != "true" ]]; then
emit agents-md harness P1 \
"No AGENTS.md / CLAUDE.md — agents can't discover the dev loop" \
"Did not find AGENTS.md, CLAUDE.md, or .cursorrules" \
scaffold-agents-md 10 6 2
fi
fi
# ci-status
if ! should_skip ci-status; then
CI=$(jq -r '.ci_provider' "$FP_FILE")
if [[ "$CI" == "none" || -z "$CI" ]]; then
emit ci-status harness P2 \
"No CI workflow detected" \
"No .github/workflows, .circleci, .gitlab-ci, or azure-pipelines" \
manual 6 6 7
fi
fi
# dependency-pin-drift
if ! should_skip dependency-pin-drift; then
LOCK=$(jq -r '.lockfile_status' "$FP_FILE")
case "$LOCK" in
untracked)
emit dependency-pin-drift harness P2 \
"Lockfile exists but is not committed" \
"Lockfile present in working tree but git does not track it" \
manual 5 7 1
;;
no-lockfile)
HAS_PKG="false"
[[ -f "${ROOT}/package.json" ]] && HAS_PKG="true"
[[ -f "${ROOT}/Cargo.toml" ]] && HAS_PKG="true"
if [[ "$HAS_PKG" == "true" ]]; then
emit dependency-pin-drift harness P2 \
"No lockfile present" \
"Package manifest exists but no corresponding lockfile" \
manual 5 7 2
fi
;;
esac
fi
# repeated-manual-steps (history scan)
if ! should_skip repeated-manual-steps; then
SINCE_ARG=()
if [[ -n "$HISTORY_DAYS" ]]; then
SINCE_ARG=(--since="${HISTORY_DAYS} days ago")
fi
LOG_OUT=$( ( cd "$ROOT" && git log --all --oneline -i "${SINCE_ARG[@]}" -n "$HISTORY_COMMITS" 2>/dev/null ) || true)
patterns=(
"again|every time|always need:repeated-toil:scaffold-bootstrap:8:7:3"
"manual(ly)?|by hand:manual-step:scaffold-bootstrap:7:7:3"
"re-?seed|reset.{0,5}db|wipe.{0,5}db|drop.{0,5}db:db-recovery:scaffold-reset:9:8:3"
"register again|re-?login|re-?register|test user|create.{0,10}account|signup:re-register:scaffold-seed:10:9:2"
"wip.{0,5}setup|fix.{0,5}bootstrap|getting.{0,5}started:bootstrap-churn:scaffold-bootstrap:7:8:3"
"flaky|intermittent|sometimes fails:flaky-test:manual:6:8:8"
)
for entry in "${patterns[@]}"; do
IFS=':' read -r regex category recipe freq pain cost <<< "$entry"
COUNT=$(printf '%s\n' "$LOG_OUT" | grep -Ec -i "$regex" || true)
if [[ "$COUNT" -gt 0 ]]; then
sev=P2
[[ "$COUNT" -ge 5 ]] && sev=P1
emit "attrition-${category}" history "$sev" \
"Git history shows ${COUNT} commits matching pattern: ${category}" \
"Pattern '${regex}' matched ${COUNT}x in last ${HISTORY_COMMITS} commits / ${HISTORY_DAYS}d" \
"$recipe" "$freq" "$pain" "$cost"
fi
done
fi
# ---------------- Emit findings array ----------------
jq -n \
--slurpfile findings "$FINDINGS_FILE" \
--arg generated_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--argjson ttfc_target "$TTFC_TARGET" \
--slurpfile fingerprint "$FP_FILE" \
'{
schema_version: 1,
generated_at: $generated_at,
ttfc_target_seconds: $ttfc_target,
fingerprint_hash: ($fingerprint[0].repo_hash // null),
findings: ($findings | flatten),
total_findings: ($findings | flatten | length)
}'
#!/usr/bin/env bash
# discover.sh — fingerprint the repo
# Part of: dx-harness
#
# Emits a JSON object describing the repo's languages, toolchain, task runner,
# CI, database, existing harness, and AGENTS.md presence. Every downstream
# script reads this fingerprint — detection should not happen anywhere else.
#
# Usage:
# bash discover.sh # writes JSON to stdout
# bash discover.sh > /tmp/fingerprint.json
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/detectors.sh"
require_jq
ROOT=$(repo_root)
[[ -d "$ROOT" ]] || die "repo_root resolves to non-directory: $ROOT"
LANGS=$(detect_languages "$ROOT")
PKG_MGR=$(detect_package_manager "$ROOT")
TASK_RUNNER=$(detect_task_runner "$ROOT")
TEST_RUNNER=$(detect_test_runner "$ROOT")
CI=$(detect_ci_provider "$ROOT")
DB=$(detect_database_kind "$ROOT")
BOOTSTRAP=$(detect_bootstrap "$ROOT")
AGENTS=$(detect_agents_md "$ROOT")
LOCK=$(detect_lockfile_committed "$ROOT")
# Existing harness scripts the repo already has
EXISTING_SCRIPTS=()
for f in bootstrap.sh reset.sh seed.sh setup.sh; do
[[ -e "${ROOT}/${f}" ]] && EXISTING_SCRIPTS+=("$f")
[[ -e "${ROOT}/scripts/${f}" ]] && EXISTING_SCRIPTS+=("scripts/${f}")
done
# Recent commit count for history-window sizing
COMMIT_COUNT=$( ( cd "$ROOT" && git rev-list --count HEAD 2>/dev/null ) || printf '0')
# Build JSON via jq for safety
jq -n \
--arg repo_root "$ROOT" \
--arg repo_hash "$(repo_hash)" \
--argjson languages "${LANGS:-[]}" \
--arg package_manager "$PKG_MGR" \
--arg task_runner "$TASK_RUNNER" \
--arg test_runner "$TEST_RUNNER" \
--arg ci_provider "$CI" \
--arg db_kind "$DB" \
--arg bootstrap_command "$BOOTSTRAP" \
--arg agents_md "$AGENTS" \
--arg lockfile_status "$LOCK" \
--argjson existing_scripts "$(printf '%s\n' "${EXISTING_SCRIPTS[@]:-}" | jq -R . | jq -s 'map(select(length>0))')" \
--argjson commit_count "$COMMIT_COUNT" \
--arg generated_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{
schema_version: 1,
generated_at: $generated_at,
repo_root: $repo_root,
repo_hash: $repo_hash,
languages: $languages,
package_manager: $package_manager,
task_runner: $task_runner,
test_runner: $test_runner,
ci_provider: $ci_provider,
has_database: ($db_kind != ""),
db_kind: $db_kind,
bootstrap_command: $bootstrap_command,
agents_md_present: ($agents_md != ""),
agents_md_path: $agents_md,
lockfile_status: $lockfile_status,
existing_scripts: $existing_scripts,
commit_count: $commit_count
}'
#!/usr/bin/env bash
# common.sh — shared helpers for dx-harness scripts
# Source from other scripts: source "$(dirname "$0")/lib/common.sh"
# --- Strict mode ---
set -euo pipefail
# --- Resolve plugin/skill root ---
DX_SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)"
DX_CONFIG_FILE="${DX_SKILL_DIR}/config.json"
# --- Plugin data dir for persistent state ---
DX_DATA_DIR="${CLAUDE_PLUGIN_DATA:-${HOME}/.local/share/dx-harness}/dx-harness"
mkdir -p "$DX_DATA_DIR"
# --- Logging ---
log() { printf '[dx-harness] %s\n' "$*" >&2; }
warn() { printf '[dx-harness] WARN: %s\n' "$*" >&2; }
die() { printf '[dx-harness] ERROR: %s\n' "$*" >&2; exit 1; }
# --- jq required ---
require_jq() {
command -v jq >/dev/null 2>&1 || die "jq is required. Install: brew install jq (macOS) | apt install jq (Debian)"
}
# --- Safely substitute ${VAR} references in a string ---
# Only an allowlist of env vars is expanded; everything else is left literal.
# Refuses to expand $(...) or `...` — those are passed through unchanged and
# will fail loudly downstream rather than execute.
# Usage: safe_expand "${TMPDIR}/foo"
safe_expand() {
local s="$1"
# If the value contains a command substitution, bail without expanding.
if [[ "$s" == *'$('* || "$s" == *'`'* ]]; then
printf '%s' "$s"
return
fi
# Expand only the allowlisted variables, in order.
local var
for var in TMPDIR HOME CLAUDE_PLUGIN_DATA CLAUDE_PLUGIN_ROOT PWD USER; do
local val="${!var:-}"
# Replace both ${VAR} and $VAR forms
s="${s//\$\{$var\}/$val}"
s="${s//\$$var/$val}"
done
printf '%s' "$s"
}
# --- Read config value with a default ---
# Usage: config_get "ttfc_target_seconds" 60
config_get() {
local key="$1" default="${2:-}"
if [[ -f "$DX_CONFIG_FILE" ]]; then
local val
val=$(jq -r --arg k "$key" '.[$k] // empty' "$DX_CONFIG_FILE" 2>/dev/null || true)
if [[ -n "$val" && "$val" != "null" ]]; then
safe_expand "$val"
return
fi
fi
printf '%s' "$default"
}
# --- Resolve repo root (config override → $PWD → git toplevel) ---
repo_root() {
local override
override=$(config_get "repo_root" "")
if [[ -n "$override" ]]; then
printf '%s' "$override"
return
fi
git -C "${PWD}" rev-parse --show-toplevel 2>/dev/null || printf '%s' "$PWD"
}
# --- Scratch directory for ephemeral output ---
# Each call yields a fresh dir under $TMPDIR.
scratch_dir() {
local prefix="${1:-dx-harness}"
local root
root=$(config_get "scratch_root" "${TMPDIR:-/tmp}")
local dir="${root%/}/${prefix}-$(date +%s)-$$"
mkdir -p "$dir"
printf '%s' "$dir"
}
# --- Make a git worktree at a scratch path on a detached HEAD ---
# Usage: make_worktree <path>
# Caller is responsible for cleanup with cleanup_worktree.
make_worktree() {
local path="$1"
local root
root=$(repo_root)
( cd "$root" && git worktree add --detach "$path" >/dev/null )
printf '%s' "$path"
}
cleanup_worktree() {
local path="$1"
local root
root=$(repo_root)
( cd "$root" && git worktree remove --force "$path" >/dev/null 2>&1 ) || true
rm -rf "$path" 2>/dev/null || true
}
# --- Trap helper: register a cleanup for a worktree on EXIT ---
register_worktree_cleanup() {
local path="$1"
trap "cleanup_worktree '$path'" EXIT INT TERM
}
# --- Time a command in milliseconds ---
# Usage: ms=$(time_ms my_command arg1 arg2)
# stdout/stderr of the inner command go to FD 3 — caller can redirect.
time_ms() {
local start_s start_ns
if date +%s%3N >/dev/null 2>&1; then
start_s=$(date +%s%3N)
"$@"
local end_s
end_s=$(date +%s%3N)
printf '%s' "$((end_s - start_s))"
else
# macOS date has no %N; use python fallback
start_s=$(python3 -c 'import time;print(int(time.time()*1000))')
"$@"
local end_s
end_s=$(python3 -c 'import time;print(int(time.time()*1000))')
printf '%s' "$((end_s - start_s))"
fi
}
# --- Template rendering: substitute {{var}} placeholders ---
# Usage: render_template <template-path> <output-path> KEY1=val1 KEY2=val2 ...
render_template() {
local tmpl="$1" out="$2"
shift 2
local content
content=$(cat "$tmpl")
while [[ $# -gt 0 ]]; do
local pair="$1"
local k="${pair%%=*}"
local v="${pair#*=}"
# Use a python helper for safe substitution (sed chokes on slashes/newlines in $v)
content=$(KEY="$k" VAL="$v" python3 -c '
import os, sys
data = sys.stdin.read()
k = os.environ["KEY"]
v = os.environ["VAL"]
print(data.replace("{{" + k + "}}", v), end="")
' <<< "$content")
shift
done
mkdir -p "$(dirname "$out")"
printf '%s' "$content" > "$out"
}
# --- Hash the repo for attrition-log keying ---
repo_hash() {
local root
root=$(repo_root)
printf '%s' "$root" | shasum -a 256 | cut -c1-12
}
#!/usr/bin/env bash
# detectors.sh — primitive checks for what a repo IS
# Source from other scripts: source "$(dirname "$0")/lib/detectors.sh"
#
# Each detector echoes a single value (or empty) and returns 0. They never fail —
# absence is communicated by empty stdout, so detectors are safe to chain in pipelines.
set -euo pipefail
# ---- Languages ----
# Emits a JSON array string like ["typescript","python"] (empty array if none).
detect_languages() {
local root="${1:-$PWD}"
local langs=()
[[ -f "${root}/package.json" ]] && langs+=("javascript")
( [[ -f "${root}/tsconfig.json" ]] || compgen -G "${root}/**/*.ts" >/dev/null 2>&1 ) && langs+=("typescript")
[[ -f "${root}/Cargo.toml" ]] && langs+=("rust")
[[ -f "${root}/go.mod" ]] && langs+=("go")
( [[ -f "${root}/pyproject.toml" ]] || [[ -f "${root}/requirements.txt" ]] || [[ -f "${root}/setup.py" ]] ) && langs+=("python")
[[ -f "${root}/Gemfile" ]] && langs+=("ruby")
[[ -f "${root}/pom.xml" ]] || [[ -f "${root}/build.gradle" ]] || [[ -f "${root}/build.gradle.kts" ]] && langs+=("java")
if [[ ${#langs[@]} -eq 0 ]]; then
printf '[]'
else
printf '['
local first=1
for l in "${langs[@]}"; do
[[ $first -eq 1 ]] || printf ','
printf '"%s"' "$l"
first=0
done
printf ']'
fi
}
# ---- Package manager (JS ecosystem) ----
detect_package_manager() {
local root="${1:-$PWD}"
[[ -f "${root}/pnpm-lock.yaml" ]] && { printf 'pnpm'; return; }
[[ -f "${root}/yarn.lock" ]] && { printf 'yarn'; return; }
[[ -f "${root}/bun.lockb" ]] && { printf 'bun'; return; }
[[ -f "${root}/package-lock.json" ]] && { printf 'npm'; return; }
[[ -f "${root}/package.json" ]] && { printf 'npm'; return; }
printf ''
}
# ---- Task runner ----
detect_task_runner() {
local root="${1:-$PWD}"
[[ -f "${root}/Justfile" ]] || [[ -f "${root}/justfile" ]] && { printf 'just'; return; }
[[ -f "${root}/Makefile" ]] && { printf 'make'; return; }
[[ -f "${root}/package.json" ]] && { printf 'npm-scripts'; return; }
printf 'none'
}
# ---- Test runner ----
detect_test_runner() {
local root="${1:-$PWD}"
if [[ -f "${root}/package.json" ]]; then
if grep -q '"vitest"' "${root}/package.json" 2>/dev/null; then printf 'vitest'; return; fi
if grep -q '"jest"' "${root}/package.json" 2>/dev/null; then printf 'jest'; return; fi
if grep -q '"mocha"' "${root}/package.json" 2>/dev/null; then printf 'mocha'; return; fi
if grep -q '"playwright"' "${root}/package.json" 2>/dev/null; then printf 'playwright'; return; fi
if jq -e '.scripts.test // empty' "${root}/package.json" >/dev/null 2>&1; then printf 'npm-test'; return; fi
fi
[[ -f "${root}/pytest.ini" ]] || grep -q '\[tool.pytest' "${root}/pyproject.toml" 2>/dev/null && { printf 'pytest'; return; }
[[ -f "${root}/Cargo.toml" ]] && { printf 'cargo-test'; return; }
[[ -f "${root}/go.mod" ]] && { printf 'go-test'; return; }
printf ''
}
# ---- CI provider ----
detect_ci_provider() {
local root="${1:-$PWD}"
if compgen -G "${root}/.github/workflows/*.y*ml" >/dev/null; then printf 'github-actions'; return; fi
[[ -f "${root}/.circleci/config.yml" ]] && { printf 'circleci'; return; }
[[ -f "${root}/.gitlab-ci.yml" ]] && { printf 'gitlab-ci'; return; }
[[ -f "${root}/azure-pipelines.yml" ]] && { printf 'azure-pipelines'; return; }
printf 'none'
}
# ---- Database presence + kind ----
detect_database_kind() {
local root="${1:-$PWD}"
# docker-compose with postgres service
if [[ -f "${root}/docker-compose.yml" ]] || [[ -f "${root}/compose.yaml" ]] || [[ -f "${root}/compose.yml" ]]; then
local f
for f in "${root}/docker-compose.yml" "${root}/compose.yaml" "${root}/compose.yml"; do
[[ -f "$f" ]] || continue
grep -qE 'postgres|postgis' "$f" && { printf 'postgres'; return; }
grep -qE 'mysql|mariadb' "$f" && { printf 'mysql'; return; }
grep -qE 'mongo' "$f" && { printf 'mongodb'; return; }
grep -qE 'redis' "$f" && { printf 'redis'; return; }
done
fi
# Env file mentions DATABASE_URL
for f in "${root}/.env.example" "${root}/.env.sample" "${root}/.env"; do
[[ -f "$f" ]] || continue
grep -q 'DATABASE_URL' "$f" && { printf 'unknown-sql'; return; }
done
# Prisma / Drizzle / Sequelize hints
[[ -d "${root}/prisma" ]] && { printf 'prisma'; return; }
printf ''
}
# ---- Bootstrap command detection ----
detect_bootstrap() {
local root="${1:-$PWD}"
for f in bootstrap.sh scripts/bootstrap.sh bin/bootstrap setup.sh scripts/setup.sh; do
[[ -x "${root}/${f}" ]] && { printf '%s' "$f"; return; }
done
if [[ -f "${root}/Justfile" ]] || [[ -f "${root}/justfile" ]]; then
if grep -qE '^(bootstrap|setup):' "${root}/Justfile" 2>/dev/null || grep -qE '^(bootstrap|setup):' "${root}/justfile" 2>/dev/null; then
printf 'just bootstrap'; return
fi
fi
if [[ -f "${root}/Makefile" ]] && grep -qE '^(bootstrap|setup):' "${root}/Makefile"; then
printf 'make bootstrap'; return
fi
if [[ -f "${root}/package.json" ]]; then
if jq -e '.scripts.bootstrap // .scripts.setup // empty' "${root}/package.json" >/dev/null 2>&1; then
printf 'npm run bootstrap'; return
fi
fi
printf ''
}
# ---- AGENTS.md / CLAUDE.md ----
detect_agents_md() {
local root="${1:-$PWD}"
for f in AGENTS.md CLAUDE.md .cursorrules .agents.md; do
[[ -f "${root}/${f}" ]] && { printf '%s' "$f"; return; }
done
printf ''
}
# ---- Lockfile presence (committed?) ----
detect_lockfile_committed() {
local root="${1:-$PWD}"
local locks=(package-lock.json pnpm-lock.yaml yarn.lock bun.lockb Cargo.lock poetry.lock uv.lock Gemfile.lock)
for l in "${locks[@]}"; do
if [[ -f "${root}/${l}" ]]; then
if ( cd "$root" && git ls-files --error-unmatch "$l" >/dev/null 2>&1 ); then
printf 'true'; return
else
printf 'untracked'; return
fi
fi
done
printf 'no-lockfile'
}
#!/usr/bin/env bash
# prioritize.sh — score and rank audit findings
# Part of: dx-harness
#
# Reads dx-audit.json, computes score = (frequency × pain) / max(fix_cost,1),
# normalizes 0-100, sorts descending, writes ranked JSON to stdout.
#
# Usage:
# bash prioritize.sh <audit-json>
# bash prioritize.sh /tmp/audit.json > /tmp/ranked.json
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
[[ $# -eq 1 ]] || die "Usage: $0 <audit-json>"
[[ -f "$1" ]] || die "Audit file not found: $1 (run scripts/audit.sh first)"
jq '
.findings as $f
| ($f | map((.frequency_score * .pain_score) / ([.fix_cost_score,1] | max))) as $raw
| ($raw | max // 1) as $maxRaw
| .findings = ($f
| to_entries
| map(.value + {score: (($raw[.key] / $maxRaw) * 100 | round)})
| sort_by(-.score)
)
| .ranked_at = (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
' "$1"
#!/usr/bin/env bash
# scaffold-agents-md.sh — render AGENTS.md into a scratch dir
# Part of: dx-harness
#
# Generates an AGENTS.md from fingerprint facts. Every path/command in the
# output is one that was detected — no hallucinated references.
#
# Usage:
# bash scaffold-agents-md.sh <fingerprint-json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
[[ $# -eq 1 ]] || die "Usage: $0 <fingerprint-json>"
FP="$1"
[[ -f "$FP" ]] || die "Fingerprint file not found: $FP"
TASK_RUNNER=$(jq -r '.task_runner' "$FP")
BOOTSTRAP=$(jq -r '.bootstrap_command // ""' "$FP")
TEST_RUNNER=$(jq -r '.test_runner // ""' "$FP")
HAS_DB=$(jq -r '.has_database' "$FP")
LANGS=$(jq -r '.languages | join(", ")' "$FP")
# Pick the canonical command for each action, given the runner
case "$TASK_RUNNER" in
just) BOOTSTRAP_CMD="${BOOTSTRAP:-just bootstrap}"; DEV_CMD="just dev"; TEST_CMD="just test"; WATCH_CMD="just test-watch"; RESET_CMD="just reset"; SEED_CMD="just seed" ;;
make) BOOTSTRAP_CMD="${BOOTSTRAP:-make bootstrap}"; DEV_CMD="make dev"; TEST_CMD="make test"; WATCH_CMD="make test-watch"; RESET_CMD="make reset"; SEED_CMD="make seed" ;;
npm-scripts) BOOTSTRAP_CMD="${BOOTSTRAP:-./bootstrap.sh}"; DEV_CMD="npm run dev"; TEST_CMD="npm test"; WATCH_CMD="npm run test:watch"; RESET_CMD="./reset.sh"; SEED_CMD="./seed.sh" ;;
*) BOOTSTRAP_CMD="${BOOTSTRAP:-./bootstrap.sh}"; DEV_CMD="(start your server)"; TEST_CMD="${TEST_RUNNER:-(no test command detected)}"; WATCH_CMD="(no watcher detected)"; RESET_CMD="./reset.sh"; SEED_CMD="./seed.sh" ;;
esac
DB_SECTION=""
if [[ "$HAS_DB" == "true" ]]; then
DB_SECTION=$(cat <<'EOM'
## Test User (seeded by `seed.sh`)
- Email: `dev@local.test`
- Password: `password`
This user is created by `seed.sh` as part of bootstrap. After running `reset`, the user is re-seeded automatically — do not register manually.
EOM
)
fi
OUT_DIR=$(scratch_dir "agents-md")
TMPL="${DX_SKILL_DIR}/assets/templates/AGENTS.md.tmpl"
[[ -f "$TMPL" ]] || die "Template missing: $TMPL"
render_template "$TMPL" "${OUT_DIR}/AGENTS.md" \
"BOOTSTRAP_CMD=${BOOTSTRAP_CMD}" \
"DEV_CMD=${DEV_CMD}" \
"TEST_CMD=${TEST_CMD}" \
"WATCH_CMD=${WATCH_CMD}" \
"RESET_CMD=${RESET_CMD}" \
"SEED_CMD=${SEED_CMD}" \
"LANGUAGES=${LANGS:-(unknown)}" \
"TASK_RUNNER=${TASK_RUNNER}" \
"DB_SECTION=${DB_SECTION}"
log "Wrote AGENTS.md to ${OUT_DIR}/AGENTS.md"
printf '%s\n' "$OUT_DIR"
#!/usr/bin/env bash
# scaffold-bootstrap.sh — render bootstrap.sh into a scratch dir
# Part of: dx-harness
#
# Reads fingerprint, picks the right template variant, substitutes vars,
# writes to a fresh scratch dir, prints the scratch path on stdout.
# Never writes into the working tree directly.
#
# Usage:
# bash scaffold-bootstrap.sh <fingerprint-json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
[[ $# -eq 1 ]] || die "Usage: $0 <fingerprint-json>"
FP="$1"
[[ -f "$FP" ]] || die "Fingerprint file not found: $FP"
PKG_MGR=$(jq -r '.package_manager // ""' "$FP")
HAS_DB=$(jq -r '.has_database' "$FP")
DB_KIND=$(jq -r '.db_kind // ""' "$FP")
LANGS=$(jq -r '.languages | join(",")' "$FP")
# Choose install command for the detected package manager
case "$PKG_MGR" in
pnpm) INSTALL_CMD="pnpm install --frozen-lockfile" ;;
yarn) INSTALL_CMD="yarn install --frozen-lockfile" ;;
bun) INSTALL_CMD="bun install --frozen-lockfile" ;;
npm) INSTALL_CMD="npm ci || npm install" ;;
"") INSTALL_CMD="" ;;
*) INSTALL_CMD="$PKG_MGR install" ;;
esac
# Python install
PY_INSTALL=""
if [[ ",${LANGS}," == *",python,"* ]]; then
if [[ -f "$(jq -r '.repo_root' "$FP")/uv.lock" ]]; then
PY_INSTALL="uv sync"
elif [[ -f "$(jq -r '.repo_root' "$FP")/poetry.lock" ]]; then
PY_INSTALL="poetry install"
else
PY_INSTALL="python3 -m pip install -e ."
fi
fi
# Rust install
RUST_BUILD=""
[[ ",${LANGS}," == *",rust,"* ]] && RUST_BUILD="cargo build --quiet"
# Go install
GO_INSTALL=""
[[ ",${LANGS}," == *",go,"* ]] && GO_INSTALL="go mod download"
# Database service start
DB_START=""
if [[ "$HAS_DB" == "true" ]]; then
DB_START="if [[ -f docker-compose.yml || -f compose.yaml || -f compose.yml ]]; then docker compose up -d; fi"
fi
OUT_DIR=$(scratch_dir "bootstrap")
TMPL="${DX_SKILL_DIR}/assets/templates/bootstrap.sh.tmpl"
[[ -f "$TMPL" ]] || die "Template missing: $TMPL"
render_template "$TMPL" "${OUT_DIR}/bootstrap.sh" \
"INSTALL_CMD=${INSTALL_CMD}" \
"PY_INSTALL=${PY_INSTALL}" \
"RUST_BUILD=${RUST_BUILD}" \
"GO_INSTALL=${GO_INSTALL}" \
"DB_START=${DB_START}"
chmod +x "${OUT_DIR}/bootstrap.sh"
log "Wrote bootstrap to ${OUT_DIR}/bootstrap.sh"
printf '%s\n' "$OUT_DIR"
#!/usr/bin/env bash
# scaffold-justfile.sh — emit task-runner entries into a scratch dir
# Part of: dx-harness
#
# Adapts to whatever runner exists: Justfile, Makefile, or package.json scripts.
# Never migrates between runners — extends the one that's already there.
# If none exists, creates a Justfile by default (or whatever preferred_task_runner
# in config.json says).
#
# Output for each variant: a fragment file the user copies into / appends to the
# existing runner. Never overwrites.
#
# Usage:
# bash scaffold-justfile.sh <fingerprint-json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
[[ $# -eq 1 ]] || die "Usage: $0 <fingerprint-json>"
FP="$1"
[[ -f "$FP" ]] || die "Fingerprint file not found: $FP"
TASK_RUNNER=$(jq -r '.task_runner' "$FP")
TEST_RUNNER=$(jq -r '.test_runner // ""' "$FP")
PKG_MGR=$(jq -r '.package_manager // ""' "$FP")
PREFERRED=$(config_get "preferred_task_runner" "auto")
# If no runner exists, pick one
if [[ "$TASK_RUNNER" == "none" ]]; then
case "$PREFERRED" in
just) TASK_RUNNER="just" ;;
make) TASK_RUNNER="make" ;;
npm) TASK_RUNNER="npm-scripts" ;;
auto|*)
# Auto: if package.json exists, use npm-scripts; else just
if [[ -f "$(jq -r '.repo_root' "$FP")/package.json" ]]; then
TASK_RUNNER="npm-scripts"
else
TASK_RUNNER="just"
fi
;;
esac
fi
# Pick canonical commands per language for dev/test/watch
DEV_CMD="echo 'TODO: set your dev command'"
TEST_CMD="echo 'TODO: set your test command'"
WATCH_CMD="echo 'TODO: set your test watch command'"
case "$TEST_RUNNER" in
vitest) TEST_CMD="vitest run"; WATCH_CMD="vitest" ;;
jest) TEST_CMD="jest"; WATCH_CMD="jest --watch" ;;
mocha) TEST_CMD="mocha"; WATCH_CMD="mocha --watch" ;;
playwright) TEST_CMD="playwright test"; WATCH_CMD="playwright test --ui" ;;
pytest) TEST_CMD="pytest"; WATCH_CMD="pytest-watch || pytest" ;;
cargo-test) TEST_CMD="cargo test"; WATCH_CMD="cargo watch -x test" ;;
go-test) TEST_CMD="go test ./..."; WATCH_CMD="go test -run . ./..." ;;
npm-test) TEST_CMD="${PKG_MGR:-npm} test"; WATCH_CMD="${PKG_MGR:-npm} run test:watch" ;;
esac
# Dev command guesses
if [[ -n "$PKG_MGR" ]]; then
DEV_CMD="${PKG_MGR} run dev"
fi
OUT_DIR=$(scratch_dir "taskrunner")
case "$TASK_RUNNER" in
just)
TMPL="${DX_SKILL_DIR}/assets/templates/Justfile.tmpl"
render_template "$TMPL" "${OUT_DIR}/Justfile" \
"DEV_CMD=${DEV_CMD}" "TEST_CMD=${TEST_CMD}" "WATCH_CMD=${WATCH_CMD}"
log "Wrote Justfile fragment to ${OUT_DIR}/Justfile (append to repo Justfile, do not overwrite)"
;;
make)
TMPL="${DX_SKILL_DIR}/assets/templates/Makefile.tmpl"
render_template "$TMPL" "${OUT_DIR}/Makefile" \
"DEV_CMD=${DEV_CMD}" "TEST_CMD=${TEST_CMD}" "WATCH_CMD=${WATCH_CMD}"
log "Wrote Makefile fragment to ${OUT_DIR}/Makefile (append to repo Makefile, do not overwrite)"
;;
npm-scripts)
# Emit a JSON patch the user can merge into package.json scripts
jq -n \
--arg dev "$DEV_CMD" --arg test "$TEST_CMD" --arg watch "$WATCH_CMD" \
'{scripts: {bootstrap: "./bootstrap.sh", dev: $dev, test: $test, "test:watch": $watch, reset: "./reset.sh", seed: "./seed.sh"}}' \
> "${OUT_DIR}/package.json.patch"
log "Wrote package.json scripts patch to ${OUT_DIR}/package.json.patch (merge into existing scripts)"
;;
esac
printf '%s\n' "$OUT_DIR"
#!/usr/bin/env bash
# scaffold-reset.sh — render reset.sh into a scratch dir
# Part of: dx-harness
#
# Generates a reset script that:
# - Stops services (docker compose down -v if present)
# - Wipes ephemeral data (node_modules only if --hard, build artifacts always)
# - Re-runs bootstrap (which re-runs seed)
#
# Usage:
# bash scaffold-reset.sh <fingerprint-json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
[[ $# -eq 1 ]] || die "Usage: $0 <fingerprint-json>"
FP="$1"
[[ -f "$FP" ]] || die "Fingerprint file not found: $FP"
HAS_DB=$(jq -r '.has_database' "$FP")
LANGS=$(jq -r '.languages | join(",")' "$FP")
DOWN_SERVICES=""
if [[ "$HAS_DB" == "true" ]]; then
DOWN_SERVICES="if [[ -f docker-compose.yml || -f compose.yaml || -f compose.yml ]]; then docker compose down -v; fi"
fi
CLEAN_ARTIFACTS=""
[[ ",${LANGS}," == *",javascript,"* || ",${LANGS}," == *",typescript,"* ]] && \
CLEAN_ARTIFACTS="${CLEAN_ARTIFACTS}rm -rf .next dist build .turbo 2>/dev/null || true"$'\n'
[[ ",${LANGS}," == *",rust,"* ]] && \
CLEAN_ARTIFACTS="${CLEAN_ARTIFACTS}rm -rf target 2>/dev/null || true"$'\n'
[[ ",${LANGS}," == *",python,"* ]] && \
CLEAN_ARTIFACTS="${CLEAN_ARTIFACTS}find . -type d -name __pycache__ -prune -exec rm -rf {} + 2>/dev/null || true"$'\n'
OUT_DIR=$(scratch_dir "reset")
TMPL="${DX_SKILL_DIR}/assets/templates/reset.sh.tmpl"
[[ -f "$TMPL" ]] || die "Template missing: $TMPL"
render_template "$TMPL" "${OUT_DIR}/reset.sh" \
"DOWN_SERVICES=${DOWN_SERVICES}" \
"CLEAN_ARTIFACTS=${CLEAN_ARTIFACTS}"
chmod +x "${OUT_DIR}/reset.sh"
log "Wrote reset to ${OUT_DIR}/reset.sh"
printf '%s\n' "$OUT_DIR"
#!/usr/bin/env bash
# scaffold-seed.sh — render seed.sh into a scratch dir
# Part of: dx-harness
#
# Generates a seed script that creates a canonical test user
# (dev@local.test / password) and minimal fixtures. Idempotent: re-running
# does not error or duplicate. The credentials are written to AGENTS.md too
# so devs and agents find them without asking.
#
# Usage:
# bash scaffold-seed.sh <fingerprint-json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
[[ $# -eq 1 ]] || die "Usage: $0 <fingerprint-json>"
FP="$1"
[[ -f "$FP" ]] || die "Fingerprint file not found: $FP"
DB_KIND=$(jq -r '.db_kind // ""' "$FP")
LANGS=$(jq -r '.languages | join(",")' "$FP")
# Real bcrypt hash of literal string "password" (cost 10).
# Generated with `htpasswd -bnBC 10 "" password | cut -d: -f2`.
# Most bcrypt libraries (bcryptjs, passlib, golang.org/x/crypto/bcrypt) accept $2a$ and $2b$.
BCRYPT_PASSWORD='$2b$10$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW'
# Choose the seed strategy by framework / DB
SEED_BODY=""
case "$DB_KIND" in
prisma)
SEED_BODY='npx prisma db seed'
;;
postgres|unknown-sql)
SEED_BODY="psql \"\${DATABASE_URL:?DATABASE_URL must be set}\" <<SQL
-- Idempotent: use ON CONFLICT DO NOTHING
INSERT INTO users (id, email, password_hash, created_at)
VALUES (1, 'dev@local.test', '${BCRYPT_PASSWORD}', NOW())
ON CONFLICT (id) DO NOTHING;
SQL"
;;
mysql)
SEED_BODY="mysql \"\${DATABASE_URL:?DATABASE_URL must be set}\" <<SQL
INSERT IGNORE INTO users (id, email, password_hash, created_at)
VALUES (1, 'dev@local.test', '${BCRYPT_PASSWORD}', NOW());
SQL"
;;
mongodb)
SEED_BODY="mongosh \"\${DATABASE_URL:?DATABASE_URL must be set}\" --eval \"
db.users.updateOne(
{ email: 'dev@local.test' },
{ \\\$setOnInsert: { email: 'dev@local.test', passwordHash: '${BCRYPT_PASSWORD}', createdAt: new Date() } },
{ upsert: true }
)\""
;;
*)
SEED_BODY='echo "TODO: customize seed for your stack — see references/fix-recipes.md (scaffold-seed)"'
;;
esac
OUT_DIR=$(scratch_dir "seed")
TMPL="${DX_SKILL_DIR}/assets/templates/seed.sh.tmpl"
[[ -f "$TMPL" ]] || die "Template missing: $TMPL"
render_template "$TMPL" "${OUT_DIR}/seed.sh" \
"SEED_BODY=${SEED_BODY}" \
"TEST_EMAIL=dev@local.test" \
"TEST_PASSWORD=password"
chmod +x "${OUT_DIR}/seed.sh"
log "Wrote seed to ${OUT_DIR}/seed.sh"
printf '%s\n' "$OUT_DIR"
#!/usr/bin/env bash
# time-to-first-commit.sh — measure TTFC in isolation
# Part of: dx-harness
#
# A single-purpose primitive: clone-equivalent (fresh worktree) → bootstrap → test,
# emit wall-clock JSON. Useful when you want just the metric, without the rest of
# the verify checklist.
#
# Usage:
# bash time-to-first-commit.sh # detect from $PWD
# bash time-to-first-commit.sh <fingerprint-json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
if [[ $# -ge 1 ]]; then
FP="$1"
else
FP=$(mktemp -t dx-fp-XXXXXX.json)
bash "${SCRIPT_DIR}/discover.sh" > "$FP"
trap 'rm -f "$FP"' EXIT
fi
BOOTSTRAP=$(jq -r '.bootstrap_command // ""' "$FP")
TEST_RUNNER=$(jq -r '.test_runner' "$FP")
[[ -n "$BOOTSTRAP" ]] || die "No bootstrap command detected — TTFC undefined. Scaffold one first."
# Resolve test command (must be runnable)
case "$TEST_RUNNER" in
vitest) TEST_CMD="npx vitest run --reporter=dot --bail=1 --silent" ;;
jest) TEST_CMD="npx jest --bail=1 --silent" ;;
pytest) TEST_CMD="pytest -x -q" ;;
cargo-test) TEST_CMD="cargo test --quiet" ;;
go-test) TEST_CMD="go test ./..." ;;
npm-test) TEST_CMD="npm test --silent" ;;
*) TEST_CMD="" ;;
esac
WT=$(scratch_dir "ttfc")
make_worktree "$WT" >/dev/null
register_worktree_cleanup "$WT"
bootstrap_start=$(python3 -c 'import time;print(int(time.time()*1000))')
( cd "$WT" && eval "$BOOTSTRAP" ) >/dev/null 2>&1 || true
bootstrap_end=$(python3 -c 'import time;print(int(time.time()*1000))')
test_start=$bootstrap_end
if [[ -n "$TEST_CMD" ]]; then
( cd "$WT" && eval "$TEST_CMD" ) >/dev/null 2>&1 || true
fi
test_end=$(python3 -c 'import time;print(int(time.time()*1000))')
jq -n \
--argjson bootstrap_ms "$((bootstrap_end - bootstrap_start))" \
--argjson test_ms "$((test_end - test_start))" \
--argjson total_ms "$((test_end - bootstrap_start))" \
--arg bootstrap_cmd "$BOOTSTRAP" \
--arg test_cmd "${TEST_CMD:-(none)}" \
--arg generated_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{
schema_version: 1,
generated_at: $generated_at,
bootstrap_ms: $bootstrap_ms,
test_ms: $test_ms,
total_ms: $total_ms,
bootstrap_cmd: $bootstrap_cmd,
test_cmd: $test_cmd
}'
#!/usr/bin/env bash
# track-attrition.sh — append audit to log, diff vs previous
# Part of: dx-harness
#
# Persists each audit run keyed by repo_hash. Reads the previous audit for the
# same repo and prints a short diff: new findings (regressions), missing findings
# (wins), score-of-scores trend.
#
# Storage: ${CLAUDE_PLUGIN_DATA}/dx-harness/audits.log (NDJSON, append-only)
#
# Usage:
# bash track-attrition.sh <audit-json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
[[ $# -eq 1 ]] || die "Usage: $0 <audit-json>"
AUDIT_FILE="$1"
[[ -f "$AUDIT_FILE" ]] || die "Audit file not found: $AUDIT_FILE"
LOG_FILE=$(config_get "audit_log_path" "${DX_DATA_DIR}/audits.log")
# config_get already runs safe_expand; nothing more to do.
mkdir -p "$(dirname "$LOG_FILE")"
REPO_HASH=$(jq -r '.fingerprint_hash // ""' "$AUDIT_FILE")
[[ -n "$REPO_HASH" && "$REPO_HASH" != "null" ]] || die "Audit missing fingerprint_hash"
# --- find previous entry for this repo (last line where fingerprint_hash matches) ---
PREV=""
if [[ -f "$LOG_FILE" ]]; then
PREV=$(grep -F "\"fingerprint_hash\":\"${REPO_HASH}\"" "$LOG_FILE" | tail -1 || true)
fi
# --- append current ---
COMPACT=$(jq -c '.' "$AUDIT_FILE")
printf '%s\n' "$COMPACT" >> "$LOG_FILE"
# --- print trend summary ---
CUR_TOTAL=$(jq -r '.total_findings // 0' "$AUDIT_FILE")
CUR_IDS=$(jq -r '.findings[]?.id' "$AUDIT_FILE" | sort -u)
if [[ -z "$PREV" ]]; then
printf 'Trend: first audit for this repo (hash %s). %d findings recorded.\n' "$REPO_HASH" "$CUR_TOTAL"
exit 0
fi
PREV_TOTAL=$(printf '%s' "$PREV" | jq -r '.total_findings // 0')
PREV_IDS=$(printf '%s' "$PREV" | jq -r '.findings[]?.id' | sort -u)
NEW_IDS=$(comm -23 <(printf '%s' "$CUR_IDS") <(printf '%s' "$PREV_IDS") || true)
RESOLVED_IDS=$(comm -13 <(printf '%s' "$CUR_IDS") <(printf '%s' "$PREV_IDS") || true)
DELTA=$((CUR_TOTAL - PREV_TOTAL))
SIGN="="
[[ $DELTA -gt 0 ]] && SIGN="+"
[[ $DELTA -lt 0 ]] && SIGN="" # negative already prints with -
printf 'Trend: %d findings (%s%d vs previous audit).\n' "$CUR_TOTAL" "$SIGN" "$DELTA"
if [[ -n "$NEW_IDS" ]]; then
printf ' Regressions (new findings):\n'
printf ' - %s\n' $NEW_IDS
fi
if [[ -n "$RESOLVED_IDS" ]]; then
printf ' Wins (resolved since last audit):\n'
printf ' - %s\n' $RESOLVED_IDS
fi
#!/usr/bin/env bash
# verify.sh — assert the harness actually works
# Part of: dx-harness
#
# Creates a fresh git worktree in $TMPDIR, runs the detected bootstrap command,
# times it, runs reset if present, runs the test command, asserts pass.
# Cleans up the worktree on exit.
#
# Output: PASS/FAIL report. Exit 0 if all pass, 1 if any FAIL.
#
# Usage:
# bash verify.sh # detect everything from current repo
# bash verify.sh <fingerprint-json> # use a pre-computed fingerprint
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/lib/common.sh"
require_jq
TTFC_TARGET=$(config_get "ttfc_target_seconds" 60)
if [[ $# -ge 1 ]]; then
FP="$1"
else
FP=$(mktemp -t dx-fp-XXXXXX.json)
bash "${SCRIPT_DIR}/discover.sh" > "$FP"
trap 'rm -f "$FP"' EXIT
fi
BOOTSTRAP=$(jq -r '.bootstrap_command // ""' "$FP")
TASK_RUNNER=$(jq -r '.task_runner' "$FP")
TEST_RUNNER=$(jq -r '.test_runner' "$FP")
ROOT=$(jq -r '.repo_root' "$FP")
[[ -d "$ROOT" ]] || die "repo_root invalid: $ROOT"
# Pick test command from fingerprint
case "$TEST_RUNNER" in
vitest) TEST_CMD="npx vitest run" ;;
jest) TEST_CMD="npx jest" ;;
pytest) TEST_CMD="pytest" ;;
cargo-test) TEST_CMD="cargo test" ;;
go-test) TEST_CMD="go test ./..." ;;
npm-test) TEST_CMD="npm test" ;;
"") TEST_CMD="" ;;
*) TEST_CMD="$TEST_RUNNER" ;;
esac
WT=$(scratch_dir "verify-wt")
make_worktree "$WT" >/dev/null
register_worktree_cleanup "$WT"
PASS=0
FAIL=0
report=""
assert() {
local label="$1" cond="$2" detail="${3:-}"
if [[ "$cond" == "true" ]]; then
report+=" PASS: ${label}"$'\n'
[[ -n "$detail" ]] && report+=" ${detail}"$'\n'
PASS=$((PASS + 1))
else
report+=" FAIL: ${label}"$'\n'
[[ -n "$detail" ]] && report+=" ${detail}"$'\n'
FAIL=$((FAIL + 1))
fi
}
# --- 1. Bootstrap exists ---
if [[ -n "$BOOTSTRAP" ]]; then
assert "bootstrap command exists" "true" "detected: ${BOOTSTRAP}"
else
assert "bootstrap command exists" "false" "no bootstrap.sh / just bootstrap / make bootstrap"
fi
# --- 2. Bootstrap runs successfully in scratch worktree ---
BOOTSTRAP_MS=0
if [[ -n "$BOOTSTRAP" ]]; then
log "Running bootstrap in scratch worktree: $WT"
bootstrap_log=$(mktemp -t dx-bootstrap-log-XXXXXX)
set +e
start_ms=$(python3 -c 'import time;print(int(time.time()*1000))')
( cd "$WT" && eval "$BOOTSTRAP" ) > "$bootstrap_log" 2>&1
rc=$?
end_ms=$(python3 -c 'import time;print(int(time.time()*1000))')
set -e
BOOTSTRAP_MS=$((end_ms - start_ms))
if [[ $rc -eq 0 ]]; then
assert "bootstrap completes successfully" "true" "took ${BOOTSTRAP_MS}ms"
else
tail_excerpt=$(tail -n 5 "$bootstrap_log" | tr '\n' '|')
assert "bootstrap completes successfully" "false" "exit=${rc}, tail: ${tail_excerpt}"
fi
rm -f "$bootstrap_log"
fi
# --- 3. TTFC under target ---
target_ms=$((TTFC_TARGET * 1000))
if [[ "$BOOTSTRAP_MS" -gt 0 && "$BOOTSTRAP_MS" -le "$target_ms" ]]; then
assert "time-to-first-commit under target" "true" "${BOOTSTRAP_MS}ms <= ${target_ms}ms (target ${TTFC_TARGET}s)"
elif [[ "$BOOTSTRAP_MS" -gt 0 ]]; then
assert "time-to-first-commit under target" "false" "${BOOTSTRAP_MS}ms > ${target_ms}ms (target ${TTFC_TARGET}s)"
fi
# --- 4. Reset roundtrip (if reset.sh exists) ---
# Asserts the harness's central promise: after reset, the env is ready again
# without manual steps (re-register, re-login, hand-edit DB).
if [[ -x "${WT}/reset.sh" ]]; then
log "Running reset roundtrip in scratch worktree"
reset_log=$(mktemp -t dx-reset-log-XXXXXX)
set +e
( cd "$WT" && ./reset.sh ) > "$reset_log" 2>&1
reset_rc=$?
set -e
if [[ $reset_rc -eq 0 ]]; then
assert "reset.sh completes successfully (env re-prepared without manual steps)" "true" ""
else
tail_excerpt=$(tail -n 5 "$reset_log" | tr '\n' '|')
assert "reset.sh completes successfully (env re-prepared without manual steps)" "false" "exit=${reset_rc}, tail: ${tail_excerpt}"
fi
rm -f "$reset_log"
fi
# --- 5. Test command exists and passes ---
if [[ -n "$TEST_CMD" ]]; then
set +e
test_log=$(mktemp -t dx-test-log-XXXXXX)
( cd "$WT" && eval "$TEST_CMD" ) > "$test_log" 2>&1
rc=$?
set -e
if [[ $rc -eq 0 ]]; then
assert "tests pass" "true" "command: ${TEST_CMD}"
else
tail_excerpt=$(tail -n 5 "$test_log" | tr '\n' '|')
assert "tests pass" "false" "exit=${rc}, command: ${TEST_CMD}, tail: ${tail_excerpt}"
fi
rm -f "$test_log"
else
assert "test command exists" "false" "no test runner detected"
fi
# --- 6. AGENTS.md present ---
if [[ "$(jq -r '.agents_md_present' "$FP")" == "true" ]]; then
assert "AGENTS.md present" "true" "path: $(jq -r '.agents_md_path' "$FP")"
else
assert "AGENTS.md present" "false" "no AGENTS.md / CLAUDE.md found"
fi
# --- Summary ---
echo ""
echo "Verify report (worktree: $WT):"
echo "$report"
echo "Results: ${PASS} passed, ${FAIL} failed"
if [[ "$BOOTSTRAP_MS" -gt 0 ]]; then
echo "TTFC (bootstrap wall-clock): ${BOOTSTRAP_MS}ms"
fi
[[ $FAIL -eq 0 ]] || exit 1
Related skills
FAQ
What does dx-harness do?
dx-harness is a Claude Code skill for ai & agent building.
When should I use dx-harness?
When you need to helps with ai & agent building tasks during AI-assisted development., or when dx-harness is a claude code skill for ai & agent building.
What are the main capabilities?
dx-harness; AI & Agent Building; AI-coding skill.