- name
- engine-review
- description
- Post-cycle engine state diagnostic. Runs the deterministic engine auditor, then frames its findings as 7-field ailment briefs with remedy paths. Phase 38 (revised S146 to consume auditor JSON instead of re-scanning sheets).
- version
- 1.1
- updated
- 2026-05-30T00:00:00.000Z
- tags
- ["engine","active"]
- effort
- medium
- disable-model-invocation
- true
# /engine-review — Engine State Diagnostic
## Purpose
After a cycle runs, identify what's broken, stuck, improving, declining, or incoherent — and produce a structured brief per ailment. **Consumers are the crons, not `/sift` / `/write-edition`** — see `/run-cycle` Step 4 "Who reads what" and §What Happens After; every `/sift` reference below is historical. Write the review for the next engine session and the civic chain: what is real, what is mechanism, what is routed to engine-debug.
This skill no longer scans sheets by hand. The deterministic detector lives in `scripts/engineAuditor.js` (Phase 38.1 + 38.7 + 38.8, S146). The skill is the **framer** — it reads the auditor's JSON output and turns each pattern into a seven-field ailment brief, anomaly triage, and a baseline-brief pass-through. Determinism stays in code; narrative framing stays here.
This is NOT a code check. `/pre-mortem` checks the code. This checks what the code produced — is the world making sense?
## Step 1 — Run the auditor
```bash
node scripts/engineAuditor.js
```
Fail loudly if exit code is non-zero. The script must produce three files in `output/` for the current cycle:
- `output/engine_audit_c{XX}.json` — ailment patterns (Phase 38.1)
- `output/engine_anomalies_c{XX}.json` — anomalies with triage paths (Phase 38.7)
- `output/baseline_briefs_c{XX}.json` — auto-generated event briefs (Phase 38.8)
If any file is missing or invalid JSON, stop. Report the failure to the user before drafting anything — a missing detector output means the framing would be guessing.
## Step 2 — Read the three JSON files
Read all three. Don't re-scan sheets. The auditor has already done the deterministic work — including, after Phase 38.2/3/4 (S146 spine step 5), the mitigator check, remedy recommendation, and Tribune framing per pattern.
**Audit JSON `patterns[]`** — each pattern carries:
- **Detection fields** (Phase 38.1): `type` (stuck-initiative / repeating-event / math-imbalance / cascade-failure / writeback-drift / production-imbalance / improvement / incoherence / anomaly), `severity`, `cyclesInState`, `affectedEntities` (citizens / neighborhoods / initiatives / councilSeats), `evidence` (sheet, rows, fields), `description`, `detectorVersion`.
- **Mitigator state** (Phase 38.2): `mitigatorState.exists`, `mitigators[]` (per-initiative effectsFiring + effectEvidence), `gap` (`no-mitigator | mitigator-stuck | mitigator-firing-but-insufficient | remedy-working`), `recommendedAction`, `ailmentCategory`.
- **Remedy path** (Phase 38.3): `remedyPath.worldSide[]` (advance-initiative / propose-new-initiative / character-intervention / council-vote / mayoral-pressure, each with type/target/action/rationale/expectedEngineEffect), and `remedyPath.techSide.bugReport` (only populated when writeback chain is structurally broken).
- **Tribune framing** (Phase 38.4): `tribuneFraming.storyHandles` (per desk: civic / business / culture / sports / letters), `tribuneFraming.threeLayerCoverage` (engine / simulation / user-actions one-liners pre-written), `suggestedFrontPage` boolean, `capabilityHooks` (literal phrases Phase 39.1 grades against).
- **Measurement** (Phase 38.5): `measurement.available`. When true: `priorCycle`, `expectedField`, `expected`, `observed`, `delta`, `verdict` (`remedy-firing-as-expected | remedy-firing-insufficient | remedy-not-firing | remedy-overshot`), `priorRemedyType`. When false: `reason` (`no-prior-audit | no-prior-match | prior-had-no-expectation`). Top-level `measurementHistory[]` aggregates these across patterns for cross-cycle learning.
**Anomalies JSON** `anomalies[]` — `triagePath` (`cover-as-story | route-to-engine-debug | suppress-until-verified`), `confidence`, `historicalContext`. On first run with no prior audit, `anomalies[]` may be empty — expected, not a failure.
**Baseline-briefs JSON** `briefs[]` — `id`, `eventClass`, `subjectIds`, `neighborhood`, `cycle`, `facts`, `threeLayerHandle`, `tier` (default `C`), `promotionHints`.
## Step 3 — Frame each ailment as a 7-field brief
For every pattern (excluding `type: 'improvement'` and `type: 'anomaly'` — those go to their own sections), produce a seven-field markdown block. **Most fields now have structured source data** from the auditor's enrichers — your job is to translate the structured fields into Tribune voice, not to discover or invent.
| Brief field | Structured source — read this first |
|---|---|
| **In-world symptom** | Open from `tribuneFraming.threeLayerCoverage.simulation`; expand into a one-line headline that names the people / neighborhood feeling it. |
| **Tech diagnosis** | `evidence.fields` (the cells that triggered the match) + `mitigatorState.mitigators[*].effectEvidence` (which engine field is or isn't moving). Translate sheet-column names into prose. |
| **Existing mitigators** | `mitigatorState.exists` + `mitigators[]`. Each mitigator has `name`, `implementationPhase`, `cyclesInPhase`, `effectsFiring`. |
| **Why working/not** | Read `mitigatorState.gap`. The four gap values map directly: `no-mitigator` = "nothing in motion to address this"; `mitigator-stuck` = "the right initiative exists but hasn't moved"; `mitigator-firing-but-insufficient` = "the program is running but the math hasn't caught up"; `remedy-working` = "the gap is closing — this is becoming an improvement story." Use the `effectEvidence.verdict` for specifics. |
| **Recommended remedy path** | `remedyPath.worldSide[]` is ordered most-likely-to-work first. List the top action; mention secondary options if `worldSide.length > 1`. Only mention `techSide.bugReport` if `techSide.triggered: true` (structural break, not institutional drag). |
| **Tribune framing brief** | `tribuneFraming.storyHandles` per applicable desk. Use the `angle` + `hookLine` from each non-null desk; cite `citizens` POP-IDs as the reporter's starting cast. The three layers are pre-written in `tribuneFraming.threeLayerCoverage` — quote them or paraphrase. |
| **Measurement plan** | `mitigatorState.mitigators[*].effectEvidence.expectedField` + `magnitudeThreshold` + `expectedSign` give the watch list. Add one cycle-over-cycle delta target per mitigator. |
If `tribuneFraming.suggestedFrontPage: true`, mark the brief with **FRONT PAGE CANDIDATE** at the top — sift uses this to seed front-page scoring.
If `tribuneFraming.capabilityHooks` is non-empty, list the hooks at the bottom of the brief — they are the literal phrases Phase 39.1's `assertHighestSeverityAilmentCoveredOnFrontPage` grades against. Sift can pass them through to the relevant reporter as required coverage tokens.
### Watch for first-run startup artifacts
The first audit run after S146 derived `cyclesInState` from each initiative's `LastUpdated` date string. That can produce surprisingly large numbers (Temescal at `cyclesInState=88` from "3/25/2026"). The number self-corrects once a second audit JSON exists to diff against. **Don't frame these inflated counts as "stuck for 88 cycles" in narrative copy.** Read `cyclesInState` qualitatively on first run ("stuck, with the design phase predating the current build of the auditor"); use the precise number from the second cycle onward.
### When the structured fields are empty
Phase 38.2 `gap: 'remedy-working'` doesn't surface until a second audit cycle exists with measurable deltas (per engine terminal's §17 acceptance note — "inherent to the cross-cycle design"). On the first run after any pattern, `effectEvidence.verdict` will commonly read `no-history`. That's expected — write the brief as "early signal, will measure next cycle" rather than treating it as a failure of the mitigator check.
## Step 4 — Anomaly triage section
For each entry in `anomalies[]`, write one paragraph:
- The anomaly (what changed, by how much)
- The triage call: cover as story / route to engine debug / suppress until verified
- One-line reasoning, citing `confidence` and `historicalContext`
If `anomalies[]` is empty, write a one-line "No anomalies flagged this cycle." Don't fabricate.
Note: routing an anomaly to engine-debug means writing a separate brief at `output/engine_anomalies_c{XX}_followup.md` flagged for the engine terminal to investigate. The anomaly is suppressed from the edition until cleared.
## Step 5 — Baseline brief pass-through
Don't re-write the baseline briefs. The auditor already structured them for sift. In the engine-review markdown, include:
- Total brief count, broken down by `byEventClass` from the JSON's `summary`
- Count with `promotionHints` (these are sift's promotion candidates)
- One-line note on which neighborhoods or active ailments the briefs cluster around
- Reference: `output/baseline_briefs_c{XX}.json`
`/sift` reads the JSON directly when deciding promote / publish-as-baseline / suppress per Phase 39.9 tiered review.
### Known limitation — EventType taxonomy
As of S146, most events in `WorldEvents_V3_Ledger` resolve to `eventType: misc-event`, so `subjectIds` on most baseline briefs is `[]`. This blocks citizen-attributed promotion (e.g., a death brief that should hint at Beverly Hayes by POP-ID can't, because the event isn't typed as `citizen-death`). Sift can still promote on neighborhood + ailment overlap from `promotionHints`, but the citizen-specific path is degraded until the engine adds a richer EventType breakdown. Tracked in ROLLOUT_PLAN as a follow-up item.
## Step 6 — Improvements section
For every `type: 'improvement'` in `patterns[]`, write one short paragraph: what's working and why. Don't bury good news. Phase 38.4 (S146 spine step 5) now also threads improvement-side handles into `tribuneFraming.storyHandles` when a positive trend has a named cause — surface those as story candidates for sift, same format as ailment briefs but tagged **IMPROVEMENT** at the top.
## Step 7 — Measurement check
The audit JSON carries measurement state directly: every pattern has a `measurement` field; the JSON has a top-level `measurementHistory[]` rollup. Don't read prior `engine_review_*.md` files — the structured fields already record what fired and what didn't.
**The normal case is mixed (G-ER7).** Past the first cycle, the auditor always carries `measurementHistory[]`, but each cycle still surfaces *new* patterns with no prior to match against. C95, for example: 3 of 7 ailments had `measurement.available: true`; 4 were new patterns this cycle with `available: false` / `reason: 'no-prior-match'`. So **render the table by default** — the genuine first-run / no-table case is the rare exception, not the lead.
Render in three parts:
1. **Per-pattern table.** Render a row for **every** pattern, not only the `available: true` ones. For `available: true`: pattern type, affected entity, prior remedy type, expected, observed, verdict. For `available: false`, gate the verdict cell on `measurement.reason`: `no-prior-match` → `—`; `prior-had-no-expectation` → `no prior expectation`. Don't omit `available: false` rows silently — a new pattern with no prior is signal, not absence. **Skip the table entirely only in the genuine first-run case**: every pattern is `available: false` AND `reason: 'no-prior-audit'` — then write the single line `First review — no prior to compare. Measurement loop will activate on next cycle.`
2. **Remedy-type track record.** Group `measurementHistory[]` by `priorRemedyType`, count verdicts (`remedy-firing-as-expected`, `remedy-firing-insufficient`, `remedy-not-firing`, `remedy-overshot`). One row per type. This is the city's multi-cycle learning signal — what kind of intervention has actually moved the world.
3. **Win callout.** If any pattern this cycle reads `verdict: 'remedy-firing-as-expected'` AND its prior-cycle entry in `measurementHistory[]` was `remedy-not-firing`, name it in voice: the gap closed — that's a story candidate, not just a data point. One line under the table.
## Output File
Write to `output/engine_review_c{XX}.md`:
```
# Engine Review — Cycle {XX}
**Cycle:** {XX} | **In-world:** Y{n}C{m}
**Auditor version:** {from JSON detectorVersions.engineAuditor}
**Source files:**
- `output/engine_audit_c{XX}.json` — {N} patterns
- `output/engine_anomalies_c{XX}.json` — {N} anomalies
- `output/baseline_briefs_c{XX}.json` — {N} briefs
## Ailments
### 1. [In-world symptom headline]
- **Tech diagnosis:** [grounded in evidence.fields]
- **Existing mitigators:** [from affectedEntities.initiatives]
- **Why working/not:** [gap analysis]
- **Remedy path:** [world-side preferred, tech-side fallback]
- **Tribune framing:** [three-layer story handles]
- **Measure next cycle:** [specific fields/milestones]
### 2. [next ailment]
...
## Anomalies
### [triage-by-triage, or "No anomalies flagged this cycle"]
## Improvements
### [what's working and why]
## Baseline Briefs (sift input)
- Total: {N} briefs ({byEventClass breakdown})
- With promotion hints: {N}
- Cluster note: {neighborhood / ailment overlaps}
- Source: `output/baseline_briefs_c{XX}.json`
## Measurement Check (from previous review)
| Pattern | Affected | Prior remedy | Expected | Observed | Verdict |
|---|---|---|---|---|---|
| [type] | [neighborhood / initiative] | [priorRemedyType] | [expected] | [observed] | [verdict] |
(First-run case: `First review — no prior to compare. Measurement loop will activate on next cycle.`)
[Win callout, if any pattern flipped from `remedy-not-firing` last cycle to `remedy-firing-as-expected` this cycle.]
### Remedy-type track record
| Remedy type | Firing-as-expected | Firing-insufficient | Not-firing | Overshot |
|---|---|---|---|---|
| [priorRemedyType] | [N] | [N] | [N] | [N] |
## Summary
- Ailments: {count} ({severity breakdown})
- Anomalies: {count} ({triage breakdown})
- Improvements: {count}
- Baseline briefs: {count} ({with-promotion-hints count})
- Measurements: {firing-as-expected count} / {total measured} firing as expected; {not-firing count} not firing
```
## Where This Sits
Step 4 in the run-cycle chain. After pre-flight, pre-mortem, and cycle execution. Before build-world-summary. World summary reads this file and incorporates the framing; sift reads the same file plus the baseline-briefs JSON for editorial planning.
## Why this rewrite (S146 — two passes)
**First pass (after Phase 38.1 + 38.7 + 38.8):** scanned 11 sheets by hand → consume audit + anomalies + baseline-briefs JSON. Detection moved out of the skill into `scripts/engineAuditor.js`. Skill became the framer.
**Second pass (after Phase 38.2 + 38.3 + 38.4, this rewrite):** even the framing logic mostly moves into structured fields. The auditor now writes `mitigatorState`, `remedyPath`, and `tribuneFraming` per pattern — which is what the skill was previously synthesizing by hand. The skill's remaining job: translate structured engine fields into Tribune voice, surface front-page candidates, and gate anomalies. Most assertions about "what to write" come from `tribuneFraming.threeLayerCoverage` directly.
Determinism in code, judgment in the skill — and the judgment surface keeps shrinking as the auditor learns. Sources: `docs/engine/archive/PHASE_38_PLAN.md` §6 (first rewrite) and §16.5 (this rewrite).
在 GitHub 查看