[object Object]

← back to York Reprice 2026 08

York CADENCE: daily launchd runner (read-only diff master vs live JS → reprice-drift/onboard-ready/disco, propose-only to pending-approval); installed com.steve.york-cadence 6:35am

7b9b7d0a9e09c74d682ed6d49056dbd8bd6e34d0 · 2026-07-12 08:06:06 -0700 · Steve Abrams

Files touched

Diff

commit 7b9b7d0a9e09c74d682ed6d49056dbd8bd6e34d0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Jul 12 08:06:06 2026 -0700

    York CADENCE: daily launchd runner (read-only diff master vs live JS → reprice-drift/onboard-ready/disco, propose-only to pending-approval); installed com.steve.york-cadence 6:35am
---
 .gitignore                           |  2 ++
 cadence/com.steve.york-cadence.plist | 13 ++++++++
 cadence/york-cadence.mjs             | 63 ++++++++++++++++++++++++++++++++++++
 3 files changed, 78 insertions(+)

diff --git a/.gitignore b/.gitignore
index 49bad97..071efc6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@ tmp/
 data/*.xlsx
 data/york-master.json
 data/img-cache/
+cadence/data/
+data/img-cache/
diff --git a/cadence/com.steve.york-cadence.plist b/cadence/com.steve.york-cadence.plist
new file mode 100644
index 0000000..8a54ddf
--- /dev/null
+++ b/cadence/com.steve.york-cadence.plist
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0"><dict>
+  <key>Label</key><string>com.steve.york-cadence</string>
+  <key>ProgramArguments</key><array>
+    <string>/opt/homebrew/bin/node</string>
+    <string>/Users/macstudio3/Projects/york-reprice-2026-08/cadence/york-cadence.mjs</string>
+  </array>
+  <key>StartCalendarInterval</key><dict><key>Hour</key><integer>6</integer><key>Minute</key><integer>35</integer></dict>
+  <key>StandardOutPath</key><string>/Users/macstudio3/Projects/york-reprice-2026-08/cadence/data/cadence.log</string>
+  <key>StandardErrorPath</key><string>/Users/macstudio3/Projects/york-reprice-2026-08/cadence/data/cadence.err</string>
+  <key>RunAtLoad</key><false/>
+</dict></plist>
diff --git a/cadence/york-cadence.mjs b/cadence/york-cadence.mjs
new file mode 100644
index 0000000..cd1704e
--- /dev/null
+++ b/cadence/york-cadence.mjs
@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+/**
+ * york-cadence.mjs — daily York line maintenance (READ-ONLY on Shopify; propose-not-auto).
+ * Diffs the loaded York master (dw_unified.york_master_aug2026) against LIVE Shopify (vendor
+ * "Jeffrey Stevens", keyed by dwc/custom.manufacturer_sku metafield) and surfaces 3 action buckets:
+ *   A. reprice-drift  — live ACTIVE + master Active/New + roll price != MAP (title-verified)
+ *   B. onboard-ready  — master 'New', not live, has a local image, settlement-keyword-clear
+ *   C. disco          — live ACTIVE + master Dead/Phasing
+ * Writes data/latest.json; on any actionable count, writes a pending-approval memo + CNCP card.
+ * NEVER writes to Shopify. Gated actions stay Steve's (existing york-reprice/create/disco tooling).
+ */
+import fs from 'node:fs'; import { execSync } from 'node:child_process';
+const ROOT=process.env.HOME+'/Projects/york-reprice-2026-08';
+const SHOP='designer-laboratory-sandbox.myshopify.com',VER='2024-10';
+const TOKEN=(fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1]?.trim();
+const URL=`https://${SHOP}/admin/api/${VER}/graphql.json`;
+const PG='PGPASSWORD=DW2024SecurePass /opt/homebrew/opt/postgresql@14/bin/psql -h 127.0.0.1 -U dw_admin -d dw_unified -tA -F\'\x1f\' -c';
+const q=sql=>execSync(`${PG} ${JSON.stringify(sql)}`,{encoding:'utf8'}).trim();
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const gql=async(query,v)=>{for(let a=0;a<8;a++){let j;try{const r=await fetch(URL,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query,variables:v})});j=await r.json();}catch(e){await sleep(1500*(a+1));continue;}if(j.errors){if(JSON.stringify(j.errors).includes('THROTTLED')){await sleep(2000*(a+1));continue;}throw new Error(JSON.stringify(j.errors));}const t=j.extensions?.cost?.throttleStatus;if(t&&t.currentlyAvailable<300)await sleep(1500);return j.data;}throw new Error('retries');};
+const norm=s=>String(s||'').toUpperCase().trim();
+const words=s=>new Set(String(s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').split(/\s+/).filter(w=>w.length>2));
+const share=(a,b)=>{const A=words(a),B=words(b);for(const w of A)if(B.has(w))return true;return false;};
+
+// 1. master: mfr_sku -> {price,status,pattern,color}
+const M=new Map();
+for(const line of q(`select concat_ws(chr(31), upper(trim(export_sku)), coalesce(us_map,round(us_msrp*0.75,2)), sku_status, pattern_name, colorway) from york_master_aug2026 where export_sku is not null`).split('\n').filter(Boolean)){
+  const [s,p,st,pat,col]=line.split(String.fromCharCode(31)); if(!M.has(s)) M.set(s,{price:parseFloat(p),status:st,pattern:pat,color:col});
+}
+// onboard-ready set from DB (master New, has york_catalog image, keyword-clear) — count only
+let onboardReady=0; try{ onboardReady=parseInt(q(`
+ select count(*) from (select distinct on (upper(trim(export_sku))) upper(trim(export_sku)) s, pattern_name,product_name,book_name from york_master_aug2026 where sku_status='New' and export_sku is not null) n
+ join york_catalog c on upper(trim(c.mfr_sku))=n.s
+ where c.image_url is not null and c.image_url<>'' and lower(concat_ws(' ',n.pattern_name,n.product_name,n.book_name)) !~ 'banana|grape|bird|butterfly|palm|frond|leaf|foliage|botanical|floral|flower|tropical|jungle|vine|fern|bloom|garden|blossom'`)); }catch(e){}
+
+// 2. live Jeffrey Stevens
+let cur=null, reprice=[], disco=[], liveMfr=new Set();
+for(let pg=0;pg<90;pg++){
+  const d=(await gql(`query($c:String){products(first:60,after:$c,query:"vendor:'Jeffrey Stevens'"){pageInfo{hasNextPage endCursor} nodes{status title dwc:metafield(namespace:"dwc",key:"manufacturer_sku"){value} cust:metafield(namespace:"custom",key:"manufacturer_sku"){value} variants(first:20){nodes{id sku price title}}}}}`,{c:cur}))?.products; if(!d)break;
+  for(const p of d.nodes){const mfr=norm(p.dwc?.value||p.cust?.value); if(!mfr)continue; liveMfr.add(mfr);
+    const m=M.get(mfr); if(!m)continue;
+    const rolls=p.variants.nodes.filter(v=>!/sample|memo|-sample$/i.test((v.sku||'')+(v.title||''))); if(!rolls.length)continue;
+    const roll=rolls.reduce((a,b)=>parseFloat(b.price)>parseFloat(a.price)?b:a); const price=parseFloat(roll.price);
+    if(p.status==='ACTIVE'){
+      if(['Dead','Phasing'].includes(m.status)) disco.push({mfr,title:p.title,status:m.status});
+      else if(['Active','New'].includes(m.status) && m.price>0 && Math.abs(price-m.price)>=0.01 && share(p.title,m.pattern))
+        reprice.push({mfr,title:p.title,cur:price,map:m.price});
+    }
+  }
+  if(!d.pageInfo.hasNextPage)break; cur=d.pageInfo.endCursor;
+}
+const out={ts:new Date().toISOString(),live_js:liveMfr.size,
+  reprice_drift:reprice.length, onboard_ready:onboardReady, disco:disco.length,
+  reprice_sample:reprice.slice(0,10), disco_sample:disco.slice(0,10)};
+fs.mkdirSync(ROOT+'/cadence/data',{recursive:true});
+fs.writeFileSync(ROOT+'/cadence/data/latest.json',JSON.stringify(out,null,2));
+const actionable=reprice.length+onboardReady+disco.length;
+console.log(`york-cadence ${out.ts} | live JS ${liveMfr.size} | reprice-drift ${reprice.length} | onboard-ready ${onboardReady} | disco ${disco.length}`);
+if(actionable>0){
+  const memo=`# York cadence — ${actionable} actionable (${out.ts.slice(0,10)})\n\n- **Reprice drift**: ${reprice.length} live ACTIVE below/above MAP → run york-pl-reprice / bridge-reprice (gated).\n- **Onboard-ready**: ${onboardReady} master 'New' with image + settlement-clear not yet live → run york-new-create (gated).\n- **Disco**: ${disco.length} live ACTIVE now Dead/Phasing → archive (gated).\n\nData: cadence/data/latest.json. All customer-facing writes stay gated — this is a surface, not an auto-apply.\n\n- [ ] APPROVE which action to run\n`;
+  fs.writeFileSync(process.env.HOME+'/.claude/yolo-queue/pending-approval/york-cadence-actions.md',memo);
+  try{execSync(`curl -s -X POST http://127.0.0.1:3333/api/parking-lot -H 'Content-Type: application/json' -d ${JSON.stringify(JSON.stringify({project:'designerwallcoverings',title:`York cadence: ${actionable} actionable`,note:`reprice ${reprice.length} / onboard ${onboardReady} / disco ${disco.length}`}))}`,{stdio:'ignore'});}catch(e){}
+}

← 3725ed7 Fix collectionAddProducts mutation (var name) + treat smart-  ·  back to York Reprice 2026 08  ·  auto-save: 2026-07-12T08:17:05 (1 files) — clear-cadence-4.m 989c0ef →