@tritard/waterbrother 0.8.38 → 0.8.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.js +9 -0
- package/src/frontend.js +121 -0
- package/src/workflow.js +9 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ import { computeImpactMap } from "./impact.js";
|
|
|
16
16
|
import { reviewTurn } from "./reviewer.js";
|
|
17
17
|
import {
|
|
18
18
|
buildFrontendExecutionContext,
|
|
19
|
+
getFrontendAcceptanceFailure,
|
|
19
20
|
buildFrontendRebuildPrompt,
|
|
20
21
|
buildFrontendRevisionPrompt,
|
|
21
22
|
captureFrontendScreenshot,
|
|
@@ -1316,6 +1317,14 @@ async function maybeReviseInteractiveFrontend({
|
|
|
1316
1317
|
};
|
|
1317
1318
|
}
|
|
1318
1319
|
|
|
1320
|
+
const acceptanceFailure = getFrontendAcceptanceFailure({
|
|
1321
|
+
frontend: frontendExecutionContext.frontend || null,
|
|
1322
|
+
slop: artifacts.designSlop
|
|
1323
|
+
});
|
|
1324
|
+
if (acceptanceFailure) {
|
|
1325
|
+
throw new Error(acceptanceFailure.reason);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1319
1328
|
return { response: activeResponse, receipt: activeReceipt, artifacts };
|
|
1320
1329
|
}
|
|
1321
1330
|
|
package/src/frontend.js
CHANGED
|
@@ -257,6 +257,67 @@ const BENCHMARK_STARTERS = {
|
|
|
257
257
|
]
|
|
258
258
|
};
|
|
259
259
|
|
|
260
|
+
const BENCHMARK_STARTER_VARIANTS = {
|
|
261
|
+
blog: [
|
|
262
|
+
{
|
|
263
|
+
key: "architectural-journal",
|
|
264
|
+
label: "architectural journal",
|
|
265
|
+
starter: [
|
|
266
|
+
"Variant shape: severe image-text split with one offset note card and one lean secondary pair.",
|
|
267
|
+
"Palette should feel mineral, paper, and shadow-driven rather than warm lifestyle-editorial.",
|
|
268
|
+
"Finish with one restrained final gesture, not a generic footer label."
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
key: "art-book-minimal",
|
|
273
|
+
label: "art-book minimal",
|
|
274
|
+
starter: [
|
|
275
|
+
"Variant shape: oversized white space, one dominant visual surface, one short text block, one caption-like secondary band.",
|
|
276
|
+
"Treat typography like exhibition text: sparse, quiet, and deliberate.",
|
|
277
|
+
"Avoid default editorial chrome entirely; let spacing and crop do the work."
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
key: "review-folio",
|
|
282
|
+
label: "review folio",
|
|
283
|
+
starter: [
|
|
284
|
+
"Variant shape: folio-like review page with strong masthead mark, one lead essay, and one compact pair of related notes.",
|
|
285
|
+
"Use stronger typographic contrast and one unexpected compositional move, not a generic archive scaffold.",
|
|
286
|
+
"Closing section should feel like a designed coda rather than a footer."
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
],
|
|
290
|
+
store: [
|
|
291
|
+
{
|
|
292
|
+
key: "cold-premium-tech",
|
|
293
|
+
label: "cold premium tech",
|
|
294
|
+
starter: [
|
|
295
|
+
"Variant shape: hard-edged flagship PDP with dark hero, precise specification accents, and a compact proof/guarantee band.",
|
|
296
|
+
"Use crisp hierarchy, hard contrast, and performance language rather than luxury-lifestyle filler.",
|
|
297
|
+
"Cart UI should feel real and quiet; no gimmicks, no fake app chrome."
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
key: "warm-object-craft",
|
|
302
|
+
label: "warm object craft",
|
|
303
|
+
starter: [
|
|
304
|
+
"Variant shape: tactile product story with warm neutral palette, close material crop, and trust/objection handling near the CTA.",
|
|
305
|
+
"Lead with material, construction, weight, care, and use-case details.",
|
|
306
|
+
"Keep the page commercially sharp, but let the object feel physical and desirable."
|
|
307
|
+
]
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
key: "dense-conversion-pdp",
|
|
311
|
+
label: "dense conversion pdp",
|
|
312
|
+
starter: [
|
|
313
|
+
"Variant shape: stronger purchase stack with nearby shipping, returns, warranty, and one compact objection-handling block.",
|
|
314
|
+
"Use a more aggressive conversion rhythm than the minimalist variants, but keep it authored and clean.",
|
|
315
|
+
"Hero and cart item surfaces must both feel product-real, not abstract placeholders."
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
};
|
|
320
|
+
|
|
260
321
|
const BENCHMARK_FEWSHOT_EXAMPLES = {
|
|
261
322
|
blog: {
|
|
262
323
|
good: [
|
|
@@ -351,6 +412,38 @@ function getBenchmarkStarter(siteType, benchmarkMode) {
|
|
|
351
412
|
return BENCHMARK_STARTERS[siteType] || null;
|
|
352
413
|
}
|
|
353
414
|
|
|
415
|
+
function stablePromptHash(text = "") {
|
|
416
|
+
const value = String(text || "");
|
|
417
|
+
let hash = 0;
|
|
418
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
419
|
+
hash = ((hash * 31) + value.charCodeAt(index)) >>> 0;
|
|
420
|
+
}
|
|
421
|
+
return hash;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function chooseBenchmarkStarterVariant({ siteType, promptText = "", archetype = "" } = {}) {
|
|
425
|
+
const variants = BENCHMARK_STARTER_VARIANTS[siteType] || [];
|
|
426
|
+
if (variants.length === 0) return null;
|
|
427
|
+
const text = String(promptText || "").toLowerCase();
|
|
428
|
+
if (siteType === "blog") {
|
|
429
|
+
if (/\b(?:architecture|architectural|concrete|brutalis|ando|chapel|osaka|kyoto|sao paulo)\b/.test(text)) {
|
|
430
|
+
return variants.find((variant) => variant.key === "architectural-journal") || variants[0];
|
|
431
|
+
}
|
|
432
|
+
if (/\b(?:gallery|art book|art-book|folio|exhibition|museum)\b/.test(text)) {
|
|
433
|
+
return variants.find((variant) => variant.key === "art-book-minimal") || variants[0];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (siteType === "store") {
|
|
437
|
+
if (/\b(?:audio|headphones|speaker|tech|wireless|anc|bluetooth)\b/.test(text) || archetype === "high-contrast-tech") {
|
|
438
|
+
return variants.find((variant) => variant.key === "cold-premium-tech") || variants[0];
|
|
439
|
+
}
|
|
440
|
+
if (/\b(?:lamp|chair|desk|leather|tote|wallet|wood|ceramic|object)\b/.test(text)) {
|
|
441
|
+
return variants.find((variant) => variant.key === "warm-object-craft") || variants[0];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return variants[stablePromptHash(text) % variants.length];
|
|
445
|
+
}
|
|
446
|
+
|
|
354
447
|
function getBenchmarkExamples(siteType, benchmarkMode) {
|
|
355
448
|
if (!benchmarkMode) return null;
|
|
356
449
|
return BENCHMARK_FEWSHOT_EXAMPLES[siteType] || null;
|
|
@@ -499,6 +592,7 @@ export function buildFrontendExecutionContext({ promptText = "", profile = "code
|
|
|
499
592
|
const assetPolicy = inferAssetPolicy(text);
|
|
500
593
|
const layoutStarter = getLayoutStarter(siteType, archetype);
|
|
501
594
|
const benchmarkStarter = getBenchmarkStarter(siteType, benchmarkMode);
|
|
595
|
+
const benchmarkVariant = chooseBenchmarkStarterVariant({ siteType, promptText: text, archetype });
|
|
502
596
|
const benchmarkExamples = getBenchmarkExamples(siteType, benchmarkMode);
|
|
503
597
|
const siteTypeRules = getSiteTypeRules(siteType);
|
|
504
598
|
const benchmarkSiteTypeRules = getBenchmarkSiteTypeRules(siteType, benchmarkMode);
|
|
@@ -507,6 +601,7 @@ export function buildFrontendExecutionContext({ promptText = "", profile = "code
|
|
|
507
601
|
`Content mode: ${contentMode}. Asset policy: ${assetPolicy}.`,
|
|
508
602
|
layoutStarter ? `Layout starter: ${layoutStarter[0]}` : "",
|
|
509
603
|
...(benchmarkStarter ? [`Benchmark starter skeleton:\n- ${benchmarkStarter.join("\n- ")}`] : []),
|
|
604
|
+
...(benchmarkVariant ? [`Benchmark starter variant: ${benchmarkVariant.label}\n- ${benchmarkVariant.starter.join("\n- ")}`] : []),
|
|
510
605
|
...(benchmarkExamples ? [
|
|
511
606
|
`Benchmark few-shot guide:\nGood:\n- ${benchmarkExamples.good.join("\n- ")}\nBad:\n- ${benchmarkExamples.bad.join("\n- ")}`
|
|
512
607
|
] : []),
|
|
@@ -528,6 +623,8 @@ export function buildFrontendExecutionContext({ promptText = "", profile = "code
|
|
|
528
623
|
assetPolicy,
|
|
529
624
|
layoutStarter: layoutStarter ? layoutStarter[0] : null,
|
|
530
625
|
benchmarkStarter: benchmarkStarter ? benchmarkStarter[0] : null,
|
|
626
|
+
benchmarkVariant: benchmarkVariant?.label || null,
|
|
627
|
+
benchmarkVariantStarter: benchmarkVariant?.starter || null,
|
|
531
628
|
benchmarkMode,
|
|
532
629
|
siteTypeRules,
|
|
533
630
|
benchmarkSiteTypeRules
|
|
@@ -806,6 +903,24 @@ export function shouldForceFrontendRebuild({ frontend = null, slop = null, revis
|
|
|
806
903
|
return revisionCount < 2;
|
|
807
904
|
}
|
|
808
905
|
|
|
906
|
+
export function getFrontendAcceptanceFailure({ frontend = null, slop = null } = {}) {
|
|
907
|
+
if (!frontend?.benchmarkMode || !slop?.hardBlock) return null;
|
|
908
|
+
const flags = Array.isArray(slop.flags) ? slop.flags : [];
|
|
909
|
+
if (frontend.siteType === "store") {
|
|
910
|
+
const blocking = flags.filter((flag) =>
|
|
911
|
+
/Tailwind CDN|Font Awesome|placeholder product imagery|placeholder imagery|fake proof or badge chrome|fake keyboard or shortcut chrome|demo behavior|demo page explanation copy|fake cart or checkout fallback behavior|generic luxury-commerce copy/i.test(flag)
|
|
912
|
+
);
|
|
913
|
+
if (blocking.length > 0) {
|
|
914
|
+
return {
|
|
915
|
+
siteType: frontend.siteType,
|
|
916
|
+
reason: `benchmark store acceptance failed: ${blocking.join(", ")}`,
|
|
917
|
+
flags: blocking
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
|
|
809
924
|
export function buildFrontendRevisionPrompt({
|
|
810
925
|
originalPrompt = "",
|
|
811
926
|
designReview = null,
|
|
@@ -814,6 +929,7 @@ export function buildFrontendRevisionPrompt({
|
|
|
814
929
|
} = {}) {
|
|
815
930
|
const siteType = pickByPattern(String(originalPrompt || ""), SITE_TYPES, "landing");
|
|
816
931
|
const benchmarkMode = isBenchmarkFrontendTask(originalPrompt);
|
|
932
|
+
const benchmarkVariant = chooseBenchmarkStarterVariant({ siteType, promptText: originalPrompt });
|
|
817
933
|
const benchmarkExamples = getBenchmarkExamples(siteType, benchmarkMode);
|
|
818
934
|
const issues = Array.isArray(designReview?.issues) ? designReview.issues.slice(0, 6) : [];
|
|
819
935
|
const nextPass = Array.isArray(designReview?.nextPass) ? designReview.nextPass.slice(0, 6) : [];
|
|
@@ -826,6 +942,7 @@ export function buildFrontendRevisionPrompt({
|
|
|
826
942
|
issues.length > 0 ? `Problems to fix:\n- ${issues.join("\n- ")}` : "",
|
|
827
943
|
visualIssues.length > 0 ? `Visible screenshot problems:\n- ${visualIssues.join("\n- ")}` : "",
|
|
828
944
|
slopFlags.length > 0 ? `Deterministic slop flags:\n- ${slopFlags.join("\n- ")}` : "",
|
|
945
|
+
benchmarkVariant ? `Preserve this benchmark variant direction: ${benchmarkVariant.label}\n- ${benchmarkVariant.starter.join("\n- ")}` : "",
|
|
829
946
|
benchmarkExamples ? `Benchmark example guide:\nGood:\n- ${benchmarkExamples.good.join("\n- ")}\nBad:\n- ${benchmarkExamples.bad.join("\n- ")}` : "",
|
|
830
947
|
nextPass.length > 0 ? `Revision priorities:\n- ${nextPass.join("\n- ")}` : "",
|
|
831
948
|
visualNextPass.length > 0 ? `Screenshot revision priorities:\n- ${visualNextPass.join("\n- ")}` : "",
|
|
@@ -862,6 +979,9 @@ export function buildFrontendRebuildPrompt({
|
|
|
862
979
|
} = {}) {
|
|
863
980
|
const siteType = pickByPattern(String(originalPrompt || ""), SITE_TYPES, "landing");
|
|
864
981
|
const benchmarkMode = isBenchmarkFrontendTask(originalPrompt);
|
|
982
|
+
const benchmarkVariant = frontend?.benchmarkVariantStarter?.length
|
|
983
|
+
? { label: frontend.benchmarkVariant || "selected benchmark variant", starter: frontend.benchmarkVariantStarter }
|
|
984
|
+
: chooseBenchmarkStarterVariant({ siteType, promptText: originalPrompt, archetype: frontend?.archetype || "" });
|
|
865
985
|
const benchmarkExamples = getBenchmarkExamples(siteType, benchmarkMode);
|
|
866
986
|
const slopFlags = Array.isArray(slop?.flags) ? slop.flags.slice(0, 8) : [];
|
|
867
987
|
const issues = Array.isArray(designReview?.issues) ? designReview.issues.slice(0, 6) : [];
|
|
@@ -871,6 +991,7 @@ export function buildFrontendRebuildPrompt({
|
|
|
871
991
|
"Rebuild the affected frontend files from scratch within the same contract.",
|
|
872
992
|
`Original task: ${String(originalPrompt || "").trim()}`,
|
|
873
993
|
starter ? `Required starter skeleton:\n- ${starter.join("\n- ")}` : "",
|
|
994
|
+
benchmarkVariant ? `Required benchmark variant direction: ${benchmarkVariant.label}\n- ${benchmarkVariant.starter.join("\n- ")}` : "",
|
|
874
995
|
benchmarkExamples ? `Benchmark example guide:\nGood:\n- ${benchmarkExamples.good.join("\n- ")}\nBad:\n- ${benchmarkExamples.bad.join("\n- ")}` : "",
|
|
875
996
|
slopFlags.length > 0 ? `Banned patterns that must be removed completely:\n- ${slopFlags.join("\n- ")}` : "",
|
|
876
997
|
issues.length > 0 ? `Design issues to correct in the rebuild:\n- ${issues.join("\n- ")}` : "",
|
package/src/workflow.js
CHANGED
|
@@ -3,6 +3,7 @@ import { computeImpactMap, summarizeImpactMap } from "./impact.js";
|
|
|
3
3
|
import { reviewTurn, challengeReceipt } from "./reviewer.js";
|
|
4
4
|
import {
|
|
5
5
|
buildFrontendExecutionContext,
|
|
6
|
+
getFrontendAcceptanceFailure,
|
|
6
7
|
buildFrontendRebuildPrompt,
|
|
7
8
|
buildFrontendRevisionPrompt,
|
|
8
9
|
captureFrontendScreenshot,
|
|
@@ -253,6 +254,14 @@ export async function runBuildWorkflow({
|
|
|
253
254
|
};
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
const acceptanceFailure = getFrontendAcceptanceFailure({
|
|
258
|
+
frontend: frontendCtx?.frontend || null,
|
|
259
|
+
slop: designSlop
|
|
260
|
+
});
|
|
261
|
+
if (acceptanceFailure) {
|
|
262
|
+
throw new Error(acceptanceFailure.reason);
|
|
263
|
+
}
|
|
264
|
+
|
|
256
265
|
// Update receipt with impact and review
|
|
257
266
|
const updates = {};
|
|
258
267
|
if (impact) updates.impact = impact;
|