From 1c4897694c5ac9ab65a2b78ba8521c5eb66f3bb3 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Wed, 27 May 2026 01:23:02 +0200 Subject: [PATCH] Accept snake-case Scattermind bridge exports --- scripts/check-rank-feedback.mjs | 46 +++++++++++- server.js | 119 ++++++++++++++++++-------------- 2 files changed, 113 insertions(+), 52 deletions(-) diff --git a/scripts/check-rank-feedback.mjs b/scripts/check-rank-feedback.mjs index 2750f19..b3e7a7c 100644 --- a/scripts/check-rank-feedback.mjs +++ b/scripts/check-rank-feedback.mjs @@ -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'); } diff --git a/server.js b/server.js index 3b2d3db..e955bcf 100644 --- a/server.js +++ b/server.js @@ -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)