Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ test("Overview mounts production topic browsing with the selected corpus RPC sco
await settle();
expect(invoke).toHaveBeenCalledWith("memoryQueryTopics", {
corpusId: "a",
curated: true,
search: "",
pageSize: 24,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const evidence = invokeView as jest.Mock<Promise<unknown>, [string, unknown]>;
const content: ProjectBriefContent = {
kind: "projectBrief",
construction: "informational-project-summary-v1",
status: "partial",
coverageNote: "Owner approval remains unverified",
title: "Payments reliability",
summary: "Project active; incident closed",
citations: [
Expand Down Expand Up @@ -166,6 +168,10 @@ test("fixed project template uses human-readable fields, honest unknowns and exa
"Project knowledge as-of: Unknown",
);
expect(editor.element.textContent).toContain("not executable guides");
expect(editor.element.textContent).toContain("Completeness: partial");
expect(editor.element.textContent).toContain(
"Coverage: Owner approval remains unverified",
);
expect(editor.element.textContent).not.toContain('"inventoryIds"');
expect(editor.element.textContent).not.toContain("Activate skill");
change(editor.element, "Project brief title", "Edited project brief");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,32 @@ export function createProjectBriefEditor(
),
);
case "risks":
return details.items.map((entry, index) =>
choice(
`Risk ${index + 1} state`,
entry.status,
["open", "blocked", "resolved", "unknown"],
(value) => (entry.status = value),
return [
...details.items.map((entry, index) =>
choice(
`Risk ${index + 1} state`,
entry.status,
["open", "blocked", "resolved", "unknown"],
(value) => (entry.status = value),
),
),
);
...(details.openQuestions
? [
text(
"Open questions (one per line)",
details.openQuestions.join("\n"),
(value) => {
const questions = value
.split("\n")
.map((item) => item.trim())
.filter(Boolean);
details.openQuestions = questions;
},
true,
),
]
: []),
];
case "context":
return [
rbNode(
Expand Down Expand Up @@ -225,6 +243,10 @@ export function createProjectBriefEditor(
true,
),
);
if (content.status)
root.append(rbNode("p", `Completeness: ${content.status}`));
if (content.coverageNote)
root.append(rbNode("p", `Coverage: ${content.coverageNote}`));
for (const section of content.sections) {
const group = rbNode("fieldset");
group.append(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import type { TopicQueryPage } from "@typeagent/memory-service";
import type {
TopicQueryPage,
TopicQueryRequest,
} from "@typeagent/memory-service";
import { mountTopicBrowser } from "./memoryHubTopics";

function page(): TopicQueryPage {
Expand Down Expand Up @@ -52,6 +55,177 @@ async function settle() {
await new Promise((resolve) => setTimeout(resolve, 0));
}

test("concept browsing is lazy, explicit summary opening is scoped, and raw evidence browsing remains available", async () => {
const raw = page();
const concept = {
id: "concept:one",
label: "Connection lease",
scope: "Runtime",
purpose: "Understand resource lifetime",
summary: "",
summaryState: "pending" as const,
memberIds: ["topic:one"],
memberCount: 1,
omittedMembers: 0,
citations: raw.nodes[0]!.citations,
citationCount: 1,
omittedCitations: 0,
salience: 7,
};
const query = jest.fn(
async (request: TopicQueryRequest): Promise<TopicQueryPage> => {
if (!request.curated) return raw;
return {
...raw,
nodes: [],
totalConcepts: 1,
nextContinuationToken: undefined,
concepts: [
{
...concept,
...(request.summarize
? {
summary:
"Command and lease lifetimes differ.",
summaryState: "ready" as const,
}
: {}),
},
],
};
},
);
const onError = jest.fn();
const view = mountTopicBrowser(document.body, {
corpusId: () => "corpus",
query,
onError,
onOpenSource: jest.fn(),
rebuild: async () => {
throw new Error("unused");
},
});
const click = (text: string) => {
const button = [...document.querySelectorAll("button")].find(
(button) => button.textContent === text,
);
if (!button) throw new Error(`Missing button ${text}`);
button.click();
};
try {
click("Browse topics");
await settle();
expect(query).toHaveBeenCalledTimes(1);
expect(query.mock.calls[0]![0].summarize).toBeUndefined();
expect(document.body.textContent).toContain("Summary not generated");
expect(document.body.textContent).toContain("revision r2");
click("Open concept summary");
await settle();
expect(query).toHaveBeenLastCalledWith({
corpusId: "corpus",
curated: true,
topicId: "concept:one",
summarize: true,
pageSize: 1,
});
expect(document.body.textContent).toContain(
"Command and lease lifetimes differ.",
);
click("All topics");
await settle();
expect(query).toHaveBeenLastCalledWith({
corpusId: "corpus",
curated: false,
search: "",
pageSize: 24,
});
expect(onError).not.toHaveBeenCalled();
} finally {
view.dispose();
}
});

test("curated concepts preserve raw member drilldown and exact source evidence", async () => {
const raw = page();
raw.totalConcepts = 1;
const concepts: TopicQueryPage = {
...raw,
nodes: [],
concepts: [
{
id: "concept:one",
label: "Connection lease",
scope: "Runtime",
purpose: "Understand resource lifetime",
summary:
"A command and its held lease have different lifetimes.",
memberIds: ["topic:one"],
memberCount: 1,
omittedMembers: 0,
citations: raw.nodes[0]!.citations,
citationCount: 1,
omittedCitations: 0,
salience: 7,
},
],
totalConcepts: 1,
};
const query = jest.fn(async (request: TopicQueryRequest) =>
request.curated ? concepts : raw,
);
const onOpenSource = jest.fn();
const view = mountTopicBrowser(document.body, {
corpusId: () => "corpus",
query,
onOpenSource,
onError: jest.fn(),
rebuild: async () => {
throw new Error("unused");
},
});
try {
[...document.querySelectorAll("button")]
.find((button) => button.textContent === "Browse topics")!
.click();
await settle();
expect(
[...document.querySelectorAll("button")].find(
(button) => button.textContent === "Curated concepts",
)!.hidden,
).toBe(false);
[...document.querySelectorAll("button")]
.find((button) => button.textContent === "Curated concepts")!
.click();
await settle();
expect(query).toHaveBeenLastCalledWith({
corpusId: "corpus",
curated: true,
search: "",
pageSize: 24,
});
expect(document.body.textContent).toContain("250 raw topics retained");
expect(document.body.textContent).toContain("not truth confidence");
[...document.querySelectorAll("button")]
.find((button) =>
button.textContent?.startsWith("Source original"),
)!
.click();
expect(onOpenSource).toHaveBeenCalledWith("corpus", "original");
[...document.querySelectorAll("button")]
.find((button) => button.textContent === "Inspect original topic")!
.click();
await settle();
expect(query).toHaveBeenLastCalledWith({
corpusId: "corpus",
topicId: "topic:one",
depth: 0,
pageSize: 24,
});
} finally {
view.dispose();
}
});

test("topic browser shows scoped identity, original evidence, generation and omission/IO honesty", async () => {
const query = jest.fn(async () => page());
const onOpenSource = jest.fn();
Expand All @@ -73,6 +247,7 @@ test("topic browser shows scoped identity, original evidence, generation and omi
await settle();
expect(query).toHaveBeenCalledWith({
corpusId: "corpus",
curated: true,
search: "",
pageSize: 24,
});
Expand All @@ -97,6 +272,7 @@ test("topic browser shows scoped identity, original evidence, generation and omi
search: "",
pageSize: 24,
continuationToken: "next",
curated: true,
});
expect(onError).not.toHaveBeenCalled();
} finally {
Expand Down
Loading
Loading