Harden Scattermind summary handoff provenance
This commit is contained in:
@@ -594,6 +594,7 @@ function cleanProvenance(input = {}) {
|
||||
const conceptMap = objectFrom(input.conceptMap || input.concept_map || featureSet.conceptMap || featureSet.concept_map || artifact.conceptMap || artifact.concept_map);
|
||||
const snapshot = objectFrom(input.snapshot || featureSet.snapshot || artifact.snapshot || conceptMap.snapshot);
|
||||
const source = objectFrom(input.source || featureSet.source || artifact.source);
|
||||
const sourceSummary = input.sourceSummary || input.source_summary || input.opening_reflection || input.restated_idea || artifact.sourceSummary || artifact.source_summary || artifact.opening_reflection || snapshot.sourceSummary || snapshot.source_summary || snapshot.restated_idea || conceptMap.sourceSummary || conceptMap.source_summary || conceptMap.opening_reflection || conceptMap.restated_idea || '';
|
||||
return {
|
||||
schema: cleanText(input.schema || featureSet.schema || artifact.schema || input.type || 'prioritix-feature-set-v1', 80),
|
||||
source: cleanText(input.sourceName || input.source_name || featureSet.sourceName || featureSet.source_name || source.name || artifact.sourceName || artifact.source_name || 'Scattermind', 80),
|
||||
@@ -601,6 +602,7 @@ function cleanProvenance(input = {}) {
|
||||
snapshotTitle: cleanText(input.snapshotTitle || input.snapshot_title || input.working_name || input.workingName || artifact.snapshotTitle || artifact.snapshot_title || snapshot.title || snapshot.name || conceptMap.snapshotTitle || conceptMap.snapshot_title || conceptMap.working_name || conceptMap.workingName || input.ideaTitle || input.idea_title || '', 160),
|
||||
conceptMapId: cleanText(input.conceptMapId || input.concept_map_id || artifact.conceptMapId || artifact.concept_map_id || conceptMap.id || conceptMap.artifactId || conceptMap.artifact_id || input.referenceCode || input.reference_code || conceptMap.referenceCode || conceptMap.reference_code || '', 120),
|
||||
originalPrompt: cleanMultiline(input.originalPrompt || input.original_prompt || input.initialPrompt || input.initial_prompt || input.ideaText || input.idea_text || input.prompt || artifact.originalPrompt || artifact.original_prompt || source.originalPrompt || source.original_prompt || snapshot.originalPrompt || snapshot.original_prompt || snapshot.prompt || conceptMap.originalPrompt || conceptMap.original_prompt || conceptMap.ideaText || conceptMap.idea_text || input.idea || '', 1200),
|
||||
sourceSummary: cleanMultiline(sourceSummary, 1200),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -802,20 +804,20 @@ function normalizeCandidateGroup(group = []) {
|
||||
|
||||
function sentenceFragments(text = '') {
|
||||
return cleanMultiline(text, 4000)
|
||||
.replace(/\s+(build first|start here|ship first|continue first|make tangible first|make tangible|try next|evidence next|learn next|test manually|validate next|hold for later|not yet|defer|set aside|out of scope|probably noise|park|do not build yet|don't build yet)\s*:/gi, '\n$1:')
|
||||
.replace(/\s+(build first|start here|ship first|first week|week one|first-week build order|continue first|make tangible first|make tangible|try next|evidence next|learn next|test manually|validate next|hold for later|not yet|defer|set aside|out of scope|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|continue first|make tangible first|make tangible|try next|evidence next|learn next|test manually|validate next|hold for later|not yet|defer|set aside|out of scope|probably noise|park|do not build yet|don't build yet)\s*:\s*/i, ''), 220);
|
||||
const cleaned = cleanText(value.replace(/^(build first|start here|ship first|first week|week one|first-week build order|continue first|make tangible first|make tangible|try next|evidence next|learn next|test manually|validate next|hold for later|not yet|defer|set aside|out of scope|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|continue first|make tangible first|make tangible)\s*:/i.test(fragment)) return 'do-first';
|
||||
if (/^(build first|start here|ship first|first week|week one|first-week build order|continue first|make tangible first|make tangible)\s*:/i.test(fragment)) return 'do-first';
|
||||
if (/^(try next|evidence next|learn next|test manually|validate next)\s*:/i.test(fragment)) return 'validate-next';
|
||||
if (/^(hold for later|not yet|defer|do not build yet|don't build yet)\s*:/i.test(fragment)) return 'defer';
|
||||
if (/^(set aside|out of scope|probably noise|park)\s*:/i.test(fragment)) return 'park';
|
||||
@@ -1152,6 +1154,7 @@ function createDecisionBrief({ idea, context, mode, ranked, provenance, decision
|
||||
snapshotTitle: provenance.snapshotTitle,
|
||||
conceptMapId: provenance.conceptMapId,
|
||||
originalPromptExcerpt: cleanText(provenance.originalPrompt, 260),
|
||||
sourceSummaryExcerpt: cleanText(provenance.sourceSummary, 260),
|
||||
} : null,
|
||||
expertReflections: [
|
||||
{
|
||||
@@ -1226,7 +1229,7 @@ function handoffReadinessFor({ ranked = [], provenance = {}, warnings = [], expe
|
||||
const uniqueWarnings = [...new Set(warnings)];
|
||||
const activeItems = ranked.filter(item => ['do', 'test'].includes(item.lane?.id));
|
||||
const guardrailWarnings = uniqueWarnings.filter(item => /^active item .* conflicts with source non-goals/i.test(item));
|
||||
const sourceWarnings = uniqueWarnings.filter(item => /^missing (source section|original prompt provenance)/i.test(item));
|
||||
const sourceWarnings = uniqueWarnings.filter(item => /^missing (source section|source context provenance)/i.test(item));
|
||||
const evidenceWarnings = uniqueWarnings.filter(item => /^missing evidence needed for active item/i.test(item));
|
||||
const duplicateWarnings = uniqueWarnings.filter(item => /^duplicate source id/i.test(item));
|
||||
const missingArtifact = uniqueWarnings.includes('missing source artifact id');
|
||||
@@ -1238,7 +1241,7 @@ function handoffReadinessFor({ ranked = [], provenance = {}, warnings = [], expe
|
||||
const nextChecks = [];
|
||||
|
||||
if (guardrailWarnings.length) nextChecks.push('Resolve source non-goal conflicts before treating any active item as build-ready.');
|
||||
if (expectsSourceTrace && sourceWarnings.length) nextChecks.push('Attach source section / prompt provenance from the Scattermind artifact.');
|
||||
if (expectsSourceTrace && sourceWarnings.length) nextChecks.push('Attach source section / prompt or summary provenance from the Scattermind artifact.');
|
||||
if (expectsSourceTrace && evidenceWarnings.length) nextChecks.push('Add evidenceNeeded to every Do first / Validate next candidate before handoff.');
|
||||
if (missingArtifact) nextChecks.push(expectsSourceTrace ? 'Attach the Scattermind artifact id so the build order can be traced back.' : 'Attach a source artifact id if this came from Scattermind rather than a plain paste.');
|
||||
if (duplicateWarnings.length) nextChecks.push('Review normalized duplicate IDs before downstream tools store references.');
|
||||
@@ -1272,7 +1275,7 @@ function handoffReadinessFor({ ranked = [], provenance = {}, warnings = [], expe
|
||||
nextChecks: nextChecks.slice(0, 5),
|
||||
activeItemCount: activeItems.length,
|
||||
warningCount: uniqueWarnings.length,
|
||||
sourceComplete: Boolean(!expectsSourceTrace || (provenance?.originalPrompt && activeItems.every(item => item.provenance?.sourceSection))),
|
||||
sourceComplete: Boolean(!expectsSourceTrace || ((provenance?.originalPrompt || provenance?.sourceSummary) && activeItems.every(item => item.provenance?.sourceSection))),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1288,6 +1291,11 @@ function copyableHandoffText({ ranked = [], provenance = {}, decisionContext = {
|
||||
provenance?.snapshotTitle,
|
||||
provenance?.artifactId,
|
||||
].filter(Boolean).join(' · ');
|
||||
const sourceContextLine = provenance?.originalPrompt
|
||||
? `Original prompt: ${cleanText(provenance.originalPrompt, 240)}`
|
||||
: provenance?.sourceSummary
|
||||
? `Source summary: ${cleanText(provenance.sourceSummary, 240)}`
|
||||
: '';
|
||||
const contextLines = [
|
||||
decisionContext?.targetAudience ? `Audience: ${decisionContext.targetAudience}` : '',
|
||||
...(decisionContext?.constraints || []).slice(0, 3).map(item => `Constraint: ${item}`),
|
||||
@@ -1316,7 +1324,7 @@ function copyableHandoffText({ ranked = [], provenance = {}, decisionContext = {
|
||||
return [
|
||||
'# Ranker build-order handoff',
|
||||
sourceLine ? `Source: ${sourceLine}` : '',
|
||||
provenance?.originalPrompt ? `Original prompt: ${cleanText(provenance.originalPrompt, 240)}` : '',
|
||||
sourceContextLine,
|
||||
readiness?.status ? `Readiness: ${readiness.status} — ${readiness.summary || ''}` : '',
|
||||
contextLines.length ? ['## Carried context', ...contextLines.map(item => `- ${item}`)].join('\n') : '',
|
||||
...itemLines,
|
||||
@@ -1328,7 +1336,7 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
|
||||
const warnings = [];
|
||||
const expectsSourceTrace = Boolean(provenance?.artifactId || provenance?.conceptMapId || provenance?.snapshotTitle);
|
||||
if (!provenance?.artifactId && provenance?.originalPrompt) warnings.push('missing source artifact id');
|
||||
if (expectsSourceTrace && !provenance?.originalPrompt) warnings.push('missing original prompt provenance');
|
||||
if (expectsSourceTrace && !provenance?.originalPrompt && !provenance?.sourceSummary) warnings.push('missing source context provenance');
|
||||
|
||||
const itemTrace = ranked.map(item => {
|
||||
if (expectsSourceTrace && !item.provenance?.sourceSection) warnings.push(`missing source section for ${item.id}`);
|
||||
@@ -1364,7 +1372,9 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
|
||||
snapshotTitle: provenance?.snapshotTitle || '',
|
||||
conceptMapId: provenance?.conceptMapId || '',
|
||||
originalPromptExcerpt: cleanText(provenance?.originalPrompt || '', 260),
|
||||
sourceSummaryExcerpt: cleanText(provenance?.sourceSummary || '', 260),
|
||||
hasOriginalPrompt: Boolean(provenance?.originalPrompt),
|
||||
hasSourceSummary: Boolean(provenance?.sourceSummary),
|
||||
requiresSourceTrace: expectsSourceTrace,
|
||||
},
|
||||
decisionContext: {
|
||||
@@ -1382,7 +1392,7 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
|
||||
|
||||
app.post('/api/rank-feedback', (req, res) => {
|
||||
const body = expandEmbeddedRankPayload(req.body || {});
|
||||
const idea = cleanMultiline(body?.idea || '', 3000);
|
||||
const idea = cleanMultiline(body?.idea || body?.opening_reflection || body?.restated_idea || '', 3000);
|
||||
const context = cleanContextText(body?.context || '');
|
||||
const modeId = cleanText(body?.mode || 'progress', 40);
|
||||
const mode = judgementModes[modeId] || judgementModes.progress;
|
||||
|
||||
Reference in New Issue
Block a user