Preserve source trace for laned build-order strings

This commit is contained in:
OpenClaw Bot
2026-05-27 00:58:30 +02:00
parent 1f8739444c
commit b8c518f7cb
3 changed files with 10 additions and 4 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ Ranker's continuation job is narrow:
Candidate items may include optional 110 `rankerHints` (`value`, `effort`, `confidence`, `urgency`, `revenue`, `novelty`, `risk`). Ranker blends those explicit Scattermind hints with text heuristics; `effort` is inverted into feasibility. Hints improve the defended order, but `recommendedLane: "defer"` or `"park"` remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send `sourceSection` and `evidenceNeeded` on each active next move. Scattermind can also send `targetAudience`, `constraints`, `assumptions`, and `nonGoals` / `avoid` at the top level, in `featureSet`, inside `conceptMap.context`, or as a structured top-level `context` object with `summary`, `targetAudience`, `constraints`, `nonGoals` / `avoid`, and `assumptions`; Ranker merges these sources rather than letting a shallow wrapper context shadow deeper Concept Map guardrails. Lens-only Concept Maps may additionally send audience / constraints / assumptions / risk lens content, and Ranker will split sentence-style lens notes into readable decision context instead of leaking `[object Object]`. If Scattermind only has a flat context string, Ranker now extracts simple guardrails such as `Solo builder`, `Manual proof`, `Avoid ...`, `No ...`, and `Do not ...` into `input.decisionContext` / `handoff.decisionContext` so early bridge exports still protect against dashboard/auth/billing drift. Ranker returns that decision context in `input.decisionContext` and `handoff.decisionContext`, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof). If Scattermind sends duplicate candidate IDs, Ranker keeps the first ID, suffixes later duplicates (`preview-2`), and reports the normalization in `handoff.warnings` / `handoff.itemTrace` so downstream build-order references remain addressable. Ranker also accepts Scattermind's paid Concept Map object directly when it arrives with top-level `reference_code`, `working_name`, `ideaText`, and string-valued `lenses.channel` / `lenses.risk` fields; the reference code becomes source provenance, the working name becomes the source title, and labelled Build Order text is turned into rank-ready candidates without requiring Scattermind to wrap it first. Candidate items may include optional 110 `rankerHints` (`value`, `effort`, `confidence`, `urgency`, `revenue`, `novelty`, `risk`). Ranker blends those explicit Scattermind hints with text heuristics; `effort` is inverted into feasibility. Hints improve the defended order, but `recommendedLane: "defer"` or `"park"` remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send `sourceSection` and `evidenceNeeded` on each active next move. Scattermind can also send `targetAudience`, `constraints`, `assumptions`, and `nonGoals` / `avoid` at the top level, in `featureSet`, inside `conceptMap.context`, or as a structured top-level `context` object with `summary`, `targetAudience`, `constraints`, `nonGoals` / `avoid`, and `assumptions`; Ranker merges these sources rather than letting a shallow wrapper context shadow deeper Concept Map guardrails. Lens-only Concept Maps may additionally send audience / constraints / assumptions / risk lens content, and Ranker will split sentence-style lens notes into readable decision context instead of leaking `[object Object]`. If Scattermind only has a flat context string, Ranker now extracts simple guardrails such as `Solo builder`, `Manual proof`, `Avoid ...`, `No ...`, and `Do not ...` into `input.decisionContext` / `handoff.decisionContext` so early bridge exports still protect against dashboard/auth/billing drift. Ranker returns that decision context in `input.decisionContext` and `handoff.decisionContext`, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof). If Scattermind sends duplicate candidate IDs, Ranker keeps the first ID, suffixes later duplicates (`preview-2`), and reports the normalization in `handoff.warnings` / `handoff.itemTrace` so downstream build-order references remain addressable. Ranker also accepts Scattermind's paid Concept Map object directly when it arrives with top-level `reference_code`, `working_name`, `ideaText`, and string-valued `lenses.channel` / `lenses.risk` fields; the reference code becomes source provenance, the working name becomes the source title, and labelled Build Order text is turned into rank-ready candidates without requiring Scattermind to wrap it first.
Candidate trace note: candidate-level `sourceItemId` / `traceId`, `sourceTitle` / `lensTitle`, and `sourceExcerpt` / `sourceQuote` are preserved in ranked items, `buildOrderDetails`, and `handoff.itemTrace`. Lens-only Build Order text is also split into deterministic `concept-map.lenses.channel#N` source IDs with the original labelled sentence carried as `sourceQuote`, so pasted paid Concept Maps remain traceable even without explicit candidate objects. The decision `brief.quickGlance.sourceTrace` now repeats the winning item's source section/id/title/quote, and both `brief.source.originalPromptExcerpt` and `handoff.source.originalPromptExcerpt` carry a short prompt excerpt so a downstream Scattermind handoff can show why the build order exists without digging through `input.provenance`. Scattermind should use these when a next move came from a specific Concept Map lens sentence, so Ranker can defend not just what wins but where the judgement came from. Candidate trace note: candidate-level `sourceItemId` / `traceId`, `sourceTitle` / `lensTitle`, and `sourceExcerpt` / `sourceQuote` are preserved in ranked items, `buildOrderDetails`, and `handoff.itemTrace`. Lens-only Build Order text is also split into deterministic `concept-map.lenses.channel#N` source IDs with the original labelled sentence carried as `sourceQuote`, so pasted paid Concept Maps remain traceable even without explicit candidate objects. String items in laned Build Order arrays now also receive deterministic section-local source IDs such as `concept-map.buildOrder.validateNext#1` and carry the original string as `sourceQuote`, so simple Scattermind exports stay addressable downstream instead of becoming anonymous `feature-1` rows. The decision `brief.quickGlance.sourceTrace` now repeats the winning item's source section/id/title/quote, and both `brief.source.originalPromptExcerpt` and `handoff.source.originalPromptExcerpt` carry a short prompt excerpt so a downstream Scattermind handoff can show why the build order exists without digging through `input.provenance`. Scattermind should use these when a next move came from a specific Concept Map lens sentence, so Ranker can defend not just what wins but where the judgement came from.
Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; the result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff `source.requiresSourceTrace` is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. Handoff `readiness` now gives downstream bridge consumers a deterministic gate: `ready`, `usable-with-warnings`, `needs-source-context`, or `blocked`, with blockers and next checks for missing evidence, source trace, duplicate IDs, or active source-non-goal conflicts. For low-friction handoff, `/api/rank-feedback` also detects a raw Scattermind/Concept Map JSON object pasted into `idea`, `ideaText`, `optionsText`, or wrapper keys such as `payload`; it expands that object before ranking and reports `input.embeddedPayloadSource` so the public form can accept copy/paste exports without a custom import screen. Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; the result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff `source.requiresSourceTrace` is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. Handoff `readiness` now gives downstream bridge consumers a deterministic gate: `ready`, `usable-with-warnings`, `needs-source-context`, or `blocked`, with blockers and next checks for missing evidence, source trace, duplicate IDs, or active source-non-goal conflicts. For low-friction handoff, `/api/rank-feedback` also detects a raw Scattermind/Concept Map JSON object pasted into `idea`, `ideaText`, `optionsText`, or wrapper keys such as `payload`; it expands that object before ranking and reports `input.embeddedPayloadSource` so the public form can accept copy/paste exports without a custom import screen.
+4
View File
@@ -633,8 +633,12 @@ try {
assert.equal(objectBuildOrder.buildOrderDetails.doFirst[0].successSignal, 'One prospect asks how to buy or book the next step.'); assert.equal(objectBuildOrder.buildOrderDetails.doFirst[0].successSignal, 'One prospect asks how to buy or book the next step.');
assert.equal(objectBuildOrder.handoff.itemTrace.find(item => item.id === 'manual-offer-proof').nextStep, 'Send the offer to three named prospects before touching the page design.'); assert.equal(objectBuildOrder.handoff.itemTrace.find(item => item.id === 'manual-offer-proof').nextStep, 'Send the offer to three named prospects before touching the page design.');
assert.equal(objectBuildOrder.ranked.find(item => item.id === 'feature-1').title, 'Copyable follow-up script'); assert.equal(objectBuildOrder.ranked.find(item => item.id === 'feature-1').title, 'Copyable follow-up script');
assert.equal(objectBuildOrder.ranked.find(item => item.id === 'feature-1').provenance.sourceId, 'concept-map.buildOrder.validateNext#1');
assert.equal(objectBuildOrder.ranked.find(item => item.id === 'feature-1').provenance.sourceQuote, 'Copyable follow-up script');
assert.equal(objectBuildOrder.ranked.find(item => item.id === 'feature-1').lane.id, 'test'); assert.equal(objectBuildOrder.ranked.find(item => item.id === 'feature-1').lane.id, 'test');
assert.equal(objectBuildOrder.handoff.itemTrace.find(item => item.id === 'feature-1').sourceSection, 'concept-map.buildOrder.validateNext'); assert.equal(objectBuildOrder.handoff.itemTrace.find(item => item.id === 'feature-1').sourceSection, 'concept-map.buildOrder.validateNext');
assert.equal(objectBuildOrder.handoff.itemTrace.find(item => item.id === 'feature-1').sourceId, 'concept-map.buildOrder.validateNext#1');
assert.equal(objectBuildOrder.handoff.itemTrace.find(item => item.id === 'feature-1').sourceQuote, 'Copyable follow-up script');
assert.equal(objectBuildOrder.ranked.find(item => item.id === 'workspace-dashboard').lane.id, 'park'); assert.equal(objectBuildOrder.ranked.find(item => item.id === 'workspace-dashboard').lane.id, 'park');
assert.equal(objectBuildOrder.ranked.find(item => item.id === 'workspace-dashboard').lane.source, 'hint'); assert.equal(objectBuildOrder.ranked.find(item => item.id === 'workspace-dashboard').lane.source, 'hint');
assert.ok(objectBuildOrder.handoff.warnings.includes('missing evidence needed for active item feature-1')); assert.ok(objectBuildOrder.handoff.warnings.includes('missing evidence needed for active item feature-1'));
+5 -3
View File
@@ -674,7 +674,8 @@ function nonGoalConflicts(optionText, decisionContext = {}) {
} }
function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSourceSection = '', defaultRecommendedLane = '') { function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSourceSection = '', defaultRecommendedLane = '') {
const raw = typeof item === 'string' || typeof item === 'number' ? { action: String(item) } : objectFrom(item); const rawValue = typeof item === 'string' || typeof item === 'number' ? String(item) : '';
const raw = rawValue ? { action: rawValue } : objectFrom(item);
const title = cleanText(raw.title || raw.name || raw.action || raw.move || raw.experiment || raw.testName || raw.hypothesis || raw.label || '', 140); const title = cleanText(raw.title || raw.name || raw.action || raw.move || raw.experiment || raw.testName || raw.hypothesis || raw.label || '', 140);
const proofSteps = cleanTextList(raw.proofSteps || raw.proof || raw.validationSteps || raw.steps || raw.method, 5, 180); const proofSteps = cleanTextList(raw.proofSteps || raw.proof || raw.validationSteps || raw.steps || raw.method, 5, 180);
const dependencies = cleanTextList(raw.dependencies || raw.blockedBy, 5, 120); const dependencies = cleanTextList(raw.dependencies || raw.blockedBy, 5, 120);
@@ -687,9 +688,10 @@ function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSour
const rawLane = cleanText(raw.lane || '', 40); const rawLane = cleanText(raw.lane || '', 40);
const laneLooksLikeHint = Boolean(normalizeLaneHint(rawLane)); const laneLooksLikeHint = Boolean(normalizeLaneHint(rawLane));
const sourceSection = cleanText(raw.sourceSection || raw.section || raw.origin || (!laneLooksLikeHint ? rawLane : '') || defaultSourceSection, 80); const sourceSection = cleanText(raw.sourceSection || raw.section || raw.origin || (!laneLooksLikeHint ? rawLane : '') || defaultSourceSection, 80);
const sourceId = cleanText(raw.sourceId || raw.sourceArtifactId || raw.sourceItemId || raw.traceId || raw.id || '', 120); const explicitSourceId = cleanText(raw.sourceId || raw.sourceArtifactId || raw.sourceItemId || raw.traceId || '', 120);
const sourceId = explicitSourceId || cleanText(raw.id || (sourceSection ? `${sourceSection}#${index + 1}` : ''), 120);
const sourceTitle = cleanText(raw.sourceTitle || raw.sourceHeading || raw.lensTitle || raw.heading || '', 140); const sourceTitle = cleanText(raw.sourceTitle || raw.sourceHeading || raw.lensTitle || raw.heading || '', 140);
const sourceQuote = cleanMultiline(raw.sourceQuote || raw.sourceExcerpt || raw.evidenceQuote || raw.quote || raw.originalText || raw.rawText || '', 420); const sourceQuote = cleanMultiline(raw.sourceQuote || raw.sourceExcerpt || raw.evidenceQuote || raw.quote || raw.originalText || raw.rawText || rawValue, 420);
const recommendedLane = cleanText(raw.recommendedLane || raw.laneHint || raw.suggestedLane || (laneLooksLikeHint ? rawLane : '') || defaultRecommendedLane || '', 40).toLowerCase(); const recommendedLane = cleanText(raw.recommendedLane || raw.laneHint || raw.suggestedLane || (laneLooksLikeHint ? rawLane : '') || defaultRecommendedLane || '', 40).toLowerCase();
const descriptionParts = [ const descriptionParts = [
raw.description || raw.brief || (raw.hypothesis && raw.hypothesis !== title ? `Hypothesis: ${raw.hypothesis}` : ''), raw.description || raw.brief || (raw.hypothesis && raw.hypothesis !== title ? `Hypothesis: ${raw.hypothesis}` : ''),