@the-i18n-kit/cli 5.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +8 -58
- package/dist/bin.js +1 -6
- package/dist/bin.js.map +1 -1
- package/dist/{_shared-DFyy3sh8.js → cli-waEjbUM1.js} +157 -27
- package/dist/cli-waEjbUM1.js.map +1 -0
- package/dist/config/framework/stubs/{next-intl-routing-Cwaa3f4R.d.ts → next-intl-routing-i6Mlxwdt.d.ts} +1 -1
- package/dist/config/framework/stubs/{next-intl-routing-Cwaa3f4R.d.ts.map → next-intl-routing-i6Mlxwdt.d.ts.map} +1 -1
- package/dist/{define-config-BaZ1dZph.d.ts → define-config-ChY3jk6K.d.ts} +26 -4
- package/dist/define-config-ChY3jk6K.d.ts.map +1 -0
- package/dist/{define-config-CLgKN-7N.d.ts → define-config-e0B0VHq7.d.ts} +1 -1
- package/dist/define-config.d.ts +1 -1
- package/dist/descriptors-10sgyqEs.js +1225 -0
- package/dist/descriptors-10sgyqEs.js.map +1 -0
- package/dist/detector-BJTkyhQ0.js +5 -0
- package/dist/detector-DtFY4qm3.js +2189 -0
- package/dist/detector-DtFY4qm3.js.map +1 -0
- package/dist/{index-DCG3dOdC.d.ts → index-CdjJ71Ag.d.ts} +665 -250
- package/dist/index-CdjJ71Ag.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -5
- package/dist/json-writer-D0b6vEax.js +2 -0
- package/dist/json-writer-ek8yEI5R.js +342 -0
- package/dist/json-writer-ek8yEI5R.js.map +1 -0
- package/dist/{operations-CTo44gPu.js → operations-CNqEBD__.js} +1505 -3285
- package/dist/operations-CNqEBD__.js.map +1 -0
- package/dist/operations-D7IMu_xY.js +8 -0
- package/dist/php-reader-BGx6Ii2d.js +2 -0
- package/dist/{php-reader-3Fgw80zK.js → php-reader-DRpcRVzv.js} +6 -3
- package/dist/{php-reader-3Fgw80zK.js.map → php-reader-DRpcRVzv.js.map} +1 -1
- package/dist/{providers-CHE2ffi6.js → project-config-C3ao4Uii.js} +24 -214
- package/dist/project-config-C3ao4Uii.js.map +1 -0
- package/dist/providers-BbVPelvp.js +406 -0
- package/dist/providers-BbVPelvp.js.map +1 -0
- package/dist/report-By1-5JtO.js +37 -0
- package/dist/report-By1-5JtO.js.map +1 -0
- package/dist/report-CHZgH9Wy.js +2 -0
- package/package.json +11 -23
- package/dist/_shared-DFyy3sh8.js.map +0 -1
- package/dist/add-C-qs8iBa.js +0 -40
- package/dist/add-C-qs8iBa.js.map +0 -1
- package/dist/check-BI1orjXb.js +0 -41
- package/dist/check-BI1orjXb.js.map +0 -1
- package/dist/cli-DsFuVJNP.js +0 -74
- package/dist/cli-DsFuVJNP.js.map +0 -1
- package/dist/config/framework/stubs/unplugin-vue-i18n-1ud7-Ly8.d.ts +0 -25
- package/dist/config/framework/stubs/unplugin-vue-i18n-1ud7-Ly8.d.ts.map +0 -1
- package/dist/config/framework/stubs/unplugin-vue-i18n.js +0 -26
- package/dist/config/framework/stubs/unplugin-vue-i18n.js.map +0 -1
- package/dist/define-config-BaZ1dZph.d.ts.map +0 -1
- package/dist/detect-7cVfsNJP.js +0 -17
- package/dist/detect-7cVfsNJP.js.map +0 -1
- package/dist/empty-DB8Hl_8u.js +0 -36
- package/dist/empty-DB8Hl_8u.js.map +0 -1
- package/dist/find-duplicates-CmEqBfby.js +0 -42
- package/dist/find-duplicates-CmEqBfby.js.map +0 -1
- package/dist/get-BE21Iy6N.js +0 -41
- package/dist/get-BE21Iy6N.js.map +0 -1
- package/dist/index-DCG3dOdC.d.ts.map +0 -1
- package/dist/init-BtzmhsOM.js +0 -33
- package/dist/init-BtzmhsOM.js.map +0 -1
- package/dist/list-dirs-DUQbwvlE.js +0 -17
- package/dist/list-dirs-DUQbwvlE.js.map +0 -1
- package/dist/missing-CcYrgd54.js +0 -51
- package/dist/missing-CcYrgd54.js.map +0 -1
- package/dist/move-B2px6Ck-.js +0 -50
- package/dist/move-B2px6Ck-.js.map +0 -1
- package/dist/operations-CTo44gPu.js.map +0 -1
- package/dist/php-reader-CpnaPSpZ.js +0 -2
- package/dist/providers-CHE2ffi6.js.map +0 -1
- package/dist/remove-DTbqM4bR.js +0 -41
- package/dist/remove-DTbqM4bR.js.map +0 -1
- package/dist/remove-orphans-Dfzu7hRz.js +0 -57
- package/dist/remove-orphans-Dfzu7hRz.js.map +0 -1
- package/dist/rename-C_rFKkU4.js +0 -45
- package/dist/rename-C_rFKkU4.js.map +0 -1
- package/dist/rename-notice-BV8HNX3O.js +0 -25
- package/dist/rename-notice-BV8HNX3O.js.map +0 -1
- package/dist/rename-notice-Cx1vuTRz.js +0 -2
- package/dist/scaffold-CUimlNqR.js +0 -39
- package/dist/scaffold-CUimlNqR.js.map +0 -1
- package/dist/scan-BkOIYhjZ.js +0 -31
- package/dist/scan-BkOIYhjZ.js.map +0 -1
- package/dist/search-0io1BcsY.js +0 -49
- package/dist/search-0io1BcsY.js.map +0 -1
- package/dist/status-Bp-SzjTN.js +0 -45
- package/dist/status-Bp-SzjTN.js.map +0 -1
- package/dist/translate-cCb2Z57w.js +0 -92
- package/dist/translate-cCb2Z57w.js.map +0 -1
- package/dist/translate-key-DHLWEmrA.js +0 -73
- package/dist/translate-key-DHLWEmrA.js.map +0 -1
- package/dist/update-DV4ijRVB.js +0 -40
- package/dist/update-DV4ijRVB.js.map +0 -1
- package/dist/write-Czzm_Nb0.js +0 -47
- package/dist/write-Czzm_Nb0.js.map +0 -1
- /package/dist/{bin-DrKRgnr9.d.ts → bin-NyzIHE2F.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptors-10sgyqEs.js","names":[],"sources":["../src/core/codequality.ts","../src/surface/report.ts","../src/surface/types.ts","../src/surface/guidance.ts","../src/surface/descriptors.ts"],"sourcesContent":["/**\n * GitLab Code Quality (CodeClimate) mapping — pure result-shape → issue-array\n * transforms, no I/O.\n *\n * Format contract (https://docs.gitlab.com/ci/testing/code_quality/):\n * every issue needs description, check_name, fingerprint, severity, and\n * location.path + location.lines.begin, where path is project-root-relative\n * (no leading `./`) and lines.begin is an integer ≥ 1.\n *\n * Fingerprints deliberately exclude line numbers so unrelated edits that\n * shift a usage do not churn findings as new/resolved in the MR widget.\n */\n\nimport { createHash } from 'node:crypto'\nimport { join, relative } from 'node:path'\n\nimport type { I18nConfig, LocaleDefinition } from '../config/types.js'\n\nimport type { UndefinedKeyFinding } from './ops-check.js'\nimport type { FindDuplicateKeysResult } from './ops-duplicates.js'\nimport { findLocaleImpl, resolveReferenceLocale } from './shared.js'\nimport type { MissingTranslationsResult, TranslationStatusResult } from './types.js'\n\nexport interface CodeQualityIssue {\n description: string\n check_name: string\n fingerprint: string\n severity: 'info' | 'minor' | 'major' | 'critical' | 'blocker'\n location: {\n path: string\n lines: { begin: number }\n }\n}\n\nconst UNDEFINED_KEY_CHECK = 'i18n.undefined-key'\nconst ORPHAN_KEY_CHECK = 'i18n.orphan-key'\nconst MISSING_TRANSLATION_CHECK = 'i18n.missing-translation'\nconst INCOMPLETE_LOCALE_CHECK = 'i18n.incomplete-locale'\nconst UNCONSUMED_LAYER_CHECK = 'i18n.unconsumed-layer'\nconst DUPLICATE_KEY_CHECK = 'i18n.duplicate-key'\n\n/** NUL-joined so no part can bleed into its neighbor (keys/paths never contain NUL). */\nfunction fingerprintOf(...parts: string[]): string {\n return createHash('sha256').update(parts.join('\\0')).digest('hex')\n}\n\n/** Normalize to the report's path shape: forward slashes, no leading `./`. */\nfunction toReportPath(path: string): string {\n const normalized = path.replaceAll('\\\\', '/')\n return normalized.startsWith('./') ? normalized.slice(2) : normalized\n}\n\n/** GitLab rejects non-positive or fractional line numbers. */\nfunction toLineBegin(line: number): number {\n return Math.max(1, Math.trunc(line))\n}\n\n/**\n * `check` findings → one issue per usage location. Uncertain findings are\n * not mapped: the widget has no \"maybe\" state, and a hard-looking finding\n * for an unverifiable key would train consumers to ignore the report.\n */\nexport function undefinedKeysToCodeQuality(undefinedKeys: UndefinedKeyFinding[]): CodeQualityIssue[] {\n const issues: CodeQualityIssue[] = []\n for (const finding of undefinedKeys) {\n for (const usage of finding.usages) {\n const path = toReportPath(usage.file)\n issues.push({\n description: `i18n key \"${finding.key}\" is referenced here but defined in no locale file of the consuming app's layers — it renders as the raw key at runtime.`,\n check_name: UNDEFINED_KEY_CHECK,\n fingerprint: fingerprintOf(UNDEFINED_KEY_CHECK, finding.key, path),\n severity: 'major',\n location: { path, lines: { begin: toLineBegin(usage.line) } },\n })\n }\n }\n return issues\n}\n\n/**\n * Orphan findings → one issue per key, anchored at the layer's\n * reference-locale file (line 1): orphans live in locale files, not code.\n * The fingerprint is anchor-independent (layer + key), so locale-file\n * renames or layout changes do not churn findings. Uncertain,\n * dynamic-matched, and misplaced keys are not part of `orphansByLayer`\n * and therefore never mapped.\n */\nexport function orphanKeysToCodeQuality(\n orphansByLayer: Record<string, string[]>,\n anchorFileByLayer: Record<string, string>,\n): CodeQualityIssue[] {\n const issues: CodeQualityIssue[] = []\n for (const [layer, keys] of Object.entries(orphansByLayer)) {\n // A missing anchor (layer absent from config) degrades to the layer name\n // as path — the fingerprint is path-independent, so findings stay stable.\n const path = toReportPath(anchorFileByLayer[layer] ?? layer)\n for (const key of keys) {\n issues.push({\n description: `Orphan i18n key \"${key}\" in layer \"${layer}\" has no source-code reference — it may be consumed dynamically; verify before deleting.`,\n check_name: ORPHAN_KEY_CHECK,\n fingerprint: fingerprintOf(ORPHAN_KEY_CHECK, layer, key),\n severity: 'minor',\n location: { path, lines: { begin: 1 } },\n })\n }\n }\n return issues\n}\n\n/**\n * The orphan findings of a result — whatever the run was asked to do with\n * them — mapped and anchored in one call.\n *\n * Takes the result rather than the scan internals, which is what lets the\n * mapping run where the result is handed over instead of inside the operation.\n * A removal run reports what it deleted; either way the keys are the same set\n * the scan found, and nothing else in the result is a finding.\n */\nexport function orphanResultToCodeQuality(\n result: { orphanKeys?: Record<string, string[]>; removed?: Record<string, string[]> },\n ctx: { config: I18nConfig; projectDir: string; locale?: string },\n): CodeQualityIssue[] {\n const orphansByLayer = result.orphanKeys ?? result.removed ?? {}\n const { localeDef } = resolveReferenceLocale(ctx.config, ctx.locale)\n return orphanKeysToCodeQuality(\n orphansByLayer,\n referenceLocaleAnchorPaths(ctx.config, Object.keys(orphansByLayer), localeDef, ctx.projectDir),\n )\n}\n\n/**\n * The locale file of one layer, or the layer name when the config has no path\n * for it. Every issue below anchors at a locale file rather than a call site:\n * these findings are about what a file holds, so there is no line to point at\n * and `lines.begin` is 1 throughout.\n */\nfunction anchorPathOf(\n ctx: { config: I18nConfig; projectDir: string },\n layer: string,\n localeRef: string,\n): string {\n const locale = findLocaleImpl(ctx.config, localeRef)\n const anchors = locale === undefined\n ? {}\n : referenceLocaleAnchorPaths(ctx.config, [layer], locale, ctx.projectDir)\n return toReportPath(anchors[layer] ?? layer)\n}\n\n/**\n * `missing` findings → one issue per key per locale, anchored at the locale file\n * of the layer that is short of it.\n *\n * Minor rather than major: a missing key falls back to another locale and\n * renders text, where an undefined key renders its own name at the user.\n */\nexport function missingTranslationsToCodeQuality(\n result: MissingTranslationsResult,\n ctx: { config: I18nConfig; projectDir: string },\n): CodeQualityIssue[] {\n const issues: CodeQualityIssue[] = []\n for (const [localeCode, byLayer] of Object.entries(result.missing)) {\n const locale = findLocaleImpl(ctx.config, localeCode)\n // Resolved once per locale rather than per key: a locale short of a\n // thousand keys would otherwise walk the config a thousand times.\n const anchors = locale === undefined\n ? {}\n : referenceLocaleAnchorPaths(ctx.config, Object.keys(byLayer), locale, ctx.projectDir)\n\n for (const [layer, keys] of Object.entries(byLayer)) {\n const path = toReportPath(anchors[layer] ?? layer)\n for (const key of keys) {\n issues.push({\n description: `Missing translation for \"${key}\" in ${localeCode} — the reference locale defines it, layer \"${layer}\" does not carry it in this locale.`,\n check_name: MISSING_TRANSLATION_CHECK,\n fingerprint: fingerprintOf(MISSING_TRANSLATION_CHECK, layer, localeCode, key),\n severity: 'minor',\n location: { path, lines: { begin: 1 } },\n })\n }\n }\n }\n return issues\n}\n\n/**\n * `status` findings → one issue per locale under the bar, plus one per layer no\n * app consumes.\n *\n * The bar is `failUnder` when the caller named one and 100% otherwise, so the\n * report says what the gate says. A locale is `info`: coverage is a figure that\n * moves with every merge request and is not a defect. An unconsumed layer is\n * `minor` — keys nothing can render, which is a fact about the project rather\n * than about today's translation work.\n *\n * Protected locales are skipped. Their gaps are deliberate and already excluded\n * from the overall figure; reporting them would be reporting a decision.\n */\nexport function statusToCodeQuality(\n result: TranslationStatusResult,\n ctx: { config: I18nConfig; projectDir: string; failUnder?: number },\n): CodeQualityIssue[] {\n const threshold = ctx.failUnder ?? 100\n // A locale's coverage spans every scanned layer, so no single file is behind\n // it; the first scanned layer is where the report stands to say so.\n const primaryLayer = result.summary.layersScanned[0] ?? ''\n const issues: CodeQualityIssue[] = []\n\n for (const locale of result.locales) {\n if (locale.protected === true || locale.completion >= threshold) continue\n issues.push({\n description: `Locale \"${locale.code}\" is ${locale.completion}% translated, below the ${threshold}% expected — ${locale.missing} key(s) missing, ${locale.empty} empty.`,\n check_name: INCOMPLETE_LOCALE_CHECK,\n // Neither the percentage nor the counts are part of this: a locale gaining\n // one key would otherwise resolve its finding and open a new one.\n fingerprint: fingerprintOf(INCOMPLETE_LOCALE_CHECK, locale.code),\n severity: 'info',\n location: {\n path: anchorPathOf(ctx, primaryLayer, locale.code) || locale.code,\n lines: { begin: 1 },\n },\n })\n }\n\n const referenceCode = result.summary.referenceLocale.code\n for (const layer of result.summary.unconsumedLayers) {\n issues.push({\n description: `Layer \"${layer}\" is consumed by no app — the keys it defines cannot render anywhere. Fold it into a consumed layer or declare the app that uses it.`,\n check_name: UNCONSUMED_LAYER_CHECK,\n fingerprint: fingerprintOf(UNCONSUMED_LAYER_CHECK, layer),\n severity: 'minor',\n location: { path: anchorPathOf(ctx, layer, referenceCode), lines: { begin: 1 } },\n })\n }\n\n return issues\n}\n\n/**\n * `find-duplicates` collisions → one issue per key defined in both a shared and\n * a consuming layer, anchored at the shadowing layer's file: that is the copy\n * whose value wins at runtime and the one a reader has to look at.\n *\n * Value duplicates (`byValue`) are not mapped. Two keys carrying the same text\n * are a consolidation opportunity, not a defect in the file, and the group has\n * no single key or layer to anchor a stable fingerprint on.\n */\nexport function duplicateKeysToCodeQuality(\n result: FindDuplicateKeysResult,\n ctx: { config: I18nConfig; projectDir: string; locale?: string },\n): CodeQualityIssue[] {\n const { localeDef } = resolveReferenceLocale(ctx.config, ctx.locale)\n const anchors = referenceLocaleAnchorPaths(\n ctx.config,\n [...new Set(result.collisions.map(collision => collision.childLayer))],\n localeDef,\n ctx.projectDir,\n )\n\n return result.collisions.map(collision => ({\n description: `i18n key \"${collision.key}\" is defined in both \"${collision.sharedLayer}\" and \"${collision.childLayer}\"`\n + (collision.divergent\n ? ', with different values — the shared value never shows. Delete one side; never move the key.'\n : ', with the same value — the shared definition never shows. Delete one side; never move the key.'),\n check_name: DUPLICATE_KEY_CHECK,\n // Values are left out on purpose: rewording one side is not a new finding.\n fingerprint: fingerprintOf(DUPLICATE_KEY_CHECK, collision.sharedLayer, collision.childLayer, collision.key),\n severity: 'minor' as const,\n location: {\n path: toReportPath(anchors[collision.childLayer] ?? collision.childLayer),\n lines: { begin: 1 },\n },\n }))\n}\n\n/**\n * Project-root-relative reference-locale file path per layer, the anchor for\n * orphan issues. Aliases resolve to their target layer's directory. Flat\n * layouts anchor at the locale file; namespaced layouts (per-locale\n * directories) have no single file and anchor at the locale's directory.\n */\nexport function referenceLocaleAnchorPaths(\n config: I18nConfig,\n layers: string[],\n locale: LocaleDefinition,\n projectDir: string,\n): Record<string, string> {\n const anchors: Record<string, string> = {}\n for (const layer of layers) {\n const localeDir = config.localeDirs.find(d => d.layer === layer)\n const resolved = localeDir?.aliasOf\n ? config.localeDirs.find(d => d.layer === localeDir.aliasOf) ?? localeDir\n : localeDir\n if (!resolved) continue\n const absPath = locale.file\n ? join(resolved.path, locale.file)\n : join(resolved.path, locale.code)\n anchors[layer] = toReportPath(relative(projectDir, absPath))\n }\n return anchors\n}\n","/**\n * File-diversion of a large result, as the surfaces apply it.\n *\n * An operation returns its whole result and knows nothing about report files.\n * Everything the diversion needs is surface vocabulary — the `outputFile`\n * parameter, the `reportOutput` directory a project configures, the tool name\n * the file is written under — so it lives here, once, and both the command\n * factory and the tool registrar call it. That is what keeps every operation\n * returning exactly one shape and keeps the names of MCP tools out of the\n * engine.\n *\n * The heavy modules are imported per run rather than at module load: the\n * descriptor table is read by `--help`, by the tool registrar and by the\n * reference generator, none of which write a report.\n */\n\nimport type { AnyOperationDescriptor, AnyReportSpec, GateSpec, Params } from './types.js'\n\n/** The parameters a `report` declaration adds, in the order they are offered. */\nexport const REPORT_PARAM_NAMES = ['outputFile', 'codequalityOutput'] as const\n\n/**\n * The parameters that request a diversion, built from what the operation\n * declared about its report. One description per concept, filled in with the\n * operation's own example, rather than one hand-written variation per command.\n */\nexport function reportParams(spec: AnyReportSpec): Params {\n return {\n outputFile: {\n type: 'string',\n description: `Absolute path to write the full JSON output to. Only a compact summary is returned to the caller, which is what you want for a result too large to read in one piece. Example: \"${spec.outputFile.example}\"`,\n ...(spec.outputFile.cli === undefined ? {} : { cli: spec.outputFile.cli }),\n ...(spec.outputFile.mcp === undefined ? {} : { mcp: spec.outputFile.mcp }),\n },\n ...(spec.codequality === undefined\n ? {}\n : {\n codequalityOutput: {\n type: 'string' as const,\n description: `Also write the ${spec.codequality.findings} as a GitLab Code Quality (CodeClimate) JSON report to this file path.`,\n // A pipeline artifact means nothing to an MCP host, which reads the\n // result it is handed rather than a file a runner collects\n // afterwards.\n mcp: { hidden: true },\n },\n }),\n }\n}\n\n/**\n * An operation's parameters with the report parameters merged in.\n *\n * They sit after what the operation takes and before the flags that only\n * decide an exit code, which is where they were written by hand and where\n * `--help` and the generated reference still show them.\n */\nexport function withReportParams(\n params: Params,\n gates: GateSpec[] | undefined,\n spec: AnyReportSpec,\n): Params {\n const gateFlags = flagsOf(gates)\n const declared = Object.entries(params)\n return Object.fromEntries([\n ...declared.filter(([name]) => !gateFlags.has(name)),\n ...Object.entries(reportParams(spec)),\n ...declared.filter(([name]) => gateFlags.has(name)),\n ])\n}\n\n/**\n * Write the result where the caller asked for it, and hand back the compact\n * stand-in — or hand back the result untouched when nothing asked for a file.\n *\n * Called with the arguments the operation ran with, which is what lets an\n * operation answering several questions write each under its own report name.\n */\nexport async function divertToReport(\n result: unknown,\n descriptor: AnyOperationDescriptor,\n args: Record<string, unknown>,\n): Promise<unknown> {\n const spec = descriptor.report\n if (spec === undefined || result === null || typeof result !== 'object') return result\n\n const projectDir = typeof args.projectDir === 'string' ? args.projectDir : process.cwd()\n const [\n { detectI18nConfig },\n { resolveOutputFile, resolveReportFilePath, validateReportPath },\n { writeCodequalityFile, writeReportFile },\n ] = await Promise.all([\n import('../config/detector.js'),\n import('../core/report.js'),\n import('../io/json-writer.js'),\n ])\n // A cache hit: the operation detected the same project directory a moment ago.\n const config = await detectI18nConfig(projectDir)\n\n const codequalityOutput = pathArg(args.codequalityOutput)\n if (spec.codequality !== undefined && codequalityOutput !== undefined) {\n const issues = spec.codequality.issues(result, { projectDir, config, args })\n if (issues !== undefined) {\n validateReportPath(projectDir, codequalityOutput)\n await writeCodequalityFile(codequalityOutput, issues)\n }\n }\n\n const name = typeof spec.name === 'function' ? spec.name(args) : spec.name\n const reportFile = resolveOutputFile(projectDir, pathArg(args.outputFile))\n ?? resolveReportFilePath(config, projectDir, name)\n if (reportFile === undefined) return result\n\n await writeReportFile(reportFile, result, { tool: name, args: requestedArgs(descriptor, args) })\n return { reportFile, summary: spec.summary(result) }\n}\n\n/**\n * What the report records as the request behind it: what the caller actually\n * asked for. Arguments left at their default say nothing a reader of the file\n * does not already know, the report parameters are how the file was asked for\n * rather than part of the question, and a gate flag decides an exit code and\n * nothing about the result.\n */\nfunction requestedArgs(\n descriptor: AnyOperationDescriptor,\n args: Record<string, unknown>,\n): Record<string, unknown> {\n const excluded = new Set<string>([...REPORT_PARAM_NAMES, ...flagsOf(descriptor.gates)])\n const requested: Record<string, unknown> = {}\n for (const [name, spec] of Object.entries(descriptor.params)) {\n const value = args[name]\n if (excluded.has(name) || value === undefined || value === spec.default) continue\n requested[name] = value\n }\n return requested\n}\n\nfunction flagsOf(gates: GateSpec[] | undefined): Set<string> {\n const flags = new Set<string>()\n for (const gate of gates ?? []) {\n if (gate.flag !== undefined) flags.add(gate.flag)\n }\n return flags\n}\n\n/** A path parameter as the surfaces deliver it; anything else is not a path. */\nfunction pathArg(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n","/**\n * The declaration language both surfaces are built from.\n *\n * A descriptor states an operation once — what it does, what it takes, what it\n * runs — and the CLI command factory and the MCP tool registrar each read the\n * same table. The two surfaces used to be independent transcriptions of the\n * same fifteen operations, which is why a parameter could be called `ref` in\n * one and `referenceLocale` in the other, with descriptions that had drifted\n * apart and nothing to notice.\n *\n * A parameter therefore has ONE name. Where the CLI used a shorter spelling it\n * survives as an alias, so `--ref` keeps working while the name both surfaces\n * agree on is `referenceLocale`. The only other latitude a surface has is to\n * hide a parameter it genuinely does not have — a CI gate flag means nothing\n * over MCP, a provider flag means nothing to a server that resolves its backend\n * from the environment — and hiding one has to be written down, which is what\n * the drift test reads.\n */\n\nimport type { I18nConfig } from '../config/types.js'\nimport type { CodeQualityIssue } from '../core/codequality.js'\nimport type { ProgressFn, TranslateFn } from '../core/types.js'\nimport { withReportParams } from './report.js'\n\n/** Which surface is invoking an operation. They differ only in the prose they own. */\nexport type Surface = 'cli' | 'mcp'\n\n/**\n * What a parameter carries.\n *\n * `string[]` is a list on both surfaces: MCP passes a JSON array, the CLI a\n * comma-separated string it splits. `record` is the one nested shape on the\n * surface — the key → locale → value map `write` takes — so the builders state\n * its inner levels once instead of every spec carrying a schema.\n */\nexport type ParamType = 'string' | 'boolean' | 'number' | 'string[]' | 'record'\n\n/** The value shape of a `record` parameter: dot-path key → locale ref → string. */\nexport type TranslationsRecord = Record<string, Record<string, string>>\n\nexport interface CliParamOptions {\n /**\n * Additional spellings citty accepts for this flag. Single characters render\n * as `-d`; longer ones are the flag's previous name, kept working after the\n * descriptor took the name the MCP tool used.\n */\n alias?: string | readonly string[]\n /** Not exposed as a flag. Every use of this states why in a comment. */\n hidden?: boolean\n}\n\nexport interface McpParamOptions {\n /** Not part of the tool's input schema. Every use of this states why in a comment. */\n hidden?: boolean\n}\n\n/** One parameter of an operation, as both surfaces expose it. */\nexport interface ParamSpec {\n type: ParamType\n /**\n * The one description. It reaches `--help`, the generated CLI page and the\n * JSON Schema an MCP host hands its model, so it is written for a reader who\n * knows neither surface: no `--flag` spellings, no \"this tool\".\n */\n description: string\n required?: boolean\n /**\n * Applied by the CLI so `--help` can state it. Deliberately not put into the\n * JSON Schema: the operations already apply their own defaults, and a schema\n * default would make a host send a value the caller never chose.\n */\n default?: unknown\n /** The accepted values. The CLI validates against them and MCP emits an enum. */\n enum?: readonly string[]\n /** Numbers only: reject a fractional value. */\n integer?: boolean\n /** Numbers only: reject anything below this. */\n min?: number\n /** `string[]` parameters that also accept the literal \"all\". */\n allowAll?: boolean\n cli?: CliParamOptions\n mcp?: McpParamOptions\n}\n\nexport type Params = Record<string, ParamSpec>\n\n/**\n * A CI gate a command evaluates. `counter` is the field of `result.summary`\n * carrying the observed value.\n *\n * The two shapes are a union rather than one type with optional halves so the\n * invariants hold at compile time: a flagged gate always has a flag to read\n * its name and threshold from, and a flagless one always carries both itself.\n * Stated as options, `{ counter, threshold }` type-checks and then has no name\n * to report the gate under.\n */\nexport type GateSpec = FlaggedGateSpec | AlwaysOnGateSpec\n\ninterface GateSpecBase {\n counter: string\n /** 'above' trips when observed > threshold (default); 'below' when observed < threshold. */\n direction?: 'above' | 'below'\n}\n\n/**\n * Requested by a flag, and evaluated only when that flag is passed. Omitting\n * `threshold` takes it from the flag's own value, so a boolean flag pairs with\n * `threshold: 0` and a numeric one (`--failUnder 90`) omits it.\n */\nexport interface FlaggedGateSpec extends GateSpecBase {\n flag: string\n name?: never\n threshold?: number\n}\n\n/**\n * Always evaluated, for findings that are a defect rather than a threshold — a\n * key that renders raw in production is not something you opt into caring\n * about. It still reports as a gate: the run succeeded, and what it found is\n * what you are being told about.\n */\nexport interface AlwaysOnGateSpec extends GateSpecBase {\n flag?: never\n name: string\n threshold: number\n}\n\n/**\n * What the surface hands a report builder beyond the result: the project the\n * operation ran against and the arguments it ran with.\n */\nexport interface ReportContext<A = Record<string, unknown>> {\n projectDir: string\n /** The project's resolved i18n configuration, as the operation itself read it. */\n config: I18nConfig\n /** The operation's arguments, as the surface resolved them. */\n args: A\n}\n\n/**\n * How a result too large to hand back is diverted to a file.\n *\n * An operation always returns its whole result. Whether that result is written\n * to disk and replaced by a compact stand-in is the surface's decision — it is\n * the surface that owns `outputFile`, the configured report directory and the\n * name the file is written under — so the operations know nothing about any of\n * it, and a descriptor declaring this is what gives an operation the parameters\n * that request it.\n */\nexport interface ReportSpec<R = unknown, A = Record<string, unknown>> {\n /**\n * The file's base name under the configured report directory, and the tool\n * name recorded inside the report. A function where one operation answers\n * different questions and writes each under its own name — pipelines archive\n * these paths, so they are part of the contract.\n */\n name: string | ((args: A) => string)\n /** The compact stand-in returned once the full result is on disk. */\n summary: (result: R) => unknown\n /**\n * The `outputFile` parameter as this operation offers it. Declared here and\n * nowhere else, so an operation cannot advertise the parameter without the\n * plumbing behind it, nor grow the plumbing without the parameter.\n */\n outputFile: {\n /** The example path its description carries. */\n example: string\n cli?: CliParamOptions\n mcp?: McpParamOptions\n }\n /** The same findings, as a GitLab Code Quality report the pipeline collects. */\n codequality?: {\n /** What the findings are called in the parameter's description. */\n findings: string\n /**\n * Returning undefined writes nothing, which is not the same as writing an\n * empty array: the empty array is the baseline the merge-request widget\n * diffs against, so it is only right for a run that looked for these\n * findings and found none.\n */\n issues: (result: R, ctx: ReportContext<A>) => CodeQualityIssue[] | undefined\n }\n}\n\n/**\n * A report spec with its result type erased, as the runner sees it.\n *\n * `any` rather than `unknown`: the runner holds a result whose type it cannot\n * know, while every declaration site is checked against its own operation's\n * result type.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see above\nexport type AnyReportSpec = ReportSpec<any>\n\n/** How the MCP server advertises an operation as a tool. */\nexport interface McpToolSpec {\n name: string\n /** The display title a host shows. */\n title: string\n /** Behaviour hints a host reads. Only the translating tools declare any. */\n annotations?: {\n title?: string\n readOnlyHint?: boolean\n }\n}\n\n/** How the CLI exposes an operation as a command. */\nexport interface CliCommandSpec {\n name: string\n}\n\n/**\n * What a surface hands an operation beyond its parameters.\n *\n * `surface` exists because the guidance an agent-mode result carries is the\n * caller's, not the operation's: a terminal is told to pass `--provider`, a\n * host is told to translate the fallback contexts inline. Keeping both strings\n * in one place is the point — they used to sit in two files and say different\n * things about the same state.\n */\nexport interface OperationContext {\n surface: Surface\n /** Provider-backed translation, when the surface resolved one. */\n translateFn?: TranslateFn\n /**\n * Progress reporting, when the caller asked for it. Only MCP does — a\n * terminal is left with its exit code. The operations that take long enough\n * to report call it (translating, orphan scans); the rest never do.\n */\n progressFn?: ProgressFn\n /**\n * The number of steps the operation is about to report, counted in whatever\n * unit it reports in. Called once, before the first `progressFn` call, so a\n * notification never goes out against an unknown total.\n */\n onProgressTotal?: (total: number) => void\n}\n\n/**\n * The value of one parameter as the operation receives it: already split,\n * parsed and validated by whichever surface took it.\n */\ntype ParamValue<S extends ParamSpec>\n = S extends { type: 'boolean' } ? boolean\n : S extends { type: 'number' } ? number\n : S extends { type: 'record' } ? TranslationsRecord\n : S extends { type: 'string[]', allowAll: true } ? string[] | 'all'\n : S extends { type: 'string[]' } ? string[]\n : S extends { type: 'string', enum: readonly (infer E extends string)[] } ? E\n : S extends { type: 'string' } ? string\n : never\n\ntype RequiredParamNames<P extends Params> = {\n [K in keyof P]-?: P[K] extends { required: true } ? K : never\n}[keyof P]\n\n/**\n * The arguments an operation's `run` receives. Required parameters are present,\n * the rest are optional, and `projectDir` is always available because both\n * surfaces resolve it themselves (the CLI from `--projectDir`, the server from\n * I18N_PROJECT_DIR) rather than making every operation declare it.\n */\nexport type OperationArgs<P extends Params = Params> =\n & { [K in RequiredParamNames<P> & keyof P]: ParamValue<P[K]> }\n & { [K in Exclude<keyof P, RequiredParamNames<P>>]?: ParamValue<P[K]> }\n & { projectDir?: string }\n\n/** One operation, as both surfaces read it. */\nexport interface OperationDescriptor<P extends Params = Params> {\n /** Stable identifier, independent of what either surface calls it. */\n id: string\n /** Null for an operation the CLI does not expose. */\n cli: CliCommandSpec | null\n /** Null for an operation the MCP server does not advertise. */\n mcp: McpToolSpec | null\n /** One sentence, on both surfaces. */\n description: string\n /**\n * Further prose for a model deciding whether to call the tool: when to reach\n * for it, what the result carries, what it will not do. MCP only — a `--help`\n * line has to stay a line, and the generated CLI page says the same things in\n * its flag table.\n */\n longDescription?: string\n params: P\n /** CI gates the CLI evaluates. Exit codes are a CLI notion, so MCP ignores these. */\n gates?: GateSpec[]\n /**\n * The operation translates, so the surface has to hand it a backend: the CLI\n * resolves one from the provider flags, the server from its startup\n * environment. Without one the operation returns contexts to translate by\n * hand rather than failing.\n */\n usesTranslateFn?: boolean\n /**\n * The result is large enough to be worth writing to a file instead of\n * returning. Declaring this is what adds the parameters that request it and\n * what makes the configured report directory apply to the operation.\n *\n * The result type stays open: a `run` whose parameters are contextually typed\n * is not an inference site, so nothing here can see the operation's own\n * result type. Declaration sites annotate it on the builder instead, which is\n * what type-checks them.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see above\n report?: ReportSpec<any, OperationArgs<P>>\n /**\n * Declared as a method so the table can hold descriptors with different\n * parameter maps: method parameters are compared bivariantly, which is what\n * lets `run` be typed against each operation's own arguments and still be\n * callable through the erased element type below.\n */\n run(args: OperationArgs<P>, ctx: OperationContext): Promise<unknown>\n}\n\n/**\n * A descriptor as the registrars see it, with `run` erased to plain arguments.\n * They build those arguments from a schema at runtime and cannot know the\n * per-operation type, so the erasure happens once, in `defineOperation`,\n * instead of at every call site.\n */\nexport type AnyOperationDescriptor = Omit<OperationDescriptor<Params>, 'run' | 'params' | 'report'> & {\n params: Params\n report?: AnyReportSpec\n run(args: Record<string, unknown>, ctx: OperationContext): Promise<unknown>\n}\n\n/**\n * Declare one operation. The `const` type parameter is what keeps `required:\n * true` and `enum: [...]` literal, so `run` receives `layer: string` rather\n * than `string | undefined` for a parameter the surface guarantees.\n *\n * An operation that declares a `report` gains the parameters that request one\n * here, so the two cannot drift apart.\n */\nexport function defineOperation<const P extends Params>(\n descriptor: OperationDescriptor<P>,\n): AnyOperationDescriptor {\n const erased = descriptor as unknown as AnyOperationDescriptor\n if (erased.report === undefined) return erased\n return { ...erased, params: withReportParams(erased.params, erased.gates, erased.report) }\n}\n","/**\n * The guidance a translate result carries when nothing was translated, or when\n * only part of it was.\n *\n * This is the caller's prose, not the operation's: a terminal is told to pass\n * `--provider`, a host is told to translate the fallback contexts inline and\n * write them back. Both live here, side by side, because they describe the same\n * state — as two texts in two packages they said different things about it, and\n * the all-layers case was covered on one surface only.\n */\n\nimport type {\n TranslateKeyResult,\n TranslateMissingOutcome,\n TranslateMissingResult,\n} from '../core/types.js'\nimport type { Surface } from './types.js'\n\nconst NO_PROVIDER_CLI = 'No provider configured — nothing was translated. Pass --provider and --model '\n + '(API key via --apiKey or the OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY env vars) '\n + 'to translate automatically.'\n\nconst AGENT_MODE_MCP = 'Agent mode — no provider configured on the server. Use the fallbackContexts to translate '\n + 'inline, then call write_translations (mode: \"upsert\") to write the results. To enable '\n + 'provider mode, set I18N_PROVIDER, I18N_MODEL, and the provider API key env on the server process.'\n\nconst AGENT_MODE_KEY_MCP = 'Agent mode — no provider configured on the server. Use the fallbackContext to '\n + 'translate inline, then call write_translations (mode: \"upsert\") to write the results. To enable '\n + 'provider mode, set I18N_PROVIDER, I18N_MODEL, and the provider API key env on the server process.'\n\n/**\n * Locales that lost at least one key, sorted for a stable message.\n * A bare `totalFailed: 141` says nothing about which languages shipped\n * incomplete — this is what makes the count actionable in a CI log.\n * Reads the full and the compact shapes, since either may reach here.\n */\nexport function localesWithFailures(result: TranslateMissingOutcome): string[] {\n const perLayer: TranslateMissingResult[] = 'layers' in result\n ? Object.values(result.layers)\n : [result]\n\n const locales = new Set<string>()\n for (const layer of perLayer) {\n for (const [locale, entry] of Object.entries(layer.results ?? {})) {\n if (entry.failed.length > 0) locales.add(locale)\n }\n for (const entry of layer.summary.byLocale ?? []) {\n if (entry.failed > 0) locales.add(entry.locale)\n }\n }\n return [...locales].sort()\n}\n\n/** True when any layer of the outcome carries fallback contexts to translate by hand. */\nfunction hasFallbackContexts(result: TranslateMissingOutcome): boolean {\n // All-layers mode nests the fallback contexts per layer, so checking only the\n // top level would skip the guidance in exactly the case a layered project\n // hits by default.\n return 'layers' in result\n ? Object.values(result.layers).some(layer => layer.fallbackContexts)\n : Boolean(result.fallbackContexts)\n}\n\nexport function applyTranslateMissingGuidance(\n result: TranslateMissingOutcome,\n surface: Surface,\n): void {\n if (surface === 'mcp') {\n if (hasFallbackContexts(result) && result.summary) {\n (result.summary as Record<string, unknown>).message = AGENT_MODE_MCP\n }\n return\n }\n\n // Single-layer and all-layers results both carry summary.mode, so this needs\n // no narrowing.\n if (result.summary.mode === 'agent') {\n result.summary.message = NO_PROVIDER_CLI\n return\n }\n\n if (result.summary.totalFailed > 0) {\n // A partial failure exits 0 unless --failOnFailed was passed, so without\n // this the only trace of it is a count nobody reads. Re-running is the fix:\n // the keys are still missing, so the next run retries them.\n const affected = localesWithFailures(result)\n result.summary.message = `${result.summary.totalFailed} key(s) failed to translate`\n + (affected.length > 0 ? ` (locales: ${affected.join(', ')})` : '')\n + '. Those keys remain missing — re-run to retry them. '\n + 'Pass --fail-on-failed to make this exit 2 in CI.'\n }\n}\n\nexport function applyTranslateKeyGuidance(result: TranslateKeyResult, surface: Surface): void {\n if (surface === 'mcp') {\n if (result.fallbackContext) result.message = AGENT_MODE_KEY_MCP\n return\n }\n\n // At a terminal, agent mode means exactly one thing: no provider was given.\n if (result.mode === 'agent' && result.skipped.some(skip => skip.reason === 'no-provider')) {\n result.message = NO_PROVIDER_CLI\n }\n}\n","/**\n * Every operation the kit exposes, declared once.\n *\n * The CLI builds its fifteen commands from this table and the MCP server builds\n * its fifteen tools from it. Read `./types.ts` first for what a descriptor is\n * allowed to say; this file is the table itself.\n *\n * The core operations are imported per run rather than at module load. This\n * table is read by `--help`, by the tool registrar and by the reference\n * generator, none of which run anything, while the core barrel pulls in the\n * scanner, the parsers and the file writers — a quarter of a megabyte nobody\n * asking for usage text should pay for.\n */\n\nimport { BASE_URL_ENV } from '../llm/providers.js'\n// The two mappings a report can carry. Imported outright rather than per run\n// like the operations below: they are pure transforms over a result, with\n// nothing behind them but a hash and a path join.\nimport {\n duplicateKeysToCodeQuality,\n missingTranslationsToCodeQuality,\n orphanResultToCodeQuality,\n statusToCodeQuality,\n undefinedKeysToCodeQuality,\n} from '../core/codequality.js'\n// The result types the report builders below are written against. Type-only,\n// so the core barrel is still not loaded to print usage text.\nimport type { CheckUndefinedKeysResult } from '../core/ops-check.js'\nimport type { FindDuplicateKeysResult } from '../core/ops-duplicates.js'\nimport type {\n CodeUsageResult,\n FindOrphanKeysResult,\n MissingTranslationsResult,\n RemoveOrphanKeysResult,\n SearchTranslationsResult,\n TranslationStatusResult,\n} from '../core/types.js'\nimport { defineOperation } from './types.js'\nimport type { AnyOperationDescriptor, ParamSpec, Params } from './types.js'\nimport {\n applyTranslateKeyGuidance,\n applyTranslateMissingGuidance,\n} from './guidance.js'\n\nconst core = () => import('../core/operations.js')\n\n/** What the orphans command answers with, whichever of its three questions was asked. */\ntype OrphanCommandResult = FindOrphanKeysResult | RemoveOrphanKeysResult | CodeUsageResult\n\n// ─── Shared parameter fragments ──────────────────────────────────\n// One concept, one description. These used to be fifteen hand-written\n// variations that had drifted into saying different things.\n\nconst layerFilter = {\n type: 'string',\n description: 'Layer name to scope this to (e.g., \"root\", \"app-admin\"). If omitted, every layer is included. Call discover to list the layers.',\n} as const satisfies ParamSpec\n\nconst layerRequired = {\n type: 'string',\n required: true,\n description: 'Layer name from discover (e.g., \"root\", \"app-admin\").',\n} as const satisfies ParamSpec\n\nconst referenceLocale = {\n type: 'string',\n description: 'Locale code used as the source of truth (e.g., \"en\", \"en-US\"). Defaults to the project default locale.',\n // `--ref` was the CLI spelling before the two surfaces agreed on one name.\n cli: { alias: 'ref' },\n} as const satisfies ParamSpec\n\nconst readLocale = {\n type: 'string',\n description: 'Locale code to read from (e.g., \"en\", \"en-US\"). Defaults to the project default locale. Keys are the same across locales, so one is enough.',\n} as const satisfies ParamSpec\n\nconst dryRun = (description: string) => ({\n type: 'boolean',\n default: false,\n description,\n} as const satisfies ParamSpec)\n\nconst scanDirs = {\n type: 'string[]',\n description: 'Absolute paths of the directories to scan for source usage. Overrides scope-aware scanning: every layer is then checked against these directories alone. Example: [\"/home/user/my-app/apps/admin\"].',\n // Manual scan roots were never a CLI flag: a terminal run gets the\n // scope-aware plan, which is the answer that accounts for layer consumption.\n cli: { hidden: true },\n} as const satisfies ParamSpec\n\nconst excludeDirs = {\n type: 'string[]',\n description: 'Directory names to skip when scanning source files. Example: [\"storybook\", \"__tests__\", \"node_modules\"].',\n // Paired with scanDirs, and hidden on the CLI for the same reason.\n cli: { hidden: true },\n} as const satisfies ParamSpec\n\n/**\n * Provider selection, shared by the two translating operations.\n *\n * CLI only: the server resolves one backend at startup from I18N_PROVIDER,\n * I18N_MODEL and the provider's API key env, so a request cannot pick another\n * one — and an API key does not belong in a tool call an agent composes.\n */\nconst providerParams = {\n provider: {\n type: 'string',\n enum: ['openai', 'anthropic', 'google'],\n description: 'LLM provider to translate through. Without one nothing is translated automatically — the result carries the contexts to translate by hand instead.',\n mcp: { hidden: true },\n },\n model: {\n type: 'string',\n description: 'Model name. Required whenever a provider is set.',\n mcp: { hidden: true },\n },\n apiKey: {\n type: 'string',\n description: 'API key. Falls back to the OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY environment variables.',\n mcp: { hidden: true },\n },\n baseUrl: {\n type: 'string',\n description: `Provider base URL for gateways, self-hosted models and proxies speaking the provider's protocol. Falls back to ${BASE_URL_ENV}, then to providerBaseUrl in .i18n-mcp.json.`,\n mcp: { hidden: true },\n },\n} as const satisfies Params\n\n// ─── The table ───────────────────────────────────────────────────\n\n/**\n * Registry order: it is the order `the-i18n-cli --help` lists commands in and\n * the order both generated reference overviews are written in.\n */\nexport const descriptors: readonly AnyOperationDescriptor[] = [\n\n defineOperation({\n id: 'init',\n cli: { name: 'init' },\n // Writing a config file is a setup step someone takes at a terminal, with\n // the framework detection in front of them. An agent that wants one calls\n // discover and writes the file itself.\n mcp: null,\n description: 'Create a schema-valid .i18n-mcp.json from framework detection. Non-interactive; refuses to overwrite without force.',\n params: {\n force: {\n type: 'boolean',\n default: false,\n description: 'Overwrite an existing .i18n-mcp.json.',\n },\n dryRun: dryRun('Report the config that would be written without touching disk. Default: false.'),\n },\n async run(args) {\n const { initProjectConfig } = await core()\n return initProjectConfig({\n projectDir: args.projectDir,\n force: args.force,\n dryRun: args.dryRun,\n })\n },\n }),\n\n defineOperation({\n id: 'discover',\n cli: { name: 'discover' },\n mcp: { name: 'discover', title: 'Discover i18n Setup' },\n description: 'Describe the project: detected config, locale directories per layer with file counts and top-level namespaces, the layer graph, and the hand-maintained locales.',\n longDescription: 'Call this first to understand the project before reading or writing translations. The result also names the active translation mode (\"provider\" when the server has an env-configured LLM provider, \"agent\" otherwise). layerGraph answers where a new key belongs: a key used by more than one app belongs in a layer those apps share, and layerGraph.shared names those layers.',\n params: {},\n async run(args) {\n const { describeProject } = await core()\n return describeProject({ projectDir: args.projectDir })\n },\n }),\n\n defineOperation({\n id: 'list-namespaces',\n // No command: the same question is answered at a terminal by `discover`,\n // which lists the top-level namespaces per layer. Stated here rather than\n // left as an absence nobody could see.\n cli: null,\n mcp: { name: 'list_namespaces', title: 'List Namespaces' },\n description: 'List the translation key tree grouped by namespace prefix, with a count per namespace node.',\n longDescription: 'Use this to explore the available keys without guessing path prefixes.',\n params: {\n layer: {\n ...layerFilter,\n description: 'Layer name to filter by (e.g., \"root\", \"app-admin\"). If omitted or \"*\", scans all layers. Call discover to list the layers.',\n },\n locale: readLocale,\n },\n async run(args) {\n const { listNamespaces } = await core()\n return listNamespaces({ layer: args.layer, locale: args.locale, projectDir: args.projectDir })\n },\n }),\n\n defineOperation({\n id: 'get',\n cli: { name: 'get' },\n mcp: { name: 'get_translations', title: 'Get Translations' },\n description: 'Get translation values for given key paths from a specific locale and layer. Use \"*\" as the locale to read from all locales.',\n params: {\n layer: layerRequired,\n locale: {\n type: 'string',\n required: true,\n description: 'Locale code, locale file name, or \"*\" to read all locales. Examples: \"en\", \"en-US\", \"en-US.json\", \"*\".',\n },\n keys: {\n type: 'string[]',\n required: true,\n description: 'Dot-separated key paths to read. Example: [\"common.actions.save\", \"auth.login.title\"].',\n },\n compact: {\n type: 'boolean',\n description: 'When true and locale is \"*\", returns a summary grouped by key instead of per-locale detail. Default: false.',\n // Never exposed as a flag: the CLI answer is already a stream a caller\n // pipes into jq, which is where they would trim it.\n cli: { hidden: true },\n },\n },\n async run(args) {\n const { getTranslations } = await core()\n return getTranslations({\n layer: args.layer,\n locale: args.locale,\n keys: args.keys,\n compact: args.compact,\n projectDir: args.projectDir,\n })\n },\n }),\n\n defineOperation({\n id: 'write',\n cli: { name: 'write' },\n mcp: { name: 'write_translations', title: 'Write Translations' },\n description: 'Write translation key-value pairs to a layer. Keys are inserted in alphabetical order.',\n longDescription: 'Mode \"upsert\" adds new keys and updates existing ones (default, most common). Mode \"add\" only creates new keys, skipping existing ones. Mode \"update\" only modifies existing keys, skipping missing ones. Use dryRun to preview without writing.',\n params: {\n layer: layerRequired,\n translations: {\n type: 'record',\n required: true,\n description: 'Map of dot-path keys to locale-value pairs. IMPORTANT: values must be locale maps, NOT plain strings. Locale refs may be a code (\"en-us\"), a language (\"en-US\") or a file (\"en-US.json\"). Wrong: { \"auth.failed\": \"Login failed\" }. Correct: { \"auth.failed\": { \"en-US\": \"Login failed\", \"de-DE\": \"Anmeldung fehlgeschlagen\" } }',\n },\n mode: {\n type: 'string',\n enum: ['add', 'update', 'upsert'],\n default: 'upsert',\n description: 'Write mode. \"upsert\": add-or-update (never fails). \"add\": only new keys. \"update\": only existing keys. Default: \"upsert\".',\n },\n dryRun: dryRun('Return a preview of what would be written without writing any files. Default: false.'),\n },\n async run(args) {\n const { writeTranslations } = await core()\n return writeTranslations({\n layer: args.layer,\n translations: args.translations,\n mode: args.mode,\n dryRun: args.dryRun,\n projectDir: args.projectDir,\n })\n },\n }),\n\n defineOperation({\n id: 'missing',\n cli: { name: 'missing' },\n mcp: { name: 'get_missing_translations', title: 'Get Missing Translations' },\n description: 'Find translation keys that exist in the reference locale but are missing in other locales. Scans a specific layer or all layers.',\n params: {\n layer: layerFilter,\n referenceLocale,\n targetLocales: {\n type: 'string[]',\n description: 'Locale codes to check for missing keys (e.g., [\"de\", \"fr\", \"es\"]). Defaults to all locales except the reference.',\n // `--targets` was the CLI spelling before the two surfaces agreed.\n cli: { alias: 'targets' },\n },\n failOnMissing: {\n type: 'boolean',\n default: false,\n description: 'Exit 2 when any key is missing (CI gate).',\n // Exit codes exist at a terminal. A host reads the summary instead.\n mcp: { hidden: true },\n },\n },\n gates: [{ flag: 'failOnMissing', counter: 'totalMissingKeys', threshold: 0 }],\n report: {\n name: 'get_missing_translations',\n outputFile: { example: '/tmp/missing-translations.json' },\n summary: (result: MissingTranslationsResult) => result.summary,\n codequality: {\n findings: 'missing translations',\n issues: (result: MissingTranslationsResult, ctx) => missingTranslationsToCodeQuality(result, {\n config: ctx.config,\n projectDir: ctx.projectDir,\n }),\n },\n },\n async run(args) {\n const { getMissingTranslations } = await core()\n return getMissingTranslations({\n layer: args.layer,\n referenceLocale: args.referenceLocale,\n targetLocales: args.targetLocales,\n projectDir: args.projectDir,\n })\n },\n }),\n\n defineOperation({\n id: 'status',\n cli: { name: 'status' },\n mcp: { name: 'get_translation_status', title: 'Get Translation Status' },\n description: 'Translation coverage in one call: per-locale and per-layer counts of total, translated, missing and empty keys, plus an overall completion percentage.',\n longDescription: 'Use this instead of calling get_missing_translations per layer and counting keys yourself. Empty-string values count as untranslated; set listEmpty to get the keys behind that count — they exist in the locale file, so they are never reported as missing, and they render as nothing in the UI. Locales listed in protectedLocales are reported but excluded from the overall figure, since they are maintained by hand.',\n params: {\n layer: layerFilter,\n referenceLocale,\n listEmpty: {\n type: 'boolean',\n default: false,\n description: 'Also list the keys behind summary.emptyKeys under \"empty\" (locale → layer → keys), and keys that are empty in the reference locale itself under \"emptyInReference\" — useful after a scaffold or an interrupted translation run. Default: false, which returns counts only.',\n },\n failUnder: {\n type: 'number',\n description: 'Exit 2 when overall completion is below this percentage (CI gate).',\n mcp: { hidden: true },\n },\n },\n // The threshold comes from the flag's own value; `direction: below` is what\n // makes this a floor rather than a ceiling (see resolveExitCode).\n gates: [{ flag: 'failUnder', counter: 'completionPercent', direction: 'below' }],\n report: {\n name: 'get_translation_status',\n outputFile: { example: '/tmp/translation-status.json' },\n // Summary only: the per-locale and per-layer arrays grow with the\n // project, and a health check must never flood a caller's context.\n summary: (result: TranslationStatusResult) => result.summary,\n codequality: {\n findings: 'incomplete locales and unconsumed layers',\n // The gate's threshold is the report's threshold: a pipeline that asks\n // to fail under 90% should not see a finding for every locale at 97%.\n issues: (result: TranslationStatusResult, ctx) => statusToCodeQuality(result, {\n config: ctx.config,\n projectDir: ctx.projectDir,\n failUnder: ctx.args.failUnder,\n }),\n },\n },\n async run(args) {\n const { getTranslationStatus } = await core()\n return getTranslationStatus({\n layer: args.layer,\n referenceLocale: args.referenceLocale,\n listEmpty: args.listEmpty,\n projectDir: args.projectDir,\n })\n },\n }),\n\n defineOperation({\n id: 'search',\n cli: { name: 'search' },\n mcp: { name: 'search_translations', title: 'Search Translations' },\n description: 'Search translation files by key path or value. A case-insensitive substring match — not fuzzy, not a regular expression.',\n longDescription: 'Useful for finding an existing translation before adding a duplicate of it.',\n params: {\n query: {\n type: 'string',\n required: true,\n description: 'Substring to search for, matched against keys and/or values. Case-insensitive. Example: \"save\" matches the key \"common.actions.save\" and the value \"Save changes\".',\n },\n searchIn: {\n type: 'string',\n enum: ['keys', 'values', 'both'],\n default: 'both',\n description: 'Whether to search translation keys, values, or both. Default: \"both\".',\n // `--in` was the CLI spelling before the two surfaces agreed.\n cli: { alias: 'in' },\n },\n layer: {\n ...layerFilter,\n description: 'Layer name to search in (e.g., \"root\", \"app-admin\"), or \"*\" for all layers. If omitted, searches every layer.',\n },\n locale: {\n type: 'string',\n description: 'Locale code to search in (e.g., \"en\", \"de\"). If omitted, searches every locale.',\n },\n },\n report: {\n name: 'search_translations',\n outputFile: {\n example: '/tmp/search-results.json',\n // The only read operation the CLI never gave a report path to. Left as\n // it was rather than quietly growing the command's surface.\n cli: { hidden: true },\n },\n // The one operation whose result carries no summary of its own: the match\n // count is what is left of it once the matches are on disk.\n summary: (result: SearchTranslationsResult) => ({ totalMatches: result.totalMatches }),\n },\n async run(args) {\n const { searchTranslations } = await core()\n return searchTranslations({\n query: args.query,\n searchIn: args.searchIn,\n layer: args.layer,\n locale: args.locale,\n projectDir: args.projectDir,\n })\n },\n }),\n\n defineOperation({\n id: 'remove',\n cli: { name: 'remove' },\n mcp: { name: 'remove_translations', title: 'Remove Translations' },\n description: 'Remove one or more translation keys from ALL locale files in the given layer.',\n longDescription: 'Use dryRun to preview the changes before applying them.',\n params: {\n layer: {\n ...layerRequired,\n description: 'Layer name from discover (e.g., \"root\", \"app-admin\"). The keys are removed from ALL locale files in this layer.',\n },\n keys: {\n type: 'string[]',\n required: true,\n description: 'Dot-separated key paths to remove from every locale file in the layer. Example: [\"common.actions.delete\", \"auth.errors.expired\"].',\n },\n dryRun: dryRun('Return a preview of what would be removed without writing any files. Default: false.'),\n },\n async run(args) {\n const { removeTranslations } = await core()\n return removeTranslations({\n layer: args.layer,\n keys: args.keys,\n dryRun: args.dryRun,\n projectDir: args.projectDir,\n })\n },\n }),\n\n defineOperation({\n id: 'move',\n cli: { name: 'move' },\n mcp: { name: 'move_translation_key', title: 'Move or Rename a Translation Key' },\n description: 'Move a translation key to another layer, to another key path, or both, carrying every locale that defines it.',\n longDescription: 'Pass toLayer to promote an app-layer key to a shared layer once a second app needs it (or to demote a shared key that turned out to be app-specific); call discover first, layerGraph.shared names the layers more than one app consumes. Pass newKey alone to rename the key in place across every locale file of its layer. Writes nothing at all if the destination already holds the key with a different value in any locale; if it holds the same value, that locale is deduplicated instead. Use dryRun to preview the plan.',\n params: {\n layer: {\n ...layerRequired,\n description: 'Layer the key lives in today, from discover. Example: \"app-admin\".',\n },\n key: {\n type: 'string',\n required: true,\n description: 'Dot-separated key path to move. Example: \"calendar.views.save\".',\n },\n // Neither destination is required on its own: which one is passed is what\n // the caller meant to do, and passing neither is the error.\n toLayer: {\n type: 'string',\n description: 'Layer to move it to, from discover. Example: \"root\". Omit (or repeat layer) to rename the key within its current layer, which then requires newKey.',\n },\n newKey: {\n type: 'string',\n description: 'Key path to give it. Example: \"common.actions.save\". Omit to keep the current path, which then requires toLayer.',\n },\n dryRun: dryRun('Return the plan without writing any files. Default: false.'),\n },\n async run(args) {\n const { moveTranslationKey } = await core()\n return moveTranslationKey({\n layer: args.layer,\n key: args.key,\n toLayer: args.toLayer,\n newKey: args.newKey,\n dryRun: args.dryRun,\n projectDir: args.projectDir,\n })\n },\n }),\n\n defineOperation({\n id: 'translate',\n cli: { name: 'translate' },\n mcp: {\n name: 'translate_missing',\n title: 'Translate Missing',\n annotations: { title: 'Translate Missing Translations', readOnlyHint: false },\n },\n description: 'Find the keys missing in the target locales and translate them. Without a translation backend nothing is written: the result carries per-locale fallback contexts to translate by hand instead.',\n longDescription: 'Two modes: in provider mode (the server env-configured with I18N_PROVIDER, I18N_MODEL and an API key) it calls the LLM provider directly and writes the results; in agent mode it returns those fallbackContexts — translate them inline and persist via write_translations. Check the discover output for the active mode. Uses the project config (glossary, translation prompt, locale notes, examples) where there is one. Translates all locales concurrently, so pass every target locale at once.',\n params: {\n layer: {\n ...layerFilter,\n description: 'Layer to translate (e.g., \"root\", \"app-admin\"). Omit to translate every locale-backed layer in one call — the recommended default for layered projects, which returns a result per layer plus an aggregated summary.',\n },\n referenceLocale: {\n ...referenceLocale,\n description: 'Locale code used as the translation source (e.g., \"en\", \"en-US\"). Defaults to the project default locale.',\n },\n targetLocales: {\n type: 'string[]',\n description: 'Locale codes to translate into (e.g., [\"de\", \"fr\", \"sv\"]). Defaults to all locales except the reference.',\n cli: { alias: 'targets' },\n },\n keys: {\n type: 'string[]',\n description: 'Dot-path keys to translate (e.g., [\"auth.login.title\", \"common.save\"]). If omitted, translates every missing key in the layer.',\n },\n batchSize: {\n type: 'number',\n integer: true,\n min: 1,\n description: 'Maximum number of keys per provider request. Default: 50. A lower value reduces per-batch risk and increases round trips.',\n },\n overwriteStale: {\n type: 'boolean',\n default: false,\n description: 'Also re-translate keys whose target value was written from source text that has changed since. Requires translationMemory in the project config — without it nothing is known to be stale and this changes nothing. Default: false, which reports those keys under \"stale\" and leaves their values alone.',\n },\n dryRun: dryRun('Return which keys would be translated without calling the provider or writing files. Default: false.'),\n compact: {\n type: 'boolean',\n description: 'Return a compact summary (totalTranslated, totalFailed, byLocale) instead of full per-locale results. Default: false.',\n // The CLI answer is a stream a caller pipes into jq, which is where a\n // terminal user trims it.\n cli: { hidden: true },\n },\n ...providerParams,\n failOnFailed: {\n type: 'boolean',\n default: false,\n description: 'Exit 2 when any key failed to translate (CI gate).',\n mcp: { hidden: true },\n },\n },\n // Without this gate a partly failed run is indistinguishable from a clean\n // one: isTotalFailure only reports exit 1 when NOTHING was translated, so a\n // run that wrote 795 keys and lost 141 exits 0 and its partial result gets\n // committed. Opt-in rather than default, because providers fail transiently\n // and a red pipeline on every flake is a red pipeline nobody reads.\n gates: [{ flag: 'failOnFailed', counter: 'totalFailed', threshold: 0 }],\n usesTranslateFn: true,\n async run(args, ctx) {\n const { translateMissing } = await core()\n const result = await translateMissing({\n layer: args.layer,\n referenceLocale: args.referenceLocale,\n targetLocales: args.targetLocales,\n keys: args.keys,\n batchSize: args.batchSize,\n overwriteStale: args.overwriteStale,\n dryRun: args.dryRun,\n compact: args.compact,\n projectDir: args.projectDir,\n translateFn: ctx.translateFn,\n progressFn: ctx.progressFn,\n onProgressTotal: ctx.onProgressTotal,\n })\n applyTranslateMissingGuidance(result, ctx.surface)\n return result\n },\n }),\n\n defineOperation({\n id: 'translate-key',\n cli: { name: 'translate-key' },\n mcp: {\n name: 'translate_key',\n title: 'Translate Key',\n annotations: { title: 'Translate Single Key', readOnlyHint: false },\n },\n description: 'Add or update one source translation key and translate it into the target locales.',\n longDescription: 'Unlike translate_missing, this can overwrite an existing but stale target translation. Same two modes as translate_missing: provider mode (the server env-configured) translates directly; agent mode returns a fallbackContext — translate it inline and persist via write_translations.',\n params: {\n layer: {\n ...layerRequired,\n description: 'Layer holding the key, from discover (e.g., \"root\", \"app-admin\").',\n },\n key: {\n type: 'string',\n required: true,\n description: 'Dot-separated key path to translate. Example: \"bookingCreator.options.removeSubResource\".',\n },\n sourceLocale: {\n type: 'string',\n required: true,\n description: 'Source locale ref. May be a code (\"en-us\"), a language (\"en-US\") or a file (\"en-US.json\").',\n },\n sourceValue: {\n type: 'string',\n description: 'Source value to write before translating. If omitted, the existing source value is read.',\n },\n targetLocales: {\n type: 'string[]',\n allowAll: true,\n description: 'Locales to translate into. Pass \"all\", or omit, for every locale except the source.',\n cli: { alias: 'targets' },\n },\n overwrite: {\n type: 'boolean',\n default: true,\n description: 'Overwrite existing target translations. When false, only missing targets are filled. Default: true.',\n },\n dryRun: dryRun('Report the source and target locales without writing files or calling the translation backend. Default: false.'),\n includePreview: {\n type: 'boolean',\n default: false,\n description: 'Include the translated values in the result. Default: false, which keeps the response compact.',\n },\n ...providerParams,\n },\n usesTranslateFn: true,\n async run(args, ctx) {\n const { translateKey } = await core()\n const result = await translateKey({\n layer: args.layer,\n key: args.key,\n sourceLocale: args.sourceLocale,\n sourceValue: args.sourceValue,\n targetLocales: args.targetLocales,\n overwrite: args.overwrite,\n dryRun: args.dryRun,\n includePreview: args.includePreview,\n projectDir: args.projectDir,\n translateFn: ctx.translateFn,\n })\n applyTranslateKeyGuidance(result, ctx.surface)\n return result\n },\n }),\n\n defineOperation({\n id: 'check',\n cli: { name: 'check' },\n mcp: { name: 'find_undefined_keys', title: 'Find Used-But-Undefined Translation Keys' },\n description: 'Find keys referenced in source code but defined in NO locale layer the using app consumes — the direction that ships raw keys to production.',\n longDescription: 'The inverse of find_orphan_keys. Scope-aware: each scan unit (app) is checked against the layers it consumes (summary.searchedLayersByApp), so a key defined only in a layer the using app does not consume is still undefined for that app. Known limitation: extraction is line-based and static — dynamically built keys (template literals, concatenation) cannot be verified and are reported as uncertainKeys, never as hard findings. With write, the hard findings are also added to a locale file as empty translations, which is the first half of the fix; uncertain findings are never written.',\n params: {\n locale: {\n ...readLocale,\n description: 'Reference locale to resolve key definitions in (e.g., \"en\", \"en-US\"). Defaults to the project default locale.',\n },\n write: {\n type: 'boolean',\n default: false,\n description: 'Add every undefined key to a locale file, with an empty string as its value, in the project default locale only. Existing values are never touched, and uncertain findings are never written. The layer is the one the using code resolves against; when that is more than one layer, the run refuses and asks for a layer name. Default: false, which only reports.',\n },\n layer: {\n type: 'string',\n description: 'Layer to write the undefined keys into (e.g., \"root\", \"app-admin\"). Only read together with write, and only needed when the using code resolves against more than one layer. Call discover to list the layers.',\n },\n scanDirs,\n excludeDirs,\n },\n /**\n * Always on, and a gate rather than a run failure. A key that renders raw in\n * production is a defect, so there is no flag to opt into caring about it —\n * but it is still a finding, and reporting it as exit 1 left the caller\n * unable to tell an undefined key from a scan that fell over.\n *\n * Reads summary.undefinedCount, which the result carries whether or not it\n * was diverted to a file. Uncertain findings never trip it.\n *\n * A `write` run counts what it wrote out of that number: a key with a\n * definition, even an empty one, no longer renders raw, so extracting every\n * finding exits 0 and anything left over — a key the layer already defined,\n * skipped by the add-only write — still exits 2.\n */\n gates: [{ name: 'undefined-keys', counter: 'undefinedCount', threshold: 0 }],\n report: {\n name: 'find_undefined_keys',\n outputFile: { example: '/tmp/undefined-keys.json' },\n summary: (result: CheckUndefinedKeysResult) => result.summary,\n codequality: {\n findings: 'findings',\n // Uncertain findings are deliberately not mapped; see codequality.ts.\n issues: (result: CheckUndefinedKeysResult) => undefinedKeysToCodeQuality(result.undefinedKeys),\n },\n },\n async run(args) {\n const { checkUndefinedKeys } = await core()\n return checkUndefinedKeys({\n locale: args.locale,\n write: args.write,\n layer: args.layer,\n scanDirs: args.scanDirs,\n excludeDirs: args.excludeDirs,\n projectDir: args.projectDir,\n })\n },\n }),\n\n defineOperation({\n id: 'orphans',\n cli: { name: 'orphans' },\n mcp: { name: 'find_orphan_keys', title: 'Find Orphan Translation Keys' },\n /**\n * Three questions about one subject, so one operation: which keys nothing\n * references, where the references that do exist are, and delete the first\n * set. Deleting is the only destructive spelling and it has to be asked for.\n */\n description: 'Report translation keys that no source code references. Nothing is deleted unless remove is set.',\n longDescription: 'Scans a specific layer or all layers, and also detects dynamic key patterns and uncertain matches. Scope-aware: each layer is checked only against the code of the apps that consume it (summary.scanScope shows each layer\\'s effective scope), and keys referenced only from non-consuming apps are reported separately as misplacedUsages rather than as orphans. With remove the orphan keys are deleted from every locale file of their layer — uncertain keys and misplaced usages are never deleted, in any mode.',\n params: {\n layer: layerFilter,\n locale: {\n ...readLocale,\n description: 'Locale code to read the translation keys from (e.g., \"en\", \"en-US\"). Defaults to the project default locale.',\n },\n remove: {\n type: 'boolean',\n default: false,\n description: 'Permanently delete the orphan keys from every locale file of their layer. Default: false, which only reports them — run without it first and read the findings. Uncertain keys and misplaced usages are never deleted.',\n },\n usages: {\n type: 'boolean',\n default: false,\n description: 'Report where keys are referenced in source (file paths and line numbers) instead of which keys are unreferenced.',\n // The inverted question, which the tool surface answers through the\n // orphan report itself; adding it to a tool would give one tool two\n // result shapes an agent has to tell apart.\n mcp: { hidden: true },\n },\n keys: {\n type: 'string[]',\n description: 'Keys to report usages for. Only read together with usages; without it, every key is considered.',\n mcp: { hidden: true },\n },\n scanDirs,\n excludeDirs,\n failOnOrphans: {\n type: 'boolean',\n default: false,\n description: 'Exit 2 when any orphan key is found (CI gate).',\n mcp: { hidden: true },\n },\n },\n gates: [{ flag: 'failOnOrphans', counter: 'orphanCount', threshold: 0 }],\n report: {\n // Three questions, three report names — the paths pipelines archive.\n name: args => (args.usages === true\n ? 'scan_code_usage'\n : args.remove === true ? 'remove_orphan_keys' : 'find_orphan_keys'),\n outputFile: { example: '/tmp/orphan-keys.json' },\n summary: (result: OrphanCommandResult) => result.summary,\n codequality: {\n findings: 'orphan findings',\n // A result carrying usages answers where keys are referenced, which is\n // not a finding — nothing to write, not even the empty baseline.\n issues: (result: OrphanCommandResult, ctx) => ('usages' in result\n ? undefined\n : orphanResultToCodeQuality(result, {\n config: ctx.config,\n projectDir: ctx.projectDir,\n locale: ctx.args.locale,\n })),\n },\n },\n async run(args, ctx) {\n const { findOrphanKeys, removeOrphanKeys, scanCodeUsage } = await core()\n\n if (args.usages) {\n if (args.remove) {\n throw new Error('--usages reports where keys are used and never writes. Drop --remove, or drop --usages to delete orphans.')\n }\n // No progress here: the usage question walks the layer root dirs\n // directly rather than the scope-aware plan, so it is a different scan\n // with a different file count.\n return scanCodeUsage({\n keys: args.keys,\n scanDirs: args.scanDirs,\n excludeDirs: args.excludeDirs,\n projectDir: args.projectDir,\n })\n }\n\n // Two functions behind one operation: the report is richer than the\n // removal report (candidate-only keys, unresolved dynamic references),\n // and deleting is not something a caller should reach by omitting a flag.\n if (args.remove) {\n return removeOrphanKeys({\n layer: args.layer,\n locale: args.locale,\n scanDirs: args.scanDirs,\n excludeDirs: args.excludeDirs,\n dryRun: false,\n projectDir: args.projectDir,\n progressFn: ctx.progressFn,\n onProgressTotal: ctx.onProgressTotal,\n })\n }\n\n return findOrphanKeys({\n layer: args.layer,\n locale: args.locale,\n scanDirs: args.scanDirs,\n excludeDirs: args.excludeDirs,\n projectDir: args.projectDir,\n progressFn: ctx.progressFn,\n onProgressTotal: ctx.onProgressTotal,\n })\n },\n }),\n\n defineOperation({\n id: 'find-duplicates',\n cli: { name: 'find-duplicates' },\n mcp: { name: 'find_duplicate_keys', title: 'Find Duplicate Translation Keys Across Layers' },\n description: 'Find translation keys defined in BOTH a shared layer and an app layer that consumes it.',\n longDescription: 'For example the same key in a monorepo root layer and in app-shop. At runtime the app layer\\'s value shadows the shared one, so a collision with divergent values is the dangerous case: the shared value silently never shows. Compares one reference locale and reports each collision with both values and a divergent flag. Fix by deleting one side, never by moving.',\n params: {\n locale: {\n ...readLocale,\n description: 'Locale code to compare values in (e.g., \"de\", \"en-US\"). Defaults to the project default locale.',\n },\n byValue: {\n type: 'boolean',\n default: false,\n description: 'Also group different keys carrying the same value — e.g. common.actions.save and calendar.views.save both \"Speichern\". Each group says what to do about it: \"reuse\" (a shared layer already has it — delete the app copies and repoint the call sites), \"promote\" (move one to a shared layer) or \"consolidate\" (duplication inside one layer). Default: false.',\n },\n minValueLength: {\n type: 'number',\n integer: true,\n min: 1,\n description: 'Shortest value worth grouping when byValue is set. Default: 4 — below that, values like \"OK\" repeat across unrelated namespaces legitimately.',\n },\n },\n report: {\n name: 'find_duplicate_keys',\n outputFile: { example: '/tmp/duplicate-keys.json' },\n summary: (result: FindDuplicateKeysResult) => result.summary,\n codequality: {\n findings: 'duplicate keys',\n issues: (result: FindDuplicateKeysResult, ctx) => duplicateKeysToCodeQuality(result, {\n config: ctx.config,\n projectDir: ctx.projectDir,\n locale: ctx.args.locale,\n }),\n },\n },\n async run(args) {\n const { findDuplicateKeys } = await core()\n return findDuplicateKeys({\n locale: args.locale,\n projectDir: args.projectDir,\n byValue: args.byValue,\n minValueLength: args.minValueLength,\n })\n },\n }),\n\n defineOperation({\n id: 'scaffold',\n cli: { name: 'scaffold' },\n mcp: { name: 'scaffold_locale', title: 'Scaffold Locale' },\n description: 'Create empty locale files for new languages, copying the key structure of the default locale with every value set to an empty string.',\n longDescription: 'Supports both JSON (Nuxt) and PHP (Laravel) formats. Does NOT modify the framework configuration — add the locale there first, then call this.',\n params: {\n locales: {\n type: 'string[]',\n description: 'Locale codes to scaffold empty files for (e.g., [\"sv\", \"ja\", \"pt-BR\"]). If omitted, auto-detects the locales the config declares but has no files for.',\n },\n layer: {\n ...layerFilter,\n description: 'Layer to scaffold in (e.g., \"root\", \"app-admin\"). If omitted, scaffolds across every layer.',\n },\n dryRun: dryRun('Report the files that would be created without writing them. Default: false.'),\n },\n async run(args) {\n const { scaffoldLocaleFiles } = await core()\n return scaffoldLocaleFiles({\n locales: args.locales,\n layer: args.layer,\n dryRun: args.dryRun,\n projectDir: args.projectDir,\n })\n },\n }),\n]\n\n/** The descriptors a surface exposes, in registry order. */\nexport function descriptorsFor(surface: 'cli' | 'mcp'): AnyOperationDescriptor[] {\n return descriptors.filter(descriptor => descriptor[surface] !== null)\n}\n\n/** The parameter names a surface exposes for one operation, in declaration order. */\nexport function visibleParams(\n descriptor: AnyOperationDescriptor,\n surface: 'cli' | 'mcp',\n): string[] {\n return Object.entries(descriptor.params)\n .filter(([, spec]) => spec[surface]?.hidden !== true)\n .map(([name]) => name)\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkCA,MAAM,sBAAsB;AAC5B,MAAM,mBAAmB;AACzB,MAAM,4BAA4B;AAClC,MAAM,0BAA0B;AAChC,MAAM,yBAAyB;AAC/B,MAAM,sBAAsB;;AAG5B,SAAS,cAAc,GAAG,OAAyB;AACjD,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,OAAO,MAAM;;;AAIpE,SAAS,aAAa,MAAsB;CAC1C,MAAM,aAAa,KAAK,WAAW,MAAM,IAAI;AAC7C,QAAO,WAAW,WAAW,KAAK,GAAG,WAAW,MAAM,EAAE,GAAG;;;AAI7D,SAAS,YAAY,MAAsB;AACzC,QAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;;;;;;;AAQtC,SAAgB,2BAA2B,eAA0D;CACnG,MAAM,SAA6B,EAAE;AACrC,MAAK,MAAM,WAAW,cACpB,MAAK,MAAM,SAAS,QAAQ,QAAQ;EAClC,MAAM,OAAO,aAAa,MAAM,KAAK;AACrC,SAAO,KAAK;GACV,aAAa,aAAa,QAAQ,IAAI;GACtC,YAAY;GACZ,aAAa,cAAc,qBAAqB,QAAQ,KAAK,KAAK;GAClE,UAAU;GACV,UAAU;IAAE;IAAM,OAAO,EAAE,OAAO,YAAY,MAAM,KAAK,EAAE;IAAE;GAC9D,CAAC;;AAGN,QAAO;;;;;;;;;;AAWT,SAAgB,wBACd,gBACA,mBACoB;CACpB,MAAM,SAA6B,EAAE;AACrC,MAAK,MAAM,CAAC,OAAO,SAAS,OAAO,QAAQ,eAAe,EAAE;EAG1D,MAAM,OAAO,aAAa,kBAAkB,UAAU,MAAM;AAC5D,OAAK,MAAM,OAAO,KAChB,QAAO,KAAK;GACV,aAAa,oBAAoB,IAAI,cAAc,MAAM;GACzD,YAAY;GACZ,aAAa,cAAc,kBAAkB,OAAO,IAAI;GACxD,UAAU;GACV,UAAU;IAAE;IAAM,OAAO,EAAE,OAAO,GAAG;IAAE;GACxC,CAAC;;AAGN,QAAO;;;;;;;;;;;AAYT,SAAgB,0BACd,QACA,KACoB;CACpB,MAAM,iBAAiB,OAAO,cAAc,OAAO,WAAW,EAAE;CAChE,MAAM,EAAE,cAAc,uBAAuB,IAAI,QAAQ,IAAI,OAAO;AACpE,QAAO,wBACL,gBACA,2BAA2B,IAAI,QAAQ,OAAO,KAAK,eAAe,EAAE,WAAW,IAAI,WAAW,CAC/F;;;;;;;;AASH,SAAS,aACP,KACA,OACA,WACQ;CACR,MAAM,SAAS,eAAe,IAAI,QAAQ,UAAU;AAIpD,QAAO,cAHS,WAAW,KAAA,IACvB,EAAE,GACF,2BAA2B,IAAI,QAAQ,CAAC,MAAM,EAAE,QAAQ,IAAI,WAAW,EAC/C,UAAU,MAAM;;;;;;;;;AAU9C,SAAgB,iCACd,QACA,KACoB;CACpB,MAAM,SAA6B,EAAE;AACrC,MAAK,MAAM,CAAC,YAAY,YAAY,OAAO,QAAQ,OAAO,QAAQ,EAAE;EAClE,MAAM,SAAS,eAAe,IAAI,QAAQ,WAAW;EAGrD,MAAM,UAAU,WAAW,KAAA,IACvB,EAAE,GACF,2BAA2B,IAAI,QAAQ,OAAO,KAAK,QAAQ,EAAE,QAAQ,IAAI,WAAW;AAExF,OAAK,MAAM,CAAC,OAAO,SAAS,OAAO,QAAQ,QAAQ,EAAE;GACnD,MAAM,OAAO,aAAa,QAAQ,UAAU,MAAM;AAClD,QAAK,MAAM,OAAO,KAChB,QAAO,KAAK;IACV,aAAa,4BAA4B,IAAI,OAAO,WAAW,6CAA6C,MAAM;IAClH,YAAY;IACZ,aAAa,cAAc,2BAA2B,OAAO,YAAY,IAAI;IAC7E,UAAU;IACV,UAAU;KAAE;KAAM,OAAO,EAAE,OAAO,GAAG;KAAE;IACxC,CAAC;;;AAIR,QAAO;;;;;;;;;;;;;;;AAgBT,SAAgB,oBACd,QACA,KACoB;CACpB,MAAM,YAAY,IAAI,aAAa;CAGnC,MAAM,eAAe,OAAO,QAAQ,cAAc,MAAM;CACxD,MAAM,SAA6B,EAAE;AAErC,MAAK,MAAM,UAAU,OAAO,SAAS;AACnC,MAAI,OAAO,cAAc,QAAQ,OAAO,cAAc,UAAW;AACjE,SAAO,KAAK;GACV,aAAa,WAAW,OAAO,KAAK,OAAO,OAAO,WAAW,0BAA0B,UAAU,eAAe,OAAO,QAAQ,mBAAmB,OAAO,MAAM;GAC/J,YAAY;GAGZ,aAAa,cAAc,yBAAyB,OAAO,KAAK;GAChE,UAAU;GACV,UAAU;IACR,MAAM,aAAa,KAAK,cAAc,OAAO,KAAK,IAAI,OAAO;IAC7D,OAAO,EAAE,OAAO,GAAG;IACpB;GACF,CAAC;;CAGJ,MAAM,gBAAgB,OAAO,QAAQ,gBAAgB;AACrD,MAAK,MAAM,SAAS,OAAO,QAAQ,iBACjC,QAAO,KAAK;EACV,aAAa,UAAU,MAAM;EAC7B,YAAY;EACZ,aAAa,cAAc,wBAAwB,MAAM;EACzD,UAAU;EACV,UAAU;GAAE,MAAM,aAAa,KAAK,OAAO,cAAc;GAAE,OAAO,EAAE,OAAO,GAAG;GAAE;EACjF,CAAC;AAGJ,QAAO;;;;;;;;;;;AAYT,SAAgB,2BACd,QACA,KACoB;CACpB,MAAM,EAAE,cAAc,uBAAuB,IAAI,QAAQ,IAAI,OAAO;CACpE,MAAM,UAAU,2BACd,IAAI,QACJ,CAAC,GAAG,IAAI,IAAI,OAAO,WAAW,KAAI,cAAa,UAAU,WAAW,CAAC,CAAC,EACtE,WACA,IAAI,WACL;AAED,QAAO,OAAO,WAAW,KAAI,eAAc;EACzC,aAAa,aAAa,UAAU,IAAI,wBAAwB,UAAU,YAAY,SAAS,UAAU,WAAW,MAC/G,UAAU,YACT,iGACA;EACN,YAAY;EAEZ,aAAa,cAAc,qBAAqB,UAAU,aAAa,UAAU,YAAY,UAAU,IAAI;EAC3G,UAAU;EACV,UAAU;GACR,MAAM,aAAa,QAAQ,UAAU,eAAe,UAAU,WAAW;GACzE,OAAO,EAAE,OAAO,GAAG;GACpB;EACF,EAAE;;;;;;;;AASL,SAAgB,2BACd,QACA,QACA,QACA,YACwB;CACxB,MAAM,UAAkC,EAAE;AAC1C,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,YAAY,OAAO,WAAW,MAAK,MAAK,EAAE,UAAU,MAAM;EAChE,MAAM,WAAW,WAAW,UACxB,OAAO,WAAW,MAAK,MAAK,EAAE,UAAU,UAAU,QAAQ,IAAI,YAC9D;AACJ,MAAI,CAAC,SAAU;AAIf,UAAQ,SAAS,aAAa,SAAS,YAHvB,OAAO,OACnB,KAAK,SAAS,MAAM,OAAO,KAAK,GAChC,KAAK,SAAS,MAAM,OAAO,KAAK,CACuB,CAAC;;AAE9D,QAAO;;;;;ACvRT,MAAa,qBAAqB,CAAC,cAAc,oBAAoB;;;;;;AAOrE,SAAgB,aAAa,MAA6B;AACxD,QAAO;EACL,YAAY;GACV,MAAM;GACN,aAAa,mLAAmL,KAAK,WAAW,QAAQ;GACxN,GAAI,KAAK,WAAW,QAAQ,KAAA,IAAY,EAAE,GAAG,EAAE,KAAK,KAAK,WAAW,KAAK;GACzE,GAAI,KAAK,WAAW,QAAQ,KAAA,IAAY,EAAE,GAAG,EAAE,KAAK,KAAK,WAAW,KAAK;GAC1E;EACD,GAAI,KAAK,gBAAgB,KAAA,IACrB,EAAE,GACF,EACE,mBAAmB;GACjB,MAAM;GACN,aAAa,kBAAkB,KAAK,YAAY,SAAS;GAIzD,KAAK,EAAE,QAAQ,MAAM;GACtB,EACF;EACN;;;;;;;;;AAUH,SAAgB,iBACd,QACA,OACA,MACQ;CACR,MAAM,YAAY,QAAQ,MAAM;CAChC,MAAM,WAAW,OAAO,QAAQ,OAAO;AACvC,QAAO,OAAO,YAAY;EACxB,GAAG,SAAS,QAAQ,CAAC,UAAU,CAAC,UAAU,IAAI,KAAK,CAAC;EACpD,GAAG,OAAO,QAAQ,aAAa,KAAK,CAAC;EACrC,GAAG,SAAS,QAAQ,CAAC,UAAU,UAAU,IAAI,KAAK,CAAC;EACpD,CAAC;;;;;;;;;AAUJ,eAAsB,eACpB,QACA,YACA,MACkB;CAClB,MAAM,OAAO,WAAW;AACxB,KAAI,SAAS,KAAA,KAAa,WAAW,QAAQ,OAAO,WAAW,SAAU,QAAO;CAEhF,MAAM,aAAa,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa,QAAQ,KAAK;CACxF,MAAM,CACJ,EAAE,oBACF,EAAE,mBAAmB,uBAAuB,sBAC5C,EAAE,sBAAsB,qBACtB,MAAM,QAAQ,IAAI;EACpB,OAAO;EACP,OAAO;EACP,OAAO;EACR,CAAC;CAEF,MAAM,SAAS,MAAM,iBAAiB,WAAW;CAEjD,MAAM,oBAAoB,QAAQ,KAAK,kBAAkB;AACzD,KAAI,KAAK,gBAAgB,KAAA,KAAa,sBAAsB,KAAA,GAAW;EACrE,MAAM,SAAS,KAAK,YAAY,OAAO,QAAQ;GAAE;GAAY;GAAQ;GAAM,CAAC;AAC5E,MAAI,WAAW,KAAA,GAAW;AACxB,sBAAmB,YAAY,kBAAkB;AACjD,SAAM,qBAAqB,mBAAmB,OAAO;;;CAIzD,MAAM,OAAO,OAAO,KAAK,SAAS,aAAa,KAAK,KAAK,KAAK,GAAG,KAAK;CACtE,MAAM,aAAa,kBAAkB,YAAY,QAAQ,KAAK,WAAW,CAAC,IACrE,sBAAsB,QAAQ,YAAY,KAAK;AACpD,KAAI,eAAe,KAAA,EAAW,QAAO;AAErC,OAAM,gBAAgB,YAAY,QAAQ;EAAE,MAAM;EAAM,MAAM,cAAc,YAAY,KAAK;EAAE,CAAC;AAChG,QAAO;EAAE;EAAY,SAAS,KAAK,QAAQ,OAAO;EAAE;;;;;;;;;AAUtD,SAAS,cACP,YACA,MACyB;CACzB,MAAM,WAAW,IAAI,IAAY,CAAC,GAAG,oBAAoB,GAAG,QAAQ,WAAW,MAAM,CAAC,CAAC;CACvF,MAAM,YAAqC,EAAE;AAC7C,MAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,WAAW,OAAO,EAAE;EAC5D,MAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,IAAI,KAAK,IAAI,UAAU,KAAA,KAAa,UAAU,KAAK,QAAS;AACzE,YAAU,QAAQ;;AAEpB,QAAO;;AAGT,SAAS,QAAQ,OAA4C;CAC3D,MAAM,wBAAQ,IAAI,KAAa;AAC/B,MAAK,MAAM,QAAQ,SAAS,EAAE,CAC5B,KAAI,KAAK,SAAS,KAAA,EAAW,OAAM,IAAI,KAAK,KAAK;AAEnD,QAAO;;;AAIT,SAAS,QAAQ,OAAoC;AACnD,QAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;;;;;;;;;;;;AC4LjE,SAAgB,gBACd,YACwB;CACxB,MAAM,SAAS;AACf,KAAI,OAAO,WAAW,KAAA,EAAW,QAAO;AACxC,QAAO;EAAE,GAAG;EAAQ,QAAQ,iBAAiB,OAAO,QAAQ,OAAO,OAAO,OAAO,OAAO;EAAE;;;;AClU5F,MAAM,kBAAkB;AAIxB,MAAM,iBAAiB;AAIvB,MAAM,qBAAqB;;;;;;;AAU3B,SAAgB,oBAAoB,QAA2C;CAC7E,MAAM,WAAqC,YAAY,SACnD,OAAO,OAAO,OAAO,OAAO,GAC5B,CAAC,OAAO;CAEZ,MAAM,0BAAU,IAAI,KAAa;AACjC,MAAK,MAAM,SAAS,UAAU;AAC5B,OAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,MAAM,WAAW,EAAE,CAAC,CAC/D,KAAI,MAAM,OAAO,SAAS,EAAG,SAAQ,IAAI,OAAO;AAElD,OAAK,MAAM,SAAS,MAAM,QAAQ,YAAY,EAAE,CAC9C,KAAI,MAAM,SAAS,EAAG,SAAQ,IAAI,MAAM,OAAO;;AAGnD,QAAO,CAAC,GAAG,QAAQ,CAAC,MAAM;;;AAI5B,SAAS,oBAAoB,QAA0C;AAIrE,QAAO,YAAY,SACf,OAAO,OAAO,OAAO,OAAO,CAAC,MAAK,UAAS,MAAM,iBAAiB,GAClE,QAAQ,OAAO,iBAAiB;;AAGtC,SAAgB,8BACd,QACA,SACM;AACN,KAAI,YAAY,OAAO;AACrB,MAAI,oBAAoB,OAAO,IAAI,OAAO,QACvC,QAAO,QAAoC,UAAU;AAExD;;AAKF,KAAI,OAAO,QAAQ,SAAS,SAAS;AACnC,SAAO,QAAQ,UAAU;AACzB;;AAGF,KAAI,OAAO,QAAQ,cAAc,GAAG;EAIlC,MAAM,WAAW,oBAAoB,OAAO;AAC5C,SAAO,QAAQ,UAAU,GAAG,OAAO,QAAQ,YAAY,gCAClD,SAAS,SAAS,IAAI,cAAc,SAAS,KAAK,KAAK,CAAC,KAAK,MAC9D;;;AAKR,SAAgB,0BAA0B,QAA4B,SAAwB;AAC5F,KAAI,YAAY,OAAO;AACrB,MAAI,OAAO,gBAAiB,QAAO,UAAU;AAC7C;;AAIF,KAAI,OAAO,SAAS,WAAW,OAAO,QAAQ,MAAK,SAAQ,KAAK,WAAW,cAAc,CACvF,QAAO,UAAU;;;;;;;;;;;;;;;;;ACzDrB,MAAM,aAAa,OAAO;AAS1B,MAAM,cAAc;CAClB,MAAM;CACN,aAAa;CACd;AAED,MAAM,gBAAgB;CACpB,MAAM;CACN,UAAU;CACV,aAAa;CACd;AAED,MAAM,kBAAkB;CACtB,MAAM;CACN,aAAa;CAEb,KAAK,EAAE,OAAO,OAAO;CACtB;AAED,MAAM,aAAa;CACjB,MAAM;CACN,aAAa;CACd;AAED,MAAM,UAAU,iBAAyB;CACvC,MAAM;CACN,SAAS;CACT;CACD;AAED,MAAM,WAAW;CACf,MAAM;CACN,aAAa;CAGb,KAAK,EAAE,QAAQ,MAAM;CACtB;AAED,MAAM,cAAc;CAClB,MAAM;CACN,aAAa;CAEb,KAAK,EAAE,QAAQ,MAAM;CACtB;;;;;;;;AASD,MAAM,iBAAiB;CACrB,UAAU;EACR,MAAM;EACN,MAAM;GAAC;GAAU;GAAa;GAAS;EACvC,aAAa;EACb,KAAK,EAAE,QAAQ,MAAM;EACtB;CACD,OAAO;EACL,MAAM;EACN,aAAa;EACb,KAAK,EAAE,QAAQ,MAAM;EACtB;CACD,QAAQ;EACN,MAAM;EACN,aAAa;EACb,KAAK,EAAE,QAAQ,MAAM;EACtB;CACD,SAAS;EACP,MAAM;EACN,aAAa,kHAAkH,aAAa;EAC5I,KAAK,EAAE,QAAQ,MAAM;EACtB;CACF;;;;;AAQD,MAAa,cAAiD;CAE5D,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,QAAQ;EAIrB,KAAK;EACL,aAAa;EACb,QAAQ;GACN,OAAO;IACL,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACD,QAAQ,OAAO,iFAAiF;GACjG;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,sBAAsB,MAAM,MAAM;AAC1C,UAAO,kBAAkB;IACvB,YAAY,KAAK;IACjB,OAAO,KAAK;IACZ,QAAQ,KAAK;IACd,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,YAAY;EACzB,KAAK;GAAE,MAAM;GAAY,OAAO;GAAuB;EACvD,aAAa;EACb,iBAAiB;EACjB,QAAQ,EAAE;EACV,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,oBAAoB,MAAM,MAAM;AACxC,UAAO,gBAAgB,EAAE,YAAY,KAAK,YAAY,CAAC;;EAE1D,CAAC;CAEF,gBAAgB;EACd,IAAI;EAIJ,KAAK;EACL,KAAK;GAAE,MAAM;GAAmB,OAAO;GAAmB;EAC1D,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;IACL,GAAG;IACH,aAAa;IACd;GACD,QAAQ;GACT;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,mBAAmB,MAAM,MAAM;AACvC,UAAO,eAAe;IAAE,OAAO,KAAK;IAAO,QAAQ,KAAK;IAAQ,YAAY,KAAK;IAAY,CAAC;;EAEjG,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,OAAO;EACpB,KAAK;GAAE,MAAM;GAAoB,OAAO;GAAoB;EAC5D,aAAa;EACb,QAAQ;GACN,OAAO;GACP,QAAQ;IACN,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD,SAAS;IACP,MAAM;IACN,aAAa;IAGb,KAAK,EAAE,QAAQ,MAAM;IACtB;GACF;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,oBAAoB,MAAM,MAAM;AACxC,UAAO,gBAAgB;IACrB,OAAO,KAAK;IACZ,QAAQ,KAAK;IACb,MAAM,KAAK;IACX,SAAS,KAAK;IACd,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,SAAS;EACtB,KAAK;GAAE,MAAM;GAAsB,OAAO;GAAsB;EAChE,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;GACP,cAAc;IACZ,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,MAAM;KAAC;KAAO;KAAU;KAAS;IACjC,SAAS;IACT,aAAa;IACd;GACD,QAAQ,OAAO,uFAAuF;GACvG;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,sBAAsB,MAAM,MAAM;AAC1C,UAAO,kBAAkB;IACvB,OAAO,KAAK;IACZ,cAAc,KAAK;IACnB,MAAM,KAAK;IACX,QAAQ,KAAK;IACb,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,WAAW;EACxB,KAAK;GAAE,MAAM;GAA4B,OAAO;GAA4B;EAC5E,aAAa;EACb,QAAQ;GACN,OAAO;GACP;GACA,eAAe;IACb,MAAM;IACN,aAAa;IAEb,KAAK,EAAE,OAAO,WAAW;IAC1B;GACD,eAAe;IACb,MAAM;IACN,SAAS;IACT,aAAa;IAEb,KAAK,EAAE,QAAQ,MAAM;IACtB;GACF;EACD,OAAO,CAAC;GAAE,MAAM;GAAiB,SAAS;GAAoB,WAAW;GAAG,CAAC;EAC7E,QAAQ;GACN,MAAM;GACN,YAAY,EAAE,SAAS,kCAAkC;GACzD,UAAU,WAAsC,OAAO;GACvD,aAAa;IACX,UAAU;IACV,SAAS,QAAmC,QAAQ,iCAAiC,QAAQ;KAC3F,QAAQ,IAAI;KACZ,YAAY,IAAI;KACjB,CAAC;IACH;GACF;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,2BAA2B,MAAM,MAAM;AAC/C,UAAO,uBAAuB;IAC5B,OAAO,KAAK;IACZ,iBAAiB,KAAK;IACtB,eAAe,KAAK;IACpB,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,UAAU;EACvB,KAAK;GAAE,MAAM;GAA0B,OAAO;GAA0B;EACxE,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;GACP;GACA,WAAW;IACT,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACD,WAAW;IACT,MAAM;IACN,aAAa;IACb,KAAK,EAAE,QAAQ,MAAM;IACtB;GACF;EAGD,OAAO,CAAC;GAAE,MAAM;GAAa,SAAS;GAAqB,WAAW;GAAS,CAAC;EAChF,QAAQ;GACN,MAAM;GACN,YAAY,EAAE,SAAS,gCAAgC;GAGvD,UAAU,WAAoC,OAAO;GACrD,aAAa;IACX,UAAU;IAGV,SAAS,QAAiC,QAAQ,oBAAoB,QAAQ;KAC5E,QAAQ,IAAI;KACZ,YAAY,IAAI;KAChB,WAAW,IAAI,KAAK;KACrB,CAAC;IACH;GACF;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,yBAAyB,MAAM,MAAM;AAC7C,UAAO,qBAAqB;IAC1B,OAAO,KAAK;IACZ,iBAAiB,KAAK;IACtB,WAAW,KAAK;IAChB,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,UAAU;EACvB,KAAK;GAAE,MAAM;GAAuB,OAAO;GAAuB;EAClE,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;IACL,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD,UAAU;IACR,MAAM;IACN,MAAM;KAAC;KAAQ;KAAU;KAAO;IAChC,SAAS;IACT,aAAa;IAEb,KAAK,EAAE,OAAO,MAAM;IACrB;GACD,OAAO;IACL,GAAG;IACH,aAAa;IACd;GACD,QAAQ;IACN,MAAM;IACN,aAAa;IACd;GACF;EACD,QAAQ;GACN,MAAM;GACN,YAAY;IACV,SAAS;IAGT,KAAK,EAAE,QAAQ,MAAM;IACtB;GAGD,UAAU,YAAsC,EAAE,cAAc,OAAO,cAAc;GACtF;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,uBAAuB,MAAM,MAAM;AAC3C,UAAO,mBAAmB;IACxB,OAAO,KAAK;IACZ,UAAU,KAAK;IACf,OAAO,KAAK;IACZ,QAAQ,KAAK;IACb,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,UAAU;EACvB,KAAK;GAAE,MAAM;GAAuB,OAAO;GAAuB;EAClE,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;IACL,GAAG;IACH,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD,QAAQ,OAAO,uFAAuF;GACvG;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,uBAAuB,MAAM,MAAM;AAC3C,UAAO,mBAAmB;IACxB,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,QAAQ,KAAK;IACb,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,QAAQ;EACrB,KAAK;GAAE,MAAM;GAAwB,OAAO;GAAoC;EAChF,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;IACL,GAAG;IACH,aAAa;IACd;GACD,KAAK;IACH,MAAM;IACN,UAAU;IACV,aAAa;IACd;GAGD,SAAS;IACP,MAAM;IACN,aAAa;IACd;GACD,QAAQ;IACN,MAAM;IACN,aAAa;IACd;GACD,QAAQ,OAAO,6DAA6D;GAC7E;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,uBAAuB,MAAM,MAAM;AAC3C,UAAO,mBAAmB;IACxB,OAAO,KAAK;IACZ,KAAK,KAAK;IACV,SAAS,KAAK;IACd,QAAQ,KAAK;IACb,QAAQ,KAAK;IACb,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,aAAa;EAC1B,KAAK;GACH,MAAM;GACN,OAAO;GACP,aAAa;IAAE,OAAO;IAAkC,cAAc;IAAO;GAC9E;EACD,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;IACL,GAAG;IACH,aAAa;IACd;GACD,iBAAiB;IACf,GAAG;IACH,aAAa;IACd;GACD,eAAe;IACb,MAAM;IACN,aAAa;IACb,KAAK,EAAE,OAAO,WAAW;IAC1B;GACD,MAAM;IACJ,MAAM;IACN,aAAa;IACd;GACD,WAAW;IACT,MAAM;IACN,SAAS;IACT,KAAK;IACL,aAAa;IACd;GACD,gBAAgB;IACd,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACD,QAAQ,OAAO,uGAAuG;GACtH,SAAS;IACP,MAAM;IACN,aAAa;IAGb,KAAK,EAAE,QAAQ,MAAM;IACtB;GACD,GAAG;GACH,cAAc;IACZ,MAAM;IACN,SAAS;IACT,aAAa;IACb,KAAK,EAAE,QAAQ,MAAM;IACtB;GACF;EAMD,OAAO,CAAC;GAAE,MAAM;GAAgB,SAAS;GAAe,WAAW;GAAG,CAAC;EACvE,iBAAiB;EACjB,MAAM,IAAI,MAAM,KAAK;GACnB,MAAM,EAAE,qBAAqB,MAAM,MAAM;GACzC,MAAM,SAAS,MAAM,iBAAiB;IACpC,OAAO,KAAK;IACZ,iBAAiB,KAAK;IACtB,eAAe,KAAK;IACpB,MAAM,KAAK;IACX,WAAW,KAAK;IAChB,gBAAgB,KAAK;IACrB,QAAQ,KAAK;IACb,SAAS,KAAK;IACd,YAAY,KAAK;IACjB,aAAa,IAAI;IACjB,YAAY,IAAI;IAChB,iBAAiB,IAAI;IACtB,CAAC;AACF,iCAA8B,QAAQ,IAAI,QAAQ;AAClD,UAAO;;EAEV,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,iBAAiB;EAC9B,KAAK;GACH,MAAM;GACN,OAAO;GACP,aAAa;IAAE,OAAO;IAAwB,cAAc;IAAO;GACpE;EACD,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;IACL,GAAG;IACH,aAAa;IACd;GACD,KAAK;IACH,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD,cAAc;IACZ,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD,aAAa;IACX,MAAM;IACN,aAAa;IACd;GACD,eAAe;IACb,MAAM;IACN,UAAU;IACV,aAAa;IACb,KAAK,EAAE,OAAO,WAAW;IAC1B;GACD,WAAW;IACT,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACD,QAAQ,OAAO,iHAAiH;GAChI,gBAAgB;IACd,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACD,GAAG;GACJ;EACD,iBAAiB;EACjB,MAAM,IAAI,MAAM,KAAK;GACnB,MAAM,EAAE,iBAAiB,MAAM,MAAM;GACrC,MAAM,SAAS,MAAM,aAAa;IAChC,OAAO,KAAK;IACZ,KAAK,KAAK;IACV,cAAc,KAAK;IACnB,aAAa,KAAK;IAClB,eAAe,KAAK;IACpB,WAAW,KAAK;IAChB,QAAQ,KAAK;IACb,gBAAgB,KAAK;IACrB,YAAY,KAAK;IACjB,aAAa,IAAI;IAClB,CAAC;AACF,6BAA0B,QAAQ,IAAI,QAAQ;AAC9C,UAAO;;EAEV,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,SAAS;EACtB,KAAK;GAAE,MAAM;GAAuB,OAAO;GAA4C;EACvF,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,QAAQ;IACN,GAAG;IACH,aAAa;IACd;GACD,OAAO;IACL,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACD,OAAO;IACL,MAAM;IACN,aAAa;IACd;GACD;GACA;GACD;EAeD,OAAO,CAAC;GAAE,MAAM;GAAkB,SAAS;GAAkB,WAAW;GAAG,CAAC;EAC5E,QAAQ;GACN,MAAM;GACN,YAAY,EAAE,SAAS,4BAA4B;GACnD,UAAU,WAAqC,OAAO;GACtD,aAAa;IACX,UAAU;IAEV,SAAS,WAAqC,2BAA2B,OAAO,cAAc;IAC/F;GACF;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,uBAAuB,MAAM,MAAM;AAC3C,UAAO,mBAAmB;IACxB,QAAQ,KAAK;IACb,OAAO,KAAK;IACZ,OAAO,KAAK;IACZ,UAAU,KAAK;IACf,aAAa,KAAK;IAClB,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,WAAW;EACxB,KAAK;GAAE,MAAM;GAAoB,OAAO;GAAgC;EAMxE,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,OAAO;GACP,QAAQ;IACN,GAAG;IACH,aAAa;IACd;GACD,QAAQ;IACN,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACD,QAAQ;IACN,MAAM;IACN,SAAS;IACT,aAAa;IAIb,KAAK,EAAE,QAAQ,MAAM;IACtB;GACD,MAAM;IACJ,MAAM;IACN,aAAa;IACb,KAAK,EAAE,QAAQ,MAAM;IACtB;GACD;GACA;GACA,eAAe;IACb,MAAM;IACN,SAAS;IACT,aAAa;IACb,KAAK,EAAE,QAAQ,MAAM;IACtB;GACF;EACD,OAAO,CAAC;GAAE,MAAM;GAAiB,SAAS;GAAe,WAAW;GAAG,CAAC;EACxE,QAAQ;GAEN,OAAM,SAAS,KAAK,WAAW,OAC3B,oBACA,KAAK,WAAW,OAAO,uBAAuB;GAClD,YAAY,EAAE,SAAS,yBAAyB;GAChD,UAAU,WAAgC,OAAO;GACjD,aAAa;IACX,UAAU;IAGV,SAAS,QAA6B,QAAS,YAAY,SACvD,KAAA,IACA,0BAA0B,QAAQ;KAChC,QAAQ,IAAI;KACZ,YAAY,IAAI;KAChB,QAAQ,IAAI,KAAK;KAClB,CAAC;IACP;GACF;EACD,MAAM,IAAI,MAAM,KAAK;GACnB,MAAM,EAAE,gBAAgB,kBAAkB,kBAAkB,MAAM,MAAM;AAExE,OAAI,KAAK,QAAQ;AACf,QAAI,KAAK,OACP,OAAM,IAAI,MAAM,4GAA4G;AAK9H,WAAO,cAAc;KACnB,MAAM,KAAK;KACX,UAAU,KAAK;KACf,aAAa,KAAK;KAClB,YAAY,KAAK;KAClB,CAAC;;AAMJ,OAAI,KAAK,OACP,QAAO,iBAAiB;IACtB,OAAO,KAAK;IACZ,QAAQ,KAAK;IACb,UAAU,KAAK;IACf,aAAa,KAAK;IAClB,QAAQ;IACR,YAAY,KAAK;IACjB,YAAY,IAAI;IAChB,iBAAiB,IAAI;IACtB,CAAC;AAGJ,UAAO,eAAe;IACpB,OAAO,KAAK;IACZ,QAAQ,KAAK;IACb,UAAU,KAAK;IACf,aAAa,KAAK;IAClB,YAAY,KAAK;IACjB,YAAY,IAAI;IAChB,iBAAiB,IAAI;IACtB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,mBAAmB;EAChC,KAAK;GAAE,MAAM;GAAuB,OAAO;GAAiD;EAC5F,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,QAAQ;IACN,GAAG;IACH,aAAa;IACd;GACD,SAAS;IACP,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACD,gBAAgB;IACd,MAAM;IACN,SAAS;IACT,KAAK;IACL,aAAa;IACd;GACF;EACD,QAAQ;GACN,MAAM;GACN,YAAY,EAAE,SAAS,4BAA4B;GACnD,UAAU,WAAoC,OAAO;GACrD,aAAa;IACX,UAAU;IACV,SAAS,QAAiC,QAAQ,2BAA2B,QAAQ;KACnF,QAAQ,IAAI;KACZ,YAAY,IAAI;KAChB,QAAQ,IAAI,KAAK;KAClB,CAAC;IACH;GACF;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,sBAAsB,MAAM,MAAM;AAC1C,UAAO,kBAAkB;IACvB,QAAQ,KAAK;IACb,YAAY,KAAK;IACjB,SAAS,KAAK;IACd,gBAAgB,KAAK;IACtB,CAAC;;EAEL,CAAC;CAEF,gBAAgB;EACd,IAAI;EACJ,KAAK,EAAE,MAAM,YAAY;EACzB,KAAK;GAAE,MAAM;GAAmB,OAAO;GAAmB;EAC1D,aAAa;EACb,iBAAiB;EACjB,QAAQ;GACN,SAAS;IACP,MAAM;IACN,aAAa;IACd;GACD,OAAO;IACL,GAAG;IACH,aAAa;IACd;GACD,QAAQ,OAAO,+EAA+E;GAC/F;EACD,MAAM,IAAI,MAAM;GACd,MAAM,EAAE,wBAAwB,MAAM,MAAM;AAC5C,UAAO,oBAAoB;IACzB,SAAS,KAAK;IACd,OAAO,KAAK;IACZ,QAAQ,KAAK;IACb,YAAY,KAAK;IAClB,CAAC;;EAEL,CAAC;CACH;;AAGD,SAAgB,eAAe,SAAkD;AAC/E,QAAO,YAAY,QAAO,eAAc,WAAW,aAAa,KAAK;;;AAIvE,SAAgB,cACd,YACA,SACU;AACV,QAAO,OAAO,QAAQ,WAAW,OAAO,CACrC,QAAQ,GAAG,UAAU,KAAK,UAAU,WAAW,KAAK,CACpD,KAAK,CAAC,UAAU,KAAK"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import "./project-config-C3ao4Uii.js";
|
|
2
|
+
import "./json-writer-ek8yEI5R.js";
|
|
3
|
+
import "./php-reader-DRpcRVzv.js";
|
|
4
|
+
import { i as discoverNuxtApps, n as clearConfigCache, r as getCachedConfig, t as detectI18nConfig } from "./detector-DtFY4qm3.js";
|
|
5
|
+
export { clearConfigCache, detectI18nConfig, discoverNuxtApps, getCachedConfig };
|