diff --git a/scripts/check-rank-feedback.mjs b/scripts/check-rank-feedback.mjs index b4e4838..f083749 100644 --- a/scripts/check-rank-feedback.mjs +++ b/scripts/check-rank-feedback.mjs @@ -936,6 +936,43 @@ try { assert.equal(experimentSection.ranked.find(item => item.id === 'accounted-test-library').lane.id, 'park'); assert.deepEqual(experimentSection.handoff.warnings, []); + const buildOrderQuestionsResponse = await fetch(`${base}/api/rank-feedback`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sourceName: 'Scattermind', + artifactId: 'concept_map_build_order_questions', + snapshotTitle: 'Build Order with decision questions', + originalPrompt: 'Clarify a continuation engine and name what to build first.', + idea: 'A full Scattermind Concept Map emitted a Build Order lens plus questions to sit with.', + mode: 'mvp', + conceptMap: { + lenses: { + channel: { + title: 'Build Order', + content: 'Build first: Manual bridge handoff preview — turn one Concept Map into a defended first move. Test manually: Show the copied handoff to 3 tired builders and ask what they would do next. Defer: Saved workspaces until the copyable handoff proves useful. Probably noise: Team dashboard and billing layer.', + }, + question: { + title: 'Proof Steps', + content: 'Ask 3 tired builders to read the handoff and tell you the first move without extra explanation.', + }, + }, + questions_to_sit_with: [ + 'Can the user explain why the first move wins after reading only the handoff?', + 'What evidence would make the saved workspace worth reopening later?', + ], + }, + }), + }); + assert.equal(buildOrderQuestionsResponse.status, 200); + const buildOrderQuestions = await buildOrderQuestionsResponse.json(); + assert.equal(buildOrderQuestions.input.optionCount, 7, 'Build Order lens should carry Proof Steps and questions into the candidate set'); + assert.equal(buildOrderQuestions.ranked[0].provenance.sourceSection, 'concept-map.lenses.channel'); + assert.ok(buildOrderQuestions.ranked.some(item => item.provenance.sourceSection === 'concept-map.questionsToSitWith')); + assert.equal(buildOrderQuestions.ranked.find(item => /Can the user explain why the first move wins/i.test(item.title)).lane.id, 'test'); + assert.ok(buildOrderQuestions.buildOrder.validateNext.some(id => buildOrderQuestions.ranked.find(item => item.id === id)?.provenance.sourceSection === 'concept-map.questionsToSitWith')); + assert.match(buildOrderQuestions.handoff.copyableText, /Can the user explain why the first move wins/); + const emptyWrapperResponse = await fetch(`${base}/api/rank-feedback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -1265,10 +1302,11 @@ try { assert.equal(privateReadingEnvelope.input.provenance.artifactId, 'SM-PRIVATE1'); assert.equal(privateReadingEnvelope.input.provenance.snapshotTitle, 'Local Workshop Starter'); assert.match(privateReadingEnvelope.input.provenance.originalPrompt, /Scattermind clarified/); - assert.equal(privateReadingEnvelope.input.optionCount, 6); + assert.equal(privateReadingEnvelope.input.optionCount, 7); assert.equal(privateReadingEnvelope.ranked[0].id, 'build-order-1'); assert.equal(privateReadingEnvelope.ranked[0].provenance.sourceSection, 'concept-map.lenses.channel'); assert.equal(privateReadingEnvelope.ranked.find(item => item.id === 'proof-step-1').lane.id, 'test'); + assert.ok(privateReadingEnvelope.ranked.some(item => item.provenance.sourceSection === 'concept-map.questionsToSitWith')); assert.equal(privateReadingEnvelope.ranked.find(item => item.id === 'build-order-4').lane.id, 'park'); assert.ok(privateReadingEnvelope.input.decisionContext.nonGoals.includes('Avoid accounts, saved calendars, and payment dashboards until one workshop has real interest')); assert.deepEqual(privateReadingEnvelope.handoff.warnings, []); @@ -2065,12 +2103,13 @@ try { assert.equal(storedScattermindRow.input.provenance.artifactId, 'SM-STORED-1'); assert.equal(storedScattermindRow.input.provenance.snapshotTitle, 'Stored Row Bridge'); assert.match(storedScattermindRow.input.provenance.originalPrompt, /actual stored row ranked/); - assert.equal(storedScattermindRow.input.optionCount, 4); + assert.equal(storedScattermindRow.input.optionCount, 5); assert.equal(storedScattermindRow.ranked[0].id, 'build-order-1'); assert.equal(storedScattermindRow.ranked[0].provenance.sourceTitle, 'Build Order'); assert.match(storedScattermindRow.ranked[0].provenance.sourceQuote, /Stored-row build-order preview/); assert.ok(storedScattermindRow.input.decisionContext.nonGoals.includes('Avoid account dashboards and saved workspaces before one user acts')); assert.equal(storedScattermindRow.ranked.find(item => item.id === 'build-order-4').lane.id, 'park'); + assert.ok(storedScattermindRow.ranked.some(item => item.provenance.sourceSection === 'concept-map.questionsToSitWith')); assert.equal(storedScattermindRow.handoff.readiness.status, 'ready'); assert.deepEqual(storedScattermindRow.handoff.warnings, []); @@ -2101,7 +2140,7 @@ try { assert.equal(numberedMarkdownBuildOrderResponse.status, 200); const numberedMarkdownBuildOrder = await numberedMarkdownBuildOrderResponse.json(); assert.equal(numberedMarkdownBuildOrder.input.provenance.artifactId, 'SM-NUMBERED-BUILD-ORDER-1'); - assert.equal(numberedMarkdownBuildOrder.input.optionCount, 4); + assert.equal(numberedMarkdownBuildOrder.input.optionCount, 5); assert.equal(numberedMarkdownBuildOrder.ranked[0].id, 'build-order-1'); assert.equal(numberedMarkdownBuildOrder.ranked[0].title, 'Numbered source-traced preview'); assert.equal(numberedMarkdownBuildOrder.ranked[0].lane.id, 'do'); @@ -2109,6 +2148,7 @@ try { assert.equal(numberedMarkdownBuildOrder.ranked.find(item => item.id === 'build-order-2').lane.id, 'test'); assert.equal(numberedMarkdownBuildOrder.ranked.find(item => item.id === 'build-order-3').lane.id, 'defer'); assert.equal(numberedMarkdownBuildOrder.ranked.find(item => item.id === 'build-order-4').lane.id, 'park'); + assert.ok(numberedMarkdownBuildOrder.ranked.some(item => item.provenance.sourceSection === 'concept-map.questionsToSitWith')); assert.equal(numberedMarkdownBuildOrder.handoff.readiness.status, 'ready'); assert.deepEqual(numberedMarkdownBuildOrder.handoff.warnings, []); diff --git a/server.js b/server.js index b4a0983..d6b5cc4 100644 --- a/server.js +++ b/server.js @@ -1651,6 +1651,18 @@ function optionsFromBody(body = {}) { 140 ); const buildOrderOptions = optionsFromBuildOrderText(buildOrderText, 'concept-map.lenses.channel', buildOrderSourceTitle); + const questionSource = firstArraySource([ + { items: conceptMap.questions_to_sit_with || conceptMap.questionsToSitWith || conceptMap.evidenceQuestions || conceptMap.evidence_questions || conceptMap.proofQuestions || conceptMap.proof_questions || conceptMap.validationQuestions || conceptMap.validation_questions || conceptMap.decisionQuestions || conceptMap.decision_questions || conceptMap.questionsToAnswer || conceptMap.questions_to_answer || conceptMap.followupQuestions || conceptMap.followup_questions || conceptMap.openQuestions || conceptMap.open_questions, sourceSection: 'concept-map.questionsToSitWith' }, + { items: snapshot.questions_to_sit_with || snapshot.questionsToSitWith || snapshot.evidenceQuestions || snapshot.evidence_questions || snapshot.proofQuestions || snapshot.proof_questions || snapshot.validationQuestions || snapshot.validation_questions || snapshot.decisionQuestions || snapshot.decision_questions || snapshot.questionsToAnswer || snapshot.questions_to_answer || snapshot.followupQuestions || snapshot.followup_questions || snapshot.openQuestions || snapshot.open_questions, sourceSection: 'snapshot.questionsToSitWith' }, + { items: envelope.questions_to_sit_with || envelope.questionsToSitWith || envelope.evidenceQuestions || envelope.evidence_questions || envelope.proofQuestions || envelope.proof_questions || envelope.validationQuestions || envelope.validation_questions || envelope.decisionQuestions || envelope.decision_questions || envelope.questionsToAnswer || envelope.questions_to_answer || envelope.followupQuestions || envelope.followup_questions || envelope.openQuestions || envelope.open_questions, sourceSection: 'ranker-input.questionsToSitWith' }, + { items: featureSet.questions_to_sit_with || featureSet.questionsToSitWith || featureSet.evidenceQuestions || featureSet.evidence_questions || featureSet.proofQuestions || featureSet.proof_questions || featureSet.validationQuestions || featureSet.validation_questions || featureSet.decisionQuestions || featureSet.decision_questions || featureSet.questionsToAnswer || featureSet.questions_to_answer || featureSet.followupQuestions || featureSet.followup_questions || featureSet.openQuestions || featureSet.open_questions, sourceSection: 'feature-set.questionsToSitWith' }, + { items: body.questions_to_sit_with || body.questionsToSitWith || body.evidenceQuestions || body.evidence_questions || body.proofQuestions || body.proof_questions || body.validationQuestions || body.validation_questions || body.decisionQuestions || body.decision_questions || body.questionsToAnswer || body.questions_to_answer || body.followupQuestions || body.followup_questions || body.openQuestions || body.open_questions, sourceSection: 'questionsToSitWith' }, + ]); + const questionOptions = optionsFromQuestionsToSitWith( + questionSource.items, + questionSource.sourceSection || 'questionsToSitWith', + 'Question to sit with' + ); if (buildOrderOptions.length) { const proofLens = objectFrom(conceptMapLenses.question || conceptMapLenses.proof || conceptMapLenses.validation || conceptMapLenses.evidence); const proofLensText = lensContent(conceptMapLenses.question) @@ -1663,6 +1675,7 @@ function optionsFromBody(body = {}) { return normalizeCandidateGroup([ { items: buildOrderOptions, sourceSection: 'concept-map.lenses.channel' }, ...(proofOptions.length ? [{ items: proofOptions, sourceSection: 'concept-map.lenses.question', defaultLane: 'validate-next' }] : []), + ...(questionOptions.length ? [{ items: questionOptions, sourceSection: questionSource.sourceSection || 'questionsToSitWith', defaultLane: 'validate-next' }] : []), ]); } const actionThreadSource = firstArraySource([ @@ -1677,18 +1690,6 @@ function optionsFromBody(body = {}) { actionThreadSource.sourceSection || 'threadsToHold', 'Thread to hold' ); - const questionSource = firstArraySource([ - { items: conceptMap.questions_to_sit_with || conceptMap.questionsToSitWith || conceptMap.evidenceQuestions || conceptMap.evidence_questions || conceptMap.proofQuestions || conceptMap.proof_questions || conceptMap.validationQuestions || conceptMap.validation_questions || conceptMap.decisionQuestions || conceptMap.decision_questions || conceptMap.questionsToAnswer || conceptMap.questions_to_answer || conceptMap.followupQuestions || conceptMap.followup_questions || conceptMap.openQuestions || conceptMap.open_questions, sourceSection: 'concept-map.questionsToSitWith' }, - { items: snapshot.questions_to_sit_with || snapshot.questionsToSitWith || snapshot.evidenceQuestions || snapshot.evidence_questions || snapshot.proofQuestions || snapshot.proof_questions || snapshot.validationQuestions || snapshot.validation_questions || snapshot.decisionQuestions || snapshot.decision_questions || snapshot.questionsToAnswer || snapshot.questions_to_answer || snapshot.followupQuestions || snapshot.followup_questions || snapshot.openQuestions || snapshot.open_questions, sourceSection: 'snapshot.questionsToSitWith' }, - { items: envelope.questions_to_sit_with || envelope.questionsToSitWith || envelope.evidenceQuestions || envelope.evidence_questions || envelope.proofQuestions || envelope.proof_questions || envelope.validationQuestions || envelope.validation_questions || envelope.decisionQuestions || envelope.decision_questions || envelope.questionsToAnswer || envelope.questions_to_answer || envelope.followupQuestions || envelope.followup_questions || envelope.openQuestions || envelope.open_questions, sourceSection: 'ranker-input.questionsToSitWith' }, - { items: featureSet.questions_to_sit_with || featureSet.questionsToSitWith || featureSet.evidenceQuestions || featureSet.evidence_questions || featureSet.proofQuestions || featureSet.proof_questions || featureSet.validationQuestions || featureSet.validation_questions || featureSet.decisionQuestions || featureSet.decision_questions || featureSet.questionsToAnswer || featureSet.questions_to_answer || featureSet.followupQuestions || featureSet.followup_questions || featureSet.openQuestions || featureSet.open_questions, sourceSection: 'feature-set.questionsToSitWith' }, - { items: body.questions_to_sit_with || body.questionsToSitWith || body.evidenceQuestions || body.evidence_questions || body.proofQuestions || body.proof_questions || body.validationQuestions || body.validation_questions || body.decisionQuestions || body.decision_questions || body.questionsToAnswer || body.questions_to_answer || body.followupQuestions || body.followup_questions || body.openQuestions || body.open_questions, sourceSection: 'questionsToSitWith' }, - ]); - const questionOptions = optionsFromQuestionsToSitWith( - questionSource.items, - questionSource.sourceSection || 'questionsToSitWith', - 'Question to sit with' - ); if (actionThreadOptions.length >= 2) return normalizeCandidateGroup([{ items: actionThreadOptions, sourceSection: actionThreadSource.sourceSection || 'threadsToHold' }]); if (actionThreadOptions.length === 1 && questionOptions.length) { return normalizeCandidateGroup([