Files
accounted/extensions/general/mcp-server/__tests__/to-summary.test.ts
T
Jakob WennbergandClaude Opus 4.8 43007fa869 feat(mcp): self-describing agent surface — staging _meta, company identity, clean skill summaries (#775)
* feat(mcp): make the agent surface self-describing (staging _meta, company identity, clean summaries)

A pass over the MCP server's agent-facing surface so an agent can act
correctly without parsing description prose:

- Machine-readable staging contract: deriveToolMeta() attaches _meta to
  tools/list (and search detail=full) — { requires_approval, approve_tool,
  preflight? } — keyed off the STAGED_OPERATION_SCHEMA output schema. Literal
  _meta (e.g. UI widget hints) wins on collision. TOOL_PREFLIGHT_MAP names the
  read-only pre-flight for the few writes that have one (year-end readiness,
  VAT validate, depreciation proposal). Guarded by staging-meta.test.ts.
- Company identity in gnubok_get_agent_briefing: returns a `company` block
  (id, name, org_number, entity_type, accounting_method) so the agent can
  confirm WHICH entity it operates on and pick the right settlement account
  (accrual = credit 1510; cash = debit 19xx) before any write. Best-effort —
  a missing row never blocks the briefing. Covered by agent-briefing.test.ts.
- toSummary(): trims the long, keyword-stuffed SKILL.md frontmatter into clean
  one-liners for gnubok_list_skills / gnubok_get_agent_briefing so the client
  never truncates one mid-sentence; full bodies stay in gnubok_load_skill.
  Covered by to-summary.test.ts.
- bank-reconciliation skill: a match/link decision tree (what you have x
  whether a verifikat exists) and kontant- vs faktureringsmetoden settlement
  accounts.
- Prose/description clarifications: "Stages"/"Stages for approval" on the
  link tools; propose_dispositioner/accruals note there is no dedicated MCP
  poster; server-info documents _meta and the legacy gnubok_ tool prefix.

All 34 touched MCP tests pass. Merged cleanly on top of #759/#760 (server.ts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mcp): make accounting_method description state the full settlement posting

Review (swedish-accounting-compliance): the agent-briefing schema described
accrual as "credit 1510 on payment", which reads as a one-sided entry. Spell
out both sides (payment debits 19xx AND credits 1510) so an agent can't infer a
single-leg posting that violates BFL 5 kap double-entry. Mirrors the precision
already in the bank-reconciliation skill body. Payload-size guard still passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:42:12 +02:00

56 lines
2.5 KiB
TypeScript

/**
* Unit tests for toSummary — trims the long, keyword-stuffed SKILL.md
* frontmatter descriptions into clean one-liners for gnubok_list_skills and
* gnubok_get_agent_briefing, so the client never truncates one mid-sentence.
*/
import { describe, it, expect } from 'vitest'
import { toSummary } from '../skills/atoms'
describe('toSummary', () => {
it('returns short input unchanged (idempotent)', () => {
const s = 'Swedish VAT compliance reference.'
expect(toSummary(s)).toBe(s)
expect(toSummary(toSummary(s))).toBe(s)
})
it('collapses internal whitespace', () => {
expect(toSummary('Foo bar\n\tbaz')).toBe('Foo bar baz')
})
it('prefers the first sentence when it ends within the cap', () => {
const s = 'Project accounting basics. ' + 'tail '.repeat(60) // well over the cap
expect(toSummary(s, 60)).toBe('Project accounting basics.')
})
it('cuts at a word boundary with an ellipsis when no sentence ends within the cap', () => {
const long = 'a '.repeat(300).trim() // 300 single-char words, no punctuation
const out = toSummary(long, 50)
expect(out.length).toBeLessThanOrEqual(51) // <= maxLen + ellipsis
expect(out.endsWith('…')).toBe(true)
expect(out).not.toMatch(/\s…$/) // trimmed before the ellipsis
expect(out.includes(' ')).toBe(false)
})
it('never cuts in the middle of a word', () => {
const s = 'alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima'
const out = toSummary(s, 25)
const body = out.replace(/…$/, '').trim()
// every whitespace-delimited token in the output is a whole word from the input
const words = new Set(s.split(/\s+/))
for (const tok of body.split(/\s+/)) expect(words.has(tok)).toBe(true)
})
it('handles the real project-accounting description without a mid-word cut', () => {
const desc =
'Swedish project accounting (projektredovisning) covering dimensional tagging of bokföringsposter with project codes, WIP accounting (pågående arbeten), revenue recognition under K2 and K3 (successiv vinstavräkning, färdigställandemetoden), construction contracts (entreprenadavtal), BAS account patterns for project tracking (1470, 1620, 2420, 2450, 4970), SIE4 dimension encoding (#DIM 6, #OBJEKT, #TRANS object lists)'
const out = toSummary(desc)
expect(out.length).toBeLessThanOrEqual(201)
expect(out.endsWith('…')).toBe(true)
})
it('tolerates empty/blank input', () => {
expect(toSummary('')).toBe('')
expect(toSummary(' ')).toBe('')
})
})