From 6d12ee3e9bd54e0c92db44e4a476f79028aa3051 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 2 Jun 2026 18:59:41 -0700 Subject: [PATCH] feat(compound-engineering): bundle and install ce-* pipeline skills (U2) Bundle pinned copies of 7 CE pipeline-stage skills (strategy, ideate, brainstorm, plan, work, code-review, compound) under src/skills/ and declare them via PluginSkillContribution. Empirical finding: the skills contribution alone does not make a SKILL.md resolvable in a session -- the engine ingests it as a name only. So onLoad runs an idempotent, isolation-guarded physical install into a plugin-local .fusion-ce-skills/ dir (never a global ~/.claude/skills), which the engine skill-resolver can then discover. Proven against the real loadSkills + resolveSessionSkills pipeline. --- ...pound-engineering-skill-resolution.test.ts | 159 ++++ .../.gitignore | 3 + .../src/__tests__/manifest.test.ts | 30 +- .../src/__tests__/skill-installation.test.ts | 114 +++ .../src/index.ts | 40 +- .../src/skill-installation.ts | 159 ++++ .../src/skills.ts | 79 ++ .../src/skills/ce-brainstorm/SKILL.md | 283 ++++++ .../references/brainstorm-sections.md | 263 +++++ .../ce-brainstorm/references/handoff.md | 132 +++ .../references/html-rendering.md | 538 +++++++++++ .../references/markdown-rendering.md | 207 ++++ .../references/synthesis-summary.md | 271 ++++++ .../references/universal-brainstorming.md | 63 ++ .../src/skills/ce-code-review/SKILL.md | 898 ++++++++++++++++++ .../ce-code-review/references/bulk-preview.md | 112 +++ .../ce-code-review/references/diff-scope.md | 31 + .../references/findings-schema.json | 139 +++ .../references/persona-catalog.md | 63 ++ .../references/review-output-template.md | 147 +++ .../references/subagent-template.md | 200 ++++ .../references/tracker-defer.md | 149 +++ .../references/validator-template.md | 85 ++ .../ce-code-review/references/walkthrough.md | 249 +++++ .../src/skills/ce-compound/SKILL.md | 628 ++++++++++++ .../ce-compound/assets/resolution-template.md | 94 ++ .../references/concepts-vocabulary.md | 78 ++ .../skills/ce-compound/references/schema.yaml | 231 +++++ .../ce-compound/references/yaml-schema.md | 118 +++ .../scripts/validate-frontmatter.py | 137 +++ .../src/skills/ce-ideate/SKILL.md | 400 ++++++++ .../references/post-ideation-workflow.md | 252 +++++ .../references/universal-ideation.md | 103 ++ .../references/web-research-cache.md | 55 ++ .../src/skills/ce-plan/SKILL.md | 770 +++++++++++++++ .../ce-plan/references/deepening-workflow.md | 249 +++++ .../ce-plan/references/html-rendering.md | 538 +++++++++++ .../ce-plan/references/markdown-rendering.md | 207 ++++ .../skills/ce-plan/references/plan-handoff.md | 126 +++ .../ce-plan/references/plan-sections.md | 240 +++++ .../ce-plan/references/synthesis-summary.md | 396 ++++++++ .../ce-plan/references/universal-planning.md | 167 ++++ .../src/skills/ce-strategy/SKILL.md | 97 ++ .../ce-strategy/references/interview.md | 143 +++ .../references/strategy-template.md | 89 ++ .../src/skills/ce-work/SKILL.md | 369 +++++++ .../ce-work/references/shipping-workflow.md | 154 +++ .../ce-work/references/tracker-defer.md | 149 +++ 48 files changed, 10201 insertions(+), 3 deletions(-) create mode 100644 packages/engine/src/__tests__/compound-engineering-skill-resolution.test.ts create mode 100644 plugins/fusion-plugin-compound-engineering/.gitignore create mode 100644 plugins/fusion-plugin-compound-engineering/src/__tests__/skill-installation.test.ts create mode 100644 plugins/fusion-plugin-compound-engineering/src/skill-installation.ts create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills.ts create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/SKILL.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/brainstorm-sections.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/handoff.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/html-rendering.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/markdown-rendering.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/synthesis-summary.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/universal-brainstorming.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/SKILL.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/bulk-preview.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/diff-scope.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/findings-schema.json create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/persona-catalog.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/review-output-template.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/subagent-template.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/tracker-defer.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/validator-template.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-code-review/references/walkthrough.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-compound/SKILL.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-compound/assets/resolution-template.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-compound/references/concepts-vocabulary.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-compound/references/schema.yaml create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-compound/references/yaml-schema.md create mode 100755 plugins/fusion-plugin-compound-engineering/src/skills/ce-compound/scripts/validate-frontmatter.py create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-ideate/SKILL.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-ideate/references/post-ideation-workflow.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-ideate/references/universal-ideation.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-ideate/references/web-research-cache.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-plan/SKILL.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-plan/references/deepening-workflow.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-plan/references/html-rendering.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-plan/references/markdown-rendering.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-plan/references/plan-handoff.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-plan/references/plan-sections.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-plan/references/synthesis-summary.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-plan/references/universal-planning.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-strategy/SKILL.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-strategy/references/interview.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-strategy/references/strategy-template.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-work/SKILL.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-work/references/shipping-workflow.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-work/references/tracker-defer.md diff --git a/packages/engine/src/__tests__/compound-engineering-skill-resolution.test.ts b/packages/engine/src/__tests__/compound-engineering-skill-resolution.test.ts new file mode 100644 index 0000000000..cb4d5f2046 --- /dev/null +++ b/packages/engine/src/__tests__/compound-engineering-skill-resolution.test.ts @@ -0,0 +1,159 @@ +/** + * U2 — Empirical proof of how Compound Engineering bundled skills become + * resolvable in an agent session. + * + * This drives the REAL engine skill pipeline: + * pi-coding-agent `loadSkills` (disk discovery) → + * `resolveSessionSkills` + `createSkillsOverrideFromSelection` (the same + * path `createFnAgent` uses in pi.ts via DefaultResourceLoader.skillsOverride). + * + * THE QUESTION: does declaring `skills: PluginSkillContribution[]` (whose + * contribution surfaces only as a *name* in `requestedSkillNames`) make a + * bundled SKILL.md resolvable, OR is a physical install into a discoverable + * directory also required? + * + * ANSWER (asserted below): the contribution alone is NOT enough. The engine + * never ingests `PluginSkillContribution.skillFiles` into the discovered set; + * the requested name has nothing on disk to match. A physical, plugin-local + * install (so the SKILL.md lives on a path `loadSkills` scans) is REQUIRED. + * + * The test is self-contained on the engine side: it models "a physical install" + * by materializing a `ce-plan/SKILL.md` on disk and pointing disk discovery at + * its parent dir — exactly what the plugin's `installBundledCeSkills` does into + * a plugin-local directory wired through `additionalSkillPaths`. (The plugin's + * own cpSync + isolation behavior is verified in the plugin package's + * skill-installation.test.ts; the engine package cannot import plugin source + * without violating its tsc rootDir.) + */ + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { loadSkills } from "@earendil-works/pi-coding-agent"; +import { + createSkillsOverrideFromSelection, + resolveSessionSkills, +} from "../skill-resolver.js"; + +vi.mock("../logger.js", () => ({ + piLog: { log: vi.fn(), warn: vi.fn(), error: vi.fn() }, +})); + +const CE_STAGES = [ + "ce-strategy", + "ce-ideate", + "ce-brainstorm", + "ce-plan", + "ce-work", + "ce-code-review", + "ce-compound", +] as const; + +/** Model the plugin-local physical install: write each stage's SKILL.md to disk. */ +function materializeInstalledSkills(root: string, stages: readonly string[]): void { + for (const id of stages) { + const dir = join(root, id); + mkdirSync(dir, { recursive: true }); + writeFileSync( + join(dir, "SKILL.md"), + `---\nname: ${id}\ndescription: ${id} pipeline stage\n---\n\n# ${id}\n`, + ); + } +} + +/** + * Run the exact engine resolution path for a session that requests CE skill + * names (as if a plugin contributed them via getPluginSkills -> + * requestedSkillNames), over whatever skills `loadSkills` discovers from + * `discoveredSkillPaths`. Returns the resolved skill names visible to the + * session. + */ +function resolveSessionFor(opts: { + projectRootDir: string; + agentDir: string; + discoveredSkillPaths: string[]; + requestedSkillNames: string[]; +}): string[] { + // 1. Disk discovery — exactly what DefaultResourceLoader feeds to its override. + const discovered = loadSkills({ + cwd: opts.projectRootDir, + agentDir: opts.agentDir, + skillPaths: opts.discoveredSkillPaths, + includeDefaults: false, + }); + + // 2. Engine resolver (project settings + requested names). + const selection = resolveSessionSkills({ + projectRootDir: opts.projectRootDir, + requestedSkillNames: opts.requestedSkillNames, + sessionPurpose: "executor", + }); + const override = createSkillsOverrideFromSelection(selection, { + requestedSkillNames: opts.requestedSkillNames, + sessionPurpose: "executor", + }); + + const result = override({ skills: discovered.skills, diagnostics: discovered.diagnostics }); + return result.skills.map((s) => s.name); +} + +describe("U2: CE bundled skill session-resolution (empirical)", () => { + let tmp: string; + let projectRootDir: string; + let agentDir: string; + + beforeEach(() => { + tmp = mkdtempSync(join(tmpdir(), "ce-resolve-")); + // An empty project + empty agent dir: NOTHING ce-* is discoverable yet. + projectRootDir = join(tmp, "project"); + agentDir = join(tmp, "agent"); + }); + + afterEach(() => { + rmSync(tmp, { recursive: true, force: true }); + }); + + it("FAILING-FIRST: contribution name alone (no physical install) does NOT resolve ce-plan", () => { + // Simulate: plugin declared skills -> requestedSkillNames includes ce-plan, + // but no SKILL.md was installed anywhere discoverable. + const resolved = resolveSessionFor({ + projectRootDir, + agentDir, + discoveredSkillPaths: [], // nothing on disk + requestedSkillNames: ["ce-plan"], + }); + // Proves the contribution alone is insufficient: ce-plan is NOT resolvable. + expect(resolved).not.toContain("ce-plan"); + expect(resolved).toEqual([]); + }); + + it("PASSING: after a plugin-local physical install, ce-plan IS resolvable for the session", () => { + const installRoot = join(tmp, "plugin-local", ".fusion-ce-skills"); + materializeInstalledSkills(installRoot, ["ce-plan"]); + + const resolved = resolveSessionFor({ + projectRootDir, + agentDir, + discoveredSkillPaths: [installRoot], // installed dir is now discoverable + requestedSkillNames: ["ce-plan"], + }); + + expect(resolved).toContain("ce-plan"); + }); + + it("PASSING: all seven CE stages resolve when requested after install", () => { + const installRoot = join(tmp, ".fusion-ce-skills"); + materializeInstalledSkills(installRoot, CE_STAGES); + + const resolved = resolveSessionFor({ + projectRootDir, + agentDir, + discoveredSkillPaths: [installRoot], + requestedSkillNames: [...CE_STAGES], + }); + for (const s of CE_STAGES) { + expect(resolved).toContain(s); + } + }); +}); diff --git a/plugins/fusion-plugin-compound-engineering/.gitignore b/plugins/fusion-plugin-compound-engineering/.gitignore new file mode 100644 index 0000000000..c4379f32ff --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/.gitignore @@ -0,0 +1,3 @@ +# Runtime, plugin-local install target for bundled ce-* skills (U2). +# Populated by installBundledCeSkills() on plugin load; never committed. +.fusion-ce-skills/ diff --git a/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts b/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts index 6cbb2bcd0a..ef9e9b66e3 100644 --- a/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts +++ b/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import manifest from "../../manifest.json"; import plugin from "../index.js"; +import { COMPOUND_ENGINEERING_SKILLS } from "../skills.js"; describe("compound engineering plugin manifest", () => { it("exports expected plugin id", () => { @@ -30,8 +31,33 @@ describe("compound engineering plugin manifest", () => { expect(manifest.dashboardViews).toEqual(plugin.dashboardViews); }); - it("ships an empty hooks/routes scaffold (U1)", () => { - expect(plugin.hooks).toEqual({}); + it("ships an empty routes scaffold (U1)", () => { expect(plugin.routes).toEqual([]); }); + + it("registers the bundled CE pipeline-stage skills on plugin and manifest (U2)", () => { + const expectedIds = [ + "ce-strategy", + "ce-ideate", + "ce-brainstorm", + "ce-plan", + "ce-work", + "ce-code-review", + "ce-compound", + ]; + expect(COMPOUND_ENGINEERING_SKILLS.map((s) => s.skillId)).toEqual(expectedIds); + expect(plugin.skills).toBe(COMPOUND_ENGINEERING_SKILLS); + // Manifest mirrors agent-browser: { skillId, name } projection. + expect(plugin.manifest.skills).toEqual( + COMPOUND_ENGINEERING_SKILLS.map((s) => ({ skillId: s.skillId, name: s.name })), + ); + // Each contribution points at a plugin-root-relative bundled SKILL.md. + for (const s of COMPOUND_ENGINEERING_SKILLS) { + expect(s.skillFiles).toEqual([`skills/${s.skillId}/SKILL.md`]); + } + }); + + it("registers an onLoad hook that installs bundled skills (U2)", () => { + expect(typeof plugin.hooks?.onLoad).toBe("function"); + }); }); diff --git a/plugins/fusion-plugin-compound-engineering/src/__tests__/skill-installation.test.ts b/plugins/fusion-plugin-compound-engineering/src/__tests__/skill-installation.test.ts new file mode 100644 index 0000000000..93df601021 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/__tests__/skill-installation.test.ts @@ -0,0 +1,114 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + assertPluginLocalTarget, + installBundledCeSkills, + isPluginLocalPath, + resolveBundledSkillsRoot, +} from "../skill-installation.js"; +import { COMPOUND_ENGINEERING_SKILLS } from "../skills.js"; + +describe("compound engineering bundled skill install", () => { + let tmp: string; + + beforeEach(() => { + tmp = mkdtempSync(join(tmpdir(), "ce-skill-install-")); + }); + + afterEach(() => { + rmSync(tmp, { recursive: true, force: true }); + }); + + it("installs every bundled CE skill into the plugin-local target", () => { + const targetRoot = join(tmp, "plugin-local", ".fusion-ce-skills"); + const { results } = installBundledCeSkills({ targetRoot }); + + for (const skill of COMPOUND_ENGINEERING_SKILLS) { + const r = results.find((x) => x.skillId === skill.skillId)!; + expect(r.outcome).toBe("installed"); + const skillMd = join(targetRoot, skill.skillId, "SKILL.md"); + expect(existsSync(skillMd)).toBe(true); + } + }); + + it("is idempotent: a second run with the target present is a skip-if-exists no-op", () => { + const targetRoot = join(tmp, ".fusion-ce-skills"); + const first = installBundledCeSkills({ targetRoot }); + expect(first.results.every((r) => r.outcome === "installed")).toBe(true); + + // Tamper with an installed file; skip-if-exists must NOT overwrite it. + const sentinelPath = join(targetRoot, "ce-plan", "SKILL.md"); + writeFileSync(sentinelPath, "SENTINEL"); + + const second = installBundledCeSkills({ targetRoot }); + expect(second.results.every((r) => r.outcome === "skipped")).toBe(true); + expect(readFileSync(sentinelPath, "utf-8")).toBe("SENTINEL"); + }); + + // ── AE2: isolation — a global compound-engineering install is untouched ── + it("AE2: never writes outside the plugin-local target when a global install exists", () => { + // Seed a fake global compound-engineering install under a fake HOME. + const fakeHome = join(tmp, "home"); + const globalSkillsDir = join(fakeHome, ".claude", "skills", "ce-plan"); + mkdirSync(globalSkillsDir, { recursive: true }); + const globalSkillMd = join(globalSkillsDir, "SKILL.md"); + writeFileSync(globalSkillMd, "GLOBAL-ORIGINAL"); + const beforeContent = readFileSync(globalSkillMd, "utf-8"); + const beforeMtime = statSync(globalSkillMd).mtimeMs; + + const targetRoot = join(tmp, "plugin-local", ".fusion-ce-skills"); + const { targetRoot: usedTarget, results } = installBundledCeSkills({ targetRoot }); + + // The install target is provably plugin-local, never the global dir. + expect(usedTarget.includes(join(".claude", "skills"))).toBe(false); + expect(isPluginLocalPath(usedTarget)).toBe(true); + for (const r of results) { + expect(r.targetDir.includes(join(".claude", "skills"))).toBe(false); + } + + // The global install is byte-for-byte and mtime untouched. + expect(readFileSync(globalSkillMd, "utf-8")).toBe(beforeContent); + expect(statSync(globalSkillMd).mtimeMs).toBe(beforeMtime); + }); + + it("AE2 guard: refuses to install into a global client skills directory", () => { + const globalTarget = join(tmp, "home", ".claude", "skills"); + expect(() => assertPluginLocalTarget(globalTarget)).toThrow(/plugin-local/i); + expect(() => installBundledCeSkills({ targetRoot: globalTarget })).toThrow(/plugin-local/i); + expect(isPluginLocalPath(globalTarget)).toBe(false); + }); + + // ── Edge: malformed/missing SKILL.md surfaces a clear error ── + it("edge: a missing/malformed bundled SKILL.md surfaces a clear load error, not a silent skip", () => { + // Point at an empty source root so every skill's source dir is missing. + const emptySource = join(tmp, "empty-source"); + mkdirSync(emptySource, { recursive: true }); + const targetRoot = join(tmp, ".fusion-ce-skills"); + + const { results } = installBundledCeSkills({ targetRoot, sourceRoot: emptySource }); + for (const r of results) { + expect(r.outcome).toBe("error"); + expect(r.reason).toMatch(/missing|SKILL\.md/i); + } + + // Now a malformed SKILL.md (no frontmatter name) for one skill. + const malformedSource = join(tmp, "malformed-source"); + const planDir = join(malformedSource, "ce-plan"); + mkdirSync(planDir, { recursive: true }); + writeFileSync(join(planDir, "SKILL.md"), "no frontmatter here\n"); + const res2 = installBundledCeSkills({ targetRoot: join(tmp, "t2"), sourceRoot: malformedSource }); + const plan = res2.results.find((r) => r.skillId === "ce-plan")!; + expect(plan.outcome).toBe("error"); + expect(plan.reason).toMatch(/frontmatter 'name:'/i); + }); + + it("bundled source root resolves and contains all SKILL.md files", () => { + const root = resolveBundledSkillsRoot(); + expect(existsSync(root)).toBe(true); + for (const skill of COMPOUND_ENGINEERING_SKILLS) { + expect(existsSync(join(root, skill.skillId, "SKILL.md"))).toBe(true); + } + }); +}); diff --git a/plugins/fusion-plugin-compound-engineering/src/index.ts b/plugins/fusion-plugin-compound-engineering/src/index.ts index 49563f7459..ef2e33cd8f 100644 --- a/plugins/fusion-plugin-compound-engineering/src/index.ts +++ b/plugins/fusion-plugin-compound-engineering/src/index.ts @@ -1,6 +1,15 @@ import { definePlugin } from "@fusion/plugin-sdk"; +import { COMPOUND_ENGINEERING_SKILLS } from "./skills.js"; +import { installBundledCeSkills } from "./skill-installation.js"; export { CompoundEngineeringDashboardView } from "./dashboard-view.js"; +export { COMPOUND_ENGINEERING_SKILLS } from "./skills.js"; +export { + installBundledCeSkills, + resolveBundledSkillsRoot, + resolveDefaultInstallTargetRoot, + isPluginLocalPath, +} from "./skill-installation.js"; const plugin = definePlugin({ manifest: { @@ -10,9 +19,38 @@ const plugin = definePlugin({ 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 })), }, state: "installed", - hooks: {}, + skills: COMPOUND_ENGINEERING_SKILLS, + hooks: { + // 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: [], dashboardViews: [ { diff --git a/plugins/fusion-plugin-compound-engineering/src/skill-installation.ts b/plugins/fusion-plugin-compound-engineering/src/skill-installation.ts new file mode 100644 index 0000000000..22769787df --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/skill-installation.ts @@ -0,0 +1,159 @@ +import { cpSync, existsSync, mkdirSync, readFileSync } from "node:fs"; +import { dirname, isAbsolute, join, resolve, sep } from "node:path"; +import { fileURLToPath } from "node:url"; +import { COMPOUND_ENGINEERING_SKILLS } from "./skills.js"; + +/** + * Physical install of the bundled Compound Engineering skills. + * + * EMPIRICAL FINDING (U2): the engine never ingests + * `PluginSkillContribution.skillFiles` into the set of skills that + * pi-coding-agent's `DefaultResourceLoader`/`loadSkills` discovers. The + * contribution only contributes a *name* to `requestedSkillNames`, which the + * skill-resolver then tries to MATCH against skills already discovered from + * disk. If no `SKILL.md` for that name was discovered, the requested name + * resolves to nothing. Therefore a physical install into a discoverable, + * PLUGIN-LOCAL skills directory is required. + * + * This mirrors the cpSync + skip-if-exists pattern of + * `installBundledFusionSkill` (packages/cli) but the target is ALWAYS + * plugin-local — it MUST NOT be a global `/.claude/skills` path (R12/AE2). + * The installed directory is intended to be wired into a session via + * `additionalSkillPaths` (engine-side, in later units), keeping discovery + * scoped to the plugin and never clobbering a user's global install. + */ + +export type CeSkillInstallOutcome = "installed" | "skipped" | "error"; + +export interface CeSkillInstallResult { + skillId: string; + sourceDir: string; + targetDir: string; + outcome: CeSkillInstallOutcome; + reason?: string; +} + +export interface InstallBundledCeSkillsResult { + targetRoot: string; + results: CeSkillInstallResult[]; +} + +/** + * Absolute path to the plugin's bundled `src/skills` directory (the pinned + * source of truth). Resolved relative to this module so it is correct whether + * running from `src` (tests/dev) or `dist` (build output). + */ +export function resolveBundledSkillsRoot(): string { + const here = fileURLToPath(import.meta.url); + // src/skill-installation.ts -> src/skills ; dist/skill-installation.js -> the + // bundled skills live next to source under src/skills, so when running from + // dist we walk up one and into src/skills. + const dir = dirname(here); + const local = resolve(dir, "skills"); + if (existsSync(local)) return local; + return resolve(dir, "..", "src", "skills"); +} + +/** + * Default plugin-local install target. Lives under the plugin package root + * (`.fusion-ce-skills/`), which is ALWAYS plugin-local and never a global + * client skills directory. Callers may override via `targetRoot` (e.g. tests), + * but a guard rejects any target inside a global ".claude"/".codex"/".gemini" + * skills tree. + */ +export function resolveDefaultInstallTargetRoot(): string { + const here = fileURLToPath(import.meta.url); + // /(src|dist)/skill-installation.* -> /.fusion-ce-skills + return resolve(dirname(here), "..", ".fusion-ce-skills"); +} + +const GLOBAL_SKILL_DIR_PATTERN = /[\\/]\.(claude|codex|gemini)[\\/]skills([\\/]|$)/; + +/** + * Guard: refuse to install into a global client skills directory. + * This is the AE2 isolation invariant — the global compound-engineering install + * (if present) must be provably untouched. + */ +export function assertPluginLocalTarget(targetRoot: string): void { + const normalized = resolve(targetRoot); + if (GLOBAL_SKILL_DIR_PATTERN.test(normalized + sep)) { + throw new Error( + `Refusing to install Compound Engineering skills into a global client skills directory: ${normalized}. ` + + `Install target MUST be plugin-local (never /.claude|.codex|.gemini/skills).`, + ); + } +} + +/** + * Validate that a bundled SKILL.md exists and has a non-empty frontmatter + * `name:`. A malformed/missing file surfaces a clear error instead of being + * silently skipped. + */ +function assertValidSkillSource(skillId: string, sourceDir: string): void { + if (!existsSync(sourceDir)) { + throw new Error(`Bundled skill source directory missing for '${skillId}': ${sourceDir}`); + } + const skillMd = join(sourceDir, "SKILL.md"); + if (!existsSync(skillMd)) { + throw new Error(`Bundled skill '${skillId}' has no SKILL.md at ${skillMd}`); + } + const content = readFileSync(skillMd, "utf-8"); + if (!/^---[\s\S]*?\bname\s*:\s*\S/m.test(content)) { + throw new Error( + `Bundled skill '${skillId}' SKILL.md at ${skillMd} is missing a frontmatter 'name:' field`, + ); + } +} + +export interface InstallBundledCeSkillsOptions { + /** Override the install target root (must be plugin-local). */ + targetRoot?: string; + /** Override the bundled source root (tests). */ + sourceRoot?: string; +} + +/** + * Copy each bundled CE skill directory into the plugin-local install target. + * Idempotent: existing per-skill target dirs are preserved (skip-if-exists). + */ +export function installBundledCeSkills( + options: InstallBundledCeSkillsOptions = {}, +): InstallBundledCeSkillsResult { + const targetRoot = options.targetRoot + ? resolve(options.targetRoot) + : resolveDefaultInstallTargetRoot(); + assertPluginLocalTarget(targetRoot); + + const sourceRoot = options.sourceRoot ? resolve(options.sourceRoot) : resolveBundledSkillsRoot(); + + const results = COMPOUND_ENGINEERING_SKILLS.map((skill) => { + const sourceDir = join(sourceRoot, skill.skillId); + const targetDir = join(targetRoot, skill.skillId); + try { + assertValidSkillSource(skill.skillId, sourceDir); + + if (existsSync(targetDir)) { + return { skillId: skill.skillId, sourceDir, targetDir, outcome: "skipped", reason: "existing install preserved" }; + } + + mkdirSync(targetRoot, { recursive: true }); + cpSync(sourceDir, targetDir, { recursive: true }); + return { skillId: skill.skillId, sourceDir, targetDir, outcome: "installed" }; + } catch (error) { + return { + skillId: skill.skillId, + sourceDir, + targetDir, + outcome: "error", + reason: error instanceof Error ? error.message : String(error), + }; + } + }); + + return { targetRoot, results }; +} + +/** True if the given path is absolute and not inside a global client skills dir. */ +export function isPluginLocalPath(p: string): boolean { + return isAbsolute(p) && !GLOBAL_SKILL_DIR_PATTERN.test(resolve(p) + sep); +} diff --git a/plugins/fusion-plugin-compound-engineering/src/skills.ts b/plugins/fusion-plugin-compound-engineering/src/skills.ts new file mode 100644 index 0000000000..6b15245f98 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/skills.ts @@ -0,0 +1,79 @@ +import type { PluginSkillContribution } from "@fusion/plugin-sdk"; + +/** + * Compound Engineering pipeline-stage skills, bundled (pinned) inside the plugin. + * + * Each entry's `skillFiles` is plugin-root-relative and points at a `SKILL.md` + * physically shipped under `src/skills//`. The bundled copy is a pinned + * snapshot (KTD5) — never a symlink to the global compound-engineering cache — + * so registering it can never clobber a user's global install (R12). + * + * The frontmatter `name` in each bundled SKILL.md equals the directory name + * (e.g. `ce-brainstorm`), so `skillId === name` here. pi-coding-agent's + * `loadSkills` derives `Skill.name` from that frontmatter, which is what the + * engine skill-resolver matches against. + */ +export const COMPOUND_ENGINEERING_SKILLS: PluginSkillContribution[] = [ + { + skillId: "ce-strategy", + name: "ce-strategy", + description: + "Create or maintain STRATEGY.md — the product's target problem, approach, users, key metrics, and tracks of work.", + skillFiles: ["skills/ce-strategy/SKILL.md"], + enabled: true, + triggerPatterns: ["strategy", "roadmap", "what are we working on", "set up the strategy doc"], + }, + { + skillId: "ce-ideate", + name: "ce-ideate", + description: + "Generate and critically evaluate grounded ideas about a topic before committing to one direction.", + skillFiles: ["skills/ce-ideate/SKILL.md"], + enabled: true, + triggerPatterns: ["ideate", "give me ideas", "what should I improve", "surprise me"], + }, + { + skillId: "ce-brainstorm", + name: "ce-brainstorm", + description: + "Explore requirements and approaches through collaborative dialogue, then write a right-sized requirements document.", + skillFiles: ["skills/ce-brainstorm/SKILL.md"], + enabled: true, + triggerPatterns: ["brainstorm", "what should we build", "help me think through"], + }, + { + skillId: "ce-plan", + name: "ce-plan", + description: + "Create structured plans for multi-step tasks and optionally deepen existing plans via sub-agent review.", + skillFiles: ["skills/ce-plan/SKILL.md"], + enabled: true, + triggerPatterns: ["plan this", "create a plan", "break this down", "deepen the plan"], + }, + { + skillId: "ce-work", + name: "ce-work", + description: "Execute work efficiently while maintaining quality and finishing features.", + skillFiles: ["skills/ce-work/SKILL.md"], + enabled: true, + triggerPatterns: ["do the work", "implement", "execute the plan", "finish this feature"], + }, + { + skillId: "ce-code-review", + name: "ce-code-review", + description: + "Structured code review using tiered persona agents, confidence-gated findings, and a merge/dedup pipeline.", + skillFiles: ["skills/ce-code-review/SKILL.md"], + enabled: true, + triggerPatterns: ["code review", "review this change", "review before PR"], + }, + { + skillId: "ce-compound", + name: "ce-compound", + description: + "Document a recently solved problem to compound the team's knowledge or the project's shared CONCEPTS.md vocabulary.", + skillFiles: ["skills/ce-compound/SKILL.md"], + enabled: true, + triggerPatterns: ["compound this", "document this learning", "capture this solution"], + }, +]; diff --git a/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/SKILL.md b/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/SKILL.md new file mode 100644 index 0000000000..0060392204 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/SKILL.md @@ -0,0 +1,283 @@ +--- +name: ce-brainstorm +description: 'Explore requirements and approaches through collaborative dialogue, then write a right-sized requirements document. Use when the user says "let''s brainstorm", "what should we build", or "help me think through X", presents a vague or ambitious feature request, or seems unsure about scope or direction -- even without explicitly asking to brainstorm.' +argument-hint: "[feature idea or problem to explore] [output:html]" +--- + +# Brainstorm a Feature or Improvement + +**Note: The current year is 2026.** Use this when dating requirements documents. + +Brainstorming helps answer **WHAT** to build through collaborative dialogue. It precedes `/ce-plan`, which answers **HOW** to build it. + +The durable output of this workflow is a **requirements document**. In other workflows this might be called a lightweight PRD or feature brief. In compound engineering, keep the workflow name `brainstorm`, but make the written artifact strong enough that planning does not need to invent product behavior, scope boundaries, or success criteria. + +This skill does not implement code. It explores, clarifies, and documents decisions for later planning or execution. + +**IMPORTANT: All file references in generated documents must use repo-relative paths (e.g., `src/models/user.rb`), never absolute paths. Absolute paths break portability across machines, worktrees, and teammates.** + +## Core Principles + +1. **Assess scope first** - Match the amount of ceremony to the size and ambiguity of the work. +2. **Be a thinking partner** - Suggest alternatives, challenge assumptions, and explore what-ifs instead of only extracting requirements. +3. **Resolve product decisions here** - User-facing behavior, scope boundaries, and success criteria belong in this workflow. Detailed implementation belongs in planning. +4. **Keep implementation out of the requirements doc by default** - Do not include libraries, schemas, endpoints, file layouts, or code-level design unless the brainstorm itself is inherently about a technical or architectural change. +5. **Right-size the artifact** - Simple work gets a compact requirements document or brief alignment. Larger work gets a fuller document. Do not add ceremony that does not help planning. +6. **Apply YAGNI to carrying cost, not coding effort** - Prefer the simplest approach that delivers meaningful value. Avoid speculative complexity and hypothetical future-proofing, but low-cost polish or delight is worth including when its ongoing cost is small and easy to maintain. + +## Interaction Rules + +These rules apply to every brainstorm, including the universal (non-software) flow routed to `references/universal-brainstorming.md`. + +1. **Ask one question at a time** - One question per turn, even when sub-questions feel related. Stacking several questions in a single message produces diluted answers; pick the single most useful one and ask it. +2. **Prefer single-select multiple choice** - Use single-select when choosing one direction, one priority, or one next step. +3. **Use multi-select rarely and intentionally** - Use it only for compatible sets such as goals, constraints, non-goals, or success criteria that can all coexist. If prioritization matters, follow up by asking which selected item is primary. +4. **Default to the platform's blocking question tool** - Use `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_user` in Gemini, `ask_user` in Pi (requires the `pi-ask-user` extension). These tools include a free-text fallback (e.g., "Other" in Claude Code), so options scaffold the answer without confining it — well-chosen options surface dimensions the user may not have separated, and pick-plus-optional-note is lower activation energy than composing prose from scratch. This default holds for opening and elicitation questions too, not only narrowing. Fall back to numbered options in chat only when no blocking tool exists in the harness or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. +5. **Use an open-ended question only when the question is genuinely open** - Drop the blocking tool only when (a) the answer is inherently narrative ("walk me through how you got here"), (b) the question is diagnostic or introspective and presented options would unintentionally influence the user's answer (e.g., "what concerns you most?" — a 4-option menu would nudge them toward those axes rather than the ones actually on their mind), or (c) you cannot write 3-4 genuinely distinct, plausibly-correct options that cover the space without padding or strawmen. The test: if you'd be straining to fill the option slots, the question is open — ask it open-ended. Rule 1 still applies: still one question per turn. +6. **Open-ended questions earn their place only when they're specific enough to elicit a substantive answer** - Apply Rule 5 silently: just ask the question, do not narrate the form choice. The question itself must give the user something concrete to anchor on. Good: *"What's the most concrete thing someone's already done about this — paid for it, built a workaround, quit a tool over it?"* (this is one of Phase 1.2's rigor probes — it earns its open-endedness by naming what counts as an answer). Too thin: *"What's your take?"* (nothing to bite into; user defaults to a one-liner that wastes the open question). Avoid (a) narrating the form choice ("the most useful question I can ask here is..."), (b) framings that imply a short answer ("briefly", "in one sentence"), (c) yes/no traps, and (d) AI-slop warmth wrappers ("take it wherever feels relevant"). + +## Output Guidance + +- **Keep outputs concise** - Prefer short sections, brief bullets, and only enough detail to support the next decision. +- **Use repo-relative paths** - When referencing files, use paths relative to the repo root (e.g., `src/models/user.rb`), never absolute paths. Absolute paths make documents non-portable across machines and teammates. + +## Feature Description + + #$ARGUMENTS + +**If the feature description above is empty, ask the user:** "What would you like to explore? Please describe the feature, problem, or improvement you're thinking about." + +Do not proceed until you have a feature description from the user. + +## Execution Flow + +### Phase 0: Resume, Assess, and Route + +#### 0.0 Resolve Output Mode + +Determine `OUTPUT_FORMAT` before any other phase fires. Output mode is **exclusive** — the requirements doc is written as either markdown (`.md`) OR HTML (`.html`), never both. Precedence: CLI arg > config > default (`md`), with a hard pipeline-mode override. + +**Read config (pre-resolved at skill load):** +!`cat "$(git rev-parse --show-toplevel 2>/dev/null)/.compound-engineering/config.local.yaml" 2>/dev/null || echo '__NO_CONFIG__'` + +Resolution steps: + +1. **CLI arg.** Scan `$ARGUMENTS` for a token starting with the literal prefix `output:`. If found, strip it from arguments before treating the remainder as the feature description, and match its value case-insensitively against `md` and `html`. + - `output:` alone (no value) → no-op, fall through to step 2. + - `output:` (e.g., `output:pdf`) → drop the token, fall through to step 2, and remember to emit a one-line note above the post-generation menu after final resolution: `Ignored unknown output: value '' — using instead.` where `` is the value `OUTPUT_FORMAT` actually resolved to after steps 2-4. Do not hardcode `md` in the note — that misleads users when config has set HTML. +2. **Config.** If step 1 did not resolve and the pre-resolved YAML above has an **active (non-commented)** `brainstorm_output:` key whose value matches `md` or `html` (case-insensitive), use it. Missing, invalid, or commented values fall through silently. Critical: lines starting with `#` are YAML comments and must be ignored — the shipped config template includes commented examples like `# brainstorm_output: html` to document the option, and matching those as active settings would silently force HTML mode on every run without the user having opted in. +3. **Default.** Otherwise `OUTPUT_FORMAT=md`. +4. **Pipeline override.** When invoked from LFG or any `disable-model-invocation` context, force `OUTPUT_FORMAT=md` regardless of steps 1-3. Downstream consumers (`ce-plan`, `ce-work`) parse markdown reliably; HTML in pipeline runs is unnecessary friction. + +**Token-parsing convention:** only literal-prefix flag tokens (`output:`, `mode:`, `delegate:` where applicable) are consumed and stripped. Other `:` tokens — including conventional commit prefixes like `feat:`, `fix:`, `chore:` that may appear inside a feature description — pass through verbatim. + +**Load the format-rendering reference based on the resolved value.** Section content is the same in either format; presentation differs. Both rendering references are paired with `references/brainstorm-sections.md`, which describes what the brainstorm contains regardless of format. + +- When `OUTPUT_FORMAT=md`, read `references/markdown-rendering.md` for format principles. +- When `OUTPUT_FORMAT=html`, read `references/html-rendering.md` for format principles. + +The `output:` preference does NOT auto-propagate to `ce-plan` on handoff — ce-plan re-resolves its own `plan_output` config independently. Asymmetric output (`requirements.html` + `plan.md`) is acceptable; users who want HTML for both set both keys in `.compound-engineering/config.local.yaml`. + +#### 0.1 Resume Existing Work When Appropriate + +If the user references an existing brainstorm topic or document, or there is an obvious recent matching `*-requirements.{md,html}` file in `docs/brainstorms/`: +- Read the document +- Confirm with the user before resuming: "Found an existing requirements doc for [topic]. Should I continue from this, or start fresh?" +- If resuming, summarize the current state briefly, continue from its existing decisions and outstanding questions, and update the existing document instead of creating a duplicate +- **Resume preserves the existing artifact's format, except pipeline mode.** Write back in whatever format the existing artifact uses — markdown if the existing file is `.md`, HTML if it is `.html`. Explicit `output:` arguments on this run override (e.g., resuming an `.html` doc with `output:md` switches the artifact to markdown). Pipeline mode (LFG, any `disable-model-invocation` context) always wins per Phase 0.0: even when resuming an existing `.html` brainstorm, pipeline runs force `OUTPUT_FORMAT=md` so downstream automation receives the markdown shape it expects. The resume rewrites the markdown file at the parallel path and the original `.html` is left in place untouched. + +#### 0.1b Classify Task Domain + +Before proceeding to Phase 0.2, classify whether this is a software task. The key question is: **does the task involve building, modifying, or architecting software?** -- not whether the task *mentions* software topics. + +**Software** (continue to Phase 0.2) -- the task references code, repositories, APIs, databases, or asks to build/modify/debug/deploy software. + +**Non-software brainstorming** (route to universal brainstorming) -- BOTH conditions must be true: +- None of the software signals above are present +- The task describes something the user wants to explore, decide, or think through in a non-software domain + +**Neither** (respond directly, skip all brainstorming phases) -- the input is a quick-help request, error message, factual question, or single-step task that doesn't need a brainstorm. + +**If non-software brainstorming is detected:** Read `references/universal-brainstorming.md` and use those facilitation principles. Skip Phases 0.2–4 below — the **Core Principles and Interaction Rules above still apply unchanged**, including one-question-per-turn and the default to the platform's blocking question tool. + +#### 0.2 Assess Whether Brainstorming Is Needed + +**Clear requirements indicators:** +- Specific acceptance criteria provided +- Referenced existing patterns to follow +- Described exact expected behavior +- Constrained, well-defined scope + +**If requirements are already clear:** +Keep the interaction brief. Confirm understanding and present concise next-step options rather than forcing a long brainstorm. Only write a short requirements document when a durable handoff to planning or later review would be valuable. Skip Phase 1.1 and 1.2 entirely — go straight to Phase 1.3 or Phase 2.5 in announce-mode (synthesis emitted for visibility, no blocking confirmation), then to Phase 3. + +#### 0.3 Assess Scope + +Use the feature description plus a light repo scan to classify the work: +- **Lightweight** - small, well-bounded, low ambiguity +- **Standard** - normal feature or bounded refactor with some decisions to make +- **Deep** - cross-cutting, strategic, or highly ambiguous + +If the scope is unclear, ask one targeted question to disambiguate and then proceed. + +**Deep sub-mode: feature vs product.** For Deep scope, also classify whether the brainstorm must establish product shape or inherit it: + +- **Deep — feature** (default): existing product shape anchors decisions. Primary actors, core outcome, positioning, and primary flows are already established in the product or repo. The brainstorm extends or refines within that shape. +- **Deep — product**: the brainstorm must establish product shape rather than inherit it. Primary actors, core outcome, positioning against adjacent products, or primary end-to-end flows are materially unresolved. Existing code lowers the odds of product-tier but does not by itself rule it out — a half-built tool with ambiguous shape is still product-tier. + +Product-tier triggers additional Phase 1.2 questions and additional sections in the requirements document. Feature-tier uses the current Deep behavior unchanged. + +### Phase 1: Understand the Idea + +#### 1.1 Existing Context Scan + +Scan the repo before substantive brainstorming. Match depth to scope: + +**Lightweight** — Search for the topic, check if something similar already exists, and move on. + +**Standard and Deep** — Two passes: + +*Constraint Check* — Check project instruction files (`AGENTS.md`, and `CLAUDE.md` only if retained as compatibility context) for workflow, product, or scope constraints that affect the brainstorm. Also read `STRATEGY.md` if it exists — the product's target problem, approach, persona, and active tracks are direct input to what this brainstorm should deliver and should shape scope, success criteria, and which approaches are aligned vs out-of-scope. Also read `CONCEPTS.md` at repo root if it exists — the project's authoritative vocabulary. Use these names in dialogue, approaches, and the requirements doc; map user-offered synonyms back. If any of these add nothing, move on. + +*Topic Scan* — Search for relevant terms. Read the most relevant existing artifact if one exists (brainstorm, plan, spec, skill, feature doc). Skim adjacent examples covering similar behavior. + +If nothing obvious appears after a short scan, say so and continue. Two rules govern technical depth during the scan: + +1. **Verify before claiming** — When the brainstorm touches checkable infrastructure (database tables, routes, config files, dependencies, model definitions), read the relevant source files to confirm what actually exists. Any claim that something is absent — a missing table, an endpoint that doesn't exist, a dependency not in the Gemfile, a config option with no current support — must be verified against the codebase first; if not verified, label it as an unverified assumption. This applies to every brainstorm regardless of topic. + +2. **Defer design decisions to planning** — Implementation details like schemas, migration strategies, endpoint structure, or deployment topology belong in planning, not here — unless the brainstorm is itself about a technical or architectural decision, in which case those details are the subject of the brainstorm and should be explored. + +**Slack context** (opt-in, Standard and Deep only) — never auto-dispatch. Route by condition: + +- **Tools available + user asked**: Dispatch `ce-slack-researcher` with a brief summary of the brainstorm topic alongside Phase 1.1 work. Incorporate findings into constraint and context awareness. +- **Tools available + user didn't ask**: Note in output: "Slack tools detected. Ask me to search Slack for organizational context at any point, or include it in your next prompt." +- **No tools + user asked**: Note in output: "Slack context was requested but no Slack tools are available. Install and authenticate the Slack plugin to enable organizational context search." + +#### 1.2 Product Pressure Test + +Before generating approaches, scan the user's opening for rigor gaps. Match depth to scope. + +This is agent-internal analysis, not a user-facing checklist. Read the opening, note which gaps actually exist, and raise only those as questions during Phase 1.3 — folded into the normal flow of dialogue, not fired as a pre-flight gauntlet. A fuzzy opening may earn three or four probes; a concrete, well-framed one may earn zero because no scope-appropriate gaps were found. + +**Lightweight:** +- Is this solving the real user problem? +- Are we duplicating something that already covers this? +- Is there a clearly better framing with near-zero extra cost? + +**Standard — scan for these gaps:** + +- **Evidence gap.** The opening asserts want or need, but doesn't point to anything the would-be user has already done — time spent, money paid, workarounds built — that would make the want observable. When present, ask for the most concrete thing someone has already done about this. + +- **Specificity gap.** The opening describes the beneficiary at a level of abstraction where the agent couldn't design without silently inventing who they are and what changes for them. When present, ask the user to name a specific person or narrow segment, and what changes for that person when this ships. + +- **Counterfactual gap.** The opening doesn't make visible what users do today when this problem arises, nor what changes if nothing ships. When present, ask what the current workaround is, even if it's messy — and what it costs them. + +- **Attachment gap.** The opening treats a particular solution shape as the thing being built, rather than the value that shape is supposed to deliver, and hasn't been examined against smaller forms that might deliver the same value. When present, ask what the smallest version that still delivers real value would look like. + +Plus these synthesis questions — not gap lenses, product-judgment the agent weighs in its own reasoning: +- Is there a nearby framing that creates more user value without more carrying cost? If so, what complexity does it add? +- Given the current project state, user goal, and constraints, what is the single highest-leverage move right now: the request as framed, a reframing, one adjacent addition, a simplification, or doing nothing? + +Favor moves that compound value, reduce future carrying cost, or make the product meaningfully more useful or compelling. Use the result to sharpen the conversation, not to bulldoze the user's intent. + +**Deep** — Standard lenses and synthesis questions plus: +- Is this a local patch, or does it move the broader system toward where it wants to be? + +**Deep — product** — Deep plus: + +- **Durability gap.** The opening's value proposition rests on a current state of the world that may shift in predictable ways within the horizon the user cares about. When present, ask how the idea fares under the most plausible near-term shifts — and push past rising-tide answers every competitor could make. + +- What adjacent product could we accidentally build instead, and why is that the wrong one? +- What would have to be true in the world for this to fail? + +These questions force an explicit product thesis and feed the Scope Boundaries subsections ("Deferred for later" and "Outside this product's identity") and Dependencies / Assumptions in the requirements document. + +#### 1.3 Collaborative Dialogue + +Follow the Interaction Rules above. Use the platform's blocking question tool when available. + +**Guidelines:** +- Ask what the user is already thinking before offering your own ideas. This surfaces hidden context and prevents fixation on AI-generated framings. +- Start broad (problem, users, value) then narrow (constraints, exclusions, edge cases) +- **Rigor probes fire before Phase 2 and are open-ended, not menus.** Narrowing is legitimate, but Phase 1 cannot end with un-probed rigor gaps. Each scope-appropriate gap from Phase 1.2 fires as a **separate** direct open-ended probe — one probe satisfies one gap, not multiple. Standard brainstorms scan four gap lenses (evidence, specificity, counterfactual, attachment); Deep-product adds durability (five total), but only the gaps actually present in the opening must be probed. Surface those probes progressively across the conversation — interleaving with narrowing moves is fine, as long as every scope-appropriate gap that was found in Phase 1.2 has been probed open-ended before Phase 2. Rigor probes map to Interaction Rule 5(b): a 4-option menu signals which kinds of evidence count and lets the user pick rather than produce. Open-ended questions force them to produce real observation or surface their uncertainty. Examples (one per gap): *evidence — "What's the most concrete thing someone's already done about this — paid, built a workaround, quit a tool over it?"* / *specificity — "Can you name a team you've actually watched hit this, or are you reasoning?"* / *counterfactual — "What do teams do today when this breaks — who reconciles?"* / *attachment — "Before we move to shapes or approaches — what's the smallest version that would still prove the bet right, and what's excluded?"* — **attachment is the final rigor probe before Phase 2 when the attachment gap is present. Fire it regardless of whether a specific shape has emerged through narrowing; its job is to pressure-test the user's implicit framing of the product before Phase 2 inherits it** / *durability — "Under the most plausible near-term shifts, how does this bet hold?"* If the answer reveals genuine uncertainty, record it as an explicit assumption in the requirements document rather than skipping the probe. +- Clarify the problem frame, validate assumptions, and ask about success criteria +- Make requirements concrete enough that planning will not need to invent behavior +- Surface dependencies or prerequisites only when they materially affect scope +- Resolve product decisions here; leave technical implementation choices for planning +- Bring ideas, alternatives, and challenges instead of only interviewing + +**Before exiting Phase 1.3: integration check.** Mentally combine what the user has said so far and surface any non-obvious consequences the dialogue hasn't probed. If user-stated X plus user-stated Y plus your-default-Z produces a downstream effect the user is unlikely to have tracked through one-question-at-a-time dialogue ("if mute lives on the rule AND we don't warn on delete, then rule-delete silently loses pause state"), probe it now while you're still in dialogue. One probe per genuine combination effect, asked open-ended, same discipline as rigor probes. Phase 2.5's call-outs are a safety net for residuals (silent agent inferences, pre-loaded contexts with no dialogue) — NOT a punt list for consequences you could have asked about now. + +**Exit condition:** Continue until the idea is clear AND no integration-check questions are pending, OR the user explicitly wants to proceed. + +### Phase 2: Explore Approaches + +If multiple plausible directions remain, propose **2-3 concrete approaches** based on research and conversation. Otherwise state the recommended direction directly. + +Use at least one non-obvious angle — inversion (what if we did the opposite?), constraint removal (what if X weren't a limitation?), or analogy from how another domain solves this. The first approaches that come to mind are usually variations on the same axis. + +Present approaches first, then evaluate. Let the user see all options before hearing which one is recommended — leading with a recommendation before the user has seen alternatives anchors the conversation prematurely. + +When useful, include one deliberately higher-upside alternative: +- Identify what adjacent addition or reframing would most increase usefulness, compounding value, or durability without disproportionate carrying cost. Present it as a challenger option alongside the baseline, not as the default. Omit it when the work is already obviously over-scoped or the baseline request is clearly the right move. + +At product tier, alternatives should differ on *what* is built (product shape, actor set, positioning), not *how* it is built. Implementation-variant alternatives belong at feature tier. + +For each approach, provide: +- Brief description (2-3 sentences) +- Pros and cons +- Key risks or unknowns +- When it's best suited + +**Approach granularity: mechanism / product shape, not architecture.** Approach descriptions name mechanism-level distinctions ("pause as a rule property" vs "pause as an event filter" vs "pause as a separate entity") and product-relevant trade-offs (plan-tier coupling, complexity surface, migration difficulty). They do NOT name implementation specifics — column names, table names, file paths, service classes, JSON shapes, exact method names. Those are ce-plan's job. Bringing architecture forward at brainstorm time forces the user to make architectural decisions on ce-brainstorm's intentionally-shallow research, and the synthesis at Phase 2.5 then has to filter out the leak. + +After presenting all approaches, state your recommendation and explain why. Prefer simpler solutions when added complexity creates real carrying cost, but do not reject low-cost, high-value polish just because it is not strictly necessary. + +If one approach is clearly best and alternatives are not meaningful, skip the menu and state the recommendation directly. + +If relevant, call out whether the choice is: +- Reuse an existing pattern +- Extend an existing capability +- Build something net new + +### Phase 2.5: Synthesis Summary + +**STOP. Before composing the synthesis, read `references/synthesis-summary.md`.** The two-stage shape (internal three-bucket draft → chat-time scoping synthesis), the Path A / Path B gate, the four scoping synthesis sections with their keep tests, the tier-aware bullet budget with re-cut rule, anti-pattern guidance, soft-cut behavior, self-redirect support, and internal-draft routing into doc body sections all live there. Composing a synthesis without these rules loaded reliably produces malformed output — pasting the full internal three-bucket draft verbatim into chat, implementation-detail leakage into the scoping synthesis, the proposal-pitch anti-pattern. **Each scoping synthesis bullet must pass the affirmability test (can the user evaluate this without reading code?) AND the detail test (1–2 lines max, conversational not documentary); over-share and over-detail are the failure modes to avoid.** This is not optional supplementary reading; it is the source of truth for how the phase behaves. + +Surface a scoping synthesis to the user before Phase 3 writes the requirements doc — the user's last opportunity to correct scope before the artifact lands. The scoping synthesis is shaped like what two product collaborators would confirm before writing a PRD, not like a comprehensive audit or a one-line preview. + +Fires for **all tiers** including Lightweight. Skip Phase 2.5 entirely on the Phase 0.1b non-software (universal-brainstorming) route. + +**Path A vs Path B:** the scoping synthesis shape depends on TWO signals — whether any blocking question fired AND what tier Phase 0.3 classified the scope as. + +- **Path A — no blocking questions fired AND tier is Lightweight**: announce-mode. Emit "What we're building" prose only (1–3 sentences), then proceed to Phase 3 doc-write in the same turn. No other sections, no confirmation question. Do NOT end the turn waiting for acknowledgment. The user can revise after the doc lands if the shape is wrong — Lightweight Path A docs are short, post-hoc revision is cheap. +- **Path B — at least one blocking question fired, OR tier is Standard / Deep-feature / Deep-product**: full tier-aware scoping synthesis with confirmation gate. Two scenarios fire Path B: (a) the user invested answer-time during dialogue, or (b) the user pre-loaded substantive scope content (Phase 0.2 fast-path with a richly-specified opening prompt). Either way, the substance earns a real checkpoint. Confirmation is unconditional even when zero call-outs survive the keep test. + +**Why the tier guard on Path A**: Phase 0.2's fast path serves two very different cases — a tight one-liner that needs no dialogue ("fix the typo on line 47") and a richly pre-loaded brainstorm context that ALSO needs no dialogue because the user pre-stated everything. Without the tier guard, both route to Path A and the pre-loaded case gets a 1-sentence checkpoint for what may be 20+ items worth of scope. Tier-classifying Phase 0.3 distinguishes the two — pre-loaded substance makes the tier Standard or Deep, which then routes to Path B. + +### Phase 3: Capture the Requirements + +Write or update a requirements document only when the conversation produced durable decisions worth preserving — see `references/brainstorm-sections.md` "Decide whether a doc is warranted at all" for the criteria and the bug-fix stress test. Skip document creation when the user only needs brief alignment and the decisions can flow downstream (ce-plan, commit message, docs/solutions/) without a brainstorm artifact in the middle. + +When a doc is warranted, compose it using: + +- `references/brainstorm-sections.md` — section contract (outcomes, hard floor, include-when-material catalog, agency rules, ID conventions). +- The format-specific rendering reference loaded at Phase 0.0 (`markdown-rendering.md` OR `html-rendering.md`) — how the resolved format presents the sections. + +Write to `docs/brainstorms/YYYY-MM-DD--requirements.` — extension follows `OUTPUT_FORMAT`. Confirm with the absolute path so the reference is clickable. + +#### Vocabulary Capture — after the requirements doc (only if CONCEPTS.md already exists) + +**Skip this step entirely if `CONCEPTS.md` does not exist at repo root** — creation is owned by ce-compound and ce-compound-refresh. + +Run this **after** the approaches, the scope synthesis, and the requirements doc — that is where the canonical term often gets chosen or corrected, so capturing during early dialogue (before this point) would miss the final resolved name. If it exists, scan the full dialogue and the requirements doc for **resolved** domain terms — terms where the conversation actively pinned down a precise local meaning, not terms merely mentioned in passing. **Resolved means the definition is settled, not still under discussion.** Provisional terms that may still revise stay in the conversation only. + +For each resolved term: if missing, add it; if present but new precision surfaced, refine it; if already consistent, no action. + +**Domain entities, named processes, and status concepts with project-specific meaning only.** Not file paths, class names, function signatures, or implementation decisions — `CONCEPTS.md` is a glossary, not a spec or catch-all. + +Follow the format set by existing entries. Apply edits silently. (If Phase 3 skipped the doc, still run this against the resolved dialogue.) + +### Phase 4: Handoff + +Present next-step options and execute the user's selection. Read `references/handoff.md` for the option logic, dispatch instructions, and closing summary format. diff --git a/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/brainstorm-sections.md b/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/brainstorm-sections.md new file mode 100644 index 0000000000..b18007b1ad --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/brainstorm-sections.md @@ -0,0 +1,263 @@ +# Brainstorm Sections + +This reference describes what makes a great brainstorm requirements document. +It does NOT prescribe how the doc looks on the page — rendering is handled by +the format-specific references (`markdown-rendering.md`, `html-rendering.md`). + +## The outcome + +A great brainstorm produces a doc that enables three audiences to act: + +- **The planning agent** (`ce-plan` or a human) produces an implementation + plan without inventing user behavior, scope boundaries, or success + criteria — the brainstorm answered those. +- **The reviewer** sees the framing choices, distinguishes pinned from open, + and catches scope gaps before planning. +- **The future reader** traces why the proposed thing matters, who it's for, + and what success looks like. + +Sections earn their place by serving one of these audiences. Omit padding. + +## Decide whether a doc is warranted at all + +Brainstorm dialogue does not always need to produce a durable document. +Skip document creation when **both** hold: + +- The user only needs brief alignment — no exploration produced novel scope, + framing, or decisions worth preserving in IDed shape. +- Any durable decisions made during the dialogue can flow naturally to + downstream artifacts (`ce-plan`, the commit message, `docs/solutions/`) + without a brainstorm doc as an intermediary. + +The trigger for creating a doc is when the dialogue surfaced enough +structural decisions, scope boundaries, or acceptance criteria that +downstream consumers (planner, reviewer, future reader) need them in a +durable, IDed form — not just as conversational artifacts. + +**Stress test:** a brainstorm about a tiny bug fix where the user asks "fix +this with a null check or with upstream validation?" and the agent confirms +"upstream validation, here's why" doesn't need a brainstorm doc. The +decision flows to `ce-plan` (or directly to commit message, or to +`docs/solutions/` if it's a pattern worth carrying) without a brainstorm +artifact in the middle. + +Conversely, a brainstorm about a multi-actor feature with contested scope +and several behavioral conditions probably does need a doc — the planning +agent needs the structured content the dialogue produced. + +## Match depth to content + +When a doc IS warranted, depth matches what the dialogue produced. A +brainstorm with sparse content produces a sparse doc; one with rich content +produces a rich doc. Don't add ceremony to make a slim brainstorm look +substantial. + +## Hard floor + +When a doc is warranted, these are present. + +- **Summary** — what is being proposed, in 1-3 lines. Forward-looking. + Orients the reader before they invest in detail. +- **Requirements** (with stable R-IDs) — what must be true about the + proposed thing. For very sparse brainstorms (≤3 simple items where the + bullets ARE the summary), plain bullets without IDs are acceptable; the + trigger for R-IDs is whether downstream consumers will reference them. + When requirements span distinct concerns (e.g., "Packaging" / + "Migration and compatibility" / "Contributor workflow"), group them + under bold inline headers within the Requirements section — group by + capability or concern, not by the order requirements were discussed. + The trigger is distinct concerns, not item count — even four + requirements benefit if they cover three different topics. Skip + grouping only when all requirements are genuinely about the same thing; + a long flat list is a smell that subgroups were missed. R-IDs stay + continuous across groups (R1, R2 in the first group; R3, R4 in the + second; never restart at R1 per group). + +## Include when material + +The agent decides per brainstorm whether each section carries information +that isn't covered elsewhere. Filling a section with placeholder prose is +worse than omitting it. + +- **Problem Frame** — include when motivation isn't obvious from Summary + alone (the *why* needs paragraphs, not a sentence). Backward-looking / + situational. Does NOT restate the proposal; the remedy lives in Summary. + +- **Key Decisions** — include when the brainstorm produced opinionated + framing choices (defaults, scope narrowings, foundational technical picks) + that constrain Requirements / Flows / Scope below. Each entry names the + decision in bold with prose rationale. Sits high in the rendered doc so + readers encounter the framing choices before descending into detail. + +- **Actors** — include when the proposed thing has multi-party behavior + (multiple humans, agents, or systems meaningfully involved). Skip for + non-behavioral brainstorms (naming briefs, data-shape briefs, pure + research, decision frameworks). + +- **Key Flows** — include when the proposed thing has multi-step behavior. + Expected by default for behavioral brainstorms unless the proposed thing + is genuinely non-flow-shaped (pure API surface, policy, artifact output) + and Actors / Requirements / Scope Boundaries / Acceptance Examples + together prevent downstream invention of paths. When omitting from a + behavioral brainstorm, note the reason in the doc. + +- **Visualizations** — include a diagram when the brainstorm contains a + diagram-shaped concept that a picture carries faster than prose. Common + shapes: a data-shape transformation (before/after schema or field + mapping), a source-of-truth fan-out (one authority feeding many derived + surfaces), state-or-lifecycle logic, a multi-step flow, or a quantitative + comparison. A diagram is cross-cutting, not a section of its own — it sits + next to the Key Decision, Requirements group, or Flow it illustrates. The + named test: *does the picture let a reader grasp the concept faster than + the paragraph alone?* If yes, add it; if the prose already conveys it at a + glance, skip it. One diagram per load-bearing concept — don't add visuals + for ceremony. This affordance is the conceptual-diagram path; it is + distinct from the wireframe affordance (a wireframe is for visual-product + UI and does not apply to non-visual systems like data models or agent + workflows, but a conceptual diagram does). + + **Diagrams complement prose; they never replace it.** A diagram is an + on-ramp to the prose it illustrates, not a substitute. The IDed prose + (Requirements, Key Decisions, Acceptance Examples) stays complete and + standalone — a reader who ignores every diagram still gets the full + content in text, and a downstream agent that reads the artifact as linear + text is never left with a relationship that exists only in an SVG. Adding + a before/after diagram is not license to thin the requirement or decision + prose it depicts. + +- **Acceptance Examples** — include when any requirement has a + state-dependent or conditional shape ("When X, Y") where prose alone leaves + ambiguity about edge cases. **Always include AEs covering + behavioral-conditional requirements** — that's where the ambiguity bites + hardest. Skip when all requirements are unconditional and unambiguous. + +- **Success Criteria** — include when there are quality / metric / handoff + signals that Requirements don't already carry: quantitative metrics ("p95 + latency under 200ms"), qualitative criteria ("the agent's output reads as + one voice"), process / handoff quality ("ce-doc-review can act on this + without follow-ups"). Skip when Requirements ARE the success criteria + (every R is "done when the R is true"). + +- **Scope Boundaries** — include when scope is contested or there are + tempting non-goals worth naming explicitly. When the brainstorm is about + positioning a product against adjacent ones the team could have built but + is rejecting, split into "Deferred for later" (eventually but not v1) and + "Outside this product's identity" (positioning decision). Otherwise, a + single list is fine. + +- **Dependencies / Assumptions** — include when material upstream + dependencies exist or when load-bearing assumptions need to be surfaced. + +- **Outstanding Questions** — include when there are unresolved items. + Distinguish "Resolve Before Planning" (blocks planning) from "Deferred to + Planning" (answered during planning or codebase exploration). + +- **Sources / Research** — surface research that orients the planner or + justifies framing choices. The test: *"if I were the planner reading this + cold, would this breadcrumb help me make better choices?"* Yes → surface + (code locations, external docs, RFCs, constraints, prior plans — the + category is inclusive, not enumerated). Process exhaust (reading the + user's prompt, glancing at obvious files) → omit. + +## Agent agency + +The catalog is a floor, not a ceiling. When the brainstorm's content doesn't +fit any catalog section, introduce a new one — don't force the content into +a section it doesn't belong in. Content drives section choices, not vice +versa. + +The agent also picks per artifact: + +- Whether Acceptance Examples render as a separate section or embed in each + requirement +- How much depth each present section gets + +(Requirements grouping is covered above in the Hard Floor item — group by +concern by default, rendering a flat list only when all requirements are +about the same thing, with continuous R-IDs across groups.) + +## Brainstorm metadata fields + +Every brainstorm carries a small set of stable metadata fields that +downstream tooling depends on. The contract is format-independent: in +markdown these fields appear as YAML frontmatter at the top of the file; in +HTML they appear as visible header text (typically a `
` of `
`/`
` +pairs or a stats strip). Field names and semantics are the same across both +formats so consumers can locate them without knowing which format produced +the brainstorm. + +### Required + +- **`date`** — creation date in ISO 8601 (`YYYY-MM-DD`), ASCII digits only. + Used in the filename (`docs/brainstorms/YYYY-MM-DD--requirements.`). +- **`topic`** — kebab-case slug identifying the brainstorm subject (e.g., + `surface-scope-earlier`, `demo-reel-local-save`). Used in the filename + alongside `date` and as the resume-detection key when `ce-brainstorm`'s + Phase 0.1 scans `docs/brainstorms/` for an existing artifact to continue. + +### Status flip does not apply to brainstorm + +Unlike plans, brainstorm artifacts have no `status` field — there is no +`active → completed` lifecycle. A brainstorm is a one-time output that +downstream consumers (`ce-plan`, `ce-doc-review`) reference via the plan's +`origin:` field. The `` HTML hook described in +`html-rendering.md` is a plan-side mechanic and does not render on +brainstorm artifacts. + +### Field-name stability + +Field names are stable across brainstorm revisions — never rename a field +or repurpose its semantics. Agents composing new brainstorms MUST use these +exact names; adding new fields is fine, but renaming `topic` to `subject` +or `date` to `created` breaks filename construction and resume detection. + +## ID and content rules + +Same shape as plan rules. + +- **Stable IDs.** R-IDs (Requirements), A-IDs (if Actors fire), F-IDs (if + Flows fire), AE-IDs (if Acceptance Examples fire). No other ID namespaces. +- **Plain prefix.** `R1.`, `A1.`, `F1.`, `AE1.` as bullet prefixes. Do not + bold; the prefix is visually distinctive on its own. +- **Bold leader labels** inside Flows and Acceptance Examples + (`**Trigger:**`, `**Covers R4, R8.**`) provide structure without deeper + heading levels. +- **Repo-relative paths.** Always. Never absolute paths. +- **No process exhaust.** No "captured at Phase X" notes, no `## Next Steps` + pointing to ce-plan, no italic provenance lines. Engineering process + metadata belongs in commit messages and tool output, not the artifact. +- **No implementation details by default.** Libraries, schemas, endpoints, + file layouts, code structure stay out unless the brainstorm itself is + inherently about a technical or architectural change and those details are + the subject of the decision. + +## Discipline: Summary vs Problem Frame + +When both sections are present, they earn separate sections only by holding +to different purposes: + +| Section | Question it answers | Time direction | Length | +|---|---|---|---| +| `## Summary` | What is this doc proposing? | Forward-looking | 1-3 lines | +| `## Problem Frame` | Why does this proposal exist? | Backward-looking / situational | Paragraphs | + +- **Summary doesn't need problem context.** A reader scanning Summary gets + the proposal at a glance. +- **Problem Frame doesn't restate the proposal.** It establishes the + situation, the specific moment of pain, and the cost shape — then stops. + The remedy lives in Summary; restating it in Problem Frame is the + duplication that makes the two sections feel redundant. + +## Rendering + +The format-specific references describe how to render these sections in each +output format: + +- **Markdown rendering:** `references/markdown-rendering.md` +- **HTML rendering:** `references/html-rendering.md` + +This reference (`brainstorm-sections.md`) is about WHAT the brainstorm +contains; rendering references are about HOW each format presents it. The +brainstorm is written in one format — markdown OR HTML, never both — based +on the resolved output mode. The section catalog is the same regardless of +format. diff --git a/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/handoff.md b/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/handoff.md new file mode 100644 index 0000000000..3c8ff59f00 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/handoff.md @@ -0,0 +1,132 @@ +# Handoff + +This content is loaded when Phase 4 begins — after the requirements document is written. + +--- + +#### 4.1 Present Next-Step Options + +The Phase 4 menu's visible option count varies by state: no requirements doc hides the review and Proof options, `OUTPUT_FORMAT=html` also hides the review option (ce-doc-review is markdown-only today), unresolved `Resolve Before Planning` hides `Plan implementation` and `Build it now`, a failing direct-to-work gate hides `Build it now`. Count the visible options for the current state and choose the rendering mode accordingly: + +- **4 or fewer visible:** use the platform's blocking question tool (`AskUserQuestion` in Claude Code — call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded; `request_user_input` in Codex; `ask_user` in Gemini, `ask_user` in Pi (requires the `pi-ask-user` extension)). This is the default. +- **5 or more visible:** render as a numbered list in chat. This is the narrow option-overflow fallback; trimming would hide legitimate choices (plan, review, Proof, build, refine, pause are all distinct destinations). Include a hint that free-form input is accepted ("Pick a number or describe what you want.") so the numbered list retains the blocking tool's open-endedness. + +Never silently skip the question. + +If `Resolve Before Planning` contains any items: +- Ask the blocking questions now, one at a time, by default +- If the user explicitly wants to proceed anyway, first convert each remaining item into an explicit decision, assumption, or `Deferred to Planning` question +- If the user chooses to pause instead, present the handoff as paused or blocked rather than complete +- Do not offer the `Plan implementation` or `Build it now` options while `Resolve Before Planning` remains non-empty + +In both preambles below, the "Pick a number or describe what you want." hint applies only in numbered-list mode. When using the blocking tool, omit that line and pass the remaining stem as the question. + +**Path format:** Use absolute paths for chat-output file references — relative paths are not auto-linked as clickable in most terminals. + +**Preamble when no blocking questions remain:** + +``` +Brainstorm complete. + +Requirements doc: # omit line if no doc was created + +What would you like to do next? (Pick a number or describe what you want.) +``` + +**Preamble when blocking questions remain and user wants to pause:** + +``` +Brainstorm paused. Planning is blocked until the remaining questions are resolved. + +Requirements doc: # omit line if no doc was created + +What would you like to do next? (Pick a number or describe what you want.) +``` + +Present only the options that apply. Renumber so visible options stay contiguous starting at 1. + +1. **Plan implementation with `ce-plan` (Recommended)** - Move to `ce-plan` for structured implementation planning. Shown only when `Resolve Before Planning` is empty. +2. **Agent review of requirements doc with `ce-doc-review`** - Dispatch reviewer agents to check the doc for coherence, feasibility, scope, and other persona-specific issues; auto-apply safe fixes; route remaining findings interactively. Shown only when a requirements document exists **and `OUTPUT_FORMAT=md`** — ce-doc-review's walkthrough applies markdown-only mutations (`##`/`###` heading inserts, single-file markdown edits via apply-set) and would corrupt an HTML artifact, so HTML brainstorms skip this option until ce-doc-review gains HTML-aware mutation support. Under HTML mode, surface a one-line note above the menu: `Agent review unavailable in output:html mode — ce-doc-review is markdown-only today. Switch to output:md if you want a review pass.` +3. **Open in Proof — review and comment to iterate with the agent** - Open the doc in Every's Proof editor, iterate with the agent via comments, or copy a link to share with others. Shown only when a requirements document exists. **Render only when `OUTPUT_FORMAT=md`** (Proof operates on markdown and cannot ingest HTML). +3. **Open in browser** — open the HTML requirements file locally for review and sharing. Shown only when a requirements document exists. **Render only when `OUTPUT_FORMAT=html`.** Replaces "Open in Proof" at the same slot under exclusive output mode — the doc is either markdown OR HTML, never both, so exactly one of the two labels applies per run. +4. **Build it now with `ce-work` (skip planning)** - Skip planning and move to `ce-work`; suited to lightweight, well-defined changes. Shown only when `Resolve Before Planning` is empty **and** scope is lightweight, success criteria are clear, scope boundaries are clear, and no meaningful technical or research questions remain (the "direct-to-work gate"). +5. **More clarifying questions to sharpen the doc** - Keep refining scope, edge cases, constraints, and preferences through further dialogue. Always shown. +6. **Done for now** - Pause; the requirements doc is saved and can be resumed later. Always shown. + +**Post-review nudge (subsequent rounds only):** If the user has already run `ce-doc-review` this session and residual P0/P1 findings remain unaddressed, add a one-line prose nudge adjacent to the menu (e.g., "Document review flagged 2 P1 findings you may want to address — pick \"Agent review of requirements doc\" to run another pass."). Reference the option by label, not number: the menu renumbers when `Resolve Before Planning` hides `Plan implementation` and `Build it now`, so a hardcoded option number can point users at the wrong action. Do not add a separate menu option; reuse the existing agent-review option. Suppress this nudge when `OUTPUT_FORMAT=html` — the agent-review option is hidden in that mode, so the nudge would point users at a missing action. + +#### 4.2 Handle the Selected Option + +Selections may be the literal option label (when the user types the label or a close paraphrase) or the option number. Match numbers against the currently-rendered (post-trim) list. Free-form input that doesn't match an option or describe an alternative action should be treated as clarification — ask a follow-up rather than guessing. + +**If user selects "Plan implementation with `ce-plan` (Recommended)":** + +Immediately load the `ce-plan` skill in the current session. Pass the requirements document path when one exists; otherwise pass a concise summary of the finalized brainstorm decisions. Do not print the closing summary first. + +**If user selects "Agent review of requirements doc with `ce-doc-review`":** + +Load the `ce-doc-review` skill, passing the requirements document path as the argument. When ce-doc-review returns "Review complete", return to the Phase 4 options and re-render the menu (the doc may have changed, so re-evaluate `Resolve Before Planning`, direct-to-work gate, and residual findings). If residual P0/P1 findings remain unaddressed, include the post-review nudge above the menu. Do not show the closing summary yet. + +**If user selects "Build it now with `ce-work` (skip planning)":** + +Immediately load the `ce-work` skill in the current session using the finalized brainstorm output as context. If a compact requirements document exists, pass its path. Do not print the closing summary first. + +**If user selects "More clarifying questions to sharpen the doc":** Return to Phase 1.3 (Collaborative Dialogue) and continue asking the user clarifying questions one at a time to further refine scope, edge cases, constraints, and preferences. Continue until the user is satisfied, then return to Phase 4. Do not show the closing summary yet. + +**If user selects "Open in Proof — review and comment to iterate with the agent":** + +Load the `ce-proof` skill in HITL-review mode with: + +- **source file:** `docs/brainstorms/YYYY-MM-DD--requirements.md` +- **doc title:** `Requirements: ` +- **identity:** `ai:compound-engineering` / `Compound Engineering` +- **recommended next step:** `ce-plan` (shown in the ce-proof skill's final terminal output) + +Follow `references/hitl-review.md` in the ce-proof skill. It uploads the doc, prompts the user for review in Proof's web UI, ingests filtered comment threads, applies agreed edits through the current Proof edit APIs, replies/resolves in-thread, and syncs the final markdown back to the source file atomically on proceed. + +When the ce-proof skill returns control: + +- `status: proceeded` with `localSynced: true` → the requirements doc on disk now reflects the review. Return to the Phase 4 options and re-render the menu (the doc may have changed substantially during review, so option eligibility can shift — re-evaluate `Resolve Before Planning`, direct-to-work gate, and residual ce-doc-review findings against the updated doc). +- `status: proceeded` with `localSynced: false` → the reviewed version lives in Proof at `docUrl` but the local copy is stale. Offer to pull the Proof doc to `localPath` using the ce-proof skill's Pull workflow. Re-render the Phase 4 menu after the pull completes (or is declined). If the pull was declined, include a one-line note above the menu that `` is stale vs. Proof — otherwise `Plan implementation` / `Build it now` / `Agent review of requirements doc` will silently read the pre-review copy. +- `status: done_for_now` → the doc on disk may be stale if the user edited in Proof before leaving. Offer to pull the Proof doc to `localPath` so the local requirements file stays in sync, then return to the Phase 4 options. If the pull was declined, include the stale-local note above the menu. `done_for_now` means the user stopped the HITL loop without syncing — it does not mean they ended the whole brainstorm. +- `status: aborted` → fall back to the Phase 4 options without changes. + +If the initial upload fails (network error, Proof API down), retry once after a short wait. If it still fails, tell the user the upload didn't succeed and briefly explain why, then return to the Phase 4 options — don't leave them wondering why the option did nothing. + +**If user selects "Open in browser":** Display the absolute path to the `.html` requirements file so the user can open it locally. Where the platform exposes a browser-opening primitive (e.g., `open` on macOS, `xdg-open` on Linux, `start` on Windows), the agent may invoke it directly; otherwise print the absolute path and let the user open it. After the path is displayed (or the browser is opened), return to the Phase 4 options so the user can pick a follow-up action. + +**If user selects "Done for now":** Display the closing summary (see 4.3) and end the turn. + +#### 4.3 Closing Summary + +Use the closing summary only when this run of the workflow is ending or handing off, not when returning to the Phase 4 options. + +In both templates below, substitute `` with the actual file path written this run — `.md` for `OUTPUT_FORMAT=md`, `.html` for `OUTPUT_FORMAT=html`. Do not emit a hardcoded `.md` path when the artifact is HTML, or the closing summary will point users at a file that was never written. + +When complete and ready for planning, display: + +```text +Brainstorm complete! + +Requirements doc: # omit line if no doc was created + +Key decisions: +- [Decision 1] +- [Decision 2] + +Recommended next step: `ce-plan` +``` + +If the user pauses with `Resolve Before Planning` still populated, display: + +```text +Brainstorm paused. + +Requirements doc: # omit line if no doc was created + +Planning is blocked by: +- [Blocking question 1] +- [Blocking question 2] + +Resume with `ce-brainstorm` when ready to resolve these before planning. +``` diff --git a/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/html-rendering.md b/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/html-rendering.md new file mode 100644 index 0000000000..7c61b74c29 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/skills/ce-brainstorm/references/html-rendering.md @@ -0,0 +1,538 @@ +# HTML Rendering + +This is a format-rendering reference — it describes how to render any +artifact in HTML, independent of which skill is producing it. + +It is paired with a section contract (`plan-sections.md`, +`brainstorm-sections.md`, etc.) that describes *what* the artifact contains. +This reference describes *how* HTML specifically presents it. The same +content rendered by different skills shares the same HTML principles. + +The HTML artifact is the *only* artifact the skill produces for that run — +output mode is exclusive (markdown OR HTML, never both). Downstream +consumers that read HTML today (`ce-work`, human readers) do so directly; +the agent-consumability rules below make that work. `ce-doc-review` is +*not* currently an HTML consumer — its mutation mechanics are markdown-only, +so the ce-plan handoff gates the 5.3.8 doc-review pass to `OUTPUT_FORMAT=md` +runs and skips it for HTML. + +## Hard invariants + +These hold regardless of which skill produced the artifact. + +- **Single self-contained HTML5 file.** No companion `.css`, `.js`, or + `.svg` files. CSS lives in `