Skip to content

cli: os i18n extract --no-metadata-forms is ignored once --no-objects-only is passed — the 761-key English Studio metadataForms baseline is inlined into the app's zh-CN bundle #14894

Description

@baozhoutao

Summary

os i18n extract --no-metadata-forms is honoured only while the default --objects-only is in effect. Pass --no-objects-only (needed to get apps / dashboards / pages keys into the bundle) and the Studio metadataForms baseline (761 keys, English source text) is inlined into the app's per-locale bundle file even though --no-metadata-forms was given — the flag's own help says it exists precisely for "a package that owns only its own objects".

Minimal reproduction

Platform 17.2.0, any stack with a few objects and an app.

$ objectstack i18n extract --locales=zh-CN --no-metadata-forms --no-objects-only --dry-run
  Skeleton summary
    zh-CN     1290 key(s)  (of 1290 expected)  + 761 metadataForms key(s)
...
  metadataForms: {          ← line 1374 of the emitted zh-CN.objects.generated.ts

Control: drop --no-objects-only → no metadataForms block (but no apps either, so the app label cannot be localised).

Workaround we used: --filter=kpi_ (our namespace prefix) removes the block; a line diff against the unfiltered output shows the metadataForms block is the only difference.

Expected

  • --no-metadata-forms suppresses the metadataForms group regardless of --objects-only; the two flags are documented as independent.
  • Either way, --no-objects-only should not silently ship the platform's English Studio baseline inside an application bundle for a non-English locale — under the runtime's zh-CN → en → authored chain that file would override the platform's own zh-CN Studio strings with English.

Environment

@objectstack/cli 17.2.0 · Node 22 · app objectstack-ai/kpi (i18n.defaultLocale: 'zh-CN').

Activity

  1. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊(半状态补齐)· pm:queue 补上 · bug / domain:cli / i18n / priority:p3 维持

    ⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡。本卡此前是半状态(有域、有级、有类型,⛔ 缺 pm:*)。

    为什么可以直接派发

    MEMBER 报告,自带最小复现与对照:

    • 复现:--locales=zh-CN --no-metadata-forms --no-objects-only --dry-run ⇒ + 761 metadataForms key(s),且产物第 1374 行确有 metadataForms: 块;
    • ⭐ 对照:去掉 --no-objects-only ⇒ 没有 metadataForms 块(但也没有 apps,于是 app 标签无法本地化)—— 这条对照把缺陷精确地钉在两个 flag 的交互上,⛔ 不是 --no-metadata-forms 单独失效;
    • ⭐ 第二条对照:绕过办法 --filter=kpi_ 也能去掉该块,而逐行 diff 显示 metadataForms 块是唯一差异 —— 用一条独立路径确认了同一个结论。

    ⇒ 接手者不需要再测量什么。

    定级维持 priority:p3(说明理由,因为它有一条听起来更重的后果)

    卡片的第二条期望点出了真正的危害:

    --no-objects-only 不该把平台的英文 Studio 基线静默塞进一个非英语 locale 的应用 bundle —— 在运行期 zh-CN → en → authored 的链下,那个文件会用英文覆盖平台自己的 zh-CN Studio 字符串。

    ⚠️ 那是用户可见的退化,不只是包变大。本席位仍维持前一席位的 p3,理由有二:① 有直接可用的绕过(--filter=<前缀>,报告者自己在用);② ⛔ 「会覆盖」这一步是推论,不是本卡实测的(卡片写的是 "would override")。

    ⇒ ⭐ 若有人实测到一个已发布应用因此在 zh-CN 下看到英文 Studio 字符串,请带着读数回来,本卡应当抬到 p2。 在此之前,凭推论抬级会与本仓其余同形状 i18n 卡(objectui#14884 等 = p3)不一致。

    ⚠️ 另注:那条 zh-CN → en → authored 链的语义本身正在决策箱里 —— 见 #15711(本席位 R+158 定级 needs-user-decision / domain:spec / p2)。⇒ 本卡修的是提取器不该塞进那个文件,⛔ 与链该怎么走是两件事,不要互相等待。

    与 #14895 的关系

    同一位报告者、同一个 app、同一条命令族,⛔ 不同缺陷:本卡是产出错的 bundle,#14895 是 --check 失败时给出错的建议命令。⭐ 建议先修本卡 —— flag 组合的语义定下来之后,#14895 「该回显哪些 flag」才有确定答案。

    分诊席位 · claude-opus-5 · R+159 半状态巡查(objectstack)


    Generated by Claude Code

  2. self-assigned this
    on Sep 6, 2026
  3. os-litant commented on Sep 6, 2026

    @os-litant
    Collaborator

    Claim: os-dev seat, session 01D47qPfEWVPmhguWgBZCi5N, branch claude/issue-14894-i18n-extract-no-metadata-forms.

    Working this end to end: drive the reproduction (and the control) first, then repair. Assignee stays as the dispatching PM seat — the shared identity makes that field no proof of ownership; this comment is the claim of record.


    Generated by Claude Code

  4. added a commit that references this issue on Sep 6, 2026
    5da3bfe
  5. os-litant commented on Sep 6, 2026

    @os-litant
    Collaborator

    Claim: os-dev seat, session 01D47qPfEWVPmhguWgBZCi5N, branch claude/issue-14894-i18n-extract-no-metadata-forms — unchanged from the claim above. This addendum carries the clause ② declaration, judged from the DELIVERED diff in PR #16120.

    Clause-②: yes

    • Mechanical limb — yes. No packages/spec/** file is touched, and no key is ADDED to a published payload. What does change is a published payload's content: the --json payload's bundles value, and the emitted module's content, export type and size, for the --no-objects-only input class. The call is not clean-cut in either direction, and the doctrine grades an unclear call yes.
    • Conformance limb — yes. One input class is re-selected between two already-published verdicts on a shipped face. os i18n extract shipped both behaviours — the baseline inlined into the objects module, and the baseline written to its own companion — and this change moves --no-objects-only from the first to the second.

    Grading yes buys one review round and costs nothing; the diff was not shaped to keep either limb no. ⛔ The needs:contract-review label is not applied by this seat.


    Generated by Claude Code

  6. os-litant commented on Sep 6, 2026

    @os-litant
    Collaborator

    os-dev-report

    {
      "issue": 14894,
      "status": "done",
      "branch": "claude/issue-14894-i18n-extract-no-metadata-forms",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16120",
      "premise_still_valid": true,
      "summary": "Clause 2, mechanical limb: YES — no packages/spec file is touched and no key is added, but a published payload's content changes (the --json bundles value, and the emitted module's content, export type and size) for the --no-objects-only input class, and the doctrine grades an unclear call yes. Clause 2, conformance limb: YES — one input class is re-selected between two already-published verdicts on a shipped face, moving --no-objects-only from 'baseline inlined into the objects module' to 'baseline in its own companion'. The defect was that --metadata-forms gated only the companion module while the renderer's kind 'full' serialised the whole TranslationData, so --no-objects-only re-inlined the baseline the other flag had just switched off. The renderer's three kinds are now a partition of one locale's generated leaves ('full' renamed 'stack', omitting metadataForms), so the baseline has exactly one home and --metadata-forms is its only control; --json carries the same payload the files carry. Neither flag's documented meaning changes and no precedence between them is invented — the overlap was in the emitter, not in the two meanings, so this was implemented rather than handed back as a design question.",
      "tests": "REPRODUCTION AND CONTROL, driven before any edit on a fixture stack (one object, one app, i18n.defaultLocale zh-CN), key counts taken structurally by walking each emitted module and counting string leaves. BEFORE — (A) '--no-metadata-forms --no-objects-only': one file, 776 leaves = objects 2 + apps 1 + metadataForms 773, 1716 lines, the metadataForms block starting at line 33 with English source text ('Object', 'Basics', 'Identity, labels, and taxonomy.'); (B) control '--no-metadata-forms' with the default --objects-only: 2 leaves, objects only, no metadataForms and no apps — exactly the control the card reports; (C) '--no-objects-only' with the baseline flag left ON: the same 773 keys in BOTH the objects module and the companion. So the flag interaction reproduces in the direction the card describes and only in that direction: the flag works alone and stops working when --no-objects-only joins it. AFTER — (A) 3 leaves, 33 lines, no metadataForms group; (B) 2 leaves, byte-identical to before; (C) objects module 3 leaves, companion 773, duplication gone. WHAT MOVED IN THE REGENERATED BUNDLES: nothing in this repository. All nine i18n-extract.config.ts docstrings run under the default --objects-only (flags read out of each), whose module, export name and type signature are unchanged; `pnpm check:i18n` exit 0 on the committed tree, and `pnpm check:i18n-coverage` OK at 13 configs / 621 baselined / none new after its build prerequisite was satisfied. TESTS: new command-level e2e pin (spawns the real CLI, group census of the written bytes, all four flag combinations plus --json) and new unit pins on the three renderer kinds and on stackAuthoredSubtree — 4 e2e + 20 unit, all green. Blast-radius suite = the 20 test files under packages/cli/test that reach the changed modules, derived by grep: 20 files / 301 tests passed. `pnpm --filter @objectstack/cli build` and `typecheck` exit 0 (test-layer typecheck included). ABLATION: the fix was committed FIRST, then the payload line reverted to its pre-fix form. Mutation confirmed on disk before any measurement — injected ': data;' found 1 occurrence, 'stackAuthoredSubtree(data)' found 0, and the blob hash moved from 6899355f to 4f9230d7; the script carried a trap restoring from HEAD with an absolute path. NO REBUILD LEG, and that is a property of this subject, not an omission: both suites resolve it from packages/cli/src (vitest imports the source directly, bin/run-dev.js runs src through tsx), not through a package's exports into dist, so there is no dist copy that could go stale. Result: unit 2 failed | 18 passed, e2e 2 failed | 2 passed. The two e2e cases that stayed GREEN are the --objects-only case and the --json case, the two the mutated line does not reach — the selectivity the pin should have. Restore verified: blob hash back to 6899355f and `git diff HEAD` empty. GATES: union derived from `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands`, asserted against its own 'Reconciliation — 56 famil(ies)' line — 56 printed, 56 run, all exit 0, exit codes captured before any pipe. Two were exit 3 PREREQUISITE NOT MET on first contact and were re-run to a real verdict rather than reported as passes: check:i18n-coverage (needed the 13-config build closure it names) and check:dual-build-cjs-loads (needed a full workspace build). The Artifact rosters block was run SEPARATELY: 37 families, 34 exit 0, three NOT MEASURED and none of them a pass — check-partof-closing-keyword.mjs and check-single-claim-paths.mjs at exit 2 NOT WIRED (no PR context locally; their pnpm spellings exit 0 but resolve to --self-test only, grading the checker's fixtures, #16030), and @objectstack/spec check:react-declaration-parity at exit 1 printing 'this gate did NOT run' because it needs a browser-produced sdui.manifest.json this repo does not contain — nothing in this diff touches React blocks. check-partof-closing-keyword was afterwards driven for real with the finished PR body in PR_BODY: exit 0, no closing-keyword contradiction. Repo-wide ESLint is CI's; the narrowing is measured, not assumed — population read from ESLint's own config (4 of the 5 changed files produce result rows; the changeset .md is reported by ESLint itself as 'File ignored because no matching configuration was supplied'), count read from --format json (4 files, 0 errors, 0 warnings), and eslint.config.mjs registers no parserOptions.project and no typed rules, so no rule reads a file other than the one being linted and this diff cannot move any untouched file's verdict. All gate readings were taken at 5da3bfea1e6, this branch's head; nothing was committed after them. NOT MEASURED, stated by name: the full 136-file @objectstack/cli suite (the 56 e2e files in it put a single foreground run past the container's ~10 minute cap) — narrowed to the 20-file blast radius above and declared here; CI runs the farm regardless.",
      "mcp_calls": "10 — for the whole run, including this comment and the read-back after it",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #16121: `os i18n extract` prints a key count the emitted bundle does not have — `counts[locale]` is every expected entry including metadataForms, so the default --objects-only run prints 'Wrote PATH (776 keys)' for a file holding 2 leaves, and the skeleton summary appends '+ 773 metadataForms key(s)' to a 776 that already contains those 773. Measured on main at 2648774b967 and unchanged by this PR (that is a count, this card is an emit). Dedupe searched before filing; #14816 / #14728 / #13837 are different or closed. No assignee, no label, left for triage."
      ]
    }

    Generated by Claude Code

  7. os-litant commented on Sep 6, 2026

    @os-litant
    Collaborator

    Claim: os-dev seat, session 01D47qPfEWVPmhguWgBZCi5N, branch claude/issue-14894-i18n-extract-no-metadata-forms — unchanged. This supersedes the clause ② addendum above, which stands corrected on two counts by contract review. The verdict does not change; the reasoning was weaker than the facts. Judged from the delivered diff at ac8add2e4f4 (PR #16120, two commits).

    Clause-②: yes

    • Mechanical limb — yes, on concrete keys, not on a doctrine call. My first addendum graded this yes "because the call is not clean-cut". It is clean-cut. For the --no-objects-only class the --json bundles value LOSES its metadataForms key (groups objects, apps, metadataForms at 776 leaves, down to objects, apps at 3), the payload GAINS a top-level metadataForms key, and the emitted module's export type narrows to an Omit of TranslationData without metadataForms. Probed with tsc against the emitted module: a consumer typed as TranslationData still compiles (exit 0), a consumer reading .metadataForms off the module gets TS2339.
    • Conformance limb — yes, and the --json face reaches a THIRD verdict. My first addendum described a move between two already-published verdicts. That is right on the file face — baseline inlined in the stack module, baseline in its own companion. On --json the same input class also reaches a verdict no face had before: with --no-metadata-forms the baseline is now nowhere in the payload, where that face previously ignored the flag in both directions.

    Also corrected in the PR and in the code it describes: the first commit claimed the renderer's three kinds are a partition of a locale's generated leaves. They are not — 'objects' is a sub-selection of 'stack', not a sibling of it. The invariant that holds is about the PAIR a run emits: the module it writes and the companion beside it are disjoint, and under 'stack' the two together are everything the extractor built (3 + 773 = 776, the extractor's own count).

    ⛔ The needs:contract-review label is not applied or altered by this seat.


    Generated by Claude Code

  8. os-litant commented on Sep 6, 2026

    @os-litant
    Collaborator

    os-dev-report

    Patch round after contract review. Supersedes the report above; the earlier one is left in place as the record of what was measured then.

    {
      "issue": 14894,
      "status": "done",
      "branch": "claude/issue-14894-i18n-extract-no-metadata-forms",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16120",
      "premise_still_valid": true,
      "summary": "Clause 2, mechanical limb: YES, on concrete keys rather than the doctrine call I first offered — the --json bundles value LOSES its metadataForms key for the --no-objects-only class (groups objects,apps,metadataForms at 776 leaves down to objects,apps at 3), the payload GAINS a top-level metadataForms key, and the module's export type narrows to an Omit of TranslationData without metadataForms; probed with tsc, a consumer typed as TranslationData compiles and one reading .metadataForms gets TS2339. Clause 2, conformance limb: YES, and on --json the class reaches a THIRD verdict — baseline nowhere in the payload — which no face had before, on top of the two-verdict move on the file face. The review's blocking item was real and is fixed: my first commit stopped the kind 'full' fold on the --json face too, and that face has no second file for the baseline to move to, so the baseline was dropped outright while metadataFormsCounts kept reporting 773. The payload now mirrors the file set — bundles is the stack module, a new metadataForms map is the companion, keyed by the locales whose companion is written and gated by emitsMetadataForms(locale), the same predicate and not a second one.",
      "tests": "BLOCKING ITEM, DRIVEN IN BOTH FLAG STATES ACROSS THREE CODE STATES. `--json --no-objects-only` run with --metadata-forms ON and with --no-metadata-forms, on each code state in turn, the two source files restored from HEAD between states with blob hashes checked. base 2648774b967: bundles groups [apps,metadataForms,objects] 776 leaves, no top-level metadataForms key, IDENTICAL in both flag states — the flag was already ignored here. First commit 5da3bfea1e6: bundles [apps,objects] 3 leaves, baseline nowhere, again IDENTICAL in both states — still ignored, opposite direction. Head ac8add2e4f4: flag ON gives bundles 3 leaves plus metadataForms {zh-CN} 773 leaves; flag OFF gives bundles 3 leaves plus metadataForms {} — payloads DIFFER. In the first two rows 'identical' means equal in every field once `duration` (wall clock) is dropped; that was the only field separating them. Restore verified: both files back to their HEAD blobs, `git diff HEAD` empty. PIN EXTENDED TO THE FLAG-ON AXIS: the --json case drove --no-metadata-forms only, which is why it was green while the flag did nothing; it now drives both states and asserts the axis (payloads must differ once duration is dropped; flag-ON must carry the baseline under its own key with a leaf count equal to metadataFormsCounts; flag-OFF must carry none). ABLATION PROVING IT CAN FAIL ON THAT AXIS, both directions, on the committed head, each leg with the mutation proven on disk before measuring (injected text counted 1, remaining predicate counted 0, blob hash moved off 5c5e3c50, restored from HEAD and re-hashed to 5c5e3c50 with `git diff HEAD` empty): leg 1 `.filter(() => false)` — the baseline never in JSON, i.e. the first commit's behaviour — e2e exit 1, 1 failed | 3 passed; leg 2 `.filter(() => true)` — predicate ignored, baseline in both states — e2e exit 1, 1 failed | 3 passed. In both legs the three file-face cases stayed GREEN, the selectivity the pin should have. That the one-state version could not have caught this is a reading, not an argument: it was green 4/4 at 5da3bfea1e6, on code the table above shows was ignoring the flag on that face. NO REBUILD LEG in any ablation, and that is a property of the subject: both suites resolve it from packages/cli/src (vitest imports the source, bin/run-dev.js runs it through tsx), never through a package's exports into dist. GATES ON THE NEW HEAD ac8add2e4f4: union re-derived there, identical family list to round one, asserted against its own 'Reconciliation — 56 famil(ies)' line — 56 printed, 56 run, ALL EXIT 0, exit codes captured before any pipe; the workspace was built first so nothing returned exit 3 this time. Check Changeset's members are among them and green: check-empty-changeset --base origin/main, check-changeset-no-major --base origin/main, check-adr-0087-registration --base origin/main, check:changeset-gate-self-tests, check-changeset-fixed. Artifact rosters run SEPARATELY on the same head: 37 families, 34 exit 0, three NOT MEASURED and none a pass — check-partof-closing-keyword.mjs and check-single-claim-paths.mjs at exit 2 NOT WIRED, and @objectstack/spec check:react-declaration-parity at exit 1 printing 'this gate did NOT run' (needs a browser-produced sdui.manifest.json this repo does not contain). Blast radius re-run on the head: 20 files / 301 tests passed. Build and typecheck green on the head, test-layer typecheck included. ESLint: 4 files, 0 errors, 0 warnings, population read from ESLint's own config and the changeset .md reported by ESLint itself as having no matching configuration. FILE LIST at origin/main...HEAD, five files: .changeset/i18n-extract-metadata-forms-flag-independence.md; packages/cli/src/commands/i18n/extract.ts; packages/cli/src/utils/i18n-extract.ts; packages/cli/test/i18n-extract-metadata-forms-flag.e2e.test.ts; packages/cli/test/i18n-extract.test.ts. 456 insertions, 14 deletions.",
      "mcp_calls": "14 — for the whole run including both rounds",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #16121 (round one, unchanged): `os i18n extract` prints a key count the emitted bundle does not have — counts[locale] is every expected entry including metadataForms, so the default --objects-only run prints 'Wrote PATH (776 keys)' for a file holding 2 leaves, and the skeleton summary appends '+ 773 metadataForms key(s)' to a 776 that already contains those 773. Unchanged by either commit here.",
        "NOT filed, needs the maintainer's hand, no card opened: this branch's two commits carry contradicting card relations — commit 1 `Fixes #14894`, commit 2 `Part of #14894`. Per the branch convention neither should exist (the PR body declares the relation once), and removing them needs a force-push, which is barred on this lane. A default squash concatenates both into one self-contradicting message. The PR body carries a merge note asking for the squash body to be taken from the body rather than the commit messages. Flagging it as MY defect from this round, not a pre-existing one."
      ]
    }

    Generated by Claude Code

  9. os-litant commented on Sep 6, 2026

    @os-litant
    Collaborator

    os-dev-report

    Placement round. Supersedes the two reports above; both are left in place as the record of what was measured then.

    {
      "issue": 14894,
      "status": "done",
      "branch": "claude/issue-14894-i18n-extract-no-metadata-forms",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16120",
      "premise_still_valid": true,
      "summary": "Clause 2, mechanical limb: YES on concrete keys — the --json bundles value loses its metadataForms key for the --no-objects-only class (776 leaves to 3), the payload gains a top-level metadataForms map, and the emitted module's type narrows to an Omit of TranslationData without metadataForms (tsc: a TranslationData-typed consumer compiles, one reading .metadataForms gets TS2339). Clause 2, conformance limb: YES, and on --json the class reaches a THIRD verdict, baseline nowhere, that no face had before. This round is placement only: the e2e created its fixture root in-tree at packages/cli/test/.tmp-i18n-14894, no tracked rule covers .tmp-*, and dispatch-gates --self-test failed its in-tree-directory case naming this file. Both roots are now mkdtempSync under the system temp dir, where this file's --out root already was. No reviewed source moved; commit 3 touches exactly one file.",
      "tests": "SELF-TEST, SAME COMMAND AND TREE, BEFORE AND AFTER. Before: '✗ every in-tree directory this tree's sources create is covered by a tracked ignore rule, or is tracked itself — EXPOSED: packages/cli/test/.tmp-i18n-14894 (packages/cli/test/i18n-extract-metadata-forms-flag.e2e.test.ts:83)' and '✗ dispatch-gates self-test: 1 of 1511 case(s) failed.' After: '✓ every in-tree directory this tree's sources create is covered by a tracked ignore rule, or is tracked itself' and '✓ dispatch-gates self-test: 1511 cases pass.' — EXPOSED: occurrences in the log went 1 to 0. Verdict read from the gate's own printed line, not from a captured $?. THE ONE ADJUSTMENT THE MOVE FORCES, measured rather than assumed: bundle-require writes its bundled module next to the config and Node resolves the config's bare specifiers from THAT directory, so the fixture's defineStack import cannot survive the move — driven, 'Cannot find package @objectstack/spec imported from /tmp/.../stack.config.bundled_....mjs'. The fixture is now a plain object default export, which is what the command consumes anyway (normalizeStackInput on whatever is exported); every reading is unchanged — 3 stack leaves, 773 baseline leaves, empty under --no-metadata-forms. ⚠️ COST, recorded in the file and not glossed: defineStack validated the fixture at load and a plain object is not validated the same way — driven on the same tree, dropping `type` from the field is REFUSED under defineStack ('Invalid field type') and accepted silently (exit 0) without it. The exact leaf counts the pin asserts are the remaining guard; a malformed fixture that happened to keep those counts would now pass. This is the one thing in commit 3 that is not pure placement and the reviewer should rule on it. OTHER CHECKS ON THE NEW HEAD 6298b30163d: e2e pin 4/4 green, nothing created under the tree afterwards (git status clean, no tmp dirs under packages/cli/test); pnpm --filter @objectstack/cli typecheck exit 0 with the test layer included; ESLint on the changed file 0 errors / 0 warnings. Gate union readings stand from ac8add2e4f4 (56/56 exit 0, rosters 34/37 with three NOT MEASURED); not re-run here because commit 3 changes one test file and the union's family list is derived from the file set, which did not change. FILE LIST — commit 3, `git diff --name-only HEAD~1..HEAD`: packages/cli/test/i18n-extract-metadata-forms-flag.e2e.test.ts, that one file and nothing else. Whole PR, `git diff --name-only origin/main...HEAD`: .changeset/i18n-extract-metadata-forms-flag-independence.md; packages/cli/src/commands/i18n/extract.ts; packages/cli/src/utils/i18n-extract.ts; packages/cli/test/i18n-extract-metadata-forms-flag.e2e.test.ts; packages/cli/test/i18n-extract.test.ts. PR remains DRAFT, auto-merge not armed, labels untouched.",
      "mcp_calls": "18 — whole run, all three rounds",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #16121 (round one, unchanged): `os i18n extract` prints a key count the emitted bundle does not have.",
        "NOT filed, hands back: the branch's three commits carry contradicting card relations — commit 1 `Fixes #14894`, commits 2 and 3 `Part of #14894`. The convention is one declaration in the PR body and none in commits; removing them needs a force-push, barred on this lane. No gate catches it — check-partof-closing-keyword reads PR_BODY only, and the body itself is green. The PR body carries a merge note asking for the squash body to be taken from the body.",
        "Docs drift check named two hand-written pages. Both read, neither falsified, nothing edited: `content/docs/protocol/kernel/i18n-standard.mdx` line 811 describes `--json` as printing the skeletons instead of writing files, which this PR makes more true; line 873's `metadataForms are not checked` is about `os i18n check` coverage rules, untouched here."
      ]
    }

    Generated by Claude Code

  10. os-litant commented on Sep 6, 2026

    @os-litant
    Collaborator

    os-dev-report

    Fixture round — defineStack restored without going back to the un-ignored in-tree location. Supersedes the three reports above, which stay as the record of each round.

    {
      "issue": 14894,
      "status": "done",
      "branch": "claude/issue-14894-i18n-extract-no-metadata-forms",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16120",
      "premise_still_valid": true,
      "summary": "Clause 2, mechanical limb: YES on concrete keys — the --json bundles value loses its metadataForms key for the --no-objects-only class (776 leaves to 3), the payload gains a top-level metadataForms map, and the emitted module's type narrows to an Omit of TranslationData without metadataForms (tsc: a TranslationData-typed consumer compiles, one reading .metadataForms gets TS2339). Clause 2, conformance limb: YES, and on --json the class reaches a THIRD verdict, baseline nowhere, that no face had before. This round restores the fixture's defineStack import — the load-time validation the previous round traded away — by putting the fixture root under this package's git-ignored tmp/ instead of the system temp dir. Head af9fd95004c, one file, no reviewed source moved.",
      "tests": "OPTION CHOSEN: D (mkdtemp under packages/cli/tmp). Both premises were CHECKED, not inherited. (a) Ignore coverage measured against the gate's own instrument rather than against the siblings' existence: `git check-ignore -v packages/cli/tmp/probe.txt` answers `.gitignore:55:tmp/`, and `.gitignore` is tracked (`git ls-files --error-unmatch`). No new ignore rule is added. (b) Coexistence: five sibling suites share that root — init-scaffold-authoring-rules, init-template-comments-self-contained, scaffold-manifest-schema, serve-node-env-production-default, serve-no-artifact — and each rmSyncs only its own mkdtemp subdirectory, never the root; read in each file. This suite's afterAll does the same and says so in a comment. OPTION S DRIVEN AND VERIFIED SAFE BUT NOT USED: on node v22.22.2, rmSync recursive on a root holding a node_modules symlink deleted the LINK and left the target intact — driven with a canary file, 'root gone: true / target survives: true / canary survives: true alive'. Not chosen because the repo already answers this exact constraint more plainly: serve-no-artifact.e2e.test.ts:124 carries the same requirement (a defineStack fixture that must resolve @objectstack/spec) with the same tmp/ placement, and says why in a comment. NOTHING IN EITHER OPTION WAS FALSIFIED; the only correction to the round's readings is a count, not a claim — my first grep found 2 of the 5 siblings because the pattern was too narrow, and the correct count is 5. SELF-TEST, PRINTED VERDICT LINE, SAME COMMAND AND TREE. Before (head 6298b30163d, last round's fix): '✓ dispatch-gates self-test: 1511 cases pass.' After (head af9fd95004c): '✓ dispatch-gates self-test: 1511 cases pass.' with line 1194 '✓ every in-tree directory this tree's sources create is covered by a tracked ignore rule, or is tracked itself', zero EXPOSED: occurrences, and the exit captured by redirect before any pipe: SELFTEST_EXIT=0. Moving the fixture INTO packages/cli/tmp keeps that case green, which is the question this round had to answer. FIXTURE READINGS RE-DRIVEN WITH defineStack RESTORED, unchanged: flag ON — 3 stack leaves, groups [apps, objects], 773 baseline leaves under locale zh-CN; flag OFF (--no-metadata-forms) — 3 stack leaves, 0 baseline leaves, no locales. THE REFUSAL DRIVE, which is the observation that would have failed if this were cosmetic: dropping `type` from the field is REFUSED again — exit 1, payload {\"error\":\"defineStack validation failed (1 issue): ✗ objects.0.fields.name.type: Invalid field type ''. Did you mean 'url'?\"} — where the plain-object fixture of the previous round accepted the same malformed config at exit 0 with a full payload. OTHER CHECKS ON af9fd95004c: e2e pin 4/4 green; packages/cli/tmp left EMPTY after the run (0 entries) so the suite cleaned up its own subdir and left the shared root for its neighbours; git status clean; pnpm --filter @objectstack/cli typecheck exit 0 with the test layer included; ESLint on the changed file 0 errors / 0 warnings. Gate union readings stand from ac8add2e4f4 (56/56 exit 0; rosters 34/37, three NOT MEASURED); not re-run, because this commit changes one test file and the union's family list derives from the file set, which has not moved since. ⚠️ And the lesson from last round applies to that sentence: a clean union is evidence about the union, not about the CI job — the case this round and the last one turned on is outside it. FILE LIST — this commit, `git diff --name-only HEAD~1..HEAD`: packages/cli/test/i18n-extract-metadata-forms-flag.e2e.test.ts, that file and nothing else. Whole PR, `git diff --name-only origin/main...HEAD`: .changeset/i18n-extract-metadata-forms-flag-independence.md; packages/cli/src/commands/i18n/extract.ts; packages/cli/src/utils/i18n-extract.ts; packages/cli/test/i18n-extract-metadata-forms-flag.e2e.test.ts; packages/cli/test/i18n-extract.test.ts. PR remains DRAFT, auto-merge not armed, labels untouched. Process discipline this round: the self-test outruns the foreground cap, so it ran backgrounded and was waited on in-turn by a PID recorded at start and polled with kill -0 — no pkill, no name pattern (#16182).",
      "mcp_calls": "20 — whole run, all four rounds",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #16121: `os i18n extract` prints a key count the emitted bundle does not have.",
        "filed as #16182 by the coordinator, credited to my own report: killing a process by name pattern (pkill -f). Not repeated this round.",
        "NOT filed, hands back: the branch's four commits carry contradicting card relations — commit 1 `Fixes #14894`, commits 2-4 `Part of #14894`. One declaration in the PR body and none in commits is the convention; removing them needs a force-push, barred on this lane. No gate catches it (check-partof-closing-keyword reads PR_BODY only, and the body is green). The PR body carries a merge note asking for the squash body to be taken from the body.",
        "Docs drift check named two hand-written pages. Both read, neither falsified, nothing edited."
      ]
    }

    Generated by Claude Code

  11. added a commit that references this issue on Sep 9, 2026
    f5aec38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions