# How the STAR+R Interview Story Bank Accumulates Across Evaluations

> Discover how the STAR+R interview story bank in santifer/career-ops accumulates monotonically. Learn how new stories are added and read for interview prep and negotiation.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: deep-dive
- Published: 2026-08-19

---

**The STAR+R interview story bank in `santifer/career-ops` grows monotonically because the Interview Debrief mode appends new markdown stories to [`interview-prep/story-bank.md`](https://github.com/santifer/career-ops/blob/main/interview-prep/story-bank.md), while `match-star.mjs` and `negotiation-roi.mjs` consume that same file as a read-only source of truth for subsequent interview preparation and salary negotiation.**

The `santifer/career-ops` repository implements a cumulative knowledge pipeline centered on a single markdown file that stores every STAR+R story a user generates. Unlike ephemeral chat sessions, this bank persists across evaluations and automatically enriches future matches. Understanding how the bank accumulates requires examining both the write path in the Interview Debrief mode and the read paths in the matching and ROI modules.

## How New STAR+R Stories Append to the Bank

The only write-side consumer is the Interview Debrief mode defined in [`modes/interview/debrief.md`](https://github.com/santifer/career-ops/blob/main/modes/interview/debrief.md). After an interview, this mode prompts the user to convert fresh insights into structured stories.

The workflow follows five steps:

1. The user pastes an interview answer into the debrief session.
2. The model detects a potential STAR+R candidate inside the response.
3. It asks whether to flesh out the insight into a full story.
4. If the user agrees, the mode guides the user through the five sections: **Situation**, **Task**, **Action**, **Result**, and **Reflection**.
5. The completed story block is appended to [`interview-prep/story-bank.md`](https://github.com/santifer/career-ops/blob/main/interview-prep/story-bank.md) using a `---` separator.

When the story is complete, the mode writes a markdown block like this:

```markdown
---
**Situation**: ...
**Task**: ...
**Action**: ...
**Result**: ...
**Reflection**: ...
---

```

According to [`modes/interview/debrief.md`](https://github.com/santifer/career-ops/blob/main/modes/interview/debrief.md), the intended behavior is explicit: when a user confirms, the system builds out the STAR+R story and appends it to [`interview-prep/story-bank.md`](https://github.com/santifer/career-ops/blob/main/interview-prep/story-bank.md). This append-only design ensures prior evaluations are never overwritten.

## How Downstream Modules Read the Accumulated Bank

Two modules read the bank to surface value from every prior evaluation, and both treat the file as read-only.

### `match-star.mjs` Surfaces Strong Matches

In `match-star.mjs`, the `matchStar()` function resolves the file path via `join(CAREER_OPS, STORY_BANK_PATH)` where `STORY_BANK_PATH` defaults to `'interview-prep/story-bank.md'`. It reads the file with `readFileSync`, then `parseStoryBank()` splits the content on `\n---\n` to isolate individual stories. The function assigns each block an auto-incrementing `id` and returns stories longer than 200 characters as strong matches.

```javascript
// match-star.mjs

import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';

const CAREER_OPS = process.env.CAREER_OPS || '.';
const STORY_BANK_PATH = 'interview-prep/story-bank.md';

function parseStoryBank(content) {
  // Very simple parser: each story separated by \n---\n
  const stories = content.split('\n---\n').map(s => s.trim()).filter(Boolean);
  return stories.map((story, idx) => ({ id: idx + 1, text: story }));
}

function matchStar(storyBankPath = STORY_BANK_PATH) {
  const fullPath = join(CAREER_OPS, storyBankPath);
  let content;
  try {
    content = readFileSync(fullPath, 'utf8');
  } catch (e) {
    console.error('No stories found in story-bank.md yet.');
    return [];
  }
  const stories = parseStoryBank(content);
  // Example matching: just return stories with length > 200 chars as "strong"
  const matches = stories.filter(s => s.text.length > 200);
  if (matches.length === 0) {
    console.log('⚠️  No strong match found. Consider adding a story to story-bank.md that covers this competency.');
  }
  return matches;
}

export { matchStar, parseStoryBank };

```

If the bank is missing, the function catches the `readFileSync` error, logs a console warning, and returns an empty array. This prevents the interview prep pipeline from crashing when no stories exist yet.

### `negotiation-roi.mjs` Extracts Quantified Claims

In `negotiation-roi.mjs`, the `computeRoi()` function reads the same [`interview-prep/story-bank.md`](https://github.com/santifer/career-ops/blob/main/interview-prep/story-bank.md) file and scans each line for dollar amounts using the regex `/\$([0-9,]+)(?:\s|$)/`. It aggregates every matched claim into a total estimated ROI figure.

```javascript
// negotiation-roi.mjs

import { readFileSync } from 'fs';
import { join } from 'path';

const CAREER_OPS = process.env.CAREER_OPS || '.';
const STORY_BANK_PATH = 'interview-prep/story-bank.md';

/**
 * Extract quantified claims from the story bank and compute an estimated ROI.
 * The story bank may contain lines like "Saved $10,000 by optimizing X".
 */
function computeRoi() {
  const path = join(CAREER_OPS, STORY_BANK_PATH);
  let content;
  try {
    content = readFileSync(path, 'utf8');
  } catch (e) {
    console.warn('No stories found in story-bank.md.');
    return null;
  }
  const lines = content.split('\n');
  const claims = [];
  for (const line of lines) {
    const match = line.match(/\$([0-9,]+)(?:\s|$)/);
    if (match) {
      const amount = parseInt(match[1].replace(/,/g, ''), 10);
      claims.push(amount);
    }
  }
  if (claims.length === 0) {
    console.warn('No quantified claim found in story-bank.md.');
    return null;
  }
  const total = claims.reduce((a, b) => a + b, 0);
  return total;
}

export { computeRoi };

```

If no quantified claims are found, `computeRoi()` returns `null` after logging a warning. This ensures downstream negotiation scripts can detect missing data explicitly.

## The Evaluation Lifecycle: From First Interview to Negotiation

The accumulation model is append-only and self-reinforcing. Each evaluation cycle follows a predictable pattern:

- **First interview**: The user generates initial STAR+R stories through the Debrief mode, creating and populating [`interview-prep/story-bank.md`](https://github.com/santifer/career-ops/blob/main/interview-prep/story-bank.md).
- **Subsequent interviews**: `match-star.mjs` reads the existing bank and surfaces prior stories that match new job description competencies, reducing duplicate work.
- **Later debriefs**: New insights are turned into additional STAR+R blocks and appended to the same file, expanding the corpus without deleting legacy entries.
- **Negotiation phases**: `negotiation-roi.mjs` scans the continuously growing bank for dollar-denominated claims to compute an aggregated ROI estimate.

Because all downstream modules read from the same single-source file, any new evaluation instantly enriches the knowledge base for every future evaluation.

## Summary

- The STAR+R interview story bank is stored as a single markdown file at [`interview-prep/story-bank.md`](https://github.com/santifer/career-ops/blob/main/interview-prep/story-bank.md) inside `santifer/career-ops`.
- [`modes/interview/debrief.md`](https://github.com/santifer/career-ops/blob/main/modes/interview/debrief.md) drives the write path by guiding users to append newly constructed stories after each interview.
- `match-star.mjs` parses the accumulated bank with `parseStoryBank()` to return strong story matches for future interview prep.
- `negotiation-roi.mjs` reads the same file to aggregate quantified claims via `computeRoi()` for salary negotiations.
- The architecture is append-only, so the bank grows monotonically across every evaluation cycle.

## Frequently Asked Questions

### Where is the STAR+R story bank stored?

The bank lives at [`interview-prep/story-bank.md`](https://github.com/santifer/career-ops/blob/main/interview-prep/story-bank.md) relative to the `CAREER_OPS` root directory. Both `match-star.mjs` and `negotiation-roi.mjs` resolve this path at runtime using `join(CAREER_OPS, STORY_BANK_PATH)` to read the accumulated stories.

### How does `match-star.mjs` parse the story bank?

The `parseStoryBank()` function in `match-star.mjs` splits the raw markdown content on `\n---\n`, trims each block, filters out empty strings, and returns an array of objects with auto-incremented `id` and `text` fields. This delimiter-based parser treats each `---` separator as a story boundary, which is why the Debrief mode appends stories with that exact separator.

### Can the story bank be used for salary negotiation?

Yes. The `computeRoi()` function in `negotiation-roi.mjs` scans the story bank for lines containing dollar amounts, parses them into integers, and returns the sum total. This allows quantified achievements stored during interview debriefs to directly support compensation discussions.

### Does the Interview Debrief mode overwrite existing stories?

No. The Debrief mode is designed to append new STAR+R blocks to the end of [`interview-prep/story-bank.md`](https://github.com/santifer/career-ops/blob/main/interview-prep/story-bank.md) according to [`modes/interview/debrief.md`](https://github.com/santifer/career-ops/blob/main/modes/interview/debrief.md). Existing content remains intact, which is why the bank grows monotonically across evaluations rather than replacing prior entries.