ec27228a8e
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>
63 lines
2.5 KiB
TypeScript
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)
|
|
}
|
|
})
|
|
})
|