675 lines
48 KiB
JavaScript
675 lines
48 KiB
JavaScript
import assert from 'node:assert/strict';
|
||
import { spawn } from 'node:child_process';
|
||
|
||
const port = 43045 + Math.floor(Math.random() * 1000);
|
||
const base = `http://127.0.0.1:${port}`;
|
||
const server = spawn(process.execPath, ['server.js'], {
|
||
cwd: new URL('..', import.meta.url),
|
||
env: { ...process.env, PORT: String(port), APPWRITE_ENDPOINT: '', APPWRITE_PROJECT_ID: '', APPWRITE_API_KEY: '' },
|
||
stdio: ['ignore', 'pipe', 'pipe'],
|
||
});
|
||
|
||
let output = '';
|
||
server.stdout.on('data', chunk => { output += chunk; });
|
||
server.stderr.on('data', chunk => { output += chunk; });
|
||
|
||
async function waitForServer() {
|
||
const deadline = Date.now() + 6000;
|
||
while (Date.now() < deadline) {
|
||
try {
|
||
const response = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ optionsText: '- one\n- two' }),
|
||
});
|
||
if (response.status < 500) return;
|
||
} catch {
|
||
await new Promise(resolve => setTimeout(resolve, 120));
|
||
}
|
||
}
|
||
throw new Error(`server did not become ready:\n${output}`);
|
||
}
|
||
|
||
try {
|
||
await waitForServer();
|
||
const response = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
schema: 'prioritix-feature-set-v1',
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'snapshot_123',
|
||
snapshotTitle: 'Tiny shop idea clarity pass',
|
||
originalPrompt: 'I have a tiny shop idea and do not know what to build first.',
|
||
idea: 'Scattermind clarified a small product idea. Ranker must defend the next build order, not create a dashboard.',
|
||
context: 'Solo builder. Need a rank-ready build order after Snapshot / Concept Map. Avoid accounts, workspaces, and team voting.',
|
||
mode: 'mvp',
|
||
featureSet: {
|
||
features: [
|
||
{ id: 'bridge-contract', title: 'Snapshot to Ranker feature-set contract', description: 'Convert Concept Map next moves into a rank-ready feature set with provenance.', userValue: 'Preserves the user prompt and source artifact so build order is defensible.', evidenceNeeded: 'Can one generated Concept Map create 3-7 sane next moves with source sections?', proofSteps: ['Run one fixture through /api/rank-feedback'], recommendedLane: 'do-first', sourceSection: 'concept-map.nextMoves' },
|
||
{ id: 'build-order-preview', title: 'Build order preview', description: 'Show do first, validate next, defer, and park with reasons.', userValue: 'A tired builder sees the next move without opening a dashboard.', evidenceNeeded: 'Can 3 non-AI-native users understand the first recommended action?', proofSteps: ['Show a static result screen to 3 people'], recommendedLane: 'validate-next', sourceSection: 'concept-map.nextMoves' },
|
||
{ id: 'workspace', title: 'Accounts and saved workspaces', description: 'Full dashboard with auth, workspace collaboration, team voting, and sync.', dependencies: ['auth', 'teams', 'saved projects', 'sync'], risk: 'Turns the bridge into generic dashboard swamp before the first proof.', sourceSection: 'concept-map.deferred' },
|
||
{ id: 'billing', title: 'Subscription billing layer', description: 'Pricing, checkout, invoices, account plans, and admin controls.', dependencies: ['account model', 'checkout', 'fulfillment'], risk: 'Payment machinery before the continuation value is proven.', sourceSection: 'concept-map.deferred' },
|
||
{ id: 'export', title: 'Exportable decision brief', description: 'Simple brief for sharing the defended build order.', evidenceNeeded: 'Does a plain brief help a user act within 48 hours?', recommendedLane: 'validate-next', sourceSection: 'concept-map.nextMoves' },
|
||
{ id: 'parked-bridge-dashboard', title: 'Saved Snapshot dashboard with provenance', description: 'Looks bridge-adjacent, but Scattermind already marked it as not-now because it needs auth, saved workspaces, and collaboration first.', recommendedLane: 'park', sourceSection: 'concept-map.parkingLot' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(response.status, 200);
|
||
const data = await response.json();
|
||
assert.equal(data.ok, true);
|
||
assert.equal(data.input.provenance.artifactId, 'snapshot_123');
|
||
assert.equal(data.input.provenance.source, 'Scattermind');
|
||
assert.match(data.input.provenance.originalPrompt, /tiny shop idea/);
|
||
assert.equal(data.ranked.length, 6);
|
||
assert.deepEqual(Object.keys(data.buildOrder), ['doFirst', 'validateNext', 'defer', 'park']);
|
||
assert.equal(data.ranked[0].id, data.buildOrder.doFirst[0]);
|
||
assert.equal(data.buildOrderDetails.doFirst[0].id, data.buildOrder.doFirst[0]);
|
||
assert.equal(data.buildOrderDetails.doFirst[0].sourceSection, 'concept-map.nextMoves');
|
||
assert.equal(data.buildOrderDetails.doFirst[0].laneSource, 'ranked');
|
||
assert.equal(typeof data.buildOrderDetails.doFirst[0].score, 'number');
|
||
assert.equal(typeof data.buildOrderDetails.doFirst[0].confidence, 'number');
|
||
assert.match(data.buildOrderDetails.doFirst[0].nextStep, /manual proof/i);
|
||
assert.notEqual(data.ranked[0].id, 'workspace', 'dashboard swamp must not win the bridge fixture');
|
||
assert.ok(['defer', 'park'].includes(data.ranked.find(item => item.id === 'workspace').lane.id));
|
||
assert.ok(['defer', 'park'].includes(data.ranked.find(item => item.id === 'billing').lane.id));
|
||
assert.equal(data.ranked.find(item => item.id === 'parked-bridge-dashboard').lane.id, 'park', 'explicit Scattermind park hints must stay out of the active proof slice');
|
||
assert.equal(data.ranked.find(item => item.id === 'parked-bridge-dashboard').lane.source, 'hint');
|
||
assert.equal(data.ranked.find(item => item.id === 'bridge-contract').provenance.sourceSection, 'concept-map.nextMoves');
|
||
assert.match(data.ranked.find(item => item.id === 'bridge-contract').factors.evidenceNeeded, /Concept Map/);
|
||
assert.ok(data.ranked.find(item => item.id === 'bridge-contract').factors.metricHints.value === undefined);
|
||
assert.equal(data.brief.source.artifactId, 'snapshot_123');
|
||
assert.match(data.brief.summary, /Source: Tiny shop idea clarity pass · snapshot_123/);
|
||
assert.equal(data.handoff.schema, 'rank-feedback-result-v1');
|
||
assert.equal(data.handoff.source.artifactId, 'snapshot_123');
|
||
assert.equal(data.handoff.source.hasOriginalPrompt, true);
|
||
assert.equal(data.handoff.source.requiresSourceTrace, true);
|
||
assert.equal(data.handoff.itemTrace.length, data.ranked.length);
|
||
assert.equal(data.handoff.itemTrace.find(item => item.id === 'bridge-contract').sourceSection, 'concept-map.nextMoves');
|
||
assert.ok(data.input.decisionContext.constraints.includes('Solo builder'));
|
||
assert.ok(data.input.decisionContext.nonGoals.includes('Avoid accounts, workspaces, and team voting'));
|
||
assert.deepEqual(data.handoff.decisionContext.nonGoals, ['Avoid accounts, workspaces, and team voting']);
|
||
assert.ok(data.ranked.find(item => item.id === 'workspace').metrics.nonGoalConflicts.length >= 1, 'flat text avoid guardrails should protect against workspace candidates');
|
||
assert.deepEqual(data.handoff.warnings, []);
|
||
assert.ok(data.brief.next48Hours.some(item => /Open the source artifact \(snapshot_123\)/i.test(item)));
|
||
assert.ok(data.brief.next48Hours.some(item => /Evidence to collect/i.test(item)));
|
||
assert.match(data.brief.summary, /nearest follow-up|strongest signal/i);
|
||
assert.ok(data.brief.whatWouldChangeRanking.some(item => /evidence fails|re-run the order/i.test(item)));
|
||
assert.ok(Array.isArray(data.brief.assumptions));
|
||
|
||
const messyIdeaOnlyResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
idea: 'I’m building a tool that helps freelancers package their services and decide what to sell first. Maybe offer critique, pricing calculator, proposal generator, landing page copywriter, client persona mapper, and some kind of dashboard later? I only have a week and want the fastest useful proof.',
|
||
context: 'Solo builder. Manual proof first. Avoid dashboards, accounts, and saved workspaces before evidence.',
|
||
mode: 'validation',
|
||
}),
|
||
});
|
||
assert.equal(messyIdeaOnlyResponse.status, 200);
|
||
const messyIdeaOnly = await messyIdeaOnlyResponse.json();
|
||
assert.equal(messyIdeaOnly.input.optionCount, 6, 'idea-only messy dumps should be split into rank-ready candidates');
|
||
assert.ok(messyIdeaOnly.ranked.some(item => /Offer critique/i.test(item.title)));
|
||
assert.ok(messyIdeaOnly.ranked.some(item => /Pricing calculator/i.test(item.title)));
|
||
assert.equal(messyIdeaOnly.ranked.find(item => /dashboard/i.test(item.title)).lane.source, 'source-non-goal');
|
||
assert.ok(!/dashboard/i.test(messyIdeaOnly.ranked[0].title), 'dashboard-flavored candidate must not win tired-user first pass');
|
||
assert.equal(messyIdeaOnly.handoff.source.requiresSourceTrace, false);
|
||
assert.ok(!messyIdeaOnly.handoff.warnings.some(item => /missing source section|missing original prompt/.test(item)));
|
||
assert.ok(messyIdeaOnly.handoff.warnings.includes('missing source artifact id'));
|
||
|
||
const hintedResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
schema: 'prioritix-feature-set-v1',
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_metric_hints',
|
||
idea: 'A concept map produced possible next moves for an overwhelmed solo builder.',
|
||
context: 'Defend build order from explicit Scattermind scoring hints plus text; do not let flashy platform language win.',
|
||
mode: 'mvp',
|
||
featureSet: {
|
||
features: [
|
||
{ id: 'manual-concierge-proof', title: 'Manual concierge proof', description: 'Personally rank three real idea snapshots and turn each into a small build order preview.', rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 }, evidenceNeeded: 'Will a tired user act on the first recommended move?', proofSteps: ['Run 3 manual previews'] },
|
||
{ id: 'ai-autopilot-roadmap', title: 'AI autopilot roadmap platform', description: 'Generate a full automated roadmap with dashboard, workspace, team voting, sync, and integrations.', rankerHints: { value: 5, effort: 9, confidence: 3, urgency: 3, risk: 9 } },
|
||
{ id: 'nice-export', title: 'Clean export of the build order', description: 'Turn the defended order into a shareable text brief.', scoring: { impact: 7, complexity: 3, certainty: 7, timing: 5, assumptionRisk: 3 }, recommendedLane: 'validate-next' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(hintedResponse.status, 200);
|
||
const hinted = await hintedResponse.json();
|
||
assert.equal(hinted.ranked[0].id, 'manual-concierge-proof', 'explicit low-effort/high-confidence Scattermind hints should defend the manual proof slice');
|
||
assert.ok(hinted.ranked[0].factors.metricHints.value >= 9);
|
||
assert.ok(hinted.ranked.find(item => item.id === 'ai-autopilot-roadmap').metrics.risk > hinted.ranked[0].metrics.risk);
|
||
|
||
const actionsResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
schema: 'prioritix-feature-set-v1',
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_actions',
|
||
snapshotTitle: 'Workshop idea continuation',
|
||
originalPrompt: 'I want to turn a workshop idea into the first useful thing to build.',
|
||
idea: 'Scattermind emitted next actions rather than feature objects; Ranker should still defend build order.',
|
||
context: 'Candidate action set from Concept Map. Keep it action-first and avoid generic workspace layers.',
|
||
mode: 'mvp',
|
||
featureSet: {
|
||
actions: [
|
||
{ id: 'manual-preview', action: 'Manual build-order preview', why: 'A user sees one defended next move before any app machinery exists.', evidence: 'Can two tired users explain what to do next?', validationSteps: ['Create one static preview from the Concept Map'], suggestedLane: 'do-first', sourceSection: 'concept-map.nextActions' },
|
||
{ id: 'saved-workspace', action: 'Saved project workspace', why: 'Keep every idea and roadmap in an account dashboard.', dependencies: ['auth', 'database permissions', 'workspace model', 'sync'], risk: 'Dashboard swamp before the continuation proof.', suggestedLane: 'park', sourceSection: 'concept-map.parkingLot' },
|
||
{ id: 'text-export', action: 'Copyable decision brief', why: 'Let the user paste the defended order into notes or a chat.', evidence: 'Does a plain text brief help them act within 48 hours?', validationSteps: ['Export the top lane and concerns as text'], suggestedLane: 'validate-next', sourceSection: 'concept-map.nextActions' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(actionsResponse.status, 200);
|
||
const actions = await actionsResponse.json();
|
||
assert.equal(actions.input.optionCount, 3);
|
||
assert.equal(actions.ranked[0].id, 'manual-preview', 'action-shaped Concept Map next moves should be rankable without a features wrapper');
|
||
assert.equal(actions.ranked.find(item => item.id === 'manual-preview').provenance.sourceSection, 'concept-map.nextActions');
|
||
assert.equal(actions.ranked.find(item => item.id === 'saved-workspace').lane.id, 'park');
|
||
assert.deepEqual(actions.handoff.warnings, []);
|
||
|
||
const nestedConceptResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
idea: 'Scattermind produced a Concept Map artifact with nested next actions.',
|
||
context: 'Ranker should consume the artifact directly and preserve provenance without asking Scattermind to rename actions as features.',
|
||
mode: 'mvp',
|
||
conceptMap: {
|
||
id: 'concept_map_nested_42',
|
||
snapshotTitle: 'Course idea continuation',
|
||
originalPrompt: 'I have a course idea and need the first build step.',
|
||
nextActions: [
|
||
{ id: 'landing-proof', action: 'One-page promise test', why: 'A tired creator can see whether the promise lands before building lessons.', evidence: 'Can 5 target users describe the promised outcome?', validationSteps: ['Write the promise', 'Ask 5 target users'], rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 }, suggestedLane: 'do-first' },
|
||
{ id: 'lesson-library', action: 'Full lesson library', why: 'Build every module, account dashboard, saved progress, and team workspace.', dependencies: ['auth', 'content system', 'progress tracking', 'workspace'], risk: 'Large platform before promise proof.', suggestedLane: 'park' },
|
||
{ id: 'copyable-plan', action: 'Copyable build order brief', why: 'Gives the creator a concrete next step to paste into notes.', evidence: 'Does the brief trigger one real follow-up action?', suggestedLane: 'validate-next' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(nestedConceptResponse.status, 200);
|
||
const nestedConcept = await nestedConceptResponse.json();
|
||
assert.equal(nestedConcept.input.provenance.artifactId, 'concept_map_nested_42');
|
||
assert.equal(nestedConcept.input.provenance.conceptMapId, 'concept_map_nested_42');
|
||
assert.equal(nestedConcept.handoff.source.hasOriginalPrompt, true);
|
||
assert.equal(nestedConcept.ranked[0].id, 'landing-proof');
|
||
assert.equal(nestedConcept.ranked.find(item => item.id === 'landing-proof').provenance.sourceSection, 'concept-map.nextActions');
|
||
assert.equal(nestedConcept.ranked.find(item => item.id === 'lesson-library').lane.id, 'park');
|
||
assert.deepEqual(nestedConcept.handoff.warnings, []);
|
||
|
||
const sectionedConceptResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_sectioned_lanes',
|
||
originalPrompt: 'The Concept Map separates next actions, validation ideas, deferred items, and a parking lot.',
|
||
idea: 'Ranker should combine a sectioned Concept Map into one defended build order without losing lane provenance.',
|
||
mode: 'mvp',
|
||
conceptMap: {
|
||
nextActions: [
|
||
{ id: 'one-source-preview', action: 'One-source build-order preview', why: 'Turn the Concept Map into a defended first move.', evidence: 'Can one tired user say what to do next?', rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } },
|
||
],
|
||
validateNext: [
|
||
{ id: 'copyable-handoff', action: 'Copyable handoff brief', why: 'Let the user paste the build order into notes.', evidence: 'Does the copied brief preserve the action and reason?' },
|
||
],
|
||
deferred: [
|
||
{ id: 'visual-polish-pass', action: 'Visual polish pass', why: 'Improve the result screen after the bridge proves useful.', evidence: 'Do users understand the rough brief first?' },
|
||
],
|
||
parkingLot: [
|
||
{ id: 'saved-team-workspace', action: 'Saved team workspace', why: 'Accounts, auth dashboard, collaboration, and sync for every idea.', evidence: 'No proof yet' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(sectionedConceptResponse.status, 200);
|
||
const sectionedConcept = await sectionedConceptResponse.json();
|
||
assert.equal(sectionedConcept.input.optionCount, 4);
|
||
assert.equal(sectionedConcept.ranked[0].id, 'one-source-preview');
|
||
assert.equal(sectionedConcept.ranked.find(item => item.id === 'copyable-handoff').lane.id, 'test', 'validateNext sections should default into Validate next');
|
||
assert.equal(sectionedConcept.ranked.find(item => item.id === 'copyable-handoff').lane.source, 'hint');
|
||
assert.equal(sectionedConcept.ranked.find(item => item.id === 'visual-polish-pass').lane.id, 'defer', 'deferred sections should not enter the active proof slice');
|
||
assert.equal(sectionedConcept.ranked.find(item => item.id === 'saved-team-workspace').lane.id, 'park', 'parking lot sections should stay parked');
|
||
assert.equal(sectionedConcept.handoff.itemTrace.find(item => item.id === 'saved-team-workspace').sourceSection, 'concept-map.parkingLot');
|
||
assert.deepEqual(sectionedConcept.handoff.warnings, []);
|
||
|
||
const emptyWrapperResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_empty_wrappers',
|
||
originalPrompt: 'The export includes empty feature arrays plus real Concept Map next actions.',
|
||
idea: 'Ranker should ignore empty wrapper arrays and rank the real nested action set.',
|
||
mode: 'mvp',
|
||
features: [],
|
||
featureSet: { features: [], actions: [] },
|
||
conceptMap: {
|
||
nextActions: [
|
||
{ id: 'source-action-proof', action: 'Source action proof', why: 'Use the Concept Map next action directly to defend the first move.', evidence: 'Can this action become a build-order preview?', suggestedLane: 'do-first' },
|
||
{ id: 'source-action-export', action: 'Copyable action brief', why: 'Keep the handoff artifact-shaped for a tired user.', evidence: 'Can the brief be pasted into notes?', suggestedLane: 'validate-next' },
|
||
{ id: 'empty-shadow-dashboard', action: 'Saved dashboard after empty import', why: 'Auth workspace dashboard that should not win.', suggestedLane: 'park' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(emptyWrapperResponse.status, 200);
|
||
const emptyWrapper = await emptyWrapperResponse.json();
|
||
assert.equal(emptyWrapper.input.optionCount, 3);
|
||
assert.equal(emptyWrapper.ranked[0].id, 'source-action-proof', 'empty top-level arrays must not shadow nested Concept Map actions');
|
||
assert.equal(emptyWrapper.ranked.find(item => item.id === 'source-action-proof').provenance.sourceSection, 'concept-map.nextActions');
|
||
assert.deepEqual(emptyWrapper.handoff.warnings, []);
|
||
|
||
const nonGoalResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_non_goals',
|
||
originalPrompt: 'I clarified a tiny service idea and need the first build order.',
|
||
idea: 'Ranker should respect Concept Map non-goals when defending the build order.',
|
||
mode: 'mvp',
|
||
conceptMap: {
|
||
snapshotTitle: 'Non-goal guarded continuation',
|
||
context: {
|
||
targetAudience: 'Tired non-AI-native solo operator',
|
||
constraints: ['No account before first value', 'Manual proof is acceptable'],
|
||
nonGoals: ['Avoid saved workspaces', 'No auth dashboard', 'No billing layer before proof'],
|
||
},
|
||
nextActions: [
|
||
{ id: 'workspace-autopilot', action: 'Saved workspace autopilot', why: 'Build accounts, auth dashboard, saved workspaces, and team sync.', evidence: 'None yet', suggestedLane: 'do-first', rankerHints: { value: 10, effort: 2, confidence: 9, urgency: 9, risk: 2 } },
|
||
{ id: 'manual-next-move', action: 'Manual next-move build order preview', why: 'Turn one source artifact into a defended first action and rank-ready build order without accounts.', evidence: 'Can 3 users act on the first move?', validationSteps: ['Create one static brief'], suggestedLane: 'do-first', rankerHints: { value: 9, effort: 1, confidence: 9, urgency: 9, risk: 1 } },
|
||
{ id: 'copy-brief', action: 'Copyable build-order brief', why: 'Give the user a plain artifact they can paste into notes.', evidence: 'Does copy/paste preserve the next step?', suggestedLane: 'validate-next' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(nonGoalResponse.status, 200);
|
||
const nonGoal = await nonGoalResponse.json();
|
||
assert.equal(nonGoal.input.decisionContext.targetAudience, 'Tired non-AI-native solo operator');
|
||
assert.deepEqual(nonGoal.input.decisionContext.nonGoals, ['Avoid saved workspaces', 'No auth dashboard', 'No billing layer before proof']);
|
||
assert.ok(nonGoal.brief.assumptions.includes('Constraint: No account before first value'));
|
||
assert.ok(nonGoal.brief.assumptions.includes('Non-goal: Avoid saved workspaces'));
|
||
assert.equal(nonGoal.ranked[0].id, 'manual-next-move', 'non-goal conflicts should beat flashy positive hints');
|
||
const workspace = nonGoal.ranked.find(item => item.id === 'workspace-autopilot');
|
||
assert.ok(workspace.metrics.nonGoalConflicts.length >= 2);
|
||
assert.match(workspace.concern, /Source context says not to do this yet/);
|
||
assert.equal(workspace.lane.id, 'defer', 'source non-goals should keep conflicted candidates out of the active proof slice even with strong hints');
|
||
assert.equal(workspace.lane.source, 'source-non-goal');
|
||
assert.deepEqual(nonGoal.handoff.itemTrace.find(item => item.id === 'workspace-autopilot').nonGoalConflicts, workspace.metrics.nonGoalConflicts);
|
||
assert.ok(!nonGoal.buildOrder.doFirst.includes('workspace-autopilot'));
|
||
assert.ok(!nonGoal.buildOrder.validateNext.includes('workspace-autopilot'));
|
||
assert.ok(!nonGoal.handoff.warnings.some(item => /active item workspace-autopilot conflicts/.test(item)), 'conflicted candidates should be demoted before handoff warnings need to flag active-lane conflict');
|
||
|
||
const mixedWrapperResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_mixed_wrappers',
|
||
originalPrompt: 'The bridge export contains a normalized feature list plus Concept Map validation and parking sections.',
|
||
idea: 'Ranker should not lose sectioned Concept Map context when a partial feature wrapper is also present.',
|
||
mode: 'mvp',
|
||
featureSet: {
|
||
features: [
|
||
{ id: 'manual-bridge-proof', title: 'Manual bridge proof', description: 'Turn one Concept Map into a defended build-order preview.', evidenceNeeded: 'Can one tired user act on the preview?', recommendedLane: 'do-first', rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } },
|
||
],
|
||
},
|
||
conceptMap: {
|
||
validateNext: [
|
||
{ id: 'copyable-brief-test', action: 'Copyable brief test', why: 'Check whether the decision brief remains useful outside Ranker.', evidence: 'Can the user paste it into notes and still know the next move?' },
|
||
],
|
||
parkingLot: [
|
||
{ id: 'account-dashboard', action: 'Account dashboard', why: 'Saved workspaces, auth, billing, and team sync.', evidence: 'No bridge proof yet' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(mixedWrapperResponse.status, 200);
|
||
const mixedWrapper = await mixedWrapperResponse.json();
|
||
assert.equal(mixedWrapper.input.optionCount, 3);
|
||
assert.equal(mixedWrapper.ranked[0].id, 'manual-bridge-proof');
|
||
assert.equal(mixedWrapper.handoff.itemTrace.find(item => item.id === 'manual-bridge-proof').sourceSection, 'feature-set.features');
|
||
assert.equal(mixedWrapper.ranked.find(item => item.id === 'copyable-brief-test').lane.id, 'test');
|
||
assert.equal(mixedWrapper.handoff.itemTrace.find(item => item.id === 'copyable-brief-test').sourceSection, 'concept-map.validateNext');
|
||
assert.equal(mixedWrapper.ranked.find(item => item.id === 'account-dashboard').lane.id, 'park');
|
||
assert.equal(mixedWrapper.handoff.itemTrace.find(item => item.id === 'account-dashboard').sourceSection, 'concept-map.parkingLot');
|
||
assert.deepEqual(mixedWrapper.handoff.warnings, []);
|
||
|
||
const laneAliasResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_lane_aliases',
|
||
originalPrompt: 'Scattermind exported normalized candidates with lane fields instead of recommendedLane.',
|
||
idea: 'Ranker should treat lane as a lane hint when it is do-first/validate-next/defer/park, while preserving the actual source section.',
|
||
mode: 'mvp',
|
||
featureSet: {
|
||
features: [
|
||
{ id: 'manual-bridge-proof', title: 'Manual bridge proof', description: 'Defended build order from one Concept Map.', evidenceNeeded: 'Can one tired user act on the first move?', lane: 'do-first', sourceSection: 'concept-map.nextActions', rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } },
|
||
{ id: 'copyable-decision-brief', title: 'Copyable decision brief', description: 'Export the defended order as plain text.', evidenceNeeded: 'Does the copied brief preserve the next action?', lane: 'validate-next', sourceSection: 'concept-map.nextActions' },
|
||
{ id: 'auth-workspace', title: 'Auth workspace', description: 'Accounts, dashboard, saved projects, and collaboration.', evidenceNeeded: 'No bridge proof yet.', lane: 'park', sourceSection: 'concept-map.parkingLot' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(laneAliasResponse.status, 200);
|
||
const laneAlias = await laneAliasResponse.json();
|
||
assert.equal(laneAlias.ranked[0].id, 'manual-bridge-proof');
|
||
assert.equal(laneAlias.ranked.find(item => item.id === 'manual-bridge-proof').provenance.sourceSection, 'concept-map.nextActions');
|
||
assert.equal(laneAlias.ranked.find(item => item.id === 'copyable-decision-brief').lane.id, 'test');
|
||
assert.equal(laneAlias.ranked.find(item => item.id === 'copyable-decision-brief').lane.source, 'hint');
|
||
assert.equal(laneAlias.ranked.find(item => item.id === 'auth-workspace').lane.id, 'park');
|
||
assert.equal(laneAlias.ranked.find(item => item.id === 'auth-workspace').lane.source, 'hint');
|
||
assert.equal(laneAlias.handoff.itemTrace.find(item => item.id === 'auth-workspace').sourceSection, 'concept-map.parkingLot');
|
||
assert.deepEqual(laneAlias.handoff.warnings, []);
|
||
|
||
const duplicateIdResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_duplicate_ids',
|
||
originalPrompt: 'I pasted duplicated next-move IDs from a messy Concept Map export.',
|
||
idea: 'Ranker should keep handoff rows addressable even if Scattermind sends duplicate candidate IDs.',
|
||
mode: 'mvp',
|
||
conceptMap: {
|
||
nextActions: [
|
||
{ id: 'preview', action: 'Manual build-order preview', why: 'Defends the first move without dashboard machinery.', evidence: 'Can one user act on the preview?', suggestedLane: 'do-first' },
|
||
{ id: 'preview', action: 'Copyable preview brief', why: 'Exports the defended order into notes.', evidence: 'Does the copied brief preserve the next action?', suggestedLane: 'validate-next' },
|
||
{ id: 'workspace', action: 'Saved workspace', why: 'Auth dashboard for every idea.', evidence: 'None yet', suggestedLane: 'park' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(duplicateIdResponse.status, 200);
|
||
const duplicateIds = await duplicateIdResponse.json();
|
||
assert.deepEqual(duplicateIds.ranked.map(item => item.id).sort(), ['preview', 'preview-2', 'workspace']);
|
||
assert.equal(new Set(duplicateIds.handoff.itemTrace.map(item => item.id)).size, duplicateIds.handoff.itemTrace.length);
|
||
assert.equal(duplicateIds.handoff.itemTrace.find(item => item.id === 'preview-2').originalId, 'preview');
|
||
assert.equal(duplicateIds.handoff.itemTrace.find(item => item.id === 'preview-2').idNormalized, true);
|
||
assert.ok(duplicateIds.handoff.warnings.some(item => /duplicate source id preview normalized to preview-2/.test(item)));
|
||
assert.ok(Object.values(duplicateIds.buildOrder).flat().includes('preview-2'));
|
||
|
||
const structuredContextResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_structured_context',
|
||
originalPrompt: 'Scattermind sent a structured context object instead of a flat context string.',
|
||
idea: 'Ranker should preserve structured context and not turn it into [object Object].',
|
||
mode: 'mvp',
|
||
context: {
|
||
summary: 'Bridge proof for a tired non-AI-native user.',
|
||
targetAudience: 'Tired non-AI-native solo builder',
|
||
constraints: ['No account before first value', 'Use a copyable artifact first'],
|
||
nonGoals: ['Avoid auth dashboard', 'Avoid saved workspaces'],
|
||
assumptions: ['Manual proof is acceptable for the first pass'],
|
||
},
|
||
conceptMap: {
|
||
nextActions: [
|
||
{ id: 'copyable-bridge-artifact', action: 'Copyable bridge artifact', why: 'Turn the source Concept Map into one defended build-order brief.', evidence: 'Can the user paste it and know the first move?', suggestedLane: 'do-first', rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } },
|
||
{ id: 'auth-dashboard', action: 'Auth dashboard', why: 'Accounts and saved workspaces for every idea.', evidence: 'None yet', suggestedLane: 'do-first', rankerHints: { value: 10, effort: 2, confidence: 9, urgency: 9, risk: 2 } },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(structuredContextResponse.status, 200);
|
||
const structuredContext = await structuredContextResponse.json();
|
||
assert.doesNotMatch(structuredContext.input.context, /\[object Object\]/);
|
||
assert.match(structuredContext.input.context, /Target audience: Tired non-AI-native solo builder/);
|
||
assert.deepEqual(structuredContext.input.decisionContext.constraints, ['No account before first value', 'Use a copyable artifact first']);
|
||
assert.deepEqual(structuredContext.handoff.decisionContext.nonGoals, ['Avoid auth dashboard', 'Avoid saved workspaces']);
|
||
assert.equal(structuredContext.ranked[0].id, 'copyable-bridge-artifact');
|
||
assert.equal(structuredContext.ranked.find(item => item.id === 'auth-dashboard').lane.source, 'source-non-goal');
|
||
|
||
const lensOnlyResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_lens_only',
|
||
originalPrompt: 'Scattermind produced the current paid Concept Map shape: lenses with a Build Order paragraph, not arrays.',
|
||
idea: 'Ranker should turn the Build Order lens into rank-ready candidates without asking Scattermind to rename fields first.',
|
||
mode: 'mvp',
|
||
conceptMap: {
|
||
snapshotTitle: 'Lens-only Concept Map bridge',
|
||
lenses: {
|
||
risk: {
|
||
title: 'What Can Mislead You',
|
||
content: 'Avoid saved workspaces and auth dashboard before one manual proof. Do not build billing yet.',
|
||
},
|
||
channel: {
|
||
title: 'Build Order',
|
||
content: 'Build first: Manual build-order preview from one Concept Map - prove the bridge before adding product machinery. Test manually: Copyable decision brief - show it to 3 tired users and ask what they would do next. Defer: Visual polish pass after the rough artifact is understood. Probably noise: Saved workspace dashboard with auth, billing, and team collaboration.',
|
||
},
|
||
},
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(lensOnlyResponse.status, 200);
|
||
const lensOnly = await lensOnlyResponse.json();
|
||
assert.equal(lensOnly.input.optionCount, 4);
|
||
assert.equal(lensOnly.ranked[0].id, 'build-order-1');
|
||
assert.equal(lensOnly.ranked[0].provenance.sourceSection, 'concept-map.lenses.channel');
|
||
assert.equal(lensOnly.ranked.find(item => item.id === 'build-order-2').lane.id, 'test');
|
||
assert.equal(lensOnly.ranked.find(item => item.id === 'build-order-3').lane.id, 'defer');
|
||
assert.equal(lensOnly.ranked.find(item => item.id === 'build-order-4').lane.id, 'park');
|
||
assert.ok(lensOnly.input.decisionContext.nonGoals.includes('Avoid saved workspaces and auth dashboard before one manual proof'));
|
||
assert.ok(lensOnly.input.decisionContext.nonGoals.includes('Do not build billing yet'));
|
||
assert.ok(lensOnly.ranked.find(item => item.id === 'build-order-4').metrics.nonGoalConflicts.length >= 1);
|
||
assert.deepEqual(lensOnly.handoff.warnings, []);
|
||
|
||
const scattermindPaidShapeResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
reference_code: 'SM-PAID1',
|
||
working_name: 'Neighborhood Supper Club',
|
||
ideaText: 'I want to test a tiny paid supper club before building a food community app.',
|
||
context: 'Solo operator. Manual proof first. Avoid accounts and saved workspaces before anyone pays.',
|
||
mode: 'mvp',
|
||
lenses: {
|
||
audience: 'First buyers are local neighbors who already attend small food events and want a low-pressure dinner plan.',
|
||
risk: 'Avoid accounts and saved workspaces before anyone pays. Do not build subscriptions or an app dashboard yet.',
|
||
channel: 'Build first: One manual supper-club offer page - collect three real yes/no replies before building machinery. Test manually: Concierge invitation script - ask ten neighbors if they would pay for the first dinner. Defer: Pretty event calendar after the first paid table. Probably noise: Saved member workspace with accounts, billing, subscriptions, and team dashboard.',
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(scattermindPaidShapeResponse.status, 200);
|
||
const scattermindPaidShape = await scattermindPaidShapeResponse.json();
|
||
assert.equal(scattermindPaidShape.input.provenance.artifactId, 'SM-PAID1');
|
||
assert.equal(scattermindPaidShape.input.provenance.snapshotTitle, 'Neighborhood Supper Club');
|
||
assert.match(scattermindPaidShape.input.provenance.originalPrompt, /supper club/);
|
||
assert.equal(scattermindPaidShape.input.optionCount, 4);
|
||
assert.equal(scattermindPaidShape.ranked[0].id, 'build-order-1');
|
||
assert.equal(scattermindPaidShape.ranked.find(item => item.id === 'build-order-4').lane.id, 'park');
|
||
assert.ok(scattermindPaidShape.input.decisionContext.nonGoals.includes('Avoid accounts and saved workspaces before anyone pays'));
|
||
assert.ok(scattermindPaidShape.ranked.find(item => item.id === 'build-order-4').metrics.nonGoalConflicts.length >= 1);
|
||
assert.deepEqual(scattermindPaidShape.handoff.warnings, []);
|
||
|
||
const mergedContextResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_merged_context',
|
||
originalPrompt: 'Scattermind sent summary context plus deeper Concept Map guardrails and lenses.',
|
||
idea: 'Ranker should merge context sources instead of letting a shallow top-level summary shadow Concept Map constraints.',
|
||
mode: 'mvp',
|
||
context: { summary: 'Continuation bridge for an overwhelmed non-AI-native operator.' },
|
||
conceptMap: {
|
||
context: {
|
||
targetAudience: 'Overwhelmed non-AI-native shop owner',
|
||
constraints: ['Manual proof before saved projects'],
|
||
nonGoals: ['Avoid workspace dashboard'],
|
||
assumptions: ['A copyable brief is enough for the first pass'],
|
||
},
|
||
lenses: {
|
||
constraints: { content: 'No auth before first value. Keep the result copyable.' },
|
||
assumptions: { content: 'The user is tired; one defended next move beats a backlog.' },
|
||
},
|
||
nextActions: [
|
||
{ id: 'defended-next-move', action: 'Defended next-move brief', why: 'One copyable build order with source provenance.', evidence: 'Can the shop owner name the next step?', suggestedLane: 'do-first', rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } },
|
||
{ id: 'workspace-dashboard', action: 'Workspace dashboard', why: 'Accounts, saved projects, and auth dashboard.', evidence: 'No proof yet', suggestedLane: 'do-first', rankerHints: { value: 10, effort: 2, confidence: 9, urgency: 9, risk: 2 } },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(mergedContextResponse.status, 200);
|
||
const mergedContext = await mergedContextResponse.json();
|
||
assert.equal(mergedContext.input.decisionContext.targetAudience, 'Overwhelmed non-AI-native shop owner');
|
||
assert.ok(mergedContext.input.decisionContext.constraints.includes('Manual proof before saved projects'));
|
||
assert.ok(mergedContext.input.decisionContext.constraints.includes('No auth before first value'));
|
||
assert.ok(mergedContext.input.decisionContext.nonGoals.includes('Avoid workspace dashboard'));
|
||
assert.ok(mergedContext.input.decisionContext.assumptions.includes('A copyable brief is enough for the first pass'));
|
||
assert.ok(mergedContext.input.decisionContext.assumptions.includes('The user is tired'));
|
||
assert.equal(mergedContext.ranked[0].id, 'defended-next-move');
|
||
assert.equal(mergedContext.ranked.find(item => item.id === 'workspace-dashboard').lane.source, 'source-non-goal');
|
||
assert.deepEqual(mergedContext.handoff.warnings, []);
|
||
|
||
const objectBuildOrderResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_object_build_order',
|
||
originalPrompt: 'Scattermind exported a Build Order object instead of nextActions arrays.',
|
||
idea: 'Ranker should accept an already-laned Build Order object and preserve lane provenance.',
|
||
mode: 'mvp',
|
||
conceptMap: {
|
||
snapshotTitle: 'Object Build Order bridge',
|
||
context: {
|
||
targetAudience: 'Tired local service seller',
|
||
constraints: ['Copyable output before accounts'],
|
||
nonGoals: ['Avoid saved workspace dashboard'],
|
||
},
|
||
buildOrder: {
|
||
doFirst: [
|
||
{ id: 'manual-offer-proof', move: 'Manual offer proof', why: 'Send one copyable offer to three real prospects.', questionToAnswer: 'Will one prospect ask for the next step?', nextAction: 'Send the offer to three named prospects before touching the page design.', successSignal: 'One prospect asks how to buy or book the next step.', killSignal: 'All three prospects understand it but none want the next step.' },
|
||
],
|
||
validateNext: [
|
||
'Copyable follow-up script',
|
||
],
|
||
deferred: [
|
||
{ id: 'visual-refresh', title: 'Visual refresh after proof', evidenceQuestion: 'Does the rough brief work first?' },
|
||
],
|
||
parkingLot: [
|
||
{ id: 'workspace-dashboard', title: 'Saved workspace dashboard', description: 'Accounts, auth, and saved projects for every offer.', evidenceNeeded: 'No proof yet' },
|
||
],
|
||
},
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(objectBuildOrderResponse.status, 200);
|
||
const objectBuildOrder = await objectBuildOrderResponse.json();
|
||
assert.equal(objectBuildOrder.input.optionCount, 4);
|
||
assert.equal(objectBuildOrder.ranked[0].id, 'manual-offer-proof');
|
||
assert.equal(objectBuildOrder.ranked[0].factors.evidenceNeeded, 'Will one prospect ask for the next step?');
|
||
assert.equal(objectBuildOrder.ranked[0].nextStep, 'Send the offer to three named prospects before touching the page design.');
|
||
assert.equal(objectBuildOrder.ranked[0].successSignal, 'One prospect asks how to buy or book the next step.');
|
||
assert.equal(objectBuildOrder.ranked[0].killSignal, 'All three prospects understand it but none want 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.ranked.find(item => item.id === 'feature-1').title, 'Copyable follow-up script');
|
||
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.ranked.find(item => item.id === 'workspace-dashboard').lane.id, 'park');
|
||
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'));
|
||
|
||
const embeddedJsonResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
idea: JSON.stringify({
|
||
reference_code: 'SM-PASTE1',
|
||
working_name: 'Raw pasted Concept Map',
|
||
ideaText: 'The user pasted a raw Scattermind Concept Map JSON blob into the public Ranker form.',
|
||
lenses: {
|
||
risk: 'Avoid saved workspaces before one copyable result lands.',
|
||
channel: 'Build first: Raw JSON handoff detection - turn the pasted Concept Map into a defended build order. Test manually: Copy the decision brief into notes and check whether the first move survives. Probably noise: Saved workspace with accounts and team dashboard.',
|
||
},
|
||
}),
|
||
mode: 'mvp',
|
||
context: '',
|
||
}),
|
||
});
|
||
assert.equal(embeddedJsonResponse.status, 200);
|
||
const embeddedJson = await embeddedJsonResponse.json();
|
||
assert.equal(embeddedJson.input.embeddedPayloadSource, 'idea');
|
||
assert.equal(embeddedJson.input.provenance.artifactId, 'SM-PASTE1');
|
||
assert.equal(embeddedJson.input.optionCount, 3);
|
||
assert.equal(embeddedJson.ranked[0].id, 'build-order-1');
|
||
assert.equal(embeddedJson.ranked.find(item => item.id === 'build-order-3').lane.id, 'park');
|
||
assert.ok(embeddedJson.input.decisionContext.nonGoals.includes('Avoid saved workspaces before one copyable result lands'));
|
||
assert.deepEqual(embeddedJson.handoff.warnings, []);
|
||
|
||
const fencedJsonResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
optionsText: `Here is the Scattermind export I copied:\n\n\`\`\`json\n${JSON.stringify({
|
||
reference_code: 'SM-FENCE1',
|
||
working_name: 'Fenced pasted Concept Map',
|
||
ideaText: 'The user copied a fenced Scattermind JSON export plus surrounding prose.',
|
||
lenses: {
|
||
risk: 'Avoid saved workspaces and account dashboard before the first manual proof.',
|
||
channel: 'Build first: Fenced JSON handoff - detect the pasted Concept Map and defend the first move. Test manually: Copy the ranked brief into notes and ask one user what they would do next. Probably noise: Account dashboard with saved workspaces and collaboration.',
|
||
},
|
||
})}\n\`\`\`\n\nPlease rank this.`,
|
||
mode: 'mvp',
|
||
}),
|
||
});
|
||
assert.equal(fencedJsonResponse.status, 200);
|
||
const fencedJson = await fencedJsonResponse.json();
|
||
assert.equal(fencedJson.input.embeddedPayloadSource, 'optionsText');
|
||
assert.equal(fencedJson.input.provenance.artifactId, 'SM-FENCE1');
|
||
assert.equal(fencedJson.input.optionCount, 3);
|
||
assert.equal(fencedJson.ranked[0].id, 'build-order-1');
|
||
assert.equal(fencedJson.ranked.find(item => item.id === 'build-order-3').lane.id, 'park');
|
||
assert.ok(fencedJson.input.decisionContext.nonGoals.includes('Avoid saved workspaces and account dashboard before the first manual proof'));
|
||
assert.deepEqual(fencedJson.handoff.warnings, []);
|
||
|
||
const sourceExcerptResponse = await fetch(`${base}/api/rank-feedback`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sourceName: 'Scattermind',
|
||
artifactId: 'concept_map_source_excerpts',
|
||
originalPrompt: 'Scattermind exported source excerpts for each recommended move.',
|
||
idea: 'Ranker should preserve source excerpts so the defended build order can point back to the exact Concept Map note.',
|
||
mode: 'mvp',
|
||
conceptMap: {
|
||
nextActions: [
|
||
{ id: 'manual-proof', action: 'Manual proof preview', why: 'Show one defended next move before adding machinery.', evidence: 'Can one tired user act on it?', sourceItemId: 'lens-channel-1', sourceTitle: 'Build Order', sourceExcerpt: 'Build first: show one defended next move before adding machinery.', suggestedLane: 'do-first', rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } },
|
||
{ id: 'copyable-brief', action: 'Copyable brief', why: 'Let the user paste the defended order into notes.', evidence: 'Does the pasted brief preserve the first move?', sourceItemId: 'lens-channel-2', lensTitle: 'Build Order', quote: 'Test manually: copy the ranked brief into notes.', suggestedLane: 'validate-next' },
|
||
{ id: 'saved-workspace', action: 'Saved workspace', why: 'Accounts and saved projects for every idea.', evidence: 'No bridge proof yet.', sourceItemId: 'lens-risk-1', sourceHeading: 'What can mislead you', sourceQuote: 'Probably noise: saved workspace with accounts before proof.', suggestedLane: 'park' },
|
||
],
|
||
},
|
||
}),
|
||
});
|
||
assert.equal(sourceExcerptResponse.status, 200);
|
||
const sourceExcerpt = await sourceExcerptResponse.json();
|
||
assert.equal(sourceExcerpt.ranked[0].id, 'manual-proof');
|
||
assert.equal(sourceExcerpt.ranked[0].provenance.sourceId, 'lens-channel-1');
|
||
assert.equal(sourceExcerpt.ranked[0].provenance.sourceTitle, 'Build Order');
|
||
assert.match(sourceExcerpt.ranked[0].provenance.sourceQuote, /Build first/);
|
||
assert.equal(sourceExcerpt.buildOrderDetails.doFirst[0].sourceId, 'lens-channel-1');
|
||
assert.match(sourceExcerpt.buildOrderDetails.doFirst[0].sourceQuote, /defended next move/);
|
||
assert.equal(sourceExcerpt.handoff.itemTrace.find(item => item.id === 'copyable-brief').sourceTitle, 'Build Order');
|
||
assert.match(sourceExcerpt.handoff.itemTrace.find(item => item.id === 'saved-workspace').sourceQuote, /Probably noise/);
|
||
assert.deepEqual(sourceExcerpt.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, sourceExcerptTop: sourceExcerpt.ranked[0].id, duplicateIds: duplicateIds.ranked.map(item => item.id), provenance: data.input.provenance, buildOrder: data.buildOrder }, null, 2));
|
||
} finally {
|
||
server.kill('SIGTERM');
|
||
}
|