Feature/product platform foundation v0.62 #1

Merged
robbond merged 683 commits from feature/product-platform-foundation-v0.62 into feature/emergent-unknowns-v0.5 2026-09-09 07:58:20 +01:00
2 changed files with 103 additions and 6 deletions
Showing only changes of commit f861e2cac0 - Show all commits
+74 -2
View File
@@ -123,6 +123,49 @@ function buildEvidenceFallbackQuestion(meaning) {
return `What evidence would confirm or rule out ${stripTrailingPunctuation(meaning)}?`;
}
function extractConstraintClarificationSubject(node) {
const label = stripTrailingPunctuation(node?.label || "");
const description = String(node?.description || "");
const combined = `${label} ${description}`;
const labelMatch = label.match(
/^Whether\s+(.+)\s+is\s+a\s+hard constraint$/i,
);
if (labelMatch?.[1]) {
return labelMatch[1].trim();
}
const descriptionMatch = combined.match(
/whether\s+(.+?)\s+is\s+a\s+hard constraint\s+or\s+a\s+preference(?:\/|-|\s)trade(?:\/|-|\s)?off/i,
);
if (descriptionMatch?.[1]) {
return descriptionMatch[1].trim();
}
return null;
}
function buildUserMeaningClarificationQuestion(node) {
const subject = extractConstraintClarificationSubject(node);
if (subject) {
return `Is ${subject} a hard constraint or a preference/trade-off?`;
}
return buildNeutralClarificationQuestion(extractMeaning(node));
}
function isUserOwnedMeaningBoundaryUnknown(node) {
const text = normaliseText(`${node?.label || ""} ${node?.description || ""}`);
return (
text.includes("hard constraint") &&
(text.includes("preference trade off") ||
text.includes("preference/trade-off") ||
text.includes("preference or trade off") ||
text.includes("preference or trade-off"))
);
}
function collectObservationNodes(graph) {
return (graph?.nodes || []).filter(
(node) => node.kind === "observation" && node.status === "supported",
@@ -1726,6 +1769,34 @@ export function formulateQuestion({ node, graph, context = {} }) {
const rejectedQuestionFamilies = rejectedQuestionFamiliesForPattern(
reasoningPatternSelection.pattern,
);
if (isUserOwnedMeaningBoundaryUnknown(node)) {
const question = sanitizeQuestionText(
buildUserMeaningClarificationQuestion(node),
);
const questionComplexity = assessQuestionComplexity({
question,
selectedUnknown: node,
graph,
});
return {
question,
reason:
"Formulated as a user-clarification question because this unresolved distinction depends on the user's own meaning rather than external evidence.",
strategy: null,
investigationStrategy: null,
reasoningPattern: reasoningPatternSelection.pattern,
reasoningPatternReason: reasoningPatternSelection.reason,
questionFamily: "prioritisation",
allowedQuestionFamilies,
rejectedQuestionFamilies,
selectedQuestionTemplate: "user_meaning_clarification",
questionComplexity,
plainLanguageNormalisations: [],
};
}
const foundationalDirectQuestion = buildFoundationalDirectQuestion(node);
if (
foundationalDirectQuestion &&
@@ -1794,7 +1865,7 @@ export function formulateQuestion({ node, graph, context = {} }) {
)
) {
question = sanitizeQuestionText(
investigationStrategy &&
(investigationStrategy &&
isClaimLikeUnknown(
node,
normaliseText(
@@ -1807,7 +1878,8 @@ export function formulateQuestion({ node, graph, context = {} }) {
.filter(Boolean)
.join(" "),
),
)
)) ||
reasoningPatternSelection.pattern === "diagnosis"
? buildEvidenceFallbackQuestion(fallbackMeaning)
: buildNeutralClarificationQuestion(fallbackMeaning),
);
+29 -4
View File
@@ -280,12 +280,12 @@ describe("formulateQuestion", () => {
expect(result.pattern).toBe("comparison");
});
it("constraint unknown uses evidence-gathering within the fixed strategy set", () => {
it("user-owned constraint ambiguity produces a clarification question rather than an evidence request", () => {
const unknown = makeNode({
id: "n-constraint",
label: "Budget constraint",
label: "Whether avoiding additional risk is a hard constraint",
description:
"Need the main budget constraint because it limits the available options.",
"Need to know whether avoiding additional risk is a hard constraint or a preference/trade-off.",
kind: "unknown",
status: "unknown",
confidence: "medium",
@@ -296,8 +296,33 @@ describe("formulateQuestion", () => {
graph: makeGraphFor(unknown),
});
expect(result.strategy).toBe("evidence_gathering");
expect(result.strategy).toBeNull();
expect(result.question).toBe(
"Is avoiding additional risk a hard constraint or a preference/trade-off?",
);
});
it("evidence-resolvable competing-cause unknown stays on an evidence route rather than neutral clarification", () => {
const unknown = makeNode({
id: "n-delivery-cause",
label: "Possible causes of the delivery delay",
description:
"Need to determine whether staff capacity or supplier lead times are responsible for the delivery delay.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
const result = formulateQuestion({
node: unknown,
graph: makeGraphFor(unknown, {
centralStatement: "Delivery is delayed and the cause is still unknown.",
}),
});
expect(result.reasoningPattern).toBe("diagnosis");
expect(result.question).toContain("What evidence");
expect(result.question).not.toContain("What would clarify");
});
it("the same unknown can produce different questions when paired with different strategies", () => {