<!-- Modulo on-demand di /new — NON un file installato a sé. Caricato via Read dalla § "Routing" del SKILL.md core. -->

> **Modulo `/new`** — eseguilo, poi torna al core § "Routing" per la fase successiva. I `§ "..."` (Context economy, Context Tracking, Trivial-card fast-lane, Risk-signal detector, Fix Application Log) vivono nel **core SKILL.md**.

## Phase 8 — Metrics Log (MONITORING SIGNAL — non-blocking — runs after Phase 7)

After the Production Readiness Checklist is complete, log this batch run to the skill
effectiveness registry. This enables tracking of first-attempt success rate, actionability,
and QA quality over time. This is a telemetry write, NOT a gate — it never blocks the batch (see
the fail-safe note at the end of the phase). It was previously labelled "MANDATORY", but since it
has no blocking consequence the honest label is "monitoring signal".

**Steps:**

1. Read the batch tracker file (`/tmp/batch-tracker-<FIRST-CARD-ID>.md`) to extract — every field below has a NAMED upstream producer; read the structured key, do NOT re-parse prose:
   - Start timestamp (from `Started:` field)
   - Worktree-creation timestamp (from `## Worktree` — the time Phase 0/Pre-flight created the worktree; the strategy-independent cycle-time anchor, see step 2)
   - Card list and total count
   - For each completed card:
     - `fix_cycles` (from the per-phase retry counts logged in `## Completed Cards`)
     - `qa_profile`, `qa_verdict` (Phase 3.5 step 25 / D.4 QA line)
     - `qa_first_attempt` (Phase 3.5 step 25 named flag — `pass`/`fail`/`n/a`)
     - `doc_gaps: found=<N> fixed=<M>` (Phase 3 step 15 / D.4a named counter)
   - Batch-level `review_findings: total / verified / blockers` (Final Review F.5 step 13 named counters — the canonical source for `findings_total` / `findings_verified` / `blockers_count`)
   - Merge commit hash (and, when `git.merge_strategy: pr`, the PR merge timestamp recorded by Phase 6 — see step 2)

   If any named field is absent (e.g. a legacy tracker), record `0`/`n/a` for that field and append `metrics_gaps: <field>` to the JSONL row's notes — do NOT invent a value by re-parsing free-form prose.

2. Compute aggregate metrics:
   - `first_attempt_success_rate`: cards with fix_cycles == 0 / total_cards
   - `mean_fix_cycles`: mean of per-card fix cycle counts
   - `qa_profiles`: count of {skip, light, balanced, deep}
   - `qa_pass_first_attempt_rate`: cards with `qa_first_attempt == pass` / cards where qa ran (exclude `n/a` SKIP/LIGHT cards from the denominator) — read the named flag from step 1, do NOT re-infer from `## Fix Application Log` rows
   - `findings_total` / `findings_verified`: from the batch-level `review_findings` named counter (Final Review), NOT a per-card re-sum (which would conflate QA gate counts with codex review findings)
   - `actionability_rate`: findings_verified / findings_total (conservative proxy — a lower-bound on reviewer precision, since some VERIFIED findings end as NEEDS_MANUAL_CONFIRMATION and never produce a fix; 0 if no findings)
   - `severity_p0_pct`: blockers_count / findings_total (0 if no findings)
   - `cycle_time_mins`: minutes from the **strategy-independent anchor** to batch completion. Use the **worktree-creation timestamp** (Phase 0/Pre-flight) as the start, NOT `Started:` (which precedes workspace hygiene) and NOT a merge-commit hash (which varies by `git.merge_strategy` — under `pr` the merge commit is created asynchronously by GitHub CI and may not exist locally at Phase 8 time). End anchor: read the `merge_ts` field Phase 6 step 6 recorded under `## Worktree Merges` (it is already strategy-resolved there — local merge-commit time for `local-push`, PR merge completion time for `pr`). If `merge_ts: pending` (a `pr` merge GitHub has not yet finalized), fall back to "now" (`date -u`). Never recompute from a raw merge-commit hash that may be the PR HEAD rather than the actual merge commit.

   > `$METRICS` = `paths.metrics` from `baldart.config.yml` (default `docs/metrics`). All paths below resolve under `$METRICS` — never a hardcoded `docs/metrics`.

