diff --git a/README.md b/README.md
index b1d1955..02b1d83 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Ranker's continuation job is narrow:
`Snapshot / Concept Map → candidate feature/action set → Rank-ready build order`
-`POST /api/rank-feedback` accepts a `prioritix-feature-set-v1`-style payload from Scattermind and returns ranked items plus `buildOrder.doFirst / validateNext / defer / park`. It accepts candidate arrays as `features`, `actions`, `nextMoves`, or `candidates` either at the top level or under `featureSet`, and it can consume a nested `conceptMap.nextActions / nextMoves` artifact directly, so Scattermind can hand off Concept Map next actions without renaming them into fake software features. It also returns a `handoff` object (`rank-feedback-result-v1`) with source provenance, item trace rows, and contract warnings for missing artifact IDs, source sections, original prompt provenance, or evidence on active items. Keep this contract action-first; do not use it as a reason to add generic dashboard, auth, billing, or workspace layers before the bridge has proof.
+`POST /api/rank-feedback` accepts a `prioritix-feature-set-v1`-style payload from Scattermind and returns ranked items plus `buildOrder.doFirst / validateNext / defer / park`. It accepts candidate arrays as `features`, `actions`, `nextMoves`, or `candidates` either at the top level or under `featureSet`, and it can consume a nested `conceptMap.nextActions / nextMoves` artifact directly, so Scattermind can hand off Concept Map next actions without renaming them into fake software features. It also returns a `brief` with source, next-48-hour actions, carried-forward assumptions/constraints/non-goals, and `whatWouldChangeRanking` checks, plus a `handoff` object (`rank-feedback-result-v1`) with source provenance, item trace rows, and contract warnings for missing artifact IDs, source sections, original prompt provenance, or evidence on active items. Keep this contract action-first; do not use it as a reason to add generic dashboard, auth, billing, or workspace layers before the bridge has proof.
Candidate items may include optional 1–10 `rankerHints` (`value`, `effort`, `confidence`, `urgency`, `revenue`, `novelty`, `risk`). Ranker blends those explicit Scattermind hints with text heuristics; `effort` is inverted into feasibility. Hints improve the defended order, but `recommendedLane: "defer"` or `"park"` remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send `sourceSection` and `evidenceNeeded` on each active next move. Scattermind can also send `targetAudience`, `constraints`, `assumptions`, and `nonGoals` / `avoid` at the top level, in `featureSet`, or inside `conceptMap.context`; Ranker returns that decision context in `input.decisionContext` and `handoff.decisionContext`, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof).
diff --git a/public/app.js b/public/app.js
index 6d5cfe7..4ed18dd 100644
--- a/public/app.js
+++ b/public/app.js
@@ -1,14 +1,14 @@
const sample = {
- idea: 'I’m building a lightweight product feedback tool for people with too many ideas and too many possible features. It should feel useful before becoming a workspace.',
- optionsText: `- Ranked feedback map for pasted feature lists
-- Expert reflections on the top options
+ idea: 'Scattermind clarified a messy course idea. Now I need the first build order, not a dashboard.',
+ optionsText: `- Manual build-order preview from one Concept Map
+- Copyable decision brief with Do first / Validate next / Defer / Park
+- Evidence questions beside each next move
- Accounts and saved workspaces
-- Team voting on feature priority
-- Exportable decision brief for Slack or Notion
-- Custom criteria builder
-- Paid deeper product strategy pass`,
- context: 'MVP, solo builder, needs to feel valuable in under two minutes, avoid dashboard swamp.',
- mode: 'progress',
+- Team voting on roadmap priority
+- Subscription billing layer
+- Polished export for sharing the defended order`,
+ context: 'Snapshot / Concept Map handoff, solo builder, tired non-AI-native user, avoid auth/workspaces/billing before proof.',
+ mode: 'mvp',
};
const form = document.querySelector('#rankForm');
@@ -89,6 +89,18 @@ function renderResults(data) {
Next 48 hours
${(brief.next48Hours || []).map((step) => `- ${escapeHtml(step)}
`).join('')}
+ ${(brief.whatWouldChangeRanking || []).length ? `
+
+ What would change the order
+ ${brief.whatWouldChangeRanking.map((change) => `- ${escapeHtml(change)}
`).join('')}
+
+ ` : ''}
+ ${(brief.assumptions || []).length ? `
+
+ Context carried forward
+ ${brief.assumptions.map((assumption) => `- ${escapeHtml(assumption)}
`).join('')}
+
+ ` : ''}
${(brief.expertReflections || []).map((reflection) => `
${escapeHtml(reflection.lens)}
diff --git a/scripts/check-rank-feedback.mjs b/scripts/check-rank-feedback.mjs
index 7943c78..2dc5dda 100644
--- a/scripts/check-rank-feedback.mjs
+++ b/scripts/check-rank-feedback.mjs
@@ -84,6 +84,8 @@ try {
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 hintedResponse = await fetch(`${base}/api/rank-feedback`, {
method: 'POST',
@@ -197,6 +199,8 @@ try {
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);
diff --git a/server.js b/server.js
index f61b870..541453f 100644
--- a/server.js
+++ b/server.js
@@ -598,13 +598,29 @@ function concernFor(option) {
return 'The main risk is sequencing: do it only if it supports the first useful proof.';
}
-function createDecisionBrief({ idea, context, mode, ranked, provenance }) {
+function whatWouldChangeRanking(top, second, risky) {
+ if (!top) return ['Add at least two concrete next moves with evidence needed.'];
+ const changes = [];
+ if (top.factors?.evidenceNeeded) changes.push(`If evidence fails for “${top.title}” (${top.factors.evidenceNeeded}), move it out of Do first.`);
+ else changes.push(`If “${top.title}” cannot name a cheap proof step, demote it until the evidence question is clear.`);
+ if (second) changes.push(`If “${second.title}” gets stronger proof or meaningfully lower effort, it can overtake the current first move.`);
+ if (risky && risky.id !== top.id) changes.push(`If the riskiest assumption around “${risky.title}” is answered cheaply, re-rank before building around it.`);
+ changes.push('If the source Concept Map adds new constraints or non-goals, re-run the order instead of editing around stale context.');
+ return changes.slice(0, 4);
+}
+
+function createDecisionBrief({ idea, context, mode, ranked, provenance, decisionContext }) {
const top = ranked[0];
const second = ranked[1];
const risky = ranked.slice().sort((a, b) => b.metrics.risk - a.metrics.risk)[0];
const deferred = ranked.filter(item => ['defer', 'park'].includes(item.lane.id)).slice(0, 3);
const sourceLabel = [provenance?.snapshotTitle, provenance?.artifactId].filter(Boolean).join(' · ');
const theme = top ? `The strongest signal is “${top.title}” because ${reasonFor(top)}.` : 'The list needs at least two options before ranking becomes useful.';
+ const assumptions = [
+ ...(decisionContext?.assumptions || []),
+ ...(decisionContext?.constraints || []).map(item => `Constraint: ${item}`),
+ ...(decisionContext?.nonGoals || []).map(item => `Non-goal: ${item}`),
+ ].slice(0, 6);
return {
headline: top ? `Start with ${top.title}` : 'Add options to get a ranked feedback map',
summary: `${theme}${second ? ` “${second.title}” is the nearest follow-up, not a parallel first step.` : ''}${sourceLabel ? ` Source: ${sourceLabel}.` : ''}`,
@@ -635,6 +651,8 @@ function createDecisionBrief({ idea, context, mode, ranked, provenance }) {
'Put it in front of 3–5 real people or run it manually once.',
`Do not touch ${deferred[0] ? `“${deferred[0].title}”` : 'the parked ideas'} until the first signal is real.`,
] : ['Paste 3–10 options.', 'Choose what the ranking should care about.', 'Run the first-pass judgement.'],
+ assumptions,
+ whatWouldChangeRanking: whatWouldChangeRanking(top, second, risky),
caution: 'This is first-pass judgement, not an oracle. Change the criteria if the context changes.',
};
}
@@ -701,7 +719,7 @@ app.post('/api/rank-feedback', (req, res) => {
concern: concernFor(rankedOption),
};
});
- const brief = createDecisionBrief({ idea, context, mode, ranked: options, provenance });
+ const brief = createDecisionBrief({ idea, context, mode, ranked: options, provenance, decisionContext });
const handoff = createHandoffContract({ ranked: options, provenance, decisionContext });
res.json({
ok: true,