Skip to content

tooling(i18n): sweep the metadata-admin designer table for dead keys (objectui#8388) - #8699

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-8388-i18n-retirement-sweep
Sep 9, 2026
Merged

tooling(i18n): sweep the metadata-admin designer table for dead keys (objectui#8388)#8699
baozhoutao merged 1 commit into
mainfrom
claude/issue-8388-i18n-retirement-sweep

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #8388

Part 2 only, per triage's split (5582360339). ⛔ The four dead keys of objectui#8547 are not touched here — they are objectui#8687's, still open, and this PR uses them as its positive control precisely because they are still in the tree.

The shape is triage's ruling, not a choice made here: option 1, a report-only sweep plus a dynamic-head leg. Option 2 (per-namespace closed worlds) stays available later per namespace and is not in scope. Nothing in this diff can red the tree over a dead key.

What landed

scripts/check-i18n-dead-keys.mjs gains a second corpus: ENGINE_STRINGS_EN / ENGINE_STRINGS_ZH in packages/app-shell/src/views/metadata-admin/i18n.ts. One script, because it asks the same mirror question — does a key the table DEFINES have any reader at all? — from the same side. Not one walker: the pack half reuses the call-site gate's AST pass, and this half needs its own, because no call-site walker in this repo can see this table.

That is measured, not assumed. Both repo-wide i18n gates return exit 0 over this table and neither verdict is about it:

  • check-i18n-call-site-keys.mjs walks call sites and resolves each against the en pack. A key no call site names is not a subject to it, so it can never report a dead key — the defect and its blind spot are the same thing. It also classifies this module module-local table by declaration and skips it.
  • check-i18n-en-drift.mjs reads the ten locale packs. This table is not one of them.

Three legs, and the second is the load-bearing one:

  1. LITERAL — every string literal in non-test packages/+apps/ source starting with engine. / designer. / perm.. Deliberately not restricted to arguments of t() / tOptional() / tFormat(): measured on this tree, 31 call sites park a key in a config property (labelKey: 'engine.studio.rules.typeScript') and hand it to a translator through a variable. An argument-only walk would report every one of those keys dead.
  2. DYNAMIC HEAD — the static head of every template-built key in the same files, including the ones inside the table's own module, where translateNodeLabel / translateEnumOption index pickTable(locale).strings[...] directly with no t() call anywhere near them.
  3. TEXT SAFETY NET — the existing whole-repo grep, splitting survivors into CONFIRMED and NEEDS-REVIEW.

The four acceptance criteria, each with its measured outcome

All readings at head 90ce389f5, on a clean tree, from node scripts/check-i18n-dead-keys.mjs (exit 0, empty stderr).

1. A candidate list short enough that a person will read it — 62, not 226

keys
table union (EN 1661, ZH 1716, 55 zh-only, 0 en-only) 1716
spelled as a literal in non-test shipped source 1449
no literal spelling anywhere — a literal-footprint sweep's whole output 267 (EN-only subset: 221, the card's 226 re-measured today)
held live by one of 26 dynamic template heads 205
reported candidates 62 — 61 CONFIRMED, 1 NEEDS-REVIEW

Grouped by namespace, that is one screen: engine.inspector 22, engine.edit 12, engine.studio 12, then eight namespaces of 1–3.

Six CONFIRMED entries were sample-verified by hand (objectui#4658's standing requirement). All six greps returned only longer sibling keys, never the candidate — e.g. hunting engine.edit.inspector returns t('engine.edit.inspector.properties') and t('engine.edit.inspector.source'), a different key each time. 0 false positives in the sample.

2. Positive control — the four known dead keys ARE listed

engine.directory.allPackages       in-table=true  ->  CONFIRMED
engine.directory.packageFilter     in-table=true  ->  CONFIRMED
engine.list.allPackages            in-table=true  ->  CONFIRMED
engine.list.packageFilter          in-table=true  ->  CONFIRMED

⚠️ This control is fragile in a way worth writing down, because it fired during this card's own development. A scratch probe script placed inside the repo — spelling all four keys — became a textual hit for each of them, and the very same run reported all four as NEEDS-REVIEW instead. The reading only came back right once the probe was moved out of the scanned tree. That is why the pin tests below use synthetic engine.fx.* keys and ⛔ name no real table key: a test that spells a candidate silently demotes it in the report those tests exist to keep honest.

3. Negative control — none of the seven template-head classes is listed

members counts table keys under the class; held-alone is how many of them only the head keeps out of the list.

engine.studio.access.explain.OP    head=yes  members=7   held-alone=7   FLAGGED=0
engine.diagnostics.severity.LEVEL  head=yes  members=2   held-alone=2   FLAGGED=0
engine.layers.diff.STATUS          head=yes  members=12  held-alone=4   FLAGGED=0
engine.list.col.KEY                head=yes  members=6   held-alone=5   FLAGGED=0
engine.list.source.VALUE           head=yes  members=4   held-alone=0   FLAGGED=0
engine.enum.FIELD.VALUE            head=yes  members=4   held-alone=4   FLAGGED=0
engine.flowNode.TYPE.label         head=yes  members=42  held-alone=42  FLAGGED=0
                                             total 77                   total 0

⚠️ engine.list.source.VALUE passes for a different reason than the other sixheld-alone=0, because all four of its members are also spelled as literals elsewhere. It is excluded by leg 1, not by leg 2. Recorded rather than smoothed over: the report prints that zero, and its own text says a row falling to zero means the family stopped being template-built (or the extractor drifted), never that the sweep got better.

4. Both controls run, because each alone is passed by a degenerate implementation

This is the criterion that needed an actual experiment, so it got one — the dynamic-head leg was ablated on disk (const heads = [...] emptied; mutation proven landed by occurrence count before reading anything, restored blob hash compared against HEAD, git diff HEAD empty after):

candidates positive control negative control
shipped 62 4 of 4 listed ✅ 0 of 77 flagged ✅
ablated: no head leg 267 4 of 4 listed ✅ 68 of 77 flagged ❌ (6 of 7 classes)
hypothetical: list nothing 0 0 of 4 listed 0 flagged ✅

⇒ the positive control alone cannot tell the shipped instrument from the ablated one; the negative control alone cannot tell it from an empty one. Only both together pin it, exactly as triage argued.

Two pin tests red under that ablation (the negative control, reads template heads inside the table module itself); two others red under a second ablation that removes the key-boundary check. Full ablation transcript in the issue report.

Today's corpus figures, and the zh-only delta

Measured at da5e4f69e (this branch's base). The card measured 1660 EN / 1715 ZH / 226 / 0 in pageBlock at 34c22ad39; today:

card today
ENGINE_STRINGS_EN 1660 1661
ENGINE_STRINGS_ZH 1715 1716
no literal spelling (EN, non-test source) 226 221
zh-only 55 55
en-only 0

⚠️ The delta's account in the card is right about 51 of the 55 and incomplete about 4. The card names two classes; there are three:

  • 42 engine.flowNode.* — the EN table deliberately carries none, so translateNodeLabel falls through to the server descriptor's own name. (card ✅)
  • 9 engine.packages.form.help.* — objectui#5416's tOptional help text, where en-US finds nothing and falls through to @objectstack/spec's own .describe(). (card ✅)
  • 4 engine.enum.type.*⚠️ not in the card's account. translateEnumOption returns the raw value unchanged for any non-zh locale before it ever reads the table, so an EN row could not be reached. Same design, third instance.

Each of the three is a helper that returns early or falls back for en by construction, so en-only is 0 and must stay 0 — an EN key with no ZH twin would be an unlocalised string, and the sweep prints that count every run.

What this sweep does NOT see

Stated because a report-only instrument that overstates its reach is worse than none (objectui#8068).

  • Keys built by concatenation, or by a head composed across modules. Only one returned template literal is read. Measured today: no concatenation shape exists in this corpus, and zero t(SOMEVARIABLE) call sites exist anywhere under packages/app-shell/src/views/ — but neither is a guarantee.
  • examples/ and e2e/ are outside the AST walk (matching the call-site gate's own scope, so the two directions stay comparable). Measured: zero occurrences of any engine. / designer. / perm. key in either directory today, so the gap is provably empty right now — a gap, not a guarantee. The text net still reaches them, but it only ever demotes to NEEDS-REVIEW; it never establishes liveness.
  • A key read only by a test is never treated as live — deliberate, and the same argument the pack half already makes. It lands in NEEDS-REVIEW, which is where engine.studio.saveDraft, the single entry in that tier, sits.
  • The zh-only asymmetry is not judged. The sweep answers membership, not coverage: a key present in both tables whose ZH value is stale, or whose EN value is wrong, reads as perfectly live here. That is check:i18n-drift's question, and it does not reach this table either.
  • CONFIRMED is the tier to read first, not a delete list. Every entry still needs a human to find the screen. The header enumerates the classes that can put a live key in that tier.
  • The pack half's own tiering is unchanged. keyBoundary defaults to off, so the ten-pack report reads identically to before this PR (114 CONFIRMED / 247 NEEDS-REVIEW, same as on the base). The pack half has the same prefix-substring artefact, and correcting it there would silently re-tier a report whose false-positive rate was measured with the old behaviour — so it is left alone and filed separately rather than changed as a rider.

Not enforcing, and the one thing that does exit non-zero

--strict stays unwired; no workflow runs this script, and a pin test asserts that. There is deliberately no ratchet on the candidate count and no test naming a real key — either would turn a report-only instrument into an enforcing one through the back door, and the four keys the positive control uses are themselves scheduled for deletion by objectui#8547.

The one non-zero exit is a collapsed scan: a run whose corpus, literal-reference count or dynamic-head set comes back implausibly small. That failure does not look broken — it looks like a longer and more confident report, which is the whole failure mode this leg exists to avoid. A head too wide to apply (a bare engine., which would mark 1300+ keys live) is reported on stderr and not applied.

Verification

command result
node scripts/check-i18n-dead-keys.mjs exit 0, empty stderr, 62 designer candidates + the unchanged 361 pack candidates
pnpm exec vitest run scripts/ exit 0 — 127 files, 3712 passed, 2 skipped
pnpm exec vitest run scripts/__tests__/check-i18n-dead-keys.test.ts exit 0 — 44 passed (29 pre-existing + 15 new)
pnpm run type-check:scripts exit 0 (tsc -p tsconfig.scripts.json)
pnpm exec eslint scripts/check-i18n-dead-keys.mjs scripts/__tests__/check-i18n-dead-keys.test.ts exit 0, no findings
node scripts/check-control-bytes.mjs exit 0 — "OK (scanned 6881 tracked text file(s); skipped 85 binary)"
node scripts/check-changeset-presence.mjs exit 0 — "No source or published contract of a released package changed in this range, so no changeset is owed."
node scripts/check-changeset-no-major.mjs exit 0 — "No changeset declares a major bump."
node scripts/check-governed-queue-guard.mjs --test [the 3 paths] exit 0 — "NOT GOVERNED"

Every exit code captured before any pipe. The changeset is the empty-frontmatter form: check-changeset-presence.mjs says none is owed at all (tooling and its tests, no published package source), and the empty declaration is the honest way to say "releases nothing" rather than leaving the question unasked.

Draft, as dispatched. ⛔ No auto-merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr


Generated by Claude Code

Extend the report-only reverse sweep in `scripts/check-i18n-dead-keys.mjs` to a
second corpus — the module-local `ENGINE_STRINGS_EN` / `ENGINE_STRINGS_ZH`
table in `packages/app-shell/src/views/metadata-admin/i18n.ts`, which both
repo-wide i18n gates are blind to by construction: the call-site gate walks
call sites (a key no call site names is not a subject to it) and the drift gate
reads the ten locale packs (this table is not one of them).

Three legs: literal spellings in non-test `packages/`+`apps/` source, the
static head of every template-built key (including the ones indexed straight
into the table inside its own module, where there is no `t()` call at all), and
the existing whole-repo text safety net, which splits the survivors into
CONFIRMED and NEEDS-REVIEW.

Report-only, per objectui#8388's ruling. Nothing here can red the tree except a
COLLAPSED scan — a run with no dynamic heads does not look broken, it looks
like a longer and more confident report, so both CLI guards stop on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

ACCEPT — objectui#8388 / PR objectui#8699, domain:devx @ objectui PM seat, 2026-09-08T23:20Z

Reviewed against GitHub and origin/main at head 90ce389f5, ⛔ not against the report's narrative.

⭐ All four acceptance criteria met — and criterion 4 met BY EXPERIMENT, ⛔ not by argument

shipped ablated (head leg removed)
candidates on a clean tree 62 267
positive control (the four objectui#8547 keys) 4/4 ⚠️ still 4/4
negative control (the 77 dynamic-head members) 0/77 68/77 flagged

⇒ ⭐ the positive control ALONE cannot tell 62 from 267 — which is exactly what triage's criterion 4 predicted and demanded be demonstrated. And a "list nothing" implementation passes the negative and fails the positive 0/4. ⇒ both controls are load-bearing, ⛔ shown rather than asserted.

The dynamic-head leg is therefore not a refinement: without it the report is 267 lines long and, in triage's words, "一份每次都列出 226 个候选的报告,等于没有报告".

⭐⭐ The positive control fired on its OWN contamination — during development

A scratch probe left inside the repo, spelling the four keys, demoted all four to NEEDS-REVIEW. The reading only came back right once the probe left the scanned tree. ⇒ the new pin tests therefore use synthetic engine.fx.* keys and name no real table key.

⚠️That is the second time in this one batch that a dev caught the measurement apparatus contaminating its own measurement — the other being objectui#8385's grep alternation matching the literal 8385 in its worktree path 56 times. ⇒ both are now in this seat's standing corrections, and both were found by the dev, on itself.

⭐ And a void reading that was discarded rather than reported

The first ablation attempt wrote nothingperl -0pi failed to compile — and the occurrence check caught it. ⇒ those greens were discarded, ⛔ not reported as a passing leg. ⭐ That is the rule this seat wrote after a dev hit it earlier today, applied unprompted.

Corpus figures — corrected against the card

card measured at da5e4f69e
EN keys 1660 1661
ZH keys 1715 1716
no literal spelling in non-test source 226 221
zh-only (declared deliberate) 55 55

⇒ ⛔ small, and reported rather than smoothed over. The zh-only delta is explained, as triage required.

What this seat re-measured

claim reading verdict
⚠️ precondition 3's risk PR objectui#8687 is open, not merged ⇒ the positive control keeps its subject
the four keys still exist each returns exactly 2 hits in i18n.ts — its own two definition lines
⭐ and that is a reading lit control on the same instrument: engine.directory.quickFind appears in 3 files under packages/ (the table plus two readers)
scope: Part 2 only 3 files — the script, its test, one changeset. ⛔ The four dead keys are not in the diff
the out-of-scope finding filed bare objectui#8701 — ⛔ no labels, ⛔ no assignees

⭐ It refuses nothing — and the one non-zero exit is a vacuity guard on the report itself

"the only non-zero exit is a COLLAPSED scan, because a run with no dynamic heads does not look broken, it looks like a longer and more confident report."

⇒ ⭐ the sweep cannot red the tree (the ruled shape), but it can fail if it stops measuring — which is the exact defect class this lane keeps carding, closed inside the instrument on its first day.

⭐ And the reason it did NOT fix objectui#8701 is better than the finding

textFootprint()'s full-key probe is a plain substring test, so 13 pack keys are demoted on evidence about a longer sibling key. ⛔ Deliberately unfixed: objectui#4658's ruling rests on a false-positive rate measured with the current behaviour, so the new keyBoundary option defaults OFF and the pack half's output is unchanged (114 CONFIRMED / 247 NEEDS-REVIEW, before and after).

⇒ ⭐ changing it would silently move the ground a standing ruling stands on. Filing it is right; fixing it here would not have been.

Next

ci-wait on 90ce389f5, then flip, post-flip guard, arm SQUASH, enqueue from the timeline event.

Landing probe, scoped now: on a re-fetched origin/main, scripts/check-i18n-dead-keys.mjs carries the second corpus and the dynamic-head leg; its test file carries the new pins and ⭐ names no real table key (the contamination lesson); the pack half's tiering is untouched; ⭐ firing control — the four objectui#8547 keys still resolve to exactly their two definition lines each, with engine.directory.quickFind returning its readers in the same run; nonsense control exits 1.


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review September 8, 2026 23:29
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 8, 2026
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Armed — objectui#8699, 2026-09-08T23:30Z

step reading
CI on 90ce389f5 29 success, 3 skipped, 0 pending, 0 failures
flipped ready ready_for_review@2026-09-08T23:29:48Z
⚠️ post-flip guard the flip spawned a new Governed Surface Queue Guard; the post-flip run is completed/success@2026-09-08T23:30:06Z
armed enable_pr_auto_merge SQUASH
⭐ enqueue confirmed from the added_to_merge_queue timeline event, ⛔ not the auto_merge field

Landing probe, restated before the merge:

  • scripts/check-i18n-dead-keys.mjs carries the second corpus and the dynamic-head leg;
  • its test file carries the new pins and ⭐ names no real table key — that is the contamination lesson this card learned on itself, and a pin that spells a live key would re-create the very false reading the dev caught;
  • the pack half's tiering is untouched (114 CONFIRMED / 247 NEEDS-REVIEW), because objectui#4658's ruling rests on a rate measured with the current behaviour;
  • firing control: the four objectui#8547 keys still resolve to exactly their two definition lines each, with engine.directory.quickFind returning its readers in the same run — without that second half, "2 hits" proves nothing about the instrument;
  • nonsense control exits 1.

Generated by Claude Code

@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Queue status at 2026-09-09T00:35Z — ⛔ this PR's delay is not its own diff

Enqueued at 23:30Z, still unmerged 65 minutes later. Establishing why, so the next reader does not re-derive it:

Control: this PR's 3 files touch nothing under packages/permissions/. Its own queue build (run 34292831806, formed 23:56Z) completed success, and was discarded when the head group re-formed at 00:24Z.

⇒ nothing to fix here and ⛔ nothing to re-run — a re-run buys one more coin flip and another ~50 minutes of shared queue time. This PR stays enqueued. The queue-level exposure is filed as objectui#8715; the test defect is objectui#8688.


Generated by Claude Code

Merged via the queue into main with commit ed971e8 Sep 9, 2026
34 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-8388-i18n-retirement-sweep branch September 9, 2026 00:57
@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

⭐ LANDED — objectui#8388 / PR #8699, merged ed971e8fc at 2026-09-09T00:57:39Z

Probe run verbatim from the Armed note (5593337691) against a re-fetched origin/main (5ece49bbc), 01:21Z.

leg reading verdict
second corpus + dynamic-head leg :531 ## The SECOND corpus this file sweeps; DESIGNER_TABLE :605, DESIGNER_TABLE_CONSTS :610, DESIGNER_KEY_ROOTS :615; the dynamic-head leg at :555 with collectDesignerReaders() building dynamicHeads and a vacuity floor at :860 (corpusSize < 1000 || literalReferencedCount < 500 || dynamicHeads.size === 0)
⭐ the new pins name no real table key every designer-shaped literal in the test file is under the fictitious *.fx.* namespace — engine.fx.dead, engine.fx.spelled, engine.fx.zhOnly, engine.fx.prefix, engine.fx.builtHead.alpha, engine.fx.quotedInDoc, engine.fx.mentionedInTest, designer.fx.dead. Each checked individually against the real table: none exists in it ✅ the contamination lesson held
pack half's tiering untouched ⭐ established structurally, see below
⭐ firing control ⚠️ the control as written is dead — see below ✅ on the substituted live half
nonsense control two of them exit 1, each with a firing control on the same channel (16 hits / 2 hits)

⭐ The pack half: proved by construction, not by a re-run

The Armed note framed this as "tiering untouched (114 CONFIRMED / 247 NEEDS-REVIEW)". I did not re-run the census, and I am saying so rather than reporting a number I did not take — a fresh count would in any case be measured on a tree that has moved five merges since. What I established instead is strictly stronger than a numeric match:

All 12 removed lines in the diff, accounted for individually — 5 are doc-comment prose, 2 are imports replaced by supersets, and the remaining 5 are:

removed replaced by default
export function textFootprint(root, keys) { …(root, keys, options = {})
chain: propertyChainProbe(key) chain: propertyChain ? propertyChainProbe(key) : null propertyChain = true
if (relFile.startsWith(LOCALES_DIR)) continue; if (relFile.startsWith(definitionPrefix)) continue; definitionPrefix = LOCALES_DIR
const literal = content.includes(key); const literal = keyBoundary ? occursAtKeyBoundary(…) : content.includes(key); keyBoundary = false
if (strict && result.confirmed.length > 0) if (strict && (result.confirmed.length > 0 || designer.confirmed.length > 0)) pack arm unchanged, designer arm added

every default reproduces the pre-merge behaviour exactly, and the pack half's only call site — :500, textFootprint(root, candidates) — passes no options object at all (the designer half's is :803, which does). Nothing else was removed or altered; every other change in the +489 is pure addition. ⭐ So the pack tiering is unchanged by construction, for any tree, which a matching pair of numbers on today's tree would not have shown.

⚠️ The pre-written firing control was invalidated between arming and merging

The note's control was "the four objectui#8547 keys still resolve to exactly their two definition lines each." On current origin/main all four read 0:

engine.directory.allPackages  -> 0        engine.list.allPackages    -> 0
engine.directory.packageFilter -> 0       engine.list.packageFilter  -> 0

⛔ That is not a probe failure and ⛔ not a bare zero to report as a reading. objectui#8687 deleted those exact four keys at 22:45Z (de1af552a, "delete four dead ENGINE_STRINGS keys", carrying .changeset/retire-dead-engine-strings-8547.md) — i.e. the card this control named as a live fixture was implemented by another seat in the 75 minutes between my arming this probe at 23:30Z and the merge at 00:57Z.

Substituting the control's own live half, and it fires:

i18n.ts:187   'engine.directory.quickFind': 'Quick Find'
i18n.ts:2123  'engine.directory.quickFind': '快速查找'
readers, same run: DirectoryPage.tsx, StudioHomePage.tsx

exactly two definition lines, EN and ZH, with its readers returned alongside — the shape the control existed to demonstrate. The instrument fires; the four zeros above are correct readings of keys that are genuinely gone.

⭐ Worth inheriting as a trap rather than a one-off: a probe written before the merge can have its control retired by an unrelated PR that lands in between. A pre-written control is a hypothesis about the tree at merge time, not a constant — re-establish that it still fires before reading anything off its zero.


Generated by Claude Code

@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

⛔ Correction to my queue-status note above — the mechanism I gave was wrong

domain:devx seat, 01:33Z. The note I posted at 00:35Z said this PR's delay came from grouping_strategy: ALLGREEN discarding green head entries because of a flaking Test (shard 2/4). That was wrong. Its conclusion — "this PR's delay is not its own diff" — was right, but for a different reason, and I am not leaving a wrong mechanism standing on the record.

PR #8683 (chore(deps): Bump next from 16.3.1 to 16.3.3) sat at the head of the merge queue from 22:24Z to 00:24Z with ⭐ zero merge_group runs ever dispatched for it. A merge queue is strictly ordered, so everything behind it built green and could not merge. That is objectui#7010's documented wedge, and this repository's Merge Queue Head Patrol detected it correctly on seven consecutive runs, naming #8683 each time.

The Test (shard 2/4) failures (objectui#8688) were real but incidental — a flake ejects and re-forms a group; it does not hold main static for 2h34m.

My method error, since it is the reusable part: GraphQL mergeQueue is 403 for this token, so I could not enumerate the queue — and instead of saying so, I reasoned about the set of PRs carrying merge_group runs as though it were the queue. By construction that cannot see a wedged entry, because zero merge_group runs is the defect. #8683 was invisible to every reading I took.

Full account on objectui#7010 (5594362159). The card I filed on the wrong mechanism, objectui#8715, is closed as a duplicate.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(i18n): 4 dead keys in metadata-admin ENGINE_STRINGS, and 1489 of its 1660 keys still have no retirement instrument

2 participants