fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy - #15062

Merged
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count
Sep 3, 2026
Merged

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy#15062
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13837

os i18n check --help described the command as detecting "missing translation keys (object/field/option/view/action labels)". That parenthetical named 5 source kinds. The report carries 15. A five-of-fifteen sample does not read as an illustration — it reads as a scope statement, so a reader who wanted app navigation or dashboard widgets checked was told this command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist. This is the CLI-side twin of the sentence PR #13833 corrected in the published objectstack-i18n skill; fixing only the skill leaves the CLI teaching it at the moment a user runs the command.

What I measured on today's main (nothing inherited)

Triage measured 5-named against a 14-member union, and one small fixture reaching 12 kinds / 783 issues (metadataForm 769). Re-derived from origin/main at 99b4deba49, both numbers have moved and are re-taken here.

1 — the taxonomy is 15 members, not 14.dataset has been added to the CoverageIssue.source union since triage.

runtime witness, read from the map the phrase is derived from:
kinds: 15 ["object","field","option","section","view","action","globalAction","app",
"navigation","dashboard","widget","dataset","page","flow","metadataForm"]

2 — the retired parenthetical named 5 of those 15: object, field, option, view, action. Ten unnamed, not nine.

3 — an ordinary stack reaches 14 of the 15 kinds in one run. Driving the real walker (collectExpectedEntries) and the real detector (computeI18nCoverage) over one object, one global action, one app with nested navigation, one dashboard with a widget, one dataset, and one page carrying a filter-preset tab — the fixture now committed as this PR's pin:

walker : 16 kinds, 1195 entries
report : 14 kinds, 794 expectedKeys / 794 issues
per kind: object 1, field 2, option 2, view 2, action 2, globalAction 1, app 2,
navigation 2, dashboard 1, widget 1, dataset 3, page 1, section 1,
metadataForm 773

The one kind that did not appear is flow, and the reason is mechanical rather than incidental: authorWarnedTranslationGroups() returns ["flows"] (the shipped liveness ledger has that group at status: planned / authorWarn: true), and collectExpectedEntries filters every entry whose group is warned. See the rationale block in packages/cli/src/utils/i18n-extract.ts — that suppression is deliberate and self-lifting the day the screen-flow runner lands. The description still names flow screens, matching both the taxonomy and the landed skill twin; see "One thing I did not decide" below.

The fix

Wording. The parenthetical is gone from the line a user meets first, and the full surface set is stated where they go to read it.

Mechanism (the optional half — taken). The set is not retyped beside the taxonomy; it is derived from it. packages/cli/src/utils/i18n-coverage.ts gains SOURCE_SURFACE, a Record keyed by CoverageIssue.source whose value is the plural surface noun for each kind, and exports COVERAGE_SOURCE_KINDS and COVERAGE_SURFACE_PHRASE built from it. The Record keyed by the union is what makes it exhaustive by construction.

Declared file face. The card expected check.ts plus its pin. Deriving the phrase needs a second source file: packages/cli/src/utils/i18n-coverage.ts, where the taxonomy lives. Declaring that here, as the claim comment asks. Nothing else is touched; packages/spec is untouched, the skill text is untouched, content/docs/releases/ is untouched.

Why an oclif summary as well — no sibling command sets one, so this is the shape change worth naming. oclif renders a command in two places with different budgets. The COMMANDS list prints summary, falling back to the description's first line when a command sets none; the command's own --help prints both. Measured at COLUMNS=80, the derived set in the COMMANDS column wraps to five lines, at renderList's lines.length > 4 multiline threshold. So the short line goes in summary with the parenthetical dropped outright — triage's own second option, never a sample — and the derived set goes in description.

Rendered from the built CLI:

COLUMNS=80 node ./bin/run.js i18n --help
COMMANDS
i18n check Detect missing translation keys across all configured locales
i18n extract Scaffold per-locale translation skeletons from a stack config.
Default locale is filled from schema labels; other locales
follow --fill.
COLUMNS=80 node ./bin/run.js i18n check --help
DESCRIPTION
Detect missing translation keys across all configured locales
Every translatable surface the coverage report carries is checked: objects,
fields, options, sections, views, actions, global actions, apps, navigation
items, dashboards, widgets, datasets, pages, flow screens, metadata forms.

The A/B that produced the five-line reading was run on the build artifact (dist/commands/i18n/check.js), not on source: summary deleted and the one-line description restored, os i18n --help rendered, then the artifact restored and proved byte-identical by sha256 (76e899fa... before and after).

Red-first, and which arm fired

Three arms, each mutating on disk under trap on EXIT INT TERM with absolute paths, each mutation proved by blob hash and marker counts, each restore proved by blob-hash equality against the HEAD blob. Final git status --porcelain empty.

Arm A — the defect's own red. Retired description restored on check.ts (static override summary occurrences 0, retired-sample occurrences 1; HEAD blob eba0697d, on disk fca8c60c). The pin goes RED, 4 failed / 2 passed:

AssertionError: --help omits 15 of 15 kinds
AssertionError: reported but absent from --help: action, app, dashboard, dataset,
field, globalAction, metadataForm, navigation, object, option, page, section,
view, widget

Restored: on disk eba0697d == HEAD blob eba0697d.

Arm B1 — the derivation is enforced, not decorative. A sixteenth member 'sandbox' added to CoverageIssue.source and deliberately not named. tsc --noEmitrefuses, exit 1:

src/utils/i18n-coverage.ts(233,7): error TS2741: Property 'sandbox' is missing ... SOURCE_NOUN
src/utils/i18n-coverage.ts(274,7): error TS2741: Property 'sandbox' is missing ... SOURCE_SURFACE

Arm B2 — the derivation is proved to move. The same member added and named (sandbox: 'sandboxes'), with zero edits to check.ts:

kinds: 16 phrase length 169 (was 158)
description: ... pages, flow screens, metadata forms, sandboxes.

That is the arm the card asked for: the derived phrase follows a union change on its own, and a member that would go unnamed cannot compile.

Ablation validity. No rebuild is required for these arms and none is claimed: the pin imports ../src/commands/i18n/check.js and ../src/utils/i18n-coverage.js — same-package relative specifiers vitest and tsx resolve to source, never through a package exports map to dist. The dependency closure (pnpm --filter '@objectstack/cli^...' build) was built before any reading was taken, and no dependency was mutated.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe; verdicts quoted from each gate's own verdict line.

