Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/pm-dispatch/references/lanes/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
- **安全族卡的披露纪律**:复现配方不落任何公开面(卡、PR、评论);证据以抽象描述
或私有通道承载。
- 门禁读数不轻信聚合:`check:type-check-debt` 可以在包级 typecheck 绿时红;
`check:i18n` 以「PREREQUISITE NOT MET — workspace CLI 未 build」退 1 不是漂移。
`check:i18n` 以「PREREQUISITE NOT MET — workspace CLI 未 build」退 3 不是漂移。

## 席内判断

Expand Down
33 changes: 31 additions & 2 deletions packages/cli/scripts/check-app-nav-i18n.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
import { existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
// The exit-code contract for a refusal, from the frame that owns it —
// imported rather than re-picked, the shape `packages/lint/scripts/*` already
// use to reach repo-root gate infrastructure from inside a package.
import { EXIT_FINDINGS, EXIT_PREREQUISITE_NOT_MET } from '../../../scripts/import-prerequisite.mjs';

const HERE = dirname(fileURLToPath(import.meta.url));
const CLI_ROOT = join(HERE, '..');
Expand Down Expand Up @@ -605,6 +609,25 @@ if (process.argv.includes('--self-test')) {
* Answered once, before anything is imported — a missing build must cost one
* stated verdict, never a node stack pointing at whichever package happened to
* be imported first (the #5862 lesson on the neighbouring i18n gates).
*
* Exits `EXIT_PREREQUISITE_NOT_MET`, and the printed advisory says the same
* number: nothing was measured, so this is NOT a finding.
*
* ⛔ The closing paragraph is the frame's, verbatim apart from this gate's own
* command, and it is NOT a place to improvise. The wording it replaced was true
* but prescribed `echo "EXIT=$?"` without saying WHERE, so a reader who did the
* natural thing — `... | tail -4; echo "EXIT=$?"` — read `tail`'s status rather
* than this gate's, which is the exact false green the prescription exists to
* prevent. The one true half ("capture it BEFORE any pipe") had existed in
* `scripts/check-test-completeness.mjs` all along and simply never reached here.
*
* ⛔ That older phrasing is also the CENSUS-NEGATIVE string: the instrument for
* this advisory family is `git grep -n "no pipe shape repairs it"`, and the
* acceptance criterion is that no copy of the pre-convergence sentence survives
* anywhere under `scripts/**` or `packages/**`. So do not reintroduce it here —
* not even inside a comment, quoting it to explain what was wrong. (Measured:
* the first draft of THIS comment did exactly that, and put the criterion back
* into the red while the code beside it was already correct.)
*/
function checkBuildPrerequisite() {
const probe = join(CLI_ROOT, 'node_modules', '@objectstack', 'setup', 'dist', 'index.mjs');
Expand All @@ -617,9 +640,15 @@ function checkBuildPrerequisite() {
` Fix: pnpm build (or: pnpm --filter '@objectstack/cli^...' build)\n\n` +
` Nothing was measured: no app was merged and no locale was compared, so this\n` +
` result says NOTHING about whether any nav label went untranslated.\n` +
` (Exit code 1 — but piping this gate reports the PIPE's status. Use \`echo "EXIT=$?"\`.)`,
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from a finding's ${EXIT_FINDINGS} — capture it BEFORE any pipe:\n` +
` \`node packages/cli/scripts/check-app-nav-i18n.mjs > /tmp/check-app-nav-i18n.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
process.exit(EXIT_PREREQUISITE_NOT_MET);
}

checkBuildPrerequisite();
Expand Down
18 changes: 13 additions & 5 deletions scripts/check-i18n-bundles.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import {
resolveCliCommandFile,
workspaceBuildFix,
} from './cli-build-prerequisite.mjs';
import { EXIT_FINDINGS, EXIT_PREREQUISITE_NOT_MET } from './import-prerequisite.mjs';
import { findExtractConfigs, flagsFromDocstring } from './i18n-bundle-surface.mjs';

/**
Expand Down Expand Up @@ -971,9 +972,15 @@ if (process.argv.includes('--self-test')) {
* ONE prerequisite and ONE command to satisfy it — never per package, and never
* phrased so it can be mistaken for a verdict about the bundles.
*
* Exits 1, the same code the two real verdicts use: any wrapper that treats
* non-zero as failure keeps behaving identically, and inventing a second failure
* code would be a new contract nobody asked for.
* Exits `EXIT_PREREQUISITE_NOT_MET` — the constant `import-prerequisite.mjs`
* exports, imported rather than re-picked, and printed by the advisory in the
* same stroke. ⛔ NOT a second failure code invented here: 3 is what every
* other gate in this repo already means by these two words (#13983 moved the
* 45-gate shared frame onto it), and this site was one of the last three
* contradicting them. Nothing mechanical changes — every consumer of these
* gates treats any non-zero as failure — so the whole benefit is that a reader
* who sees only the number learns what the text already says: nothing was
* measured, and this is NOT a finding.
*
* `scanned` is how many packages the loop had already attempted when the
* prerequisite fired, and it is what keeps the closing paragraph TRUE. The
Expand Down Expand Up @@ -1005,14 +1012,15 @@ function reportPrerequisiteNotMet(headline, detail, options = {}) {
`\n\n Fix: ${fix}\n` +
alsoFix.map((l) => ` ${l}\n`).join('') +
`\n${nothingChecked}\n` +
` (Exit code 1 — capture it BEFORE any pipe: \`pnpm check:i18n > /tmp/i18n.log 2>&1; echo "EXIT=$?"\`.\n` +
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from a finding's ${EXIT_FINDINGS} — capture it BEFORE any pipe:\n` +
` \`pnpm check:i18n > /tmp/i18n.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
process.exit(EXIT_PREREQUISITE_NOT_MET);
}

/**
Expand Down
34 changes: 23 additions & 11 deletions scripts/check-i18n-coverage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ import {
owningPackageOf,
resolveCliCommandFile,
} from './cli-build-prerequisite.mjs';
import { EXIT_FINDINGS, EXIT_PREREQUISITE_NOT_MET } from './import-prerequisite.mjs';

const HERE = dirname(fileURLToPath(import.meta.url));
/** This script lives in `scripts/`, so the repo root is one level up (#10907). */
Expand Down Expand Up @@ -1141,9 +1142,15 @@ if (process.argv.includes('--self-test')) {
* ONE prerequisite and ONE command to satisfy it — never per config, and never
* phrased so it can be mistaken for a verdict about a config's translations.
*
* Exits 1, the same code the real verdict uses: any wrapper that treats non-zero
* as failure keeps behaving identically, and inventing a second failure code
* would be a new contract nobody asked for.
* Exits `EXIT_PREREQUISITE_NOT_MET` — the constant `import-prerequisite.mjs`
* exports, imported rather than re-picked, and printed by the advisory in the
* same stroke. ⛔ NOT a second failure code invented here: 3 is what every
* other gate in this repo already means by these two words (#13983 moved the
* 45-gate shared frame onto it), and this site was one of the last three
* contradicting them. Nothing mechanical changes — every consumer of these
* gates treats any non-zero as failure — so the whole benefit is that a reader
* who sees only the number learns what the text already says: nothing was
* measured, and this is NOT a finding.
*
* The remedy is stated at TWO widths on purpose. `CLI_BUILD_FIX` is the command
* that clears exactly what was checked, and nothing more — this probe measures the
Expand Down Expand Up @@ -1172,15 +1179,15 @@ function reportPrerequisiteNotMet(headline, detail) {
` Nothing was measured: no config was linted and no count was compared, so this\n` +
` result says NOTHING about whether any declared label went untranslated — and\n` +
` the baseline was left exactly as committed (\`--update\` included).\n` +
` (Exit code 1 — capture it BEFORE any pipe:\n` +
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from a finding's ${EXIT_FINDINGS} — capture it BEFORE any pipe:\n` +
` \`pnpm check:i18n-coverage > /tmp/i18n-coverage.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
process.exit(EXIT_PREREQUISITE_NOT_MET);
}

/**
Expand All @@ -1198,7 +1205,10 @@ function reportPrerequisiteNotMet(headline, detail) {
* The same invariant `reportPrerequisiteNotMet` states: nothing measured, nothing
* written.
*
* Exits 1, the same code every other verdict here uses.
* Exits `EXIT_PREREQUISITE_NOT_MET`, NOT a finding's 1 — the closing paragraph
* already says nothing was compared, and the code now says the same thing. A
* partial round answered this gate's question about exactly nothing, which is
* what that code means everywhere else in this repo.
*/
function reportUnmeasuredConfigs(failures, measuredCount) {
const groups = groupFailuresByCause(failures);
Expand Down Expand Up @@ -1232,15 +1242,15 @@ function reportUnmeasuredConfigs(failures, measuredCount) {
` \`--update\` would freeze the survivors while silently dropping the rest. So this\n` +
` result says NOTHING about whether any declared label went untranslated, and the\n` +
` baseline was left exactly as committed (\`--update\` included).\n` +
` (Exit code 1 — capture it BEFORE any pipe:\n` +
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from a finding's ${EXIT_FINDINGS} — capture it BEFORE any pipe:\n` +
` \`pnpm check:i18n-coverage > /tmp/i18n-coverage.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
process.exit(EXIT_PREREQUISITE_NOT_MET);
}

/**
Expand All @@ -1253,7 +1263,9 @@ function reportUnmeasuredConfigs(failures, measuredCount) {
* is to record it. Same invariant the two reports above state, and for the same
* reason: nothing measured, nothing written.
*
* Exits 1, the code every other verdict here uses.
* Exits `EXIT_PREREQUISITE_NOT_MET`, NOT a finding's 1 — an empty population is
* the sharpest case of "nothing was measured", and that is the code this repo
* reserves for it.
*
* @param {{ headline: string, detail: string[] }} verdict
*/
Expand All @@ -1266,15 +1278,15 @@ function reportEmptyPopulation(verdict) {
` Nothing was measured: no config was linted and no count was compared, so this\n` +
` result says NOTHING about whether any declared label went untranslated — and\n` +
` the baseline was left exactly as committed (\`--update\` included).\n` +
` (Exit code 1 — capture it BEFORE any pipe:\n` +
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from a finding's ${EXIT_FINDINGS} — capture it BEFORE any pipe:\n` +
` \`pnpm check:i18n-coverage > /tmp/i18n-coverage.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
process.exit(EXIT_PREREQUISITE_NOT_MET);
}

/**
Expand Down
Loading