Files
2026-05-27 23:29:53 +01:00

9.5 KiB

Tasks — @suaveplan/i18n-backend-fs-multifile (Node fs/promises multi-file translation backend, server-tier)

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick.

1. Spec authoring

  • 1.1 Read openspec/cross-cutting-designs/2026-05-05-i18n-modular-split.md end-to-end (especially §3, §6, §6.1 — the only Wave 5 backend on a different tier)
  • 1.2 Read openspec/AGENTS.md Sections 0, 0a, 6a, 7, 9, 13
  • 1.3 Read source: packages/i18n/i18n/src/core/backend/multi-file-fs/multi-file-fs-backend.ts, multi-file-fs-backend.test.ts, multi-file-fs-backend.schemas.ts, multi-file-fs-backend.md, plus packages/i18n/i18n/src/core/backend/backend.types.ts
  • 1.4 Draft proposal.md from openspec/templates/proposal.md
  • 1.5 Document the tier split rationale in proposal.md (### Tier split rationale (server-only, NOT universal) section)
  • 1.6 Fill ## Source mapping table with verified line ranges from monolith
  • 1.7 Draft specs/i18n-backend-fs-multifile/spec.md from openspec/templates/spec-delta.md
  • 1.8 Every requirement carries a **Verified by:** line naming concrete it(...) titles
  • 1.9 openspec change validate i18n-backend-fs-multifile exits with no errors

2. Package scaffold

  • 2.1 mkdir -p packages/i18n/i18n-backend-fs-multifile/src
  • 2.2 Author package.json — name @suaveplan/i18n-backend-fs-multifile, version 0.1.0, type module, sideEffects false. Server tier: declare "engines": { "node": ">=20" }; OMIT "browser" field. Deps: @suaveplan/i18n-shared workspace:^, @suaveplan/types workspace:^, @suaveplan/error workspace:^, @opentelemetry/api catalog:observability, tslib catalog:build, zod catalog:forms. devDeps: @suaveplan/testing workspace:^, @suaveplan/typescript-config workspace:^, @vitest/coverage-istanbul catalog:testing, vitest catalog:testing. Optional peerDeps: yaml, json5 (markedOptional via peerDependenciesMeta).
  • 2.3 Author tsconfig.json extending @suaveplan/typescript-config/library-node.json (server-tier)
  • 2.4 Author vite.config.ts using createTestConfig() from @suaveplan/testing/config
  • 2.5 Author README.md (≥200 words, includes Modules table linking to each .md; opens with a server-tier marker callout)
  • 2.6 Add package.json exports map with subpaths ., ./backend, ./factory, ./path-resolver, ./payload-codec, ./fs-error-classifier

3. Implementation

  • 3.1 Author src/backend.types.ts — port MultiFileFsBackendOptions, MultiFileFsLayout, MultiFileFsLike from monolith L15-L51 verbatim
  • 3.2 Author src/backend.schemas.ts — port MultiFileFsBackendOptionsSchema Zod schema verbatim (15 lines)
  • 3.3 Author src/path-resolver.ts — extract #resolvePath body into a standalone resolvePath(root, layout, extension, globPattern, locale, namespace): string plus joinPath helper
  • 3.4 Author src/payload-codec.ts — extract parsePayload, parseJson, parseWithPeer, encodePayload from monolith L313-L400 as standalone exports
  • 3.5 Author src/fs-error-classifier.ts — extract classifyFsError from monolith L297-L311 as a standalone export
  • 3.6 Author src/observability.ts — inline equivalents of recordRead / recordWrite; export histograms and counters with i18n.backend.fs_multifile. prefix; include layout attribute on counters
  • 3.7 Author src/backend.ts — port MultiFileFsBackend class from monolith L67-L284; rewire imports; use helpers from src/path-resolver.ts, src/payload-codec.ts, src/fs-error-classifier.ts; emit metrics via src/observability.ts; add optional signal?: AbortSignal parameter on read / save and throw I18nError(I18N.BACKEND_TIMEOUT) when signal aborts; LEADING SOURCE COMMENT: // Server-tier package — uses node:fs/promises. Browser consumers: use @suaveplan/i18n-backend-http or @suaveplan/i18n-backend-storage.

4. Factory function

  • 4.1 Author src/factory.ts — multiFileFsBackend(options: MultiFileFsBackendOptions): MultiFileFsBackend returning new MultiFileFsBackend(options)

5. Documentation (sibling .md per implementation file)

  • 5.1 src/backend.md — port from packages/i18n/i18n/src/core/backend/multi-file-fs/multi-file-fs-backend.md; ≥200 words; opens with server-tier marker; basic + advanced examples (per layout, with file-watch, with YAML peer)
  • 5.2 src/factory.md — ≥200 words; basic + advanced examples
  • 5.3 src/path-resolver.md — per-layout path shapes; glob substitution rules
  • 5.4 src/payload-codec.md — supported extensions; peer-detection contract; missing-peer error shape
  • 5.5 src/fs-error-classifier.md — errno → I18N code mapping table
  • 5.6 src/observability.md — emitted metrics + units + cardinality
  • 5.7 README.md ## Modules section links every .md; opens with > **Server-tier package** — usesnode:fs/promises. For browser consumers, use@suaveplan/i18n-backend-httpor@suaveplan/i18n-backend-storage.

6. Tests (Vitest, runner-neutral imports per CLAUDE.md §11)

  • 6.1 src/backend.test.ts — port every test from packages/i18n/i18n/src/core/backend/multi-file-fs/multi-file-fs-backend.test.ts (556 lines); same it(...) titles for conformance traceability; cover nested, flat, glob layouts; read hit/miss/parse-failure; save round-trip; lastModified; init / dispose lifecycle; YAML / JSON5 peer-detect happy path AND missing-peer path; fs.watch integration via injected MultiFileFsLike
  • 6.2 src/backend.spec-conformance.test.ts — one it(...) per spec requirement; titles match **Verified by:** lines exactly
  • 6.3 src/factory.test.ts — multiFileFsBackend() returns MultiFileFsBackend instance; default name === "multi-file-fs"
  • 6.4 src/path-resolver.test.ts — nested produces <root>/<locale>/<namespace>.<ext>; flat produces <root>/<locale>.<namespace>.<ext>; glob substitutes both placeholders
  • 6.5 src/payload-codec.test.ts — JSON parse; YAML peer-detect happy + missing; JSON5 peer-detect happy + missing; unknown extension → BACKEND_READ_FAILED; non-object payload → BACKEND_READ_FAILED; encode for unsupported extension → BACKEND_WRITE_FAILED
  • 6.6 src/fs-error-classifier.test.ts — ENOENT and ENOTDIR → BACKEND_LOCALE_NOT_AVAILABLE; EACCES and EBUSY → BACKEND_READ_FAILED
  • 6.7 src/index.test.ts — every documented public export resolves through ., ./backend, ./factory, ./path-resolver, ./payload-codec, ./fs-error-classifier; package.json declares engines.node >=20 AND omits browser field
  • 6.8 Add an AbortSignal cancellation test — pre-aborted signal causes read to throw I18nError(I18N.BACKEND_TIMEOUT)
  • 6.9 Every it(...) carries { timeout: 5000 }
  • 6.10 All test imports go through @suaveplan/testing/runner (CLAUDE.md §11) — verified by grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/i18n/i18n-backend-fs-multifile/src/ returning zero hits
  • 6.11 No // biome-ignore suppressions (CLAUDE.md §9)
  • 6.12 No export * in any non-root barrel (CLAUDE.md §10)

7. Pre-archive verification (5-gate triple)

  • 7.1 bun run check — Biome 0/0/0 across the monorepo
  • 7.2 bunx turbo lint typecheck test --filter=@suaveplan/i18n-backend-fs-multifile — green
  • 7.3 bunx turbo test:coverage --filter=@suaveplan/i18n-backend-fs-multifile — 100/100/100/100
  • 7.4 bun scripts/openspec-conformance.ts @suaveplan/i18n-backend-fs-multifile — exit 0
  • 7.5 bunx turbo build --filter=@suaveplan/i18n-backend-fs-multifile — green

8. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 8.1 Generics threaded — MultiFileFsLike injection contract typed; layout discriminator union exhaustively narrowed
  • 8.2 Zod schemas wired at the public boundary — MultiFileFsBackendOptionsSchema parses constructor input; globPattern required when layout: "glob" enforced via INVALID_OPTIONS
  • 8.3 Every error code thrown by at least one production path: INVALID_OPTIONS (constructor), BACKEND_LOCALE_NOT_AVAILABLE (ENOENT/ENOTDIR), BACKEND_READ_FAILED (other fs errors / parse failure / unknown extension / missing peer), BACKEND_WRITE_FAILED (writeFile failure / unsupported extension), BACKEND_TIMEOUT (signal aborted)
  • 8.4 Strict-mode tested — exhaustive switch on MultiFileFsLayout enforced via expectTypeOf
  • 8.5 OTel histograms emitted from production paths: i18n.backend.fs_multifile.read_duration_ms, i18n.backend.fs_multifile.write_duration_ms; counters with layout attribute
  • 8.6 Lifecycle dedup — init is idempotent (re-calling does not start a second watcher); dispose is idempotent (re-calling is a no-op)
  • 8.7 No retroactive tick — every box reflects work done in this change

9. Manifest + ecosystem awareness

  • 9.1 Add @suaveplan/i18n-backend-fs-multifile entry to root CLAUDE.md package listing under the i18n cluster with a (server-tier) marker
  • 9.2 Run bun scripts/folder-reorg/generate-manifest.ts to refresh MANIFEST.md
  • 9.3 Append @suaveplan/i18n-backend-fs-multifile to STATUS.md Completed list with a server-tier marker

10. Archive

  • 10.1 Fold spec delta into openspec/specs/i18n-backend-fs-multifile/spec.md (NEW capability spec — first delta becomes canonical)
  • 10.2 Move openspec/changes/i18n-backend-fs-multifile/ → openspec/archive/<yyyy-mm-dd>-i18n-backend-fs-multifile/