3. **Assemble the full JSONL record in memory** (single atomic append — JSONL is append-only, so the row must be COMPLETE before it touches the file). If `STATS=true` (step 6), the `"cost"` key is part of THIS record — do NOT write a baseline row now and rewrite it later (a mid-file rewrite corrupts the JSONL; there is no `sed -i`/`jq` rewrite step in this skill). When `STATS=true`, defer the single append to step 6 after the cost object is computed; when `STATS=false`, append now. The record shape:

   ```json
   {"ts":"<ISO-8601-UTC>","skill":"new","run_id":"batch-<FIRST-CARD-ID>","cards":["FEAT-XXX"],"total_cards":N,"first_attempt_success_rate":0.0,"mean_fix_cycles":0.0,"qa_profiles":{"skip":0,"light":0,"balanced":0,"deep":0},"qa_pass_first_attempt_rate":0.0,"findings_total":0,"findings_verified":0,"actionability_rate":0.0,"severity_p0_pct":0.0,"doc_gaps_found":0,"doc_gaps_fixed":0,"cycle_time_mins":0,"worktree_branch":"","merge_commit":""}
   ```

   Use `date -u +%Y-%m-%dT%H:%M:%SZ` for the timestamp. Append the assembled line ONCE with `echo '<full-record>' >> "$METRICS/skill-runs.jsonl"` via Bash — never append a partial row then mutate it.

4. Copy the batch tracker to archive:

   ```bash
   cp /tmp/batch-tracker-<FIRST-CARD-ID>.md "$METRICS/archive/"
   ```

5. Note in tracker: `## Metrics Log: WRITTEN (run_id: batch-<FIRST-CARD-ID>)`

6. **Session token telemetry (ONLY when `STATS=true`)** — if the run was invoked with `-stats` / `--stats`:

   Measure REAL per-role token + wall-clock cost by post-processing the session transcripts (zero model-token cost — runs in Bash). Invoke:

   ```bash
   node .framework/framework/scripts/analyze-session-tokens.js \
     --skill new --run-id "batch-<FIRST-CARD-ID>" --out-dir "$METRICS"
   ```

   The script reads `$CLAUDE_CODE_SESSION_ID` from the environment and derives the transcript paths itself (do NOT pass `--session` / `--project-dir` — those are test-only overrides). It writes a human-readable breakdown to `$METRICS/sessions/batch-<FIRST-CARD-ID>.md` and prints a summary plus a final `TELEMETRY_JSON=<json>` line.

   Parse the `TELEMETRY_JSON=` line and add a `"cost"` key (holding that JSON object: `by_role` + `totals` + `run_wall_ms`) to the in-memory record from step 3, then **append the complete record ONCE** to `$METRICS/skill-runs.jsonl` (this is the deferred single atomic append from step 3 — there is NO already-written row to rewrite, so JSONL semantics stay intact: exactly one line per `run_id`). If the script printed `stats: SKIPPED (...)` instead, set `"cost":{"skipped":"<reason>"}` and append the single record. Echo the script's summary to the user so they see the per-role breakdown.

**If `$METRICS/skill-runs.jsonl` does not exist**: create it first with `mkdir -p "$METRICS" && touch "$METRICS/skill-runs.jsonl"`.
**If batch tracker is missing or unreadable**: log "Metrics Log: SKIPPED (tracker not found)" and proceed without blocking.
**This phase is NON-BLOCKING** — if it fails for any reason, do not abort the run. The `-stats` step in particular is best-effort: the script is fail-safe (always exits 0), so never let it block the commit.
