From 0271bfcbf6750b08d40d9ce3c9813587df059f37 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Wed, 27 May 2026 16:53:58 +0200 Subject: [PATCH] Accept rank-ready candidate action aliases --- public/app.js | 2 + scripts/check-rank-feedback.mjs | 70 ++++++++++++++++++++++++++++++++- server.js | 11 ++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/public/app.js b/public/app.js index 10a2fd6..961ee31 100644 --- a/public/app.js +++ b/public/app.js @@ -68,6 +68,8 @@ function parsePastedJsonPayload(value) { || parsed.reference_code || parsed.referenceCode || parsed.artifactId || parsed.sourceArtifactId || parsed.source_artifact_id || parsed.ideaText || parsed.idea_text || parsed.originalPrompt || parsed.original_prompt || parsed.sourceSummary || parsed.source_summary || parsed.opening_reflection || parsed.restated_idea || Array.isArray(parsed.features) || Array.isArray(parsed.actions) || Array.isArray(parsed.candidates) + || Array.isArray(parsed.candidateActions) || Array.isArray(parsed.candidate_actions) || Array.isArray(parsed.candidateMoves) || Array.isArray(parsed.candidate_moves) + || Array.isArray(parsed.rankReadyActions) || Array.isArray(parsed.rank_ready_actions) || Array.isArray(parsed.recommendedActions) || Array.isArray(parsed.recommended_actions) || Array.isArray(parsed.suggestedActions) || Array.isArray(parsed.suggested_actions) || Array.isArray(parsed.nextActions) || Array.isArray(parsed.next_actions) || Array.isArray(parsed.nextMoves) || Array.isArray(parsed.next_moves) || Array.isArray(parsed.doFirst) || Array.isArray(parsed.do_first) || Array.isArray(parsed.continueFirst) || Array.isArray(parsed.continue_first) || Array.isArray(parsed.makeTangible) || Array.isArray(parsed.make_tangible) diff --git a/scripts/check-rank-feedback.mjs b/scripts/check-rank-feedback.mjs index c3b0865..808193e 100644 --- a/scripts/check-rank-feedback.mjs +++ b/scripts/check-rank-feedback.mjs @@ -1414,7 +1414,75 @@ try { assert.equal(storedScattermindRow.handoff.readiness.status, 'ready'); assert.deepEqual(storedScattermindRow.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, nextStepsAliasTop: nextStepsAlias.ranked[0].id, summaryGuardrailTop: summaryGuardrail.ranked[0].id, bridgeEnvelopeTop: bridgeEnvelope.ranked[0].id, directEnvelopeSectionsTop: directEnvelopeSections.ranked[0].id, softDirectLaneAliasesTop: softDirectLaneAliases.ranked[0].id, threadsFallbackTop: threadsFallback.ranked[0].id, questionsFallbackTop: questionsFallback.ranked[0].id, freeSnapshotTop: freeSnapshot.ranked[0].id, storedScattermindRowTop: storedScattermindRow.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 candidateActionsAliasResponse = await fetch(`${base}/api/rank-feedback`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sourceName: 'Scattermind', + artifactId: 'concept_map_candidate_actions_aliases', + originalPrompt: 'Scattermind exported candidate_actions as the rank-ready action set after a Concept Map.', + idea: 'Ranker should accept candidate action set naming without making Scattermind rename it into features.', + context: { + targetAudience: 'Tired non-AI-native solo builder', + nonGoals: ['Avoid saved workspace dashboards before the first defended action works'], + }, + mode: 'mvp', + conceptMap: { + candidate_actions: [ + { id: 'candidate-manual-preview', action: 'Candidate manual preview', why: 'Turn the Concept Map into one defended active build-order slice.', evidence_needed: 'Can one tired user explain why this candidate should be first?', suggested_lane: 'do-first', source_item_id: 'candidate-action-1', source_title: 'Rank-ready candidate actions', ranker_hints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } }, + { id: 'candidate-copyable-handoff', action: 'Candidate copyable handoff', why: 'Make the defended order portable after the active slice is clear.', evidence_needed: 'Does the copied handoff preserve the first action and source?', suggested_lane: 'validate-next', source_item_id: 'candidate-action-2', source_title: 'Rank-ready candidate actions' }, + { id: 'candidate-workspace-dashboard', action: 'Candidate saved workspace dashboard', why: 'Accounts, saved projects, collaboration, and dashboard history before proof.', evidence_needed: 'No bridge proof yet.', suggested_lane: 'park', source_item_id: 'candidate-action-3', source_title: 'Rank-ready candidate actions' }, + ], + }, + }), + }); + assert.equal(candidateActionsAliasResponse.status, 200); + const candidateActionsAlias = await candidateActionsAliasResponse.json(); + assert.equal(candidateActionsAlias.input.optionCount, 3); + assert.equal(candidateActionsAlias.ranked[0].id, 'candidate-manual-preview'); + assert.equal(candidateActionsAlias.ranked[0].provenance.sourceSection, 'concept-map.candidateActions'); + assert.equal(candidateActionsAlias.ranked[0].provenance.sourceId, 'candidate-action-1'); + assert.equal(candidateActionsAlias.ranked[0].provenance.sourceTitle, 'Rank-ready candidate actions'); + assert.ok(['test', 'defer'].includes(candidateActionsAlias.ranked.find(item => item.id === 'candidate-copyable-handoff').lane.id)); + assert.equal(candidateActionsAlias.ranked.find(item => item.id === 'candidate-workspace-dashboard').lane.id, 'park'); + assert.equal(candidateActionsAlias.handoff.readiness.status, 'ready'); + assert.deepEqual(candidateActionsAlias.handoff.warnings, []); + + const rankReadyActionsEnvelopeResponse = await fetch(`${base}/api/rank-feedback`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + schema: 'scattermind-ranker-bridge-v1', + rank_ready: { + sourceName: 'Scattermind', + reference_code: 'SM-RANK-READY-ACTIONS-1', + working_name: 'Rank-ready action envelope', + ideaText: 'Scattermind wrapped a candidate action set under rank_ready with rank_ready_actions.', + context: { + targetAudience: 'Overwhelmed solo maker', + avoid: ['Avoid auth dashboards before action proof'], + }, + rank_ready_actions: [ + { id: 'rank-ready-action-proof', action: 'Rank-ready action proof', why: 'Accept the bridge naming directly and defend the first action.', evidence_needed: 'Can the action envelope produce a source-traced Do first?', suggested_lane: 'do-first', source_item_id: 'rank-ready-action-1', source_section: 'rank_ready.rank_ready_actions', ranker_hints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } }, + { id: 'rank-ready-action-copy', action: 'Rank-ready action copy brief', why: 'Preserve the defended order outside the app.', evidence_needed: 'Does copyable text keep the active action clear?', suggested_lane: 'validate-next', source_item_id: 'rank-ready-action-2', source_section: 'rank_ready.rank_ready_actions' }, + { id: 'rank-ready-action-dashboard', action: 'Rank-ready action dashboard', why: 'Auth dashboard and saved workspaces for every idea.', evidence_needed: 'No proof yet.', suggested_lane: 'park', source_item_id: 'rank-ready-action-3', source_section: 'rank_ready.rank_ready_actions' }, + ], + }, + }), + }); + assert.equal(rankReadyActionsEnvelopeResponse.status, 200); + const rankReadyActionsEnvelope = await rankReadyActionsEnvelopeResponse.json(); + assert.equal(rankReadyActionsEnvelope.input.provenance.artifactId, 'SM-RANK-READY-ACTIONS-1'); + assert.equal(rankReadyActionsEnvelope.input.provenance.snapshotTitle, 'Rank-ready action envelope'); + assert.equal(rankReadyActionsEnvelope.input.optionCount, 3); + assert.equal(rankReadyActionsEnvelope.ranked[0].id, 'rank-ready-action-proof'); + assert.equal(rankReadyActionsEnvelope.ranked[0].provenance.sourceSection, 'rank_ready.rank_ready_actions'); + assert.ok(['test', 'defer'].includes(rankReadyActionsEnvelope.ranked.find(item => item.id === 'rank-ready-action-copy').lane.id)); + assert.equal(rankReadyActionsEnvelope.ranked.find(item => item.id === 'rank-ready-action-dashboard').lane.id, 'park'); + assert.equal(rankReadyActionsEnvelope.handoff.readiness.status, 'ready'); + assert.deepEqual(rankReadyActionsEnvelope.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, nextStepsAliasTop: nextStepsAlias.ranked[0].id, summaryGuardrailTop: summaryGuardrail.ranked[0].id, bridgeEnvelopeTop: bridgeEnvelope.ranked[0].id, directEnvelopeSectionsTop: directEnvelopeSections.ranked[0].id, softDirectLaneAliasesTop: softDirectLaneAliases.ranked[0].id, threadsFallbackTop: threadsFallback.ranked[0].id, questionsFallbackTop: questionsFallback.ranked[0].id, freeSnapshotTop: freeSnapshot.ranked[0].id, storedScattermindRowTop: storedScattermindRow.ranked[0].id, candidateActionsAliasTop: candidateActionsAlias.ranked[0].id, rankReadyActionsEnvelopeTop: rankReadyActionsEnvelope.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 0037b1a..90093e4 100644 --- a/server.js +++ b/server.js @@ -580,6 +580,12 @@ function looksLikeRankPayload(value = {}) { || Array.isArray(value.nextMoves) || Array.isArray(value.next_moves) || Array.isArray(value.candidates) + || Array.isArray(value.candidateActions) + || Array.isArray(value.candidate_actions) + || Array.isArray(value.candidateMoves) + || Array.isArray(value.candidate_moves) + || Array.isArray(value.rankReadyActions) + || Array.isArray(value.rank_ready_actions) || Array.isArray(value.doFirst) || Array.isArray(value.do_first) || Array.isArray(value.continueFirst) @@ -1134,6 +1140,9 @@ function optionsFromBody(body = {}) { { items: body.candidates, sourceSection: 'candidates' }, { items: envelope.candidates, sourceSection: 'ranker-input.candidates' }, { items: featureSet.candidates, sourceSection: 'feature-set.candidates' }, + { items: body.candidateActions || body.candidate_actions || body.candidateMoves || body.candidate_moves || body.rankReadyActions || body.rank_ready_actions, sourceSection: 'candidateActions' }, + { items: envelope.candidateActions || envelope.candidate_actions || envelope.candidateMoves || envelope.candidate_moves || envelope.rankReadyActions || envelope.rank_ready_actions, sourceSection: 'ranker-input.candidateActions' }, + { items: featureSet.candidateActions || featureSet.candidate_actions || featureSet.candidateMoves || featureSet.candidate_moves || featureSet.rankReadyActions || featureSet.rank_ready_actions, sourceSection: 'feature-set.candidateActions' }, { items: body.doFirst || body.do_first || body.buildFirst || body.build_first || body.buildNow || body.build_now || body.continueFirst || body.continue_first || body.makeTangible || body.make_tangible || body.startHere || body.start_here, sourceSection: 'doFirst', defaultLane: 'do-first' }, { items: envelope.doFirst || envelope.do_first || envelope.buildFirst || envelope.build_first || envelope.buildNow || envelope.build_now || envelope.continueFirst || envelope.continue_first || envelope.makeTangible || envelope.make_tangible || envelope.startHere || envelope.start_here, sourceSection: 'ranker-input.doFirst', defaultLane: 'do-first' }, { items: featureSet.doFirst || featureSet.do_first || featureSet.buildFirst || featureSet.build_first || featureSet.buildNow || featureSet.build_now || featureSet.continueFirst || featureSet.continue_first || featureSet.makeTangible || featureSet.make_tangible || featureSet.startHere || featureSet.start_here, sourceSection: 'feature-set.doFirst', defaultLane: 'do-first' }, @@ -1161,6 +1170,7 @@ function optionsFromBody(body = {}) { { 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.candidateActions || conceptMap.candidate_actions || conceptMap.candidateMoves || conceptMap.candidate_moves || conceptMap.rankReadyActions || conceptMap.rank_ready_actions, sourceSection: 'concept-map.candidateActions' }, { items: conceptMap.doFirst || conceptMap.do_first || conceptMap.buildFirst || conceptMap.build_first || conceptMap.buildNow || conceptMap.build_now || conceptMap.continueFirst || conceptMap.continue_first || conceptMap.makeTangible || conceptMap.make_tangible || conceptMap.startHere || conceptMap.start_here, sourceSection: 'concept-map.doFirst', defaultLane: 'do-first' }, { items: conceptMap.validateNext || conceptMap.validate_next || conceptMap.validate || conceptMap.validation || conceptMap.evidenceNext || conceptMap.evidence_next || conceptMap.tryNext || conceptMap.try_next || conceptMap.learnNext || conceptMap.learn_next || conceptMap.testManually || conceptMap.test_manually, sourceSection: 'concept-map.validateNext', defaultLane: 'validate-next' }, { items: conceptMap.experiments, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' }, @@ -1175,6 +1185,7 @@ function optionsFromBody(body = {}) { { items: snapshot.actions, sourceSection: 'snapshot.actions' }, { items: snapshot.features, sourceSection: 'snapshot.features' }, { items: snapshot.candidates, sourceSection: 'snapshot.candidates' }, + { items: snapshot.candidateActions || snapshot.candidate_actions || snapshot.candidateMoves || snapshot.candidate_moves || snapshot.rankReadyActions || snapshot.rank_ready_actions, sourceSection: 'snapshot.candidateActions' }, { items: snapshot.doFirst || snapshot.do_first || snapshot.buildFirst || snapshot.build_first || snapshot.buildNow || snapshot.build_now || snapshot.continueFirst || snapshot.continue_first || snapshot.makeTangible || snapshot.make_tangible || snapshot.startHere || snapshot.start_here, sourceSection: 'snapshot.doFirst', defaultLane: 'do-first' }, { items: snapshot.validateNext || snapshot.validate_next || snapshot.validate || snapshot.validation || snapshot.evidenceNext || snapshot.evidence_next || snapshot.tryNext || snapshot.try_next || snapshot.learnNext || snapshot.learn_next || snapshot.testManually || snapshot.test_manually, sourceSection: 'snapshot.validateNext', defaultLane: 'validate-next' }, { items: snapshot.experiments, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },