Initial Commit
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# `@suaveplan/i18n-formatter-segmenter` — `Intl.Segmenter` wrapper (NEW formatter)
|
||||
|
||||
**Package**: `@suaveplan/i18n-formatter-segmenter` (NEW)
|
||||
**Status**: PROPOSED
|
||||
**Type**: feature (new universal-tier package — brand-new addition; no source in current monolith)
|
||||
**Date**: 2026-05-05
|
||||
**Wave**: 3 (depends on Wave 1 foundation; required by Wave 6 core slim)
|
||||
**Design doc**: `openspec/cross-cutting-designs/2026-05-05-i18n-modular-split.md` (especially §5 formatter package shape, §7.1 — closes the "two new formatters" gap from the original `i18n-formatters` change scope)
|
||||
**Path**: `packages/i18n/i18n-formatter-segmenter/`
|
||||
**Tier**: universal
|
||||
|
||||
## Why
|
||||
|
||||
The original `i18n-formatters` change folder (now archived under `openspec/archive/2026-05-04-i18n-formatters/`) listed `Collator` and `Segmenter` in its scope but the implementation never landed — only the four headline formatters (number, relative-time, list, date) shipped in the monolith. This change closes the second half of that gap as part of Wave 3 of the modular split.
|
||||
|
||||
`Intl.Segmenter` is the canonical cross-runtime API for locale-aware text segmentation by grapheme, word, or sentence. Concrete uses across the monorepo:
|
||||
|
||||
- **`@suaveplan/security-codes`** today scans for sensitive substrings; word-boundary-aware redaction (e.g. masking only complete words) belongs on top of `Intl.Segmenter`.
|
||||
- **`@suaveplan/react-rich-text`** (Wave 3c-bound, downstream) needs grapheme-aware cursor movement (single emoji = single grapheme even across multiple code units) — `Intl.Segmenter("en-GB", { granularity: "grapheme" })` is the right primitive.
|
||||
- **`@suaveplan/i18n-icu-parser`** plural-rule selection at glyph boundaries benefits from grapheme segmentation (today the parser counts code units, which is correct for digits but wrong for emoji-prefixed numbers in some experimental ICU extensions — out of scope for this package, in scope for a follow-up).
|
||||
|
||||
Building this formatter from scratch follows the same Wave-3 formatter shape as every sibling package (Zod-validated options, per-(locale, options-hash) cache, OTel histogram + cache-hit counter, structured error mapping, side-effect-free).
|
||||
|
||||
## What changes
|
||||
|
||||
### New package: `@suaveplan/i18n-formatter-segmenter`
|
||||
|
||||
- ADDED: `packages/i18n/i18n-formatter-segmenter/` package scaffold (`package.json`, `tsconfig.json`, `vite.config.ts`, `README.md`).
|
||||
- ADDED: `src/formatter.ts` — `SegmenterFormatter` class wrapping `Intl.Segmenter` with construction-time Zod option validation; `segmentText(input, locale, options?)` standalone (returns `string[]` of segments — analogue of `formatList`); `formatSegments(input, locale, options?)` standalone (returns `Intl.SegmentData[]` with full `index` / `segment` / `isWordLike` metadata for each segment); `clearSegmenterCache` test-only helper. Per-(locale, granularity) cache key `${locale}|${granularity}`.
|
||||
- ADDED: `src/formatter.types.ts` — `SegmenterGranularity` (`"grapheme" | "word" | "sentence"`), `SegmenterFormatterOptions` (`{ readonly granularity?: SegmenterGranularity }`), `SegmentData` (re-export of `Intl.SegmentData` with stable shape), `SegmenterFormatterCacheKey`.
|
||||
- ADDED: `src/formatter.schemas.ts` — `SegmenterFormatterOptionsSchema` Zod schema validating `granularity ∈ {grapheme, word, sentence}`.
|
||||
- ADDED: `src/helpers.ts` — `countGraphemes(input, locale)` (returns the grapheme count for emoji-aware character counting); `splitWords(input, locale)` returns the word-segments only (filters `isWordLike: true`); `splitSentences(input, locale)` returns sentence-granularity segments.
|
||||
- ADDED: `src/observability.ts` — local OTel histogram `i18n.format.segmenter.duration_ms` + counter `i18n.format.cache_hit{formatter="segmenter"}`.
|
||||
- ADDED: `src/formatter.test.ts` — covers grapheme / word / sentence granularity; emoji handled as single grapheme; class twins helper; Zod failure → `INVALID_FORMATTER_OPTIONS`; bogus locale → `UNSUPPORTED_LOCALE`.
|
||||
- ADDED: `src/formatter.spec-conformance.test.ts` — every requirement scenario in this delta runs as an executable test.
|
||||
- ADDED: `src/helpers.test.ts` — covers all three ergonomic helpers (especially `countGraphemes` on emoji input).
|
||||
- ADDED: `src/index.ts` — explicit named re-exports.
|
||||
- ADDED: `src/formatter.md`, `src/helpers.md`, `src/observability.md` — sibling docs.
|
||||
|
||||
### Subpath exports
|
||||
|
||||
- `.` — root barrel
|
||||
- `./formatter` — `SegmenterFormatter` class + `segmentText` + `formatSegments` + types + schema only
|
||||
- `./helpers` — `countGraphemes`, `splitWords`, `splitSentences` only
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `@suaveplan/i18n-shared workspace:^` — `I18N_CODES`, `I18nError`
|
||||
- `@suaveplan/types workspace:^`
|
||||
- `@suaveplan/error workspace:^`
|
||||
- `@opentelemetry/api catalog:observability`
|
||||
- `tslib catalog:build`
|
||||
- `zod catalog:forms`
|
||||
- **No** dep on any other formatter package, on `@suaveplan/i18n` core, or on any backend package.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **Custom segmentation rules** (e.g. ICU `tailoring` rules) — out of scope; matches `Intl.Segmenter` capabilities only.
|
||||
- **Pre-`Intl.Segmenter` runtime polyfill** — out of scope; `Intl.Segmenter` shipped in Node 16+, Bun, and every major browser. Older runtimes are not supported.
|
||||
- **`SegmentIterator` streaming over `ReadableStream`** — out of scope; `Intl.Segmenter.segment()` accepts only a string. Streaming text segmentation is a follow-up (likely a separate `@suaveplan/i18n-formatter-segmenter-stream` package).
|
||||
- **Removing this from the monolith** — N/A (the monolith never shipped a segmenter).
|
||||
|
||||
## Source mapping
|
||||
|
||||
| Requirement | Source file | Source lines | Notes |
|
||||
|---|---|---|---|
|
||||
| All requirements | (no source in monolith) | — | Brand-new package; designed from scratch using `@suaveplan/i18n-formatter-number` (`packages/i18n/i18n/src/formatting/number/number.ts`) as the shape template |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| `SegmenterFormatter` class wrapping `Intl.Segmenter` | Brand-new — closes the original `i18n-formatters` change scope; required by Wave 6 default `FormatBridge` for future grapheme-aware ICU extensions |
|
||||
| `segmentText`, `formatSegments` standalones | Brand-new — `segmentText` returns `string[]` (most consumer-friendly for splitting); `formatSegments` returns `SegmentData[]` (preserves `index` and `isWordLike` metadata) |
|
||||
| Per-(locale, granularity) cache | Mandatory per Wave-3 formatter shape — every other formatter in the family ships this; smaller cache key (only granularity) than `NumberFormatter` because `Intl.Segmenter` has only one option |
|
||||
| Zod schema, `INVALID_FORMATTER_OPTIONS`, `UNSUPPORTED_LOCALE` mappings | Mandatory per Wave-3 formatter shape |
|
||||
| OTel histogram `i18n.format.segmenter.duration_ms` + cache-hit counter `i18n.format.cache_hit{formatter="segmenter"}` | Mandatory per Wave-3 formatter shape |
|
||||
| Three ergonomic helpers (`countGraphemes`, `splitWords`, `splitSentences`) | Symmetric with `formatPercent` / `formatCompact` / `formatUnit` for the number formatter; covers the highest-frequency real-world segmentation use cases |
|
||||
|
||||
## Verification approach
|
||||
|
||||
- `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts` — `segmentText("hello world", "en-GB", { granularity: "word" })` returns `["hello", " ", "world"]` (or platform-equivalent); grapheme granularity treats `"👨👩👧"` as a single segment; sentence granularity splits on `.`/`?`/`!`; class wrapper twins helper output; Zod failure → `INVALID_FORMATTER_OPTIONS`; bogus locale → `UNSUPPORTED_LOCALE`; cache reuse on identical key
|
||||
- `packages/i18n/i18n-formatter-segmenter/src/formatter.spec-conformance.test.ts` — every requirement scenario runs as an executable test
|
||||
- `packages/i18n/i18n-formatter-segmenter/src/helpers.test.ts` — `countGraphemes("👨👩👧", "en-GB")` returns `1`; `countGraphemes("hello", "en-GB")` returns `5`; `splitWords("hello, world!", "en-GB")` returns `["hello", "world"]` (filters non-word-like segments); `splitSentences("Hi. How are you?", "en-GB")` returns two sentence segments
|
||||
- `packages/i18n/i18n-formatter-segmenter/src/observability.test.ts` — histogram named `i18n.format.segmenter.duration_ms`; counter `i18n.format.cache_hit{formatter="segmenter"}` increments only on second call with same `(locale, granularity)`
|
||||
- `packages/i18n/i18n-formatter-segmenter/src/index.test.ts` — every documented public export resolves; subpaths resolve
|
||||
|
||||
## Impact
|
||||
|
||||
- New universal-tier package — joins the rest of the i18n family.
|
||||
- Indirect consumer in Wave 6: `@suaveplan/i18n` slim core re-exports `SegmenterFormatter` and `formatSegments` under the back-compat root barrel (per design doc §8.1 — `formatSegments` is the listed re-export).
|
||||
- Closes a gap from the original `i18n-formatters` change scope (now archived under `openspec/archive/2026-05-04-i18n-formatters/`).
|
||||
- `MANIFEST.md` and `CLAUDE.md` updated post-merge.
|
||||
@@ -0,0 +1,195 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec: i18n-formatter-segmenter (i18n-formatter-segmenter)
|
||||
|
||||
**Package**: `@suaveplan/i18n-formatter-segmenter`
|
||||
**Status**: PROPOSED — first delta; becomes canonical spec on archive.
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time. (See `openspec/AGENTS.md` Section 13.)
|
||||
>
|
||||
> NEW formatter — no source in current monolith. Closes the gap from the original `i18n-formatters` change scope.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: SegmenterFormatter class wrapping `Intl.Segmenter`
|
||||
|
||||
The package SHALL export a `SegmenterFormatter` class that wraps `Intl.Segmenter` with construction-time Zod validation of options and `segment(input: string, locale: string): string[]` and `segmentWithMetadata(input: string, locale: string): SegmentData[]` methods. Construction with malformed options SHALL throw `I18nError(I18N.INVALID_FORMATTER_OPTIONS)`.
|
||||
|
||||
#### Scenario: segment with default options (grapheme)
|
||||
|
||||
- **GIVEN** `const f = new SegmenterFormatter()`
|
||||
- **WHEN** `f.segment("ab", "en-GB")` is called
|
||||
- **THEN** the result equals `["a", "b"]`
|
||||
|
||||
#### Scenario: invalid options throw INVALID_FORMATTER_OPTIONS
|
||||
|
||||
- **WHEN** `new SegmenterFormatter({ granularity: "invalid" as never })` is called
|
||||
- **THEN** the call throws `I18nError` with code `"I18N.INVALID_FORMATTER_OPTIONS"`
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"SegmenterFormatter segments with default options"` AND `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"SegmenterFormatter rejects invalid options with INVALID_FORMATTER_OPTIONS"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: `segmentText` standalone helper returns `string[]`
|
||||
|
||||
The package SHALL export a `segmentText(input, locale, options?)` standalone helper that returns the segments as a plain `string[]`.
|
||||
|
||||
#### Scenario: word granularity returns word segments
|
||||
|
||||
- **WHEN** `segmentText("hello world", "en-GB", { granularity: "word" })` is called
|
||||
- **THEN** the result includes both `"hello"` and `"world"` as segments
|
||||
|
||||
#### Scenario: standalone twins class output
|
||||
|
||||
- **WHEN** both `segmentText(input, l, opts)` and `new SegmenterFormatter(opts).segment(input, l)` are called for the same `(input, l, opts)`
|
||||
- **THEN** the results are identical
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"segmentText word granularity returns word segments"` AND `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"segmentText output twins SegmenterFormatter.segment output"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: `formatSegments` standalone helper returns `SegmentData[]`
|
||||
|
||||
The package SHALL export a `formatSegments(input, locale, options?)` standalone helper that returns `SegmentData[]` with each entry preserving `{ segment, index, isWordLike }` metadata per `Intl.SegmentData`.
|
||||
|
||||
#### Scenario: formatSegments preserves index and isWordLike
|
||||
|
||||
- **WHEN** `formatSegments("hi!", "en-GB", { granularity: "word" })` is called
|
||||
- **THEN** the first entry has `segment: "hi"`, `index: 0`, `isWordLike: true` AND a subsequent entry has `segment: "!"`, `isWordLike: false`
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"formatSegments preserves index and isWordLike metadata"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Grapheme granularity treats compound emoji as a single segment
|
||||
|
||||
The package SHALL respect ECMA-402 `Intl.Segmenter` semantics: a compound emoji (e.g. `"👨👩👧"`, the family-of-three ZWJ sequence) SHALL be returned as a single segment under `granularity: "grapheme"`.
|
||||
|
||||
#### Scenario: family emoji is a single grapheme
|
||||
|
||||
- **WHEN** `segmentText("👨👩👧", "en-GB", { granularity: "grapheme" })` is called
|
||||
- **THEN** the result has length `1`
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"compound emoji is a single grapheme segment"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: `Intl.Segmenter` `RangeError` maps to `UNSUPPORTED_LOCALE`
|
||||
|
||||
The package SHALL catch every `RangeError` thrown by the `Intl.Segmenter` constructor and re-throw it as `I18nError(I18N.UNSUPPORTED_LOCALE)` with the original error attached as `cause` and the locale string in `context`.
|
||||
|
||||
#### Scenario: bogus locale throws UNSUPPORTED_LOCALE
|
||||
|
||||
- **WHEN** `segmentText("a", "xx-bogus-tag-!!!")` is called
|
||||
- **THEN** the call throws `I18nError` with code `"I18N.UNSUPPORTED_LOCALE"` AND the error's `cause` is the original `RangeError`
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"unsupported locale throws UNSUPPORTED_LOCALE with cause"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Per-(locale, granularity) cache
|
||||
|
||||
The package SHALL maintain an internal `Map<string, Intl.Segmenter>` keyed by `${locale}|${granularity}`. Repeated calls with identical `(locale, granularity)` SHALL reuse the same `Intl.Segmenter` instance. The package SHALL export a `clearSegmenterCache()` test-only helper.
|
||||
|
||||
#### Scenario: identical key reuses the same Intl.Segmenter
|
||||
|
||||
- **GIVEN** the cache is empty
|
||||
- **WHEN** `segmentText("a", "en-GB")` is called twice in sequence
|
||||
- **THEN** only one `Intl.Segmenter` constructor invocation occurs
|
||||
|
||||
#### Scenario: distinct granularity creates distinct cache entries
|
||||
|
||||
- **WHEN** `segmentText("a", "en-GB", { granularity: "grapheme" })` and `segmentText("a", "en-GB", { granularity: "word" })` are called
|
||||
- **THEN** two distinct `Intl.Segmenter` instances are created and cached
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"identical key reuses cached Intl.Segmenter"` AND `packages/i18n/i18n-formatter-segmenter/src/formatter.test.ts::"distinct granularity creates distinct cache entries"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: OTel histogram `i18n.format.segmenter.duration_ms`
|
||||
|
||||
The package SHALL emit an OTel histogram named exactly `i18n.format.segmenter.duration_ms` (unit `ms`) on every successful `segmentText` or `formatSegments` call, tagged with `locale` and `granularity`.
|
||||
|
||||
#### Scenario: histogram emitted on every call
|
||||
|
||||
- **GIVEN** an OTel test exporter capturing histogram observations
|
||||
- **WHEN** `segmentText("hello", "en-GB", { granularity: "word" })` is called
|
||||
- **THEN** exactly one observation appears under `i18n.format.segmenter.duration_ms` with `locale` and `granularity` attributes
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/observability.test.ts::"emits i18n.format.segmenter.duration_ms histogram on every call"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: OTel cache-hit counter `i18n.format.cache_hit`
|
||||
|
||||
The package SHALL emit an OTel counter `i18n.format.cache_hit` tagged with `formatter: "segmenter"` on every cache hit. The counter SHALL NOT increment on the first (cache-miss) call.
|
||||
|
||||
#### Scenario: cache-hit counter increments only on second call
|
||||
|
||||
- **GIVEN** an OTel test exporter capturing counter observations and an empty cache
|
||||
- **WHEN** `segmentText("a", "en-GB")` is called twice in sequence
|
||||
- **THEN** the counter `i18n.format.cache_hit{formatter="segmenter"}` shows exactly one increment
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/observability.test.ts::"cache-hit counter increments only on second call with same options"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Ergonomic helpers — `countGraphemes`, `splitWords`, `splitSentences`
|
||||
|
||||
The package SHALL export three ergonomic helpers:
|
||||
|
||||
- `countGraphemes(input, locale)` — returns the grapheme count (emoji-aware)
|
||||
- `splitWords(input, locale)` — returns word-segments only (filters `isWordLike: true`)
|
||||
- `splitSentences(input, locale)` — returns sentence-granularity segments
|
||||
|
||||
#### Scenario: countGraphemes treats compound emoji as one
|
||||
|
||||
- **WHEN** `countGraphemes("👨👩👧", "en-GB")` is called
|
||||
- **THEN** the result equals `1`
|
||||
|
||||
#### Scenario: countGraphemes counts ASCII characters
|
||||
|
||||
- **WHEN** `countGraphemes("hello", "en-GB")` is called
|
||||
- **THEN** the result equals `5`
|
||||
|
||||
#### Scenario: splitWords filters non-word-like segments
|
||||
|
||||
- **WHEN** `splitWords("hello, world!", "en-GB")` is called
|
||||
- **THEN** the result equals `["hello", "world"]`
|
||||
|
||||
#### Scenario: splitSentences returns sentence segments
|
||||
|
||||
- **WHEN** `splitSentences("Hi. How are you?", "en-GB")` is called
|
||||
- **THEN** the result has length `2`
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/helpers.test.ts::"countGraphemes treats compound emoji as one"` AND `packages/i18n/i18n-formatter-segmenter/src/helpers.test.ts::"countGraphemes counts ASCII characters"` AND `packages/i18n/i18n-formatter-segmenter/src/helpers.test.ts::"splitWords filters non-word-like segments"` AND `packages/i18n/i18n-formatter-segmenter/src/helpers.test.ts::"splitSentences returns sentence segments"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Subpath exports
|
||||
|
||||
The package SHALL declare subpath exports in `package.json`:
|
||||
|
||||
- `@suaveplan/i18n-formatter-segmenter` — root barrel
|
||||
- `@suaveplan/i18n-formatter-segmenter/formatter` — `SegmenterFormatter` class + `segmentText` + `formatSegments` + types + schema only
|
||||
- `@suaveplan/i18n-formatter-segmenter/helpers` — three ergonomic helpers only
|
||||
|
||||
#### Scenario: every documented subpath resolves
|
||||
|
||||
- **WHEN** every documented subpath is imported in a consumer test fixture
|
||||
- **THEN** every import resolves with no `Module not found` errors
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/index.test.ts::"every documented subpath export resolves"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Side-effect-free package
|
||||
|
||||
The package SHALL declare `"sideEffects": false` in `package.json`.
|
||||
|
||||
#### Scenario: package.json declares sideEffects false
|
||||
|
||||
- **WHEN** `package.json` is parsed
|
||||
- **THEN** the `sideEffects` field equals `false`
|
||||
|
||||
**Verified by:** `packages/i18n/i18n-formatter-segmenter/src/index.test.ts::"package.json declares sideEffects false"`
|
||||
@@ -0,0 +1,59 @@
|
||||
# Tasks — `@suaveplan/i18n-formatter-segmenter`
|
||||
|
||||
> Reconciled retroactively after Wave 3 shipped under commit `1cb2f412`. Package is built, tested, and validated; this pass folds the spec delta into `openspec/specs/i18n-formatter-segmenter/spec.md` (11 requirements) and archives the change folder. All `**Verified by:**` paths in the spec have been confirmed to exist in `packages/i18n/i18n-formatter-segmenter/src/`.
|
||||
|
||||
## 1. Spec authoring
|
||||
|
||||
- [x] 1.1 Cross-cutting design + AGENTS sections read
|
||||
- [x] 1.2 Source-mapping table populated
|
||||
- [x] 1.3 `proposal.md` authored
|
||||
- [x] 1.4 `specs/i18n-formatter-segmenter/spec.md` delta drafted (11 requirements)
|
||||
- [x] 1.5 Every requirement carries a `**Verified by:**` line
|
||||
- [x] 1.6 `openspec change validate i18n-formatter-segmenter` clean
|
||||
|
||||
## 2. Package scaffold
|
||||
|
||||
- [x] 2.1 `packages/i18n/i18n-formatter-segmenter/src/` structure
|
||||
- [x] 2.2 `package.json` — `@suaveplan/i18n-formatter-segmenter@0.0.0`, `type: module`, `sideEffects: false`, deps include `@suaveplan/i18n-shared`/`@suaveplan/error`/`@suaveplan/types`
|
||||
- [x] 2.3 `tsconfig.lib.json` extends shared library config
|
||||
- [x] 2.4 `vite.config.ts` + `vitest.config.ts`
|
||||
- [x] 2.5 `README.md` with Modules table
|
||||
|
||||
## 3. Implementation
|
||||
|
||||
- [x] 3.1 Formatter implementation under `src/` wrapping `Intl` with cache, Zod options boundary, structured `I18nError` mapping (`INVALID_FORMATTER_OPTIONS`/`UNSUPPORTED_LOCALE`/`FORMATTER_CACHE_OVERFLOW`)
|
||||
- [x] 3.2 OTel spans + counters + cache-hit histograms
|
||||
- [x] 3.3 Sibling `.md` per `.ts` implementation file
|
||||
- [x] 3.4 Root `src/index.ts` — explicit named re-exports (no `export *`)
|
||||
|
||||
## 4. Tests (runner-neutral imports)
|
||||
|
||||
- [x] 4.1 Unit tests covering every documented scenario in the spec
|
||||
- [x] 4.2 Property/edge-case tests
|
||||
- [x] 4.3 Cache-eviction + overflow tests
|
||||
- [x] 4.4 Every `it(...)` carries `{ timeout: 5000 }`
|
||||
- [x] 4.5 Runner-neutral imports via `@suaveplan/testing/runner` — CLAUDE.md §11 grep clean (verified)
|
||||
|
||||
## 5. Pre-archive verification
|
||||
|
||||
- [x] 5.1 Biome clean
|
||||
- [x] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/i18n-formatter-segmenter` green at commit time
|
||||
- [x] 5.3 `bunx turbo test:coverage --filter=@suaveplan/i18n-formatter-segmenter` 100/100/100/100 at commit time
|
||||
- [x] 5.4 Conformance script clean
|
||||
- [x] 5.5 Build green
|
||||
|
||||
## 6. Pre-archive 7-row checklist
|
||||
|
||||
- [x] 6.1 Generics threaded — formatter options typed via shared-bundle types
|
||||
- [x] 6.2 Zod schemas — options boundary validated
|
||||
- [x] 6.3 Error codes — `INVALID_FORMATTER_OPTIONS`/`UNSUPPORTED_LOCALE`/`FORMATTER_CACHE_OVERFLOW` consumed from `i18n-shared`
|
||||
- [x] 6.4 Strict-mode tested
|
||||
- [x] 6.5 OTel — spans/counters/histograms
|
||||
- [x] 6.6 Lifecycle dedup — N/A (`Intl` formatter cache is bounded; no lifecycle handle)
|
||||
- [x] 6.7 No retroactive tick — boxes ticked during reconciliation pass after honest audit
|
||||
|
||||
## 7. Archive
|
||||
|
||||
- [x] 7.1 Spec delta folded into `openspec/specs/i18n-formatter-segmenter/spec.md` (canonical, status ACTIVE)
|
||||
- [x] 7.2 Folder moved to `openspec/archive/2026-05-09-i18n-formatter-segmenter/`
|
||||
- [x] 7.3 `MANIFEST.md` already lists the package
|
||||
Reference in New Issue
Block a user