@nurix/etna 0.18.4 → 0.18.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "5be7cfbb369e",
2
+ "fingerprint": "a4f3a0487fe5",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "0.17.3",
5
- "epoch": 14,
4
+ "version": "0.17.4",
5
+ "epoch": 15,
6
6
  "files": [
7
7
  ".prettierrc.json",
8
8
  ".prettierignore",
9
9
  ".vscode/settings.json"
10
10
  ],
11
- "digest": "e0a34e10d625"
11
+ "digest": "f3cd8c8f9404"
12
12
  }
@@ -17,3 +17,13 @@ Shared libraries (`@nurix/components`, etc.) are generic on purpose — that's w
17
17
  - **Anchor with a collection annotation** when nothing else can: `const columns: ColumnDefinition<BriefRow>[] = [...]`.
18
18
 
19
19
  Don't repeat `fn<Type>(…)` / `<Comp<Type>>` at every call site — cosmetic only (same safety, same emitted JS).
20
+
21
+ ## Monorepo & Package Boundaries
22
+
23
+ One `tsconfig.base.json` at the repo root is the **single owner of TS policy** — the strict set above, `target`, and any option uniform across packages (here, `module`/`moduleResolution`). Each package's `tsconfig.json` is thin: it `extends` the base, then adds only its own *coordinates* (`rootDir`/`outDir`/`include`) and genuine *environment* overrides (`lib`/`types`/`jsx`, plus `module`/`moduleResolution` when a package's module system genuinely differs — a Worker, browser, or NodeNext package). That is environment, not policy. Never re-declare or weaken a *policy* option locally (no per-package `strict: false`, no widening `noUncheckedIndexedAccess`). A framework that owns its own base (e.g. an Astro app on `astro/tsconfigs/strict`) is the one allowed exception.
24
+
25
+ - **Internal packages are source-first.** A `private: true` workspace package exposes its API through the `exports` field pointing at TypeScript source (`"types"`/`"default"` → `./src/index.ts`) — consumed across the workspace with **no build step**. Prefer the `exports` field; don't add a bare `main`/`module` to new packages. Add one typed subpath per public surface (`"./nodes": { "types": "./src/nodes/index.ts", "default": "./src/nodes/index.ts" }`); consumers import the subpath, **never** a deep relative path into another package's `src/`.
26
+ - **Published packages build to `dist` — never ship raw `src`.** The moment a package crosses a publish boundary (`private: false`), it compiles with `tsc` (`declaration: true`, `declarationMap: true` from the base), ships `files: ["dist"]`, and points `exports`/`types` at `./dist/*` (use `publishConfig.exports` to swap source→dist on publish if the dev entry stays on source). `prepublishOnly` runs the build.
27
+ - **Declare host singletons as `peerDependencies`** on published libraries (`react`, `react-dom`, the framework client) so consumers keep one copy; never bundle them. Apps depend on them directly.
28
+ - **Single-source shared dep versions with pnpm `catalog:`** — `typescript`, `zod`, `vitest`, `react`, type packages. One version per dependency across the workspace so every package's types resolve identically; no per-manifest version drift.
29
+ - **Reach for TS project references only when it hurts** — adopt `references` once the typecheck/build graph is slow enough to feel. Until then, `pnpm -r typecheck` over independent `extends` configs is the correct default.
@@ -4,6 +4,13 @@ This repo evaluates **how a person steered a Claude Code session** — for inter
4
4
 
5
5
  You are the **evaluator**. The work is the `interview-eval` skill (its `rubric.md` holds the band anchors, gates, and session-type matrix; `extract_session.py` is the deterministic extractor; `TEMPLATE.md` holds the output shapes). This `CLAUDE.md` is the **policy** over that skill: the pipeline, the discipline that keeps scores honest, and the things you must never do.
6
6
 
7
+ ## Two things you can evaluate
8
+
9
+ Pick the mode from what the user hands you:
10
+
11
+ - **Session steering** (`.jsonl` transcripts) — score how the candidate drove the agent. That is the `interview-eval` skill and the pipeline below; it is the default and the bulk of this file.
12
+ - **Code-project quality** (one or more repos / paths) — score the project the candidate produced. For a *set* of projects, follow the **`code-eval-batcher`** rule (it fans the `evaluator` skill across them and ranks the result); for a single project, invoke the `evaluator` skill directly. The session-steering discipline below does not apply to this mode — the skill and the rule are self-contained.
13
+
7
14
  ---
