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>
102 lines
3.5 KiB
TypeScript
102 lines
3.5 KiB
TypeScript
import { z } from 'zod'
|
|
|
|
// Atom selection output. The Opus call returns this via tool_use forcing:
|
|
// the model is forced to invoke `compose_agent_profile(...)` once, which
|
|
// gives us Zod-validatable structured output instead of free-text JSON.
|
|
export const AtomSelectionSchema = z.object({
|
|
horizontal_atoms: z
|
|
.array(z.string())
|
|
.describe('Atom IDs of horizontal regulatory skills to load (e.g. "horizontal/swedish-vat").'),
|
|
vertical_atoms: z
|
|
.array(z.string())
|
|
.describe(
|
|
'Atom IDs of industry/vertical atoms (e.g. "vertical/konsult-it"). Empty array if no vertical fits.',
|
|
),
|
|
modifier_atoms: z
|
|
.array(z.string())
|
|
.describe(
|
|
'Atom IDs of cross-cutting modifier atoms (e.g. "modifier/single-shareholder-ab-fmb").',
|
|
),
|
|
is_multi_vertical: z
|
|
.boolean()
|
|
.describe('True when the company genuinely spans more than one industry.'),
|
|
verification_questions: z
|
|
.array(z.string())
|
|
.describe(
|
|
'Short Swedish questions the user must confirm during Phase B verification. Highest-leverage uncertainties only.',
|
|
),
|
|
uncertainty_notes: z
|
|
.array(z.string())
|
|
.describe('Free-form notes the composer wants to surface to a developer reviewing the selection.'),
|
|
})
|
|
|
|
export type AtomSelection = z.infer<typeof AtomSelectionSchema>
|
|
|
|
// JSON-Schema for the tool_use forcing. Anthropic's API requires the tool
|
|
// schema in plain JSON Schema (not Zod). Kept in sync with AtomSelectionSchema
|
|
// by convention; the response is re-validated through Zod after parsing.
|
|
// Typed as the SDK's InputSchema shape (mutable) so it satisfies the
|
|
// Tool.input_schema parameter.
|
|
export const ATOM_SELECTION_TOOL_SCHEMA: {
|
|
type: 'object'
|
|
properties: Record<string, unknown>
|
|
required: string[]
|
|
additionalProperties: boolean
|
|
} = {
|
|
type: 'object',
|
|
properties: {
|
|
horizontal_atoms: { type: 'array', items: { type: 'string' } },
|
|
vertical_atoms: { type: 'array', items: { type: 'string' } },
|
|
modifier_atoms: { type: 'array', items: { type: 'string' } },
|
|
is_multi_vertical: { type: 'boolean' },
|
|
verification_questions: { type: 'array', items: { type: 'string' } },
|
|
uncertainty_notes: { type: 'array', items: { type: 'string' } },
|
|
},
|
|
required: [
|
|
'horizontal_atoms',
|
|
'vertical_atoms',
|
|
'modifier_atoms',
|
|
'is_multi_vertical',
|
|
'verification_questions',
|
|
'uncertainty_notes',
|
|
],
|
|
additionalProperties: false,
|
|
}
|
|
|
|
// Source signals snapshot. Persisted in agent_profiles.source_signals so the
|
|
// selection is reconstructable later even if upstream data (TIC, SIE) changes.
|
|
export const SourceSignalsSchema = z.object({
|
|
tic: z.record(z.string(), z.unknown()).nullable(),
|
|
sie_summary: z
|
|
.object({
|
|
top_accounts: z.array(z.object({ account: z.string(), abs_amount: z.number() })),
|
|
top_counterparties: z.array(z.object({ name: z.string(), abs_amount: z.number() })),
|
|
year_count: z.number(),
|
|
})
|
|
.nullable(),
|
|
banking_summary: z
|
|
.object({
|
|
top_counterparties: z.array(z.object({ name: z.string(), abs_amount: z.number() })),
|
|
monthly_volume: z.number().nullable(),
|
|
})
|
|
.nullable(),
|
|
atom_registry_version: z.number(),
|
|
})
|
|
|
|
export type SourceSignals = z.infer<typeof SourceSignalsSchema>
|
|
|
|
// Output of the full composer pipeline.
|
|
export interface ComposedProfile {
|
|
companyId: string
|
|
horizontalAtoms: string[]
|
|
verticalAtoms: string[]
|
|
modifierAtoms: string[]
|
|
isMultiVertical: boolean
|
|
verificationQuestions: string[]
|
|
uncertaintyNotes: string[]
|
|
profileSummary: string
|
|
sourceSignals: SourceSignals
|
|
composerModel: string
|
|
composedAt: string
|
|
}
|