Files
fusion/plugins/fusion-plugin-compound-engineering/src/index.ts
gsxdsm 81c5bfeb27 FN-6085: avoid dashboard CSS import in plugin entry
Prevent the Compound Engineering plugin from pulling dashboard CSS into Node-side plugin loading.

- remove the dashboard view re-export from the Compound Engineering plugin server entry
- document the CSS-import failure mode caused by server-side index re-exports of dashboard views
- update bundled plugin registration guidance to keep dashboard view loading client-only

Files changed:
 docs/solutions/integration-issues/bundled-plugin-registration-drift.md |  4 ++--
 docs/solutions/integration-issues/bundled-plugin-vite-alias-missing.md | 13 ++++++++++++-
 plugins/fusion-plugin-compound-engineering/src/index.ts     |  1 -
 3 files changed, 14 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-6085

Fusion-Task-Lineage: 38e6d4de-18ff-4f7b-9ea4-39b0653b4cce
2026-06-09 12:50:26 -07:00

147 lines
5.8 KiB
TypeScript

import { definePlugin } from "@fusion/plugin-sdk";
import { COMPOUND_ENGINEERING_SKILLS } from "./skills.js";
import { installBundledCeSkills } from "./skill-installation.js";
import { ensureCeSchema } from "./schema.js";
import { createSessionRoutes } from "./routes/session-routes.js";
import { createArtifactRoutes } from "./routes/artifact-routes.js";
import { getCePipelineStore } from "./sync/pipeline-store.js";
import { reconcileCePipelines } from "./sync/reconciler.js";
import { settingsSchema } from "./settings.js";
import { getReconcileOnHooks } from "./settings.js";
export { COMPOUND_ENGINEERING_SKILLS } from "./skills.js";
export {
installBundledCeSkills,
resolveBundledSkillsRoot,
resolveDefaultInstallTargetRoot,
isPluginLocalPath,
} from "./skill-installation.js";
export { ensureCeSchema } from "./schema.js";
export { CeSessionStore, getCeSessionStore } from "./session/session-store.js";
export { CePipelineStore, getCePipelineStore } from "./sync/pipeline-store.js";
export type {
CePipelineLink,
CreateCePipelineLinkInput,
CePipelineState,
CePipelineStatus,
CeSyncQueueEntry,
CeSyncReason,
} from "./sync/pipeline-store.js";
export { CeReconciler, reconcileCePipelines } from "./sync/reconciler.js";
export type { ReconcileResult } from "./sync/reconciler.js";
export {
CeOrchestrator,
WORK_STAGE_ID,
CE_PLUGIN_ID,
CE_WORK_SOURCE_TYPE,
} from "./session/orchestrator.js";
export { getStage, listStages, registerStage } from "./session/stage-registry.js";
export {
settingsSchema,
getDefaultProvider,
getDefaultModelId,
getEnabledStages,
getReconcileOnHooks,
getReconcileIntervalMinutes,
} from "./settings.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",
skills: COMPOUND_ENGINEERING_SKILLS.map((s) => ({ skillId: s.skillId, name: s.name })),
settingsSchema,
},
state: "installed",
skills: COMPOUND_ENGINEERING_SKILLS,
hooks: {
// Idempotent DDL for the plugin-local CE tables (ce_sessions). Runs against
// the same DB route handlers reach via ctx.taskStore.getDatabase() (U5).
onSchemaInit: ensureCeSchema,
// INBOUND board→pipeline sync (U8 / FN-5719). The 5s hook budget
// (plugin-runner invokeHookSafe) means these MUST be fast: resolve the link,
// ENQUEUE a sync signal, and return. Heavy advancement (board reads, outbound
// task creation) happens in the reconciler, NOT inline here. A reconcile
// drain is fired-and-forgotten (never awaited) so a slow sweep cannot blow
// the hook budget; correctness does not depend on it firing because the next
// reconcile() sweep re-derives the transition from board truth.
onTaskMoved: (task, fromColumn, toColumn, ctx) => {
const store = getCePipelineStore(ctx);
const link = store.findByTaskId(task.id);
if (!link) return; // not a CE-linked task → ignore fast.
store.enqueueSync({
cePipelineId: link.cePipelineId,
taskId: task.id,
reason: "task_moved",
fromColumn,
toColumn,
});
// Setting-gated auto-drain (U9): when disabled, the enqueue still happens
// so an on-demand reconcile (route/refresh) converges later; we just skip
// the inline sweep.
if (!getReconcileOnHooks(ctx.settings)) return;
void Promise.resolve()
.then(() => reconcileCePipelines(ctx))
.catch((err) => ctx.logger.warn(`CE reconcile (onTaskMoved) failed: ${String(err)}`));
},
onTaskCompleted: (task, ctx) => {
const store = getCePipelineStore(ctx);
const link = store.findByTaskId(task.id);
if (!link) return;
store.enqueueSync({
cePipelineId: link.cePipelineId,
taskId: task.id,
reason: "task_completed",
toColumn: "done",
});
if (!getReconcileOnHooks(ctx.settings)) return;
void Promise.resolve()
.then(() => reconcileCePipelines(ctx))
.catch((err) => ctx.logger.warn(`CE reconcile (onTaskCompleted) failed: ${String(err)}`));
},
// Install the bundled, pinned ce-* SKILL.md files into a plugin-local,
// discoverable directory on load. The engine ingests
// PluginSkillContribution only as a name; physical discovery requires the
// files to exist on a path it scans (U2 finding). Install is idempotent
// (skip-if-exists) and guarded to never touch a global ~/.claude/skills.
onLoad: async (ctx) => {
try {
const { targetRoot, results } = installBundledCeSkills();
const installed = results.filter((r) => r.outcome === "installed").length;
const errored = results.filter((r) => r.outcome === "error");
if (errored.length > 0) {
ctx.logger.warn(
`Compound Engineering: ${errored.length} skill(s) failed to install: ${errored
.map((e) => `${e.skillId} (${e.reason})`)
.join(", ")}`,
);
}
ctx.logger.info(
`Compound Engineering skills ready — installed=${installed} target=${targetRoot}`,
);
ctx.emitEvent("compound-engineering:skills-installed", { targetRoot, results });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
ctx.logger.error(`Compound Engineering skill install failed: ${message}`);
}
},
},
routes: [...createSessionRoutes(), ...createArtifactRoutes()],
dashboardViews: [
{
viewId: "compound-engineering",
label: "Compound Engineering",
componentPath: "./dashboard-view",
icon: "Sparkles",
placement: "primary",
order: 36,
},
],
});
export default plugin;