Skip to main content

polystella-contributor

Edit the PolyStella package source. Use when adding a file-format adapter, adding a CLI subcommand, adding a translation provider, modifying the cache contract, debugging a translation regression, or otherwise working on the package itself (not consuming it).

معلومات المصدر

المستودع
cloudflare/polystella
آخر نشاط في المصدر
١٥ سبتمبر ٢٠٢٦ في ١٤:٥٧
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٦
التفرعات
٢

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
polystella-contributor
description
Edit the PolyStella package source. Use when adding a file-format adapter, adding a CLI subcommand, adding a translation provider, modifying the cache contract, debugging a translation regression, or otherwise working on the package itself (not consuming it).
# polystella-contributor You are editing the PolyStella package source. This skill is recipes for the common contributor tasks. If you are integrating PolyStella into a downstream Astro project, STOP and load `polystella-consumer` instead. Read first: - [`AGENTS.md`](../../AGENTS.md) — orientation, invariants, boundaries. - [`ARCHITECTURE.md`](../../ARCHITECTURE.md) — subsystem reference. Then come back here for step-by-step task recipes. Package ownership follows the direct in-process flow: ```text source/record -> adapter -> core -> provider -> core -> adapter -> output ``` Core owns low-level translation contracts and orchestration, adapters own portable formats, providers own transports, and Astro owns host policy. Reusable packages use standard Web APIs and must work without `nodejs_compat`; consumers may enable it. Do not add compatibility shims for low-level imports that moved out of the Astro package. --- ## Recipes - [Add a file-format adapter](#add-adapter) - [Add a CLI subcommand](#add-cli-subcommand) - [Add a translation provider](#add-provider) - [Change the cache contract](#change-cache-contract) - [Debug a translation regression](#debug-translation) - [Modify a runtime API](#modify-runtime-api) - [Edit UI-string handling](#edit-ui-strings) - [Strict tsconfig patterns](#strict-tsconfig) - [Testing conventions](#testing) --- ## Add a file-format adapter <a id="add-adapter"></a> **When to use:** Supporting a new file extension (`.xml`, `.html`, `.po`, custom format). **Contract:** `FileAdapter` in `packages/core/src/adapters/adapter.ts`; Astro policies wrap it in `packages/astro/src/parsing/adapter.ts`. See [#adapter-contract](../../ARCHITECTURE.md#adapter-contract). **Steps:** 1. Implement the portable adapter at `packages/core/src/adapters/<name>.ts`: ```ts import type { Segment } from "@cloudflare/polystella-core"; import type { FileAdapter, AdapterExtractOptions, AdapterApplyOptions } from "../adapter.js"; export const myFormatAdapter: FileAdapter<MyParsedShape> = { extensions: [".myext"], parse(source, sourcePath) { // Pure. No I/O. Throw on syntactic errors — the per-pair // try/catch in runTranslationPass will surface them without // aborting the build. }, extractSegments(parsed, source, opts): Segment[] { // Emit { id, text } per translatable unit. // IDs must be unique within a single file. // Empty text → no segment (translating "" is meaningless). }, applyTranslations(parsed, source, translations, opts): string { // Splice translations back into source bytes. // INVARIANT 3: produce the EXACT bytes that will be PUT to R2. // Weave any AI-translation marker from opts.topLevelAdditions // into the output here, not after. }, groupSegments(parsed, segments): Segment[][] { ... }, // optional, INVARIANT 2 }; ``` 2. Add Astro's cache-selection, `noTranslate`, URL, document-context, marker, and parser policies in a small wrapper under `packages/astro/src/parsing/adapters/`, then register that wrapper in `packages/astro/src/parsing/registry.ts`: ```ts import { myFormatAdapter } from "./adapters/myformat.js"; // ... registerAdapter(myFormatAdapter); ``` **First-registered wins.** If your adapter claims an extension another adapter already owns, your registration is silently ignored. The order at the bottom of `registry.ts` is the de-facto priority. 3. Add portable tests under `packages/core/tests/adapters/` and retain Astro-policy parity tests under `packages/astro/tests/parsing/`. Required portable coverage: parsing/reconstruction, segment IDs, translation application, and group flattening by reference. Astro wrapper tests cover selected hash values, `noTranslate`, markers, context, and idempotent URL rewriting. 4. **No changes to `packages/astro/src/translation/run.ts` or `packages/astro/src/storage/cache.ts`.** The orchestrator dispatches by extension via the registry; the cache layer is format-agnostic. If you find yourself editing either, you're doing something wrong. 5. Verify: ```sh pnpm test pnpm typecheck ``` 6. Update the package README and any per-format docs. --- ## Add a CLI subcommand <a id="add-cli-subcommand"></a> **When to use:** Adding a new top-level verb (`polystella <verb>`). **Pattern:** Each subcommand owns its argv parsing and a `run<Name>(args, deps)` handler. Shared catalog commands live in `packages/core/src/cli/`; host dispatchers stay thin. **Steps:** 1. Create `packages/core/src/cli/<name>.ts` for a shared catalog command. Keep an Astro-only command under `packages/astro/src/cli/`: ```ts export interface MySubcommandArgs { // Parsed flags. help: boolean; someFlag?: string; } export const MY_SUBCOMMAND_USAGE = `polystella my-subcommand <description> Usage: polystella my-subcommand [flags] Flags: --some-flag <value> ... --help Print this message. Exit codes: 0 ok 1 config error 2 <subcommand-specific failure> `; export function parseMySubcommandArgs(argv: ReadonlyArray<string>): MySubcommandArgs { // Throw on unknown flag or missing value — accept-then-reject // would silently swallow typos. } export interface MySubcommandDeps { cwd: string; log: (msg: string) => void; err: (msg: string) => void; // Add fakeable I/O / clock / etc. for tests. } export async function runMySubcommand(args: MySubcommandArgs, deps: MySubcommandDeps): Promise<number> { // Return process exit code. } ``` 2. Register a shared catalog command in `packages/core/src/cli/run-command.ts` and both host CLIs. For an Astro-only command, wire `packages/astro/src/cli.ts`: - Add to the `Subcommand` union type. - Add the literal to `parseSubcommand`'s `if (first === "translate" || ...)` check. - Add a case to `main()`'s switch statement. - Update `TOP_LEVEL_USAGE` to mention the new verb. 3. Add tests: - `packages/core/tests/cli/<name>.test.ts` for a shared parser + handler, or `packages/astro/tests/cli/<name>.test.ts` for an Astro-only command. - Extend `packages/astro/tests/cli.test.ts` if the top-level dispatch needs new coverage (it usually does — add at least one "dispatches `my-subcommand` to the right handler" case). 4. If consumers typically wrap the subcommand in a `pnpm` script (e.g. `pnpm i18n:sync`), document the pattern in the docs site's CLI section. Don't add the wrapper to this package — consumer projects own their own scripts. 5. Verify: ```sh pnpm test pnpm typecheck pnpm build node packages/astro/dist/cli.js my-subcommand --help # Astro host node packages/emdash/dist/cli.js my-subcommand --help # shared catalog command ``` --- ## Add a translation provider <a id="add-provider"></a> **When to use:** Adding a third translator (e.g. OpenAI, Bedrock). **Contract:** `Translator` in `packages/core/src/translator.ts`. Provider transports live in `packages/core/src/providers/`; `packages/astro/src/translation/provider.ts` only maps Astro config. See [#translator-contract](../../ARCHITECTURE.md#translator-contract). **Steps:** 1. Add a config variant to the provider zod schema in `packages/astro/src/config/options.ts`: ```ts const newProviderSchema = z.object({ kind: z.literal("new-provider"), apiKey: z.string(), model: modelSpecSchema, // string | per-locale map maxTokens: z.number().int().positive().default(8192), endpoint: z.string().url().optional(), }); // Add to the discriminated union: const providerSchema = z.discriminatedUnion("kind", [workersAISchema, anthropicSchema, newProviderSchema]); ``` 2. Implement a concrete-model factory in `packages/core/src/providers/<name>.ts`: ```ts export function createNewProviderTranslator(options: { apiKey: string; modelId: string; maxTokens: number; fetchImpl?: typeof fetch; }): Translator { return { modelId: options.modelId, async translate(systemPrompt, userPrompt, signal) { const res = await (options.fetchImpl ?? fetch)(endpoint, { method: "POST", headers: { ... }, body: JSON.stringify({ ... }), ...(signal !== undefined ? { signal } : {}), }); if (!res.ok) throw await createProviderHttpError("New provider", res, signal); return normalizeResponse(await res.json()); }, }; } ``` 3. Export the factory from `packages/core/src/providers/index.ts`, then map the validated config in Astro's `createTranslator`: ```ts if (provider.kind === "new-provider") { return createNewProviderTranslator({ apiKey: provider.apiKey, modelId: resolveModelId(provider.model, locale), maxTokens: provider.maxTokens, }); } ``` 4. **Permanent vs retriable** — reuse the providers package's HTTP classifier. The permanent set is `{400, 401, 403, 404, 422}`; 5xx, 408, 425, and 429 are retriable. **Ask first** before adding statuses. 5. Add transport tests under `packages/core/tests/providers/` and retain Astro facade parity coverage in `packages/astro/tests/translation/provider.test.ts`: - Happy path (mock fetch returns expected shape). - Each permanent status → `PermanentProviderError`. - 5xx → plain `Error` (retriable). - Network error → plain `Error`. - Unexpected response shape → clear error message with raw response preview. - `signal` propagation to `fetch`. 6. Document the new provider in the package README and docs provider section. --- ## Change the cache contract <a id="change-cache-contract"></a> **When to use:** Modifying any input to the cache hash formula. **Severity:** Cache-wide invalidation. Every cached translation across every consumer becomes a miss on the next build. **Steps:** 1. Read [#cache-key](../../ARCHITECTURE.md#cache-key). The current formula is: ``` hash = sha256(body + selectedFrontmatterValues + glossaryHash + modelId + optionalExtractionPolicyHash) ``` 2. **Stop.** Coordinate with the owner before merging. This is **Invariant 1** in `AGENTS.md`. The change needs to be in a major version bump and called out in CHANGELOG. 3. If you're confident this is the right change: - Edit `packages/astro/src/storage/hash.ts` (the `computeSourceHash` function). - Update the formula description in `ARCHITECTURE.md#cache-key`. - Update `AGENTS.md` Invariant #1. - Update the hash test pin in `packages/astro/tests/storage/hash.test.ts` — it pins a literal hash to catch accidental formula drift. Compute the new literal and replace it. - Add a CHANGELOG entry under a "Breaking changes" heading. - Bump the major version (or 0.x minor pre-1.0). 4. Verify: ```sh pnpm test pnpm typecheck ``` The pinned-hash test will catch drift if you missed the test update. --- ## Debug a translation regression <a id="debug-translation"></a> **When to use:** A translation that used to work is wrong, missing, or failing. **Diagnostic flow:** 1. **Reproduce on the fixture.** If the regression is reported against a consumer's content, reduce to the smallest source file that reproduces. Add it under `packages/astro/tests/fixtures/` if it's worth a regression test. 2. **Inspect what the cache layer planned:** ```sh polystella translate --dry-run --file 'path/to/source.md' # or in a consumer repo: pnpm translate --dry-run --file 'path/to/source.md' ``` Output includes the planned R2 key. If the key is wrong, the bug is in `computeSourceHash` or `buildR2Key`. 3. **Inspect the staged output:** ```sh cat <root>/.astro/i18n-staging/<locale>/<source-path> ``` Compare to expected. Is the AI-translation marker (`aiTranslated: true`) present? Are URLs rewritten? Is the body translated at all? 4. **Inspect the build report:** ```sh cat dist/i18n-r2-report.json | jq '.entries[] | select(.sourcePath == "<path>")' ``` Outcome will be `hit`, `miss`, `override`, `error`, or `localSkipped`. Read the corresponding code path in `packages/astro/src/storage/cache.ts` or `packages/astro/src/source/overrides.ts`. 5. **Crank up verbosity:** ```sh LOG_LEVEL=debug polystella translate --file 'path/to/source.md' ``` Emits per-batch detail (segment count, batch count, oversize warnings, retry attempts). 6. **Bypass the cache:** delete the relevant R2 object, or delete the local index entry: ```sh rm <root>/.astro/i18n-staging/.polystella-cache.json ``` 7. **Bypass R2 entirely** by passing `r2Override: null` to `runTranslationPass` (test-only). Useful for isolating the translator from the cache layer. 8. **Common regression causes:** - Adapter `parse` not idempotent — calling it twice produces different output. (Asserted by some tests; if you added a new adapter, add this test.) - Cache key formula input added/removed without updating consumers. - Workers AI `maxTokens` was lowered — multi-segment translation truncated to invalid JSON. - Glossary YAML syntax error — silently ignored on load, term not applied. - `noTranslate: true` accidentally set in source frontmatter. - Override file path mismatch — locale or mirrored-path slug differs from source. - URL rewriter doubling prefixes — confirm both rewrite layers are idempotent on already-rewritten input. --- ## Modify a runtime API
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub