Accept lens-only Scattermind build orders

This commit is contained in:
OpenClaw Bot
2026-05-26 23:55:05 +02:00
parent 35b3e6a47d
commit c13f16c0e7
3 changed files with 95 additions and 4 deletions
+56 -2
View File
@@ -464,9 +464,15 @@ function cleanDecisionContext(input = {}) {
const featureSet = objectFrom(input.featureSet);
const artifact = objectFrom(input.artifact || featureSet.artifact);
const conceptMap = objectFrom(input.conceptMap || featureSet.conceptMap || artifact.conceptMap);
const conceptMapLenses = objectFrom(conceptMap.lenses || input.lenses || featureSet.lenses);
const riskLens = objectFrom(conceptMapLenses.risk);
const structuredContext = objectFrom(input.context);
const sourceContext = firstObject(input.decisionContext, featureSet.decisionContext, artifact.decisionContext, conceptMap.decisionContext, structuredContext, conceptMap.context);
const textContextGuardrails = guardrailsFromContextText(typeof input.context === 'string' ? input.context : '');
const textContextGuardrails = guardrailsFromContextText([
typeof input.context === 'string' ? input.context : '',
riskLens.content || riskLens.text || '',
conceptMap.risk || conceptMap.whatNotToBuildYet || conceptMap.notYet || '',
].filter(Boolean).join('\n'));
return {
targetAudience: cleanText(input.targetAudience || featureSet.targetAudience || sourceContext.targetAudience || conceptMap.targetAudience || '', 180),
constraints: uniqueList([
@@ -494,7 +500,7 @@ function cleanContextText(value = '') {
}
function meaningfulTokens(text = '') {
const stop = new Set(['the', 'and', 'with', 'from', 'that', 'this', 'into', 'before', 'after', 'user', 'users', 'idea', 'ideas', 'build', 'first', 'avoid', 'without', 'more', 'full', 'proof', 'value', 'layer']);
const stop = new Set(['the', 'and', 'with', 'from', 'that', 'this', 'into', 'before', 'after', 'user', 'users', 'idea', 'ideas', 'build', 'first', 'avoid', 'without', 'more', 'full', 'proof', 'manual', 'value', 'layer']);
return [...new Set(String(text).toLowerCase().match(/[a-z0-9][a-z0-9-]{3,}/g) || [])].filter(token => !stop.has(token)).slice(0, 8);
}
@@ -566,9 +572,54 @@ function normalizeCandidateGroup(group = []) {
return normalizeOptionIds(options);
}
function sentenceFragments(text = '') {
return cleanMultiline(text, 4000)
.replace(/\s+(build first|start here|ship first|test manually|validate next|defer|probably noise|park|do not build yet|don't build yet)\s*:/gi, '\n$1:')
.split(/\n|;|\s+[•-]\s+/)
.map(part => part.trim())
.filter(Boolean);
}
function titleFromBuildOrderFragment(value = '') {
const cleaned = cleanText(value.replace(/^(build first|start here|ship first|test manually|validate next|defer|probably noise|park|do not build yet|don't build yet)\s*:\s*/i, ''), 220);
const first = cleaned.split(/\s[-–—:]\s|\.\s/)[0] || cleaned;
return cleanText(first, 120);
}
function laneFromBuildOrderLabel(fragment = '') {
if (/^(build first|start here|ship first)\s*:/i.test(fragment)) return 'do-first';
if (/^(test manually|validate next)\s*:/i.test(fragment)) return 'validate-next';
if (/^(defer|do not build yet|don't build yet)\s*:/i.test(fragment)) return 'defer';
if (/^(probably noise|park)\s*:/i.test(fragment)) return 'park';
return '';
}
function optionsFromBuildOrderText(text = '', sourceSection = 'concept-map.lenses.channel') {
const fragments = sentenceFragments(text);
const labelled = fragments.filter(fragment => laneFromBuildOrderLabel(fragment));
return labelled.map((fragment, index) => {
const lane = laneFromBuildOrderLabel(fragment);
return {
id: `build-order-${index + 1}`,
action: titleFromBuildOrderFragment(fragment),
why: fragment.replace(/^\s*[^:]{1,40}:\s*/, '').trim(),
evidence: /test|validate|proof|prove|signal|ask|show/i.test(fragment) ? fragment : (lane === 'do-first' ? 'Prove this first move manually before adding product machinery.' : ''),
suggestedLane: lane,
rankerHints: lane === 'do-first'
? { value: 8, effort: 2, confidence: 7, urgency: 7, risk: 2 }
: lane === 'validate-next'
? { value: 7, effort: 3, confidence: 6, urgency: 5, risk: 3 }
: undefined,
sourceSection,
};
}).filter(item => item.action);
}
function optionsFromBody(body = {}) {
const featureSet = objectFrom(body.featureSet);
const conceptMap = objectFrom(body.conceptMap || featureSet.conceptMap);
const conceptMapLenses = objectFrom(conceptMap.lenses || body.lenses || featureSet.lenses);
const buildOrderLens = objectFrom(conceptMapLenses.channel || conceptMapLenses.buildOrder || conceptMap.buildOrder);
const directCandidateGroup = compactCandidateGroup([
{ items: body.features, sourceSection: 'features' },
{ items: featureSet.features, sourceSection: 'feature-set.features' },
@@ -590,6 +641,9 @@ function optionsFromBody(body = {}) {
]);
const groupedCandidates = [...directCandidateGroup, ...conceptMapCandidateGroup];
if (groupedCandidates.length) return normalizeCandidateGroup(groupedCandidates);
const buildOrderText = buildOrderLens.content || buildOrderLens.text || (typeof conceptMap.buildOrder === 'string' ? conceptMap.buildOrder : '');
const buildOrderOptions = optionsFromBuildOrderText(buildOrderText);
if (buildOrderOptions.length) return normalizeCandidateGroup([{ items: buildOrderOptions, sourceSection: 'concept-map.lenses.channel' }]);
if (Array.isArray(body.options)) {
return normalizeOptionIds(body.options.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, 'option', 'options')).filter(item => item.title));
}