name: nw-density-resolution-contract description: "Shared density-resolution contract for wave skills. Canonical detail on the D12 cascade, density resolver call, ad-hoc override workflow, and DocumentationDensityEvent telemetry emission. Referenced from nw-discover / nw-discuss / nw-design / nw-devops / nw-distill / nw-deliver." user-invocable: false disable-model-invocation: true
Density Resolution Contract (shared across wave skills)
This skill is the canonical source for the density-aware behaviour every wave skill must implement. Wave skills inherit it by reference and keep a brief inline summary so phrase-grep contract tests stay green; full detail lives here so the same boilerplate is not duplicated six times.
Provenance: feature lean-wave-documentation — D2 (schema-typed sections), D4 (telemetry instrumented day-one), D6 (first-install pedagogical prompt), D10 (one-line expansion descriptions), D12 (rigor cascade), DDD-5 (density resolver shared utility), DDD-6 (telemetry event lives in DES domain).
Output Tiers (per D2 + D10)
Each wave emits a single feature-delta.md whose headings are typed [REF] (always emitted) or [WHY]/[HOW] (lazy expansions). Tier-1 is the always-on baseline; Tier-2 is the lazily-rendered expansion catalog. The .feature file (DISTILL) and other machine artifacts remain the SSOT for executable content; the wave-delta sections are pointers + structured summaries.
- Tier-1 [REF] — emitted under
## Wave: <NAME> / [REF] <Section>headings on every run. Wave-specific list of[REF]sections lives in each wave skill. - Tier-2 EXPANSION CATALOG — NOT emitted by default. Rendered only when explicitly requested via
--expand <id>(DDD-2) or via the wave-end interactive prompt whenexpansion_prompt = "ask". Each item has a one-line description (per D10) so the menu fits in a single render. Each emitted Tier-2 section is headed## Wave: <NAME> / [WHY] <Section>or## Wave: <NAME> / [HOW] <Section>. The catalog itself is wave-specific.
Density resolution (per D12)
Before emitting any Tier-1 section, resolve the active documentation density:
- Read
~/.nwave/global-config.json. Treat missing/malformed config as empty dict (fall back to defaults). - Call
resolve_density(global_config)fromscripts/shared/density_config.py. The function returns aDensityvalue object with fieldsmode("lean"|"full"),expansion_prompt("ask"|"always-skip"|"always-expand"|"smart"), andprovenance(the cascade branch that produced this result). - Branch on
density.mode:lean→ emit ONLY Tier-1[REF]sections. Do NOT auto-render Tier-2 items.full→ emit Tier-1[REF]sections PLUS all Tier-2 expansion items rendered under their[WHY]/[HOW]headings. This is auto-expansion (no menu).
- At wave end, branch on
density.expansion_prompt:"ask"→ present the expansion menu (Tier-2 catalog with one-line descriptions per D10) and append user-selected items as## Wave: <NAME> / [WHY|HOW] <Section>headings."always-skip"→ no menu, no extra sections (idempotent re-runs, CI mode)."always-expand"→ equivalent tomode = "full"for this run; auto-render every Tier-2 item."smart"→ out of scope for v1 (per OQ-3); treat as"ask"until heuristic is empirically tuned.
The resolver itself encodes the D12 cascade: explicit documentation.density override > rigor.profile mapping (lean→lean, standard→lean+ask, thorough→full, exhaustive→full+all-expansions, custom→lean+ask) > hard default lean+ask. Wave skills MUST NOT replicate the cascade locally — call resolve_density(global_config) and trust its output.
Section heading prefix convention (per D2): every emitted section starts with ## Wave: <NAME> / [REF] <Section> for Tier-1; ## Wave: <NAME> / [WHY] <Section> or ## Wave: <NAME> / [HOW] <Section> for Tier-2. Validator scripts/validation/validate_feature_delta.py enforces the regex ^## Wave: \w+ / \[(REF|WHY|HOW)\] .+$ on every wave heading.
Ad-hoc override — user request mid-session
Even when density.mode = "lean" and density.expansion_prompt = "always-skip", the user may ask DURING the wave session for specific expansions:
- "expand jtbd" / "expand jtbd-narrative" / "more on jtbd"
- "add alternatives considered"
- "show migration playbook"
- "tell me why" (interpretive — append the WHY rationale section relevant to the most recent decision)
- "more on
" (where <X>is one of the expansion catalog items for this wave)
When the user makes such a request:
- Append the corresponding
[WHY]or[HOW]section tofeature-delta.mdunder the current wave's heading. - Emit a
DocumentationDensityEventwithchoice="expand"andexpansion_id=<the requested item>toJsonlAuditLogWriter. - Do NOT modify
~/.nwave/global-config.json. The override is ONE-SHOT for this wave only.
If the user's request matches NO item in the wave's Expansion Catalog, respond with the catalog list (one-line description per item per D10) and ask for clarification — do NOT improvise an expansion outside the catalog.
Telemetry (per D4 + DDD-6)
Every expansion choice — whether the user expanded an item or skipped the menu — emits a structured event to the existing JsonlAuditLogWriter driven adapter.
Event type: DocumentationDensityEvent (dataclass at src/des/domain/telemetry/documentation_density_event.py).
Schema fields (per D4) — substitute the active wave name in the wave field:
{
"feature_id": "<feature-id>",
"wave": "<WAVE-NAME>",
"expansion_id": "<id-from-catalog-or-'*'-for-skip-all>",
"choice": "skip" | "expand",
"timestamp": "<ISO-8601 datetime>"
}
Emission pattern:
- Construct a
DocumentationDensityEvent(feature_id=..., wave="<WAVE>", expansion_id=..., choice=..., timestamp=...). - Call
event.to_audit_event()to convert to the openAuditEventshape (event_type="DOCUMENTATION_DENSITY"and the schema fields nested underdata). - Dispatch via
JsonlAuditLogWriter().log_event(audit_event).
The wave-skill harness invokes the helper scripts/shared/telemetry.py:write_density_event(...) which performs all three steps. Wave skills MUST NOT bypass the helper or write JSONL directly — every density telemetry event flows through the shared helper to keep the audit-log schema consistent.
When to emit:
- One event per user choice in the expansion menu when
expansion_prompt = "ask"(choice = "expand"for selected items,choice = "skip"withexpansion_id = "*"if the user skips the entire menu). - One synthetic
choice = "skip"event withexpansion_id = "*"whenexpansion_prompt = "always-skip"(records the skipped menu opportunity). - One
choice = "expand"event per Tier-2 item rendered whenmode = "full"orexpansion_prompt = "always-expand".
This telemetry feeds the propagation success metric: when downstream waves consume a lean upstream feature-delta and produce no --expand, the [REF] baseline plus machine artifacts is sufficient.
How wave skills reference this contract
Each wave skill keeps a short inline summary that:
- Names the wave (e.g.
## Wave: DISTILL / [REF] <Section>) and lists its own Tier-1 sections + Tier-2 expansion catalog (wave-specific content). - Re-states the
## Density resolutionheading + the resolver call + thelean/fullmode branches +expansion_promptkey + the path~/.nwave/global-config.json+ the resolver pathscripts/shared/density_config.py(phrase-grep contract). - Re-states the
## Telemetryheading +DocumentationDensityEvent+JsonlAuditLogWriter+to_audit_event+ the schema fields (feature_id,expansion_id,choice,timestamp) + the wave-specific"wave": "<WAVE>"literal (phrase-grep contract). - Cites provenance decisions:
D2,D4,D10,D12,DDD-5,DDD-6. - References this shared skill for the full how-to instead of duplicating the explanation.
The contract phrases are pinned by tests/des/unit/skills/test_wave_skills_density_aware.py; any wave skill missing a phrase fails that test. The shared skill is the source of truth for the BEHAVIOUR; the wave skills are the source of truth for the WAVE-SPECIFIC content (Tier-1 list, Tier-2 catalog, wave heading prefix).