diff --git a/COMMANDS.md b/COMMANDS.md index 5ac8c3c..577e1ec 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -203,6 +203,7 @@ fable-agent plinius godmode "improve explanation quality" - `fable5 route ` - `-d, --domain ` - `fable5 models` +- `fable5 flue` - `fable5 verify ` - `--repo ` - `fable5 goal ` diff --git a/src/fable5/flue-interop.test.ts b/src/fable5/flue-interop.test.ts new file mode 100644 index 0000000..2e25f3d --- /dev/null +++ b/src/fable5/flue-interop.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { FLUE_INTEROP_ROWS, formatFlueInteropReport, summarizeFlueInterop } from "./flue-interop.js"; + +describe("flue interop map", () => { + it("keeps Flue as an inspiration map, not vendored code", () => { + const report = formatFlueInteropReport(); + + expect(FLUE_INTEROP_ROWS.length).toBeGreaterThan(5); + expect(report).toContain("no vendored Flue code"); + expect(report).toContain("next implementable patch: channels"); + }); + + it("prioritizes gaps before partials", () => { + expect(summarizeFlueInterop().next.flueConcept).toBe("channels"); + }); +}); diff --git a/src/fable5/flue-interop.ts b/src/fable5/flue-interop.ts new file mode 100644 index 0000000..15f5a22 --- /dev/null +++ b/src/fable5/flue-interop.ts @@ -0,0 +1,92 @@ +export type FlueInteropStatus = "covered" | "partial" | "gap"; + +export interface FlueInteropRow { + flueConcept: string; + fableSurface: string; + status: FlueInteropStatus; + nextPatch: string; +} + +export const FLUE_INTEROP_ROWS: FlueInteropRow[] = [ + { + flueConcept: "agents", + fableSurface: "fable5 meta/teams/chains", + status: "covered", + nextPatch: "Keep using existing MetaAgent, AgentTeams, and AgentChain surfaces.", + }, + { + flueConcept: "workflows", + fableSurface: "fable5 workflow + DynamicWorkflows", + status: "covered", + nextPatch: "No new engine; add examples only when a real workflow needs them.", + }, + { + flueConcept: "sandboxes", + fableSurface: "WorktreeManager + factory agent-sandbox capability", + status: "partial", + nextPatch: "Add a sandbox command adapter after one deploy path needs isolation beyond git worktrees.", + }, + { + flueConcept: "durable execution", + fableSurface: "session checkpointing + StateStore + ZTE receipts", + status: "partial", + nextPatch: "Record workflow step receipts so interrupted multi-step runs resume from the last passed step.", + }, + { + flueConcept: "subagents", + fableSurface: "fable5 teams + fan-out workflow", + status: "covered", + nextPatch: "Use existing team roles; do not add another subagent abstraction.", + }, + { + flueConcept: "tools and skills", + fableSurface: "SkillRegistry + SKILLS sync + factory capability registry", + status: "covered", + nextPatch: "Bridge manifests only; do not vendor Flue tool code.", + }, + { + flueConcept: "MCP", + fableSurface: "factory capability registry", + status: "partial", + nextPatch: "Add optional MCP endpoint fields to the registry when a live MCP server is adopted.", + }, + { + flueConcept: "observability", + fableSurface: "agent-observability + Prometheus/Grafana factory services", + status: "covered", + nextPatch: "Expose existing metrics in receipts before adding tracing code.", + }, + { + flueConcept: "channels", + fableSurface: "none explicit", + status: "gap", + nextPatch: "Add a tiny channel manifest: stdin/stdout/files/http/webhook capabilities per executor.", + }, +]; + +export function summarizeFlueInterop(rows: FlueInteropRow[] = FLUE_INTEROP_ROWS): { covered: number; partial: number; gap: number; next: FlueInteropRow } { + const counts = rows.reduce( + (acc, row) => ({ ...acc, [row.status]: acc[row.status] + 1 }), + { covered: 0, partial: 0, gap: 0 }, + ); + return { ...counts, next: rows.find((row) => row.status === "gap") ?? rows.find((row) => row.status === "partial") ?? rows[0] }; +} + +export function formatFlueInteropReport(rows: FlueInteropRow[] = FLUE_INTEROP_ROWS): string { + const summary = summarizeFlueInterop(rows); + const lines = rows.map((row) => + ` ${row.status.toUpperCase().padEnd(7)} ${row.flueConcept.padEnd(18)} → ${row.fableSurface}\n next: ${row.nextPatch}`, + ); + + return [ + "", + " Flue inspiration map (no vendored Flue code)", + " ─────────────────────────────", + ` covered=${summary.covered} partial=${summary.partial} gap=${summary.gap}`, + "", + ...lines, + "", + ` next implementable patch: ${summary.next.flueConcept} — ${summary.next.nextPatch}`, + "", + ].join("\n"); +} diff --git a/src/fable5/index.ts b/src/fable5/index.ts index 8dcb819..ee985ea 100644 --- a/src/fable5/index.ts +++ b/src/fable5/index.ts @@ -47,5 +47,8 @@ export type { ZteReceipt, ZteSpec, ZteSpecOptions } from "./zte-protocol.js"; export { formatCapabilityReport, loadFactoryCapabilities, parseFactoryCapabilities, probeFactoryCapabilities } from "./factory-capabilities.js"; export type { CapabilityProbeResult, CapabilityReport, FactoryCapabilityService } from "./factory-capabilities.js"; +export { FLUE_INTEROP_ROWS, formatFlueInteropReport, summarizeFlueInterop } from "./flue-interop.js"; +export type { FlueInteropRow, FlueInteropStatus } from "./flue-interop.js"; + export { createForgejoIntakeReceipt, intakeForgejoItem, parseForgejoRef, routeForgejoLabels } from "./forgejo-intake.js"; export type { ForgejoIntakeKind, ForgejoIntakeOptions, ForgejoIntakeReceipt, ForgejoIntakeSource, ForgejoItem, ForgejoRoute } from "./forgejo-intake.js"; diff --git a/src/index.ts b/src/index.ts index 2b0bb2d..70b10d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1579,6 +1579,14 @@ fable console.log(` `); }); +fable + .command("flue") + .description("Show Flue-inspired interop gaps without vendoring Flue") + .action(async () => { + const { formatFlueInteropReport } = await import("./fable5/flue-interop.js"); + console.log(formatFlueInteropReport()); + }); + fable .command("verify ") .description("Run independent verifier against a task")