[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71GQHM2NE2QSCWHEW8FK95E":3,"skill-same:01M71GQHM2NE2QSCWHEW8FK95E":91},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":31,"publishedAt":51,"linkOnly":52,"markdownContent":53,"markdownBody":54,"frontMatter":55,"refName":68,"commitSha":69,"skillDirectory":70,"forksCount":71,"repositoryHtmlUrl":72,"repositoryHomepageUrl":6,"ownerHtmlUrl":73,"ownerBlogUrl":74,"ownerDescription":75,"updatedAt":76,"install":77,"resources":90},"01M71GQHM2NE2QSCWHEW8FK95E","health",null,"Code quality dashboard: runs the project's type checker, linter and tests, scores health 0-10, tracks trends.","timurgaleev\u002Fvibestack","timurgaleev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F32696429?v=4","01M71GMS53Z9B4JQTCQEFXAE9E","Timur Galeev","01M71GMS5S1Q7BEYB3Z7GY3E85",8,"MIT","https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack\u002Fblob\u002Fmain\u002Fskills\u002Fhealth\u002FSKILL.md",[18,23,28],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000013","分析与可视化","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",{"categoryNo":29,"categoryName":30,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000033","数据分析师",[5,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50],"write","trends","health check","type","check","healthy","tests","tracks","code quality","edit","read","how","dashboard","type checker","code","Bash","checker","linter","project","2026-11-10 10:12:30",false,"---\nname: health\ndescription: |\n  Code quality dashboard: runs the project's type checker, linter and tests, scores health 0-10, tracks trends.\ntriggers:\n  - code health check\n  - quality dashboard\n  - how healthy is codebase\nallowed-tools:\n  - Bash\n  - Read\n  - Write\n  - Edit\n  - Glob\n  - Grep\n  - AskUserQuestion\n---\n\nIf this session was compacted, the copy of this skill in context may be cut off.\nRead `${CLAUDE_SKILL_DIR}\u002FSKILL.md` from disk and resume at the step you are on.\n\n## When to invoke\n\nUse when: \"health check\", \"code quality\", \"how healthy is the codebase\", \"run all checks\", \"quality score\".\n\n## Preamble\n\n```bash\neval \"$(\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-slug\" 2>\u002Fdev\u002Fnull)\" 2>\u002Fdev\u002Fnull || SLUG=\"unknown\"\n_LEARN_FILE=\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F${SLUG:-unknown}\u002Flearnings.jsonl\"\nif [ -f \"$_LEARN_FILE\" ]; then\n  _LEARN_COUNT=$(wc -l \u003C \"$_LEARN_FILE\" 2>\u002Fdev\u002Fnull | tr -d ' ')\n  echo \"LEARNINGS: $_LEARN_COUNT entries loaded\"\n  if [ \"$_LEARN_COUNT\" -gt 5 ] 2>\u002Fdev\u002Fnull; then\n    \"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-learnings-search\" --limit 5 2>\u002Fdev\u002Fnull || true\n  fi\nelse\n  echo \"LEARNINGS: none yet\"\nfi\n```\n\n{{include lib\u002Fsnippets\u002Fsession-host.md}}\n\n{{include lib\u002Fsnippets\u002Fdecision-brief.md}}\n\n{{include lib\u002Fsnippets\u002Fworking-protocols.md}}\n\n{{include lib\u002Fsnippets\u002Fstate-protocols.md}}\n\n## User-invocable\nWhen the user types `\u002Fhealth`, run this skill. `\u002Fhealth skills` runs\n[Target: skills](#target-skills) instead of Steps 1-6. `\u002Fhealth --fix` runs\n[Mode: --fix](#mode---fix) instead of Steps 2-6.\n\n---\n\n## Step 1: Detect Health Stack\n\nRead CLAUDE.md and look for a `## Health Stack` section. If found, parse the tools\nlisted there and skip auto-detection. Then print `MISSING: \u003Ccategory> (not listed in\n## Health Stack)` for each of the five categories (typecheck, lint, test, deadcode,\nshell) the section omits.\n\nIf no `## Health Stack` section exists, auto-detect available tools:\n\n```bash\n# A Node tool installed as a devDependency is not on PATH; run it from node_modules\u002F.bin.\nnode_tool() { if [ -x \"node_modules\u002F.bin\u002F${1}\" ]; then echo \".\u002Fnode_modules\u002F.bin\u002F${1}\"; else echo \"${1}\"; fi; }\nsetopt +o nomatch 2>\u002Fdev\u002Fnull || true\nhealth_found=$(\n# Type checker\n[ -f tsconfig.json ] && echo \"TYPECHECK: $(node_tool tsc) --noEmit\"\n\n# Linter\n[ -f biome.json ] || [ -f biome.jsonc ] && echo \"LINT: $(node_tool biome) check .\"\nls eslint.config.* .eslintrc.* .eslintrc 2>\u002Fdev\u002Fnull | sed -n '1p' | xargs -I{} echo \"LINT: $(node_tool eslint) .\"\n[ -f .pylintrc ] || [ -f pyproject.toml ] && grep -q \"pylint\\|ruff\" pyproject.toml 2>\u002Fdev\u002Fnull && echo \"LINT: ruff check .\"\ncommand -v terraform >\u002Fdev\u002Fnull 2>&1 && [ -n \"$(find . -maxdepth 3 -name '*.tf' -not -path '*\u002F.terraform\u002F*' 2>\u002Fdev\u002Fnull | sed -n '1p')\" ] && echo \"LINT: terraform fmt -check -recursive\"\ncommand -v cfn-lint >\u002Fdev\u002Fnull 2>&1 && cfn_files=$(find . -maxdepth 3 \\( -name node_modules -o -name .git \\) -prune -o -type f \\( -name '*.yaml' -o -name '*.yml' -o -name '*.json' -o -name '*.template' \\) -exec grep -l 'AWSTemplateFormatVersion' {} + 2>\u002Fdev\u002Fnull | sort | awk -v q=\"'\" '{gsub(q, q \"\\\\\" q q); printf \"%s%s%s \", q, $(0), q}') && [ -n \"$cfn_files\" ] && echo \"LINT: cfn-lint ${cfn_files% }\"\ncommand -v actionlint >\u002Fdev\u002Fnull 2>&1 && [ -d .github\u002Fworkflows ] && echo \"LINT: actionlint\"\n\n# Test runner: only a non-empty, single-line scripts.test is a command.\n[ -f package.json ] && python3 -I -c '\nimport json, sys\ntry:\n    t = json.load(open(sys.argv[1])).get(\"scripts\", {}).get(\"test\")\nexcept Exception:\n    sys.exit(0)\nif isinstance(t, str) and t.strip():\n    t = t.strip()\n    if \"\\n\" in t:\n        print(\"TEST: none (multi-line test script; add it to a ## Health Stack section)\")\n    elif \"no test specified\" in t:\n        print(\"TEST: none (npm init placeholder test script)\")\n    else:\n        print(\"TEST: \" + t)\n' package.json\n[ -f pyproject.toml ] && grep -q \"pytest\" pyproject.toml 2>\u002Fdev\u002Fnull && echo \"TEST: pytest\"\n[ -f Cargo.toml ] && echo \"TEST: cargo test\"\n[ -f go.mod ] && echo \"TEST: go test .\u002F...\"\n\n# Dead code\ncommand -v knip >\u002Fdev\u002Fnull 2>&1 && echo \"DEADCODE: knip\"\n[ -f package.json ] && grep -q '\"knip\"' package.json 2>\u002Fdev\u002Fnull && echo \"DEADCODE: npx knip\"\n\n# Shell linting\ncommand -v shellcheck >\u002Fdev\u002Fnull 2>&1 && ls *.sh scripts\u002F*.sh bin\u002F*.sh 2>\u002Fdev\u002Fnull | sed -n '1p' | xargs -I{} echo \"SHELL: shellcheck\"\n)\n[ -n \"$health_found\" ] && printf '%s\\n' \"$health_found\"\n\n# One MISSING line, with its reason, per category nothing was detected for.\nhealth_missing() { printf 'MISSING: %s (%s)\\n' \"${1}\" \"${2}\"; }\nprintf '%s\\n' \"$health_found\" | grep -q '^TYPECHECK: ' || health_missing typecheck \"no tsconfig.json\"\nprintf '%s\\n' \"$health_found\" | grep -q '^LINT: ' || health_missing lint \"no biome, eslint or ruff config; no terraform, cfn-lint or actionlint target with its binary on PATH\"\nif ! printf '%s\\n' \"$health_found\" | grep '^TEST: ' | grep -vq '^TEST: none ('; then\n  test_reason=$(printf '%s\\n' \"$health_found\" | sed -n 's\u002F^TEST: none (\\(.*\\))$\u002F\\1\u002Fp' | sed -n '1p')\n  health_missing test \"${test_reason:-no scripts.test, pytest, Cargo.toml or go.mod}\"\nfi\nprintf '%s\\n' \"$health_found\" | grep -q '^DEADCODE: ' || health_missing deadcode \"knip neither on PATH nor in package.json\"\nprintf '%s\\n' \"$health_found\" | grep -q '^SHELL: ' || health_missing shell \"shellcheck not on PATH, or no *.sh in ., scripts\u002F or bin\u002F\"\n```\n\nA `TEST: none (...)` line is not a command: the test category is missing, for the\nreason in parentheses. Every `MISSING:` category is unavailable (`null` in Step 3)\nand its reason goes into the dashboard's Unavailable line.\n\nUse Glob to search for shell scripts:\n- `**\u002F*.sh` (shell scripts in the repo)\n\nAfter auto-detection, present the detected tools via AskUserQuestion:\n\n\"I detected these health check tools for this project:\n\n- Type check: `tsc --noEmit`\n- Lint: `biome check .`\n- Tests: `bun test`\n- Dead code: `knip`\n- Shell lint: `shellcheck *.sh`\n\nA) Looks right -- persist to CLAUDE.md and continue\nB) I need to adjust some tools (tell me which)\nC) Skip persistence -- just run these\"\n\nIf the user chooses A or B (after adjustments), append or update a `## Health Stack`\nsection in CLAUDE.md:\n\n```markdown\n## Health Stack\n\n- typecheck: tsc --noEmit\n- lint: biome check .\n- test: bun test\n- deadcode: knip\n- shell: shellcheck *.sh scripts\u002F*.sh\n```\n\n---\n\n## Step 2: Run Tools\n\nRun each detected tool. For each tool:\n\n1. Record the start time\n2. Run the command, capturing complete stdout and stderr in a private temporary log\n3. Record the checker's own exit code, before any parser or display command runs\n4. Record the end time\n5. Count findings from the complete log, then show its last 50 lines for the report\n\n```bash\n# Capture one tool. Per tool, set health_tool, the command line and the count pattern.\n(\n  umask 077\n  health_tool=typecheck\n  health_pattern='error TS'\n  health_capture_error() {\n    printf 'TOOL:%s ERROR:capture-%s\\n' \"$health_tool\" \"${1}\"\n    exit 125\n  }\n  health_log=$(mktemp \"${TMPDIR:-\u002Ftmp}\u002Fvibe-health.XXXXXX\") || health_capture_error log\n  trap 'rm -f -- \"$health_log\"' EXIT\n  health_start=$(date +%s) || health_capture_error timing\n  # Open the log first, so a redirection failure cannot pass for a checker result.\n  exec 3>\"$health_log\" || health_capture_error redirection\n  if tsc --noEmit >&3 2>&1; then\n    health_status=0\n  else\n    health_status=$?\n  fi\n  exec 3>&-\n  health_end=$(date +%s) || health_capture_error timing\n  # awk prints 0 for no matches, so an empty log still yields a count.\n  health_count=$(awk -v p=\"$health_pattern\" 'index($(0), p) { n++ } END { print n+0 }' \"$health_log\") || health_capture_error parsing\n  tail -50 \"$health_log\" || health_capture_error display\n  printf 'TOOL:%s EXIT:%s DURATION:%ss COUNT:%s\\n' \"$health_tool\" \"$health_status\" \"$((health_end-health_start))\" \"$health_count\"\n)\n```\n\n`EXIT` is the checker's status, never that of `tail`, `awk` or a pipe. `COUNT` comes\nfrom the whole log, not the 50 lines shown. Swap in the tool's command and the fixed\nstring that marks one finding (`error TS` for tsc; Step 3 lists the others); for a\ntool whose summary line carries the count, read it from the same log.\n\nRun tools sequentially, one block per tool (some share resources or lock files). A\nfailing checker does not stop the later ones.\n\n**SKIPPED, FAILED or ERROR — decide before and after running:**\n- **SKIPPED** — decided *before* running: the tool's binary is absent\n  (`command -v \u003Cbinary>` finds nothing, no local `node_modules\u002F.bin` entry) and no\n  `## Health Stack` entry names it. Record the reason. Only a skip redistributes weight.\n  A binary that exists only in `node_modules\u002F.bin` is present: run it as\n  `.\u002Fnode_modules\u002F.bin\u002F\u003Cbinary>`, including when a `## Health Stack` entry names it bare.\n- **FAILED** — the command ran and could not execute the check: exit 126 or 127\n  (typo, missing binary in a configured `## Health Stack` command, no permission).\n  It scores 0. A configured command that does not run is a broken check, not a\n  missing one, and must never raise the score.\n- **ERROR** — the capture itself broke (`TOOL:\u003Cname> ERROR:capture-...`: log,\n  redirection, timing, parsing or display). The category gets no score, the composite\n  is `N\u002FA — capture failed`, and no history row is written.\n\n---\n\n## Step 3: Score Each Category\n\nScore each category on a 0-10 scale using this rubric:\n\n| Category | Weight | 10 | 7 | 4 | 0 |\n|-----------|--------|------|-----------|------------|-----------|\n| Type check | 25% | Clean (exit 0) | \u003C10 errors | \u003C50 errors | >=50 errors |\n| Lint | 20% | Clean (exit 0) | \u003C5 warnings | \u003C20 warnings | >=20 warnings |\n| Tests | 30% | All pass (exit 0) | >95% pass | >80% pass | \u003C=80% pass |\n| Dead code | 15% | Clean (exit 0) | \u003C5 unused exports | \u003C20 unused | >=20 unused |\n| Shell lint | 10% | Clean (exit 0) | \u003C5 issues | >=5 issues | N\u002FA (skip) |\n\n**Parsing tool output for counts:** use the complete captured log (`COUNT` above),\nnever the displayed tail. A non-zero exit is never `CLEAN` and never 10. Exit 126 or\n127 is `FAILED` and scores 0, whatever the log says. Any other non-zero exit whose\nfull log yields no count scores 4 (as for a test runner that reports only its exit\ncode); keep its output for the details section.\n\n- **tsc:** Count lines matching `error TS` in output.\n- **biome\u002Feslint\u002Fruff:** Count lines matching error\u002Fwarning patterns. Parse the summary line if available.\n- **Tests:** Parse pass\u002Ffail counts from the test runner output. If the runner only reports exit code, use: exit 0 = 10, any other non-zero exit except 126\u002F127 = 4 (assume some failures).\n- **knip:** Count lines reporting unused exports, files, or dependencies.\n- **shellcheck:** Count distinct findings (lines starting with \"In ... line\").\n\n**Composite score:** compute it in code, never by hand. Pass every category once as\n`name=\u003Cscore>`, where the score is an integer 0-10, `null` for SKIPPED, or `error`\nfor a capture ERROR. Skipped weight is redistributed proportionally among the\nscored categories; the weights are typecheck 25%, lint 20%, test 30%, deadcode 15%,\nshell 10%.\n\n```bash\npython3 -I -c '\nimport sys\nw = {\"typecheck\": 0.25, \"lint\": 0.20, \"test\": 0.30, \"deadcode\": 0.15, \"shell\": 0.10}\ns = {}\nfor a in sys.argv[1:]:\n    k, sep, v = a.partition(\"=\")\n    if not sep or k not in w or k in s:\n        sys.exit(\"bad argument: \" + a)\n    if v not in (\"null\", \"error\") and not (v.isdigit() and int(v) \u003C= 10):\n        sys.exit(\"bad score for \" + k + \": \" + v)\n    s[k] = v\nif set(s) != set(w):\n    sys.exit(\"pass every category: \" + \" \".join(w))\nscored = [k for k in w if s[k] not in (\"null\", \"error\")]\nerrors = [k for k in w if s[k] == \"error\"]\nprint(\"CHECKED: \" + (\" \".join(scored) or \"none\"))\nprint(\"UNAVAILABLE: \" + (\" \".join(k for k in w if s[k] == \"null\") or \"none\"))\nprint(\"COVERAGE: %d\u002F%d\" % (len(scored), len(w)))\nif errors:\n    print(\"COMPOSITE: N\u002FA - capture failed (\" + \" \".join(errors) + \")\")\nelif not scored:\n    print(\"COMPOSITE: N\u002FA - no checks ran\")\nelse:\n    c = sum(int(s[k]) * w[k] for k in scored) \u002F sum(w[k] for k in scored)\n    print(\"COMPOSITE: %.1f\" % c + (\"\" if len(scored) == len(w) else \" (partial coverage)\"))\n' typecheck=10 lint=8 test=10 deadcode=7 shell=null\n```\n\nReplace the example values with this run's results. Report the `COMPOSITE` line as\nprinted; a numeric composite exists only when it prints a number.\n\n---\n\n## Step 4: Present Dashboard\n\nPresent results as a clear table:\n\n```\nCODE HEALTH DASHBOARD\n=====================\n\nProject: \u003Cproject name>\nBranch:  \u003Ccurrent branch>\nDate:    \u003Ctoday>\n\nCategory      Tool              Score   Status     Duration   Details\n----------    ----------------  -----   --------   --------   -------\nType check    tsc --noEmit      10\u002F10   CLEAN      3s         0 errors\nLint          biome check .      8\u002F10   WARNING    2s         3 warnings\nTests         bun test          10\u002F10   CLEAN      12s        47\u002F47 passed\nDead code     knip               7\u002F10   WARNING    5s         4 unused exports\nShell lint    shellcheck        10\u002F10   CLEAN      1s         0 issues\n\nCOMPOSITE SCORE: 9.2 \u002F 10\nCoverage: 5\u002F5 categories checked\nChecked: typecheck, lint, test, deadcode, shell\nUnavailable: none\n\nDuration: 23s total\nVERDICT: NOT READY (lint, deadcode exited non-zero)\n```\n\nUse these status labels:\n- 10: `CLEAN`\n- 7-9: `WARNING`\n- 4-6: `NEEDS WORK`\n- 0-3: `CRITICAL`\n- Command could not run (exit 126\u002F127): `FAILED` (scores 0)\n- Tool not available: `SKIPPED` (no score)\n- Capture broke: `ERROR` (no score; composite is N\u002FA)\n\n**Verdict line.** End the dashboard with one of:\n\n- `VERDICT: READY` only when every required check ran and exited 0 and all five\n  categories were checked. The required checks are every `## Health Stack` entry,\n  or every detected tool when the stack was auto-detected.\n- `VERDICT: NOT READY` when every required check ran and at least one exited\n  non-zero.\n- `VERDICT: INCOMPLETE` when any required check was SKIPPED, FAILED or ERROR, or\n  fewer than five categories were checked. Name each missing one. A high composite\n  does not change this: never `READY` with partial coverage.\n\nAlways show the coverage lines. With skipped categories, label the score\n`— partial coverage` (e.g. `COMPOSITE SCORE: 8.0 \u002F 10 — partial coverage`,\n`Coverage: 2\u002F5 categories checked`) and list each unavailable category with the\nreason from its Step 1 `MISSING:` line (or its SKIPPED reason), e.g.\n`Unavailable: deadcode (knip neither on PATH nor in package.json)`. When no check ran, show `COMPOSITE SCORE: N\u002FA — no checks ran` and say which\ntools need installing or configuring; never show 10\u002F10 for an empty run.\n\nIf any category scored below 7, list the top issues from that tool's output:\n\n```\nDETAILS: Lint (3 warnings)\n  biome check . output:\n    src\u002Futils.ts:42 — lint\u002Fcomplexity\u002FnoForEach: Prefer for...of\n    src\u002Fapi.ts:18 — lint\u002Fstyle\u002FuseConst: Use const instead of let\n    src\u002Fapi.ts:55 — lint\u002Fsuspicious\u002FnoExplicitAny: Unexpected any\n```\n\n---\n\n## Step 5: Persist to Health History\n\n```bash\neval \"$(\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-slug\" 2>\u002Fdev\u002Fnull)\" && mkdir -p \"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F$SLUG\"\necho \"PROJECT_DIR: ${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F$SLUG\"\n```\n\nOnly when the composite is numeric, append one JSONL line to\n`\u003CPROJECT_DIR>\u002Fhealth-history.jsonl` (where `\u003CPROJECT_DIR>` is the path the block above printed on its `PROJECT_DIR:` line). A run with no checks or a capture\nERROR writes nothing and leaves the existing history as it is:\n\n```json\n{\"ts\":\"2026-03-31T14:30:00Z\",\"branch\":\"main\",\"score\":9.2,\"typecheck\":10,\"lint\":8,\"test\":10,\"deadcode\":7,\"shell\":10,\"duration_s\":23}\n```\n\nFields:\n- `ts` -- ISO 8601 timestamp\n- `branch` -- current git branch\n- `score` -- composite score (one decimal)\n- `typecheck`, `lint`, `test`, `deadcode`, `shell` -- individual category scores (integer 0-10)\n- `duration_s` -- total time for all tools in seconds\n\nIf a category was skipped, set its value to `null`.\n\n---\n\n## Step 6: Trend Analysis + Recommendations\n\nRead the last 10 entries from `\u003CPROJECT_DIR>\u002Fhealth-history.jsonl` (if the\nfile exists and has prior entries).\n\n```bash\neval \"$(\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-slug\" 2>\u002Fdev\u002Fnull)\" && mkdir -p \"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F$SLUG\"\ntail -10 \"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F$SLUG\u002Fhealth-history.jsonl\" 2>\u002Fdev\u002Fnull || echo \"NO_HISTORY\"\n```\n\n**Compare like-for-like coverage only.** For each history row, the scored set is the\ncategories with a non-null value (a missing field counts as null). Report a delta,\n`IMPROVING` or `REGRESSIONS DETECTED` only against rows whose scored set equals this\nrun's exactly. If the previous run's set differs, say **Coverage changed — scores are\nnot comparable** and label nothing an improvement or a regression; older rows may\nstill be listed with their unavailable categories marked. An N\u002FA run has no trend.\n\n**If comparable prior entries exist, show the trend:**\n\n```\nHEALTH TREND (last 5 runs)\n==========================\nDate          Branch         Score   TC   Lint  Test  Dead  Shell\n----------    -----------    -----   --   ----  ----  ----  -----\n2026-03-28    main           9.4     10   9     10    8     10\n2026-03-29    feat\u002Fauth      8.8     10   7     10    7     10\n2026-03-30    feat\u002Fauth      8.2     10   6     9     7     10\n2026-03-31    feat\u002Fauth      9.2     10   8     10    7     10\n\nTrend: IMPROVING (+1.0 since last run)\n```\n\n**If score dropped vs the previous run with identical coverage:**\n1. Identify WHICH categories declined\n2. Show the delta for each declining category\n3. Correlate with tool output -- what specific errors\u002Fwarnings appeared?\n\n```\nREGRESSIONS DETECTED\n  Lint: 9 -> 6 (-3) — 12 new biome warnings introduced\n    Most common: lint\u002Fcomplexity\u002FnoForEach (7 instances)\n  Tests: 10 -> 9 (-1) — 2 test failures\n    FAIL src\u002Fauth.test.ts > should validate token expiry\n    FAIL src\u002Fauth.test.ts > should reject malformed JWT\n```\n\n**Health improvement suggestions (always show these):**\n\nPrioritize suggestions by impact (weight * score deficit):\n\n```\nRECOMMENDATIONS (by impact)\n============================\n1. [HIGH]  Fix 2 failing tests (Tests: 9\u002F10, weight 30%)\n   Run: bun test --verbose to see failures\n2. [MED]   Address 12 lint warnings (Lint: 6\u002F10, weight 20%)\n   Run: biome check . --write to auto-fix\n3. [LOW]   Remove 4 unused exports (Dead code: 7\u002F10, weight 15%)\n   Run: knip --fix to auto-remove\n```\n\nRank by `weight * (10 - score)` descending. Only show categories below 10.\n\n---\n\n## Mode: --fix\n\n`\u002Fhealth --fix` runs the checks Step 1 found, fixes the source until they pass, and\nproves the result from the evidence ledger. It writes no score and no history row.\n\n**Checks.** Run Step 1 (a `## Health Stack` section, or auto-detection). Each\ndetected command is one check, labelled by its category (`typecheck`, `lint`,\n`test`, `deadcode`, `shell`; `lint-2` for a second command in one category). With\nno check at all, print `CHECKS: none` and stop: there is nothing to make green,\nand saying it passed would be a lie.\n\n**Consent.** The commands come from the repo and run its code. Interactive: print\nevery command, and before the first run in a repo with no earlier health record\n(`\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-evidence\" read --skill health --limit 1`\nprints `no evidence recorded`) ask with AskUserQuestion: A) run them, B) stop.\n`SESSION_KIND: spawned` or `headless`: run only when invoked as `\u002Fhealth --fix --yes`;\notherwise print `FIX: needs --yes in a spawned or headless session` and stop.\n\n**Run the checks.** One block per run; one `fix_check` line per check, with its\nlabel and its exact command as ONE single-quoted string:\n\n```bash\n_EV=${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-evidence\n_FIX_LOG=$(mktemp -d \"${TMPDIR:-\u002Ftmp}\u002Fvibe-health-fix-XXXXXXXX\") || { echo \"FIX: cannot create a log directory\"; exit 125; }\nfix_check() {\n  \"$_EV\" run --skill health --label \"${1}\" -- \"${2}\" > \"$_FIX_LOG\u002F${1}.txt\" 2>&1\n  _rc=$?\n  printf 'CHECK: %s exit=%s log=%s\\n' \"${1}\" \"$_rc\" \"$_FIX_LOG\u002F${1}.txt\"\n  if [ \"$_rc\" -ne 0 ]; then tail -50 \"$_FIX_LOG\u002F${1}.txt\"; fi\n}\nfix_check '\u003Ccheck>' '\u003Ccommand>'\n```\n\nIf a log says `vibe-evidence` itself was not found, stop: this mode proves its\nresult from the ledger, and without it there is nothing to prove.\n\nRules for every run:\n\n- **Only exit 0 is green.** Any other `exit=`, a missing line or \"mostly passed\"\n  is red, whatever the log says.\n- **Pre-existing** means red on the first run this session, before any edit. A\n  check that turns red after your edit is yours, including a test you never\n  touched that your changed source now breaks.\n- **ENV, TIMEOUT or a missing secret** is reported with the log line that shows it.\n  Never stub, mock or skip around it.\n- **Never edit a test, a checker config or a script** (`package.json` scripts,\n  lint rules, CI files, `## Health Stack`) to get green. Fix the source the check\n  exercises; a wrong test is reported, not rewritten.\n- **Stop after 5 runs**, or on the same failure twice after an attempted fix. Report what is still red and hand it to `\u002Finvestigate`.\n\n**Finish from the ledger.** Do not re-run anything; ask the ledger whether each\ncheck's exact command passed at the tree as it is now. One `fix_ready` line per\ncheck, same label and command as the last run:\n\n```bash\n_EV=${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-evidence\nTREE_NOW=$(\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-tree-hash\" 2>\u002Fdev\u002Fnull || echo unknown)\n_fix_bad=\"\"\nfix_ready() { \"$_EV\" check --skill health --label \"${1}\" --expect-cmd \"${2}\" || _fix_bad=\"$_fix_bad ${1}\"; }\nfix_ready '\u003Ccheck>' '\u003Ccommand>'\nif [ -z \"$_fix_bad\" ]; then echo \"READY at tree $TREE_NOW\"; else echo \"NOT READY:$_fix_bad\"; fi\n```\n\nReport the `READY` or `NOT READY` line as printed, with each check's `FRESH` or\n`STALE` line, the pre-existing failures, and the files you changed. An edit after\nthe last run makes it `NOT READY`: run the checks again rather than claiming it.\n\n---\n\n## Target: skills\n\n`\u002Fhealth skills` reports on the installed skills instead of the codebase: how often\neach one is used, what its description costs every session, and what to do about it.\nBoth tools are read-only.\n\n```bash\n\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-skill-usage\" --days 90\n\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-context-budget\" --runtime claude --rendered \"$HOME\u002F.claude\u002Fskills\" --config-dir \"\" --top 0 --warn-only\n```\n\n`vibe-skill-usage` counts `typed` (the user typed `\u002F\u003Cname>`) and `model` (the model\ncalled the skill) from Claude Code transcripts, `nested` for the share found in\nsubagent transcripts, and `tracked` from the slash-command analytics file. Typed and\ntracked overlap — never add them. `vibe-context-budget` gives each skill's\ndescription length (`--top 0` lists every skill; `--config-dir \"\"` skips the repo\nconfig payload, which an installed copy does not have).\n\nPresent one row per installed skill:\n\n```\nSKILL HEALTH (last 90 days, claude measured; cursor, kiro, codex, copilot unmeasured)\nSkill           Typed  Model  Last seen    Desc chars  Proposal\n--------------  -----  -----  -----------  ----------  ------------------------------\nship               12     30  2026-10-09          112  Keep — used weekly, both ways\ndiagram             0      0  -                   140  Improve — never auto-invoked; description names no trigger phrase\nold-helper          0      0  -                   210  Retire — no use in 90 days on the one measured runtime and covered by \u002Fvibe\n```\n\nPropose exactly one of **Keep**, **Improve**, **Retire** or **Merge into `\u003Cskill>`**\nper skill, with a reason a reader can check without this conversation (the counts,\nthe overlapping skill, the description problem).\n\nRules:\n- **Unmeasured is not zero.** Usage on Cursor, Kiro, Codex or Copilot CLI, and in\n  transcripts the tool could not read, is unmeasured, and unmeasured never justifies Retire. Say\n  which runtimes the user works in before proposing Retire; a skill with zero Claude\n  use may be in daily use elsewhere.\n- **Retire is a proposal.** This target never deletes, moves or edits a skill. Point\n  to `\u002Fvibe-gc` for the removal itself.\n- **Merge into** names the surviving skill and what moves into it.\n- **Improve** names the change: a description that never matches how users ask, a\n  description long enough to dominate the budget, a trigger list that collides with\n  another skill.\n\n{{include lib\u002Fsnippets\u002Fcapture-learnings.md}}\nThe score itself belongs in `health-history.jsonl`, not here. What belongs here is what\nyou had to work out to produce it: a tool that needs a flag to be parseable, a category\nwhose count is misleading on this repo, a check that is slow enough to be worth skipping\non a quick pass.\n\n---\n\n## Important Rules\n\n1. **Wrap, don't replace.** Run the project's own tools. Never substitute your own analysis for what the tool reports.\n2. **Read-only outside `--fix`.** Never fix issues unless the user ran `\u002Fhealth --fix`. Present the dashboard and let the user decide.\n3. **Respect CLAUDE.md.** If `## Health Stack` is configured, use those exact commands. Do not second-guess.\n4. **Skipped is not failed.** Skip only a tool whose absence was established before running, show coverage, and redistribute weight among scored categories only. A command that ran and failed — including exit 127 — is FAILED, never SKIPPED.\n5. **Show raw output for failures.** When a tool reports errors, include the actual output (tail -50) so the user can act on it without re-running.\n6. **Trends require comparable history.** On the first scored run, say \"First health check -- no trend data yet. Run \u002Fhealth again after making changes to track progress.\" A changed coverage set or an N\u002FA run has no score delta.\n7. **Be honest about scores.** A codebase with 100 type errors and all tests passing is not healthy. The composite score should reflect reality.\n","If this session was compacted, the copy of this skill in context may be cut off.\nRead `${CLAUDE_SKILL_DIR}\u002FSKILL.md` from disk and resume at the step you are on.\n\n## When to invoke\n\nUse when: \"health check\", \"code quality\", \"how healthy is the codebase\", \"run all checks\", \"quality score\".\n\n## Preamble\n\n```bash\neval \"$(\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-slug\" 2>\u002Fdev\u002Fnull)\" 2>\u002Fdev\u002Fnull || SLUG=\"unknown\"\n_LEARN_FILE=\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F${SLUG:-unknown}\u002Flearnings.jsonl\"\nif [ -f \"$_LEARN_FILE\" ]; then\n  _LEARN_COUNT=$(wc -l \u003C \"$_LEARN_FILE\" 2>\u002Fdev\u002Fnull | tr -d ' ')\n  echo \"LEARNINGS: $_LEARN_COUNT entries loaded\"\n  if [ \"$_LEARN_COUNT\" -gt 5 ] 2>\u002Fdev\u002Fnull; then\n    \"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-learnings-search\" --limit 5 2>\u002Fdev\u002Fnull || true\n  fi\nelse\n  echo \"LEARNINGS: none yet\"\nfi\n```\n\n{{include lib\u002Fsnippets\u002Fsession-host.md}}\n\n{{include lib\u002Fsnippets\u002Fdecision-brief.md}}\n\n{{include lib\u002Fsnippets\u002Fworking-protocols.md}}\n\n{{include lib\u002Fsnippets\u002Fstate-protocols.md}}\n\n## User-invocable\nWhen the user types `\u002Fhealth`, run this skill. `\u002Fhealth skills` runs\n[Target: skills](#target-skills) instead of Steps 1-6. `\u002Fhealth --fix` runs\n[Mode: --fix](#mode---fix) instead of Steps 2-6.\n\n---\n\n## Step 1: Detect Health Stack\n\nRead CLAUDE.md and look for a `## Health Stack` section. If found, parse the tools\nlisted there and skip auto-detection. Then print `MISSING: \u003Ccategory> (not listed in\n## Health Stack)` for each of the five categories (typecheck, lint, test, deadcode,\nshell) the section omits.\n\nIf no `## Health Stack` section exists, auto-detect available tools:\n\n```bash\n# A Node tool installed as a devDependency is not on PATH; run it from node_modules\u002F.bin.\nnode_tool() { if [ -x \"node_modules\u002F.bin\u002F${1}\" ]; then echo \".\u002Fnode_modules\u002F.bin\u002F${1}\"; else echo \"${1}\"; fi; }\nsetopt +o nomatch 2>\u002Fdev\u002Fnull || true\nhealth_found=$(\n# Type checker\n[ -f tsconfig.json ] && echo \"TYPECHECK: $(node_tool tsc) --noEmit\"\n\n# Linter\n[ -f biome.json ] || [ -f biome.jsonc ] && echo \"LINT: $(node_tool biome) check .\"\nls eslint.config.* .eslintrc.* .eslintrc 2>\u002Fdev\u002Fnull | sed -n '1p' | xargs -I{} echo \"LINT: $(node_tool eslint) .\"\n[ -f .pylintrc ] || [ -f pyproject.toml ] && grep -q \"pylint\\|ruff\" pyproject.toml 2>\u002Fdev\u002Fnull && echo \"LINT: ruff check .\"\ncommand -v terraform >\u002Fdev\u002Fnull 2>&1 && [ -n \"$(find . -maxdepth 3 -name '*.tf' -not -path '*\u002F.terraform\u002F*' 2>\u002Fdev\u002Fnull | sed -n '1p')\" ] && echo \"LINT: terraform fmt -check -recursive\"\ncommand -v cfn-lint >\u002Fdev\u002Fnull 2>&1 && cfn_files=$(find . -maxdepth 3 \\( -name node_modules -o -name .git \\) -prune -o -type f \\( -name '*.yaml' -o -name '*.yml' -o -name '*.json' -o -name '*.template' \\) -exec grep -l 'AWSTemplateFormatVersion' {} + 2>\u002Fdev\u002Fnull | sort | awk -v q=\"'\" '{gsub(q, q \"\\\\\" q q); printf \"%s%s%s \", q, $(0), q}') && [ -n \"$cfn_files\" ] && echo \"LINT: cfn-lint ${cfn_files% }\"\ncommand -v actionlint >\u002Fdev\u002Fnull 2>&1 && [ -d .github\u002Fworkflows ] && echo \"LINT: actionlint\"\n\n# Test runner: only a non-empty, single-line scripts.test is a command.\n[ -f package.json ] && python3 -I -c '\nimport json, sys\ntry:\n    t = json.load(open(sys.argv[1])).get(\"scripts\", {}).get(\"test\")\nexcept Exception:\n    sys.exit(0)\nif isinstance(t, str) and t.strip():\n    t = t.strip()\n    if \"\\n\" in t:\n        print(\"TEST: none (multi-line test script; add it to a ## Health Stack section)\")\n    elif \"no test specified\" in t:\n        print(\"TEST: none (npm init placeholder test script)\")\n    else:\n        print(\"TEST: \" + t)\n' package.json\n[ -f pyproject.toml ] && grep -q \"pytest\" pyproject.toml 2>\u002Fdev\u002Fnull && echo \"TEST: pytest\"\n[ -f Cargo.toml ] && echo \"TEST: cargo test\"\n[ -f go.mod ] && echo \"TEST: go test .\u002F...\"\n\n# Dead code\ncommand -v knip >\u002Fdev\u002Fnull 2>&1 && echo \"DEADCODE: knip\"\n[ -f package.json ] && grep -q '\"knip\"' package.json 2>\u002Fdev\u002Fnull && echo \"DEADCODE: npx knip\"\n\n# Shell linting\ncommand -v shellcheck >\u002Fdev\u002Fnull 2>&1 && ls *.sh scripts\u002F*.sh bin\u002F*.sh 2>\u002Fdev\u002Fnull | sed -n '1p' | xargs -I{} echo \"SHELL: shellcheck\"\n)\n[ -n \"$health_found\" ] && printf '%s\\n' \"$health_found\"\n\n# One MISSING line, with its reason, per category nothing was detected for.\nhealth_missing() { printf 'MISSING: %s (%s)\\n' \"${1}\" \"${2}\"; }\nprintf '%s\\n' \"$health_found\" | grep -q '^TYPECHECK: ' || health_missing typecheck \"no tsconfig.json\"\nprintf '%s\\n' \"$health_found\" | grep -q '^LINT: ' || health_missing lint \"no biome, eslint or ruff config; no terraform, cfn-lint or actionlint target with its binary on PATH\"\nif ! printf '%s\\n' \"$health_found\" | grep '^TEST: ' | grep -vq '^TEST: none ('; then\n  test_reason=$(printf '%s\\n' \"$health_found\" | sed -n 's\u002F^TEST: none (\\(.*\\))$\u002F\\1\u002Fp' | sed -n '1p')\n  health_missing test \"${test_reason:-no scripts.test, pytest, Cargo.toml or go.mod}\"\nfi\nprintf '%s\\n' \"$health_found\" | grep -q '^DEADCODE: ' || health_missing deadcode \"knip neither on PATH nor in package.json\"\nprintf '%s\\n' \"$health_found\" | grep -q '^SHELL: ' || health_missing shell \"shellcheck not on PATH, or no *.sh in ., scripts\u002F or bin\u002F\"\n```\n\nA `TEST: none (...)` line is not a command: the test category is missing, for the\nreason in parentheses. Every `MISSING:` category is unavailable (`null` in Step 3)\nand its reason goes into the dashboard's Unavailable line.\n\nUse Glob to search for shell scripts:\n- `**\u002F*.sh` (shell scripts in the repo)\n\nAfter auto-detection, present the detected tools via AskUserQuestion:\n\n\"I detected these health check tools for this project:\n\n- Type check: `tsc --noEmit`\n- Lint: `biome check .`\n- Tests: `bun test`\n- Dead code: `knip`\n- Shell lint: `shellcheck *.sh`\n\nA) Looks right -- persist to CLAUDE.md and continue\nB) I need to adjust some tools (tell me which)\nC) Skip persistence -- just run these\"\n\nIf the user chooses A or B (after adjustments), append or update a `## Health Stack`\nsection in CLAUDE.md:\n\n```markdown\n## Health Stack\n\n- typecheck: tsc --noEmit\n- lint: biome check .\n- test: bun test\n- deadcode: knip\n- shell: shellcheck *.sh scripts\u002F*.sh\n```\n\n---\n\n## Step 2: Run Tools\n\nRun each detected tool. For each tool:\n\n1. Record the start time\n2. Run the command, capturing complete stdout and stderr in a private temporary log\n3. Record the checker's own exit code, before any parser or display command runs\n4. Record the end time\n5. Count findings from the complete log, then show its last 50 lines for the report\n\n```bash\n# Capture one tool. Per tool, set health_tool, the command line and the count pattern.\n(\n  umask 077\n  health_tool=typecheck\n  health_pattern='error TS'\n  health_capture_error() {\n    printf 'TOOL:%s ERROR:capture-%s\\n' \"$health_tool\" \"${1}\"\n    exit 125\n  }\n  health_log=$(mktemp \"${TMPDIR:-\u002Ftmp}\u002Fvibe-health.XXXXXX\") || health_capture_error log\n  trap 'rm -f -- \"$health_log\"' EXIT\n  health_start=$(date +%s) || health_capture_error timing\n  # Open the log first, so a redirection failure cannot pass for a checker result.\n  exec 3>\"$health_log\" || health_capture_error redirection\n  if tsc --noEmit >&3 2>&1; then\n    health_status=0\n  else\n    health_status=$?\n  fi\n  exec 3>&-\n  health_end=$(date +%s) || health_capture_error timing\n  # awk prints 0 for no matches, so an empty log still yields a count.\n  health_count=$(awk -v p=\"$health_pattern\" 'index($(0), p) { n++ } END { print n+0 }' \"$health_log\") || health_capture_error parsing\n  tail -50 \"$health_log\" || health_capture_error display\n  printf 'TOOL:%s EXIT:%s DURATION:%ss COUNT:%s\\n' \"$health_tool\" \"$health_status\" \"$((health_end-health_start))\" \"$health_count\"\n)\n```\n\n`EXIT` is the checker's status, never that of `tail`, `awk` or a pipe. `COUNT` comes\nfrom the whole log, not the 50 lines shown. Swap in the tool's command and the fixed\nstring that marks one finding (`error TS` for tsc; Step 3 lists the others); for a\ntool whose summary line carries the count, read it from the same log.\n\nRun tools sequentially, one block per tool (some share resources or lock files). A\nfailing checker does not stop the later ones.\n\n**SKIPPED, FAILED or ERROR — decide before and after running:**\n- **SKIPPED** — decided *before* running: the tool's binary is absent\n  (`command -v \u003Cbinary>` finds nothing, no local `node_modules\u002F.bin` entry) and no\n  `## Health Stack` entry names it. Record the reason. Only a skip redistributes weight.\n  A binary that exists only in `node_modules\u002F.bin` is present: run it as\n  `.\u002Fnode_modules\u002F.bin\u002F\u003Cbinary>`, including when a `## Health Stack` entry names it bare.\n- **FAILED** — the command ran and could not execute the check: exit 126 or 127\n  (typo, missing binary in a configured `## Health Stack` command, no permission).\n  It scores 0. A configured command that does not run is a broken check, not a\n  missing one, and must never raise the score.\n- **ERROR** — the capture itself broke (`TOOL:\u003Cname> ERROR:capture-...`: log,\n  redirection, timing, parsing or display). The category gets no score, the composite\n  is `N\u002FA — capture failed`, and no history row is written.\n\n---\n\n## Step 3: Score Each Category\n\nScore each category on a 0-10 scale using this rubric:\n\n| Category | Weight | 10 | 7 | 4 | 0 |\n|-----------|--------|------|-----------|------------|-----------|\n| Type check | 25% | Clean (exit 0) | \u003C10 errors | \u003C50 errors | >=50 errors |\n| Lint | 20% | Clean (exit 0) | \u003C5 warnings | \u003C20 warnings | >=20 warnings |\n| Tests | 30% | All pass (exit 0) | >95% pass | >80% pass | \u003C=80% pass |\n| Dead code | 15% | Clean (exit 0) | \u003C5 unused exports | \u003C20 unused | >=20 unused |\n| Shell lint | 10% | Clean (exit 0) | \u003C5 issues | >=5 issues | N\u002FA (skip) |\n\n**Parsing tool output for counts:** use the complete captured log (`COUNT` above),\nnever the displayed tail. A non-zero exit is never `CLEAN` and never 10. Exit 126 or\n127 is `FAILED` and scores 0, whatever the log says. Any other non-zero exit whose\nfull log yields no count scores 4 (as for a test runner that reports only its exit\ncode); keep its output for the details section.\n\n- **tsc:** Count lines matching `error TS` in output.\n- **biome\u002Feslint\u002Fruff:** Count lines matching error\u002Fwarning patterns. Parse the summary line if available.\n- **Tests:** Parse pass\u002Ffail counts from the test runner output. If the runner only reports exit code, use: exit 0 = 10, any other non-zero exit except 126\u002F127 = 4 (assume some failures).\n- **knip:** Count lines reporting unused exports, files, or dependencies.\n- **shellcheck:** Count distinct findings (lines starting with \"In ... line\").\n\n**Composite score:** compute it in code, never by hand. Pass every category once as\n`name=\u003Cscore>`, where the score is an integer 0-10, `null` for SKIPPED, or `error`\nfor a capture ERROR. Skipped weight is redistributed proportionally among the\nscored categories; the weights are typecheck 25%, lint 20%, test 30%, deadcode 15%,\nshell 10%.\n\n```bash\npython3 -I -c '\nimport sys\nw = {\"typecheck\": 0.25, \"lint\": 0.20, \"test\": 0.30, \"deadcode\": 0.15, \"shell\": 0.10}\ns = {}\nfor a in sys.argv[1:]:\n    k, sep, v = a.partition(\"=\")\n    if not sep or k not in w or k in s:\n        sys.exit(\"bad argument: \" + a)\n    if v not in (\"null\", \"error\") and not (v.isdigit() and int(v) \u003C= 10):\n        sys.exit(\"bad score for \" + k + \": \" + v)\n    s[k] = v\nif set(s) != set(w):\n    sys.exit(\"pass every category: \" + \" \".join(w))\nscored = [k for k in w if s[k] not in (\"null\", \"error\")]\nerrors = [k for k in w if s[k] == \"error\"]\nprint(\"CHECKED: \" + (\" \".join(scored) or \"none\"))\nprint(\"UNAVAILABLE: \" + (\" \".join(k for k in w if s[k] == \"null\") or \"none\"))\nprint(\"COVERAGE: %d\u002F%d\" % (len(scored), len(w)))\nif errors:\n    print(\"COMPOSITE: N\u002FA - capture failed (\" + \" \".join(errors) + \")\")\nelif not scored:\n    print(\"COMPOSITE: N\u002FA - no checks ran\")\nelse:\n    c = sum(int(s[k]) * w[k] for k in scored) \u002F sum(w[k] for k in scored)\n    print(\"COMPOSITE: %.1f\" % c + (\"\" if len(scored) == len(w) else \" (partial coverage)\"))\n' typecheck=10 lint=8 test=10 deadcode=7 shell=null\n```\n\nReplace the example values with this run's results. Report the `COMPOSITE` line as\nprinted; a numeric composite exists only when it prints a number.\n\n---\n\n## Step 4: Present Dashboard\n\nPresent results as a clear table:\n\n```\nCODE HEALTH DASHBOARD\n=====================\n\nProject: \u003Cproject name>\nBranch:  \u003Ccurrent branch>\nDate:    \u003Ctoday>\n\nCategory      Tool              Score   Status     Duration   Details\n----------    ----------------  -----   --------   --------   -------\nType check    tsc --noEmit      10\u002F10   CLEAN      3s         0 errors\nLint          biome check .      8\u002F10   WARNING    2s         3 warnings\nTests         bun test          10\u002F10   CLEAN      12s        47\u002F47 passed\nDead code     knip               7\u002F10   WARNING    5s         4 unused exports\nShell lint    shellcheck        10\u002F10   CLEAN      1s         0 issues\n\nCOMPOSITE SCORE: 9.2 \u002F 10\nCoverage: 5\u002F5 categories checked\nChecked: typecheck, lint, test, deadcode, shell\nUnavailable: none\n\nDuration: 23s total\nVERDICT: NOT READY (lint, deadcode exited non-zero)\n```\n\nUse these status labels:\n- 10: `CLEAN`\n- 7-9: `WARNING`\n- 4-6: `NEEDS WORK`\n- 0-3: `CRITICAL`\n- Command could not run (exit 126\u002F127): `FAILED` (scores 0)\n- Tool not available: `SKIPPED` (no score)\n- Capture broke: `ERROR` (no score; composite is N\u002FA)\n\n**Verdict line.** End the dashboard with one of:\n\n- `VERDICT: READY` only when every required check ran and exited 0 and all five\n  categories were checked. The required checks are every `## Health Stack` entry,\n  or every detected tool when the stack was auto-detected.\n- `VERDICT: NOT READY` when every required check ran and at least one exited\n  non-zero.\n- `VERDICT: INCOMPLETE` when any required check was SKIPPED, FAILED or ERROR, or\n  fewer than five categories were checked. Name each missing one. A high composite\n  does not change this: never `READY` with partial coverage.\n\nAlways show the coverage lines. With skipped categories, label the score\n`— partial coverage` (e.g. `COMPOSITE SCORE: 8.0 \u002F 10 — partial coverage`,\n`Coverage: 2\u002F5 categories checked`) and list each unavailable category with the\nreason from its Step 1 `MISSING:` line (or its SKIPPED reason), e.g.\n`Unavailable: deadcode (knip neither on PATH nor in package.json)`. When no check ran, show `COMPOSITE SCORE: N\u002FA — no checks ran` and say which\ntools need installing or configuring; never show 10\u002F10 for an empty run.\n\nIf any category scored below 7, list the top issues from that tool's output:\n\n```\nDETAILS: Lint (3 warnings)\n  biome check . output:\n    src\u002Futils.ts:42 — lint\u002Fcomplexity\u002FnoForEach: Prefer for...of\n    src\u002Fapi.ts:18 — lint\u002Fstyle\u002FuseConst: Use const instead of let\n    src\u002Fapi.ts:55 — lint\u002Fsuspicious\u002FnoExplicitAny: Unexpected any\n```\n\n---\n\n## Step 5: Persist to Health History\n\n```bash\neval \"$(\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-slug\" 2>\u002Fdev\u002Fnull)\" && mkdir -p \"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F$SLUG\"\necho \"PROJECT_DIR: ${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F$SLUG\"\n```\n\nOnly when the composite is numeric, append one JSONL line to\n`\u003CPROJECT_DIR>\u002Fhealth-history.jsonl` (where `\u003CPROJECT_DIR>` is the path the block above printed on its `PROJECT_DIR:` line). A run with no checks or a capture\nERROR writes nothing and leaves the existing history as it is:\n\n```json\n{\"ts\":\"2026-03-31T14:30:00Z\",\"branch\":\"main\",\"score\":9.2,\"typecheck\":10,\"lint\":8,\"test\":10,\"deadcode\":7,\"shell\":10,\"duration_s\":23}\n```\n\nFields:\n- `ts` -- ISO 8601 timestamp\n- `branch` -- current git branch\n- `score` -- composite score (one decimal)\n- `typecheck`, `lint`, `test`, `deadcode`, `shell` -- individual category scores (integer 0-10)\n- `duration_s` -- total time for all tools in seconds\n\nIf a category was skipped, set its value to `null`.\n\n---\n\n## Step 6: Trend Analysis + Recommendations\n\nRead the last 10 entries from `\u003CPROJECT_DIR>\u002Fhealth-history.jsonl` (if the\nfile exists and has prior entries).\n\n```bash\neval \"$(\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-slug\" 2>\u002Fdev\u002Fnull)\" && mkdir -p \"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F$SLUG\"\ntail -10 \"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fprojects\u002F$SLUG\u002Fhealth-history.jsonl\" 2>\u002Fdev\u002Fnull || echo \"NO_HISTORY\"\n```\n\n**Compare like-for-like coverage only.** For each history row, the scored set is the\ncategories with a non-null value (a missing field counts as null). Report a delta,\n`IMPROVING` or `REGRESSIONS DETECTED` only against rows whose scored set equals this\nrun's exactly. If the previous run's set differs, say **Coverage changed — scores are\nnot comparable** and label nothing an improvement or a regression; older rows may\nstill be listed with their unavailable categories marked. An N\u002FA run has no trend.\n\n**If comparable prior entries exist, show the trend:**\n\n```\nHEALTH TREND (last 5 runs)\n==========================\nDate          Branch         Score   TC   Lint  Test  Dead  Shell\n----------    -----------    -----   --   ----  ----  ----  -----\n2026-03-28    main           9.4     10   9     10    8     10\n2026-03-29    feat\u002Fauth      8.8     10   7     10    7     10\n2026-03-30    feat\u002Fauth      8.2     10   6     9     7     10\n2026-03-31    feat\u002Fauth      9.2     10   8     10    7     10\n\nTrend: IMPROVING (+1.0 since last run)\n```\n\n**If score dropped vs the previous run with identical coverage:**\n1. Identify WHICH categories declined\n2. Show the delta for each declining category\n3. Correlate with tool output -- what specific errors\u002Fwarnings appeared?\n\n```\nREGRESSIONS DETECTED\n  Lint: 9 -> 6 (-3) — 12 new biome warnings introduced\n    Most common: lint\u002Fcomplexity\u002FnoForEach (7 instances)\n  Tests: 10 -> 9 (-1) — 2 test failures\n    FAIL src\u002Fauth.test.ts > should validate token expiry\n    FAIL src\u002Fauth.test.ts > should reject malformed JWT\n```\n\n**Health improvement suggestions (always show these):**\n\nPrioritize suggestions by impact (weight * score deficit):\n\n```\nRECOMMENDATIONS (by impact)\n============================\n1. [HIGH]  Fix 2 failing tests (Tests: 9\u002F10, weight 30%)\n   Run: bun test --verbose to see failures\n2. [MED]   Address 12 lint warnings (Lint: 6\u002F10, weight 20%)\n   Run: biome check . --write to auto-fix\n3. [LOW]   Remove 4 unused exports (Dead code: 7\u002F10, weight 15%)\n   Run: knip --fix to auto-remove\n```\n\nRank by `weight * (10 - score)` descending. Only show categories below 10.\n\n---\n\n## Mode: --fix\n\n`\u002Fhealth --fix` runs the checks Step 1 found, fixes the source until they pass, and\nproves the result from the evidence ledger. It writes no score and no history row.\n\n**Checks.** Run Step 1 (a `## Health Stack` section, or auto-detection). Each\ndetected command is one check, labelled by its category (`typecheck`, `lint`,\n`test`, `deadcode`, `shell`; `lint-2` for a second command in one category). With\nno check at all, print `CHECKS: none` and stop: there is nothing to make green,\nand saying it passed would be a lie.\n\n**Consent.** The commands come from the repo and run its code. Interactive: print\nevery command, and before the first run in a repo with no earlier health record\n(`\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-evidence\" read --skill health --limit 1`\nprints `no evidence recorded`) ask with AskUserQuestion: A) run them, B) stop.\n`SESSION_KIND: spawned` or `headless`: run only when invoked as `\u002Fhealth --fix --yes`;\notherwise print `FIX: needs --yes in a spawned or headless session` and stop.\n\n**Run the checks.** One block per run; one `fix_check` line per check, with its\nlabel and its exact command as ONE single-quoted string:\n\n```bash\n_EV=${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-evidence\n_FIX_LOG=$(mktemp -d \"${TMPDIR:-\u002Ftmp}\u002Fvibe-health-fix-XXXXXXXX\") || { echo \"FIX: cannot create a log directory\"; exit 125; }\nfix_check() {\n  \"$_EV\" run --skill health --label \"${1}\" -- \"${2}\" > \"$_FIX_LOG\u002F${1}.txt\" 2>&1\n  _rc=$?\n  printf 'CHECK: %s exit=%s log=%s\\n' \"${1}\" \"$_rc\" \"$_FIX_LOG\u002F${1}.txt\"\n  if [ \"$_rc\" -ne 0 ]; then tail -50 \"$_FIX_LOG\u002F${1}.txt\"; fi\n}\nfix_check '\u003Ccheck>' '\u003Ccommand>'\n```\n\nIf a log says `vibe-evidence` itself was not found, stop: this mode proves its\nresult from the ledger, and without it there is nothing to prove.\n\nRules for every run:\n\n- **Only exit 0 is green.** Any other `exit=`, a missing line or \"mostly passed\"\n  is red, whatever the log says.\n- **Pre-existing** means red on the first run this session, before any edit. A\n  check that turns red after your edit is yours, including a test you never\n  touched that your changed source now breaks.\n- **ENV, TIMEOUT or a missing secret** is reported with the log line that shows it.\n  Never stub, mock or skip around it.\n- **Never edit a test, a checker config or a script** (`package.json` scripts,\n  lint rules, CI files, `## Health Stack`) to get green. Fix the source the check\n  exercises; a wrong test is reported, not rewritten.\n- **Stop after 5 runs**, or on the same failure twice after an attempted fix. Report what is still red and hand it to `\u002Finvestigate`.\n\n**Finish from the ledger.** Do not re-run anything; ask the ledger whether each\ncheck's exact command passed at the tree as it is now. One `fix_ready` line per\ncheck, same label and command as the last run:\n\n```bash\n_EV=${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-evidence\nTREE_NOW=$(\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-tree-hash\" 2>\u002Fdev\u002Fnull || echo unknown)\n_fix_bad=\"\"\nfix_ready() { \"$_EV\" check --skill health --label \"${1}\" --expect-cmd \"${2}\" || _fix_bad=\"$_fix_bad ${1}\"; }\nfix_ready '\u003Ccheck>' '\u003Ccommand>'\nif [ -z \"$_fix_bad\" ]; then echo \"READY at tree $TREE_NOW\"; else echo \"NOT READY:$_fix_bad\"; fi\n```\n\nReport the `READY` or `NOT READY` line as printed, with each check's `FRESH` or\n`STALE` line, the pre-existing failures, and the files you changed. An edit after\nthe last run makes it `NOT READY`: run the checks again rather than claiming it.\n\n---\n\n## Target: skills\n\n`\u002Fhealth skills` reports on the installed skills instead of the codebase: how often\neach one is used, what its description costs every session, and what to do about it.\nBoth tools are read-only.\n\n```bash\n\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-skill-usage\" --days 90\n\"${VIBESTACK_HOME:-$HOME\u002F.vibestack}\u002Fbin\u002Fvibe-context-budget\" --runtime claude --rendered \"$HOME\u002F.claude\u002Fskills\" --config-dir \"\" --top 0 --warn-only\n```\n\n`vibe-skill-usage` counts `typed` (the user typed `\u002F\u003Cname>`) and `model` (the model\ncalled the skill) from Claude Code transcripts, `nested` for the share found in\nsubagent transcripts, and `tracked` from the slash-command analytics file. Typed and\ntracked overlap — never add them. `vibe-context-budget` gives each skill's\ndescription length (`--top 0` lists every skill; `--config-dir \"\"` skips the repo\nconfig payload, which an installed copy does not have).\n\nPresent one row per installed skill:\n\n```\nSKILL HEALTH (last 90 days, claude measured; cursor, kiro, codex, copilot unmeasured)\nSkill           Typed  Model  Last seen    Desc chars  Proposal\n--------------  -----  -----  -----------  ----------  ------------------------------\nship               12     30  2026-10-09          112  Keep — used weekly, both ways\ndiagram             0      0  -                   140  Improve — never auto-invoked; description names no trigger phrase\nold-helper          0      0  -                   210  Retire — no use in 90 days on the one measured runtime and covered by \u002Fvibe\n```\n\nPropose exactly one of **Keep**, **Improve**, **Retire** or **Merge into `\u003Cskill>`**\nper skill, with a reason a reader can check without this conversation (the counts,\nthe overlapping skill, the description problem).\n\nRules:\n- **Unmeasured is not zero.** Usage on Cursor, Kiro, Codex or Copilot CLI, and in\n  transcripts the tool could not read, is unmeasured, and unmeasured never justifies Retire. Say\n  which runtimes the user works in before proposing Retire; a skill with zero Claude\n  use may be in daily use elsewhere.\n- **Retire is a proposal.** This target never deletes, moves or edits a skill. Point\n  to `\u002Fvibe-gc` for the removal itself.\n- **Merge into** names the surviving skill and what moves into it.\n- **Improve** names the change: a description that never matches how users ask, a\n  description long enough to dominate the budget, a trigger list that collides with\n  another skill.\n\n{{include lib\u002Fsnippets\u002Fcapture-learnings.md}}\nThe score itself belongs in `health-history.jsonl`, not here. What belongs here is what\nyou had to work out to produce it: a tool that needs a flag to be parseable, a category\nwhose count is misleading on this repo, a check that is slow enough to be worth skipping\non a quick pass.\n\n---\n\n## Important Rules\n\n1. **Wrap, don't replace.** Run the project's own tools. Never substitute your own analysis for what the tool reports.\n2. **Read-only outside `--fix`.** Never fix issues unless the user ran `\u002Fhealth --fix`. Present the dashboard and let the user decide.\n3. **Respect CLAUDE.md.** If `## Health Stack` is configured, use those exact commands. Do not second-guess.\n4. **Skipped is not failed.** Skip only a tool whose absence was established before running, show coverage, and redistribute weight among scored categories only. A command that ran and failed — including exit 127 — is FAILED, never SKIPPED.\n5. **Show raw output for failures.** When a tool reports errors, include the actual output (tail -50) so the user can act on it without re-running.\n6. **Trends require comparable history.** On the first scored run, say \"First health check -- no trend data yet. Run \u002Fhealth again after making changes to track progress.\" A changed coverage set or an N\u002FA run has no score delta.\n7. **Be honest about scores.** A codebase with 100 type errors and all tests passing is not healthy. The composite score should reflect reality.\n",{"name":5,"triggers":56,"description":60,"allowed-tools":61},[57,58,59],"code health check","quality dashboard","how healthy is codebase","Code quality dashboard: runs the project's type checker, linter and tests, scores health 0-10, tracks trends.\n",[47,62,63,64,65,66,67],"Read","Write","Edit","Glob","Grep","AskUserQuestion","main","777ec643a83c17a0955856b2eb7f596601709fda","skills\u002Fhealth",0,"https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack","https:\u002F\u002Fgithub.com\u002Ftimurgaleev","https:\u002F\u002Ftgaleev.com\u002F","Forward Deployed Engineer \u002F\r\nAWS Community Builder \u002F Cloud Architect ","2026-11-11 15:42:25",{"source":8,"skillName":5,"commands":78,"prompt":88,"zipAvailable":89},[79,82,85],{"tool":80,"command":81},"npx","npx skills add timurgaleev\u002Fvibestack --skill health",{"tool":83,"command":84},"bunx","bunx skills add timurgaleev\u002Fvibestack --skill health",{"tool":86,"command":87},"pnpm","pnpm dlx skills add timurgaleev\u002Fvibestack --skill health","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack\u002Fblob\u002Fmain\u002Fskills\u002Fhealth\u002FSKILL.md",true,[],[92,98,104,110,116,122],{"skillNo":93,"name":94,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":97,"publishedAt":51},"01M71GVHCCSQCCPVM8XPSPTA73","docs","https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Frender\u002F15-backtick-info-string\u002Fskills\u002Fdocs\u002FSKILL.md",[],[94],{"skillNo":99,"name":100,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":103,"publishedAt":51},"01M71GVFT1S5MRNM0AYRT2E6VQ","fences","https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Frender\u002F14-indented-tilde-fence\u002Fskills\u002Ffences\u002FSKILL.md",[],[100],{"skillNo":105,"name":106,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":109,"publishedAt":51},"01M71GVEYB54K1S83FGRNKN8CF","dirty","https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Frender\u002F10-check-with-drift\u002Fskills\u002Fdirty\u002FSKILL.md",[],[106],{"skillNo":111,"name":112,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":115,"publishedAt":51},"01M71GVDX9PMND1337C4388DK4","clean","https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Frender\u002F09-check-no-drift\u002Fskills\u002Fclean\u002FSKILL.md",[],[112],{"skillNo":117,"name":118,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":119,"categories":120,"keywords":121,"publishedAt":51},"01M71GVC3VM66F1ZQT2G8JXDR0","bare","https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Frender\u002F08-no-frontmatter\u002Fskills\u002Fbare\u002FSKILL.md",[],[118],{"skillNo":123,"name":124,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":127,"publishedAt":51},"01M71GV9T8DAYZT0PX52QX0TE1","idem","https:\u002F\u002Fgithub.com\u002Ftimurgaleev\u002Fvibestack\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Frender\u002F07-idempotent-rerender\u002Fskills\u002Fidem\u002FSKILL.md",[],[124]]