feat(FN-2924): merge fusion/fn-2924

- Add task provenance display to TaskDetailModal, surfacing AI-generated merge commit summaries so users understand when and why tasks landed
- New `ai-summarize` module in core with structured commit summarization logic and tests
- Extend CLI `fn task show` with provenance output
- Refactor merger to produce cleaner merge commit messages with task context
- Update SettingsModal to expose AI summarization toggle (hidden behind feature flag)
- Adjust QuickChatFAB styling for theme compatibility
- Update scheduler and node-routing policy tests to match new behavior
- Add changeset for `@runfusion/fusion` minor release

Commits merged:
- feat(FN-2924): display task provenance in dashboard and cli
- feat(FN-2971): merge fusion/fn-2971
- feat(FN-2947): merge fusion/fn-2947
- feat(FN-2970): merge fusion/fn-2970
- feat(FN-2956): merge fusion/fn-2956

Files changed:
.changeset/add-ai-merge-commit-summary.md          |   5 +
 docs/cli-reference.md                              |   3 +-
 docs/settings-reference.md                         |   3 +
 packages/cli/src/commands/__tests__/task.test.ts   |  44 ++++-
 packages/cli/src/commands/task.ts                  |  55 +++++++
 packages/core/src/__tests__/ai-summarize.test.ts   |  64 ++++++++
 packages/core/src/ai-summarize.ts                  | 114 +++++++++++++
 packages/core/src/index.ts                         |   3 +
 packages/core/src/settings-schema.ts               |   1 +
 packages/core/src/types.ts                         |   4 +
 packages/dashboard/app/components/QuickChatFAB.css |  17 +-
 packages/dashboard/app/components/QuickChatFAB.tsx |   2 +-
 .../dashboard/app/components/SettingsModal.css     |  11 +-
 .../dashboard/app/components/SettingsModal.tsx     |  22 ++-
 .../dashboard/app/components/TaskDetailModal.css   |  40 +++++
 .../dashboard/app/components/TaskDetailModal.tsx   |  82 +++++++++-
 .../__tests__/SettingsModalNodeRouting.test.tsx    |  12 +-
 .../components/__tests__/TaskDetailModal.test.tsx  |  81 ++++++++-
 packages/engine/src/__tests__/merger.test.ts       |  75 +++++++++
 .../src/__tests__/node-routing-policy.test.ts      |  25 ++-
 .../src/__tests__/scheduler-node-routing.test.ts   |  18 +-
 packages/engine/src/merger.ts                      | 181 +++++++++------------
 packages/engine/src/scheduler.ts                   |   8 +-
 23 files changed, 723 insertions(+), 147 deletions(-)

Fusion-Task-Id: FN-2924
This commit is contained in:
Fusion
2026-04-29 18:00:15 -07:00
committed by gsxdsm
parent 027d60292e
commit 7885f16066
6 changed files with 294 additions and 11 deletions

View File

@@ -113,6 +113,13 @@ function makeTask(overrides: Record<string, unknown> = {}) {
describe("runTaskShow", () => {
let logSpy: ReturnType<typeof vi.spyOn>;
const mockTaskStoreGetTask = (task: Record<string, unknown>) => {
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
init: vi.fn(),
getTask: vi.fn().mockResolvedValue(task),
}));
};
beforeEach(() => {
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
});
@@ -125,10 +132,7 @@ describe("runTaskShow", () => {
const longDesc = "A".repeat(120); // well over 60 chars
const task = makeTask({ description: longDesc });
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
init: vi.fn(),
getTask: vi.fn().mockResolvedValue(task),
}));
mockTaskStoreGetTask(task);
await runTaskShow("FN-001");
@@ -148,10 +152,7 @@ describe("runTaskShow", () => {
description: "This is the full description that should not appear in the header",
});
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
init: vi.fn(),
getTask: vi.fn().mockResolvedValue(task),
}));
mockTaskStoreGetTask(task);
await runTaskShow("FN-001");
@@ -162,6 +163,33 @@ describe("runTaskShow", () => {
expect(headerLine![0]).toContain("My Task Title");
expect(headerLine![0]).not.toContain("This is the full description");
});
it.each([
[{ sourceType: "dashboard_ui" }, "Source: Dashboard"],
[{ sourceType: "agent_heartbeat", sourceAgentId: "agent-123" }, "Source: Agent (agent-123)"],
[{ sourceType: "task_refine", sourceParentTaskId: "FN-2904" }, "Source: Refinement of FN-2904"],
[{ sourceType: "task_duplicate", sourceParentTaskId: "FN-2905" }, "Source: Duplicate of FN-2905"],
[
{ sourceType: "github_import", sourceMetadata: { issueUrl: "https://github.com/owner/repo/issues/42" } },
"Source: GitHub Import (https://github.com/owner/repo/issues/42)",
],
] as const)("prints provenance line for %o", async (overrides, expectedLine) => {
mockTaskStoreGetTask(makeTask(overrides));
await runTaskShow("FN-001");
const output = logSpy.mock.calls.map((call) => String(call[0])).join("\n");
expect(output).toContain(expectedLine);
});
it.each([{ sourceType: "unknown" }, {}])("omits provenance line for %o", async (overrides) => {
mockTaskStoreGetTask(makeTask(overrides));
await runTaskShow("FN-001");
const output = logSpy.mock.calls.map((call) => String(call[0])).join("\n");
expect(output).not.toContain("Source:");
});
});
describe("task node overrides", () => {