mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 14:39:22 +00:00
process: an unproven smoke link runs the visual minimum; raster defects need a witness (#690)
The two remaining owner decisions of #690 and the legacy trivial text. - scripts/smoke-select.mjs: VISUAL_MINIMUM, eight smokes of modes, layers and rendering (under a minute locally). An executable diff with no proven link now returns and prints it instead of only "the reviewer decides"; #687 missed smoke_modes that way (item 1'). - scripts/gate-small.mjs: `--smokes` runs the minimum with the selection. - PROCESS §7.1 and AUTHOR.md: a raster, sharpness or compositing defect needs a witness red on the old code for the owner's symptom and the owner's confirmation in a real GPU browser (item 4). - PROCESS §8, TESTING.md: the minimum in the smoke-select rule. - scripts/task-packet.mjs: legacy `trivial` is product flow read as track:show (§5.1), not a short track without a spec. - Tests; mutants visual-minimum-silent-again, visual-minimum-on-proven-link, gate-small-skips-visual-minimum; task-packet-trivial-is-product-flow retargeted. Issue: #690 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -36,10 +36,13 @@ export function parseArgs(argv) {
|
||||
return { base, smokes, jobs: Number.isInteger(jobs) && jobs > 0 ? jobs : 2 };
|
||||
}
|
||||
|
||||
/** Смоки для прогона из JSON smoke-select: прямые + зарегистрированные, без «широких». */
|
||||
/**
|
||||
* Смоки для прогона из JSON smoke-select: прямые + зарегистрированные, без
|
||||
* «широких»; при недоказанной связи — ещё визуальный минимум (#690 п.1′).
|
||||
*/
|
||||
export function smokesToRun(selection) {
|
||||
if (!selection || selection.noExecutableDiff) return [];
|
||||
const names = [...(selection.direct || []), ...(selection.registered || [])]
|
||||
const names = [...(selection.direct || []), ...(selection.registered || []), ...(selection.visualMinimum || [])]
|
||||
.map((entry) => (typeof entry === 'string' ? entry : entry.smoke))
|
||||
.filter(Boolean);
|
||||
return [...new Set(names)].sort();
|
||||
@@ -144,7 +147,10 @@ export async function gateSmall({ cwd = ROOT, base = 'origin/dev', smokes = fals
|
||||
else if (selection.noExecutableDiff) log('smoke-select: исполняемого frontend-диффа нет — смоки этим диффом не выбираются');
|
||||
else {
|
||||
const names = smokesToRun(selection);
|
||||
log(`smoke-select: прямые и зарегистрированные (${names.length})${smokes ? ' — прогнаны выше' : ' — гоняются автором (или `--smokes`), решение по каждой строке в ревью'}:`);
|
||||
const what = (selection.visualMinimum || []).length
|
||||
? 'связь не доказана — прямые, зарегистрированные и визуальный минимум (#690)'
|
||||
: 'прямые и зарегистрированные';
|
||||
log(`smoke-select: ${what} (${names.length})${smokes ? ' — прогнаны выше' : ' — гоняются автором (или `--smokes`), решение по каждой строке в ревью'}:`);
|
||||
for (const name of names) log(` demo/${name}`);
|
||||
if ((selection.broad || []).length) log(` «широких» символов: ${selection.broad.length} — решает ревьюер, автоматически не гоняются`);
|
||||
}
|
||||
|
||||
@@ -3623,11 +3623,11 @@ const MUTANT_DEFINITIONS = [
|
||||
{
|
||||
id: 'task-packet-trivial-is-product-flow',
|
||||
guard: 'node --test test/task-packet.test.mjs',
|
||||
because: 'the trivial short track writes no spec and no spec review, so in S6/S7 its label is '
|
||||
because: 'a legacy trivial task wrote no spec and no spec review, so in S6/S7 its label is '
|
||||
+ 'the only product-flow evidence; dropping it prints the false class A ban of #632 again (r1)',
|
||||
patches: [{
|
||||
file: 'scripts/task-packet.mjs',
|
||||
find: " if (labels.includes('trivial')) reasons.push('короткий трек trivial (ТЗ не пишется, §5.1)');",
|
||||
find: " if (labels.includes('trivial')) reasons.push('прежняя метка trivial — продуктовый поток, читается как track:show (§5.1)');",
|
||||
replace: "",
|
||||
}],
|
||||
},
|
||||
@@ -6167,6 +6167,40 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: " status: degradedExtraCount || degradedCoreCount ? 'failed-core' : 'ok',",
|
||||
}],
|
||||
},
|
||||
// #690 п.1′: недоказанная связь смоков выдаёт визуальный минимум, а не пустоту.
|
||||
{
|
||||
id: 'visual-minimum-silent-again',
|
||||
guard: 'node --test --test-name-pattern="#690" test/smoke-select.test.mjs',
|
||||
because: '#690: an executable diff with no proven smoke link printed only "the reviewer decides" '
|
||||
+ 'and ran nothing; #687 missed smoke_modes that way and left dev red for #685',
|
||||
patches: [{
|
||||
file: 'scripts/smoke-select.mjs',
|
||||
find: ' visualMinimum: unproven ? [...VISUAL_MINIMUM] : [],',
|
||||
replace: ' visualMinimum: [],',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'visual-minimum-on-proven-link',
|
||||
guard: 'node --test --test-name-pattern="#690" test/smoke-select.test.mjs',
|
||||
because: '#690: the minimum is for the unproven case only; with a proven link the selection must '
|
||||
+ 'stay smaller than the matrix',
|
||||
patches: [{
|
||||
file: 'scripts/smoke-select.mjs',
|
||||
find: ' visualMinimum: unproven ? [...VISUAL_MINIMUM] : [],',
|
||||
replace: ' visualMinimum: parsed.executable.length ? [...VISUAL_MINIMUM] : [],',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'gate-small-skips-visual-minimum',
|
||||
guard: 'node --test --test-name-pattern="#690" test/smoke-select.test.mjs',
|
||||
because: '#690: gate:small -- --smokes must run the visual minimum it was handed, not only the '
|
||||
+ 'direct and registered smokes',
|
||||
patches: [{
|
||||
file: 'scripts/gate-small.mjs',
|
||||
find: ' const names = [...(selection.direct || []), ...(selection.registered || []), ...(selection.visualMinimum || [])]',
|
||||
replace: ' const names = [...(selection.direct || []), ...(selection.registered || [])]',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'wall-isolated-extra-discarded',
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
|
||||
@@ -41,6 +41,26 @@ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
*/
|
||||
export const BROAD_SHARE = 0.2;
|
||||
|
||||
/**
|
||||
* «Визуальный минимум» (#690 п.1′): смоки режимов, слоёв и отрисовки, которые
|
||||
* выдаются, когда дифф исполняемый, а связь ни с одним смоком не доказана.
|
||||
* Раньше такой ответ был только текстом «решает ревьюер», и автор гонял одни
|
||||
* названные в ТЗ смоки: CSS-правка #687 не выбрала `smoke_modes`, и `dev`
|
||||
* остался красным для следующей задачи (#685). Набор фиксирован и короткий —
|
||||
* локально меньше минуты, — чтобы «неопределённость» стоила прогона, а не
|
||||
* полной матрицы: полная матрица остаётся гейтом `dev` и беты.
|
||||
*/
|
||||
export const VISUAL_MINIMUM = Object.freeze([
|
||||
'smoke_modes.mjs', // режимы: иконки плана, панели, выход в вид (#687)
|
||||
'smoke_mode_transition.mjs', // вид ↔ редактор: высота тулбара, сцена, камера
|
||||
'smoke_hide_layers.mjs', // слои плана по флагам
|
||||
'smoke_decor_layer_order.mjs', // порядок слоёв декора
|
||||
'smoke_daycycle_zoom_layers.mjs', // слои дня/ночи при любом масштабе (#689)
|
||||
'smoke_static_zoom_sharpness.mjs', // свежий векторный кадр после зума (#685, #689)
|
||||
'smoke_wall_hatch_density.mjs', // штриховка стен в обоих рендерах
|
||||
'smoke_visual_continuity.mjs', // последний кадр плана не пропадает
|
||||
]);
|
||||
|
||||
/** Файлы, чей дифф способен что-то сломать в браузере. */
|
||||
const isExecutableFrontend = (file) => file.startsWith('src/')
|
||||
&& file.endsWith('.ts') && !file.endsWith('.d.ts');
|
||||
@@ -197,6 +217,8 @@ export function selectSmokes(diffText, { root = repoRoot, table, corpus } = {})
|
||||
const registered = registeredSmokes(parsed.symbols)
|
||||
.filter((entry) => !directNames.has(entry.smoke));
|
||||
|
||||
const unproven = parsed.executable.length > 0
|
||||
&& !direct.some((entry) => entry.strong) && !registered.length;
|
||||
return {
|
||||
files: parsed.files,
|
||||
executable: parsed.executable,
|
||||
@@ -211,8 +233,9 @@ export function selectSmokes(diffText, { root = repoRoot, table, corpus } = {})
|
||||
// нельзя молчать. `noExecutableDiff` от него отличается: там и правда
|
||||
// нечего проверять (docs, i18n-строки без кода, чистая инфраструктура).
|
||||
noExecutableDiff: parsed.executable.length === 0,
|
||||
unproven: parsed.executable.length > 0
|
||||
&& !direct.some((entry) => entry.strong) && !registered.length,
|
||||
unproven,
|
||||
// #690 п.1′: неопределённость не молчит и не отдаёт пустой набор.
|
||||
visualMinimum: unproven ? [...VISUAL_MINIMUM] : [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -281,6 +304,9 @@ function report(selection) {
|
||||
+ ' доказуемо.');
|
||||
lines.push('Это не значит «смоки не нужны»: значит, что связь не доказана'
|
||||
+ ' и решает ревьюер.');
|
||||
lines.push(`Визуальный минимум (${selection.visualMinimum.length}) — прогнать до S7;`
|
||||
+ ' `npm run gate:small -- --smokes` гоняет его сам (#690):');
|
||||
for (const smoke of selection.visualMinimum) lines.push(` demo/${smoke}`);
|
||||
if (selection.broad.length) {
|
||||
lines.push(`Широкие символы (есть почти везде, ничего не различают):`
|
||||
+ ` ${selection.broad.slice(0, 10).join(', ')}`);
|
||||
|
||||
@@ -135,15 +135,16 @@ export { hasTrackLabel, trackFromLabels };
|
||||
* сразу на S7 и никогда не несёт S1–S5, ТЗ и ревью ТЗ; поэтому любой из этих
|
||||
* признаков делает эвристику «дифф без класса A» неприменимой. S6/S7/S8 сами по
|
||||
* себе признаком не являются: их носит и инфраструктурная задача после ревью.
|
||||
* Метка `trivial` — признак сама по себе (r1 #632): короткий трек (PROCESS §5.1)
|
||||
* идёт S2 → S5 без ТЗ и без ревью ТЗ, и в S6/S7 никакого другого следа потока у
|
||||
* него нет. Ускоренный инфраструктурный вход понятия трека не имеет, поэтому
|
||||
* `trivial` на нём не бывает; `infra` рядом с ней — тематическая метка.
|
||||
* Прежняя метка `trivial` — признак сама по себе (r1 #632): такие задачи шли
|
||||
* S2 → S5 без ТЗ и без ревью ТЗ, и в S6/S7 никакого другого следа потока у них
|
||||
* нет. С #695 `trivial` читается как `track:show` (PROCESS §5.1), новым задачам
|
||||
* не ставится, но на старых остаётся. Инфраструктурный вход её не несёт; `infra`
|
||||
* рядом с ней — тематическая метка.
|
||||
*/
|
||||
export function productFlowEvidence({ status = null, labels = [], issue = {}, specs = [], reviewDocs = [], comments = [] } = {}) {
|
||||
const reasons = [];
|
||||
if (PRE_CODE_STATUSES.includes(status)) reasons.push(`статус ${status}`);
|
||||
if (labels.includes('trivial')) reasons.push('короткий трек trivial (ТЗ не пишется, §5.1)');
|
||||
if (labels.includes('trivial')) reasons.push('прежняя метка trivial — продуктовый поток, читается как track:show (§5.1)');
|
||||
if (/^#{1,3}\s*ТЗ(?![\p{L}\p{N}_])/mu.test(String(issue?.body ?? ''))) reasons.push('раздел «## ТЗ» в теле issue');
|
||||
if (specs.length) reasons.push('файл ТЗ в docs/specs');
|
||||
if (reviewDocs.some((d) => String(d.name).startsWith('SPEC-REVIEW-'))) reasons.push('документ ревью ТЗ');
|
||||
|
||||
Reference in New Issue
Block a user