Postmortem Autobiographer
1. System Architecture & Prerequisites
Every incident deserves a review, but blank-page postmortems never get written.
The Autobiographer takes raw material — an incident description, a stack trace
paste, timestamps, slack snippets — and composes a structured, blame-free
document following industry conventions (Google SRE model): summary, timeline,
impact, root cause via 5-why, and action items with owners and deadlines.
Node 18+ stdlib.
Input formats recognized:
- Stack traces (V8, Python, generic) — service/exception extracted
- Free-form incident notes (timestamps
YYYY-MM-DD HH:MM, @owner, action)
- Structured JSON
{ title, started, ended, summary, services[] }
2. Input/Output Data Contracts
Input: a text file and optional starter JSON. Output POSTMORTEM.md (plus raw
postmortem.json) with sections:
# Postmortem: <title> + status (DRAFT)
## Summary / ## Timeline / ## Impact
## Root Cause (5-Why) — five chained why rows
## Action Items — - [ ] checklist with owner: and by: metadata
## Prevention Notes
Exit: 0 always on successful generation; 2 if no usable material.
3. Production Reference Implementation
// postmortem-autobiographer.js
const fs = require('fs');
const path = require('path');
const inputFile = process.argv[2];
const title = process.argv[3] || 'Incident Review';
if (!inputFile) { console.error('usage: node postmortem-autobiographer.js <incident.txt|incident.json> [title]'); process.exit(2); }
const raw = fs.readFileSync(inputFile, 'utf-8');
function parseStack(raw) {
const lines = raw.split(/\r?\n/);
const exception = lines.find((l) => /(Error|Exception|Traceback|failed|FATAL)/i.test(l))?.trim() || 'Unknown exception';
const frames = lines.filter((l) => /^\s+at\s|^\s+File\s"/.test(l)).slice(0, 8).map((l) => l.trim());
const first = frames[0] || exception;
return { exception, firstFrame: first, frames };
}
function parseNotes(raw) {
const timeline = [];
const re = /(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?::\d{2})?)/g;
let m;
while ((m = re.exec(raw))) timeline.push({ time: m[1] });
const owners = [...new Set((raw.match(/@(\w+)/g) || []).map((o) => o.slice(1)))];
const actions = (raw.match(/^(?:-\s*\[\s*\]|action:?)\s*(.+)$/gim) || []).map((a) => a.replace(/^-\s*\[\s*\]\s*/, '').replace(/^action:?\s*/i, ''));
return { timeline, owners, actions };
}
let input = { title, summary: '', body: raw };
try { const j = JSON.parse(raw); input = { ...input, ...j }; } catch {}
const st = parseStack(raw);
const notes = parseNotes(raw);
// Craft 5-whys from the evidence (heuristic chain, human confirms each level).
const whys = [];
const problem = input.summary || st.exception || 'Unexpected failure in production';
whys.push(`1. **Why did ${problem} occur?** — Direct cause observed: ${st.firstFrame || 'no frame captured; see timeline'}.`);
whys.push('2. **Why was the direct cause possible?** — Guard/validation missing or ordering wrong (verify against code).');
whys.push('3. **Why was the guard missing?** — Assumption made earlier; not encoded as a test/budget/boundary check.');
whys.push('4. **Why was the assumption not encoded?** — Change-management/review gap; refactor without characterization coverage.');
whys.push('5. **Why did that persist?** — Missing feedback loop (alerting/detection) surfaced it only after user impact.');
const actionItems = (notes.actions.length ? notes.actions : [
'Add characterization test reproducing the failure path',
'Add alert/budget gate so a recurrence trips before user impact',
]).map((a, i) => {
const owner = notes.owners[i % Math.max(1, notes.owners.length)] || 'TBD';
const by = new Date(Date.now() + 5 * 86400000).toISOString().slice(0, 10);
return `- [ ] ${a} \`owner: ${owner}\` \`by: ${by}\``;
});
const timelineMd = notes.timeline.length
? notes.timeline.map((t, i) => `- \`${t.time}\` step ${i + 1} (event recorded in notes)`).join('\n')
: '- (no timestamps recovered — reconstruct from logs)';
const md = [
`# Postmortem: ${input.title}`, '',
`**Status:** DRAFT · **Severity:** pending`, '',
'## Summary', '', input.summary || 'Production impact event(s) detected; see timeline and evidence.', '',
'## Timeline', '', timelineMd, '',
'## Impact', '', '- Affected service/function: to be filled', '- User-visible impact: to be filled', '- Metrics (error rate/latency): to be filled', '',
'## Root Cause (5-Why)', '', ...whys, '',
'## Evidence', '', `- First frame: \`${st.firstFrame}\``, ...st.frames.slice(0, 4).map((f) => `- Frame: \`${f}\``), '',
'## Action Items', '', ...actionItems, '',
'## Prevention Notes', '', '- Verify 5-whys line 2 against the actual guard', '- Link monitoring dashboards + runbook', '- Set severity and owner; schedule review',
].join('\n');
fs.writeFileSync('POSTMORTEM.md', md);
fs.writeFileSync('postmortem.json', JSON.stringify({ title: input.title, problem, owners: notes.owners, actions: actionItems }, null, 2));
console.log(`Postmortem written: POSTMORTEM.md (${actionItems.length} action items; owners: ${notes.owners.join(', ') || 'TBD'})`);
process.exit(0);
4. Execution Protocol & Step-by-Step Workflow
- Collect the incident material into one file: stack trace dump, timestamps,
@owner tags, and any explicit action lines.
node postmortem-autobiographer.js incident.txt "API latency spike Sep 12".
- Review the generated
POSTMORTEM.md and fill the Impact placeholders —
the tool extracts what it can extract; market impact must come from humans.
- Walk the 5-whys chain with the actual guard in code: level 2 is the load
bearing one — verify which check was missing by reading the diff/deployment.
- Assign owners to every action item, blast it in the channel, track closed
items in the review, and move the status from DRAFT once resolved.
- Store the final
POSTMORTEM.md alongside the team's incident reviews;
link it from the monitoring dashboard.
5. Edge Cases & Error Handling
- No timestamps recovered → the timeline section politely says so and asks for
reconstruction, instead of fabricating events.
- No owners/actions recovered → defaults to
TBD owner and a 5-day by date,
but the empty-places force human completion rather than silent GTM.
- Malformed JSON input falls back to treating the text as free-form notes; the
document is still generated.
- Multi-error stack traces keep only the first exception for the "problem"
line but list extra frames as evidence.
- The 5-whys are hypotheses the team must verify — the report is designed for
a group review, not as a finished accountability document.
1---2name: postmortem-autobiographer3description: Turn an incident description or production stack trace into a structured postmortem with timeline, 5-whys, and trackable action items — ready for team review.4metadata:5 source: skills/postmortem-autobiographer/postmortem-autobiographer.md6---78# Postmortem Autobiographer910## 1. System Architecture & Prerequisites1112Every incident deserves a review, but blank-page postmortems never get written.13The Autobiographer takes raw material — an incident description, a stack trace14paste, timestamps, slack snippets — and composes a structured, blame-free15document following industry conventions (Google SRE model): summary, timeline,16impact, root cause via 5-why, and action items with owners and deadlines.17Node 18+ stdlib.1819Input formats recognized:2021- Stack traces (V8, Python, generic) — service/exception extracted22- Free-form incident notes (timestamps `YYYY-MM-DD HH:MM`, `@owner`, `action`)23- Structured JSON `{ title, started, ended, summary, services[] }`2425## 2. Input/Output Data Contracts2627Input: a text file and optional starter JSON. Output `POSTMORTEM.md` (plus raw28`postmortem.json`) with sections:2930- `# Postmortem: <title>` + status (DRAFT)31- `## Summary` / `## Timeline` / `## Impact`32- `## Root Cause (5-Why)` — five chained why rows33- `## Action Items` — `- [ ]` checklist with `owner:` and `by:` metadata34- `## Prevention Notes`3536Exit: `0` always on successful generation; `2` if no usable material.3738## 3. Production Reference Implementation3940```js41// postmortem-autobiographer.js42const fs = require('fs');43const path = require('path');4445const inputFile = process.argv[2];46const title = process.argv[3] || 'Incident Review';47if (!inputFile) { console.error('usage: node postmortem-autobiographer.js <incident.txt|incident.json> [title]'); process.exit(2); }48const raw = fs.readFileSync(inputFile, 'utf-8');4950function parseStack(raw) {51 const lines = raw.split(/\r?\n/);52 const exception = lines.find((l) => /(Error|Exception|Traceback|failed|FATAL)/i.test(l))?.trim() || 'Unknown exception';53 const frames = lines.filter((l) => /^\s+at\s|^\s+File\s"/.test(l)).slice(0, 8).map((l) => l.trim());54 const first = frames[0] || exception;55 return { exception, firstFrame: first, frames };56}5758function parseNotes(raw) {59 const timeline = [];60 const re = /(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?::\d{2})?)/g;61 let m;62 while ((m = re.exec(raw))) timeline.push({ time: m[1] });63 const owners = [...new Set((raw.match(/@(\w+)/g) || []).map((o) => o.slice(1)))];64 const actions = (raw.match(/^(?:-\s*\[\s*\]|action:?)\s*(.+)$/gim) || []).map((a) => a.replace(/^-\s*\[\s*\]\s*/, '').replace(/^action:?\s*/i, ''));65 return { timeline, owners, actions };66}6768let input = { title, summary: '', body: raw };69try { const j = JSON.parse(raw); input = { ...input, ...j }; } catch {}7071const st = parseStack(raw);72const notes = parseNotes(raw);7374// Craft 5-whys from the evidence (heuristic chain, human confirms each level).75const whys = [];76const problem = input.summary || st.exception || 'Unexpected failure in production';77whys.push(`1. **Why did ${problem} occur?** — Direct cause observed: ${st.firstFrame || 'no frame captured; see timeline'}.`);78whys.push('2. **Why was the direct cause possible?** — Guard/validation missing or ordering wrong (verify against code).');79whys.push('3. **Why was the guard missing?** — Assumption made earlier; not encoded as a test/budget/boundary check.');80whys.push('4. **Why was the assumption not encoded?** — Change-management/review gap; refactor without characterization coverage.');81whys.push('5. **Why did that persist?** — Missing feedback loop (alerting/detection) surfaced it only after user impact.');8283const actionItems = (notes.actions.length ? notes.actions : [84 'Add characterization test reproducing the failure path',85 'Add alert/budget gate so a recurrence trips before user impact',86]).map((a, i) => {87 const owner = notes.owners[i % Math.max(1, notes.owners.length)] || 'TBD';88 const by = new Date(Date.now() + 5 * 86400000).toISOString().slice(0, 10);89 return `- [ ] ${a} \`owner: ${owner}\` \`by: ${by}\``;90});9192const timelineMd = notes.timeline.length93 ? notes.timeline.map((t, i) => `- \`${t.time}\` step ${i + 1} (event recorded in notes)`).join('\n')94 : '- (no timestamps recovered — reconstruct from logs)';9596const md = [97 `# Postmortem: ${input.title}`, '',98 `**Status:** DRAFT · **Severity:** pending`, '',99 '## Summary', '', input.summary || 'Production impact event(s) detected; see timeline and evidence.', '',100 '## Timeline', '', timelineMd, '',101 '## Impact', '', '- Affected service/function: to be filled', '- User-visible impact: to be filled', '- Metrics (error rate/latency): to be filled', '',102 '## Root Cause (5-Why)', '', ...whys, '',103 '## Evidence', '', `- First frame: \`${st.firstFrame}\``, ...st.frames.slice(0, 4).map((f) => `- Frame: \`${f}\``), '',104 '## Action Items', '', ...actionItems, '',105 '## Prevention Notes', '', '- Verify 5-whys line 2 against the actual guard', '- Link monitoring dashboards + runbook', '- Set severity and owner; schedule review',106].join('\n');107108fs.writeFileSync('POSTMORTEM.md', md);109fs.writeFileSync('postmortem.json', JSON.stringify({ title: input.title, problem, owners: notes.owners, actions: actionItems }, null, 2));110console.log(`Postmortem written: POSTMORTEM.md (${actionItems.length} action items; owners: ${notes.owners.join(', ') || 'TBD'})`);111process.exit(0);112```113114## 4. Execution Protocol & Step-by-Step Workflow1151161. Collect the incident material into one file: stack trace dump, timestamps,117 `@owner` tags, and any explicit action lines.1182. `node postmortem-autobiographer.js incident.txt "API latency spike Sep 12"`.1193. Review the generated `POSTMORTEM.md` and fill the `Impact` placeholders —120 the tool extracts what it can extract; market impact must come from humans.1214. Walk the 5-whys chain with the actual guard in code: level 2 is the load122 bearing one — verify which check was missing by reading the diff/deployment.1235. Assign owners to every action item, blast it in the channel, track closed124 items in the review, and move the status from DRAFT once resolved.1256. Store the final `POSTMORTEM.md` alongside the team's incident reviews;126 link it from the monitoring dashboard.127128## 5. Edge Cases & Error Handling129130- No timestamps recovered → the timeline section politely says so and asks for131 reconstruction, instead of fabricating events.132- No owners/actions recovered → defaults to `TBD` owner and a 5-day `by` date,133 but the empty-places force human completion rather than silent GTM.134- Malformed JSON input falls back to treating the text as free-form notes; the135 document is still generated.136- Multi-error stack traces keep only the first exception for the "problem"137 line but list extra frames as evidence.138- The 5-whys are *hypotheses* the team must verify — the report is designed for139 a group review, not as a finished accountability document.