fix(reasoning): clean proposition question formulation

This commit is contained in:
2026-08-13 17:45:28 +01:00
parent 4a434bb939
commit f955b875af
2 changed files with 138 additions and 15 deletions
+44 -15
View File
@@ -84,10 +84,15 @@ function hasAudienceIdentityQuestion(text) {
function extractMeaning(node) {
const raw = `${node?.label || ""} ${node?.description || ""}`.trim();
const rawLabel = stripTrailingPunctuation(String(node?.label || "")).trim();
let meaning = stripTrailingPunctuation(
String(node?.label || node?.description || "this uncertainty"),
).trim();
if (isDirectInterrogativeMeaning(rawLabel)) {
return rawLabel;
}
const lowered = normaliseText(raw);
if (hasAudienceIdentityQuestion(lowered)) {
return "the relevant customer, user, or value recipient";
@@ -99,13 +104,23 @@ function extractMeaning(node) {
.replace(/^uncertainty about\s+/i, "")
.trim();
const extractWhetherProposition = (text) => {
const match = String(text || "")
.trim()
.match(
/^(whether\b[\s\S]*?)(?:;\s+|,\s*(?:so\s+that|because)\b|\s+matters\s+because\b|$)/i,
);
return match?.[1]?.trim() || String(text || "").trim();
};
if (
/\b(status|likelihood|probability|chance|risk|uncertainty)\b/i.test(
String(node?.label || ""),
) &&
/^whether\s+/i.test(strippedDescription)
) {
return sentenceCase(strippedDescription);
return sentenceCase(extractWhetherProposition(strippedDescription));
}
meaning = meaning
@@ -185,12 +200,13 @@ function isInterrogativeMeaning(meaning) {
return true;
}
// "whether" clauses — also already question-shaped
if (/^whether\b/i.test(trimmed)) return true;
return false;
}
function isWhetherPropositionMeaning(meaning) {
return /^whether\b/i.test(String(meaning || "").trim());
}
function wrapInterrogativeForTemplate(meaning) {
const stripped = stripTrailingPunctuation(meaning).trim();
@@ -209,7 +225,7 @@ function wrapInterrogativeForTemplate(meaning) {
function buildNeutralClarificationQuestion(meaning) {
const content = wrapInterrogativeForTemplate(meaning);
// If content is already interrogative (wh-), use it as-is with trailing context
if (isInterrogativeMeaning(content)) {
if (isDirectInterrogativeMeaning(content)) {
return `${content}?`;
}
return `What would clarify ${content} in this situation?`;
@@ -217,12 +233,18 @@ function buildNeutralClarificationQuestion(meaning) {
function buildEvidenceFallbackQuestion(meaning) {
const content = wrapInterrogativeForTemplate(meaning);
if (isInterrogativeMeaning(content)) {
if (isDirectInterrogativeMeaning(content)) {
return `${content}?`;
}
return `What evidence would confirm or rule out ${content}?`;
}
function isDirectInterrogativeMeaning(meaning) {
return (
isInterrogativeMeaning(meaning) && !isWhetherPropositionMeaning(meaning)
);
}
function extractConstraintClarificationSubject(node) {
const label = stripTrailingPunctuation(node?.label || "");
const description = String(node?.description || "");
@@ -1269,7 +1291,7 @@ function buildQuestionFromFamily({
);
}
// If meaning is already interrogative, use it directly instead of wrapping
if (isInterrogativeMeaning(meaning)) {
if (isDirectInterrogativeMeaning(meaning)) {
return `${wrapInterrogativeForTemplate(meaning)}?`;
}
return `What evidence would clarify ${stripTrailingPunctuation(meaning)}?`;
@@ -1277,7 +1299,7 @@ function buildQuestionFromFamily({
if (questionFamily === "definition") {
// If meaning is already interrogative, use it directly
if (isInterrogativeMeaning(meaning)) {
if (isDirectInterrogativeMeaning(meaning)) {
return `${wrapInterrogativeForTemplate(meaning)}?`;
}
return `What does ${meaning} mean in this situation?`;
@@ -1286,20 +1308,20 @@ function buildQuestionFromFamily({
if (reasoningPattern === "comparison") {
if (selectedQuestionTemplate === "comparison_timing_basis") {
const cmpContent = wrapInterrogativeForTemplate(meaning);
if (isInterrogativeMeaning(cmpContent)) {
if (isDirectInterrogativeMeaning(cmpContent)) {
return `${cmpContent}?`;
}
return `What evidence would clarify whether ${stripTrailingPunctuation(cmpContent)}?`;
}
if (selectedQuestionTemplate === "comparison_measurement_basis") {
const cmpContent = wrapInterrogativeForTemplate(meaning);
if (isInterrogativeMeaning(cmpContent)) {
if (isDirectInterrogativeMeaning(cmpContent)) {
return `${cmpContent}?`;
}
return `What evidence would clarify ${stripTrailingPunctuation(cmpContent)}?`;
}
// Default comparison — handle interrogative meaning
if (isInterrogativeMeaning(meaning)) {
if (isDirectInterrogativeMeaning(meaning)) {
return `${wrapInterrogativeForTemplate(meaning)}?`;
}
return `What evidence would clarify ${stripTrailingPunctuation(meaning)}?`;
@@ -1310,7 +1332,7 @@ function buildQuestionFromFamily({
return buildQuestionFromStrategy(investigationStrategy);
}
// If meaning is interrogative, use it directly
if (isInterrogativeMeaning(meaning)) {
if (isDirectInterrogativeMeaning(meaning)) {
return `${wrapInterrogativeForTemplate(meaning)}?`;
}
return `What fact would resolve the contradiction about ${stripTrailingPunctuation(meaning)}?`;
@@ -1328,7 +1350,10 @@ function buildQuestionFromFamily({
if (investigationStrategy) {
return buildQuestionFromStrategy(investigationStrategy);
}
if (isInterrogativeMeaning(meaning)) {
if (isWhetherPropositionMeaning(meaning)) {
return `What evidence would clarify ${stripTrailingPunctuation(meaning)}?`;
}
if (isDirectInterrogativeMeaning(meaning)) {
return `${wrapInterrogativeForTemplate(meaning)}?`;
}
return buildNeutralClarificationQuestion(meaning);
@@ -1649,7 +1674,11 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
});
}
if (!selectedStrategy && (hasDecisionValueLanguage || hasCriteriaLanguage)) {
if (
!selectedStrategy &&
(hasCriteriaLanguage ||
(hasDecisionValueLanguage && !isWhetherPropositionMeaning(meaning)))
) {
selectedStrategy = buildInvestigationStrategy({
key: "decision_threshold",
reason:
@@ -1718,7 +1747,7 @@ export function selectInvestigationStrategy({ node, graph, context = {} }) {
function buildQuestionFromStrategy(strategy) {
// If meaning is interrogative, use it directly instead of embedding in a template
const m = strategy.meaning;
if (isInterrogativeMeaning(m)) {
if (isDirectInterrogativeMeaning(m)) {
return `${wrapInterrogativeForTemplate(m)}?`;
}
+94
View File
@@ -442,6 +442,100 @@ describe("formulateQuestion", () => {
);
});
it("60B.24 regression strips whether-rationale and uses evidence framing without mutating the source node", () => {
const description =
"Whether one prospective enterprise customer will sign if we launch this year; they account for ~£700k of the £1.2M expected annual revenue, so that resolving their intent is needed to assess the financial impact of launching now.";
const unknown = makeNode({
id: "n-60b24-signing-status",
label: "Prospective enterprise customer signing status",
description,
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const graph = makeGraphFor(unknown, {
centralStatement:
"We need to decide which launch timing option provides superior net value.",
});
const result = formulateQuestion({ node: unknown, graph });
expect(result.question).toContain(
"whether one prospective enterprise customer will sign if we launch this year",
);
expect(result.question).toContain("What evidence would clarify");
expect(result.question).not.toContain("£700k");
expect(result.question).not.toContain("£1.2M");
expect(result.question).not.toContain("resolving their intent");
expect(result.question).not.toContain("financial impact");
expect(unknown.description).toBe(description);
});
it("clean whether proposition uses evidence framing rather than a direct whether-question", () => {
const unknown = makeNode({
id: "n-supplier-renewal",
label: "Supplier renewal likelihood",
description: "Whether the supplier will renew the contract.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toBe(
"What evidence would clarify whether the supplier will renew the contract?",
);
expect(result.question).not.toBe(
"Whether the supplier will renew the contract?",
);
});
it("wh-question remains unchanged", () => {
const unknown = makeNode({
id: "n-wh-question",
label: "What would change the preferred option?",
description: "What would change the preferred option?",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toBe("What would change the preferred option?");
});
it("non-whether semicolon content is not globally truncated", () => {
const unknown = makeNode({
id: "n-semicolon-baseline",
label:
"Unknown baseline measurement basis; current and previous figures were prepared differently.",
description:
"Baseline measurement basis; current and previous figures were prepared differently.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown),
});
expect(result.question).toContain(
"baseline measurement basis; current and previous figures were prepared differently",
);
expect(result.question).not.toContain("What would clarify whether");
});
it("evidence-resolvable competing-cause unknown stays on an evidence route rather than neutral clarification", () => {
const unknown = makeNode({
id: "n-delivery-cause",