Skip to content

v5.0.0: GenerateTelosSummary.ts has 3 parser bugs that drop content from auto-generated PRINCIPAL_TELOS.md #1140

Description

@jdrolls

Summary

Three independent parser bugs in PAI/TOOLS/GenerateTelosSummary.ts cause the auto-generated PRINCIPAL_TELOS.md (loaded into the DA's context at every session start via CLAUDE.md @-import) to silently miss most of the user's TELOS data.

The DA boots without seeing the user's missions, active goals, narratives, challenges, or formative experiences — even when those source files are fully populated.


Bug 1 — parseItems() only matches bullet form; standard PAI source files use header form

File: PAI/TOOLS/GenerateTelosSummary.ts, function parseItems (lines 47-59)

Symptom: Generated PRINCIPAL_TELOS.md has empty ## Missions, ## Active Goals (2026), ## Active Narratives, ## Personal Challenges, and ## Core Models sections — even when MISSION.md, GOALS.md, NARRATIVES.md, CHALLENGES.md, and MODELS.md are fully populated.

Root cause: parseItems matches only this single regex:

const match = line.match(/^-\s+\*?\*?(\w+)\*?\*?:\s*(.+)/);

That matches bullet form: - **M0**: Family Presence. But the canonical PAI source files (after a real /interview run, after /migrate, or after the user fills them in by hand) use header form:

### M0: Family Presence

Be fully present for family — attend every kids' event...

parseItems returns zero items for these files, so all five callers (parseMissions, parseGoals, parseNarratives, parseChallenges, parseModels) yield empty arrays. Note: parseProblems and parseStrategies already handle the header form correctly via their own regex — parseItems is the inconsistent one.

Proposed fix: Extend parseItems to try header form when bullet form misses:

function parseItems(content: string): ParsedItem[] {
  const items: ParsedItem[] = [];
  for (const line of content.split('\n')) {
    // Bullet: "- **M0**: text"
    const bullet = line.match(/^-\s+\*?\*?(\w+)\*?\*?:\s*(.+)/);
    if (bullet) {
      items.push({ id: bullet[1], text: bullet[2].trim() });
      continue;
    }
    // Header: "## M0: text" / "### G0: text" / "#### MO0: text"
    const header = line.match(/^#{2,4}\s+([A-Z]+\d+):\s*(.+?)(?:\s*\(.*\))?\s*$/);
    if (header) {
      items.push({ id: header[1], text: header[2].trim() });
    }
  }
  return items;
}

Bug 2 — Hardcoded {{PRINCIPAL_FULL_NAME}} placeholder in generated output

File: PAI/TOOLS/GenerateTelosSummary.ts, line 220

Symptom: Generated PRINCIPAL_TELOS.md starts with the literal string:

# Principal TELOS — {{PRINCIPAL_FULL_NAME}}

The {{PRINCIPAL_FULL_NAME}} token is never substituted because there is no substitution mechanism in this generator (it's a write, not a render). Different problem from #1135 (which is about the installer not substituting in PAI_SYSTEM_PROMPT.md); this is a generator that hardcodes a placeholder it can't resolve.

Proposed fix: Read the principal name from settings.json daidentity (or PRINCIPAL_IDENTITY.md frontmatter) at generation time:

import { readFileSync } from 'fs';
const settings = JSON.parse(readFileSync(join(process.env.HOME!, '.claude/settings.json'), 'utf-8'));
const principalName = settings.principal?.fullName || settings.principal?.name || 'Principal';
// then:
'# Principal TELOS — ' + principalName,

Or accept it as an environment variable / config injection.


Bug 3 — parseTraumas() requires ID-prefixed items but the bootstrap TRAUMAS.md uses plain titles

File: PAI/TOOLS/GenerateTelosSummary.ts, function parseTraumas (lines 189-193)

Symptom: Generated ## Formative Experiences (Traumas) section is empty even when TRAUMAS.md is fully populated.

Root cause: parseTraumas calls parseItems (which expects TR0, TR1, etc. prefixed items), but the canonical/bootstrap TRAUMAS.md uses plain titled headers:

## Major Transformations

### Religious Worldview Shift
**Age:** ~28 (after 7-year marriage)
**What happened:** ...

### Divorce
...

No TR# prefix, so parseItems returns zero, and the section in the generated summary is empty.

Proposed fix: Fall back to extracting ### Title headers and assigning synthetic TR0..TRn IDs:

function parseTraumas(): string[] {
  const content = readTelosFile('TRAUMAS.md');
  if (!content) return [];

  // Try ID-prefixed format first
  const items = parseItems(content);
  if (items.length > 0) {
    return items.map(i => `- **${i.id}**: ${truncate(i.text, 90)}`);
  }

  // Fallback: ### Title headers, assign synthetic TR0..TRn
  const headers = [...content.matchAll(/^###\s+(.+?)\s*$/gm)];
  return headers.map((m, i) => `- **TR${i}**: ${truncate(m[1].trim(), 90)}`);
}

Reproduce

# Use the bootstrap-default TRAUMAS.md and populate MISSION.md / GOALS.md with `### M0: Title` form
bun ~/.claude/PAI/TOOLS/GenerateTelosSummary.ts
cat ~/.claude/PAI/USER/TELOS/PRINCIPAL_TELOS.md
# Expect: Missions, Goals, Narratives, Challenges, Formative Experiences sections all empty
# Expect: header reads "# Principal TELOS — {{PRINCIPAL_FULL_NAME}}"

After applying the three patches above:

# Principal TELOS — <user's actual name>
## Missions
- **M0**: Family Presence
- **M1**: Financial Independence
...
## Formative Experiences (Traumas)
- **TR0**: Religious Worldview Shift
- **TR1**: Divorce
...

Impact

PRINCIPAL_TELOS.md is @-imported into the DA's system context on every session start (per CLAUDE.md). When these bugs fire, the DA boots blind to the user's missions, goals, narratives, challenges, and formative experiences — the entire point of the TELOS system. Users may not realize until they ask the DA something like "what's my current mission?" and get an unsteered answer.


Environment

  • PAI v5.0.0
  • Bun 1.3.8

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions