Skip to content

Commit da1c7da

Browse files
Aymericrclaude
andcommitted
docs(skills): harden lingo skill after Codex review passes
Corrects claims against source (failure results omit span/confidence; issues[].span is optional; LOCALE_NOT_LOADED only on an explicit unloaded locale), switches the AI SDK snippet to a provider-neutral model + injected now, qualifies the two-way and [CODE] promises, adds a timezone/applyZone caveat, enriches the description and entry table (describe/schema), and fixes a frontmatter YAML parse break (colon in the description value). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent fff4b2d commit da1c7da

1 file changed

Lines changed: 38 additions & 20 deletions

File tree

skills/lingo/SKILL.md

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: lingo
3-
description: Parse natural-language quantities, units, dates, and ranges ("5'11\"", "1.5 cups", "72 in to cm", "three days ago", "between 5 and 10 kg", "it's hot") into canonical, validated values — and humanize them back. Use when building form inputs that accept measurements/units/dates, making LLM tool calls safe at the boundary (AI SDK / MCP), or converting and validating any human- or model-entered measurement string. Zero-dependency TypeScript, two-way (round-trips), deterministic.
3+
description: Parse natural-language quantities, units, dates, and ranges ("5'11\"", "1.5 cups", "72 in to cm", "three days ago", "between 5 and 10 kg", "it's hot") into canonical, validated values with issue codes and spans — and humanize them back. Use when a form input, LLM tool schema, MCP handler, or import pipeline takes free-text measurements/units/dates. Covers Standard Schema fields (quantityField/dateField/lingoObject) for the AI SDK and MCP, the headless <lingo-input>, unit conversion, and format/humanize round-trips. Zero-dependency TypeScript, deterministic.
44
metadata:
55
author: pascalorg
66
version: "1.0.0"
@@ -11,14 +11,20 @@ metadata:
1111
[`@pascal-app/lingo`](https://github.com/pascalorg/lingo) is a zero-dependency
1212
TypeScript library that turns the strings people type and models emit —
1313
`180cm`, `5ft 11`, `1.5 cups`, `90 min`, `next friday`, `1,5 kg`, `"5'11\""`,
14-
`"twenty-five kg"`, `"3pm EST"` — into canonical, validated values (one number
15-
in one unit, one ISO date), converts and range-checks them, then humanizes the
16-
value back. Every result carries a stable issue code and a `[start, end)` span
17-
into the original input. It's **two-way**: whatever `format()`/`humanize*()`
18-
emits re-parses to the same value.
14+
`"twenty-five kg"`, `"3pm EST"` — into canonical, validated values: a quantity,
15+
range, conversion, date, date-range, or duration in one canonical unit. It
16+
converts, range-checks, and humanizes the value back. Every successful result
17+
carries a `[start, end)` span into the original input plus any issues with stable
18+
codes. It's **two-way**: default `format()`/`humanize*()` output re-parses to the
19+
same value (display-only options like `localizedUnits` are outside the guarantee).
1920

2021
Tagline: **Make forms easier, LLM tools safer.**
2122

23+
> **Source of truth:** for anything past the patterns below — the full API, every
24+
> unit and kind, all issue codes, and per-input examples — open
25+
> `node_modules/@pascal-app/lingo/llms.txt` or
26+
> [`lingo.pascal.app/llms.txt`](https://lingo.pascal.app/llms.txt).
27+
2228
## When to use this skill
2329

2430
Reach for lingo whenever a value arrives as free text and needs to become a
@@ -35,7 +41,8 @@ trustworthy stored value:
3541
- **Anything that converts** (`72 in to cm`), **ranges** (`between 5 and 10 kg`,
3642
`under 10 minutes`, `10 ± 0.5 mm`), or **relative dates** (`three days ago`).
3743

38-
Do **not** reach for it for plain already-canonical numbers, or for currency
44+
Do **not** reach for it for already-canonical typed numbers, generic calendar
45+
scheduling or timezone-database work, arbitrary NLP extraction, or currency
3946
conversion needing live FX (lingo returns `RATE_REQUIRED` — you inject rates).
4047

4148
## Install & entry points
@@ -58,6 +65,8 @@ Import only what you need; each subpath is tree-shakeable.
5865
| `@pascal-app/lingo/complete` | ranked autocomplete `completions()` |
5966
| `@pascal-app/lingo/locales/{en,en-gb,es,fr,pt,zh,ja}` | opt-in parsing language packs |
6067
| `@pascal-app/lingo/catalog` | query units/kinds/currencies |
68+
| `@pascal-app/lingo/describe` | rich human/agent-readable views of a value or result |
69+
| `@pascal-app/lingo/schema` | JSON Schema / OpenAPI generated from the v3 wire types |
6170

6271
## Pattern 1 — parse, validate, convert (core)
6372

@@ -72,10 +81,12 @@ parseRange("between 5 and 10 kg", { kind: "mass" }) // range 5..10 kg
7281
```
7382

7483
`lingo(text, opts?)` returns a versioned union on `.type`
75-
(`quantity | range | conversion | number | failure`), serialized as flat v3 JSON
76-
(`{ schemaVersion: 3, ok, type, ...value, text, span, issues, confidence }`).
77-
Always branch on `.ok` / `.type` before reading the value, and surface
78-
`.issues[]` (each has `code`, `severity`, `message`, `span`) to the user.
84+
(`quantity | range | conversion | number | failure`), serialized as flat v3 JSON.
85+
**Branch on `.ok` first.** Successes carry the value fields plus `span` and
86+
`confidence`; a failure is `{ schemaVersion: 3, ok: false, type: "failure", text,
87+
issues, candidate? }` — with no `span`/`confidence`. Surface `.issues[]` (each has
88+
`code`, `severity`, `message`, and usually a `span`; field-level bound issues from
89+
`/ai` may omit it) to the user.
7990

8091
Pass context to disambiguate: `{ kind, unit, currency, locale, system, strictness }`.
8192
`strictness: "confirm"` turns each assumption (typo fix, ambiguous number) into a
@@ -104,20 +115,23 @@ element: `defineLingoInput()` then `<lingo-input kind="length" unit="m" name="he
104115

105116
The fields expose a **`string`** input JSON Schema (models are better at emitting
106117
`"5'11\""` than `1.8034`) and hand your handler the **canonical** value. Risky
107-
readings fail loudly with `[CODE] … Did you mean …?` messages a model can
108-
self-correct from in one round trip.
118+
readings fail loudly: parser failures are `[CODE]`-prefixed and carry a
119+
`candidate` when one exists (so a model can self-correct in one round trip);
120+
schema-shape errors surface as plain Standard Schema messages.
109121

110122
```ts
111123
import { tool, generateText } from "ai"
112124
import { lingoObject, quantityField, dateField } from "@pascal-app/lingo/ai"
113125

126+
// `now` must be injected per request (fixed here for reproducibility) — not Date.now()
127+
const now = new Date("2026-07-08T12:00:00Z")
114128
const shipment = lingoObject({
115129
weight: quantityField({ kind: "mass", unit: "kg", min: 0, max: 500 }),
116-
deliverBy: dateField({ now: new Date() }), // relative dates REQUIRE now
130+
deliverBy: dateField({ now }), // relative dates REQUIRE now
117131
})
118132

119133
await generateText({
120-
model: "claude-opus-4-8",
134+
model, // your AI SDK model, e.g. "anthropic/claude-opus-4-8" via the gateway
121135
tools: { create_shipment: tool({ inputSchema: shipment, execute: run }) },
122136
})
123137
```
@@ -131,19 +145,23 @@ return `{ isError: true }` with `[CODE]`-prefixed text.
131145
1. **Keep measurements as strings in tool/form schemas.** Let lingo convert,
132146
validate, surface spans, and handle ambiguity — don't ask the model or user
133147
for a float.
134-
2. **Store the canonical value, display the humanized one.** `format()` /
135-
`humanizeDate()` round-trip back to the same value (`1.9999 m``6′7″`,
136-
never `5′12″`).
148+
2. **Store the canonical value, display the humanized one.** Default `format()` /
149+
`humanizeDate()` output round-trips back to the same value (`1.9999 m``6′7″`,
150+
never `5′12″`); display-only options like `localizedUnits` don't round-trip.
137151
3. **Always pass an explicit `now`** to `parseDate`/`dateField` for relative
138152
dates — never rely on `Date.now()`, so a queued or retried call can't drift
139153
across midnight. Reference-dependent input without `now``NOW_REQUIRED`.
154+
Timezones are detected but kept as civil time unless you pass `applyZone: true`;
155+
`/ai` `dateField` rejects an ignored zone (`TZ_IGNORED`) by default.
140156
4. **No silent guesses.** Ambiguous input returns a deterministic best reading
141157
plus ranked `alternatives`/`candidate` and a warning code — show it, or use
142158
`strictness: "confirm"` / `"strict"` to force confirmation.
143159
5. **Currency conversion needs injected rates**`5 EUR to USD` returns
144160
`RATE_REQUIRED`; call `convertCurrency` with your own rates.
145-
6. **Locale packs are opt-in.** English is built in; for other languages
146-
`createLingo({ locales: [es, fr, …] })` or you'll get `LOCALE_NOT_LOADED`.
161+
6. **Locale packs are opt-in.** English is built in; load others with
162+
`createLingo({ locales: [es, fr, …] })`. Omitting `locale` auto-detects among
163+
loaded packs plus English; requesting an explicit *unloaded* `locale`
164+
`LOCALE_NOT_LOADED`.
147165

148166
Common issue codes to handle: `UNKNOWN_UNIT`, `KIND_MISMATCH`, `UNIT_REQUIRED`,
149167
`AMBIGUOUS_NUMBER`, `AMBIGUOUS_UNIT`, `TYPO_CORRECTED`, `RANGE_MIN`/`RANGE_MAX`,

0 commit comments

Comments
 (0)