diff --git a/.changeset/compound-engineering-plugin-scaffold.md b/.changeset/compound-engineering-plugin-scaffold.md new file mode 100644 index 0000000000..e28d171a36 --- /dev/null +++ b/.changeset/compound-engineering-plugin-scaffold.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": minor +--- + +Add the Compound Engineering bundled plugin (scaffold): a dedicated dashboard surface for compound-engineering artifacts and interactive ce-* sessions. diff --git a/packages/cli/src/plugins/__tests__/bundled-plugin-install.test.ts b/packages/cli/src/plugins/__tests__/bundled-plugin-install.test.ts index 9b6dbc0de2..90887da9af 100644 --- a/packages/cli/src/plugins/__tests__/bundled-plugin-install.test.ts +++ b/packages/cli/src/plugins/__tests__/bundled-plugin-install.test.ts @@ -44,6 +44,7 @@ const CURSOR_PLUGIN_ID = "fusion-plugin-cursor-runtime"; const ROADMAP_PLUGIN_ID = "fusion-plugin-roadmap"; const REPORTS_PLUGIN_ID = "fusion-plugin-reports"; const CLI_PRINTING_PRESS_PLUGIN_ID = "fusion-plugin-cli-printing-press"; +const COMPOUND_ENGINEERING_PLUGIN_ID = "fusion-plugin-compound-engineering"; function makeManifest(overrides?: Partial<{ id: string; version: string; name: string }>) { return { @@ -315,6 +316,10 @@ describe("ensureBundledDependencyGraphPluginInstalled", () => { it("includes reports plugin in bundled plugin ids", () => { expect(BUNDLED_PLUGIN_IDS).toContain(REPORTS_PLUGIN_ID); }); + + it("includes compound engineering plugin in bundled plugin ids", () => { + expect(BUNDLED_PLUGIN_IDS).toContain(COMPOUND_ENGINEERING_PLUGIN_ID); + }); it("fresh install: registers and loads the plugin when not in DB", async () => { setupBundleExists(); const store = makePluginStore(); diff --git a/packages/cli/src/plugins/bundled-plugin-install.ts b/packages/cli/src/plugins/bundled-plugin-install.ts index 506235bc9f..7ddf24a102 100644 --- a/packages/cli/src/plugins/bundled-plugin-install.ts +++ b/packages/cli/src/plugins/bundled-plugin-install.ts @@ -17,6 +17,7 @@ export const BUNDLED_PLUGIN_IDS = [ "fusion-plugin-paperclip-runtime", "fusion-plugin-cursor-runtime", "fusion-plugin-cli-printing-press", + "fusion-plugin-compound-engineering", ] as const; export type BundledPluginId = (typeof BUNDLED_PLUGIN_IDS)[number]; diff --git a/plugins/fusion-plugin-compound-engineering/manifest.json b/plugins/fusion-plugin-compound-engineering/manifest.json new file mode 100644 index 0000000000..dc53db3c6b --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/manifest.json @@ -0,0 +1,18 @@ +{ + "id": "fusion-plugin-compound-engineering", + "name": "Compound Engineering", + "version": "0.1.0", + "description": "A dedicated dashboard surface for compound-engineering artifacts and interactive ce-* sessions.", + "author": "Fusion Team", + "fusionVersion": ">=0.1.0", + "dashboardViews": [ + { + "viewId": "compound-engineering", + "label": "Compound Engineering", + "componentPath": "./dashboard-view", + "icon": "Sparkles", + "placement": "primary", + "order": 36 + } + ] +} diff --git a/plugins/fusion-plugin-compound-engineering/package.json b/plugins/fusion-plugin-compound-engineering/package.json new file mode 100644 index 0000000000..80b2b8e082 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/package.json @@ -0,0 +1,38 @@ +{ + "name": "@fusion-plugin-examples/compound-engineering", + "version": "0.1.0", + "type": "module", + "description": "Compound Engineering plugin for Fusion", + "private": true, + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./dashboard-view": { + "types": "./src/dashboard-view.tsx", + "import": "./src/dashboard-view.tsx" + } + }, + "scripts": { + "build": "tsc", + "test": "vitest run --silent=passed-only --reporter=dot" + }, + "dependencies": { + "@fusion/core": "workspace:*", + "@fusion/dashboard": "workspace:*", + "@fusion/plugin-sdk": "workspace:*", + "lucide-react": "^0.542.0", + "react": "^19.0.0", + "react-dom": "^19.2.4" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^19.0.0", + "@types/node": "^25.5.2", + "typescript": "^5.7.0", + "vitest": "^3.2.4" + } +} diff --git a/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts b/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts new file mode 100644 index 0000000000..6cbb2bcd0a --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import manifest from "../../manifest.json"; +import plugin from "../index.js"; + +describe("compound engineering plugin manifest", () => { + it("exports expected plugin id", () => { + expect(plugin.manifest.id).toBe("fusion-plugin-compound-engineering"); + }); + + it("keeps runtime manifest metadata aligned with manifest.json", () => { + expect(plugin.manifest.id).toBe(manifest.id); + expect(plugin.manifest.name).toBe(manifest.name); + expect(plugin.manifest.version).toBe(manifest.version); + expect(plugin.manifest.description).toBe(manifest.description); + expect(plugin.manifest.author).toBe(manifest.author); + expect(plugin.manifest.fusionVersion).toBe(manifest.fusionVersion); + }); + + it("registers a single dashboard view", () => { + expect(plugin.dashboardViews).toEqual([ + { + viewId: "compound-engineering", + label: "Compound Engineering", + componentPath: "./dashboard-view", + icon: "Sparkles", + placement: "primary", + order: 36, + }, + ]); + expect(manifest.dashboardViews).toEqual(plugin.dashboardViews); + }); + + it("ships an empty hooks/routes scaffold (U1)", () => { + expect(plugin.hooks).toEqual({}); + expect(plugin.routes).toEqual([]); + }); +}); diff --git a/plugins/fusion-plugin-compound-engineering/src/dashboard-view.tsx b/plugins/fusion-plugin-compound-engineering/src/dashboard-view.tsx new file mode 100644 index 0000000000..49af3fee7f --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/dashboard-view.tsx @@ -0,0 +1,13 @@ +import type { PluginDashboardViewContext } from "@fusion/dashboard/app/plugins/types"; + +/** + * Placeholder dashboard surface for the Compound Engineering plugin. + * + * U1 ships only a loadable scaffold. The real artifact hub and interactive + * ce-* session surface arrive in later units (U3+). + */ +export function CompoundEngineeringDashboardView(_props: { context?: PluginDashboardViewContext }) { + return
Compound Engineering
; +} + +export default CompoundEngineeringDashboardView; diff --git a/plugins/fusion-plugin-compound-engineering/src/index.ts b/plugins/fusion-plugin-compound-engineering/src/index.ts new file mode 100644 index 0000000000..49563f7459 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/index.ts @@ -0,0 +1,29 @@ +import { definePlugin } from "@fusion/plugin-sdk"; + +export { CompoundEngineeringDashboardView } from "./dashboard-view.js"; + +const plugin = definePlugin({ + manifest: { + id: "fusion-plugin-compound-engineering", + name: "Compound Engineering", + version: "0.1.0", + description: "A dedicated dashboard surface for compound-engineering artifacts and interactive ce-* sessions.", + author: "Fusion Team", + fusionVersion: ">=0.1.0", + }, + state: "installed", + hooks: {}, + routes: [], + dashboardViews: [ + { + viewId: "compound-engineering", + label: "Compound Engineering", + componentPath: "./dashboard-view", + icon: "Sparkles", + placement: "primary", + order: 36, + }, + ], +}); + +export default plugin; diff --git a/plugins/fusion-plugin-compound-engineering/tsconfig.json b/plugins/fusion-plugin-compound-engineering/tsconfig.json new file mode 100644 index 0000000000..710a839f68 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "jsx": "react-jsx", + "types": ["node", "vitest/globals"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/plugins/fusion-plugin-compound-engineering/vitest.config.ts b/plugins/fusion-plugin-compound-engineering/vitest.config.ts new file mode 100644 index 0000000000..e680986c0e --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/vitest.config.ts @@ -0,0 +1,39 @@ +import { defineConfig } from "vitest/config"; +import { fileURLToPath } from "node:url"; +import { computeMaxWorkers } from "../../packages/core/src/__test-utils__/vitest-workers"; + +const maxWorkers = computeMaxWorkers(); + +export default defineConfig({ + resolve: { + alias: [ + { + find: /^@fusion-plugin-examples\/compound-engineering\/dashboard-view$/, + replacement: fileURLToPath(new URL("./src/dashboard-view.tsx", import.meta.url)), + }, + { + find: /^@fusion-plugin-examples\/compound-engineering$/, + replacement: fileURLToPath(new URL("./src/index.ts", import.meta.url)), + }, + { find: "@fusion/core", replacement: fileURLToPath(new URL("../../packages/core/src/index.ts", import.meta.url)) }, + { + find: "@fusion/plugin-sdk", + replacement: fileURLToPath(new URL("../../packages/plugin-sdk/src/index.ts", import.meta.url)), + }, + { find: "@fusion/dashboard", replacement: fileURLToPath(new URL("../../packages/dashboard", import.meta.url)) }, + { + find: "lucide-react", + replacement: fileURLToPath(new URL("../../packages/dashboard/node_modules/lucide-react", import.meta.url)), + }, + ], + }, + test: { + include: ["src/**/*.test.{ts,tsx}"], + environment: "jsdom", + setupFiles: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-setup.ts", import.meta.url))], + globalSetup: [fileURLToPath(new URL("../../packages/core/src/__test-utils__/vitest-teardown.ts", import.meta.url))], + pool: "threads", + maxWorkers, + poolOptions: { threads: { minThreads: 1, maxThreads: maxWorkers }, forks: { minForks: 1, maxForks: maxWorkers } }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 659232f24d..cd268a90fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -692,6 +692,49 @@ importers: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.2)(jiti@2.7.0)(jsdom@29.0.1)(tsx@4.21.0)(yaml@2.9.0) + plugins/fusion-plugin-compound-engineering: + dependencies: + '@fusion/core': + specifier: workspace:* + version: link:../../packages/core + '@fusion/dashboard': + specifier: workspace:* + version: link:../../packages/dashboard + '@fusion/plugin-sdk': + specifier: workspace:* + version: link:../../packages/plugin-sdk + lucide-react: + specifier: ^0.542.0 + version: 0.542.0(react@19.2.4) + react: + specifier: ^19.0.0 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) + devDependencies: + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/node': + specifier: ^25.5.2 + version: 25.5.2 + '@types/react': + specifier: ^19.0.0 + version: 19.2.14 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.2)(jiti@2.7.0)(jsdom@29.0.1)(tsx@4.21.0)(yaml@2.9.0) + plugins/fusion-plugin-cursor-runtime: dependencies: '@earendil-works/pi-ai': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9ef1e5d982..0e725df5a3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,3 +13,4 @@ packages: - "plugins/fusion-plugin-roadmap" - "plugins/fusion-plugin-even-realities-glasses" - "plugins/fusion-plugin-reports" + - "plugins/fusion-plugin-compound-engineering"