8
15
 
9
16
  ## The one rule above all others
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "harness": "hr",
3
3
  "name": "hr",
4
- "version": "0.1.2",
4
+ "version": "0.1.3",
5
5
  "epoch": null,
6
6
  "files": [
7
7
  ".prettierrc.json",
8
8
  ".prettierignore",
9
9
  ".vscode/settings.json"
10
10
  ],
11
- "digest": "4b8f1d5de9f8"
11
+ "digest": "da038267aa11"
12
12
  }
@@ -0,0 +1,71 @@
1
+ # Code-Eval Batcher
2
+
3
+ When asked to evaluate and rank **multiple code projects** against the production-readiness + product-quality rubric — candidate take-homes, submissions, a folder of repos — you (the main agent) drive this directly. You can dispatch sub-agents in parallel; a sub-agent cannot, which is why this is a rule you perform, not an agent you delegate to.
4
+
5
+ This is distinct from `interview-eval` (which scores how a person *steered a session*). This rule scores the *project's code and product*, across many projects, and ranks them.
6
+
7
+ ## Division of labor
8
+
9
+ Per-project scoring — the rubric, gates, absolute banding, the 8 sub-criteria, every recipe — belongs to the **`evaluator` skill** (`.claude/skills/evaluator/`). Do not re-derive or re-band it here. This rule owns only what the skill deliberately doesn't: **enumeration, parallel dispatch, weighting, and the cross-project rollup.** Bands are absolute — never re-band a project against the others; only weight and normalize.
10
+
11
+ ## Procedure
12
+
13
+ | # | Step |
14
+ | --- | --- |
15
+ | 0 | **Preflight once:** `bash .claude/skills/evaluator/preflight.sh`. Halt on non-zero and report the missing tools — do not score with degraded signals. |
16
+ | 1 | **Enumerate** the projects → a list of local paths, one slug each (its dir name). Choose an `out_dir` (default `./code-eval-out`). |
17
+ | 2 | **Dispatch in parallel — rolling window of 4.** For each project, `Agent(subagent_type="general-purpose", run_in_background=True, …)` instructing it to invoke the `evaluator` skill on that one project path, write the workspace to `<out_dir>/<slug>/`, validate it (`python3 .claude/skills/evaluator/validate_scorecard.py <out_dir>/<slug>/scorecard.yaml`), and return the workspace path + the scorecard's `normalized`. Fire a replacement the moment any slot frees so 4 are always in flight. |
18
+ | 3 | **Collect** every `<out_dir>/*/scorecard.yaml`. Re-dispatch any that fail validation once; on a second failure mark the project `VALIDATION_FAILED` and continue. |
19
+ | 4 | **Weight + normalize** every project (math below) using the weights block. |
20
+ | 5 | **Rollup** → `<out_dir>/results/{scorecard.csv, ranking.md, summary.md}`, sorted by rank score desc. Echo the exact weights + normalization used into `summary.md` so the ranking is reproducible. Leave everything uncommitted. |
21
+
22
+ A single project does **not** need this rule — invoke the `evaluator` skill directly.
23
+
24
+ ## Weighting math
25
+
26
+ For each project `p`, read the raw 0–5 `score` of each sub-criterion `s` from its `scorecard.yaml`:
27
+
28
+ 1. **Weighted (0–100)** over sub-criteria with a non-null band and `weight > 0`:
29
+ `weighted_p = 100 × Σ_s (band_{p,s} / 5 × weight_s) / Σ_s weight_s`
30
+ 2. **Gate penalty:** `adjusted_p = max(0, weighted_p − Σ_{g ∈ gates_triggered_p} gate_penalties[g])`.
31
+ 3. **Normalize across the set** `{adjusted_p}` per `normalization`:
32
+ - `percent_of_max` → `adjusted_p` unchanged (absolute; comparable across runs).
33
+ - `min_max` → `100 × (adjusted_p − min) / (max − min)`; if `max == min`, all = 100.
34
+ - `z_score` → `clamp(0, 100, 50 + 10 × (adjusted_p − mean) / sd)`; if `sd == 0` or n < 2, fall back to `adjusted_p`.
35
+
36
+ `VALIDATION_FAILED` projects are excluded from min/max/mean/sd and listed separately. A null sub-criterion drops out of that project's weighted average — don't impute a 0. Compute with a short throwaway script over the scorecards; round reported scores to 2 decimals.
37
+
38
+ `scorecard.csv` columns: `rank, project, <8 bands>, gates, short_circuited, evaluator_total, weighted, penalty, rank_score`. `ranking.md`: a ranked table headed with the normalization method (the sort key). `summary.md`: n scored, the weights/penalties/normalization used, top-5 / bottom-5, gate frequency, any `VALIDATION_FAILED`.
39
+
40
+ ## Weights — edit this block to change the ranking
41
+
42
+ These are the only cross-project knobs. Edit the values to re-rank; the math above consumes them verbatim. (Edits here are overwritten if you re-install the harness — to persist a custom weighting, drop a `code-eval-batcher.weights.json` with the same shape in your `out_dir` and read that instead.)
43
+
44
+ ```json
45
+ {
46
+ "weights": {
47
+ "code_quality": 12.5,
48
+ "testing": 12.5,
49
+ "deployment_readiness": 12.5,
50
+ "maintainability": 12.5,
51
+ "problem_substantiveness": 12.5,
52
+ "ux_mechanics": 12.5,
53
+ "value_articulation": 12.5,
54
+ "mechanical_ambition": 12.5
55
+ },
56
+ "gate_penalties": {
57
+ "DEMO": 0, "BROKEN": 0, "UNTESTED": 0, "TESTS_TRIVIAL": 0,
58
+ "SECRETS": 0, "VULN": 0, "AUTH": 0, "OBSERVABILITY": 0
59
+ },
60
+ "normalization": "percent_of_max"
61
+ }
62
+ ```
63
+
64
+ - **`weights`** — relative importance of each sub-criterion; any non-negative numbers (normalized to sum 100 over the present, weighted ones). Set one to `0` to drop that sub-criterion. Defaults (all 12.5) reproduce the evaluator's own equal-weight total.
65
+ - **`gate_penalties`** — flat 0–100-scale points docked per triggered gate, on top of the band caps the evaluator already applied. Default 0 = rely only on the evaluator's caps. E.g. `SECRETS: 15` docks 15 from any project with a committed secret.
66
+ - **`normalization`** — `percent_of_max` (absolute, default) · `min_max` (spreads a tight set; not comparable across runs) · `z_score` (distance from the set mean).
67
+
68
+ ## Discipline
69
+
70
+ - **No silent fallback.** A malformed weights block, a failed preflight, or a scorecard that won't validate is reported loudly — never worked around.
71
+ - **You weight; the evaluator bands.** Changing a project's band because of how others scored is the cohort-relative anti-pattern this design removed. Your only cross-project levers are the weights block.
@@ -0,0 +1,208 @@
1
+ ---
2
+ name: evaluator
3
+ description: Apply a generic production-readiness + product-quality rubric to a single software project. Takes a repo reference + an output directory, writes a per-project workspace (`<out_dir>/<project>/` containing `README.md`, `scorecard.yaml`, and a flat `raw/` directory where every file is named `<sub-criterion>-<artifact>.ext` — e.g. `raw/code-quality-semgrep-quality.json`, `raw/maintainability-deptry.json`), and returns the workspace path. The scorecard is a YAML with 8 sub-criterion blocks (4 Code + 4 Product, 12.5 pts each = 100 pts), a 0–5 score + evidence + data per item, gates triggered, and `total` / `available` / `normalized`. Bands are **absolute** per-signal thresholds declared in each recipe. The skill scores exactly one project and is unaware of any caller, cohort, or cross-project comparison. Triggers on "evaluate this project", "score this repo against the rubric", "run the evaluator on <path>".
4
+ ---
5
+
6
+ # Evaluator
7
+
8
+ Single-project evaluator. One project in → one per-project workspace out. The caller supplies the output directory; the skill writes `<out_dir>/<project>/{README.md, scorecard.yaml, raw/<sub-criterion>-<artifact>.ext}` and returns that path.
9
+
10
+ This skill scores **exactly one project**. It does not rank, normalize across projects, weight sub-criteria differently, or know anything about a batcher, a cohort, or other projects. Every band it assigns is **absolute** — computed from per-signal thresholds in each recipe — so the scorecard for a project is identical whether the project is scored alone or as one of fifty. Cross-project weighting and comparison, if wanted, is a separate concern layered on top by whoever calls this skill; the skill itself stays unaware of it.
11
+
12
+ ## Preflight (run once per host, idempotent)
13
+
14
+ This skill depends on external tools (`gitleaks`, `lizard`, `scc`, `semgrep`, `hadolint`, `ruff`, `biome`, `knip`, `deptry`, `shellcheck`, `golangci-lint`, `gh`, `jq`, `pip-audit`, `govulncheck`, `trivy`). Before scoring, run [`preflight.md`](preflight.md). It checks each tool, installs any that are missing (via `brew` / `npm -g` / `pipx` / `go install`), and fails loud if an install fails. **Do not silently fall back to grep-or-LLM-only substitutes** — that poisons the gate checks (VULN / SECRETS / AUTH / OBSERVABILITY) and makes the scorecard dishonest. If preflight exits non-zero, return a clear failure message naming the missing tools.
15
+
16
+ ## Hard rule: every slow command gets a timeout
17
+
18
+ Every command the skill runs that can hang or burst must be wrapped in a bounded wall-clock guard. Unbounded retries are how a single project eats 20+ minutes. Apply one of:
19
+
20
+ - `timeout 60 <cmd>` for any `gh api`, `semgrep`, `npm install`, `pip install`, `go build`, `go vet`, or `curl` — even when the recipe doesn't show it. If the recipe shows a naked command, wrap it yourself.
21
+ - `--maxdepth N` and `-not -path '*/node_modules/*' -not -path '*/.venv/*' -not -path '*/.git/*'` on every `find`.
22
+ - `git log -n 500` (or similar ceiling) instead of walking full history. Never `git log --all` without a limit.
23
+ - `semgrep`: always pass `--timeout 30` (per-rule), `--disable-version-check`, `--metrics=off`. Avoid `--config p/code-quality` — that registry entry 404s in retry-storm; use per-language packs (`p/python`, `p/typescript`, `p/javascript`, `p/go`) or `p/default`.
24
+ - Dep-vuln scans: `pip-audit --timeout 120`, `govulncheck` in a 180 s `timeout`, `trivy image --timeout 180s`. `npm audit --json` doesn't take a flag; wrap the whole call in `timeout 120`.
25
+
26
+ If a single mechanical command still exceeds ~2 min, something is wrong with the project (giant `node_modules/`, generated bundle, vendored deps). Skip it with `_NOT_RUN: true, reason: "timeout on <cmd>"` rather than retrying — a sparse signal is tolerable; a hang is not.
27
+
28
+ ## Input
29
+
30
+ A repo reference + an output directory:
31
+
32
+ - **Local clone path** — e.g. `/path/to/project/`. Used by filesystem reads (Semgrep, grep, file sampling, git analytics). This is the primary input.
33
+ - **GitHub ref** (optional) — e.g. `<org>/<repo>`. Used by `gh api` calls for PR / code-review history when the project is hosted on GitHub. Absence just means those signals are skipped, not a failure.
34
+ - **`out_dir`** — caller-supplied base directory. The skill writes into `<out_dir>/<project>/` and returns that path. The skill creates the folder; `raw/` is flat — one file per signal named `<sub-criterion>-<artifact>.ext`, no subdirectories.
35
+
36
+ `<project>` is a stable slug for the project (its directory name is fine). Optional baseline stats (kLoC, commit count, contributor count) may be supplied to skip a pre-pass; otherwise derive them from the local clone.
37
+
38
+ ## Output — per-project workspace
39
+
40
+ Every invocation writes a folder at `<out_dir>/<project>/` with two parts plus raw dumps:
41
+
42
+ ```
43
+ <out_dir>/<project>/
44
+ ├── README.md # Human-readable narrative. Shape: § "README shape" below.
45
+ ├── scorecard.yaml # Structured scorecard (below). Machine-readable.
46
+ └── raw/ # Flat directory — every file named `<sub-criterion>-<artifact>.ext`
47
+ ├── code-quality-* # llm-sample.jsonl, lint-{ruff,biome,eslint,golangci}.json, semgrep-quality.json, cr-comments.jsonl
48
+ ├── testing-* # tests-inventory.jsonl, llm-sample.jsonl, semgrep-resilience.json, gha-runs.json, auth-scan-{http,ws,grpc,trpc}.json
49
+ ├── deployment-readiness-* # artifacts.json, hadolint.json, localhost-refs.txt, observability-markers.json, llm-read.md
50
+ ├── maintainability-* # gitleaks.json, lizard.csv, knip.json|deptry.json|gomod-diff.txt, file-sizes.txt, tech-debt.txt, dep-vuln-{pip-audit,npm-audit,govulncheck,trivy}.json, llm-read.md
51
+ ├── problem-substantiveness-* # llm-read.md, proxies.json
52
+ ├── ux-mechanics-* # path-detection.json + path-specific files (ux-heuristics, routes, cli-probes, shellcheck, openapi-quality, api-surrogate), llm-read.md
53
+ ├── value-articulation-* # llm-read.md, proxies.json
54
+ └── mechanical-ambition-* # scc.json, stubs.json, git-analytics.json, tier-a-build.log, llm-read.md
55
+ ```
56
+
57
+ `raw/` is a **flat directory** — no subfolders. Every file is named `<sub-criterion>-<artifact>.ext` (e.g. `code-quality-semgrep-quality.json`, `maintainability-deptry.json`). A signal that couldn't be extracted (tool absent, language not present, repo shape mismatch) produces **no file** — absence is the signal, and `scorecard.yaml` flags the gap. Per-recipe artifact lists are authoritative in each recipe's "Raw dumps" section.
58
+
59
+ ### Scorecard shape
60
+
61
+ ```yaml
62
+ project: my-project
63
+
64
+ # Run metadata (also rendered into README.md)
65
+ commit_sha_pinned: 7423b5c... # HEAD at extraction — audit only
66
+ rubric_version: 1 # bump when sub-criteria / gates / banding change (see "Rubric version" below)
67
+ scored_at: 2026-06-15T23:10:00Z # ISO 8601 UTC timestamp of this run
68
+ agent_model: claude-opus-4-8 # model that executed this pass (or null)
69
+ approx_tokens: 92000 # rough total tokens consumed (or null)
70
+
71
+ # 1.x — Production-Grade Code (12.5 pts each)
72
+ code_quality: { score: 4, evidence: "...", data: { ... } }
73
+ testing: { score: 2, evidence: "...", data: { ... } }
74
+ deployment_readiness: { score: 3, evidence: "...", data: { ... } }
75
+ maintainability: { score: 3, evidence: "...", data: { ... } }
76
+
77
+ # 2.x — Product Thinking (12.5 pts each)
78
+ problem_substantiveness: { score: 4, evidence: "...", data: { ... } }
79
+ ux_mechanics: { score: 4, evidence: "...", data: { ... } }
80
+ value_articulation: { score: 3, evidence: "...", data: { ... } }
81
+ mechanical_ambition: { score: 3, evidence: "...", data: { ... } }
82
+
83
+ gates_triggered: [UNTESTED, OBSERVABILITY]
84
+ short_circuited: false # true when ≥3 gates stack; see "Dead-project short-circuit"
85
+ scored_sub_criteria: 8 # items with a non-null score
86
+ total_sub_criteria: 8 # 4 Code + 4 Product
87
+
88
+ total: 66.25 # Σ (score × 2.5) over scored items; max 100
89
+ available: 100.0 # max points from currently-scored items (lower if any item is null)
90
+ normalized: 66.25 # total ÷ available × 100
91
+ ```
92
+
93
+ **Each item scored 0–5** → points = `score × 2.5` (every item is weighted 12.5 pts, so the step is 2.5). `total` is the sum over scored items; `available` is the max points those scored items could have reached (12.5 × count); `normalized = total ÷ available × 100`. With all 8 items scored, `available == 100.0` and `total == normalized`. If a sub-criterion is genuinely unscoreable (`score: null`), it drops out of both `total` and `available` so the percentage stays honest.
94
+
95
+ The `total` here is an **equal-weight** convenience score for reading one project on its own. The skill does not apply per-sub-criterion weights — it has no opinion on relative importance. A caller that wants weighted, cross-project-comparable scores reads the raw 0–5 `score` fields and applies its own weighting; that logic lives entirely outside this skill.
96
+
97
+ ### Workspace-writing order
98
+
99
+ Write the outputs in this order — each depends on the prior:
100
+
101
+ 1. **`raw/<sub-criterion>-<artifact>.ext`** — run each recipe (see table below), dump raw tool output verbatim to the flat `raw/` directory with per-recipe filename prefixes. This is the extraction pass.
102
+ 2. **`scorecard.yaml`** — fold each recipe's aggregates into the sub-criterion block, apply gates, compute `total` / `available` / `normalized`. Pin `commit_sha_pinned` (audit metadata) and `rubric_version`.
103
+ 3. **`README.md`** — render the README shape below with values drawn from `scorecard.yaml` and pointers to `raw/` artifacts. The raw-dump section is a pointer index, not a re-embedded dump.
104
+
105
+ ### README shape
106
+
107
+ `README.md` is a self-contained human narrative — no external template. Render these sections from `scorecard.yaml`:
108
+
109
+ 1. **Title + verdict** — `# <project> — <normalized>/100` and a 2–3 sentence overall read.
110
+ 2. **Run metadata** — `rubric_version`, `scored_at`, `agent_model`, `commit_sha_pinned`, gates triggered, short-circuit flag.
111
+ 3. **Scorecard table** — one row per sub-criterion: `| Sub-criterion | Band (0–5) | Points / 12.5 | One-line evidence |`, split into a Code block and a Product block, with the `total / available / normalized` footer.
112
+ 4. **Per-sub-criterion detail** — for each item, the band, the full `evidence` string, and the key numbers from `data`.
113
+ 5. **Raw artifact index** — a pointer list of the `raw/<sub-criterion>-*` files that were produced (and a note on any that were skipped and why).
114
+
115
+ ### Rubric version
116
+
117
+ `rubric_version` is a simple integer marker recorded on each scorecard. Bump it whenever a recipe adds/drops a signal, a gate threshold moves, a banding table changes, or the scorecard shape changes. It exists so a reader can tell which rubric produced a scorecard and so a caller can decide whether a cached workspace is stale. It is **not** a cohort cache key and carries no cross-project semantics — one project, one rubric version, decided at extraction time.
118
+
119
+ ## Scoring model
120
+
121
+ - **100 points total, 50 per half:**
122
+ - **1.x Production-Grade Code** — 4 sub-criteria × 12.5 pts each = **50 pts**
123
+ - **2.x Product Thinking** — 4 sub-criteria × 12.5 pts each = **50 pts**
124
+ - **Each item scored 0–5** → points = `score × 2.5`.
125
+ - **Scorecard reports `total`, `available`, `normalized`.**
126
+
127
+ ### Bands (absolute)
128
+
129
+ | Band | Meaning |
130
+ | ----- | --------------------------------------------------------------- |
131
+ | **5** | Excellent — strong across all recipe criteria, no material gaps |
132
+ | **4** | Strong — strong on most criteria, one minor gap |
133
+ | **3** | Solid — meets criteria in basic form, material gaps present |
134
+ | **2** | Weak — thin execution, multiple criteria missing or shallow |
135
+ | **1** | Token effort — minimal execution, most criteria absent |
136
+ | **0** | Absent |
137
+
138
+ Bands are **absolute, not relative to any other project**. Per-signal numeric thresholds live in each recipe's _Banding_ section. The rubric is single-project-invokable by design — there is no cohort pass, no quintile math, no re-banding step.
139
+
140
+ ### Gates (cap specific items; stack additively)
141
+
142
+ | Gate | Trigger | Effect |
143
+ | --- | --- | --- |
144
+ | `DEMO` | (`has_container_artifact == false` AND `has_any_deploy_artifact == false` AND `readme_has_run_section == false` AND `llm_has_run_section == false`) OR (README claims a deploy artifact — Dockerfile / compose / k8s / target — that does not exist in the tree) | `deployment_readiness ≤ 1` |
145
+ | `BROKEN` | Tier-A build check fails (`ruff` / `tsc --noEmit --skipLibCheck` / `go vet` / `py_compile`) — NOT on `DEPS_NOT_INSTALLED` errors | `mechanical_ambition ≤ 2` |
146
+ | `UNTESTED` (hard) | `test_file_count == 0` | `testing ≤ 1` |
147
+ | `TESTS_TRIVIAL` (soft) | Tests exist but `assertion_density < 1.0` per test file OR `trivial_body_ratio > 0.8` | `testing ≤ 2` |
148
+ | `SECRETS` | `gitleaks_findings ≥ 1` — any secret in git history, even rotated out of HEAD | `maintainability ≤ 2` |
149
+ | `VULN` | Dep-scan reports ≥ 1 CVE at CRITICAL OR ≥ 3 at HIGH across any dep (direct or transitive). Tools: `pip-audit` / `npm audit --json` / `govulncheck` / `trivy image`. Skipped if no recognized package manifest. | `maintainability ≤ 2` |
150
+ | `AUTH` | Network-routed handlers exist AND ≥ 1 mutating endpoint is not behind an auth middleware/dependency/decorator. Covers HTTP (FastAPI/Express/Flask/Gin/Django POST/PUT/PATCH/DELETE), WebSocket (ws/Socket.io/SignalR event handlers), gRPC (grpc/grpcio unary/streaming), tRPC (@trpc/server mutations). Skipped if no routed handlers detected. | `testing ≤ 2` |
151
+ | `OBSERVABILITY` | Structured-logging library absent (no structlog / winston / pino / log-slog / zap AND no JSON log format detected in code) **AND** metrics/tracing library absent (no OpenTelemetry, Prometheus client, Datadog agent, statsd client, or custom `/metrics` endpoint). Both must be absent for the gate to fire. | `deployment_readiness ≤ 3` |
152
+
153
+ Gates cap the **band** of the named sub-criterion. When multiple gates cap the same item, the lowest cap wins. Gates are recorded in `gates_triggered`; a reader (or a downstream caller) can see exactly which caps applied.
154
+
155
+ ### Dead-project short-circuit
156
+
157
+ If **3 or more gates** fire on one project, skip the interpretive LLM passes for all sub-criteria, emit a fixed floor scorecard, and set `short_circuited: true`:
158
+
159
+ - **Non-gate-capped items**: `score: 1, evidence: "Floor — 3+ gates triggered; interpretive pass skipped"`.
160
+ - **Gate-capped items**: take the cap value (e.g. `deployment_readiness: 1` when DEMO fires, `testing: 1` when UNTESTED fires, `maintainability: 2` when SECRETS or VULN fires, `deployment_readiness: 3` when OBSERVABILITY fires). If multiple gates cap the same item, use the lowest cap.
161
+ - **Totals**: `total`, `available`, `normalized` computed normally over the floored items.
162
+
163
+ Rationale: a project that's undeployable + untested + broken is clearly dead. Running the expensive interpretive pass produces generous LLM hallucinations on dead code. Short-circuit saves compute and keeps the score honest.
164
+
165
+ ### CodeRabbit — calibrating modifier with null-data semantics
166
+
167
+ If the project's GitHub history includes CodeRabbit review comments, they act as a **calibrating modifier on 1.1 (Code Quality) and 1.2 (Testing)**, never a primary signal.
168
+
169
+ - When a project has CR data (`cr_pr_count > 0` **and** non-empty actionables): `cr_*_per_kloc` values feed the recipe's banding table as calibrating modifiers.
170
+ - When a project has no CR data (no CR history, or walkthrough-only with `cr_actionable_total == 0`): every `cr_*_per_kloc` is emitted as `null`. The scorer treats `null` as a no-op; 1.1 / 1.2 bands are decided by the remaining signals.
171
+
172
+ Design principle: `null ≠ 0`. `null` means "no CR data, skip the modifier"; `0` means "CR reviewed the PR and found nothing" — a real signal pulling the band up. Projects not hosted on GitHub, or with no PRs, simply emit `null` and the modifier is a no-op.
173
+
174
+ ## Sub-criterion extraction recipes
175
+
176
+ One recipe file per sub-criterion. All 8 are AI-scored from repo evidence.
177
+
178
+ | # | Item | Half | Contender | Recipe |
179
+ | --- | --- | --- | --- | --- |
180
+ | 1.1 | Code Quality | Code | LLM interpretive (primary) + lint (`ruff` / `biome` / `golangci-lint`) + `lizard` (per-language CCN: Py 10 / JS-TS 10 / Java 12 / Go 15) + `semgrep` quality packs + CodeRabbit (calibrating modifier) | [criteria/code-quality.md](criteria/code-quality.md) |
181
+ | 1.2 | Testing & Resilience | Code | Filesystem + GHA + Semgrep + LLM (load-bearing) + multi-protocol AUTH route-scan (HTTP/WS/gRPC/tRPC); CodeRabbit (calibrating modifier) | [criteria/testing.md](criteria/testing.md) |
182
+ | 1.3 | Deployment-Readiness | Code | Filesystem + Hadolint + observability markers + LLM | [criteria/deployment-readiness.md](criteria/deployment-readiness.md) |
183
+ | 1.4 | Maintainability | Code | gitleaks + lizard (per-language) + knip/deptry + dep-vuln scan (pip-audit/npm audit/govulncheck/trivy) + LLM | [criteria/maintainability.md](criteria/maintainability.md) |
184
+ | 2.1 | Problem substantiveness | Product | LLM-primary README + sample data + target-user read. Detects toy-problem patterns. | [criteria/problem-substantiveness.md](criteria/problem-substantiveness.md) |
185
+ | 2.2 | UX mechanics | Product | 3-path: web-UI (UX heuristics + routes) / CLI (clig.dev + shellcheck) / API-only (OpenAPI quality + versioning + idempotency + rate-limiting + input validation — **no cap, backends score up to 5**) + shared LLM UX read | [criteria/ux-mechanics.md](criteria/ux-mechanics.md) |
186
+ | 2.3 | Value articulation | Product | README parse for quantified impact claims + scope coverage ratio | [criteria/value-articulation.md](criteria/value-articulation.md) |
187
+ | 2.4 | Mechanical ambition | Product | scc + Semgrep stubs + git analytics + Tier-A build + LLM README-vs-shipped + COCOMO | [criteria/mechanical-ambition.md](criteria/mechanical-ambition.md) |
188
+
189
+ ## Workflow
190
+
191
+ The skill scores one project end-to-end:
192
+
193
+ 1. **Preflight** — run [`preflight.md`](preflight.md). Halt with a clear error if it fails.
194
+ 2. **Mechanical extraction** — per recipe, run the shell commands under each recipe's "Extraction" section. Dump every tool output **verbatim** to the flat `<out_dir>/<project>/raw/` directory using the `<sub-criterion>-<artifact>.ext` naming convention. A signal that can't be extracted produces **no file** — absence is the signal.
195
+ 3. **LLM interpretive passes** — per recipe, run the LLM reads (sample files, score axes, README parse, shipped-vs-promised diff). Write `raw/<slug>-llm-sample.jsonl` or `raw/<slug>-llm-read.md` per sub-criterion. These are the load-bearing interpretive judgments.
196
+ 4. **Apply gates** — evaluate each gate (DEMO / BROKEN / UNTESTED / TESTS_TRIVIAL / SECRETS / VULN / AUTH / OBSERVABILITY) using the triggers above. If **≥3 gates** fire, emit the dead-project short-circuit floor scorecard (`short_circuited: true`) and skip remaining interpretive passes.
197
+ 5. **Emit `scorecard.yaml`** — apply each recipe's absolute-threshold band table directly. Record the run-metadata block (`commit_sha_pinned`, `rubric_version`, `scored_at` via `date -u +"%Y-%m-%dT%H:%M:%SZ"`, `agent_model`, `approx_tokens`). Compute `total` / `available` / `normalized`.
198
+ 6. **Render `README.md`** — fill the README shape above with values from `scorecard.yaml` and pointer references to `raw/` artifacts.
199
+ 7. **Return the workspace path** — `<out_dir>/<project>/`. Write the files and leave them uncommitted; whoever invoked the skill decides whether to commit.
200
+
201
+ ### Scorecard schema validator
202
+
203
+ [`validate_scorecard.py`](validate_scorecard.py) — a Python script that asserts a `scorecard.yaml` has every required top-level field (including `total` / `available` / `normalized`), all 8 sub-criterion blocks, and `score` + `evidence` + `data` on each. Exits 0 on valid; exits 1 on first failure with a specific error on stderr. Run it after emitting a scorecard to catch shape errors early. **Extending:** if a scorecard-shape bump introduces a new required field, append it to `REQUIRED_TOP` in the validator.
204
+
205
+ ## Not in scope
206
+
207
+ - **Cross-project comparison, weighting, or ranking.** The skill emits raw 0–5 bands and an equal-weight `total`. Anything that compares projects, applies non-uniform weights, or normalizes a set against itself is a layer above this skill and must not leak into it.
208
+ - **Runtime verification.** The skill reads the repo statically. Actually running `docker compose up` / `npm run dev` and clicking through the product is a separate manual pass a reviewer may add on top; the skill does not drive it.