Merged
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
50 changes: 48 additions & 2 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -16752,6 +16752,25 @@ function selfTest() {
// Two answers to "what shape is stdout" is no answer.
const bothRun = runCli(['--commands', '--json', seamCard]);
t('passing both stdout spellings refuses instead of silently preferring one', bothRun.status === 2 && (bothRun.stdout ?? '').trim() === '');
// ⭐ The THIRD stdout shape (#14294). The pair above was enforced and this
// one was not: `mode` was computed and then discarded, so these two exited
// 0 with tier prose sitting in the file a consumer had redirected because
// `--commands` promises commands and nothing else. Driven on the real CLI
// for the same reason the pair above is — the defect was in the argv chain,
// which no pure half reaches. Both halves of the refusal are asserted: the
// STATUS, and the stdout a redirecting consumer would have kept, because a
// refusal that still prints an answer is the bug wearing an exit code.
const tierCmdRun = runCli(['--tier', '--commands', seamCard]);
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
const tierJsonRun = runCli(['--tier', '--json', seamCard]);
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
// CONTROL: green before this fix and after it. The new branch is ordered
// after the pair, so all three flags together still answer with the pair's
// own message — the fix adds a refusal and rewords none.
const allThreeRun = runCli(['--tier', '--commands', '--json', seamCard]);
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
}

// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
Expand DownExpand Up@@ -17132,9 +17151,36 @@ if (invokedDirectly) {
// Two answers to "what shape is stdout" is no answer. Blending them — or
// silently preferring one — is the class of failure this whole file is
// about, and it would be a poor place to commit it: these two flags exist
// because a consumer could not tell what it was reading.
// because a consumer could not tell what it was reading. The rule is about
// how many answers the stream carries, not about which two flags happened
// to be named when it was written: `--tier` is a THIRD shape of that same
// stdout, and it is refused against each of these two in the branch below.
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
process.exit(2);
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
// The third shape, and the one the pair above did not reach: `mode` was
// computed from --commands/--json and then DISCARDED by the `--tier` branch
// at the bottom of this block, so the tier PROSE landed in a stream whose
// caption promises one runnable command per line, or one JSON document —
// at exit 0, with nothing on either stream saying the flag had been
// dropped. That is the silent preference the rule above forbids, committed
// one flag over from where it is enforced.
//
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
// machine-readable mode is asked for is defensible — `--tier` answers a
// different question and reads no tree — but it changes what `--tier`
// MEANS when combined, and that is a design call for this file's owner,
// not a repair to a silent-drop bug. Same shape as the two refusals above
// it and the `--ran --tier` refusal above them: one line, exit 2, both
// flags named so the caller never has to guess which one was dropped.
//
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
// the message it already had, so this branch adds a refusal and changes
// none.
console.error(
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
);
process.exit(2);
} else if (wantsChanged && argvPaths.length > 0) {
// The two input modes answer different questions and must never be blended:
// silently preferring one would make the other's arguments vanish without a
Expand DownExpand Up@@ -17202,7 +17248,7 @@ if (invokedDirectly) {
derived = changedPathsFromGit();
} catch (err) {
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier] [--commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier | --commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
process.exit(2);
}
if (derived.paths.length === 0) {
Expand Down
Loading
, '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
Merged
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
50 changes: 48 additions & 2 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -16752,6 +16752,25 @@ function selfTest() {
// Two answers to "what shape is stdout" is no answer.
const bothRun = runCli(['--commands', '--json', seamCard]);
t('passing both stdout spellings refuses instead of silently preferring one', bothRun.status === 2 && (bothRun.stdout ?? '').trim() === '');
// ⭐ The THIRD stdout shape (#14294). The pair above was enforced and this
// one was not: `mode` was computed and then discarded, so these two exited
// 0 with tier prose sitting in the file a consumer had redirected because
// `--commands` promises commands and nothing else. Driven on the real CLI
// for the same reason the pair above is — the defect was in the argv chain,
// which no pure half reaches. Both halves of the refusal are asserted: the
// STATUS, and the stdout a redirecting consumer would have kept, because a
// refusal that still prints an answer is the bug wearing an exit code.
const tierCmdRun = runCli(['--tier', '--commands', seamCard]);
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
const tierJsonRun = runCli(['--tier', '--json', seamCard]);
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
// CONTROL: green before this fix and after it. The new branch is ordered
// after the pair, so all three flags together still answer with the pair's
// own message — the fix adds a refusal and rewords none.
const allThreeRun = runCli(['--tier', '--commands', '--json', seamCard]);
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
}

// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
Expand DownExpand Up@@ -17132,9 +17151,36 @@ if (invokedDirectly) {
// Two answers to "what shape is stdout" is no answer. Blending them — or
// silently preferring one — is the class of failure this whole file is
// about, and it would be a poor place to commit it: these two flags exist
// because a consumer could not tell what it was reading.
// because a consumer could not tell what it was reading. The rule is about
// how many answers the stream carries, not about which two flags happened
// to be named when it was written: `--tier` is a THIRD shape of that same
// stdout, and it is refused against each of these two in the branch below.
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
process.exit(2);
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
// The third shape, and the one the pair above did not reach: `mode` was
// computed from --commands/--json and then DISCARDED by the `--tier` branch
// at the bottom of this block, so the tier PROSE landed in a stream whose
// caption promises one runnable command per line, or one JSON document —
// at exit 0, with nothing on either stream saying the flag had been
// dropped. That is the silent preference the rule above forbids, committed
// one flag over from where it is enforced.
//
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
// machine-readable mode is asked for is defensible — `--tier` answers a
// different question and reads no tree — but it changes what `--tier`
// MEANS when combined, and that is a design call for this file's owner,
// not a repair to a silent-drop bug. Same shape as the two refusals above
// it and the `--ran --tier` refusal above them: one line, exit 2, both
// flags named so the caller never has to guess which one was dropped.
//
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
// the message it already had, so this branch adds a refusal and changes
// none.
console.error(
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
);
process.exit(2);
} else if (wantsChanged && argvPaths.length > 0) {
// The two input modes answer different questions and must never be blended:
// silently preferring one would make the other's arguments vanish without a
Expand DownExpand Up@@ -17202,7 +17248,7 @@ if (invokedDirectly) {
derived = changedPathsFromGit();
} catch (err) {
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier] [--commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier | --commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
process.exit(2);
}
if (derived.paths.length === 0) {
Expand Down
Loading
, '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
Merged
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
50 changes: 48 additions & 2 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -16752,6 +16752,25 @@ function selfTest() {
// Two answers to "what shape is stdout" is no answer.
const bothRun = runCli(['--commands', '--json', seamCard]);
t('passing both stdout spellings refuses instead of silently preferring one', bothRun.status === 2 && (bothRun.stdout ?? '').trim() === '');
// ⭐ The THIRD stdout shape (#14294). The pair above was enforced and this
// one was not: `mode` was computed and then discarded, so these two exited
// 0 with tier prose sitting in the file a consumer had redirected because
// `--commands` promises commands and nothing else. Driven on the real CLI
// for the same reason the pair above is — the defect was in the argv chain,
// which no pure half reaches. Both halves of the refusal are asserted: the
// STATUS, and the stdout a redirecting consumer would have kept, because a
// refusal that still prints an answer is the bug wearing an exit code.
const tierCmdRun = runCli(['--tier', '--commands', seamCard]);
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
const tierJsonRun = runCli(['--tier', '--json', seamCard]);
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
// CONTROL: green before this fix and after it. The new branch is ordered
// after the pair, so all three flags together still answer with the pair's
// own message — the fix adds a refusal and rewords none.
const allThreeRun = runCli(['--tier', '--commands', '--json', seamCard]);
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
}

// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
Expand DownExpand Up@@ -17132,9 +17151,36 @@ if (invokedDirectly) {
// Two answers to "what shape is stdout" is no answer. Blending them — or
// silently preferring one — is the class of failure this whole file is
// about, and it would be a poor place to commit it: these two flags exist
// because a consumer could not tell what it was reading.
// because a consumer could not tell what it was reading. The rule is about
// how many answers the stream carries, not about which two flags happened
// to be named when it was written: `--tier` is a THIRD shape of that same
// stdout, and it is refused against each of these two in the branch below.
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
process.exit(2);
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
// The third shape, and the one the pair above did not reach: `mode` was
// computed from --commands/--json and then DISCARDED by the `--tier` branch
// at the bottom of this block, so the tier PROSE landed in a stream whose
// caption promises one runnable command per line, or one JSON document —
// at exit 0, with nothing on either stream saying the flag had been
// dropped. That is the silent preference the rule above forbids, committed
// one flag over from where it is enforced.
//
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
// machine-readable mode is asked for is defensible — `--tier` answers a
// different question and reads no tree — but it changes what `--tier`
// MEANS when combined, and that is a design call for this file's owner,
// not a repair to a silent-drop bug. Same shape as the two refusals above
// it and the `--ran --tier` refusal above them: one line, exit 2, both
// flags named so the caller never has to guess which one was dropped.
//
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
// the message it already had, so this branch adds a refusal and changes
// none.
console.error(
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
);
process.exit(2);
} else if (wantsChanged && argvPaths.length > 0) {
// The two input modes answer different questions and must never be blended:
// silently preferring one would make the other's arguments vanish without a
Expand DownExpand Up@@ -17202,7 +17248,7 @@ if (invokedDirectly) {
derived = changedPathsFromGit();
} catch (err) {
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier] [--commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier | --commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
process.exit(2);
}
if (derived.paths.length === 0) {
Expand Down
Loading
, '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
Merged
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
50 changes: 48 additions & 2 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -16752,6 +16752,25 @@ function selfTest() {
// Two answers to "what shape is stdout" is no answer.
const bothRun = runCli(['--commands', '--json', seamCard]);
t('passing both stdout spellings refuses instead of silently preferring one', bothRun.status === 2 && (bothRun.stdout ?? '').trim() === '');
// ⭐ The THIRD stdout shape (#14294). The pair above was enforced and this
// one was not: `mode` was computed and then discarded, so these two exited
// 0 with tier prose sitting in the file a consumer had redirected because
// `--commands` promises commands and nothing else. Driven on the real CLI
// for the same reason the pair above is — the defect was in the argv chain,
// which no pure half reaches. Both halves of the refusal are asserted: the
// STATUS, and the stdout a redirecting consumer would have kept, because a
// refusal that still prints an answer is the bug wearing an exit code.
const tierCmdRun = runCli(['--tier', '--commands', seamCard]);
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
const tierJsonRun = runCli(['--tier', '--json', seamCard]);
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
// CONTROL: green before this fix and after it. The new branch is ordered
// after the pair, so all three flags together still answer with the pair's
// own message — the fix adds a refusal and rewords none.
const allThreeRun = runCli(['--tier', '--commands', '--json', seamCard]);
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
}

// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
Expand DownExpand Up@@ -17132,9 +17151,36 @@ if (invokedDirectly) {
// Two answers to "what shape is stdout" is no answer. Blending them — or
// silently preferring one — is the class of failure this whole file is
// about, and it would be a poor place to commit it: these two flags exist
// because a consumer could not tell what it was reading.
// because a consumer could not tell what it was reading. The rule is about
// how many answers the stream carries, not about which two flags happened
// to be named when it was written: `--tier` is a THIRD shape of that same
// stdout, and it is refused against each of these two in the branch below.
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
process.exit(2);
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
// The third shape, and the one the pair above did not reach: `mode` was
// computed from --commands/--json and then DISCARDED by the `--tier` branch
// at the bottom of this block, so the tier PROSE landed in a stream whose
// caption promises one runnable command per line, or one JSON document —
// at exit 0, with nothing on either stream saying the flag had been
// dropped. That is the silent preference the rule above forbids, committed
// one flag over from where it is enforced.
//
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
// machine-readable mode is asked for is defensible — `--tier` answers a
// different question and reads no tree — but it changes what `--tier`
// MEANS when combined, and that is a design call for this file's owner,
// not a repair to a silent-drop bug. Same shape as the two refusals above
// it and the `--ran --tier` refusal above them: one line, exit 2, both
// flags named so the caller never has to guess which one was dropped.
//
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
// the message it already had, so this branch adds a refusal and changes
// none.
console.error(
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
);
process.exit(2);
} else if (wantsChanged && argvPaths.length > 0) {
// The two input modes answer different questions and must never be blended:
// silently preferring one would make the other's arguments vanish without a
Expand DownExpand Up@@ -17202,7 +17248,7 @@ if (invokedDirectly) {
derived = changedPathsFromGit();
} catch (err) {
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier] [--commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier | --commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
process.exit(2);
}
if (derived.paths.length === 0) {
Expand Down
Loading
, '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
Merged
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
50 changes: 48 additions & 2 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -16752,6 +16752,25 @@ function selfTest() {
// Two answers to "what shape is stdout" is no answer.
const bothRun = runCli(['--commands', '--json', seamCard]);
t('passing both stdout spellings refuses instead of silently preferring one', bothRun.status === 2 && (bothRun.stdout ?? '').trim() === '');
// ⭐ The THIRD stdout shape (#14294). The pair above was enforced and this
// one was not: `mode` was computed and then discarded, so these two exited
// 0 with tier prose sitting in the file a consumer had redirected because
// `--commands` promises commands and nothing else. Driven on the real CLI
// for the same reason the pair above is — the defect was in the argv chain,
// which no pure half reaches. Both halves of the refusal are asserted: the
// STATUS, and the stdout a redirecting consumer would have kept, because a
// refusal that still prints an answer is the bug wearing an exit code.
const tierCmdRun = runCli(['--tier', '--commands', seamCard]);
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
const tierJsonRun = runCli(['--tier', '--json', seamCard]);
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
// CONTROL: green before this fix and after it. The new branch is ordered
// after the pair, so all three flags together still answer with the pair's
// own message — the fix adds a refusal and rewords none.
const allThreeRun = runCli(['--tier', '--commands', '--json', seamCard]);
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
}

// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
Expand DownExpand Up@@ -17132,9 +17151,36 @@ if (invokedDirectly) {
// Two answers to "what shape is stdout" is no answer. Blending them — or
// silently preferring one — is the class of failure this whole file is
// about, and it would be a poor place to commit it: these two flags exist
// because a consumer could not tell what it was reading.
// because a consumer could not tell what it was reading. The rule is about
// how many answers the stream carries, not about which two flags happened
// to be named when it was written: `--tier` is a THIRD shape of that same
// stdout, and it is refused against each of these two in the branch below.
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
process.exit(2);
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
// The third shape, and the one the pair above did not reach: `mode` was
// computed from --commands/--json and then DISCARDED by the `--tier` branch
// at the bottom of this block, so the tier PROSE landed in a stream whose
// caption promises one runnable command per line, or one JSON document —
// at exit 0, with nothing on either stream saying the flag had been
// dropped. That is the silent preference the rule above forbids, committed
// one flag over from where it is enforced.
//
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
// machine-readable mode is asked for is defensible — `--tier` answers a
// different question and reads no tree — but it changes what `--tier`
// MEANS when combined, and that is a design call for this file's owner,
// not a repair to a silent-drop bug. Same shape as the two refusals above
// it and the `--ran --tier` refusal above them: one line, exit 2, both
// flags named so the caller never has to guess which one was dropped.
//
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
// the message it already had, so this branch adds a refusal and changes
// none.
console.error(
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
);
process.exit(2);
} else if (wantsChanged && argvPaths.length > 0) {
// The two input modes answer different questions and must never be blended:
// silently preferring one would make the other's arguments vanish without a
Expand DownExpand Up@@ -17202,7 +17248,7 @@ if (invokedDirectly) {
derived = changedPathsFromGit();
} catch (err) {
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier] [--commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier | --commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
process.exit(2);
}
if (derived.paths.length === 0) {
Expand Down
Loading
, '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
Merged
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
50 changes: 48 additions & 2 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -16752,6 +16752,25 @@ function selfTest() {
// Two answers to "what shape is stdout" is no answer.
const bothRun = runCli(['--commands', '--json', seamCard]);
t('passing both stdout spellings refuses instead of silently preferring one', bothRun.status === 2 && (bothRun.stdout ?? '').trim() === '');
// ⭐ The THIRD stdout shape (#14294). The pair above was enforced and this
// one was not: `mode` was computed and then discarded, so these two exited
// 0 with tier prose sitting in the file a consumer had redirected because
// `--commands` promises commands and nothing else. Driven on the real CLI
// for the same reason the pair above is — the defect was in the argv chain,
// which no pure half reaches. Both halves of the refusal are asserted: the
// STATUS, and the stdout a redirecting consumer would have kept, because a
// refusal that still prints an answer is the bug wearing an exit code.
const tierCmdRun = runCli(['--tier', '--commands', seamCard]);
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
const tierJsonRun = runCli(['--tier', '--json', seamCard]);
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
// CONTROL: green before this fix and after it. The new branch is ordered
// after the pair, so all three flags together still answer with the pair's
// own message — the fix adds a refusal and rewords none.
const allThreeRun = runCli(['--tier', '--commands', '--json', seamCard]);
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
}

// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
Expand DownExpand Up@@ -17132,9 +17151,36 @@ if (invokedDirectly) {
// Two answers to "what shape is stdout" is no answer. Blending them — or
// silently preferring one — is the class of failure this whole file is
// about, and it would be a poor place to commit it: these two flags exist
// because a consumer could not tell what it was reading.
// because a consumer could not tell what it was reading. The rule is about
// how many answers the stream carries, not about which two flags happened
// to be named when it was written: `--tier` is a THIRD shape of that same
// stdout, and it is refused against each of these two in the branch below.
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
process.exit(2);
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
// The third shape, and the one the pair above did not reach: `mode` was
// computed from --commands/--json and then DISCARDED by the `--tier` branch
// at the bottom of this block, so the tier PROSE landed in a stream whose
// caption promises one runnable command per line, or one JSON document —
// at exit 0, with nothing on either stream saying the flag had been
// dropped. That is the silent preference the rule above forbids, committed
// one flag over from where it is enforced.
//
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
// machine-readable mode is asked for is defensible — `--tier` answers a
// different question and reads no tree — but it changes what `--tier`
// MEANS when combined, and that is a design call for this file's owner,
// not a repair to a silent-drop bug. Same shape as the two refusals above
// it and the `--ran --tier` refusal above them: one line, exit 2, both
// flags named so the caller never has to guess which one was dropped.
//
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
// the message it already had, so this branch adds a refusal and changes
// none.
console.error(
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
);
process.exit(2);
} else if (wantsChanged && argvPaths.length > 0) {
// The two input modes answer different questions and must never be blended:
// silently preferring one would make the other's arguments vanish without a
Expand DownExpand Up@@ -17202,7 +17248,7 @@ if (invokedDirectly) {
derived = changedPathsFromGit();
} catch (err) {
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier] [--commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier | --commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
process.exit(2);
}
if (derived.paths.length === 0) {
Expand Down
Loading
, '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
Merged
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
50 changes: 48 additions & 2 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -16752,6 +16752,25 @@ function selfTest() {
// Two answers to "what shape is stdout" is no answer.
const bothRun = runCli(['--commands', '--json', seamCard]);
t('passing both stdout spellings refuses instead of silently preferring one', bothRun.status === 2 && (bothRun.stdout ?? '').trim() === '');
// ⭐ The THIRD stdout shape (#14294). The pair above was enforced and this
// one was not: `mode` was computed and then discarded, so these two exited
// 0 with tier prose sitting in the file a consumer had redirected because
// `--commands` promises commands and nothing else. Driven on the real CLI
// for the same reason the pair above is — the defect was in the argv chain,
// which no pure half reaches. Both halves of the refusal are asserted: the
// STATUS, and the stdout a redirecting consumer would have kept, because a
// refusal that still prints an answer is the bug wearing an exit code.
const tierCmdRun = runCli(['--tier', '--commands', seamCard]);
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
const tierJsonRun = runCli(['--tier', '--json', seamCard]);
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
// CONTROL: green before this fix and after it. The new branch is ordered
// after the pair, so all three flags together still answer with the pair's
// own message — the fix adds a refusal and rewords none.
const allThreeRun = runCli(['--tier', '--commands', '--json', seamCard]);
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
}

// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
Expand DownExpand Up@@ -17132,9 +17151,36 @@ if (invokedDirectly) {
// Two answers to "what shape is stdout" is no answer. Blending them — or
// silently preferring one — is the class of failure this whole file is
// about, and it would be a poor place to commit it: these two flags exist
// because a consumer could not tell what it was reading.
// because a consumer could not tell what it was reading. The rule is about
// how many answers the stream carries, not about which two flags happened
// to be named when it was written: `--tier` is a THIRD shape of that same
// stdout, and it is refused against each of these two in the branch below.
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
process.exit(2);
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
// The third shape, and the one the pair above did not reach: `mode` was
// computed from --commands/--json and then DISCARDED by the `--tier` branch
// at the bottom of this block, so the tier PROSE landed in a stream whose
// caption promises one runnable command per line, or one JSON document —
// at exit 0, with nothing on either stream saying the flag had been
// dropped. That is the silent preference the rule above forbids, committed
// one flag over from where it is enforced.
//
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
// machine-readable mode is asked for is defensible — `--tier` answers a
// different question and reads no tree — but it changes what `--tier`
// MEANS when combined, and that is a design call for this file's owner,
// not a repair to a silent-drop bug. Same shape as the two refusals above
// it and the `--ran --tier` refusal above them: one line, exit 2, both
// flags named so the caller never has to guess which one was dropped.
//
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
// the message it already had, so this branch adds a refusal and changes
// none.
console.error(
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
);
process.exit(2);
} else if (wantsChanged && argvPaths.length > 0) {
// The two input modes answer different questions and must never be blended:
// silently preferring one would make the other's arguments vanish without a
Expand DownExpand Up@@ -17202,7 +17248,7 @@ if (invokedDirectly) {
derived = changedPathsFromGit();
} catch (err) {
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier] [--commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier | --commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
process.exit(2);
}
if (derived.paths.length === 0) {
Expand Down
Loading
, '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
Merged
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
50 changes: 48 additions & 2 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -16752,6 +16752,25 @@ function selfTest() {
// Two answers to "what shape is stdout" is no answer.
const bothRun = runCli(['--commands', '--json', seamCard]);
t('passing both stdout spellings refuses instead of silently preferring one', bothRun.status === 2 && (bothRun.stdout ?? '').trim() === '');
// ⭐ The THIRD stdout shape (#14294). The pair above was enforced and this
// one was not: `mode` was computed and then discarded, so these two exited
// 0 with tier prose sitting in the file a consumer had redirected because
// `--commands` promises commands and nothing else. Driven on the real CLI
// for the same reason the pair above is — the defect was in the argv chain,
// which no pure half reaches. Both halves of the refusal are asserted: the
// STATUS, and the stdout a redirecting consumer would have kept, because a
// refusal that still prints an answer is the bug wearing an exit code.
const tierCmdRun = runCli(['--tier', '--commands', seamCard]);
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
const tierJsonRun = runCli(['--tier', '--json', seamCard]);
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
// CONTROL: green before this fix and after it. The new branch is ordered
// after the pair, so all three flags together still answer with the pair's
// own message — the fix adds a refusal and rewords none.
const allThreeRun = runCli(['--tier', '--commands', '--json', seamCard]);
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
}

// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
Expand DownExpand Up@@ -17132,9 +17151,36 @@ if (invokedDirectly) {
// Two answers to "what shape is stdout" is no answer. Blending them — or
// silently preferring one — is the class of failure this whole file is
// about, and it would be a poor place to commit it: these two flags exist
// because a consumer could not tell what it was reading.
// because a consumer could not tell what it was reading. The rule is about
// how many answers the stream carries, not about which two flags happened
// to be named when it was written: `--tier` is a THIRD shape of that same
// stdout, and it is refused against each of these two in the branch below.
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
process.exit(2);
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
// The third shape, and the one the pair above did not reach: `mode` was
// computed from --commands/--json and then DISCARDED by the `--tier` branch
// at the bottom of this block, so the tier PROSE landed in a stream whose
// caption promises one runnable command per line, or one JSON document —
// at exit 0, with nothing on either stream saying the flag had been
// dropped. That is the silent preference the rule above forbids, committed
// one flag over from where it is enforced.
//
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
// machine-readable mode is asked for is defensible — `--tier` answers a
// different question and reads no tree — but it changes what `--tier`
// MEANS when combined, and that is a design call for this file's owner,
// not a repair to a silent-drop bug. Same shape as the two refusals above
// it and the `--ran --tier` refusal above them: one line, exit 2, both
// flags named so the caller never has to guess which one was dropped.
//
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
// the message it already had, so this branch adds a refusal and changes
// none.
console.error(
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
);
process.exit(2);
} else if (wantsChanged && argvPaths.length > 0) {
// The two input modes answer different questions and must never be blended:
// silently preferring one would make the other's arguments vanish without a
Expand DownExpand Up@@ -17202,7 +17248,7 @@ if (invokedDirectly) {
derived = changedPathsFromGit();
} catch (err) {
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier] [--commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
console.error('usage: node scripts/pm/dispatch-gates.mjs [--residue] [--tier | --commands | --json | --ran <file>] [--repo owner/name] [<path> ...] | --changed | --self-test');
process.exit(2);
}
if (derived.paths.length === 0) {
Expand Down
Loading