Refocus Ranker as Prioritix idea-layer sorting

This commit is contained in:
OpenClaw Bot
2026-05-31 22:51:08 +02:00
parent 577fd081d6
commit a348acf6ef
5 changed files with 138 additions and 47 deletions
+62 -15
View File
@@ -448,19 +448,70 @@ function hits(text, words) {
return words.reduce((count, word) => count + (lower.includes(word) ? 1 : 0), 0);
}
const sortableTextHeadings = /^(features?|functionality|functions?|options?|variants?|claims?|promises?|risks?|assumptions?|experiments?|tests?|proof steps?|validation questions?|questions?|feedback themes?|feedback|audiences?|segments?|use cases?|possible next moves?|next moves?|moves?|candidates?|things to sort|sortable layer|xyz)\s*:\s*$/i;
const contextTextHeadings = /^(idea|context|constraints?|instructions?|instruction|notes?|non[- ]?goals?|avoid|do not sort|don't sort|dont sort|background|goal|goals?)\s*:\s*$/i;
const inlineSortablePrefix = /^(features?|functionality|functions?|options?|variants?|claims?|promises?|risks?|assumptions?|experiments?|tests?|proof steps?|validation questions?|questions?|feedback themes?|feedback|audiences?|segments?|use cases?|possible next moves?|next moves?|moves?|candidates?|things to sort|sortable layer|xyz)\s*:\s*/i;
function normalizeSortableTextLine(line = '') {
return cleanText(line, 900)
.replace(/^\s*(?:[-*•]|\d+[.)]|[a-z][.)])\s*/i, '')
.replace(/^\s*(?:rough\s+idea|idea|option|feature|function|variant|claim|risk|experiment|test|question|feedback|audience|segment|move)\s+[a-z0-9]+\s*[:.)-]\s*/i, '')
.replace(/^\s*(maybe|possibly|also|plus|and|or|then|later|eventually|build|add|include|some kind of|kind of)\b\s*/i, '')
.replace(/\?+$/i, '')
.trim();
}
function splitOptionTitleDescription(line = '') {
const normalized = normalizeSortableTextLine(line);
const [rawTitle, ...rest] = normalized.split(/\s[-–—]\s/);
return {
title: cleanText(rawTitle || normalized || line, 140),
description: cleanText(rest.join(' — '), 420),
};
}
function parseOptionsFromText(value, sourceSection = 'optionsText') {
const text = cleanMultiline(value, 12000);
const candidateText = /\b(maybe|possibly|options?|features?|ideas?|next moves?|functionality|backlog)\b[:\s]/i.test(text)
? text.replace(/^[\s\S]*?\b(maybe|possibly|options?|features?|ideas?|next moves?|functionality|backlog)\b[:\s]*/i, '')
const lines = text.split('\n').map(line => line.trim()).filter(Boolean);
const hasExplicitSortableHeading = lines.some(line => sortableTextHeadings.test(line));
const extracted = [];
let activeSection = hasExplicitSortableHeading ? 'context' : 'sortable';
for (const rawLine of lines) {
const line = rawLine.trim();
if (sortableTextHeadings.test(line)) {
activeSection = 'sortable';
continue;
}
if (contextTextHeadings.test(line)) {
activeSection = 'context';
continue;
}
const inlineSortable = line.match(inlineSortablePrefix);
if (inlineSortable) {
activeSection = 'sortable';
const rest = line.replace(inlineSortablePrefix, '').trim();
if (rest) extracted.push(rest);
continue;
}
if (activeSection !== 'sortable') continue;
if (/^(constraints?|instructions?|context|notes?|goal|goals?)\s*:/i.test(line)) continue;
extracted.push(line);
}
const looseText = !hasExplicitSortableHeading && /\b(maybe|possibly|options?|features?|functionality|variants?|claims?|risks?|experiments?|validation questions?|feedback themes?|audiences?|next moves?)\b[:\s]/i.test(text)
? text.replace(/^[\s\S]*?\b(maybe|possibly|options?|features?|functionality|variants?|claims?|risks?|experiments?|validation questions?|feedback themes?|audiences?|next moves?)\b[:\s]*/i, '')
: text;
const cleanedLines = candidateText.split('\n').map(line => line.replace(/^\s*[-*•\d.)]+\s*/, '').trim()).filter(Boolean);
const candidateText = (hasExplicitSortableHeading ? extracted.join('\n').trim() : '') || looseText
.replace(/\b(maybe|possibly|options?|features?|functionality|variants?|claims?|risks?|experiments?|validation questions?|feedback themes?|audiences?|next moves?)\s*:/gi, '\n')
.trim();
const cleanedLines = candidateText.split('\n').map(normalizeSortableTextLine).filter(Boolean);
const sentenceList = candidateText
.replace(/\b(maybe|possibly|options?|features?|ideas?|next moves?|functionality|backlog)\s*:/gi, '\n')
.split(/\n|;|\|/)
.map(part => part.replace(/^\s*[-*•\d.)]+\s*/, '').trim())
.map(normalizeSortableTextLine)
.filter(Boolean);
const commaList = sentenceList.length === 1
? sentenceList[0].split(/,|\s+and\s+|[.!?]\s+/i).map(part => part.trim()).filter(Boolean)
? sentenceList[0].split(/,|\s+and\s+|[.!?]\s+/i).map(normalizeSortableTextLine).filter(Boolean)
: [];
const optionLines = cleanedLines.length >= 2
? cleanedLines
@@ -468,20 +519,16 @@ function parseOptionsFromText(value, sourceSection = 'optionsText') {
? sentenceList
: commaList.length >= 2
? commaList
: candidateText.split(/[;|]/).map(part => part.trim()).filter(Boolean);
: candidateText.split(/[;|]/).map(normalizeSortableTextLine).filter(Boolean);
return optionLines.slice(0, 24).map((line, index) => {
const normalized = line
.replace(/^\s*(maybe|possibly|also|plus|and|or|then|later|eventually|build|add|include|some kind of|kind of)\b\s*/i, '')
.replace(/\?+$/, '')
.trim();
const [rawTitle, ...rest] = normalized.split(/\s*[-–—:]\s+/);
const { title, description } = splitOptionTitleDescription(line);
return {
id: `option-${index + 1}`,
title: cleanText(rawTitle || normalized || line, 140),
description: cleanText(rest.join(' — '), 420),
title,
description,
provenance: { sourceSection },
};
}).filter(item => item.title && !/^(i('| a)?m|i am|we are|i only|want|need)\b/i.test(item.title));
}).filter(item => item.title && !/^(i('| a)?m|i am|we are|i only|want|need|rank the|sort the|do not|don't|dont)\b/i.test(item.title));
}
function objectFrom(value) {