@aiviatic/kindling 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/bin/kindling.js +14 -0
- package/bootstrap/kindling.cmd +13 -0
- package/bootstrap/setup.ps1 +98 -0
- package/bootstrap/setup.sh +59 -0
- package/dist/chunk-IS6LC3HK.js +210 -0
- package/dist/chunk-IS6LC3HK.js.map +1 -0
- package/dist/chunk-MW7UAGER.js +890 -0
- package/dist/chunk-MW7UAGER.js.map +1 -0
- package/dist/chunk-OU3WSB6B.js +77 -0
- package/dist/chunk-OU3WSB6B.js.map +1 -0
- package/dist/cli/main.d.ts +21 -0
- package/dist/cli/main.js +258 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/emitter-oidLJDmn.d.ts +135 -0
- package/dist/engine/index.d.ts +546 -0
- package/dist/engine/index.js +234 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/exec-JnCZZPZU.d.ts +8 -0
- package/dist/server/index.d.ts +39 -0
- package/dist/server/index.js +10 -0
- package/dist/server/index.js.map +1 -0
- package/dist/ui/assets/index-Bw_xLj6a.css +1 -0
- package/dist/ui/assets/index-CoPlNDA-.js +40 -0
- package/dist/ui/index.html +13 -0
- package/dist/ui/platform-codes.yaml +54 -0
- package/package.json +77 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../cli/main.ts","../../cli/json-sink.ts","../../cli/verbose-sink.ts","../../cli/server-mode.ts","../../server/welcome.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport { EngineEmitter } from '../engine/emitter';\nimport type { EventListener } from '../engine/emitter';\nimport { jsonSink } from './json-sink';\nimport { verboseSink } from './verbose-sink';\nimport { runServerMode } from './server-mode';\n\nexport type Mode = 'server' | 'json' | 'verbose';\n\n/** Pick the run mode from argv. No flag → server (the default product frontend). */\nexport function selectMode(argv: string[]): Mode {\n const { values } = parseArgs({\n args: argv,\n options: { json: { type: 'boolean' }, verbose: { type: 'boolean' } },\n strict: false,\n });\n if (values.json && values.verbose) {\n process.stderr.write('Both --json and --verbose given; using --json.\\n');\n }\n if (values.json) return 'json';\n if (values.verbose) return 'verbose';\n return 'server';\n}\n\n/** Build the stdout sink for a mode. Server mode renders via the browser UI (Epic 3), not stdout. */\nexport function makeSink(mode: Mode, write: (line: string) => void): EventListener {\n if (mode === 'json') return jsonSink(write);\n if (mode === 'verbose') return verboseSink(write);\n return () => {};\n}\n\n/**\n * Wire an EngineEmitter to the chosen sink and return both. The real engine orchestration\n * (Stories 1.4–1.7) drives the returned emitter; this harness just renders its events.\n */\nexport interface RunDeps {\n /** Server-mode starter — injectable so tests don't bind a port / open a browser. */\n startServerMode?: (emitter: EngineEmitter) => void;\n}\n\nexport function run(\n argv: string[],\n write: (line: string) => void = (line) => process.stdout.write(line + '\\n'),\n deps: RunDeps = {},\n): { mode: Mode; emitter: EngineEmitter } {\n const mode = selectMode(argv);\n const emitter = new EngineEmitter();\n emitter.on(makeSink(mode, write));\n if (mode === 'server') {\n // Default: stand up the localhost server + browser UI (FR-12 lifecycle). Fire-and-forget —\n // the server keeps the process alive; a startup failure is surfaced, not thrown.\n const start =\n deps.startServerMode ??\n ((em: EngineEmitter) => {\n void runServerMode(em, { write }).catch((err) =>\n write(`Kindling failed to start the server: ${String(err)}`),\n );\n });\n start(emitter);\n }\n return { mode, emitter };\n}\n","import type { KindlingEvent } from '../engine/contract';\nimport type { EventListener } from '../engine/emitter';\n\n// JSON-lines sink: one parseable JSON object per event. Used by `--json` (CI/debug/technical).\nexport function jsonSink(write: (line: string) => void): EventListener {\n return (event: KindlingEvent) => write(JSON.stringify(event));\n}\n","import type { KindlingEvent } from '../engine/contract';\nimport type { EventListener } from '../engine/emitter';\n\n// Plain human-readable sink for `--verbose`. Interpolates event fields (no raw step literal).\nexport function verboseSink(write: (line: string) => void): EventListener {\n return (event: KindlingEvent) => write(`[${event.status}] ${event.step} — ${event.humanMessage}`);\n}\n","import { fileURLToPath } from 'node:url';\nimport type { Config, EngineCommands, StepId } from '../engine/contract';\nimport { pins } from '../engine/pins';\nimport type { EngineEmitter } from '../engine/emitter';\nimport { Engine } from '../engine/engine';\nimport { defaultBmadInstalled } from '../engine/orchestrate/bmad-install';\nimport { readInstalledBmadVersion as defaultReadInstalledBmadVersion } from '../engine/bmad-manifest';\nimport { startServer as defaultStartServer, type RunningServer, type ServerCommands } from '../server/server';\nimport { openBrowser as defaultOpenBrowser } from '../server/open-browser';\nimport { writeWelcomeHtml as defaultWriteWelcome } from '../server/welcome';\nimport { expandTilde } from '../engine/expand-tilde';\n\n// Injectable seams so the lifecycle is unit-testable without a real install / browser / exit.\nexport interface ServerModeDeps {\n startServer?: typeof defaultStartServer;\n openBrowser?: typeof defaultOpenBrowser;\n writeWelcome?: typeof defaultWriteWelcome;\n /** Builds the engine that a /start drives — injected so tests use a fake. */\n engineFactory?: (config: Config, emitter: EngineEmitter) => EngineCommands<unknown>;\n /** Process exit, after the ephemeral server has written welcome.html + closed. */\n exit?: (code: number) => void;\n write?: (line: string) => void;\n platform?: NodeJS.Platform;\n /** Built UI directory to serve (dist/ui); resolved by the bootstrap. */\n uiDir?: string;\n /**\n * Read-only `_bmad`-dir check for the Configure-time inspect probe (Story 7.2). Default is the\n * engine's `defaultBmadInstalled`; injected as a fake in tests (no real fs).\n */\n bmadAlreadyInstalled?: (dir: string) => Promise<boolean>;\n /**\n * Read the ACTUAL installed BMad version from the manifest (7.1). Default is the engine helper;\n * injected as a fake in tests (no real fs).\n */\n readInstalledBmadVersion?: (dir: string) => Promise<string | null>;\n}\n\n/**\n * Server mode (FR-12 lifecycle). Stands up the localhost server, lazily builds the engine when\n * the browser POSTs /start (the config arrives from the Configure screen), and on the Welcome\n * render-ack writes the self-contained welcome.html (so a refresh works after exit), closes the\n * server, and exits — ONLY on success. On failure nothing acks, so the server stays alive for\n * Retry. The live browser→install→exit run is validated at the dress rehearsal.\n */\nexport async function runServerMode(\n emitter: EngineEmitter,\n deps: ServerModeDeps = {},\n): Promise<RunningServer> {\n const startServer = deps.startServer ?? defaultStartServer;\n const openBrowser = deps.openBrowser ?? defaultOpenBrowser;\n const writeWelcome = deps.writeWelcome ?? defaultWriteWelcome;\n const engineFactory =\n deps.engineFactory ?? ((config, em) => new Engine(config, em));\n const exit = deps.exit ?? ((code) => process.exit(code));\n const write = deps.write ?? ((line) => process.stdout.write(line + '\\n'));\n // The built layout is dist/cli/server-mode.js alongside dist/ui — serve that by default.\n const uiDir = deps.uiDir ?? fileURLToPath(new URL('../ui', import.meta.url));\n const bmadAlreadyInstalled = deps.bmadAlreadyInstalled ?? defaultBmadInstalled;\n const readInstalledBmadVersion = deps.readInstalledBmadVersion ?? defaultReadInstalledBmadVersion;\n\n let lastConfig: Config | null = null;\n let engine: EngineCommands<unknown> | null = null;\n\n const commands: ServerCommands = {\n start: (config) => {\n // Expand a leading `~` ONCE, at intake, so every consumer — the Engine AND the Welcome\n // writer in finish() — sees a real path (the UI default is the literal `~/kindling-project`).\n const c: Config = { ...config, projectDir: expandTilde(config.projectDir) };\n lastConfig = c;\n engine = engineFactory(c, emitter);\n return engine.start(c);\n },\n cancel: () => engine?.cancel(),\n retry: (step: StepId) => engine?.retry(step),\n // Pure read-only fs probe (Story 7.2) — wraps neither the engine nor any run state. Both reads\n // degrade to false/null, so /inspect can't crash on a bad path.\n inspect: async (projectDir) => ({\n isKindlingProject: await bmadAlreadyInstalled(projectDir),\n installedBmadVersion: await readInstalledBmadVersion(projectDir),\n }),\n };\n\n let acked = false; // one-shot: the host shutdown must run at most once\n const server = await startServer({\n emitter,\n commands,\n uiDir,\n onWelcomeAck: () => {\n if (acked) return;\n acked = true;\n void finish().catch(() => exit(0));\n },\n });\n\n // On success: persist the self-contained Welcome page (survives exit), then close + exit.\n const finish = async (): Promise<void> => {\n const done = emitter.events().find((e) => typeof e.summaryJson === 'string');\n if (lastConfig && done?.summaryJson) {\n // Best-effort: persisting welcome.html is a nicety, not required for a successful install —\n // a write failure must not crash the process or leave the server hanging.\n try {\n await writeWelcome(lastConfig.projectDir, {\n bmadVersion: pins.bmad,\n summaryJson: done.summaryJson,\n });\n } catch {\n // ignore — proceed to a clean shutdown regardless\n }\n }\n await server.close();\n exit(0);\n };\n\n openBrowser(server.url, { platform: deps.platform });\n write(`Kindling is running — open ${server.url} if it didn't open automatically.`);\n return server;\n}\n","import { writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n cliLoginGuidance,\n cliMissing,\n bmadVersionLabel,\n type CliPresence,\n type ValidationSummary,\n} from '../engine/validation-summary';\n\nexport interface WelcomeData {\n /** Pinned BMad version — the fallback for the versions table's BMad row. */\n bmadVersion: string;\n /** The engine's Validation Summary JSON (already serialized) — the table reads versions from it. */\n summaryJson: string;\n}\n\n// HTML-escape for safe interpolation into the static page (the summary is machine-generated,\n// but escape defensively so no value can break out of the text/attribute context).\nfunction esc(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n// Join a list of already-escaped fragments with English separators (\", \", \" or \" / \" and \").\nfunction joinHuman(parts: string[], last: string): string {\n if (parts.length <= 1) return parts.join('');\n return parts.slice(0, -1).join(', ') + ` ${last} ` + parts[parts.length - 1];\n}\n\n// Derive the FR25 login line + the AC-8 install-it-yourself notice from the embedded summary's\n// `cli` presence (the SAME pure helpers the React Welcome uses, so the copy is identical). Returns\n// escaped HTML fragments; empty string when the summary has no CLI info (line simply omitted).\nfunction cliGuidanceHtml(summaryJson: string): string {\n let cli: CliPresence[];\n try {\n const parsed = JSON.parse(summaryJson) as Pick<ValidationSummary, 'cli'>;\n cli = Array.isArray(parsed.cli) ? parsed.cli : [];\n } catch {\n return '';\n }\n const present = cliLoginGuidance({ cli });\n const missing = cliMissing({ cli });\n let html = '';\n if (present.length > 0) {\n const cmds = joinHuman(\n present.map((c) => `<code>${esc(c.bin)}</code>`),\n 'or',\n );\n html += ` <p class=\"cli-guidance\"><b>One last step:</b> open a terminal, run ${cmds}, and log in, then you're all set.</p>\\n`;\n }\n if (missing.length > 0) {\n const cmds = joinHuman(\n missing.map((c) => `<code>npm install -g ${esc(c.pkg)}</code>`),\n 'and',\n );\n html += ` <p class=\"cli-guidance\">Your AI assistant didn’t finish installing. Your project is still ready. To install it yourself, run ${cmds}, then start it and log in.</p>\\n`;\n }\n return html;\n}\n\n/**\n * Build the self-contained static Welcome page. It has NO external assets and embeds the\n * Validation Summary inline, so it keeps rendering on refresh after the ephemeral server has\n * exited (FR-12). Pure — returns the HTML string.\n */\n// Read the honest version chip (Story 7.2 / AC-6) from the embedded summary. A latest run reports\n// the ACTUAL installed version + \"updated to latest\"; the default pinned run (absent/null/equal\n// installedVersion) keeps `bmadVersion` + \"a stable, tested version\". Tolerant of malformed JSON.\nfunction versionChip(summaryJson: string, pinnedFallback: string): { version: string; note: string } {\n try {\n const parsed = JSON.parse(summaryJson) as { bmad?: { installedVersion?: string | null } };\n return bmadVersionLabel(parsed, pinnedFallback);\n } catch {\n return bmadVersionLabel(null, pinnedFallback);\n }\n}\n\n// Build the \"what's installed\" table from the embedded summary (mirrors the React Welcome table).\nfunction versionsTableHtml(summaryJson: string, pinnedFallback: string): string {\n let node: { version: string | null } | undefined;\n let git: { version: string | null } | undefined;\n let cli: CliPresence[] = [];\n try {\n const p = JSON.parse(summaryJson) as {\n node?: { version: string | null };\n git?: { version: string | null };\n cli?: CliPresence[];\n };\n node = p.node;\n git = p.git;\n cli = Array.isArray(p.cli) ? p.cli : [];\n } catch {\n // Malformed summary: still show the BMad row (versionChip falls back to the pin).\n }\n const chip = versionChip(summaryJson, pinnedFallback);\n const rows: string[] = [];\n if (node) rows.push(`<tr><th scope=\"row\">Node.js</th><td>${esc(node.version ?? 'Installed')}</td></tr>`);\n if (git) rows.push(`<tr><th scope=\"row\">Git</th><td>${esc(git.version ?? 'Installed')}</td></tr>`);\n rows.push(\n `<tr><th scope=\"row\">BMad Method</th><td><strong>${esc(chip.version)}</strong> · ${esc(chip.note)}</td></tr>`,\n );\n for (const c of cli) {\n rows.push(\n `<tr><th scope=\"row\">${esc(c.name)}</th><td>${c.present ? '✓ Installed' : 'Not installed'}</td></tr>`,\n );\n }\n return ` <table class=\"versions\">\\n <caption>Here's what's set up on your computer</caption>\\n <tbody>\\n${rows\n .map((r) => ' ' + r)\n .join('\\n')}\\n </tbody>\\n </table>\\n`;\n}\n\n/**\n * Build the self-contained static Welcome page. NO external assets, so it keeps rendering on refresh\n * after the ephemeral server has exited (FR-12). Shows the versions table + the CLI login step.\n * Pure — returns the HTML string.\n */\nexport function buildWelcomeHtml(data: WelcomeData): string {\n const versionsTable = versionsTableHtml(data.summaryJson, data.bmadVersion);\n const cliGuidance = cliGuidanceHtml(data.summaryJson);\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<title>Kindling: You're ready</title>\n<style>\n :root { color-scheme: dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n background:#1b1410; color:#fff6ee;\n font-family:-apple-system,\"Segoe UI Variable\",\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif; }\n main { max-width:640px; padding:48px 32px; }\n h1 { font-size:34px; font-weight:900; letter-spacing:-.02em; margin:0 0 8px; }\n .eyebrow { font-size:12px; font-weight:800; letter-spacing:.16em; text-transform:uppercase;\n color:#ffc24a; margin:0; }\n .lede { font-size:20px; color:#d6b9a3; }\n a { color:#ff6a1f; }\n .cli-guidance { color:#d6b9a3; }\n code { background:#34271e; border:1px solid #5a3c20; border-radius:6px; padding:1px 6px;\n color:#ffc24a; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }\n table.versions { width:100%; border-collapse:collapse; margin:24px 0; text-align:left; }\n .versions caption { text-align:left; color:#d6b9a3; font-weight:700; margin-bottom:8px; }\n .versions th, .versions td { padding:8px 12px; border-bottom:1px solid #463429; }\n .versions th { font-weight:700; color:#d6b9a3; }\n .versions td { color:#fff6ee; }\n</style>\n</head>\n<body>\n<main>\n <p class=\"eyebrow\">All set</p>\n <h1>You're ready 🔥</h1>\n <p class=\"lede\">Your project is set up with BMad and your tools.</p>\n${versionsTable}${cliGuidance} <p><a href=\"https://aiviatic.com\" target=\"_blank\" rel=\"noreferrer\">Join an Aiviatic workshop</a>, totally optional.</p>\n</main>\n</body>\n</html>\n`;\n}\n\n/** Write the self-contained Welcome page to `dir/welcome.html` (injectable writer for tests). */\nexport async function writeWelcomeHtml(\n dir: string,\n data: WelcomeData,\n write: (path: string, contents: string) => Promise<void> = (p, c) => writeFile(p, c, 'utf8'),\n): Promise<string> {\n const path = join(dir, 'welcome.html');\n await write(path, buildWelcomeHtml(data));\n return path;\n}\n"],"mappings":"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","names":["startServer","openBrowser","readInstalledBmadVersion"]}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
declare const Phase: {
|
|
2
|
+
readonly Provision: "provision";
|
|
3
|
+
readonly Scaffold: "scaffold";
|
|
4
|
+
readonly Install: "install";
|
|
5
|
+
readonly Finalize: "finalize";
|
|
6
|
+
};
|
|
7
|
+
type Phase = (typeof Phase)[keyof typeof Phase];
|
|
8
|
+
declare const StepId: {
|
|
9
|
+
readonly ProvisionNode: "provision.node";
|
|
10
|
+
readonly ProvisionGit: "provision.git";
|
|
11
|
+
readonly ProvisionXcodeClt: "provision.xcode-clt";
|
|
12
|
+
readonly ScaffoldGitInit: "scaffold.git-init";
|
|
13
|
+
readonly InstallBmad: "install.bmad";
|
|
14
|
+
readonly InstallAgentCli: "install.agent-cli";
|
|
15
|
+
readonly FinalizeSelfCheck: "finalize.self-check";
|
|
16
|
+
};
|
|
17
|
+
type StepId = (typeof StepId)[keyof typeof StepId];
|
|
18
|
+
/**
|
|
19
|
+
* Steps whose `Failed` must NOT fail the overall run (SSOT — "which steps are non-fatal" belongs
|
|
20
|
+
* next to the step vocabulary). The optional agent-CLI install (Story 6.1) is non-fatal by design:
|
|
21
|
+
* the engine step returns `true` unconditionally, so a CLI that didn't install still reaches the
|
|
22
|
+
* self-check / Welcome. `ui/state/reducer.ts` `deriveOverall` respects this set so a non-fatal
|
|
23
|
+
* `Failed` never yields `overall: 'failed'` (Story 6.2 / AC-8). Future non-fatal steps just join it.
|
|
24
|
+
*/
|
|
25
|
+
declare const NON_FATAL_STEPS: ReadonlySet<StepId>;
|
|
26
|
+
declare const Status: {
|
|
27
|
+
readonly Queued: "queued";
|
|
28
|
+
readonly Working: "working";
|
|
29
|
+
readonly Done: "done";
|
|
30
|
+
readonly Skipped: "skipped";
|
|
31
|
+
readonly Failed: "failed";
|
|
32
|
+
};
|
|
33
|
+
type Status = (typeof Status)[keyof typeof Status];
|
|
34
|
+
type Level = 'info' | 'warn' | 'error';
|
|
35
|
+
/** Stable error codes for failure copy (see engine/messages.ts → recoveryGuidance). */
|
|
36
|
+
declare const ErrorCode: {
|
|
37
|
+
readonly ExecFailed: "exec.failed";
|
|
38
|
+
readonly NetworkLost: "network.lost";
|
|
39
|
+
readonly BmadInstallFailed: "install.bmad-failed";
|
|
40
|
+
readonly AgentCliInstallFailed: "install.agent-cli-failed";
|
|
41
|
+
readonly ExecPolicyBlocked: "exec.policy-blocked";
|
|
42
|
+
readonly SmartScreenBlocked: "exec.smartscreen-blocked";
|
|
43
|
+
readonly ProjectConflict: "scaffold.project-conflict";
|
|
44
|
+
};
|
|
45
|
+
type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
46
|
+
/**
|
|
47
|
+
* Append-only progress event emitted by the engine. Frontends render these; never compose copy.
|
|
48
|
+
* INVARIANT: every field is a primitive — keep it so, so the emitter's shallow freeze fully locks it.
|
|
49
|
+
*/
|
|
50
|
+
interface KindlingEvent {
|
|
51
|
+
id: string;
|
|
52
|
+
phase: Phase;
|
|
53
|
+
step: StepId;
|
|
54
|
+
status: Status;
|
|
55
|
+
pct?: number;
|
|
56
|
+
humanMessage: string;
|
|
57
|
+
level: Level;
|
|
58
|
+
timestamp: string;
|
|
59
|
+
/** Set on a Failed event to key the UI's recovery guidance (engine/messages.ts). */
|
|
60
|
+
errorCode?: ErrorCode;
|
|
61
|
+
/** Set on the final self-check Done event: the Validation Summary as JSON (FR — the text the
|
|
62
|
+
* user copies into the Validation Page). A primitive string, preserving the all-primitive
|
|
63
|
+
* event invariant. */
|
|
64
|
+
summaryJson?: string;
|
|
65
|
+
}
|
|
66
|
+
/** Frozen version pins — see engine/pins.ts. */
|
|
67
|
+
interface Pins {
|
|
68
|
+
node: string;
|
|
69
|
+
bmad: string;
|
|
70
|
+
kindling: string;
|
|
71
|
+
}
|
|
72
|
+
/** Flat, serializable config the frontend submits to the engine. */
|
|
73
|
+
interface Config {
|
|
74
|
+
projectDir: string;
|
|
75
|
+
projectName: string;
|
|
76
|
+
/** Selected IDE/tool codes → `--tools`. */
|
|
77
|
+
ides: string[];
|
|
78
|
+
/** Selected BMad modules → `--modules`. */
|
|
79
|
+
modules: string[];
|
|
80
|
+
/**
|
|
81
|
+
* Picked tool ids the user opted to install an agent CLI for (Story 6.1). Only `claude-code`
|
|
82
|
+
* and `codex` are eligible (id→package table in orchestrate/agent-cli.ts); any other id is
|
|
83
|
+
* ignored. Absent/empty ⇒ the install-agent-cli step is a no-op. Story 6.2's Configure UI
|
|
84
|
+
* populates this (default-on for whichever of claude-code/codex is selected).
|
|
85
|
+
*/
|
|
86
|
+
installCli?: string[];
|
|
87
|
+
/**
|
|
88
|
+
* BMad version target for the install step (Story 7.1 / FR27). Absent ⇒ `'pinned'`: install the
|
|
89
|
+
* frozen cohort pin (`pins.bmad`) — the reproducible default every fresh install and repair
|
|
90
|
+
* re-run uses. `'latest'` is the explicit opt-in that installs `bmad-method@latest` with a FORCED
|
|
91
|
+
* `--action update` (moves the user off the frozen workshop version). Story 7.2's Configure UI
|
|
92
|
+
* populates this (default off / pinned); 7.1 only defines + honors it. Flat + serializable, like
|
|
93
|
+
* `installCli` (Story 6.1).
|
|
94
|
+
*/
|
|
95
|
+
bmadTarget?: 'pinned' | 'latest';
|
|
96
|
+
/** Escape-hatch overrides → repeated `--set <key>=<value>` (FR10). */
|
|
97
|
+
set?: Record<string, string>;
|
|
98
|
+
pins: Pins;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Result of the `POST /inspect` probe (Story 7.2 / FR26/FR27): the filesystem facts the browser
|
|
102
|
+
* cannot read itself. `isKindlingProject` = a `_bmad` DIRECTORY exists under the chosen project dir
|
|
103
|
+
* (reuses `defaultBmadInstalled`); `installedBmadVersion` = the ACTUAL manifest version (7.1's
|
|
104
|
+
* `readInstalledBmadVersion`) or `null` when the manifest is absent/unreadable. Plain, serializable
|
|
105
|
+
* browser↔server contract — sibling to `Config`/`KindlingEvent`, so both `server/server.ts` and
|
|
106
|
+
* `ui/lib/commands.ts` import ONE type with no cross-layer (UI→server) import.
|
|
107
|
+
*/
|
|
108
|
+
interface InspectResult {
|
|
109
|
+
isKindlingProject: boolean;
|
|
110
|
+
installedBmadVersion: string | null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Command interface the engine implements. Generic over the run result `R` (defaults to
|
|
114
|
+
* `void`) so a concrete engine can return a richer result (e.g. the run outcome + summary)
|
|
115
|
+
* without the contract depending on the engine module.
|
|
116
|
+
*/
|
|
117
|
+
interface EngineCommands<R = void> {
|
|
118
|
+
start(config: Config): R | Promise<R>;
|
|
119
|
+
cancel(): void;
|
|
120
|
+
retry(step: StepId): R | Promise<R>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type EventListener = (event: KindlingEvent) => void;
|
|
124
|
+
declare class EngineEmitter {
|
|
125
|
+
private readonly log;
|
|
126
|
+
private readonly listeners;
|
|
127
|
+
/** Subscribe to events. Returns an unsubscribe function. */
|
|
128
|
+
on(listener: EventListener): () => void;
|
|
129
|
+
/** Emit an event: freeze it, append to the log, then notify listeners in subscription order. */
|
|
130
|
+
emit(event: KindlingEvent): void;
|
|
131
|
+
/** A snapshot of the append-only event log, in emission order. */
|
|
132
|
+
events(): readonly KindlingEvent[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { type Config as C, type EngineCommands as E, type InspectResult as I, type KindlingEvent as K, type Level as L, NON_FATAL_STEPS as N, Phase as P, Status as S, EngineEmitter as a, ErrorCode as b, type EventListener as c, type Pins as d, StepId as e };
|