From 337bdcd0818e4ee08bae29c52c18ce582eb0484c Mon Sep 17 00:00:00 2001
From: gsxdsm
Date: Sat, 11 Jul 2026 00:06:08 -0700
Subject: [PATCH] feat(compound-engineering): align the UI with the upstream
loop
---
.changeset/clean-compound-pipeline.md | 4 +-
.../compound-engineering/pipeline-desktop.png | 4 +-
.../compound-engineering/pipeline-mobile.png | 4 +-
.../src/__tests__/stage-registry.test.ts | 17 +-
.../src/__tests__/sync.test.ts | 22 ++-
.../src/artifacts/__tests__/discovery.test.ts | 2 +-
.../src/artifacts/discovery.ts | 3 +-
.../src/dashboard/CompoundEngineeringView.css | 23 ++-
.../src/dashboard/CompoundEngineeringView.tsx | 170 +++++++++++++-----
.../CompoundEngineeringView.test.tsx | 79 +++++++-
.../src/session/stage-registry.ts | 19 +-
11 files changed, 265 insertions(+), 82 deletions(-)
diff --git a/.changeset/clean-compound-pipeline.md b/.changeset/clean-compound-pipeline.md
index 104f7734e9..c0f2de9fc5 100644
--- a/.changeset/clean-compound-pipeline.md
+++ b/.changeset/clean-compound-pipeline.md
@@ -2,6 +2,6 @@
"@runfusion/fusion": minor
---
-summary: Support repeatable Compound Engineering cycles with project-scoped progress and artifact collections.
+summary: Guide repeatable Compound Engineering cycles from product grounding through reusable learnings.
category: feature
-dev: Adds collection-aware stages, safer session controls, explicit choice confirmation, and terminal Work progression.
+dev: Adds project-scoped sessions, collection-aware stages, Work quality gates, and terminal Compound progression.
diff --git a/docs/screenshots/compound-engineering/pipeline-desktop.png b/docs/screenshots/compound-engineering/pipeline-desktop.png
index 7791a07b2f..f13368fc77 100644
--- a/docs/screenshots/compound-engineering/pipeline-desktop.png
+++ b/docs/screenshots/compound-engineering/pipeline-desktop.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a6c218824cbd21ec36305437e2adc5c810163ab25cd28fdee705bb12ddf48b93
-size 270173
+oid sha256:06d35e52a88966234de0aef50cc471a474a80422789cfaba5e3b8133f189e2d8
+size 251273
diff --git a/docs/screenshots/compound-engineering/pipeline-mobile.png b/docs/screenshots/compound-engineering/pipeline-mobile.png
index 668a8fc751..1eaa719035 100644
--- a/docs/screenshots/compound-engineering/pipeline-mobile.png
+++ b/docs/screenshots/compound-engineering/pipeline-mobile.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9bbcbf6767d6982ebb4340498396dc1453eb2e7458c3f0877c20d7a4f5ece9d3
-size 95990
+oid sha256:5ddba7235bdf54679910eae60d9716ea631fec8ef5e0a8be651e197fca243225
+size 98300
diff --git a/plugins/fusion-plugin-compound-engineering/src/__tests__/stage-registry.test.ts b/plugins/fusion-plugin-compound-engineering/src/__tests__/stage-registry.test.ts
index 6545ad778a..3ee1c1ddd5 100644
--- a/plugins/fusion-plugin-compound-engineering/src/__tests__/stage-registry.test.ts
+++ b/plugins/fusion-plugin-compound-engineering/src/__tests__/stage-registry.test.ts
@@ -8,20 +8,21 @@ describe("compound engineering stage registry", () => {
const stageIds = listStages().map((stage) => stage.stageId);
const pipelineStageIds = listPipelineStages().map((stage) => stage.stageId);
- expect(stageIds.slice(0, 5)).toEqual(["strategy", "ideate", "brainstorm", "plan", "work"]);
+ expect(stageIds.slice(0, 6)).toEqual(["strategy", "ideate", "brainstorm", "plan", "work", "compound"]);
expect(stageIds.at(-1)).toBe("debug");
expect(stageIds.filter((stageId) => stageId === "debug")).toHaveLength(1);
expect(stageIds.indexOf("plan")).toBeLessThan(stageIds.indexOf("work"));
- expect(stageIds.indexOf("work")).toBeLessThan(stageIds.indexOf("debug"));
- expect(pipelineStageIds).toEqual(["strategy", "ideate", "brainstorm", "plan", "work"]);
+ expect(stageIds.indexOf("compound")).toBeLessThan(stageIds.indexOf("debug"));
+ expect(pipelineStageIds).toEqual(["brainstorm", "plan", "work", "compound"]);
});
- it("advances through every automatic transition and treats work as terminal", () => {
- expect(nextStageAfter("strategy")).toBe("ideate");
- expect(nextStageAfter("ideate")).toBe("brainstorm");
+ it("advances through delivery into compounding and treats compound as terminal", () => {
+ expect(nextStageAfter("strategy")).toBeUndefined();
+ expect(nextStageAfter("ideate")).toBeUndefined();
expect(nextStageAfter("brainstorm")).toBe("plan");
expect(nextStageAfter("plan")).toBe("work");
- expect(nextStageAfter("work")).toBeUndefined();
+ expect(nextStageAfter("work")).toBe("compound");
+ expect(nextStageAfter("compound")).toBeUndefined();
expect(nextStageAfter("debug")).toBeUndefined();
});
@@ -47,7 +48,7 @@ describe("compound engineering stage registry", () => {
expect(stage).toMatchObject({
stageId: "debug",
- order: 600,
+ order: 700,
skillId: "ce-debug",
artifactLocation: "docs/debug/",
artifactGlob: "docs/debug/**/*.md",
diff --git a/plugins/fusion-plugin-compound-engineering/src/__tests__/sync.test.ts b/plugins/fusion-plugin-compound-engineering/src/__tests__/sync.test.ts
index 89f0335f9d..d2eb74da90 100644
--- a/plugins/fusion-plugin-compound-engineering/src/__tests__/sync.test.ts
+++ b/plugins/fusion-plugin-compound-engineering/src/__tests__/sync.test.ts
@@ -183,21 +183,37 @@ describe("U8 reconciler (convergence + outbound)", () => {
expect(state.currentStage).toBe("work");
});
- it("completing work terminates the automatic pipeline without creating a debug task", async () => {
+ it("completing work advances to Compound without creating a debug task", async () => {
const { cePipelineId, task } = await landPipeline("work");
const before = await taskStore.listTasks();
await moveTo(task.id, "done");
const result = await reconcileCePipelines(ctx);
+ expect(result.advanced).toBe(1);
+ expect(result.tasksCreated).toBe(1);
+ expect(getCePipelineStore(ctx).getState(cePipelineId)).toMatchObject({
+ currentStage: "compound",
+ status: "awaiting_board",
+ });
+ expect(await taskStore.listTasks()).toHaveLength(before.length + 1);
+ expect(getCePipelineStore(ctx).listByPipeline(cePipelineId).some((link) => link.ceStageId === "debug")).toBe(false);
+ });
+
+ it("completing Compound closes the delivery loop without creating another task", async () => {
+ const { cePipelineId, task } = await landPipeline("compound");
+ const before = await taskStore.listTasks();
+
+ await moveTo(task.id, "done");
+ const result = await reconcileCePipelines(ctx);
+
expect(result.advanced).toBe(1);
expect(result.tasksCreated).toBe(0);
expect(getCePipelineStore(ctx).getState(cePipelineId)).toMatchObject({
- currentStage: "work",
+ currentStage: "compound",
status: "completed",
});
expect(await taskStore.listTasks()).toHaveLength(before.length);
- expect(getCePipelineStore(ctx).listByPipeline(cePipelineId).some((link) => link.ceStageId === "debug")).toBe(false);
});
it("outbound: advancing the pipeline propagates a NEW next-stage board task", async () => {
diff --git a/plugins/fusion-plugin-compound-engineering/src/artifacts/__tests__/discovery.test.ts b/plugins/fusion-plugin-compound-engineering/src/artifacts/__tests__/discovery.test.ts
index 9d6716683f..10bda3d56f 100644
--- a/plugins/fusion-plugin-compound-engineering/src/artifacts/__tests__/discovery.test.ts
+++ b/plugins/fusion-plugin-compound-engineering/src/artifacts/__tests__/discovery.test.ts
@@ -78,7 +78,7 @@ describe("discoverArtifacts", () => {
expect(byStage.plan.label).toBe("Plan");
expect(byStage.work.entries[0]).toMatchObject({ path: "docs/work/work.md" });
expect(byStage.debug.entries[0]).toMatchObject({ path: "docs/debug/debug.md" });
- expect(byStage.solution.entries).toHaveLength(1);
+ expect(byStage.compound.entries).toHaveLength(1);
// Every group present is flagged present.
expect(byStage.ideate.present).toBe(true);
expect(byStage.brainstorm.entries[0]).toMatchObject({ path: "docs/brainstorms/requirements.md" });
diff --git a/plugins/fusion-plugin-compound-engineering/src/artifacts/discovery.ts b/plugins/fusion-plugin-compound-engineering/src/artifacts/discovery.ts
index 175bb835b4..deb3a46a43 100644
--- a/plugins/fusion-plugin-compound-engineering/src/artifacts/discovery.ts
+++ b/plugins/fusion-plugin-compound-engineering/src/artifacts/discovery.ts
@@ -65,7 +65,7 @@ function stageLocations(): ConventionalLocation[] {
const path = definition.artifactLocation.replace(/\/$/, "");
return {
stage: definition.stageId,
- label: definition.label,
+ label: definition.stageId === "compound" ? "Learnings" : definition.label,
path,
kind: definition.artifactLocation.endsWith("/") ? "directory" : "file",
};
@@ -76,7 +76,6 @@ function conventionalLocations(): ConventionalLocation[] {
return [
...stageLocations(),
{ stage: "brainstorm", label: "Brainstorms", path: "docs/brainstorms", kind: "directory" },
- { stage: "solution", label: "Solutions", path: "docs/solutions", kind: "directory" },
{ stage: "concepts", label: "Concepts", path: "CONCEPTS.md", kind: "file" },
];
}
diff --git a/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.css b/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.css
index d95afc69a8..22523b2fa6 100644
--- a/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.css
+++ b/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.css
@@ -132,6 +132,11 @@ The shared ViewHeader sits directly above plugin content, so the body needs top
gap: calc(var(--space-sm) - var(--space-xs) / 2);
}
+/* Keep multi-run histories scannable; older artifacts remain available on demand. */
+.ce-group-more { border-top: 1px solid var(--border); padding-top: var(--space-xs); }
+.ce-group-more summary { color: var(--text-muted); cursor: pointer; font-size: 0.74rem; font-weight: 600; }
+.ce-group-more[open] summary { margin-bottom: var(--space-xs); color: var(--text); }
+
.ce-artifact {
display: flex;
align-items: center;
@@ -748,21 +753,30 @@ The pipeline overview must communicate progression and the next useful action at
.ce-pipeline-header h2 { margin: 0; font-size: 1rem; font-weight: 650; }
.ce-pipeline-header p { margin: calc(var(--space-xs) / 2) 0 0; color: var(--text-muted); font-size: 0.78rem; }
.ce-investigate { display: inline-flex; align-items: center; gap: var(--space-xs); flex: none; }
-.ce-stage-rail { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); list-style: none; margin: 0; padding: 0; }
+.ce-pipeline-block { display: flex; flex-direction: column; gap: var(--space-xs); }
+.ce-pipeline-block-heading { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md); }
+.ce-pipeline-block-heading h3 { margin: 0; font-size: 0.78rem; font-weight: 650; }
+.ce-pipeline-block-heading span { color: var(--text-dim); font-size: 0.7rem; }
+.ce-stage-rail { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); list-style: none; margin: 0; padding: 0; }
+.ce-stage-rail[data-count="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+.ce-stage-rail[data-count="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ce-stage-step { position: relative; min-width: 0; border-block: 1px solid var(--border); border-left: 1px solid var(--border); background: var(--surface); }
.ce-stage-step:first-child { border-radius: var(--radius-md) 0 0 var(--radius-md); }
.ce-stage-step:last-child { border-right: 1px solid var(--border); border-radius: 0 var(--radius-md) var(--radius-md) 0; }
.ce-stage-step::after { position: absolute; z-index: 2; top: calc(var(--space-lg) + 1px); right: calc(var(--space-xs) * -1); width: calc(var(--space-sm) - 2px); height: calc(var(--space-sm) - 2px); border-top: 1px solid var(--border); border-right: 1px solid var(--border); background: inherit; content: ""; transform: rotate(45deg); }
.ce-stage-step:last-child::after { display: none; }
-.ce-stage-main { display: flex; width: 100%; min-height: calc(var(--space-2xl) * 2.25); align-items: flex-start; gap: var(--space-sm); border: 0; background: transparent; color: inherit; cursor: pointer; padding: var(--space-md); text-align: left; }
+.ce-stage-main { display: flex; width: 100%; min-height: calc(var(--space-2xl) * 2.65); align-items: flex-start; gap: var(--space-sm); border: 0; background: transparent; color: inherit; cursor: pointer; padding: var(--space-md); text-align: left; }
.ce-stage-main:hover:not(:disabled), .ce-stage-main:focus-visible { background: var(--card-hover); }
.ce-stage-main:focus-visible { position: relative; z-index: 3; outline: 2px solid var(--color-info); outline-offset: -2px; }
.ce-stage-icon { display: grid; width: calc(var(--space-lg) + var(--space-xs)); height: calc(var(--space-lg) + var(--space-xs)); flex: none; place-items: center; border: 1px solid var(--border); border-radius: 50%; color: var(--text-muted); }
-.ce-stage-step[data-status="complete"] .ce-stage-icon { border-color: color-mix(in srgb, var(--color-success) 45%, var(--border)); color: var(--color-success); }
+.ce-stage-step[data-status="current"] .ce-stage-icon,
+.ce-stage-step[data-status="history"] .ce-stage-icon,
+.ce-stage-step[data-status="last-run-complete"] .ce-stage-icon { border-color: color-mix(in srgb, var(--color-success) 45%, var(--border)); color: var(--color-success); }
.ce-stage-step[data-status="active"] .ce-stage-icon, .ce-stage-step[data-status="needs-input"] .ce-stage-icon { border-color: var(--color-info); background: color-mix(in srgb, var(--color-info) 12%, var(--surface)); color: var(--color-info); }
.ce-stage-step[data-status="needs-input"] .ce-stage-status { color: var(--color-warning); }
.ce-stage-copy { display: flex; min-width: 0; flex-direction: column; gap: calc(var(--space-xs) / 2); }
.ce-stage-copy strong { overflow: hidden; font-size: 0.86rem; text-overflow: ellipsis; white-space: nowrap; }
+.ce-stage-purpose { color: var(--text-muted); font-size: 0.7rem; line-height: 1.25; }
.ce-stage-status { color: var(--text-muted); font-size: 0.7rem; }
.ce-stage-artifact { display: flex; width: calc(100% - var(--space-lg)); min-width: 0; align-items: center; gap: var(--space-xs); margin: 0 var(--space-sm) var(--space-sm); border: 0; background: transparent; color: var(--text-dim); cursor: pointer; font: inherit; font-size: 0.68rem; text-align: left; }
.ce-stage-artifact span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -793,7 +807,8 @@ The pipeline overview must communicate progression and the next useful action at
@media (max-width: 768px) {
.ce-pipeline-header { align-items: center; }
- .ce-stage-rail { grid-template-columns: 1fr; }
+ .ce-stage-rail,
+ .ce-stage-rail[data-count] { grid-template-columns: 1fr; }
.ce-stage-step, .ce-stage-step:first-child, .ce-stage-step:last-child { border: 1px solid var(--border); border-bottom: 0; border-radius: 0; }
.ce-stage-step:first-child { border-radius: var(--radius-md) var(--radius-md) 0 0; }
.ce-stage-step:last-child { border-bottom: 1px solid var(--border); border-radius: 0 0 var(--radius-md) var(--radius-md); }
diff --git a/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.tsx b/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.tsx
index f4771f9ae8..dc1700ef52 100644
--- a/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.tsx
+++ b/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.tsx
@@ -170,15 +170,18 @@ function SessionsPanel({
}
function nextPipelineStageId(stageId: string): string | undefined {
+ if (stageId === "strategy" || stageId === "ideate") return "brainstorm";
const stages = listPipelineStages();
const index = stages.findIndex((stage) => stage.stageId === stageId);
return index >= 0 ? stages[index + 1]?.stageId : undefined;
}
-type RailStatus = "Not started" | "Active" | "Needs input" | "Complete";
+type RailStatus = "Not started" | "Active" | "Needs input" | "Last run complete" | "History" | "Current";
function artifactsForStage(stageId: string, groups: CeArtifactGroup[]): CeArtifactEntry[] {
- const entries = groups.find((group) => group.stage === stageId)?.entries ?? [];
+ const entries = groups.find((group) => group.stage === stageId)?.entries
+ ?? (stageId === "compound" ? groups.find((group) => group.stage === "solution")?.entries : undefined)
+ ?? [];
if (stageId === "brainstorm") {
const unifiedEntries = groups.find((group) => group.stage === "plan")?.entries ?? [];
return [...entries, ...unifiedEntries.filter((entry) => (
@@ -200,16 +203,87 @@ function railStatus(stageId: string, sessions: CeSession[], artifact?: CeArtifac
const latest = sessions.find((session) => session.stage === stageId);
if (latest?.status === "awaiting_input") return "Needs input";
if (latest?.status === "active" || latest?.status === "launching") return "Active";
- if (latest?.status === "completed" || artifact?.kind === "artifact") return "Complete";
+ if (latest?.status === "completed") return "Last run complete";
+ if (artifact?.kind === "artifact") return stageId === "strategy" ? "Current" : "History";
return "Not started";
}
+const STAGE_PURPOSE: Record = {
+ strategy: "Set the product direction",
+ ideate: "Explore high-leverage ideas",
+ brainstorm: "Define what to build",
+ plan: "Decide how to build it",
+ work: "Build, simplify, and review",
+ compound: "Save reusable learnings",
+};
+
+function StageRail({
+ stages,
+ groups,
+ sessions,
+ disabled,
+ openFile,
+ onLaunch,
+ onOpenSession,
+}: {
+ stages: CeStageDefinition[];
+ groups: CeArtifactGroup[];
+ sessions: CeSession[];
+ disabled: boolean;
+ openFile?: PluginDashboardViewContext["openFile"];
+ onLaunch: (stage: CeStageDefinition) => void;
+ onOpenSession: (session: CeSession) => void;
+}) {
+ return (
+
+ {stages.map((stage) => {
+ const artifacts = artifactsForStage(stage.stageId, groups);
+ const artifact = artifacts[0];
+ const stageSessions = sessions.filter((session) => session.stage === stage.stageId);
+ const latestSession = stageSessions[0];
+ const status = railStatus(stage.stageId, sessions, artifact);
+ const Icon = resolveIcon(stage.icon);
+ return (
+
+ latestSession && !TERMINAL.has(latestSession.status) ? onOpenSession(latestSession) : onLaunch(stage)}
+ >
+
+
+ {stage.label}
+ {STAGE_PURPOSE[stage.stageId] ? {STAGE_PURPOSE[stage.stageId]} : null}
+
+ {status} · {stageSessions.length} {stageSessions.length === 1 ? "run" : "runs"} · {artifacts.length} {artifacts.length === 1 ? "artifact" : "artifacts"}
+
+
+
+ {artifact?.kind === "artifact" ? (
+ openFile?.(artifact.path, { workspace: "project" })}>
+
+ {artifact.name}
+
+ ) : No artifact yet }
+
+ );
+ })}
+
+ );
+}
+
/**
* FNXC:CompoundEngineeringUI 2026-07-10-12:00:
- * Operators need the five-stage compounding loop as the overview's primary navigation. Stages report repeatable run and artifact collections instead of implying one permanent completion; Strategy remains the upstream singleton anchor, while Debug remains a separate investigation action because it is not pipeline progression.
+ * Operators need the optional foundation plus four-stage delivery loop as the overview's primary navigation. Stages report repeatable run and artifact collections instead of implying one permanent completion; Strategy remains the upstream singleton anchor, while Debug remains a separate investigation action because it is not pipeline progression.
*
* FNXC:CompoundEngineeringUI 2026-07-10-23:40:
- * Compound Engineering follows the upstream repeatable loop: brainstorm requirements, plan implementation, work, review, compound, then repeat with better context. Brainstorm and Plan therefore expose collection counts and latest activity over multiple unified-plan files rather than a single artifact slot.
+ * Compound Engineering follows the upstream repeatable loop: brainstorm requirements, plan implementation, work through built-in simplification and review, compound the learning, then repeat with better context. Brainstorm and Plan therefore expose collection counts and latest activity over multiple unified-plan files rather than a single artifact slot.
+ *
+ * FNXC:CompoundEngineeringUI 2026-07-11-00:02:
+ * Strategy and Ideate are optional foundation work, not mandatory delivery gates. Visually separate them from the Brainstorm -> Plan -> Work -> Compound loop, and state that Work owns simplification and review so users do not repeat quality stages manually.
*/
function PipelineOverview({
groups,
@@ -226,14 +300,16 @@ function PipelineOverview({
onLaunch: (stage: CeStageDefinition) => void;
onOpenSession: (session: CeSession) => void;
}) {
- const stages = listPipelineStages();
+ const stages = listStages().filter((stage) => stage.stageId !== "debug");
+ const foundationStages = stages.filter((stage) => stage.stageId === "strategy" || stage.stageId === "ideate");
+ const deliveryStages = stages.filter((stage) => stage.stageId !== "strategy" && stage.stageId !== "ideate");
const debug = getStage("debug");
return (
-
- {stages.map((stage) => {
- const artifacts = artifactsForStage(stage.stageId, groups);
- const artifact = artifacts[0];
- const stageSessions = sessions.filter((session) => session.stage === stage.stageId);
- const latestSession = stageSessions[0];
- const status = railStatus(stage.stageId, sessions, artifact);
- const Icon = resolveIcon(stage.icon);
- return (
-
- latestSession && !TERMINAL.has(latestSession.status) ? onOpenSession(latestSession) : onLaunch(stage)}
- >
-
-
- {stage.label}
-
- {status} · {stageSessions.length} {stageSessions.length === 1 ? "run" : "runs"} · {artifacts.length} {artifacts.length === 1 ? "artifact" : "artifacts"}
-
-
-
- {artifact?.kind === "artifact" ? (
- openFile?.(artifact.path, { workspace: "project" })}>
-
- {artifact.name}
-
- ) : No artifact yet }
-
- );
- })}
-
+
+
Foundation Optional grounding
+
+
+
+
Delivery loop Repeat for each meaningful change
+
+
);
}
@@ -302,11 +350,11 @@ function EmptyState({ onStart }: { onStart: () => void }) {
Start your compounding pipeline
No compound-engineering artifacts found yet. Compound Engineering tracks the documents your
- pipeline produces — strategy, ideation, brainstorms, plans, solutions, and concepts — as you
+ workflow produces — strategy, ideation, brainstorms, plans, work logs, learnings, and concepts — as you
move through each stage.
- Begin with a stage and its artifact will appear here, grouped and traceable.
+ Start with Brainstorm when the problem is still fuzzy, Plan when requirements are settled, or Debug for broken behavior.
Start a stage
@@ -376,8 +424,14 @@ function StageGroup({
selectedId?: string;
openFile?: PluginDashboardViewContext["openFile"];
}) {
+ /*
+ * FNXC:CompoundEngineeringUI 2026-07-11-00:06:
+ * Brainstorm and Plan are repeatable collections that can contain dozens of artifacts. Keep the four newest visible for scanning and disclose the full history on demand so power does not overwhelm the default view.
+ */
const empty = group.entries.length === 0;
const singleton = group.stage === "strategy";
+ const visibleEntries = group.entries.slice(0, 4);
+ const remainingEntries = group.entries.slice(4);
return (
@@ -390,7 +444,7 @@ function StageGroup({
) : (
- {group.entries.map((entry) => (
+ {visibleEntries.map((entry) => (
)}
+ {remainingEntries.length > 0 ? (
+
+ Show all {group.entries.length}
+
+ {remainingEntries.map((entry) => (
+
+ ))}
+
+
+ ) : null}
);
}
function artifactDisplayGroups(groups: CeArtifactGroup[]): CeArtifactGroup[] {
const conventionalBrainstorms = groups.find((group) => group.stage === "brainstorm");
+ const hasCompoundGroup = groups.some((group) => group.stage === "compound");
return groups.flatMap((group) => {
if (group.stage === "brainstorm") return [];
+ if (group.stage === "solution") {
+ return hasCompoundGroup ? [] : [{ ...group, stage: "compound", label: "Learnings" }];
+ }
if (group.stage !== "plan") return [group];
const brainstorms = group.entries.filter((entry) => (
entry.kind === "artifact"
@@ -641,7 +715,7 @@ export function CompoundEngineeringView(props: CompoundEngineeringViewProps) {
) : null}
{launcherOpen ? (
-
+ stage.stageId !== "debug")} disabled={ceSession.busy} onLaunch={onLaunch} />
) : null}
): CeSession {
const ALL_STAGES: Array<{ stage: DiscoveryResult["groups"][number]["stage"]; label: string }> = [
{ stage: "strategy", label: "Strategy" },
{ stage: "ideate", label: "Ideate" },
- { stage: "plan", label: "Brainstorm / Plan" },
+ { stage: "brainstorm", label: "Brainstorms" },
+ { stage: "plan", label: "Plan" },
{ stage: "work", label: "Work" },
{ stage: "debug", label: "Debug" },
- { stage: "solution", label: "Solutions" },
+ { stage: "compound", label: "Learnings" },
{ stage: "concepts", label: "Concepts" },
];
@@ -155,6 +156,52 @@ describe("CompoundEngineeringView", () => {
expect(plans.querySelectorAll('[data-testid="ce-artifact"]')).toHaveLength(2);
});
+ it("keeps long artifact histories collapsed after the four newest entries", async () => {
+ listArtifacts.mockResolvedValue(
+ makeResult({
+ plan: Array.from({ length: 6 }, (_, index) => ({
+ kind: "artifact" as const,
+ id: `plan:docs/plans/${index}.md`,
+ stage: "plan",
+ path: `docs/plans/${index}.md`,
+ name: `${index}.md`,
+ size: 5,
+ updatedAt: 6 - index,
+ })),
+ }),
+ );
+
+ render( );
+
+ const plans = await screen.findByRole("heading", { name: "Plans" });
+ const group = plans.closest("[data-testid='ce-group']")!;
+ expect(group.querySelectorAll("[data-testid='ce-artifact']")).toHaveLength(6);
+ const disclosure = within(group).getByText("Show all 6").closest("details")!;
+ expect(disclosure).not.toHaveAttribute("open");
+ fireEvent.click(within(group).getByText("Show all 6"));
+ expect(disclosure).toHaveAttribute("open");
+ });
+
+ it("normalizes legacy solution groups into Compound learnings", async () => {
+ const result = makeResult({});
+ result.groups = result.groups.filter((group) => group.stage !== "compound");
+ result.groups.push({
+ stage: "solution",
+ label: "Solutions",
+ present: true,
+ entries: [{ kind: "artifact", id: "solution:docs/solutions/learned.md", stage: "solution", path: "docs/solutions/learned.md", name: "learned.md", size: 5, updatedAt: 1 }],
+ });
+ result.totalArtifacts = 1;
+ listArtifacts.mockResolvedValue(result);
+
+ render( );
+
+ expect(await screen.findByRole("heading", { name: "Learnings" })).toBeInTheDocument();
+ expect(screen.queryByRole("heading", { name: "Solutions" })).not.toBeInTheDocument();
+ const compound = screen.getAllByTestId("ce-pipeline-stage").find((node) => node.getAttribute("data-stage") === "compound");
+ expect(compound).toHaveTextContent("1 artifact");
+ });
+
it("opens an artifact in the built-in file viewer via context.openFile", async () => {
const openFile = vi.fn();
listArtifacts.mockResolvedValue(
@@ -243,7 +290,10 @@ describe("CompoundEngineeringView", () => {
expect(rows[1].querySelector("[data-testid='ce-session-cancel']")).toBeInTheDocument();
expect(rows[2].querySelector("[data-testid='ce-session-cancel']")).not.toBeInTheDocument();
expect(screen.getByTestId("ce-resume-latest")).toHaveTextContent(/input needed/i);
- expect(screen.getAllByTestId("ce-pipeline-stage")).toHaveLength(5);
+ expect(screen.getAllByTestId("ce-pipeline-stage")).toHaveLength(6);
+ expect(screen.getByText("Foundation")).toBeInTheDocument();
+ expect(screen.getByText("Delivery loop")).toBeInTheDocument();
+ expect(screen.getByText("Build, simplify, and review")).toBeInTheDocument();
});
it("renders no cancel affordance for an empty sessions list", async () => {
@@ -291,6 +341,19 @@ describe("CompoundEngineeringView", () => {
expect(deleteSession).not.toHaveBeenCalled();
});
+ it("hands optional foundation sessions directly into Brainstorm", async () => {
+ const strategy = mkCeSession({ id: "strategy-done", stage: "strategy", status: "completed" });
+ listArtifacts.mockResolvedValue(makeResult({}));
+ listSessions.mockResolvedValue([strategy]);
+ getSession.mockResolvedValue(strategy);
+ render( );
+
+ fireEvent.click((await screen.findAllByTestId("ce-session-open"))[0]);
+
+ expect(await screen.findByRole("button", { name: "Start Brainstorm" })).toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: "Start Ideate" })).not.toBeInTheDocument();
+ });
+
it("cancels an in-flight session via the list", async () => {
listArtifacts.mockResolvedValue(makeResult({}));
listSessions.mockResolvedValue([mkCeSession({ id: "running", stage: "plan", status: "active" })]);
@@ -356,7 +419,7 @@ describe("CompoundEngineeringView", () => {
await waitFor(() => expect(screen.queryByTestId("ce-sessions")).not.toBeInTheDocument());
});
- it("shows unified plan readiness and derives rail completion from artifacts", async () => {
+ it("shows unified plan readiness and distinguishes artifact history from completed runs", async () => {
listArtifacts.mockResolvedValue(makeResult({
plan: [{
kind: "artifact",
@@ -374,9 +437,9 @@ describe("CompoundEngineeringView", () => {
expect(await screen.findByText("Implementation ready")).toBeInTheDocument();
const plan = screen.getAllByTestId("ce-pipeline-stage").find((node) => node.getAttribute("data-stage") === "plan")!;
- expect(plan).toHaveTextContent("Complete");
+ expect(plan).toHaveTextContent("History");
const brainstorm = screen.getAllByTestId("ce-pipeline-stage").find((node) => node.getAttribute("data-stage") === "brainstorm")!;
- expect(brainstorm).toHaveTextContent("Complete");
+ expect(brainstorm).toHaveTextContent("History");
});
it("keeps Plan incomplete when only a requirements artifact exists", async () => {
@@ -396,7 +459,7 @@ describe("CompoundEngineeringView", () => {
render( );
const stages = await screen.findAllByTestId("ce-pipeline-stage");
- expect(stages.find((node) => node.getAttribute("data-stage") === "brainstorm")).toHaveTextContent("Complete");
+ expect(stages.find((node) => node.getAttribute("data-stage") === "brainstorm")).toHaveTextContent("History");
expect(stages.find((node) => node.getAttribute("data-stage") === "plan")).toHaveTextContent("Not started");
});
diff --git a/plugins/fusion-plugin-compound-engineering/src/session/stage-registry.ts b/plugins/fusion-plugin-compound-engineering/src/session/stage-registry.ts
index d1585ccf8f..b3fefd0038 100644
--- a/plugins/fusion-plugin-compound-engineering/src/session/stage-registry.ts
+++ b/plugins/fusion-plugin-compound-engineering/src/session/stage-registry.ts
@@ -55,6 +55,7 @@ const STAGE_DEFINITIONS: CeStageDefinition[] = [
{
stageId: "strategy",
order: 100,
+ participatesInPipeline: false,
skillId: "ce-strategy",
artifactLocation: "STRATEGY.md",
icon: "Compass",
@@ -64,6 +65,7 @@ const STAGE_DEFINITIONS: CeStageDefinition[] = [
{
stageId: "ideate",
order: 200,
+ participatesInPipeline: false,
skillId: "ce-ideate",
artifactLocation: "docs/ideation/",
icon: "Lightbulb",
@@ -105,13 +107,26 @@ const STAGE_DEFINITIONS: CeStageDefinition[] = [
label: "Work",
artifactGlob: "docs/work/**/*.md",
},
+ {
+ /*
+ * FNXC:CompoundEngineeringPipeline 2026-07-11-00:02:
+ * The upstream ce-work skill already owns implementation, phase-boundary simplification, and code review. Compound is the next distinct durable stage, saving reusable learnings after delivery instead of ending the user journey at Work.
+ */
+ stageId: "compound",
+ order: 600,
+ skillId: "ce-compound",
+ artifactLocation: "docs/solutions/",
+ icon: "BookOpenCheck",
+ label: "Compound",
+ artifactGlob: "docs/solutions/**/*.md",
+ },
{
/*
* FNXC:CompoundEngineeringPipeline 2026-07-10-22:52:
- * Debug is manually launchable but does not participate in automatic Strategy -> Ideate -> Brainstorm -> Plan -> Work progression. Work remains the terminal automatic stage.
+ * Debug is manually launchable but does not participate in automatic Strategy -> Ideate -> Brainstorm -> Plan -> Work -> Compound progression. Compound is the terminal automatic stage.
*/
stageId: "debug",
- order: 600,
+ order: 700,
participatesInPipeline: false,
skillId: "ce-debug",
artifactLocation: "docs/debug/",