From 7ed035af82f67fbc46c6449b47c4b6598b435632 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Wed, 27 May 2026 18:50:34 +0200 Subject: [PATCH] Carry Scattermind proof lens into ranking --- scripts/check-rank-feedback.mjs | 7 ++++- server.js | 50 ++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/scripts/check-rank-feedback.mjs b/scripts/check-rank-feedback.mjs index 323fc78..ea2955c 100644 --- a/scripts/check-rank-feedback.mjs +++ b/scripts/check-rank-feedback.mjs @@ -775,6 +775,7 @@ try { lenses: { audience: 'First buyers are local neighbors who already attend small food events and want a low-pressure dinner plan.', risk: 'Avoid accounts and saved workspaces before anyone pays. Do not build subscriptions or an app dashboard yet.', + question: 'Proof steps: Ask ten neighbors if they would pay for the first dinner before building software. Run one manual invite script and collect yes/no replies.', channel: 'Build first: One manual supper-club offer page - collect three real yes/no replies before building machinery. Test manually: Concierge invitation script - ask ten neighbors if they would pay for the first dinner. Defer: Pretty event calendar after the first paid table. Probably noise: Saved member workspace with accounts, billing, subscriptions, and team dashboard.', }, }), @@ -784,10 +785,14 @@ try { assert.equal(scattermindPaidShape.input.provenance.artifactId, 'SM-PAID1'); assert.equal(scattermindPaidShape.input.provenance.snapshotTitle, 'Neighborhood Supper Club'); assert.match(scattermindPaidShape.input.provenance.originalPrompt, /supper club/); - assert.equal(scattermindPaidShape.input.optionCount, 4); + assert.equal(scattermindPaidShape.input.optionCount, 6); assert.equal(scattermindPaidShape.ranked[0].id, 'build-order-1'); assert.equal(scattermindPaidShape.ranked[0].provenance.sourceId, 'concept-map.lenses.channel#1'); assert.equal(scattermindPaidShape.ranked[0].provenance.sourceTitle, 'Build Order'); + assert.equal(scattermindPaidShape.ranked.find(item => item.id === 'proof-step-1').lane.id, 'test'); + assert.equal(scattermindPaidShape.ranked.find(item => item.id === 'proof-step-1').provenance.sourceSection, 'concept-map.lenses.question'); + assert.match(scattermindPaidShape.ranked.find(item => item.id === 'proof-step-1').factors.evidenceNeeded, /Ask ten neighbors/); + assert.equal(scattermindPaidShape.handoff.itemTrace.find(item => item.id === 'proof-step-1').sourceTitle, 'Proof Steps'); assert.match(scattermindPaidShape.handoff.itemTrace.find(item => item.id === 'build-order-1').sourceQuote, /One manual supper-club offer page/); assert.equal(scattermindPaidShape.ranked.find(item => item.id === 'build-order-4').lane.id, 'park'); assert.ok(scattermindPaidShape.input.decisionContext.nonGoals.includes('Avoid accounts and saved workspaces before anyone pays')); diff --git a/server.js b/server.js index d30c242..91673f1 100644 --- a/server.js +++ b/server.js @@ -1059,6 +1059,41 @@ function optionsFromBuildOrderText(text = '', sourceSection = 'concept-map.lense }).filter(item => item.action); } +function proofStepFragments(text = '') { + return cleanMultiline(text, 2600) + .replace(/\b(proof steps?|test manually|validate|evidence to collect|next proof)\s*:/gi, '\n') + .split(/\n|;|\s+[•-]\s+|\.\s+/) + .map(part => part.replace(/^\s*[-*•\d.)]+\s*/, '').trim()) + .filter(Boolean) + .filter(part => /\b(run|ask|show|send|test|validate|prototype|mock|observe|interview|collect|measure|prove|disprove|manual|concierge|offer|script|reply|signal)\b/i.test(part)) + .filter(part => !/^\s*(avoid|no|do not|don't|defer|probably noise|park|hold for later)\b/i.test(part)) + .slice(0, 3); +} + +function proofTitleFromFragment(fragment = '') { + const cleaned = cleanText(fragment + .replace(/^(run|ask|show|send|test|validate|prototype|mock|observe|interview|collect|measure)\b\s*/i, match => match.trim()[0].toUpperCase() + match.trim().slice(1).toLowerCase() + ' ') + .replace(/\s+before\s+.*$/i, '') + .replace(/\s+and\s+record\s+.*$/i, ''), 180); + return cleanText(cleaned.split(/\s[-–—:]\s/)[0] || cleaned, 120); +} + +function optionsFromProofLensText(text = '', sourceSection = 'concept-map.lenses.question', sourceTitle = 'Proof Steps') { + return proofStepFragments(text).map((fragment, index) => ({ + id: `proof-step-${index + 1}`, + action: proofTitleFromFragment(fragment), + why: 'Scattermind named this as proof to collect before promoting more build surface.', + evidence: fragment, + validationSteps: [fragment], + suggestedLane: 'validate-next', + rankerHints: { value: 7, effort: 2, confidence: 6, urgency: 6, risk: 3 }, + sourceSection, + sourceItemId: `${sourceSection}#${index + 1}`, + sourceTitle, + sourceExcerpt: fragment, + })).filter(item => item.action); +} + function laneFromActionThread(text = '') { if (/\b(probably noise|set aside|park|parking lot|do not build|don't build|not worth|distraction)\b/i.test(text)) return 'park'; if (/\b(defer|not yet|later|hold for later|after proof|wait until)\b/i.test(text)) return 'defer'; @@ -1283,7 +1318,20 @@ function optionsFromBody(body = {}) { 140 ); const buildOrderOptions = optionsFromBuildOrderText(buildOrderText, 'concept-map.lenses.channel', buildOrderSourceTitle); - if (buildOrderOptions.length) return normalizeCandidateGroup([{ items: buildOrderOptions, sourceSection: 'concept-map.lenses.channel' }]); + if (buildOrderOptions.length) { + const proofLens = objectFrom(conceptMapLenses.question || conceptMapLenses.proof || conceptMapLenses.validation || conceptMapLenses.evidence); + const proofLensText = lensContent(conceptMapLenses.question) + || lensContent(conceptMapLenses.proof) + || lensContent(conceptMapLenses.validation) + || lensContent(conceptMapLenses.evidence) + || ''; + const proofSourceTitle = cleanText(proofLens.title || 'Proof Steps', 140); + const proofOptions = optionsFromProofLensText(proofLensText, 'concept-map.lenses.question', proofSourceTitle); + return normalizeCandidateGroup([ + { items: buildOrderOptions, sourceSection: 'concept-map.lenses.channel' }, + ...(proofOptions.length ? [{ items: proofOptions, sourceSection: 'concept-map.lenses.question', defaultLane: 'validate-next' }] : []), + ]); + } const actionThreadSource = firstArraySource([ { items: conceptMap.threads_to_hold || conceptMap.threadsToHold || conceptMap.actionThreads || conceptMap.action_threads, sourceSection: 'concept-map.threadsToHold' }, { items: snapshot.threads_to_hold || snapshot.threadsToHold || snapshot.actionThreads || snapshot.action_threads, sourceSection: 'snapshot.threadsToHold' },