← back to Wallco Ai
yolo-queue: SG-1..SG-4 ✓ attorney narrowings (moth/bird/scatter/leaf word-boundaries)
26ab5d30f17821e44967e10adf1d8b40007cd06e · 2026-05-25 20:26:21 -0700 · Steve Abrams
Files touched
M .yolo-queue.mdM scripts/settlement-gate.js
Diff
commit 26ab5d30f17821e44967e10adf1d8b40007cd06e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon May 25 20:26:21 2026 -0700
yolo-queue: SG-1..SG-4 ✓ attorney narrowings (moth/bird/scatter/leaf word-boundaries)
---
.yolo-queue.md | 15 +++++++++++----
scripts/settlement-gate.js | 40 ++++++++++++++++++++++++++++++----------
2 files changed, 41 insertions(+), 14 deletions(-)
diff --git a/.yolo-queue.md b/.yolo-queue.md
index bd27040..eaedb2d 100644
--- a/.yolo-queue.md
+++ b/.yolo-queue.md
@@ -80,27 +80,34 @@ never silently pause overnight.
## Settlement gate narrowings (attorney follow-ups)
-- [ ] **SG-1** Tighten `PART_B` in `scripts/settlement-gate.js`. Drop
+- [x] **SG-1** Tighten `PART_B` in `scripts/settlement-gate.js`. Drop
`'moth'` — the binding says "butterflies (any species)" but moths
are Lepidoptera-adjacent, not butterflies. Defendant-favorable read =
remove. Add inline `// 2026-05-25 attorney narrowing` comment.
-- [ ] **SG-2** Fix `'bird'` false positives. Currently substring-matches
+- [x] **SG-2** Fix `'bird'` false positives. Currently substring-matches
`bird of paradise` (plant), `birdcage`, `blackbird`. Use a word-boundary
regex check instead: `/\b(?:bird|birds)\b(?!\s+of\s+paradise)/i`. Keep
`parrot` / `toucan` as-is (less false-positive risk).
-- [ ] **SG-3** Untangle `scatter`/`tossed` overlap between
+- [x] **SG-3** Untangle `scatter`/`tossed` overlap between
`PART_A_DIRECTIONAL` and `PART_A_OPEN_SPACE`. Same token currently
trips BOTH A1 and A2 from a single mention. Move `'scatter'` and
`'tossed'` to ONE list only (recommend OPEN_SPACE since they describe
layout, not direction).
-- [ ] **SG-4** Word boundaries on `PART_A_LEAVES`. `'leaf'` substring
+- [x] **SG-4** Word boundaries on `PART_A_LEAVES`. `'leaf'` substring
matches `leaflet`, `leafy`, `leaflike`. Use `\bleaf(?:s|ves)?\b` regex.
Convert each entry in PART_A_LEAVES into a regex with word boundaries
and change `has()` helper to use regex.test instead of includes.
+ > NOTE 2026-05-25 ✓ SG-1..4 bundled in single ship. Split PART_B into
+ > _STR (substring) + _RE (regex) lists, added PART_A_LEAVES_RE regex
+ > word-boundary variant + hasRe() helper. 6-case regression test all
+ > pass: 'monstera leaflet' (SG-4), 'bird of paradise' (SG-2), 'moth'
+ > (SG-1) all correctly avoid false-positive Part B; legit 'bird' +
+ > full Part A still BLOCKs. SG-3: scatter/tossed only in OPEN_SPACE.
+
- [ ] **SG-5** Add a single test file `scripts/settlement-gate.test.js`
with at least 10 cases: 3 legit BLOCKs (real leaf + open + multi-color
+ Part B), 3 OK (negation-suffix prompts that previously over-blocked),
diff --git a/scripts/settlement-gate.js b/scripts/settlement-gate.js
index c0f09b2..aaffcf2 100644
--- a/scripts/settlement-gate.js
+++ b/scripts/settlement-gate.js
@@ -15,11 +15,18 @@
*/
'use strict';
-const PART_A_LEAVES = [
- 'palm', 'frond', 'banana leaf', 'monstera', 'leaves', 'leaf', 'foliage', 'jungle',
+// SG-4 (2026-05-25 attorney narrowing): leaf tokens now use word
+// boundaries via regex so 'leaflet', 'leafy', 'leaflike' don't trip.
+// `\bword\b` for each entry; plural/possessive permitted.
+const PART_A_LEAVES_RE = [
+ /\bpalm\b/i, /\bfronds?\b/i, /\bbanana\s+leaf\b/i, /\bmonstera\b/i,
+ /\bleaf\b/i, /\bleaves\b/i, /\bfoliage\b/i, /\bjungle\b/i,
];
+// SG-3 (2026-05-25): 'scatter'/'tossed' moved out of PART_A_DIRECTIONAL.
+// They describe layout, not directional variation — keeping them in
+// OPEN_SPACE only so a single token doesn't trip both A1 and A2.
const PART_A_DIRECTIONAL = [
- 'directional', 'varied angle', 'tossed', 'scatter', 'rotated', 'multi-angle',
+ 'directional', 'varied angle', 'rotated', 'multi-angle',
'overlapping', 'turning leaves', 'fanning',
];
const PART_A_OPEN_SPACE = [
@@ -30,9 +37,17 @@ const PART_A_MULTICOLOR_NEG = [
'silhouette', 'tonal',
];
-const PART_B = [
- 'banana', 'banana pod', 'grape', 'bird', 'birds', 'parrot', 'toucan',
- 'butterfly', 'butterflies', 'moth',
+// SG-1 + SG-2 (2026-05-25 attorney narrowing):
+// - Dropped 'moth' — binding says 'butterflies (any species)' which
+// does not extend to moths (different family within Lepidoptera).
+// - 'bird' / 'birds' now use word boundaries + exclude 'bird of
+// paradise' (the plant), 'birdcage', 'blackbird', 'mockingbird'.
+const PART_B_STR = [
+ 'banana', 'banana pod', 'grape', 'parrot', 'toucan',
+ 'butterfly', 'butterflies',
+];
+const PART_B_RE = [
+ /\bbirds?\b(?!\s+of\s+paradise)/i, // bird/birds, but NOT 'bird of paradise' (plant)
];
const ACCEPTABLE = [
@@ -43,6 +58,8 @@ const ACCEPTABLE = [
];
const has = (text, list) => list.some(t => text.includes(t));
+// SG-4: regex variant of `has` for word-boundary checks.
+const hasRe = (text, regexList) => regexList.some(re => re.test(text));
// Strip negative-prompt context per settlement-part-b SKILL.md lines 58-60:
// "presence in negative-prompt block does NOT count as positive."
@@ -68,7 +85,8 @@ function checkPrompt(prompt) {
const text = stripNegations(raw).toLowerCase();
// Part A score
- const a1 = has(text, PART_A_LEAVES) && has(text, PART_A_DIRECTIONAL);
+ // SG-4: PART_A_LEAVES now uses regex word boundaries.
+ const a1 = hasRe(text, PART_A_LEAVES_RE) && has(text, PART_A_DIRECTIONAL);
const a2 = has(text, PART_A_OPEN_SPACE);
const a3Singlecolor = has(text, PART_A_MULTICOLOR_NEG);
// Multi-color is the default assumption unless the prompt explicitly says
@@ -77,8 +95,10 @@ function checkPrompt(prompt) {
const partA = a1 && a2 && a3;
- // Part B score
- const partBFound = PART_B.filter(el => text.includes(el));
+ // Part B score — SG-1 (no moth) + SG-2 (bird word-boundary + not 'bird of paradise').
+ const partBStrFound = PART_B_STR.filter(el => text.includes(el));
+ const partBReFound = PART_B_RE.filter(re => re.test(text)).map(re => String(re).match(/\\b([a-z]+)/)?.[1] || re.source);
+ const partBFound = [...partBStrFound, ...partBReFound];
const partB = partBFound.length > 0;
// Acceptable cues
@@ -92,7 +112,7 @@ function checkPrompt(prompt) {
if (partA && partB) {
verdict = 'BLOCK';
- reason = `Settlement violation: Part A (directional ${PART_A_LEAVES.find(l => text.includes(l)) || 'leaves'} + open space + multi-color) AND Part B (${partBFound.join(', ')}) both present.`;
+ reason = `Settlement violation: Part A (directional leaves + open space + multi-color) AND Part B (${partBFound.join(', ')}) both present.`;
rewrite = `Drop one of:
- the directional-leaf scatter (render on cane lattice / trellis / geometric substrate instead), OR
- the open-space / tossed composition (use edge-to-edge coverage), OR
← 9228ce8 drunk-animals: tiki/cognac/champagne/smoking-bubble + textur
·
back to Wallco Ai
·
yolo-queue: escalation policy — debate-team-fast on every it 2bde4b7 →