Files
accounted/scripts/__tests__/atom-discovery.test.ts
T
Jakob Wennberg ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests,
and a few UI strings, reading as AI-generated boilerplate rather than
house style. Replaced each with punctuation matching its context: colon
for explanatory clauses, comma for asides, plain hyphen for numeric/legal
ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for
paired-dash asides. messages/en.json and messages/sv.json were fixed by
hand together to keep sv/en in sync.

Left untouched where the dash is the functional subject rather than
decorative punctuation: date-range-parser.ts's separator regex,
charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE
encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the
agent system-prompt files that already instruct against em dashes, and
a golden iXBRL test fixture compared byte-for-byte.

Also fixes two bugs surfaced along the way: an off-by-one in
ApiKeysPanel's scope-label split (a leftover from an earlier partial
pass), and a charset-repair test that had lost the literal en-dash it
exists to verify.

Regenerated the agent atom seed migration (skills:generate) since 27
SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes,
with an explicit carve-out for the functional-dash cases above.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 15:58:06 +02:00

63 lines
2.5 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { discoverAtoms, type DiscoveredAtom } from '../lib/atom-discovery'
// Runs against the real .claude/skills tree (deterministic, committed content).
// Vitest's cwd is the repo root.
let atoms: DiscoveredAtom[]
async function load(): Promise<DiscoveredAtom[]> {
if (!atoms) atoms = await discoverAtoms(process.cwd())
return atoms
}
describe('discoverAtoms: reference children', () => {
it('emits top-level skills with parent_atom_id null', async () => {
const all = await load()
const vat = all.find((a) => a.id === 'horizontal/swedish-vat')
expect(vat, 'swedish-vat skill should be discovered').toBeDefined()
expect(vat!.parent_atom_id).toBeNull()
})
it('emits one child atom per references/*.md, linked to its parent', async () => {
const all = await load()
const child = all.find(
(a) => a.id === 'horizontal/swedish-vat/vat-compliance-reference',
)
expect(child, 'reference child should be discovered').toBeDefined()
expect(child!.parent_atom_id).toBe('horizontal/swedish-vat')
expect(child!.tier).toBe('horizontal')
// Child body is the raw reference file (its own heading), not the SKILL.md.
expect(child!.body).toContain('# Swedish VAT (Moms) Complete Compliance Reference')
expect(child!.estimated_tokens).toBeGreaterThan(0)
})
it('appends a Loadable references footer to parents that have references', async () => {
const all = await load()
const vat = all.find((a) => a.id === 'horizontal/swedish-vat')!
expect(vat.body).toContain('## Loadable references')
// The footer bridges the router filename to the loadable child id.
expect(vat.body).toContain(
'gnubok_load_skill("horizontal/swedish-vat/vat-compliance-reference")',
)
})
it('does not put the footer inside reference children themselves', async () => {
const all = await load()
for (const a of all.filter((x) => x.parent_atom_id !== null)) {
expect(a.body, `child ${a.id} should not carry the footer`).not.toContain(
'## Loadable references',
)
}
})
it('every child parent_atom_id resolves to a real top-level skill', async () => {
const all = await load()
const topLevelIds = new Set(all.filter((a) => a.parent_atom_id === null).map((a) => a.id))
const children = all.filter((a) => a.parent_atom_id !== null)
expect(children.length).toBeGreaterThan(0)
for (const c of children) {
expect(topLevelIds.has(c.parent_atom_id!), `${c.id}${c.parent_atom_id}`).toBe(true)
}
})
})