Re-run in full after the final commit (origin/main merged in), at git rev-parse --short HEAD = ff207ebab3 — 55/55 build tasks (FULL TURBO), 5 test files / 69 tests passed, typecheck clean:

  • pnpm exec turbo run build --filter='@objectstack/cli^...' — VERDICT command-exit 0
  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/i18n-check-help-surface-parity.test.ts test/i18n-coverage.test.ts test/commands.test.ts test/i18n-declared-surface-gate.test.ts test/i18n-dataset-coverage.test.ts5 files, 69 tests passed
  • pnpm --filter @objectstack/cli typecheck — clean, including the test layer (tsc --noEmit && check:test-typecheck; the ledger holds at 3 files / 28 errors / 6 pinned signatures, unchanged). Worth stating because packages/cli/tsconfig.json is include: ['src'], so tsc --noEmit alone says nothing about packages/cli/test/** — the check:test-typecheck leg against tsconfig.test.json is what covers the new pin.
  • pnpm --filter @objectstack/cli build — VERDICT command-exit 0 (for the rendered --help above; taken at 315e17753c, the last commit before the merge, and the strings it renders are unchanged since)

Gate union re-derived at the final HEAD after merging origin/main: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gives 42 families. Run: 37 green. Reported honestly as NOT MEASURED, not as passes and not as reds — five, each self-declaring it:

  • check-test-completeness — exit 3; needs a saved turbo run test log, which only CI produces. The script names this branch as the family-list invocation and says to record it as NOT MEASURED.
  • check-half-states / check:pm-half-states — exit 3; needs repo-scoped REST, which this session does not have (a repo-scoped probe returned 403 GitHub access is not enabled for this session).
  • check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET; 12 packages have no dist/.
  • check:i18n-coverage — exit 3 PREREQUISITE NOT MET; examples/app-showcase could not lint because @objectstack/connector-mcp has no build output in this worktree. Nothing was compared, so the baseline says nothing either way. Declared narrowing: the build it names was queued for the shared verify lock and did not get a turn. This change adds two exported constants and one map; it alters no walker, no detector and no key path, so the coverage baseline has no route to move — but that is an argument, not a measurement, and CI runs the family.
  • check:type-check-debt — its --re-measure leg needs the whole workspace closure built; not attempted for the same reason. Its structural half, check:type-check-coverage, ran green, and packages/cli's own check:test-typecheck ledger is unchanged.

Repo-wide ESLint (pnpm lint) is CI's run and is not claimed here.

One thing I did not decide, and did not bury

The description names flow screens, and today no flow gap can be reported: the ledger suppression measured above filters the whole flows group out of the walk. I followed the taxonomy and the landed skill twin (#13833 names "flow screens" in the same sentence) rather than diverging the two halves of a deliberately paired fix, and the suppression lifts itself with no edit here. I did not open a new card for it: #11617 is open and already carries the "flows become visible once the bucket lands" state, and i18n-extract.ts documents the collision that produced the suppression. Flagged here so it is triaged rather than settled by a wording PR.

Changeset

.changeset/tricky-pans-repeat.md, patch on @objectstack/cli. Measured rather than assumed: packages/cli is not private, and its files field is ["dist","README.md","CHANGELOG.md"]dist/commands/i18n/check.js carries the string oclif prints, so this help text ships to users from a published package.


🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

The description named five source kinds — object/field/option/view/action —
against the fifteen-member `CoverageIssue['source']` taxonomy the report
actually carries. A five-of-fifteen sample reads as a scope statement rather
than an illustration: a reader who wanted app navigation or dashboard widgets
checked concluded the command did not cover them.
Derive the phrase from a `Record<CoverageIssue['source'], string>` map so the
help text and the taxonomy are one fact with one source: a new union member is
a compile error until it is named, and is published in `--help` in the same
edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ived surface set
oclif renders a command in two places with different budgets: the COMMANDS list
shows `summary` (falling back to the description's first line), and flips the
whole list to a multi-line layout once an entry wraps past five lines. So the
short line goes in `summary` with the parenthetical dropped outright, and the
full derived surface set goes in `description`, where a reader asking what the
command covers is actually looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ing it
oclif prints `summary` and then `description` in the DESCRIPTION section, so a
description that repeats the summary sentence renders it twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…tement
The A/B run at COLUMNS=80 shows the description-only shape wraps to five lines
in the COMMANDS column — at oclif's `lines.length > 4` multiline threshold, not
past it. Say what was measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
  • content/docs/ui/translations.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f — the merge of head ff207ebab3b80e92544cea0b4160025f5935f902 into base 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f && git checkout c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 ff207ebab3b80e92544cea0b4160025f5935f902 && git checkout -B drift-repro 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 && git merge --no-ff ff207ebab3b80e92544cea0b4160025f5935f902
node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 19:52
@os-trump
os-trump added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit be41618Sep 3, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13837-i18n-check-help-source-count branch September 3, 2026 20:30
os-trump pushed a commit that referenced this pull request Sep 3, 2026
…arity
Brings in PR #15062 (`os i18n check --help`), which lands in packages/cli
while this branch is open — the dispatch named it as the one to merge and
re-verify against. Also #15065 (rest), #15063/#15067 (docs) and #15055 (spec).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os i18n check --help under-counts what it reports by 9 of 14 key kinds — the CLI-side twin of a skill falsehood corrected in #13833

2 participants

@os-trump@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy - #15062

Merged
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count
Sep 3, 2026
Merged

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy#15062
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13837

os i18n check --help described the command as detecting "missing translation keys (object/field/option/view/action labels)". That parenthetical named 5 source kinds. The report carries 15. A five-of-fifteen sample does not read as an illustration — it reads as a scope statement, so a reader who wanted app navigation or dashboard widgets checked was told this command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist. This is the CLI-side twin of the sentence PR #13833 corrected in the published objectstack-i18n skill; fixing only the skill leaves the CLI teaching it at the moment a user runs the command.

What I measured on today's main (nothing inherited)

Triage measured 5-named against a 14-member union, and one small fixture reaching 12 kinds / 783 issues (metadataForm 769). Re-derived from origin/main at 99b4deba49, both numbers have moved and are re-taken here.

1 — the taxonomy is 15 members, not 14.dataset has been added to the CoverageIssue.source union since triage.

runtime witness, read from the map the phrase is derived from:
kinds: 15 ["object","field","option","section","view","action","globalAction","app",
"navigation","dashboard","widget","dataset","page","flow","metadataForm"]

2 — the retired parenthetical named 5 of those 15: object, field, option, view, action. Ten unnamed, not nine.

3 — an ordinary stack reaches 14 of the 15 kinds in one run. Driving the real walker (collectExpectedEntries) and the real detector (computeI18nCoverage) over one object, one global action, one app with nested navigation, one dashboard with a widget, one dataset, and one page carrying a filter-preset tab — the fixture now committed as this PR's pin:

walker : 16 kinds, 1195 entries
report : 14 kinds, 794 expectedKeys / 794 issues
per kind: object 1, field 2, option 2, view 2, action 2, globalAction 1, app 2,
navigation 2, dashboard 1, widget 1, dataset 3, page 1, section 1,
metadataForm 773

The one kind that did not appear is flow, and the reason is mechanical rather than incidental: authorWarnedTranslationGroups() returns ["flows"] (the shipped liveness ledger has that group at status: planned / authorWarn: true), and collectExpectedEntries filters every entry whose group is warned. See the rationale block in packages/cli/src/utils/i18n-extract.ts — that suppression is deliberate and self-lifting the day the screen-flow runner lands. The description still names flow screens, matching both the taxonomy and the landed skill twin; see "One thing I did not decide" below.

The fix

Wording. The parenthetical is gone from the line a user meets first, and the full surface set is stated where they go to read it.

Mechanism (the optional half — taken). The set is not retyped beside the taxonomy; it is derived from it. packages/cli/src/utils/i18n-coverage.ts gains SOURCE_SURFACE, a Record keyed by CoverageIssue.source whose value is the plural surface noun for each kind, and exports COVERAGE_SOURCE_KINDS and COVERAGE_SURFACE_PHRASE built from it. The Record keyed by the union is what makes it exhaustive by construction.

Declared file face. The card expected check.ts plus its pin. Deriving the phrase needs a second source file: packages/cli/src/utils/i18n-coverage.ts, where the taxonomy lives. Declaring that here, as the claim comment asks. Nothing else is touched; packages/spec is untouched, the skill text is untouched, content/docs/releases/ is untouched.

Why an oclif summary as well — no sibling command sets one, so this is the shape change worth naming. oclif renders a command in two places with different budgets. The COMMANDS list prints summary, falling back to the description's first line when a command sets none; the command's own --help prints both. Measured at COLUMNS=80, the derived set in the COMMANDS column wraps to five lines, at renderList's lines.length > 4 multiline threshold. So the short line goes in summary with the parenthetical dropped outright — triage's own second option, never a sample — and the derived set goes in description.

Rendered from the built CLI:

COLUMNS=80 node ./bin/run.js i18n --help
COMMANDS
i18n check Detect missing translation keys across all configured locales
i18n extract Scaffold per-locale translation skeletons from a stack config.
Default locale is filled from schema labels; other locales
follow --fill.
COLUMNS=80 node ./bin/run.js i18n check --help
DESCRIPTION
Detect missing translation keys across all configured locales
Every translatable surface the coverage report carries is checked: objects,
fields, options, sections, views, actions, global actions, apps, navigation
items, dashboards, widgets, datasets, pages, flow screens, metadata forms.

The A/B that produced the five-line reading was run on the build artifact (dist/commands/i18n/check.js), not on source: summary deleted and the one-line description restored, os i18n --help rendered, then the artifact restored and proved byte-identical by sha256 (76e899fa... before and after).

Red-first, and which arm fired

Three arms, each mutating on disk under trap on EXIT INT TERM with absolute paths, each mutation proved by blob hash and marker counts, each restore proved by blob-hash equality against the HEAD blob. Final git status --porcelain empty.

Arm A — the defect's own red. Retired description restored on check.ts (static override summary occurrences 0, retired-sample occurrences 1; HEAD blob eba0697d, on disk fca8c60c). The pin goes RED, 4 failed / 2 passed:

AssertionError: --help omits 15 of 15 kinds
AssertionError: reported but absent from --help: action, app, dashboard, dataset,
field, globalAction, metadataForm, navigation, object, option, page, section,
view, widget

Restored: on disk eba0697d == HEAD blob eba0697d.

Arm B1 — the derivation is enforced, not decorative. A sixteenth member 'sandbox' added to CoverageIssue.source and deliberately not named. tsc --noEmitrefuses, exit 1:

src/utils/i18n-coverage.ts(233,7): error TS2741: Property 'sandbox' is missing ... SOURCE_NOUN
src/utils/i18n-coverage.ts(274,7): error TS2741: Property 'sandbox' is missing ... SOURCE_SURFACE

Arm B2 — the derivation is proved to move. The same member added and named (sandbox: 'sandboxes'), with zero edits to check.ts:

kinds: 16 phrase length 169 (was 158)
description: ... pages, flow screens, metadata forms, sandboxes.

That is the arm the card asked for: the derived phrase follows a union change on its own, and a member that would go unnamed cannot compile.

Ablation validity. No rebuild is required for these arms and none is claimed: the pin imports ../src/commands/i18n/check.js and ../src/utils/i18n-coverage.js — same-package relative specifiers vitest and tsx resolve to source, never through a package exports map to dist. The dependency closure (pnpm --filter '@objectstack/cli^...' build) was built before any reading was taken, and no dependency was mutated.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe; verdicts quoted from each gate's own verdict line.

Re-run in full after the final commit (origin/main merged in), at git rev-parse --short HEAD = ff207ebab3 — 55/55 build tasks (FULL TURBO), 5 test files / 69 tests passed, typecheck clean:

  • pnpm exec turbo run build --filter='@objectstack/cli^...' — VERDICT command-exit 0
  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/i18n-check-help-surface-parity.test.ts test/i18n-coverage.test.ts test/commands.test.ts test/i18n-declared-surface-gate.test.ts test/i18n-dataset-coverage.test.ts5 files, 69 tests passed
  • pnpm --filter @objectstack/cli typecheck — clean, including the test layer (tsc --noEmit && check:test-typecheck; the ledger holds at 3 files / 28 errors / 6 pinned signatures, unchanged). Worth stating because packages/cli/tsconfig.json is include: ['src'], so tsc --noEmit alone says nothing about packages/cli/test/** — the check:test-typecheck leg against tsconfig.test.json is what covers the new pin.
  • pnpm --filter @objectstack/cli build — VERDICT command-exit 0 (for the rendered --help above; taken at 315e17753c, the last commit before the merge, and the strings it renders are unchanged since)

Gate union re-derived at the final HEAD after merging origin/main: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gives 42 families. Run: 37 green. Reported honestly as NOT MEASURED, not as passes and not as reds — five, each self-declaring it:

  • check-test-completeness — exit 3; needs a saved turbo run test log, which only CI produces. The script names this branch as the family-list invocation and says to record it as NOT MEASURED.
  • check-half-states / check:pm-half-states — exit 3; needs repo-scoped REST, which this session does not have (a repo-scoped probe returned 403 GitHub access is not enabled for this session).
  • check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET; 12 packages have no dist/.
  • check:i18n-coverage — exit 3 PREREQUISITE NOT MET; examples/app-showcase could not lint because @objectstack/connector-mcp has no build output in this worktree. Nothing was compared, so the baseline says nothing either way. Declared narrowing: the build it names was queued for the shared verify lock and did not get a turn. This change adds two exported constants and one map; it alters no walker, no detector and no key path, so the coverage baseline has no route to move — but that is an argument, not a measurement, and CI runs the family.
  • check:type-check-debt — its --re-measure leg needs the whole workspace closure built; not attempted for the same reason. Its structural half, check:type-check-coverage, ran green, and packages/cli's own check:test-typecheck ledger is unchanged.

Repo-wide ESLint (pnpm lint) is CI's run and is not claimed here.

One thing I did not decide, and did not bury

The description names flow screens, and today no flow gap can be reported: the ledger suppression measured above filters the whole flows group out of the walk. I followed the taxonomy and the landed skill twin (#13833 names "flow screens" in the same sentence) rather than diverging the two halves of a deliberately paired fix, and the suppression lifts itself with no edit here. I did not open a new card for it: #11617 is open and already carries the "flows become visible once the bucket lands" state, and i18n-extract.ts documents the collision that produced the suppression. Flagged here so it is triaged rather than settled by a wording PR.

Changeset

.changeset/tricky-pans-repeat.md, patch on @objectstack/cli. Measured rather than assumed: packages/cli is not private, and its files field is ["dist","README.md","CHANGELOG.md"]dist/commands/i18n/check.js carries the string oclif prints, so this help text ships to users from a published package.


🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

The description named five source kinds — object/field/option/view/action —
against the fifteen-member `CoverageIssue['source']` taxonomy the report
actually carries. A five-of-fifteen sample reads as a scope statement rather
than an illustration: a reader who wanted app navigation or dashboard widgets
checked concluded the command did not cover them.
Derive the phrase from a `Record<CoverageIssue['source'], string>` map so the
help text and the taxonomy are one fact with one source: a new union member is
a compile error until it is named, and is published in `--help` in the same
edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ived surface set
oclif renders a command in two places with different budgets: the COMMANDS list
shows `summary` (falling back to the description's first line), and flips the
whole list to a multi-line layout once an entry wraps past five lines. So the
short line goes in `summary` with the parenthetical dropped outright, and the
full derived surface set goes in `description`, where a reader asking what the
command covers is actually looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ing it
oclif prints `summary` and then `description` in the DESCRIPTION section, so a
description that repeats the summary sentence renders it twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…tement
The A/B run at COLUMNS=80 shows the description-only shape wraps to five lines
in the COMMANDS column — at oclif's `lines.length > 4` multiline threshold, not
past it. Say what was measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
  • content/docs/ui/translations.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f — the merge of head ff207ebab3b80e92544cea0b4160025f5935f902 into base 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f && git checkout c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 ff207ebab3b80e92544cea0b4160025f5935f902 && git checkout -B drift-repro 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 && git merge --no-ff ff207ebab3b80e92544cea0b4160025f5935f902
node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 19:52
@os-trump
os-trump added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit be41618Sep 3, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13837-i18n-check-help-source-count branch September 3, 2026 20:30
os-trump pushed a commit that referenced this pull request Sep 3, 2026
…arity
Brings in PR #15062 (`os i18n check --help`), which lands in packages/cli
while this branch is open — the dispatch named it as the one to merge and
re-verify against. Also #15065 (rest), #15063/#15067 (docs) and #15055 (spec).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os i18n check --help under-counts what it reports by 9 of 14 key kinds — the CLI-side twin of a skill falsehood corrected in #13833

2 participants

@os-trump@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy - #15062

Merged
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count
Sep 3, 2026
Merged

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy#15062
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13837

os i18n check --help described the command as detecting "missing translation keys (object/field/option/view/action labels)". That parenthetical named 5 source kinds. The report carries 15. A five-of-fifteen sample does not read as an illustration — it reads as a scope statement, so a reader who wanted app navigation or dashboard widgets checked was told this command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist. This is the CLI-side twin of the sentence PR #13833 corrected in the published objectstack-i18n skill; fixing only the skill leaves the CLI teaching it at the moment a user runs the command.

What I measured on today's main (nothing inherited)

Triage measured 5-named against a 14-member union, and one small fixture reaching 12 kinds / 783 issues (metadataForm 769). Re-derived from origin/main at 99b4deba49, both numbers have moved and are re-taken here.

1 — the taxonomy is 15 members, not 14.dataset has been added to the CoverageIssue.source union since triage.

runtime witness, read from the map the phrase is derived from:
kinds: 15 ["object","field","option","section","view","action","globalAction","app",
"navigation","dashboard","widget","dataset","page","flow","metadataForm"]

2 — the retired parenthetical named 5 of those 15: object, field, option, view, action. Ten unnamed, not nine.

3 — an ordinary stack reaches 14 of the 15 kinds in one run. Driving the real walker (collectExpectedEntries) and the real detector (computeI18nCoverage) over one object, one global action, one app with nested navigation, one dashboard with a widget, one dataset, and one page carrying a filter-preset tab — the fixture now committed as this PR's pin:

walker : 16 kinds, 1195 entries
report : 14 kinds, 794 expectedKeys / 794 issues
per kind: object 1, field 2, option 2, view 2, action 2, globalAction 1, app 2,
navigation 2, dashboard 1, widget 1, dataset 3, page 1, section 1,
metadataForm 773

The one kind that did not appear is flow, and the reason is mechanical rather than incidental: authorWarnedTranslationGroups() returns ["flows"] (the shipped liveness ledger has that group at status: planned / authorWarn: true), and collectExpectedEntries filters every entry whose group is warned. See the rationale block in packages/cli/src/utils/i18n-extract.ts — that suppression is deliberate and self-lifting the day the screen-flow runner lands. The description still names flow screens, matching both the taxonomy and the landed skill twin; see "One thing I did not decide" below.

The fix

Wording. The parenthetical is gone from the line a user meets first, and the full surface set is stated where they go to read it.

Mechanism (the optional half — taken). The set is not retyped beside the taxonomy; it is derived from it. packages/cli/src/utils/i18n-coverage.ts gains SOURCE_SURFACE, a Record keyed by CoverageIssue.source whose value is the plural surface noun for each kind, and exports COVERAGE_SOURCE_KINDS and COVERAGE_SURFACE_PHRASE built from it. The Record keyed by the union is what makes it exhaustive by construction.

Declared file face. The card expected check.ts plus its pin. Deriving the phrase needs a second source file: packages/cli/src/utils/i18n-coverage.ts, where the taxonomy lives. Declaring that here, as the claim comment asks. Nothing else is touched; packages/spec is untouched, the skill text is untouched, content/docs/releases/ is untouched.

Why an oclif summary as well — no sibling command sets one, so this is the shape change worth naming. oclif renders a command in two places with different budgets. The COMMANDS list prints summary, falling back to the description's first line when a command sets none; the command's own --help prints both. Measured at COLUMNS=80, the derived set in the COMMANDS column wraps to five lines, at renderList's lines.length > 4 multiline threshold. So the short line goes in summary with the parenthetical dropped outright — triage's own second option, never a sample — and the derived set goes in description.

Rendered from the built CLI:

COLUMNS=80 node ./bin/run.js i18n --help
COMMANDS
i18n check Detect missing translation keys across all configured locales
i18n extract Scaffold per-locale translation skeletons from a stack config.
Default locale is filled from schema labels; other locales
follow --fill.
COLUMNS=80 node ./bin/run.js i18n check --help
DESCRIPTION
Detect missing translation keys across all configured locales
Every translatable surface the coverage report carries is checked: objects,
fields, options, sections, views, actions, global actions, apps, navigation
items, dashboards, widgets, datasets, pages, flow screens, metadata forms.

The A/B that produced the five-line reading was run on the build artifact (dist/commands/i18n/check.js), not on source: summary deleted and the one-line description restored, os i18n --help rendered, then the artifact restored and proved byte-identical by sha256 (76e899fa... before and after).

Red-first, and which arm fired

Three arms, each mutating on disk under trap on EXIT INT TERM with absolute paths, each mutation proved by blob hash and marker counts, each restore proved by blob-hash equality against the HEAD blob. Final git status --porcelain empty.

Arm A — the defect's own red. Retired description restored on check.ts (static override summary occurrences 0, retired-sample occurrences 1; HEAD blob eba0697d, on disk fca8c60c). The pin goes RED, 4 failed / 2 passed:

AssertionError: --help omits 15 of 15 kinds
AssertionError: reported but absent from --help: action, app, dashboard, dataset,
field, globalAction, metadataForm, navigation, object, option, page, section,
view, widget

Restored: on disk eba0697d == HEAD blob eba0697d.

Arm B1 — the derivation is enforced, not decorative. A sixteenth member 'sandbox' added to CoverageIssue.source and deliberately not named. tsc --noEmitrefuses, exit 1:

src/utils/i18n-coverage.ts(233,7): error TS2741: Property 'sandbox' is missing ... SOURCE_NOUN
src/utils/i18n-coverage.ts(274,7): error TS2741: Property 'sandbox' is missing ... SOURCE_SURFACE

Arm B2 — the derivation is proved to move. The same member added and named (sandbox: 'sandboxes'), with zero edits to check.ts:

kinds: 16 phrase length 169 (was 158)
description: ... pages, flow screens, metadata forms, sandboxes.

That is the arm the card asked for: the derived phrase follows a union change on its own, and a member that would go unnamed cannot compile.

Ablation validity. No rebuild is required for these arms and none is claimed: the pin imports ../src/commands/i18n/check.js and ../src/utils/i18n-coverage.js — same-package relative specifiers vitest and tsx resolve to source, never through a package exports map to dist. The dependency closure (pnpm --filter '@objectstack/cli^...' build) was built before any reading was taken, and no dependency was mutated.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe; verdicts quoted from each gate's own verdict line.

Re-run in full after the final commit (origin/main merged in), at git rev-parse --short HEAD = ff207ebab3 — 55/55 build tasks (FULL TURBO), 5 test files / 69 tests passed, typecheck clean:

  • pnpm exec turbo run build --filter='@objectstack/cli^...' — VERDICT command-exit 0
  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/i18n-check-help-surface-parity.test.ts test/i18n-coverage.test.ts test/commands.test.ts test/i18n-declared-surface-gate.test.ts test/i18n-dataset-coverage.test.ts5 files, 69 tests passed
  • pnpm --filter @objectstack/cli typecheck — clean, including the test layer (tsc --noEmit && check:test-typecheck; the ledger holds at 3 files / 28 errors / 6 pinned signatures, unchanged). Worth stating because packages/cli/tsconfig.json is include: ['src'], so tsc --noEmit alone says nothing about packages/cli/test/** — the check:test-typecheck leg against tsconfig.test.json is what covers the new pin.
  • pnpm --filter @objectstack/cli build — VERDICT command-exit 0 (for the rendered --help above; taken at 315e17753c, the last commit before the merge, and the strings it renders are unchanged since)

Gate union re-derived at the final HEAD after merging origin/main: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gives 42 families. Run: 37 green. Reported honestly as NOT MEASURED, not as passes and not as reds — five, each self-declaring it:

  • check-test-completeness — exit 3; needs a saved turbo run test log, which only CI produces. The script names this branch as the family-list invocation and says to record it as NOT MEASURED.
  • check-half-states / check:pm-half-states — exit 3; needs repo-scoped REST, which this session does not have (a repo-scoped probe returned 403 GitHub access is not enabled for this session).
  • check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET; 12 packages have no dist/.
  • check:i18n-coverage — exit 3 PREREQUISITE NOT MET; examples/app-showcase could not lint because @objectstack/connector-mcp has no build output in this worktree. Nothing was compared, so the baseline says nothing either way. Declared narrowing: the build it names was queued for the shared verify lock and did not get a turn. This change adds two exported constants and one map; it alters no walker, no detector and no key path, so the coverage baseline has no route to move — but that is an argument, not a measurement, and CI runs the family.
  • check:type-check-debt — its --re-measure leg needs the whole workspace closure built; not attempted for the same reason. Its structural half, check:type-check-coverage, ran green, and packages/cli's own check:test-typecheck ledger is unchanged.

Repo-wide ESLint (pnpm lint) is CI's run and is not claimed here.

One thing I did not decide, and did not bury

The description names flow screens, and today no flow gap can be reported: the ledger suppression measured above filters the whole flows group out of the walk. I followed the taxonomy and the landed skill twin (#13833 names "flow screens" in the same sentence) rather than diverging the two halves of a deliberately paired fix, and the suppression lifts itself with no edit here. I did not open a new card for it: #11617 is open and already carries the "flows become visible once the bucket lands" state, and i18n-extract.ts documents the collision that produced the suppression. Flagged here so it is triaged rather than settled by a wording PR.

Changeset

.changeset/tricky-pans-repeat.md, patch on @objectstack/cli. Measured rather than assumed: packages/cli is not private, and its files field is ["dist","README.md","CHANGELOG.md"]dist/commands/i18n/check.js carries the string oclif prints, so this help text ships to users from a published package.


🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

The description named five source kinds — object/field/option/view/action —
against the fifteen-member `CoverageIssue['source']` taxonomy the report
actually carries. A five-of-fifteen sample reads as a scope statement rather
than an illustration: a reader who wanted app navigation or dashboard widgets
checked concluded the command did not cover them.
Derive the phrase from a `Record<CoverageIssue['source'], string>` map so the
help text and the taxonomy are one fact with one source: a new union member is
a compile error until it is named, and is published in `--help` in the same
edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ived surface set
oclif renders a command in two places with different budgets: the COMMANDS list
shows `summary` (falling back to the description's first line), and flips the
whole list to a multi-line layout once an entry wraps past five lines. So the
short line goes in `summary` with the parenthetical dropped outright, and the
full derived surface set goes in `description`, where a reader asking what the
command covers is actually looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ing it
oclif prints `summary` and then `description` in the DESCRIPTION section, so a
description that repeats the summary sentence renders it twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…tement
The A/B run at COLUMNS=80 shows the description-only shape wraps to five lines
in the COMMANDS column — at oclif's `lines.length > 4` multiline threshold, not
past it. Say what was measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
  • content/docs/ui/translations.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f — the merge of head ff207ebab3b80e92544cea0b4160025f5935f902 into base 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f && git checkout c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 ff207ebab3b80e92544cea0b4160025f5935f902 && git checkout -B drift-repro 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 && git merge --no-ff ff207ebab3b80e92544cea0b4160025f5935f902
node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 19:52
@os-trump
os-trump added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit be41618Sep 3, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13837-i18n-check-help-source-count branch September 3, 2026 20:30
os-trump pushed a commit that referenced this pull request Sep 3, 2026
…arity
Brings in PR #15062 (`os i18n check --help`), which lands in packages/cli
while this branch is open — the dispatch named it as the one to merge and
re-verify against. Also #15065 (rest), #15063/#15067 (docs) and #15055 (spec).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os i18n check --help under-counts what it reports by 9 of 14 key kinds — the CLI-side twin of a skill falsehood corrected in #13833

2 participants

@os-trump@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy - #15062

Merged
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count
Sep 3, 2026
Merged

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy#15062
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13837

os i18n check --help described the command as detecting "missing translation keys (object/field/option/view/action labels)". That parenthetical named 5 source kinds. The report carries 15. A five-of-fifteen sample does not read as an illustration — it reads as a scope statement, so a reader who wanted app navigation or dashboard widgets checked was told this command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist. This is the CLI-side twin of the sentence PR #13833 corrected in the published objectstack-i18n skill; fixing only the skill leaves the CLI teaching it at the moment a user runs the command.

What I measured on today's main (nothing inherited)

Triage measured 5-named against a 14-member union, and one small fixture reaching 12 kinds / 783 issues (metadataForm 769). Re-derived from origin/main at 99b4deba49, both numbers have moved and are re-taken here.

1 — the taxonomy is 15 members, not 14.dataset has been added to the CoverageIssue.source union since triage.

runtime witness, read from the map the phrase is derived from:
kinds: 15 ["object","field","option","section","view","action","globalAction","app",
"navigation","dashboard","widget","dataset","page","flow","metadataForm"]

2 — the retired parenthetical named 5 of those 15: object, field, option, view, action. Ten unnamed, not nine.

3 — an ordinary stack reaches 14 of the 15 kinds in one run. Driving the real walker (collectExpectedEntries) and the real detector (computeI18nCoverage) over one object, one global action, one app with nested navigation, one dashboard with a widget, one dataset, and one page carrying a filter-preset tab — the fixture now committed as this PR's pin:

walker : 16 kinds, 1195 entries
report : 14 kinds, 794 expectedKeys / 794 issues
per kind: object 1, field 2, option 2, view 2, action 2, globalAction 1, app 2,
navigation 2, dashboard 1, widget 1, dataset 3, page 1, section 1,
metadataForm 773

The one kind that did not appear is flow, and the reason is mechanical rather than incidental: authorWarnedTranslationGroups() returns ["flows"] (the shipped liveness ledger has that group at status: planned / authorWarn: true), and collectExpectedEntries filters every entry whose group is warned. See the rationale block in packages/cli/src/utils/i18n-extract.ts — that suppression is deliberate and self-lifting the day the screen-flow runner lands. The description still names flow screens, matching both the taxonomy and the landed skill twin; see "One thing I did not decide" below.

The fix

Wording. The parenthetical is gone from the line a user meets first, and the full surface set is stated where they go to read it.

Mechanism (the optional half — taken). The set is not retyped beside the taxonomy; it is derived from it. packages/cli/src/utils/i18n-coverage.ts gains SOURCE_SURFACE, a Record keyed by CoverageIssue.source whose value is the plural surface noun for each kind, and exports COVERAGE_SOURCE_KINDS and COVERAGE_SURFACE_PHRASE built from it. The Record keyed by the union is what makes it exhaustive by construction.

Declared file face. The card expected check.ts plus its pin. Deriving the phrase needs a second source file: packages/cli/src/utils/i18n-coverage.ts, where the taxonomy lives. Declaring that here, as the claim comment asks. Nothing else is touched; packages/spec is untouched, the skill text is untouched, content/docs/releases/ is untouched.

Why an oclif summary as well — no sibling command sets one, so this is the shape change worth naming. oclif renders a command in two places with different budgets. The COMMANDS list prints summary, falling back to the description's first line when a command sets none; the command's own --help prints both. Measured at COLUMNS=80, the derived set in the COMMANDS column wraps to five lines, at renderList's lines.length > 4 multiline threshold. So the short line goes in summary with the parenthetical dropped outright — triage's own second option, never a sample — and the derived set goes in description.

Rendered from the built CLI:

COLUMNS=80 node ./bin/run.js i18n --help
COMMANDS
i18n check Detect missing translation keys across all configured locales
i18n extract Scaffold per-locale translation skeletons from a stack config.
Default locale is filled from schema labels; other locales
follow --fill.
COLUMNS=80 node ./bin/run.js i18n check --help
DESCRIPTION
Detect missing translation keys across all configured locales
Every translatable surface the coverage report carries is checked: objects,
fields, options, sections, views, actions, global actions, apps, navigation
items, dashboards, widgets, datasets, pages, flow screens, metadata forms.

The A/B that produced the five-line reading was run on the build artifact (dist/commands/i18n/check.js), not on source: summary deleted and the one-line description restored, os i18n --help rendered, then the artifact restored and proved byte-identical by sha256 (76e899fa... before and after).

Red-first, and which arm fired

Three arms, each mutating on disk under trap on EXIT INT TERM with absolute paths, each mutation proved by blob hash and marker counts, each restore proved by blob-hash equality against the HEAD blob. Final git status --porcelain empty.

Arm A — the defect's own red. Retired description restored on check.ts (static override summary occurrences 0, retired-sample occurrences 1; HEAD blob eba0697d, on disk fca8c60c). The pin goes RED, 4 failed / 2 passed:

AssertionError: --help omits 15 of 15 kinds
AssertionError: reported but absent from --help: action, app, dashboard, dataset,
field, globalAction, metadataForm, navigation, object, option, page, section,
view, widget

Restored: on disk eba0697d == HEAD blob eba0697d.

Arm B1 — the derivation is enforced, not decorative. A sixteenth member 'sandbox' added to CoverageIssue.source and deliberately not named. tsc --noEmitrefuses, exit 1:

src/utils/i18n-coverage.ts(233,7): error TS2741: Property 'sandbox' is missing ... SOURCE_NOUN
src/utils/i18n-coverage.ts(274,7): error TS2741: Property 'sandbox' is missing ... SOURCE_SURFACE

Arm B2 — the derivation is proved to move. The same member added and named (sandbox: 'sandboxes'), with zero edits to check.ts:

kinds: 16 phrase length 169 (was 158)
description: ... pages, flow screens, metadata forms, sandboxes.

That is the arm the card asked for: the derived phrase follows a union change on its own, and a member that would go unnamed cannot compile.

Ablation validity. No rebuild is required for these arms and none is claimed: the pin imports ../src/commands/i18n/check.js and ../src/utils/i18n-coverage.js — same-package relative specifiers vitest and tsx resolve to source, never through a package exports map to dist. The dependency closure (pnpm --filter '@objectstack/cli^...' build) was built before any reading was taken, and no dependency was mutated.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe; verdicts quoted from each gate's own verdict line.

Re-run in full after the final commit (origin/main merged in), at git rev-parse --short HEAD = ff207ebab3 — 55/55 build tasks (FULL TURBO), 5 test files / 69 tests passed, typecheck clean:

  • pnpm exec turbo run build --filter='@objectstack/cli^...' — VERDICT command-exit 0
  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/i18n-check-help-surface-parity.test.ts test/i18n-coverage.test.ts test/commands.test.ts test/i18n-declared-surface-gate.test.ts test/i18n-dataset-coverage.test.ts5 files, 69 tests passed
  • pnpm --filter @objectstack/cli typecheck — clean, including the test layer (tsc --noEmit && check:test-typecheck; the ledger holds at 3 files / 28 errors / 6 pinned signatures, unchanged). Worth stating because packages/cli/tsconfig.json is include: ['src'], so tsc --noEmit alone says nothing about packages/cli/test/** — the check:test-typecheck leg against tsconfig.test.json is what covers the new pin.
  • pnpm --filter @objectstack/cli build — VERDICT command-exit 0 (for the rendered --help above; taken at 315e17753c, the last commit before the merge, and the strings it renders are unchanged since)

Gate union re-derived at the final HEAD after merging origin/main: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gives 42 families. Run: 37 green. Reported honestly as NOT MEASURED, not as passes and not as reds — five, each self-declaring it:

  • check-test-completeness — exit 3; needs a saved turbo run test log, which only CI produces. The script names this branch as the family-list invocation and says to record it as NOT MEASURED.
  • check-half-states / check:pm-half-states — exit 3; needs repo-scoped REST, which this session does not have (a repo-scoped probe returned 403 GitHub access is not enabled for this session).
  • check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET; 12 packages have no dist/.
  • check:i18n-coverage — exit 3 PREREQUISITE NOT MET; examples/app-showcase could not lint because @objectstack/connector-mcp has no build output in this worktree. Nothing was compared, so the baseline says nothing either way. Declared narrowing: the build it names was queued for the shared verify lock and did not get a turn. This change adds two exported constants and one map; it alters no walker, no detector and no key path, so the coverage baseline has no route to move — but that is an argument, not a measurement, and CI runs the family.
  • check:type-check-debt — its --re-measure leg needs the whole workspace closure built; not attempted for the same reason. Its structural half, check:type-check-coverage, ran green, and packages/cli's own check:test-typecheck ledger is unchanged.

Repo-wide ESLint (pnpm lint) is CI's run and is not claimed here.

One thing I did not decide, and did not bury

The description names flow screens, and today no flow gap can be reported: the ledger suppression measured above filters the whole flows group out of the walk. I followed the taxonomy and the landed skill twin (#13833 names "flow screens" in the same sentence) rather than diverging the two halves of a deliberately paired fix, and the suppression lifts itself with no edit here. I did not open a new card for it: #11617 is open and already carries the "flows become visible once the bucket lands" state, and i18n-extract.ts documents the collision that produced the suppression. Flagged here so it is triaged rather than settled by a wording PR.

Changeset

.changeset/tricky-pans-repeat.md, patch on @objectstack/cli. Measured rather than assumed: packages/cli is not private, and its files field is ["dist","README.md","CHANGELOG.md"]dist/commands/i18n/check.js carries the string oclif prints, so this help text ships to users from a published package.


🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

The description named five source kinds — object/field/option/view/action —
against the fifteen-member `CoverageIssue['source']` taxonomy the report
actually carries. A five-of-fifteen sample reads as a scope statement rather
than an illustration: a reader who wanted app navigation or dashboard widgets
checked concluded the command did not cover them.
Derive the phrase from a `Record<CoverageIssue['source'], string>` map so the
help text and the taxonomy are one fact with one source: a new union member is
a compile error until it is named, and is published in `--help` in the same
edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ived surface set
oclif renders a command in two places with different budgets: the COMMANDS list
shows `summary` (falling back to the description's first line), and flips the
whole list to a multi-line layout once an entry wraps past five lines. So the
short line goes in `summary` with the parenthetical dropped outright, and the
full derived surface set goes in `description`, where a reader asking what the
command covers is actually looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ing it
oclif prints `summary` and then `description` in the DESCRIPTION section, so a
description that repeats the summary sentence renders it twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…tement
The A/B run at COLUMNS=80 shows the description-only shape wraps to five lines
in the COMMANDS column — at oclif's `lines.length > 4` multiline threshold, not
past it. Say what was measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
  • content/docs/ui/translations.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f — the merge of head ff207ebab3b80e92544cea0b4160025f5935f902 into base 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f && git checkout c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 ff207ebab3b80e92544cea0b4160025f5935f902 && git checkout -B drift-repro 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 && git merge --no-ff ff207ebab3b80e92544cea0b4160025f5935f902
node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 19:52
@os-trump
os-trump added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit be41618Sep 3, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13837-i18n-check-help-source-count branch September 3, 2026 20:30
os-trump pushed a commit that referenced this pull request Sep 3, 2026
…arity
Brings in PR #15062 (`os i18n check --help`), which lands in packages/cli
while this branch is open — the dispatch named it as the one to merge and
re-verify against. Also #15065 (rest), #15063/#15067 (docs) and #15055 (spec).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os i18n check --help under-counts what it reports by 9 of 14 key kinds — the CLI-side twin of a skill falsehood corrected in #13833

2 participants

@os-trump@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy - #15062

Merged
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count
Sep 3, 2026
Merged

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy#15062
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13837

os i18n check --help described the command as detecting "missing translation keys (object/field/option/view/action labels)". That parenthetical named 5 source kinds. The report carries 15. A five-of-fifteen sample does not read as an illustration — it reads as a scope statement, so a reader who wanted app navigation or dashboard widgets checked was told this command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist. This is the CLI-side twin of the sentence PR #13833 corrected in the published objectstack-i18n skill; fixing only the skill leaves the CLI teaching it at the moment a user runs the command.

What I measured on today's main (nothing inherited)

Triage measured 5-named against a 14-member union, and one small fixture reaching 12 kinds / 783 issues (metadataForm 769). Re-derived from origin/main at 99b4deba49, both numbers have moved and are re-taken here.

1 — the taxonomy is 15 members, not 14.dataset has been added to the CoverageIssue.source union since triage.

runtime witness, read from the map the phrase is derived from:
kinds: 15 ["object","field","option","section","view","action","globalAction","app",
"navigation","dashboard","widget","dataset","page","flow","metadataForm"]

2 — the retired parenthetical named 5 of those 15: object, field, option, view, action. Ten unnamed, not nine.

3 — an ordinary stack reaches 14 of the 15 kinds in one run. Driving the real walker (collectExpectedEntries) and the real detector (computeI18nCoverage) over one object, one global action, one app with nested navigation, one dashboard with a widget, one dataset, and one page carrying a filter-preset tab — the fixture now committed as this PR's pin:

walker : 16 kinds, 1195 entries
report : 14 kinds, 794 expectedKeys / 794 issues
per kind: object 1, field 2, option 2, view 2, action 2, globalAction 1, app 2,
navigation 2, dashboard 1, widget 1, dataset 3, page 1, section 1,
metadataForm 773

The one kind that did not appear is flow, and the reason is mechanical rather than incidental: authorWarnedTranslationGroups() returns ["flows"] (the shipped liveness ledger has that group at status: planned / authorWarn: true), and collectExpectedEntries filters every entry whose group is warned. See the rationale block in packages/cli/src/utils/i18n-extract.ts — that suppression is deliberate and self-lifting the day the screen-flow runner lands. The description still names flow screens, matching both the taxonomy and the landed skill twin; see "One thing I did not decide" below.

The fix

Wording. The parenthetical is gone from the line a user meets first, and the full surface set is stated where they go to read it.

Mechanism (the optional half — taken). The set is not retyped beside the taxonomy; it is derived from it. packages/cli/src/utils/i18n-coverage.ts gains SOURCE_SURFACE, a Record keyed by CoverageIssue.source whose value is the plural surface noun for each kind, and exports COVERAGE_SOURCE_KINDS and COVERAGE_SURFACE_PHRASE built from it. The Record keyed by the union is what makes it exhaustive by construction.

Declared file face. The card expected check.ts plus its pin. Deriving the phrase needs a second source file: packages/cli/src/utils/i18n-coverage.ts, where the taxonomy lives. Declaring that here, as the claim comment asks. Nothing else is touched; packages/spec is untouched, the skill text is untouched, content/docs/releases/ is untouched.

Why an oclif summary as well — no sibling command sets one, so this is the shape change worth naming. oclif renders a command in two places with different budgets. The COMMANDS list prints summary, falling back to the description's first line when a command sets none; the command's own --help prints both. Measured at COLUMNS=80, the derived set in the COMMANDS column wraps to five lines, at renderList's lines.length > 4 multiline threshold. So the short line goes in summary with the parenthetical dropped outright — triage's own second option, never a sample — and the derived set goes in description.

Rendered from the built CLI:

COLUMNS=80 node ./bin/run.js i18n --help
COMMANDS
i18n check Detect missing translation keys across all configured locales
i18n extract Scaffold per-locale translation skeletons from a stack config.
Default locale is filled from schema labels; other locales
follow --fill.
COLUMNS=80 node ./bin/run.js i18n check --help
DESCRIPTION
Detect missing translation keys across all configured locales
Every translatable surface the coverage report carries is checked: objects,
fields, options, sections, views, actions, global actions, apps, navigation
items, dashboards, widgets, datasets, pages, flow screens, metadata forms.

The A/B that produced the five-line reading was run on the build artifact (dist/commands/i18n/check.js), not on source: summary deleted and the one-line description restored, os i18n --help rendered, then the artifact restored and proved byte-identical by sha256 (76e899fa... before and after).

Red-first, and which arm fired

Three arms, each mutating on disk under trap on EXIT INT TERM with absolute paths, each mutation proved by blob hash and marker counts, each restore proved by blob-hash equality against the HEAD blob. Final git status --porcelain empty.

Arm A — the defect's own red. Retired description restored on check.ts (static override summary occurrences 0, retired-sample occurrences 1; HEAD blob eba0697d, on disk fca8c60c). The pin goes RED, 4 failed / 2 passed:

AssertionError: --help omits 15 of 15 kinds
AssertionError: reported but absent from --help: action, app, dashboard, dataset,
field, globalAction, metadataForm, navigation, object, option, page, section,
view, widget

Restored: on disk eba0697d == HEAD blob eba0697d.

Arm B1 — the derivation is enforced, not decorative. A sixteenth member 'sandbox' added to CoverageIssue.source and deliberately not named. tsc --noEmitrefuses, exit 1:

src/utils/i18n-coverage.ts(233,7): error TS2741: Property 'sandbox' is missing ... SOURCE_NOUN
src/utils/i18n-coverage.ts(274,7): error TS2741: Property 'sandbox' is missing ... SOURCE_SURFACE

Arm B2 — the derivation is proved to move. The same member added and named (sandbox: 'sandboxes'), with zero edits to check.ts:

kinds: 16 phrase length 169 (was 158)
description: ... pages, flow screens, metadata forms, sandboxes.

That is the arm the card asked for: the derived phrase follows a union change on its own, and a member that would go unnamed cannot compile.

Ablation validity. No rebuild is required for these arms and none is claimed: the pin imports ../src/commands/i18n/check.js and ../src/utils/i18n-coverage.js — same-package relative specifiers vitest and tsx resolve to source, never through a package exports map to dist. The dependency closure (pnpm --filter '@objectstack/cli^...' build) was built before any reading was taken, and no dependency was mutated.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe; verdicts quoted from each gate's own verdict line.

Re-run in full after the final commit (origin/main merged in), at git rev-parse --short HEAD = ff207ebab3 — 55/55 build tasks (FULL TURBO), 5 test files / 69 tests passed, typecheck clean:

  • pnpm exec turbo run build --filter='@objectstack/cli^...' — VERDICT command-exit 0
  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/i18n-check-help-surface-parity.test.ts test/i18n-coverage.test.ts test/commands.test.ts test/i18n-declared-surface-gate.test.ts test/i18n-dataset-coverage.test.ts5 files, 69 tests passed
  • pnpm --filter @objectstack/cli typecheck — clean, including the test layer (tsc --noEmit && check:test-typecheck; the ledger holds at 3 files / 28 errors / 6 pinned signatures, unchanged). Worth stating because packages/cli/tsconfig.json is include: ['src'], so tsc --noEmit alone says nothing about packages/cli/test/** — the check:test-typecheck leg against tsconfig.test.json is what covers the new pin.
  • pnpm --filter @objectstack/cli build — VERDICT command-exit 0 (for the rendered --help above; taken at 315e17753c, the last commit before the merge, and the strings it renders are unchanged since)

Gate union re-derived at the final HEAD after merging origin/main: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gives 42 families. Run: 37 green. Reported honestly as NOT MEASURED, not as passes and not as reds — five, each self-declaring it:

  • check-test-completeness — exit 3; needs a saved turbo run test log, which only CI produces. The script names this branch as the family-list invocation and says to record it as NOT MEASURED.
  • check-half-states / check:pm-half-states — exit 3; needs repo-scoped REST, which this session does not have (a repo-scoped probe returned 403 GitHub access is not enabled for this session).
  • check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET; 12 packages have no dist/.
  • check:i18n-coverage — exit 3 PREREQUISITE NOT MET; examples/app-showcase could not lint because @objectstack/connector-mcp has no build output in this worktree. Nothing was compared, so the baseline says nothing either way. Declared narrowing: the build it names was queued for the shared verify lock and did not get a turn. This change adds two exported constants and one map; it alters no walker, no detector and no key path, so the coverage baseline has no route to move — but that is an argument, not a measurement, and CI runs the family.
  • check:type-check-debt — its --re-measure leg needs the whole workspace closure built; not attempted for the same reason. Its structural half, check:type-check-coverage, ran green, and packages/cli's own check:test-typecheck ledger is unchanged.

Repo-wide ESLint (pnpm lint) is CI's run and is not claimed here.

One thing I did not decide, and did not bury

The description names flow screens, and today no flow gap can be reported: the ledger suppression measured above filters the whole flows group out of the walk. I followed the taxonomy and the landed skill twin (#13833 names "flow screens" in the same sentence) rather than diverging the two halves of a deliberately paired fix, and the suppression lifts itself with no edit here. I did not open a new card for it: #11617 is open and already carries the "flows become visible once the bucket lands" state, and i18n-extract.ts documents the collision that produced the suppression. Flagged here so it is triaged rather than settled by a wording PR.

Changeset

.changeset/tricky-pans-repeat.md, patch on @objectstack/cli. Measured rather than assumed: packages/cli is not private, and its files field is ["dist","README.md","CHANGELOG.md"]dist/commands/i18n/check.js carries the string oclif prints, so this help text ships to users from a published package.


🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

The description named five source kinds — object/field/option/view/action —
against the fifteen-member `CoverageIssue['source']` taxonomy the report
actually carries. A five-of-fifteen sample reads as a scope statement rather
than an illustration: a reader who wanted app navigation or dashboard widgets
checked concluded the command did not cover them.
Derive the phrase from a `Record<CoverageIssue['source'], string>` map so the
help text and the taxonomy are one fact with one source: a new union member is
a compile error until it is named, and is published in `--help` in the same
edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ived surface set
oclif renders a command in two places with different budgets: the COMMANDS list
shows `summary` (falling back to the description's first line), and flips the
whole list to a multi-line layout once an entry wraps past five lines. So the
short line goes in `summary` with the parenthetical dropped outright, and the
full derived surface set goes in `description`, where a reader asking what the
command covers is actually looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ing it
oclif prints `summary` and then `description` in the DESCRIPTION section, so a
description that repeats the summary sentence renders it twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…tement
The A/B run at COLUMNS=80 shows the description-only shape wraps to five lines
in the COMMANDS column — at oclif's `lines.length > 4` multiline threshold, not
past it. Say what was measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
  • content/docs/ui/translations.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f — the merge of head ff207ebab3b80e92544cea0b4160025f5935f902 into base 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f && git checkout c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 ff207ebab3b80e92544cea0b4160025f5935f902 && git checkout -B drift-repro 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 && git merge --no-ff ff207ebab3b80e92544cea0b4160025f5935f902
node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 19:52
@os-trump
os-trump added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit be41618Sep 3, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13837-i18n-check-help-source-count branch September 3, 2026 20:30
os-trump pushed a commit that referenced this pull request Sep 3, 2026
…arity
Brings in PR #15062 (`os i18n check --help`), which lands in packages/cli
while this branch is open — the dispatch named it as the one to merge and
re-verify against. Also #15065 (rest), #15063/#15067 (docs) and #15055 (spec).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os i18n check --help under-counts what it reports by 9 of 14 key kinds — the CLI-side twin of a skill falsehood corrected in #13833

2 participants

@os-trump@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy - #15062

Merged
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count
Sep 3, 2026
Merged

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy#15062
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13837

os i18n check --help described the command as detecting "missing translation keys (object/field/option/view/action labels)". That parenthetical named 5 source kinds. The report carries 15. A five-of-fifteen sample does not read as an illustration — it reads as a scope statement, so a reader who wanted app navigation or dashboard widgets checked was told this command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist. This is the CLI-side twin of the sentence PR #13833 corrected in the published objectstack-i18n skill; fixing only the skill leaves the CLI teaching it at the moment a user runs the command.

What I measured on today's main (nothing inherited)

Triage measured 5-named against a 14-member union, and one small fixture reaching 12 kinds / 783 issues (metadataForm 769). Re-derived from origin/main at 99b4deba49, both numbers have moved and are re-taken here.

1 — the taxonomy is 15 members, not 14.dataset has been added to the CoverageIssue.source union since triage.

runtime witness, read from the map the phrase is derived from:
kinds: 15 ["object","field","option","section","view","action","globalAction","app",
"navigation","dashboard","widget","dataset","page","flow","metadataForm"]

2 — the retired parenthetical named 5 of those 15: object, field, option, view, action. Ten unnamed, not nine.

3 — an ordinary stack reaches 14 of the 15 kinds in one run. Driving the real walker (collectExpectedEntries) and the real detector (computeI18nCoverage) over one object, one global action, one app with nested navigation, one dashboard with a widget, one dataset, and one page carrying a filter-preset tab — the fixture now committed as this PR's pin:

walker : 16 kinds, 1195 entries
report : 14 kinds, 794 expectedKeys / 794 issues
per kind: object 1, field 2, option 2, view 2, action 2, globalAction 1, app 2,
navigation 2, dashboard 1, widget 1, dataset 3, page 1, section 1,
metadataForm 773

The one kind that did not appear is flow, and the reason is mechanical rather than incidental: authorWarnedTranslationGroups() returns ["flows"] (the shipped liveness ledger has that group at status: planned / authorWarn: true), and collectExpectedEntries filters every entry whose group is warned. See the rationale block in packages/cli/src/utils/i18n-extract.ts — that suppression is deliberate and self-lifting the day the screen-flow runner lands. The description still names flow screens, matching both the taxonomy and the landed skill twin; see "One thing I did not decide" below.

The fix

Wording. The parenthetical is gone from the line a user meets first, and the full surface set is stated where they go to read it.

Mechanism (the optional half — taken). The set is not retyped beside the taxonomy; it is derived from it. packages/cli/src/utils/i18n-coverage.ts gains SOURCE_SURFACE, a Record keyed by CoverageIssue.source whose value is the plural surface noun for each kind, and exports COVERAGE_SOURCE_KINDS and COVERAGE_SURFACE_PHRASE built from it. The Record keyed by the union is what makes it exhaustive by construction.

Declared file face. The card expected check.ts plus its pin. Deriving the phrase needs a second source file: packages/cli/src/utils/i18n-coverage.ts, where the taxonomy lives. Declaring that here, as the claim comment asks. Nothing else is touched; packages/spec is untouched, the skill text is untouched, content/docs/releases/ is untouched.

Why an oclif summary as well — no sibling command sets one, so this is the shape change worth naming. oclif renders a command in two places with different budgets. The COMMANDS list prints summary, falling back to the description's first line when a command sets none; the command's own --help prints both. Measured at COLUMNS=80, the derived set in the COMMANDS column wraps to five lines, at renderList's lines.length > 4 multiline threshold. So the short line goes in summary with the parenthetical dropped outright — triage's own second option, never a sample — and the derived set goes in description.

Rendered from the built CLI:

COLUMNS=80 node ./bin/run.js i18n --help
COMMANDS
i18n check Detect missing translation keys across all configured locales
i18n extract Scaffold per-locale translation skeletons from a stack config.
Default locale is filled from schema labels; other locales
follow --fill.
COLUMNS=80 node ./bin/run.js i18n check --help
DESCRIPTION
Detect missing translation keys across all configured locales
Every translatable surface the coverage report carries is checked: objects,
fields, options, sections, views, actions, global actions, apps, navigation
items, dashboards, widgets, datasets, pages, flow screens, metadata forms.

The A/B that produced the five-line reading was run on the build artifact (dist/commands/i18n/check.js), not on source: summary deleted and the one-line description restored, os i18n --help rendered, then the artifact restored and proved byte-identical by sha256 (76e899fa... before and after).

Red-first, and which arm fired

Three arms, each mutating on disk under trap on EXIT INT TERM with absolute paths, each mutation proved by blob hash and marker counts, each restore proved by blob-hash equality against the HEAD blob. Final git status --porcelain empty.

Arm A — the defect's own red. Retired description restored on check.ts (static override summary occurrences 0, retired-sample occurrences 1; HEAD blob eba0697d, on disk fca8c60c). The pin goes RED, 4 failed / 2 passed:

AssertionError: --help omits 15 of 15 kinds
AssertionError: reported but absent from --help: action, app, dashboard, dataset,
field, globalAction, metadataForm, navigation, object, option, page, section,
view, widget

Restored: on disk eba0697d == HEAD blob eba0697d.

Arm B1 — the derivation is enforced, not decorative. A sixteenth member 'sandbox' added to CoverageIssue.source and deliberately not named. tsc --noEmitrefuses, exit 1:

src/utils/i18n-coverage.ts(233,7): error TS2741: Property 'sandbox' is missing ... SOURCE_NOUN
src/utils/i18n-coverage.ts(274,7): error TS2741: Property 'sandbox' is missing ... SOURCE_SURFACE

Arm B2 — the derivation is proved to move. The same member added and named (sandbox: 'sandboxes'), with zero edits to check.ts:

kinds: 16 phrase length 169 (was 158)
description: ... pages, flow screens, metadata forms, sandboxes.

That is the arm the card asked for: the derived phrase follows a union change on its own, and a member that would go unnamed cannot compile.

Ablation validity. No rebuild is required for these arms and none is claimed: the pin imports ../src/commands/i18n/check.js and ../src/utils/i18n-coverage.js — same-package relative specifiers vitest and tsx resolve to source, never through a package exports map to dist. The dependency closure (pnpm --filter '@objectstack/cli^...' build) was built before any reading was taken, and no dependency was mutated.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe; verdicts quoted from each gate's own verdict line.

Re-run in full after the final commit (origin/main merged in), at git rev-parse --short HEAD = ff207ebab3 — 55/55 build tasks (FULL TURBO), 5 test files / 69 tests passed, typecheck clean:

  • pnpm exec turbo run build --filter='@objectstack/cli^...' — VERDICT command-exit 0
  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/i18n-check-help-surface-parity.test.ts test/i18n-coverage.test.ts test/commands.test.ts test/i18n-declared-surface-gate.test.ts test/i18n-dataset-coverage.test.ts5 files, 69 tests passed
  • pnpm --filter @objectstack/cli typecheck — clean, including the test layer (tsc --noEmit && check:test-typecheck; the ledger holds at 3 files / 28 errors / 6 pinned signatures, unchanged). Worth stating because packages/cli/tsconfig.json is include: ['src'], so tsc --noEmit alone says nothing about packages/cli/test/** — the check:test-typecheck leg against tsconfig.test.json is what covers the new pin.
  • pnpm --filter @objectstack/cli build — VERDICT command-exit 0 (for the rendered --help above; taken at 315e17753c, the last commit before the merge, and the strings it renders are unchanged since)

Gate union re-derived at the final HEAD after merging origin/main: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gives 42 families. Run: 37 green. Reported honestly as NOT MEASURED, not as passes and not as reds — five, each self-declaring it:

  • check-test-completeness — exit 3; needs a saved turbo run test log, which only CI produces. The script names this branch as the family-list invocation and says to record it as NOT MEASURED.
  • check-half-states / check:pm-half-states — exit 3; needs repo-scoped REST, which this session does not have (a repo-scoped probe returned 403 GitHub access is not enabled for this session).
  • check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET; 12 packages have no dist/.
  • check:i18n-coverage — exit 3 PREREQUISITE NOT MET; examples/app-showcase could not lint because @objectstack/connector-mcp has no build output in this worktree. Nothing was compared, so the baseline says nothing either way. Declared narrowing: the build it names was queued for the shared verify lock and did not get a turn. This change adds two exported constants and one map; it alters no walker, no detector and no key path, so the coverage baseline has no route to move — but that is an argument, not a measurement, and CI runs the family.
  • check:type-check-debt — its --re-measure leg needs the whole workspace closure built; not attempted for the same reason. Its structural half, check:type-check-coverage, ran green, and packages/cli's own check:test-typecheck ledger is unchanged.

Repo-wide ESLint (pnpm lint) is CI's run and is not claimed here.

One thing I did not decide, and did not bury

The description names flow screens, and today no flow gap can be reported: the ledger suppression measured above filters the whole flows group out of the walk. I followed the taxonomy and the landed skill twin (#13833 names "flow screens" in the same sentence) rather than diverging the two halves of a deliberately paired fix, and the suppression lifts itself with no edit here. I did not open a new card for it: #11617 is open and already carries the "flows become visible once the bucket lands" state, and i18n-extract.ts documents the collision that produced the suppression. Flagged here so it is triaged rather than settled by a wording PR.

Changeset

.changeset/tricky-pans-repeat.md, patch on @objectstack/cli. Measured rather than assumed: packages/cli is not private, and its files field is ["dist","README.md","CHANGELOG.md"]dist/commands/i18n/check.js carries the string oclif prints, so this help text ships to users from a published package.


🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

The description named five source kinds — object/field/option/view/action —
against the fifteen-member `CoverageIssue['source']` taxonomy the report
actually carries. A five-of-fifteen sample reads as a scope statement rather
than an illustration: a reader who wanted app navigation or dashboard widgets
checked concluded the command did not cover them.
Derive the phrase from a `Record<CoverageIssue['source'], string>` map so the
help text and the taxonomy are one fact with one source: a new union member is
a compile error until it is named, and is published in `--help` in the same
edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ived surface set
oclif renders a command in two places with different budgets: the COMMANDS list
shows `summary` (falling back to the description's first line), and flips the
whole list to a multi-line layout once an entry wraps past five lines. So the
short line goes in `summary` with the parenthetical dropped outright, and the
full derived surface set goes in `description`, where a reader asking what the
command covers is actually looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ing it
oclif prints `summary` and then `description` in the DESCRIPTION section, so a
description that repeats the summary sentence renders it twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…tement
The A/B run at COLUMNS=80 shows the description-only shape wraps to five lines
in the COMMANDS column — at oclif's `lines.length > 4` multiline threshold, not
past it. Say what was measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
  • content/docs/ui/translations.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f — the merge of head ff207ebab3b80e92544cea0b4160025f5935f902 into base 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f && git checkout c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 ff207ebab3b80e92544cea0b4160025f5935f902 && git checkout -B drift-repro 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 && git merge --no-ff ff207ebab3b80e92544cea0b4160025f5935f902
node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 19:52
@os-trump
os-trump added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit be41618Sep 3, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13837-i18n-check-help-source-count branch September 3, 2026 20:30
os-trump pushed a commit that referenced this pull request Sep 3, 2026
…arity
Brings in PR #15062 (`os i18n check --help`), which lands in packages/cli
while this branch is open — the dispatch named it as the one to merge and
re-verify against. Also #15065 (rest), #15063/#15067 (docs) and #15055 (spec).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os i18n check --help under-counts what it reports by 9 of 14 key kinds — the CLI-side twin of a skill falsehood corrected in #13833

2 participants

@os-trump@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy - #15062

Merged
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count
Sep 3, 2026
Merged

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy#15062
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13837

os i18n check --help described the command as detecting "missing translation keys (object/field/option/view/action labels)". That parenthetical named 5 source kinds. The report carries 15. A five-of-fifteen sample does not read as an illustration — it reads as a scope statement, so a reader who wanted app navigation or dashboard widgets checked was told this command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist. This is the CLI-side twin of the sentence PR #13833 corrected in the published objectstack-i18n skill; fixing only the skill leaves the CLI teaching it at the moment a user runs the command.

What I measured on today's main (nothing inherited)

Triage measured 5-named against a 14-member union, and one small fixture reaching 12 kinds / 783 issues (metadataForm 769). Re-derived from origin/main at 99b4deba49, both numbers have moved and are re-taken here.

1 — the taxonomy is 15 members, not 14.dataset has been added to the CoverageIssue.source union since triage.

runtime witness, read from the map the phrase is derived from:
kinds: 15 ["object","field","option","section","view","action","globalAction","app",
"navigation","dashboard","widget","dataset","page","flow","metadataForm"]

2 — the retired parenthetical named 5 of those 15: object, field, option, view, action. Ten unnamed, not nine.

3 — an ordinary stack reaches 14 of the 15 kinds in one run. Driving the real walker (collectExpectedEntries) and the real detector (computeI18nCoverage) over one object, one global action, one app with nested navigation, one dashboard with a widget, one dataset, and one page carrying a filter-preset tab — the fixture now committed as this PR's pin:

walker : 16 kinds, 1195 entries
report : 14 kinds, 794 expectedKeys / 794 issues
per kind: object 1, field 2, option 2, view 2, action 2, globalAction 1, app 2,
navigation 2, dashboard 1, widget 1, dataset 3, page 1, section 1,
metadataForm 773

The one kind that did not appear is flow, and the reason is mechanical rather than incidental: authorWarnedTranslationGroups() returns ["flows"] (the shipped liveness ledger has that group at status: planned / authorWarn: true), and collectExpectedEntries filters every entry whose group is warned. See the rationale block in packages/cli/src/utils/i18n-extract.ts — that suppression is deliberate and self-lifting the day the screen-flow runner lands. The description still names flow screens, matching both the taxonomy and the landed skill twin; see "One thing I did not decide" below.

The fix

Wording. The parenthetical is gone from the line a user meets first, and the full surface set is stated where they go to read it.

Mechanism (the optional half — taken). The set is not retyped beside the taxonomy; it is derived from it. packages/cli/src/utils/i18n-coverage.ts gains SOURCE_SURFACE, a Record keyed by CoverageIssue.source whose value is the plural surface noun for each kind, and exports COVERAGE_SOURCE_KINDS and COVERAGE_SURFACE_PHRASE built from it. The Record keyed by the union is what makes it exhaustive by construction.

Declared file face. The card expected check.ts plus its pin. Deriving the phrase needs a second source file: packages/cli/src/utils/i18n-coverage.ts, where the taxonomy lives. Declaring that here, as the claim comment asks. Nothing else is touched; packages/spec is untouched, the skill text is untouched, content/docs/releases/ is untouched.

Why an oclif summary as well — no sibling command sets one, so this is the shape change worth naming. oclif renders a command in two places with different budgets. The COMMANDS list prints summary, falling back to the description's first line when a command sets none; the command's own --help prints both. Measured at COLUMNS=80, the derived set in the COMMANDS column wraps to five lines, at renderList's lines.length > 4 multiline threshold. So the short line goes in summary with the parenthetical dropped outright — triage's own second option, never a sample — and the derived set goes in description.

Rendered from the built CLI:

COLUMNS=80 node ./bin/run.js i18n --help
COMMANDS
i18n check Detect missing translation keys across all configured locales
i18n extract Scaffold per-locale translation skeletons from a stack config.
Default locale is filled from schema labels; other locales
follow --fill.
COLUMNS=80 node ./bin/run.js i18n check --help
DESCRIPTION
Detect missing translation keys across all configured locales
Every translatable surface the coverage report carries is checked: objects,
fields, options, sections, views, actions, global actions, apps, navigation
items, dashboards, widgets, datasets, pages, flow screens, metadata forms.

The A/B that produced the five-line reading was run on the build artifact (dist/commands/i18n/check.js), not on source: summary deleted and the one-line description restored, os i18n --help rendered, then the artifact restored and proved byte-identical by sha256 (76e899fa... before and after).

Red-first, and which arm fired

Three arms, each mutating on disk under trap on EXIT INT TERM with absolute paths, each mutation proved by blob hash and marker counts, each restore proved by blob-hash equality against the HEAD blob. Final git status --porcelain empty.

Arm A — the defect's own red. Retired description restored on check.ts (static override summary occurrences 0, retired-sample occurrences 1; HEAD blob eba0697d, on disk fca8c60c). The pin goes RED, 4 failed / 2 passed:

AssertionError: --help omits 15 of 15 kinds
AssertionError: reported but absent from --help: action, app, dashboard, dataset,
field, globalAction, metadataForm, navigation, object, option, page, section,
view, widget

Restored: on disk eba0697d == HEAD blob eba0697d.

Arm B1 — the derivation is enforced, not decorative. A sixteenth member 'sandbox' added to CoverageIssue.source and deliberately not named. tsc --noEmitrefuses, exit 1:

src/utils/i18n-coverage.ts(233,7): error TS2741: Property 'sandbox' is missing ... SOURCE_NOUN
src/utils/i18n-coverage.ts(274,7): error TS2741: Property 'sandbox' is missing ... SOURCE_SURFACE

Arm B2 — the derivation is proved to move. The same member added and named (sandbox: 'sandboxes'), with zero edits to check.ts:

kinds: 16 phrase length 169 (was 158)
description: ... pages, flow screens, metadata forms, sandboxes.

That is the arm the card asked for: the derived phrase follows a union change on its own, and a member that would go unnamed cannot compile.

Ablation validity. No rebuild is required for these arms and none is claimed: the pin imports ../src/commands/i18n/check.js and ../src/utils/i18n-coverage.js — same-package relative specifiers vitest and tsx resolve to source, never through a package exports map to dist. The dependency closure (pnpm --filter '@objectstack/cli^...' build) was built before any reading was taken, and no dependency was mutated.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe; verdicts quoted from each gate's own verdict line.

Re-run in full after the final commit (origin/main merged in), at git rev-parse --short HEAD = ff207ebab3 — 55/55 build tasks (FULL TURBO), 5 test files / 69 tests passed, typecheck clean:

  • pnpm exec turbo run build --filter='@objectstack/cli^...' — VERDICT command-exit 0
  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/i18n-check-help-surface-parity.test.ts test/i18n-coverage.test.ts test/commands.test.ts test/i18n-declared-surface-gate.test.ts test/i18n-dataset-coverage.test.ts5 files, 69 tests passed
  • pnpm --filter @objectstack/cli typecheck — clean, including the test layer (tsc --noEmit && check:test-typecheck; the ledger holds at 3 files / 28 errors / 6 pinned signatures, unchanged). Worth stating because packages/cli/tsconfig.json is include: ['src'], so tsc --noEmit alone says nothing about packages/cli/test/** — the check:test-typecheck leg against tsconfig.test.json is what covers the new pin.
  • pnpm --filter @objectstack/cli build — VERDICT command-exit 0 (for the rendered --help above; taken at 315e17753c, the last commit before the merge, and the strings it renders are unchanged since)

Gate union re-derived at the final HEAD after merging origin/main: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gives 42 families. Run: 37 green. Reported honestly as NOT MEASURED, not as passes and not as reds — five, each self-declaring it:

  • check-test-completeness — exit 3; needs a saved turbo run test log, which only CI produces. The script names this branch as the family-list invocation and says to record it as NOT MEASURED.
  • check-half-states / check:pm-half-states — exit 3; needs repo-scoped REST, which this session does not have (a repo-scoped probe returned 403 GitHub access is not enabled for this session).
  • check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET; 12 packages have no dist/.
  • check:i18n-coverage — exit 3 PREREQUISITE NOT MET; examples/app-showcase could not lint because @objectstack/connector-mcp has no build output in this worktree. Nothing was compared, so the baseline says nothing either way. Declared narrowing: the build it names was queued for the shared verify lock and did not get a turn. This change adds two exported constants and one map; it alters no walker, no detector and no key path, so the coverage baseline has no route to move — but that is an argument, not a measurement, and CI runs the family.
  • check:type-check-debt — its --re-measure leg needs the whole workspace closure built; not attempted for the same reason. Its structural half, check:type-check-coverage, ran green, and packages/cli's own check:test-typecheck ledger is unchanged.

Repo-wide ESLint (pnpm lint) is CI's run and is not claimed here.

One thing I did not decide, and did not bury

The description names flow screens, and today no flow gap can be reported: the ledger suppression measured above filters the whole flows group out of the walk. I followed the taxonomy and the landed skill twin (#13833 names "flow screens" in the same sentence) rather than diverging the two halves of a deliberately paired fix, and the suppression lifts itself with no edit here. I did not open a new card for it: #11617 is open and already carries the "flows become visible once the bucket lands" state, and i18n-extract.ts documents the collision that produced the suppression. Flagged here so it is triaged rather than settled by a wording PR.

Changeset

.changeset/tricky-pans-repeat.md, patch on @objectstack/cli. Measured rather than assumed: packages/cli is not private, and its files field is ["dist","README.md","CHANGELOG.md"]dist/commands/i18n/check.js carries the string oclif prints, so this help text ships to users from a published package.


🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

The description named five source kinds — object/field/option/view/action —
against the fifteen-member `CoverageIssue['source']` taxonomy the report
actually carries. A five-of-fifteen sample reads as a scope statement rather
than an illustration: a reader who wanted app navigation or dashboard widgets
checked concluded the command did not cover them.
Derive the phrase from a `Record<CoverageIssue['source'], string>` map so the
help text and the taxonomy are one fact with one source: a new union member is
a compile error until it is named, and is published in `--help` in the same
edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ived surface set
oclif renders a command in two places with different budgets: the COMMANDS list
shows `summary` (falling back to the description's first line), and flips the
whole list to a multi-line layout once an entry wraps past five lines. So the
short line goes in `summary` with the parenthetical dropped outright, and the
full derived surface set goes in `description`, where a reader asking what the
command covers is actually looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ing it
oclif prints `summary` and then `description` in the DESCRIPTION section, so a
description that repeats the summary sentence renders it twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…tement
The A/B run at COLUMNS=80 shows the description-only shape wraps to five lines
in the COMMANDS column — at oclif's `lines.length > 4` multiline threshold, not
past it. Say what was measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
  • content/docs/ui/translations.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f — the merge of head ff207ebab3b80e92544cea0b4160025f5935f902 into base 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f && git checkout c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 ff207ebab3b80e92544cea0b4160025f5935f902 && git checkout -B drift-repro 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 && git merge --no-ff ff207ebab3b80e92544cea0b4160025f5935f902
node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 19:52
@os-trump
os-trump added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit be41618Sep 3, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13837-i18n-check-help-source-count branch September 3, 2026 20:30
os-trump pushed a commit that referenced this pull request Sep 3, 2026
…arity
Brings in PR #15062 (`os i18n check --help`), which lands in packages/cli
while this branch is open — the dispatch named it as the one to merge and
re-verify against. Also #15065 (rest), #15063/#15067 (docs) and #15055 (spec).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os i18n check --help under-counts what it reports by 9 of 14 key kinds — the CLI-side twin of a skill falsehood corrected in #13833

2 participants

@os-trump@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy - #15062

Merged
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count
Sep 3, 2026
Merged

fix(cli): os i18n check --help names the whole surface set it reports on, derived from the taxonomy#15062
os-trump merged 5 commits into
mainfrom
claude/issue-13837-i18n-check-help-source-count

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13837

os i18n check --help described the command as detecting "missing translation keys (object/field/option/view/action labels)". That parenthetical named 5 source kinds. The report carries 15. A five-of-fifteen sample does not read as an illustration — it reads as a scope statement, so a reader who wanted app navigation or dashboard widgets checked was told this command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist. This is the CLI-side twin of the sentence PR #13833 corrected in the published objectstack-i18n skill; fixing only the skill leaves the CLI teaching it at the moment a user runs the command.

What I measured on today's main (nothing inherited)

Triage measured 5-named against a 14-member union, and one small fixture reaching 12 kinds / 783 issues (metadataForm 769). Re-derived from origin/main at 99b4deba49, both numbers have moved and are re-taken here.

1 — the taxonomy is 15 members, not 14.dataset has been added to the CoverageIssue.source union since triage.

runtime witness, read from the map the phrase is derived from:
kinds: 15 ["object","field","option","section","view","action","globalAction","app",
"navigation","dashboard","widget","dataset","page","flow","metadataForm"]

2 — the retired parenthetical named 5 of those 15: object, field, option, view, action. Ten unnamed, not nine.

3 — an ordinary stack reaches 14 of the 15 kinds in one run. Driving the real walker (collectExpectedEntries) and the real detector (computeI18nCoverage) over one object, one global action, one app with nested navigation, one dashboard with a widget, one dataset, and one page carrying a filter-preset tab — the fixture now committed as this PR's pin:

walker : 16 kinds, 1195 entries
report : 14 kinds, 794 expectedKeys / 794 issues
per kind: object 1, field 2, option 2, view 2, action 2, globalAction 1, app 2,
navigation 2, dashboard 1, widget 1, dataset 3, page 1, section 1,
metadataForm 773

The one kind that did not appear is flow, and the reason is mechanical rather than incidental: authorWarnedTranslationGroups() returns ["flows"] (the shipped liveness ledger has that group at status: planned / authorWarn: true), and collectExpectedEntries filters every entry whose group is warned. See the rationale block in packages/cli/src/utils/i18n-extract.ts — that suppression is deliberate and self-lifting the day the screen-flow runner lands. The description still names flow screens, matching both the taxonomy and the landed skill twin; see "One thing I did not decide" below.

The fix

Wording. The parenthetical is gone from the line a user meets first, and the full surface set is stated where they go to read it.

Mechanism (the optional half — taken). The set is not retyped beside the taxonomy; it is derived from it. packages/cli/src/utils/i18n-coverage.ts gains SOURCE_SURFACE, a Record keyed by CoverageIssue.source whose value is the plural surface noun for each kind, and exports COVERAGE_SOURCE_KINDS and COVERAGE_SURFACE_PHRASE built from it. The Record keyed by the union is what makes it exhaustive by construction.

Declared file face. The card expected check.ts plus its pin. Deriving the phrase needs a second source file: packages/cli/src/utils/i18n-coverage.ts, where the taxonomy lives. Declaring that here, as the claim comment asks. Nothing else is touched; packages/spec is untouched, the skill text is untouched, content/docs/releases/ is untouched.

Why an oclif summary as well — no sibling command sets one, so this is the shape change worth naming. oclif renders a command in two places with different budgets. The COMMANDS list prints summary, falling back to the description's first line when a command sets none; the command's own --help prints both. Measured at COLUMNS=80, the derived set in the COMMANDS column wraps to five lines, at renderList's lines.length > 4 multiline threshold. So the short line goes in summary with the parenthetical dropped outright — triage's own second option, never a sample — and the derived set goes in description.

Rendered from the built CLI:

COLUMNS=80 node ./bin/run.js i18n --help
COMMANDS
i18n check Detect missing translation keys across all configured locales
i18n extract Scaffold per-locale translation skeletons from a stack config.
Default locale is filled from schema labels; other locales
follow --fill.
COLUMNS=80 node ./bin/run.js i18n check --help
DESCRIPTION
Detect missing translation keys across all configured locales
Every translatable surface the coverage report carries is checked: objects,
fields, options, sections, views, actions, global actions, apps, navigation
items, dashboards, widgets, datasets, pages, flow screens, metadata forms.

The A/B that produced the five-line reading was run on the build artifact (dist/commands/i18n/check.js), not on source: summary deleted and the one-line description restored, os i18n --help rendered, then the artifact restored and proved byte-identical by sha256 (76e899fa... before and after).

Red-first, and which arm fired

Three arms, each mutating on disk under trap on EXIT INT TERM with absolute paths, each mutation proved by blob hash and marker counts, each restore proved by blob-hash equality against the HEAD blob. Final git status --porcelain empty.

Arm A — the defect's own red. Retired description restored on check.ts (static override summary occurrences 0, retired-sample occurrences 1; HEAD blob eba0697d, on disk fca8c60c). The pin goes RED, 4 failed / 2 passed:

AssertionError: --help omits 15 of 15 kinds
AssertionError: reported but absent from --help: action, app, dashboard, dataset,
field, globalAction, metadataForm, navigation, object, option, page, section,
view, widget

Restored: on disk eba0697d == HEAD blob eba0697d.

Arm B1 — the derivation is enforced, not decorative. A sixteenth member 'sandbox' added to CoverageIssue.source and deliberately not named. tsc --noEmitrefuses, exit 1:

src/utils/i18n-coverage.ts(233,7): error TS2741: Property 'sandbox' is missing ... SOURCE_NOUN
src/utils/i18n-coverage.ts(274,7): error TS2741: Property 'sandbox' is missing ... SOURCE_SURFACE

Arm B2 — the derivation is proved to move. The same member added and named (sandbox: 'sandboxes'), with zero edits to check.ts:

kinds: 16 phrase length 169 (was 158)
description: ... pages, flow screens, metadata forms, sandboxes.

That is the arm the card asked for: the derived phrase follows a union change on its own, and a member that would go unnamed cannot compile.

Ablation validity. No rebuild is required for these arms and none is claimed: the pin imports ../src/commands/i18n/check.js and ../src/utils/i18n-coverage.js — same-package relative specifiers vitest and tsx resolve to source, never through a package exports map to dist. The dependency closure (pnpm --filter '@objectstack/cli^...' build) was built before any reading was taken, and no dependency was mutated.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe; verdicts quoted from each gate's own verdict line.

Re-run in full after the final commit (origin/main merged in), at git rev-parse --short HEAD = ff207ebab3 — 55/55 build tasks (FULL TURBO), 5 test files / 69 tests passed, typecheck clean:

  • pnpm exec turbo run build --filter='@objectstack/cli^...' — VERDICT command-exit 0
  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/i18n-check-help-surface-parity.test.ts test/i18n-coverage.test.ts test/commands.test.ts test/i18n-declared-surface-gate.test.ts test/i18n-dataset-coverage.test.ts5 files, 69 tests passed
  • pnpm --filter @objectstack/cli typecheck — clean, including the test layer (tsc --noEmit && check:test-typecheck; the ledger holds at 3 files / 28 errors / 6 pinned signatures, unchanged). Worth stating because packages/cli/tsconfig.json is include: ['src'], so tsc --noEmit alone says nothing about packages/cli/test/** — the check:test-typecheck leg against tsconfig.test.json is what covers the new pin.
  • pnpm --filter @objectstack/cli build — VERDICT command-exit 0 (for the rendered --help above; taken at 315e17753c, the last commit before the merge, and the strings it renders are unchanged since)

Gate union re-derived at the final HEAD after merging origin/main: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands gives 42 families. Run: 37 green. Reported honestly as NOT MEASURED, not as passes and not as reds — five, each self-declaring it:

  • check-test-completeness — exit 3; needs a saved turbo run test log, which only CI produces. The script names this branch as the family-list invocation and says to record it as NOT MEASURED.
  • check-half-states / check:pm-half-states — exit 3; needs repo-scoped REST, which this session does not have (a repo-scoped probe returned 403 GitHub access is not enabled for this session).
  • check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET; 12 packages have no dist/.
  • check:i18n-coverage — exit 3 PREREQUISITE NOT MET; examples/app-showcase could not lint because @objectstack/connector-mcp has no build output in this worktree. Nothing was compared, so the baseline says nothing either way. Declared narrowing: the build it names was queued for the shared verify lock and did not get a turn. This change adds two exported constants and one map; it alters no walker, no detector and no key path, so the coverage baseline has no route to move — but that is an argument, not a measurement, and CI runs the family.
  • check:type-check-debt — its --re-measure leg needs the whole workspace closure built; not attempted for the same reason. Its structural half, check:type-check-coverage, ran green, and packages/cli's own check:test-typecheck ledger is unchanged.

Repo-wide ESLint (pnpm lint) is CI's run and is not claimed here.

One thing I did not decide, and did not bury

The description names flow screens, and today no flow gap can be reported: the ledger suppression measured above filters the whole flows group out of the walk. I followed the taxonomy and the landed skill twin (#13833 names "flow screens" in the same sentence) rather than diverging the two halves of a deliberately paired fix, and the suppression lifts itself with no edit here. I did not open a new card for it: #11617 is open and already carries the "flows become visible once the bucket lands" state, and i18n-extract.ts documents the collision that produced the suppression. Flagged here so it is triaged rather than settled by a wording PR.

Changeset

.changeset/tricky-pans-repeat.md, patch on @objectstack/cli. Measured rather than assumed: packages/cli is not private, and its files field is ["dist","README.md","CHANGELOG.md"]dist/commands/i18n/check.js carries the string oclif prints, so this help text ships to users from a published package.


🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

The description named five source kinds — object/field/option/view/action —
against the fifteen-member `CoverageIssue['source']` taxonomy the report
actually carries. A five-of-fifteen sample reads as a scope statement rather
than an illustration: a reader who wanted app navigation or dashboard widgets
checked concluded the command did not cover them.
Derive the phrase from a `Record<CoverageIssue['source'], string>` map so the
help text and the taxonomy are one fact with one source: a new union member is
a compile error until it is named, and is published in `--help` in the same
edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ived surface set
oclif renders a command in two places with different budgets: the COMMANDS list
shows `summary` (falling back to the description's first line), and flips the
whole list to a multi-line layout once an entry wraps past five lines. So the
short line goes in `summary` with the parenthetical dropped outright, and the
full derived surface set goes in `description`, where a reader asking what the
command covers is actually looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ing it
oclif prints `summary` and then `description` in the DESCRIPTION section, so a
description that repeats the summary sentence renders it twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…tement
The A/B run at COLUMNS=80 shows the description-only shape wraps to five lines
in the COMMANDS column — at oclif's `lines.length > 4` multiline threshold, not
past it. Say what was measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
  • content/docs/ui/translations.mdx(via os i18n check (command, read off packages/cli/src/commands/i18n/check.ts))
What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f — the merge of head ff207ebab3b80e92544cea0b4160025f5935f902 into base 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f && git checkout c7c5f8ff1dd58d2741d0e961db8004b190d3ba3f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 ff207ebab3b80e92544cea0b4160025f5935f902 && git checkout -B drift-repro 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 && git merge --no-ff ff207ebab3b80e92544cea0b4160025f5935f902
node scripts/docs-audit/affected-docs.mjs --json 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 101ad2cc13fafd8a3879ffc4a6ce6133c02ed832 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 19:52
@os-trump
os-trump added this pull request to the merge queueSep 3, 2026
Merged via the queue into main with commit be41618Sep 3, 2026
35 checks passed
@os-trump
os-trump deleted the claude/issue-13837-i18n-check-help-source-count branch September 3, 2026 20:30
os-trump pushed a commit that referenced this pull request Sep 3, 2026
…arity
Brings in PR #15062 (`os i18n check --help`), which lands in packages/cli
while this branch is open — the dispatch named it as the one to merge and
re-verify against. Also #15065 (rest), #15063/#15067 (docs) and #15055 (spec).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os i18n check --help under-counts what it reports by 9 of 14 key kinds — the CLI-side twin of a skill falsehood corrected in #13833

2 participants

@os-trump@claude