FN-5897: surface linked goals in mission read paths

Expose mission-linked goals across mission detail surfaces.

- load linked goal records into mission hierarchy reads in core and return them from the mission detail API
- show linked goals in mission detail views, add goal-chip navigation, and anchor highlighted goal cards in GoalsView
- extend CLI mission output, docs, tests, and add a published changeset for the new read-path support

Files changed:
 .changeset/fn-5897-linked-goals-read-paths.md      |  5 +++
 docs/missions.md                                   |  6 ++-
 packages/cli/src/__tests__/extension.test.ts       | 48 +++++++++++++++++++++-
 packages/cli/src/extension.ts                      | 10 +++++
 packages/core/src/__tests__/mission-store.test.ts  | 14 +++++++
 packages/core/src/mission-store.ts                 | 29 +++++++++++++
 packages/core/src/mission-types.ts                 |  4 ++
 packages/dashboard/app/App.tsx                     | 16 +++++++-
 packages/dashboard/app/components/GoalsView.css    |  6 +++
 packages/dashboard/app/components/GoalsView.tsx    | 42 +++++++++++++++++--
 packages/dashboard/app/components/MissionManager.css | 44 ++++++++++++++++++++
 packages/dashboard/app/components/MissionManager.tsx | 31 +++++++++++++-
 packages/dashboard/app/components/__tests__/GoalsView.test.tsx | 31 ++++++++++++++
 packages/dashboard/app/components/__tests__/MissionManager.test.tsx | 45 ++++++++++++++++++++
 packages/dashboard/app/components/mission-types.ts |  2 +
 packages/dashboard/src/__tests__/mission-e2e.test.ts |  4 ++
 packages/dashboard/src/mission-routes.ts           |  5 ++-
 17 files changed, 332 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-5897

Fusion-Task-Lineage: 5176270d-9885-447a-845b-4e0d69d531a0
This commit is contained in:
gsxdsm
2026-06-02 17:58:41 -07:00
parent 6c7ed1e1fc
commit abbeaec0a8
17 changed files with 332 additions and 10 deletions

View File

@@ -1132,9 +1132,10 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
});
describe("fn_mission_show", () => {
it("returns mission with hierarchy", async () => {
// Create mission
it("returns mission with hierarchy and linked goals", async () => {
const createTool = api.tools.get("fn_mission_create")!;
const goalTool = api.tools.get("fn_goal_create")!;
const linkTool = api.tools.get("fn_mission_link_goal")!;
const created = await createTool.execute(
"c1",
{ title: "Test Mission" },
@@ -1142,6 +1143,20 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
undefined,
makeCtx(tmpDir),
);
const goal = await goalTool.execute(
"g1",
{ title: "Connect mission work to goals" },
undefined,
undefined,
makeCtx(tmpDir),
);
await linkTool.execute(
"link-1",
{ missionId: created.details.missionId, goalId: goal.details.goalId },
undefined,
undefined,
makeCtx(tmpDir),
);
const showTool = api.tools.get("fn_mission_show")!;
const result = await showTool.execute(
@@ -1154,6 +1169,11 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
expect(result.details.mission).toBeDefined();
expect(result.content[0].text).toContain("Test Mission");
expect(result.content[0].text).toContain("Linked Goals:");
expect(result.content[0].text).toContain(`- ${goal.details.goalId}: Connect mission work to goals`);
expect(result.details.mission.linkedGoals).toEqual([
expect.objectContaining({ id: goal.details.goalId, title: "Connect mission work to goals" }),
]);
});
it("renders acceptanceCriteria / verification for milestones, slices, and features", async () => {
@@ -1212,6 +1232,30 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
expect(result.details.mission.milestones[0].acceptanceCriteria).toBe(longValue);
});
it("renders an empty linked goals state when no goals are linked", async () => {
const createTool = api.tools.get("fn_mission_create")!;
const created = await createTool.execute(
"c1",
{ title: "Mission Without Goals" },
undefined,
undefined,
makeCtx(tmpDir),
);
const showTool = api.tools.get("fn_mission_show")!;
const result = await showTool.execute(
"call-1",
{ id: created.details.missionId },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(result.content[0].text).toContain("Linked Goals:");
expect(result.content[0].text).toContain("No linked goals.");
expect(result.details.mission.linkedGoals).toEqual([]);
});
it("returns error when mission not found", async () => {
const showTool = api.tools.get("fn_mission_show")!;
const result = await showTool.execute(

View File

@@ -2644,6 +2644,16 @@ export default function kbExtension(pi: ExtensionAPI) {
}
lines.push("");
lines.push("Linked Goals:");
if ((mission.linkedGoals?.length ?? 0) === 0) {
lines.push("No linked goals.");
} else {
for (const goal of mission.linkedGoals ?? []) {
lines.push(`- ${goal.id}: ${goal.title}`);
}
}
lines.push("");
if (mission.milestones.length === 0) {
lines.push("No milestones yet.");
} else {