Accept snake-case Scattermind bridge exports

This commit is contained in:
OpenClaw Bot
2026-05-27 01:23:02 +02:00
parent 242fe235a5
commit 1c4897694c
2 changed files with 113 additions and 52 deletions
+45 -1
View File
@@ -839,7 +839,51 @@ try {
assert.equal(sourceExcerpt.handoff.readiness.status, 'ready');
assert.equal(sourceExcerpt.handoff.readiness.activeItemCount, 2);
console.log(JSON.stringify({ ok: true, top: data.ranked[0].id, hintedTop: hinted.ranked[0].id, actionTop: actions.ranked[0].id, nestedConceptTop: nestedConcept.ranked[0].id, nonGoalTop: nonGoal.ranked[0].id, structuredContextTop: structuredContext.ranked[0].id, lensOnlyTop: lensOnly.ranked[0].id, scattermindPaidShapeTop: scattermindPaidShape.ranked[0].id, mergedContextTop: mergedContext.ranked[0].id, embeddedJsonTop: embeddedJson.ranked[0].id, fencedJsonTop: fencedJson.ranked[0].id, embeddedSnapshotTop: embeddedSnapshot.ranked[0].id, sourceExcerptTop: sourceExcerpt.ranked[0].id, duplicateIds: duplicateIds.ranked.map(item => item.id), readiness: data.handoff.readiness.status, provenance: data.input.provenance, buildOrder: data.buildOrder }, null, 2));
const snakeCaseBridgeResponse = await fetch(`${base}/api/rank-feedback`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
schema: 'scattermind-concept-map-v1',
source_name: 'Scattermind',
source_artifact_id: 'SM-SNAKE-1',
snapshot_title: 'Snake-case bridge export',
concept_map_id: 'CM-SNAKE-1',
original_prompt: 'I have a clarified idea and need a defended build order from snake_case JSON.',
context: {
target_audience: 'Tired non-AI-native solo builder',
constraints: ['Manual proof before product shell'],
non_goals: ['Avoid account dashboards and saved workspaces before proof'],
},
concept_map: {
next_actions: [
{ id: 'manual-bridge-proof', action: 'Manual bridge proof preview', why: 'Show the defended first move with source trace.', evidence_needed: 'Can one tired user say why this wins?', proof_steps: ['Create one static preview'], recommended_lane: 'do-first', source_section: 'concept-map.next_actions', source_item_id: 'snake-next-1', source_title: 'Build order lens', source_excerpt: 'Build first: manual bridge proof preview.', ranker_hints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } },
{ id: 'copyable-handoff', action: 'Copyable handoff brief', why: 'Preserve the decision outside the app.', evidence_needed: 'Does the copied brief retain source and next step?', suggested_lane: 'validate-next', source_section: 'concept-map.next_actions', source_item_id: 'snake-next-2' },
],
parking_lot: [
{ id: 'saved-workspace', action: 'Saved workspace dashboard', why: 'Accounts, dashboards, and saved projects for every idea.', evidence_needed: 'No proof yet.', suggested_lane: 'park', source_section: 'concept-map.parking_lot', source_item_id: 'snake-park-1' },
],
},
}),
});
assert.equal(snakeCaseBridgeResponse.status, 200);
const snakeCaseBridge = await snakeCaseBridgeResponse.json();
assert.equal(snakeCaseBridge.input.provenance.artifactId, 'SM-SNAKE-1');
assert.equal(snakeCaseBridge.input.provenance.snapshotTitle, 'Snake-case bridge export');
assert.equal(snakeCaseBridge.input.provenance.conceptMapId, 'CM-SNAKE-1');
assert.equal(snakeCaseBridge.input.provenance.originalPrompt, 'I have a clarified idea and need a defended build order from snake_case JSON.');
assert.equal(snakeCaseBridge.input.decisionContext.targetAudience, 'Tired non-AI-native solo builder');
assert.ok(snakeCaseBridge.input.decisionContext.nonGoals.includes('Avoid account dashboards and saved workspaces before proof'));
assert.equal(snakeCaseBridge.ranked[0].id, 'manual-bridge-proof');
assert.equal(snakeCaseBridge.ranked[0].provenance.sourceSection, 'concept-map.next_actions');
assert.equal(snakeCaseBridge.ranked[0].provenance.sourceId, 'snake-next-1');
assert.equal(snakeCaseBridge.ranked[0].factors.evidenceNeeded, 'Can one tired user say why this wins?');
assert.ok(snakeCaseBridge.ranked[0].factors.proofSteps.includes('Create one static preview'));
assert.ok(snakeCaseBridge.ranked[0].factors.metricHints.value >= 9);
assert.equal(snakeCaseBridge.ranked.find(item => item.id === 'saved-workspace').lane.id, 'park');
assert.equal(snakeCaseBridge.handoff.readiness.status, 'ready');
assert.deepEqual(snakeCaseBridge.handoff.warnings, []);
console.log(JSON.stringify({ ok: true, top: data.ranked[0].id, hintedTop: hinted.ranked[0].id, actionTop: actions.ranked[0].id, nestedConceptTop: nestedConcept.ranked[0].id, nonGoalTop: nonGoal.ranked[0].id, structuredContextTop: structuredContext.ranked[0].id, lensOnlyTop: lensOnly.ranked[0].id, scattermindPaidShapeTop: scattermindPaidShape.ranked[0].id, mergedContextTop: mergedContext.ranked[0].id, embeddedJsonTop: embeddedJson.ranked[0].id, fencedJsonTop: fencedJson.ranked[0].id, embeddedSnapshotTop: embeddedSnapshot.ranked[0].id, sourceExcerptTop: sourceExcerpt.ranked[0].id, snakeCaseBridgeTop: snakeCaseBridge.ranked[0].id, duplicateIds: duplicateIds.ranked.map(item => item.id), readiness: data.handoff.readiness.status, provenance: data.input.provenance, buildOrder: data.buildOrder }, null, 2));
} finally {
server.kill('SIGTERM');
}
+68 -51
View File
@@ -191,16 +191,18 @@ function cleanMetricHints(item = {}) {
const raw = {
...(item.factors && typeof item.factors === 'object' ? item.factors : {}),
...(item.scoring && typeof item.scoring === 'object' ? item.scoring : {}),
...(item.ranker_hints && typeof item.ranker_hints === 'object' ? item.ranker_hints : {}),
...(item.metric_hints && typeof item.metric_hints === 'object' ? item.metric_hints : {}),
...(item.rankerHints && typeof item.rankerHints === 'object' ? item.rankerHints : {}),
};
const aliases = {
value: ['value', 'impact', 'userImpact', 'userValueScore'],
effort: ['effort', 'buildEffort', 'complexity'],
value: ['value', 'impact', 'userImpact', 'user_impact', 'userValueScore', 'user_value_score'],
effort: ['effort', 'buildEffort', 'build_effort', 'complexity'],
confidence: ['confidence', 'certainty'],
urgency: ['urgency', 'timing'],
revenue: ['revenue', 'commercial', 'buyerSignal'],
novelty: ['novelty', 'differentiation', 'originality'],
risk: ['risk', 'assumptionRisk', 'scopeRisk'],
risk: ['risk', 'assumptionRisk', 'assumption_risk', 'scopeRisk', 'scope_risk'],
};
return Object.fromEntries(Object.entries(aliases).map(([metric, keys]) => {
const found = keys.map(key => raw[key] ?? item[key]).find(value => value !== undefined && value !== null && value !== '');
@@ -472,23 +474,36 @@ function looksLikeRankPayload(value = {}) {
return Boolean(
value.schema
|| value.featureSet
|| value.feature_set
|| value.snapshot
|| value.conceptMap
|| value.concept_map
|| value.buildOrder
|| value.build_order
|| value.lenses
|| value.reference_code
|| value.referenceCode
|| value.artifactId
|| value.sourceArtifactId
|| value.source_artifact_id
|| value.snapshotTitle
|| value.snapshot_title
|| value.originalPrompt
|| value.original_prompt
|| value.ideaText
|| value.idea_text
|| Array.isArray(value.features)
|| Array.isArray(value.actions)
|| Array.isArray(value.nextActions)
|| Array.isArray(value.next_actions)
|| Array.isArray(value.nextMoves)
|| Array.isArray(value.next_moves)
|| Array.isArray(value.candidates)
|| Array.isArray(value.validateNext)
|| Array.isArray(value.validate_next)
|| Array.isArray(value.deferred)
|| Array.isArray(value.parkingLot)
|| Array.isArray(value.parking_lot)
);
}
@@ -558,18 +573,18 @@ function expandEmbeddedRankPayload(body = {}) {
}
function cleanProvenance(input = {}) {
const featureSet = objectFrom(input.featureSet);
const featureSet = objectFrom(input.featureSet || input.feature_set);
const artifact = objectFrom(input.artifact || featureSet.artifact);
const conceptMap = objectFrom(input.conceptMap || featureSet.conceptMap || artifact.conceptMap);
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);
return {
schema: cleanText(input.schema || featureSet.schema || artifact.schema || input.type || 'prioritix-feature-set-v1', 80),
source: cleanText(input.sourceName || featureSet.sourceName || source.name || artifact.sourceName || 'Scattermind', 80),
artifactId: cleanText(input.artifactId || input.sourceArtifactId || input.referenceCode || input.reference_code || artifact.id || source.artifactId || conceptMap.artifactId || conceptMap.id || conceptMap.referenceCode || conceptMap.reference_code || snapshot.artifactId || snapshot.id || '', 120),
snapshotTitle: cleanText(input.snapshotTitle || input.working_name || input.workingName || artifact.snapshotTitle || snapshot.title || snapshot.name || conceptMap.snapshotTitle || conceptMap.working_name || conceptMap.workingName || input.ideaTitle || '', 160),
conceptMapId: cleanText(input.conceptMapId || artifact.conceptMapId || conceptMap.id || conceptMap.artifactId || input.referenceCode || input.reference_code || conceptMap.referenceCode || conceptMap.reference_code || '', 120),
originalPrompt: cleanMultiline(input.originalPrompt || input.initialPrompt || input.ideaText || input.prompt || artifact.originalPrompt || source.originalPrompt || snapshot.originalPrompt || snapshot.prompt || conceptMap.originalPrompt || conceptMap.ideaText || input.idea || '', 1200),
source: cleanText(input.sourceName || input.source_name || featureSet.sourceName || featureSet.source_name || source.name || artifact.sourceName || artifact.source_name || 'Scattermind', 80),
artifactId: cleanText(input.artifactId || input.artifact_id || input.sourceArtifactId || input.source_artifact_id || input.referenceCode || input.reference_code || artifact.id || source.artifactId || source.artifact_id || conceptMap.artifactId || conceptMap.artifact_id || conceptMap.id || conceptMap.referenceCode || conceptMap.reference_code || snapshot.artifactId || snapshot.artifact_id || snapshot.id || '', 120),
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),
};
}
@@ -604,9 +619,9 @@ function firstContextText(sources = [], aliases = []) {
}
function cleanDecisionContext(input = {}) {
const featureSet = objectFrom(input.featureSet);
const featureSet = objectFrom(input.featureSet || input.feature_set);
const artifact = objectFrom(input.artifact || featureSet.artifact);
const conceptMap = objectFrom(input.conceptMap || featureSet.conceptMap || artifact.conceptMap);
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 conceptMapLenses = objectFrom(conceptMap.lenses || input.lenses || featureSet.lenses);
const riskLens = conceptMapLenses.risk || conceptMapLenses.risks || conceptMapLenses.boundaries || conceptMapLenses.notYet;
@@ -634,7 +649,7 @@ function cleanDecisionContext(input = {}) {
snapshot.risk || snapshot.whatNotToBuildYet || snapshot.notYet || '',
].filter(Boolean).join('\n'));
return {
targetAudience: cleanText(input.targetAudience || featureSet.targetAudience || snapshot.targetAudience || firstContextText(contextSources, ['targetAudience', 'audience', 'who', 'whoItHelps', 'customer', 'users']) || conceptMap.targetAudience || lensContent(audienceLens), 180),
targetAudience: cleanText(input.targetAudience || input.target_audience || featureSet.targetAudience || featureSet.target_audience || snapshot.targetAudience || snapshot.target_audience || firstContextText(contextSources, ['targetAudience', 'target_audience', 'audience', 'who', 'whoItHelps', 'who_it_helps', 'customer', 'users']) || conceptMap.targetAudience || conceptMap.target_audience || lensContent(audienceLens), 180),
constraints: uniqueList([
...cleanFlexibleTextList(input.constraints || featureSet.constraints || snapshot.constraints || conceptMap.constraints, 8, 180),
...collectContextList(contextSources, ['constraints', 'constraint', 'boundaries', 'scope'], 8),
@@ -642,8 +657,8 @@ function cleanDecisionContext(input = {}) {
...textContextGuardrails.constraints,
], 8),
nonGoals: uniqueList([
...cleanFlexibleTextList(input.nonGoals || input.avoid || featureSet.nonGoals || featureSet.avoid || snapshot.nonGoals || snapshot.avoid || conceptMap.nonGoals || conceptMap.avoid, 8, 180),
...collectContextList(contextSources, ['nonGoals', 'nonGoal', 'avoid', 'notYet', 'doNotBuild'], 8),
...cleanFlexibleTextList(input.nonGoals || input.non_goals || input.avoid || featureSet.nonGoals || featureSet.non_goals || featureSet.avoid || snapshot.nonGoals || snapshot.non_goals || snapshot.avoid || conceptMap.nonGoals || conceptMap.non_goals || conceptMap.avoid, 8, 180),
...collectContextList(contextSources, ['nonGoals', 'non_goals', 'nonGoal', 'non_goal', 'avoid', 'notYet', 'not_yet', 'doNotBuild', 'do_not_build'], 8),
...textContextGuardrails.nonGoals,
], 8),
assumptions: uniqueList([
@@ -687,22 +702,22 @@ function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSour
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 proofSteps = cleanTextList(raw.proofSteps || raw.proof || raw.validationSteps || raw.steps || raw.method, 5, 180);
const proofSteps = cleanTextList(raw.proofSteps || raw.proof_steps || raw.proof || raw.validationSteps || raw.validation_steps || raw.steps || raw.method, 5, 180);
const dependencies = cleanTextList(raw.dependencies || raw.blockedBy, 5, 120);
const evidenceNeeded = cleanText(raw.evidenceNeeded || raw.evidence || raw.test || raw.evidenceQuestion || raw.questionToAnswer || raw.question || raw.learningGoal || '', 260);
const userValue = cleanText(raw.userValue || raw.value || raw.outcome || raw.why, 260);
const evidenceNeeded = cleanText(raw.evidenceNeeded || raw.evidence_needed || raw.evidence || raw.test || raw.evidenceQuestion || raw.evidence_question || raw.questionToAnswer || raw.question_to_answer || raw.question || raw.learningGoal || raw.learning_goal || '', 260);
const userValue = cleanText(raw.userValue || raw.user_value || raw.value || raw.outcome || raw.why, 260);
const risk = cleanText(raw.risk || raw.assumption || raw.unknown || '', 220);
const nextStep = cleanText(raw.nextStep || raw.nextAction || raw.firstStep || raw.manualStep || raw.actionToTake || '', 260);
const successSignal = cleanText(raw.successSignal || raw.successCriteria || raw.successMetric || raw.greenLight || raw.signalToSee || '', 260);
const killSignal = cleanText(raw.killSignal || raw.stopSignal || raw.redFlag || raw.failureSignal || raw.cutIf || '', 260);
const nextStep = cleanText(raw.nextStep || raw.next_step || raw.nextAction || raw.next_action || raw.firstStep || raw.first_step || raw.manualStep || raw.manual_step || raw.actionToTake || raw.action_to_take || '', 260);
const successSignal = cleanText(raw.successSignal || raw.success_signal || raw.successCriteria || raw.success_criteria || raw.successMetric || raw.success_metric || raw.greenLight || raw.green_light || raw.signalToSee || raw.signal_to_see || '', 260);
const killSignal = cleanText(raw.killSignal || raw.kill_signal || raw.stopSignal || raw.stop_signal || raw.redFlag || raw.red_flag || raw.failureSignal || raw.failure_signal || raw.cutIf || raw.cut_if || '', 260);
const rawLane = cleanText(raw.lane || '', 40);
const laneLooksLikeHint = Boolean(normalizeLaneHint(rawLane));
const sourceSection = cleanText(raw.sourceSection || raw.section || raw.origin || (!laneLooksLikeHint ? rawLane : '') || defaultSourceSection, 80);
const explicitSourceId = cleanText(raw.sourceId || raw.sourceArtifactId || raw.sourceItemId || raw.traceId || '', 120);
const sourceSection = cleanText(raw.sourceSection || raw.source_section || raw.section || raw.origin || (!laneLooksLikeHint ? rawLane : '') || defaultSourceSection, 80);
const explicitSourceId = cleanText(raw.sourceId || raw.source_id || raw.sourceArtifactId || raw.source_artifact_id || raw.sourceItemId || raw.source_item_id || raw.traceId || raw.trace_id || '', 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 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 sourceTitle = cleanText(raw.sourceTitle || raw.source_title || raw.sourceHeading || raw.source_heading || raw.lensTitle || raw.lens_title || raw.heading || '', 140);
const sourceQuote = cleanMultiline(raw.sourceQuote || raw.source_quote || raw.sourceExcerpt || raw.source_excerpt || raw.evidenceQuote || raw.evidence_quote || raw.quote || raw.originalText || raw.original_text || raw.rawText || raw.raw_text || rawValue, 420);
const recommendedLane = cleanText(raw.recommendedLane || raw.recommended_lane || raw.laneHint || raw.lane_hint || raw.suggestedLane || raw.suggested_lane || (laneLooksLikeHint ? rawLane : '') || defaultRecommendedLane || '', 40).toLowerCase();
const descriptionParts = [
raw.description || raw.brief || (raw.hypothesis && raw.hypothesis !== title ? `Hypothesis: ${raw.hypothesis}` : ''),
userValue && `User value: ${userValue}`,
@@ -757,7 +772,7 @@ function buildOrderSectionGroup(buildOrder = {}, baseSection = 'buildOrder') {
{ items: source.doFirst || source.do_first || source.buildFirst || source.buildNow || source.now, sourceSection: `${baseSection}.doFirst`, defaultLane: 'do-first' },
{ items: source.validateNext || source.validate_next || source.testNext || source.testManually || source.validation, sourceSection: `${baseSection}.validateNext`, defaultLane: 'validate-next' },
{ items: source.defer || source.deferred || source.later || source.afterProof, sourceSection: `${baseSection}.defer`, defaultLane: 'defer' },
{ items: source.park || source.parkingLot || source.parked || source.probablyNoise || source.noise, sourceSection: `${baseSection}.park`, defaultLane: 'park' },
{ items: source.park || source.parkingLot || source.parking_lot || source.parked || source.probablyNoise || source.probably_noise || source.noise, sourceSection: `${baseSection}.park`, defaultLane: 'park' },
]);
}
@@ -816,9 +831,9 @@ function optionsFromBuildOrderText(text = '', sourceSection = 'concept-map.lense
}
function optionsFromBody(body = {}) {
const featureSet = objectFrom(body.featureSet);
const featureSet = objectFrom(body.featureSet || body.feature_set);
const artifact = objectFrom(body.artifact || featureSet.artifact);
const conceptMap = objectFrom(body.conceptMap || featureSet.conceptMap || artifact.conceptMap);
const conceptMap = objectFrom(body.conceptMap || body.concept_map || featureSet.conceptMap || featureSet.concept_map || artifact.conceptMap || artifact.concept_map);
const snapshot = objectFrom(body.snapshot || featureSet.snapshot || artifact.snapshot || conceptMap.snapshot);
const conceptMapLenses = objectFrom(conceptMap.lenses || body.lenses || featureSet.lenses);
const buildOrderLens = objectFrom(conceptMapLenses.channel || conceptMapLenses.buildOrder || conceptMap.buildOrder);
@@ -827,50 +842,52 @@ function optionsFromBody(body = {}) {
{ items: featureSet.features, sourceSection: 'feature-set.features' },
{ items: body.actions, sourceSection: 'actions' },
{ items: featureSet.actions, sourceSection: 'feature-set.actions' },
{ items: body.nextMoves, sourceSection: 'nextMoves' },
{ items: featureSet.nextMoves, sourceSection: 'feature-set.nextMoves' },
{ items: body.nextActions || body.next_actions, sourceSection: 'nextActions' },
{ items: featureSet.nextActions || featureSet.next_actions, sourceSection: 'feature-set.nextActions' },
{ items: body.nextMoves || body.next_moves, sourceSection: 'nextMoves' },
{ items: featureSet.nextMoves || featureSet.next_moves, sourceSection: 'feature-set.nextMoves' },
{ items: body.candidates, sourceSection: 'candidates' },
{ items: featureSet.candidates, sourceSection: 'feature-set.candidates' },
{ items: body.experiments, sourceSection: 'experiments', defaultLane: 'validate-next' },
{ items: body.validationTests, sourceSection: 'experiments', defaultLane: 'validate-next' },
{ items: body.proofTests, sourceSection: 'experiments', defaultLane: 'validate-next' },
{ items: body.validationTests || body.validation_tests, sourceSection: 'experiments', defaultLane: 'validate-next' },
{ items: body.proofTests || body.proof_tests, sourceSection: 'experiments', defaultLane: 'validate-next' },
{ items: featureSet.experiments, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.validationTests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.proofTests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.validationTests || featureSet.validation_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.proofTests || featureSet.proof_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
]);
const conceptMapCandidateGroup = compactCandidateGroup([
{ items: conceptMap.nextActions, sourceSection: 'concept-map.nextActions' },
{ items: conceptMap.nextMoves, sourceSection: 'concept-map.nextMoves' },
{ items: conceptMap.nextActions || conceptMap.next_actions, sourceSection: 'concept-map.nextActions' },
{ items: conceptMap.nextMoves || conceptMap.next_moves, sourceSection: 'concept-map.nextMoves' },
{ items: conceptMap.features, sourceSection: 'concept-map.features' },
{ items: conceptMap.candidates, sourceSection: 'concept-map.candidates' },
{ items: conceptMap.validateNext || conceptMap.validate || conceptMap.validation, sourceSection: 'concept-map.validateNext', defaultLane: 'validate-next' },
{ items: conceptMap.validateNext || conceptMap.validate_next || conceptMap.validate || conceptMap.validation, sourceSection: 'concept-map.validateNext', defaultLane: 'validate-next' },
{ items: conceptMap.experiments, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' },
{ items: conceptMap.validationTests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' },
{ items: conceptMap.proofTests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' },
{ items: conceptMap.validationTests || conceptMap.validation_tests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' },
{ items: conceptMap.proofTests || conceptMap.proof_tests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' },
{ items: conceptMap.deferred || conceptMap.defer || conceptMap.later, sourceSection: 'concept-map.deferred', defaultLane: 'defer' },
{ items: conceptMap.parkingLot || conceptMap.park || conceptMap.parked, sourceSection: 'concept-map.parkingLot', defaultLane: 'park' },
{ items: conceptMap.parkingLot || conceptMap.parking_lot || conceptMap.park || conceptMap.parked, sourceSection: 'concept-map.parkingLot', defaultLane: 'park' },
]);
const snapshotCandidateGroup = compactCandidateGroup([
{ items: snapshot.nextActions, sourceSection: 'snapshot.nextActions' },
{ items: snapshot.nextMoves, sourceSection: 'snapshot.nextMoves' },
{ items: snapshot.nextActions || snapshot.next_actions, sourceSection: 'snapshot.nextActions' },
{ items: snapshot.nextMoves || snapshot.next_moves, sourceSection: 'snapshot.nextMoves' },
{ items: snapshot.actions, sourceSection: 'snapshot.actions' },
{ items: snapshot.features, sourceSection: 'snapshot.features' },
{ items: snapshot.candidates, sourceSection: 'snapshot.candidates' },
{ items: snapshot.validateNext || snapshot.validate || snapshot.validation, sourceSection: 'snapshot.validateNext', defaultLane: 'validate-next' },
{ items: snapshot.validateNext || snapshot.validate_next || snapshot.validate || snapshot.validation, sourceSection: 'snapshot.validateNext', defaultLane: 'validate-next' },
{ items: snapshot.experiments, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },
{ items: snapshot.validationTests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },
{ items: snapshot.proofTests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },
{ items: snapshot.validationTests || snapshot.validation_tests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },
{ items: snapshot.proofTests || snapshot.proof_tests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },
{ items: snapshot.deferred || snapshot.defer || snapshot.later, sourceSection: 'snapshot.deferred', defaultLane: 'defer' },
{ items: snapshot.parkingLot || snapshot.park || snapshot.parked, sourceSection: 'snapshot.parkingLot', defaultLane: 'park' },
{ items: snapshot.parkingLot || snapshot.parking_lot || snapshot.park || snapshot.parked, sourceSection: 'snapshot.parkingLot', defaultLane: 'park' },
]);
const groupedCandidates = [
...directCandidateGroup,
...snapshotCandidateGroup,
...conceptMapCandidateGroup,
...buildOrderSectionGroup(body.buildOrder, 'buildOrder'),
...buildOrderSectionGroup(featureSet.buildOrder, 'feature-set.buildOrder'),
...buildOrderSectionGroup(snapshot.buildOrder, 'snapshot.buildOrder'),
...buildOrderSectionGroup(conceptMap.buildOrder, 'concept-map.buildOrder'),
...buildOrderSectionGroup(body.buildOrder || body.build_order, 'buildOrder'),
...buildOrderSectionGroup(featureSet.buildOrder || featureSet.build_order, 'feature-set.buildOrder'),
...buildOrderSectionGroup(snapshot.buildOrder || snapshot.build_order, 'snapshot.buildOrder'),
...buildOrderSectionGroup(conceptMap.buildOrder || conceptMap.build_order, 'concept-map.buildOrder'),
];
if (groupedCandidates.length) return normalizeCandidateGroup(groupedCandidates);
const buildOrderText = lensContent(conceptMapLenses.channel)