@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.
@@ -0,0 +1,546 @@
1
+ import { d as Pins, b as ErrorCode, e as StepId, a as EngineEmitter, C as Config, K as KindlingEvent, E as EngineCommands } from '../emitter-oidLJDmn.js';
2
+ export { c as EventListener, I as InspectResult, L as Level, N as NON_FATAL_STEPS, P as Phase, S as Status } from '../emitter-oidLJDmn.js';
3
+ import { E as ExecResult } from '../exec-JnCZZPZU.js';
4
+ export { e as exec } from '../exec-JnCZZPZU.js';
5
+
6
+ declare const pins: Readonly<Pins>;
7
+
8
+ declare const stepMessages: Record<StepId, string>;
9
+ declare const errorMessages: Record<ErrorCode, string>;
10
+ /**
11
+ * Structured recovery guidance for the in-UI error screen (Story 3.6). The UI RENDERS this; it
12
+ * never composes its own copy. Each entry names the cause and a concrete next step; some carry a
13
+ * one-line fix command (shown with a copy button) or route to a non-destructive choice.
14
+ * - `recovery: 'retry'` → show a Retry button (re-run the failed step).
15
+ * - `recovery: 'choose-folder'` → no destructive default; let the user pick another folder (FR-9).
16
+ */
17
+ interface RecoveryGuidance {
18
+ /** Short reassuring framing headline. */
19
+ title: string;
20
+ /** The cause + the concrete next step, plain language. */
21
+ detail: string;
22
+ /** Optional one-line command the user runs, then retries (e.g. the Windows policy fix). */
23
+ fixCommand?: string;
24
+ recovery: 'retry' | 'choose-folder';
25
+ }
26
+ declare const recoveryGuidance: Record<ErrorCode, RecoveryGuidance>;
27
+
28
+ type ScaffoldOutcome = 'created' | 'resumed' | 'skipped' | 'blocked';
29
+ interface ScaffoldOptions {
30
+ projectDir: string;
31
+ projectName: string;
32
+ emitter: EngineEmitter;
33
+ /** Absolute path to the provisioned git (Epic 2); defaults to PATH lookup. */
34
+ git?: string;
35
+ /** Injectable clock for deterministic tests. */
36
+ now?: () => string;
37
+ }
38
+ declare function scaffold(opts: ScaffoldOptions): Promise<ScaffoldOutcome>;
39
+
40
+ type InstallAction = 'install' | 'update';
41
+ /**
42
+ * Compose the `bmad-method install` argv. `action` (2.7) selects a fresh install vs a re-run:
43
+ * on a project that already has a BMad install, pass `--action update` so the re-run updates in
44
+ * place rather than erroring/duplicating; a fresh project omits it (defaults to install).
45
+ */
46
+ declare function composeInstallArgs(config: Config, action?: InstallAction): string[];
47
+
48
+ interface LaunchRuntime {
49
+ /** Absolute path to the provisioned node binary, or null to use `node`/`npx` on PATH
50
+ * (a reused system Node, or nvm sourced same-shell). See ProvisionResult.nodeExe. */
51
+ nodeExe: string | null;
52
+ /** Pinned Kindling version (pins.kindling). */
53
+ kindlingVersion: string;
54
+ }
55
+ interface LaunchCommand {
56
+ cmd: string;
57
+ args: string[];
58
+ }
59
+ /** Path to npx's CLI script beside a provisioned node binary (the npm bundled with the dist). */
60
+ declare function npxCliPath(nodeExe: string): string;
61
+ /**
62
+ * Compose the command to launch Kindling once Node is provisioned (Story 2.6). The clean-runtime
63
+ * rule (AR6): never depend on a freshly-mutated PATH —
64
+ * - `nodeExe === null` → Node is already on PATH (nvm sourced same-shell, or a reused system
65
+ * Node) → plain `npx -y @aiviatic/kindling@<pin>`.
66
+ * - `nodeExe` set (Windows portable, absolute) → invoke npx's CLI through that exact node binary,
67
+ * so the launch works without the portable Node ever being on PATH.
68
+ * Pure — the bootstrap (or a future Windows launcher) spawns the returned command.
69
+ */
70
+ declare function composeLaunchCommand({ nodeExe, kindlingVersion }: LaunchRuntime): LaunchCommand;
71
+
72
+ declare function defaultBmadInstalled(projectDir: string): Promise<boolean>;
73
+ /**
74
+ * Result of an install attempt.
75
+ *
76
+ * Failure contract: a **non-zero exit** resolves with `{ ok: false }` (the caller must check
77
+ * `.ok`); a **spawn error** (e.g. npx not found) emits `Failed` then *throws*. Either way a
78
+ * terminal `Failed` event is emitted exactly once. (Note: `scaffold()` throws on a non-zero
79
+ * exit — the orchestrator story must reconcile these two error models; see deferred-work.md.)
80
+ *
81
+ * Version target (Story 7.1 / FR27): `config.bmadTarget` selects the npx package tag. `'pinned'`
82
+ * (default) is byte-for-byte the historical behavior — `bmad-method@<pins.bmad>` with
83
+ * `--action install` (fresh) vs `--action update` (existing `_bmad`, Story 2.7). `'latest'` is the
84
+ * explicit opt-in update variant — `bmad-method@latest` with `--action update` FORCED regardless of
85
+ * the `_bmad`-present detection. `bmadVersion` in the result is the resolved TAG (`<pins.bmad>` or
86
+ * the literal `'latest'`); the concrete version `@latest` resolves to is picked up by the
87
+ * self-check's manifest read (FR26), not here.
88
+ */
89
+ interface BmadInstallResult {
90
+ ok: boolean;
91
+ bmadVersion: string;
92
+ }
93
+ interface BmadInstallOptions {
94
+ config: Config;
95
+ emitter: EngineEmitter;
96
+ /** Injectable subprocess runner (tests pass a fake; never runs a real install). */
97
+ exec?: (cmd: string, args: string[]) => Promise<ExecResult>;
98
+ /**
99
+ * Command used to invoke npx. Default 'npx' (macOS/Linux dev). On Windows this must be
100
+ * the provisioned `node` + npm-cli.js (Epic 2) since `npx.cmd` can't spawn with shell:false
101
+ * — see deferred-work.md.
102
+ */
103
+ npxCommand?: string;
104
+ /**
105
+ * Idempotent re-run (2.7): is BMad already installed in this project? When true, the install
106
+ * runs with `--action update` (update in place) instead of a fresh install. Default detects a
107
+ * `_bmad` dir under config.projectDir; injectable for tests.
108
+ */
109
+ bmadAlreadyInstalled?: (projectDir: string) => Promise<boolean>;
110
+ now?: () => string;
111
+ }
112
+ declare function runBmadInstall(opts: BmadInstallOptions): Promise<BmadInstallResult>;
113
+
114
+ /**
115
+ * Explicit id → npm package table. The scope guard: only these two picked-tool ids are eligible
116
+ * for an agent-CLI install; every other `--tools` id (vscode, cursor, …) is ignored and stays
117
+ * skill-files-only. Kept explicit and in one place so the mapping — and the DELIBERATE unpinned
118
+ * (latest) package spelling — is one edit to change / verify against the registry.
119
+ *
120
+ * `[DECISION]` Unpinned (latest): everywhere else Kindling pins, but the agent CLIs are the
121
+ * deliberate exception — they self-update, and pinning would strand cohort users on a stale
122
+ * binary. So the pkg strings carry NO `@version`; `exec(npm, ['install','-g', pkg])` installs
123
+ * latest.
124
+ */
125
+ declare const AGENT_CLI_TABLE: Record<string, {
126
+ pkg: string;
127
+ bin: string;
128
+ name: string;
129
+ }>;
130
+ /** A resolved, eligible agent-CLI descriptor (the id folded into its table row). */
131
+ interface AgentCliDescriptor {
132
+ id: string;
133
+ pkg: string;
134
+ bin: string;
135
+ name: string;
136
+ }
137
+ /**
138
+ * SSOT accessor: map the requested `installCli` ids to their eligible descriptors, applying the
139
+ * same scope guard + de-dupe as `installAgentCli`. The single source both the install step AND the
140
+ * self-check (Story 6.2) read, so the id→{pkg,bin,name} mapping is never hard-coded twice. A
141
+ * non-eligible id (vscode, cursor, …) is dropped; an empty/absent list yields `[]`.
142
+ */
143
+ declare function eligibleAgentClis(installCli: string[] | undefined): AgentCliDescriptor[];
144
+ /**
145
+ * Outcome of the install-agent-cli step. `ok` reflects only that the step RAN TO COMPLETION —
146
+ * it is INDEPENDENT of individual install failures (which land in `failed`). This is the step's
147
+ * defining property versus every other engine step: it is NON-FATAL, so the engine reaches the
148
+ * self-check / Welcome even if a CLI failed to install (AC-3). The failed ids are surfaced solely
149
+ * as `Failed` events (keyed by ErrorCode.AgentCliInstallFailed) for the error surface / Story 6.2.
150
+ */
151
+ interface AgentCliResult {
152
+ ok: boolean;
153
+ installed: string[];
154
+ skipped: string[];
155
+ failed: string[];
156
+ }
157
+ interface AgentCliOptions {
158
+ config: Config;
159
+ emitter: EngineEmitter;
160
+ /** Injectable subprocess runner (tests pass a fake; NEVER runs a real npm install). */
161
+ exec?: (cmd: string, args: string[]) => Promise<ExecResult>;
162
+ /**
163
+ * Command used to invoke npm. Default 'npm' (macOS/Linux dev). On Windows this must be the
164
+ * provisioned `node` + npm-cli.js (Epic 2) since `npm.cmd` can't spawn with shell:false — the
165
+ * same absolute-node strategy `launch.ts` uses via `npxCliPath` (the npm equivalent is
166
+ * `<dir>/node_modules/npm/bin/npm-cli.js`). Not over-plumbed in 6.1: like bmad-install's
167
+ * `npxCommand`, the seam defaults to 'npm' and the rehearsal / Story 6.2 plugs the abs path in.
168
+ */
169
+ npmCommand?: string;
170
+ /**
171
+ * Resolve an agent CLI's bin name to a spawnable form before the idempotent-skip probe. Default:
172
+ * identity. On Windows a global `npm install -g` writes a `claude.cmd`/`.ps1` shim (not a bare
173
+ * `claude` executable), and `exec` uses `shell:false` — so a bare bin can't be spawned there and
174
+ * `probeVersion` would wrongly report "absent" and reinstall on every run. This seam is the probe
175
+ * equivalent of `npmCommand`: the Epic-2 Windows wiring / dress rehearsal plugs the resolved shim
176
+ * path in here. Not over-plumbed in 6.1 (defaults to identity, exactly as `npmCommand` defaults
177
+ * to 'npm'). See deferred-work.md → agent-CLI Windows resolution.
178
+ */
179
+ resolveBin?: (bin: string) => string;
180
+ now?: () => string;
181
+ }
182
+ /**
183
+ * Optionally installs the picked agent's CLI (Claude Code / Codex) globally at latest, AFTER the
184
+ * BMad install and BEFORE the self-check. Mirrors `bmad-install.ts` (injectable `exec`/`now`, an
185
+ * `npmCommand` seam) but with the opposite failure contract:
186
+ *
187
+ * - **Idempotent skip** (AC-2): probe `<bin> --version` first; if present, emit `Skipped` and do
188
+ * not re-install. The install is machine-global (`npm -g`), so a second run / other project
189
+ * simply skips — which is what makes repeated runs and multiple projects safe.
190
+ * - **Scope guard** (AC-4): only ids in AGENT_CLI_TABLE install; others are ignored.
191
+ * - **NON-fatal failure** (AC-3): a non-zero exit OR a caught spawn error emits ONE `Failed`
192
+ * event (with the manual-install fallback) and CONTINUES to the next CLI — it NEVER throws.
193
+ * A CLI-install failure must not invalidate the successful BMad install.
194
+ *
195
+ * Returns `{ ok: true, ... }` whenever the step ran to completion, regardless of individual
196
+ * failures — the engine step returns `true` unconditionally off the back of this.
197
+ */
198
+ declare function installAgentCli(opts: AgentCliOptions): Promise<AgentCliResult>;
199
+
200
+ declare const SCHEMA_VERSION = 3;
201
+ /** Node runtime floor (BMad's hard requirement). */
202
+ declare const NODE_FLOOR_MAJOR = 20;
203
+ /**
204
+ * Presence of one requested agent CLI in the final self-check (FR24). Self-describing — it carries
205
+ * `name`/`bin`/`pkg` so the browser Validation Page and both Welcome renderers render copy WITHOUT
206
+ * re-importing the engine's `AGENT_CLI_TABLE`. `pkg` lets the "install-it-yourself" notice (AC-8)
207
+ * show the exact `npm install -g <pkg>` command for an absent CLI. NON-blocking: this never feeds
208
+ * `success` (AC-6). Empty list when no CLI was requested (present for schema stability).
209
+ */
210
+ interface CliPresence {
211
+ id: string;
212
+ name: string;
213
+ bin: string;
214
+ pkg: string;
215
+ present: boolean;
216
+ }
217
+ interface ValidationSummary {
218
+ schemaVersion: number;
219
+ kindlingVersion: string;
220
+ os: string;
221
+ arch: string;
222
+ osVersion: string;
223
+ node: {
224
+ present: boolean;
225
+ version: string | null;
226
+ satisfiesFloor: boolean;
227
+ };
228
+ git: {
229
+ present: boolean;
230
+ version: string | null;
231
+ };
232
+ /**
233
+ * `pinnedVersion` = the REQUESTED cohort pin (`pins.bmad`); `installed` = the `_bmad` dir is
234
+ * present; `installedVersion` (FR26) = the ACTUAL version read from the manifest — `null` when
235
+ * the manifest is absent/unreadable/unparseable (honest "couldn't read disk reality", never a
236
+ * false pin). The Validation Page (Story 7.1) compares `installedVersion` against the expected pin.
237
+ */
238
+ bmad: {
239
+ pinnedVersion: string;
240
+ installed: boolean;
241
+ installedVersion: string | null;
242
+ };
243
+ scaffold: {
244
+ created: boolean;
245
+ };
246
+ /** Requested agent CLIs + their presence (FR24). NON-blocking — never part of `success`. */
247
+ cli: CliPresence[];
248
+ success: boolean;
249
+ generatedAt: string;
250
+ }
251
+ interface ValidationFacts {
252
+ kindlingVersion: string;
253
+ os: string;
254
+ arch: string;
255
+ osVersion: string;
256
+ node: {
257
+ present: boolean;
258
+ version: string | null;
259
+ satisfiesFloor: boolean;
260
+ };
261
+ git: {
262
+ present: boolean;
263
+ version: string | null;
264
+ };
265
+ bmad: {
266
+ pinnedVersion: string;
267
+ installed: boolean;
268
+ installedVersion: string | null;
269
+ };
270
+ scaffold: {
271
+ created: boolean;
272
+ };
273
+ cli: CliPresence[];
274
+ generatedAt: string;
275
+ }
276
+ declare function buildValidationSummary(facts: ValidationFacts): ValidationSummary;
277
+ /**
278
+ * The requested CLIs that ARE present after the run — drives the FR25 login guidance ("run `claude`
279
+ * and log in"). Pure + shared so the React Welcome and the static welcome.html render identical copy
280
+ * from one source. Tolerant of a missing/legacy `cli` field or a malformed element (returns []/skips).
281
+ */
282
+ declare function cliLoginGuidance(summary: Pick<ValidationSummary, 'cli'>): CliPresence[];
283
+ /**
284
+ * The requested CLIs that are ABSENT after the run — drives the calm, non-blocking "didn't finish
285
+ * installing — here's the one line to install it, then log in" notice (AC-8). Order-robust: derived
286
+ * from the summary's actual presence, not the transient step row. Skips malformed elements.
287
+ */
288
+ declare function cliMissing(summary: Pick<ValidationSummary, 'cli'>): CliPresence[];
289
+ /**
290
+ * The honest BMad version chip (Story 7.2 / FR26). Pure + shared so the React Welcome and the static
291
+ * welcome.html render identical copy from ONE source. When the summary carries an `installedVersion`
292
+ * (7.1's manifest read) that is a non-empty string DIFFERING from the pinned fallback, the run was an
293
+ * update-to-latest, so the pinned "a stable, tested version" chip would be a false statement; show
294
+ * the real installed version + "updated to latest". Otherwise (absent/null/equal, the default run,
295
+ * unchanged) fall back to the pinned chip. Never blank, never a crash. Node-free (browser-safe).
296
+ */
297
+ declare function bmadVersionLabel(summary: {
298
+ bmad?: {
299
+ installedVersion?: string | null;
300
+ };
301
+ } | null | undefined, pinnedFallback: string): {
302
+ version: string;
303
+ note: string;
304
+ };
305
+ /**
306
+ * Parse a major version from version strings: `v24.16.0`, `24.16.0`, `v20.0`, `v20`,
307
+ * `git version 2.43.0.windows.1`, `v24.0.0-nightly…`. Null if no leading integer is found.
308
+ */
309
+ declare function parseMajor(version: string | null): number | null;
310
+
311
+ interface SelfCheckOptions {
312
+ /** Outcomes threaded from prior steps (Stories 1.4 / 1.5). */
313
+ scaffoldCreated: boolean;
314
+ bmadInstalled: boolean;
315
+ /** Project directory — where the BMad manifest lives (`<projectDir>/_bmad/_config/manifest.yaml`). */
316
+ projectDir: string;
317
+ emitter: EngineEmitter;
318
+ /** Provisioned binaries (Epic 2); default PATH lookup. */
319
+ node?: string;
320
+ git?: string;
321
+ /**
322
+ * The requested eligible agent CLIs (Story 6.2), threaded from the engine's install-agent-cli
323
+ * step. Each is probed for presence and reported in the summary's non-blocking `cli` field.
324
+ * Absent/empty ⇒ `cli: []`.
325
+ */
326
+ agentClis?: AgentCliDescriptor[];
327
+ exec?: (cmd: string, args: string[]) => Promise<ExecResult>;
328
+ now?: () => string;
329
+ /**
330
+ * Resolve a CLI bin to a spawnable form before the presence probe — the probe equivalent of
331
+ * agent-cli.ts's `resolveBin` seam. Default: identity. On Windows the installed CLI is a
332
+ * `claude.cmd` shim and `exec` is `shell:false`, so a bare-bin probe would wrongly report
333
+ * "absent"; the Epic-2 Windows wiring / dress rehearsal plugs the resolved shim path in here.
334
+ */
335
+ resolveBin?: (bin: string) => string;
336
+ /**
337
+ * Read the ACTUAL installed BMad version from the project manifest (FR26). Injectable so unit
338
+ * tests never touch a real fs/manifest (mirrors the `bmadAlreadyInstalled`/`exec` seams).
339
+ * Default: the node-side `readInstalledBmadVersion` helper. Returns `null` on any read/parse
340
+ * failure — reported as `bmad.installedVersion: null`, never a false pin.
341
+ */
342
+ readInstalledBmadVersion?: (projectDir: string) => Promise<string | null>;
343
+ /** Platform facts (injectable for tests); default the running process. */
344
+ platform?: {
345
+ os: string;
346
+ arch: string;
347
+ osVersion: string;
348
+ };
349
+ }
350
+ declare function runSelfCheck(opts: SelfCheckOptions): Promise<ValidationSummary>;
351
+
352
+ /**
353
+ * Read the ACTUAL installed BMad version from a project's manifest (FR26).
354
+ *
355
+ * Reads `<projectDir>/_bmad/_config/manifest.yaml` and returns `installation.version` as a string,
356
+ * or `null` on ANY of: file absent, YAML parse error, a non-object document, or a
357
+ * missing/non-string `installation.version`. NEVER throws — it mirrors the tolerance of
358
+ * `probeVersion`/`defaultBmadInstalled`: a "couldn't read disk reality" degrades to `null`
359
+ * (honest not-ready on the Validation Page), never a crash or a false pin.
360
+ *
361
+ * Node-side only (uses `node:fs/promises` + `js-yaml`). The PURE summary module
362
+ * (`validation-summary.ts`) must not import this — the browser imports that module.
363
+ */
364
+ declare function readInstalledBmadVersion(projectDir: string): Promise<string | null>;
365
+
366
+ interface ToolState {
367
+ present: boolean;
368
+ version: string | null;
369
+ }
370
+ interface NodeState extends ToolState {
371
+ satisfiesFloor: boolean;
372
+ }
373
+ interface DependencyState {
374
+ node: NodeState;
375
+ git: ToolState;
376
+ }
377
+ interface DetectOptions {
378
+ exec?: (cmd: string, args: string[]) => Promise<ExecResult>;
379
+ node?: string;
380
+ git?: string;
381
+ emitter?: EngineEmitter;
382
+ now?: () => string;
383
+ }
384
+ declare function detectDependencies(opts?: DetectOptions): Promise<DependencyState>;
385
+
386
+ interface ProvisionGitUnixOptions {
387
+ platform: NodeJS.Platform;
388
+ emitter: EngineEmitter;
389
+ /** From 2.1 detection: Git is already present → reuse it (Skipped). */
390
+ alreadyOk?: boolean;
391
+ /** macOS: trigger the Xcode Command Line Tools install (opens the system dialog). */
392
+ triggerXcodeInstall?: () => Promise<void>;
393
+ /** macOS: true once `xcode-select -p` resolves (CLT — hence Git — installed). */
394
+ checkXcode?: () => Promise<boolean>;
395
+ /** Linux: install Git via the distro package manager (apt primary). */
396
+ installLinuxGit?: () => Promise<void>;
397
+ /** Poll cadence + cap for the Xcode wait (injected so tests don't actually wait). */
398
+ sleep?: (ms: number) => Promise<void>;
399
+ pollIntervalMs?: number;
400
+ maxPolls?: number;
401
+ exec?: (cmd: string, args: string[]) => Promise<ExecResult>;
402
+ now?: () => string;
403
+ }
404
+ interface ProvisionGitResult {
405
+ ok: boolean;
406
+ }
407
+ /**
408
+ * Provision Git on macOS (via the Xcode Command Line Tools) or Linux (via apt). macOS triggers
409
+ * the CLT install early and POLLS `xcode-select -p`, emitting a Working event on every tick so the
410
+ * un-silenceable Apple dialog never looks frozen (AC1/AC2). Git already present → Skipped (AC).
411
+ *
412
+ * The real `xcode-select`/apt run is validated at the dress rehearsal; here the shell effects +
413
+ * the clock are injected and the orchestration/poll/events/skip/fail paths are unit-tested.
414
+ */
415
+ declare function provisionGitUnix(opts: ProvisionGitUnixOptions): Promise<ProvisionGitResult>;
416
+
417
+ declare function defaultLogDir(): string;
418
+ interface FailureLogEntry {
419
+ step: StepId;
420
+ error: string;
421
+ events: readonly KindlingEvent[];
422
+ }
423
+ interface WriteLogOptions {
424
+ dir?: string;
425
+ now?: () => string;
426
+ }
427
+ /**
428
+ * Writes a local, human-readable failure report and returns its path. Local-only (no network).
429
+ * Deliberately does NOT include the config object, environment variables, or any credentials
430
+ * (NFR7) — just the failed step, the error message, and the event log.
431
+ */
432
+ declare function writeFailureLog(entry: FailureLogEntry, opts?: WriteLogOptions): Promise<string>;
433
+
434
+ interface EngineDeps {
435
+ detect: (opts: DetectOptions) => Promise<DependencyState>;
436
+ provisionGit: (opts: ProvisionGitUnixOptions) => Promise<ProvisionGitResult>;
437
+ scaffold: (opts: ScaffoldOptions) => Promise<ScaffoldOutcome>;
438
+ runBmadInstall: (opts: BmadInstallOptions) => Promise<BmadInstallResult>;
439
+ installAgentCli: (opts: AgentCliOptions) => Promise<AgentCliResult>;
440
+ runSelfCheck: (opts: SelfCheckOptions) => Promise<ValidationSummary>;
441
+ writeFailureLog: (entry: FailureLogEntry) => Promise<string>;
442
+ /** Host platform — selects the Git provisioning step id (xcode-clt on macOS, else git). */
443
+ platform: NodeJS.Platform;
444
+ }
445
+ interface EngineRunResult {
446
+ ok: boolean;
447
+ failedStep: StepId | null;
448
+ summary: ValidationSummary | null;
449
+ logPath: string | null;
450
+ }
451
+ /**
452
+ * Orchestrates the full step sequence — provision Node (verify) → provision Git/Xcode →
453
+ * scaffold → install → install-agent-cli (optional, non-fatal) → self-check — tracking which
454
+ * steps completed so retry() can resume without re-running done steps. On a step failure it
455
+ * writes the on-disk failure report (the
456
+ * step itself already emitted its Failed event) and stops. The bootstrap provisions Node before
457
+ * launch; Git/Xcode is provisioned here so the browser shows the never-frozen progress.
458
+ */
459
+ declare class Engine implements EngineCommands<EngineRunResult> {
460
+ private readonly emitter;
461
+ private readonly config;
462
+ private readonly deps;
463
+ private readonly completed;
464
+ private cancelled;
465
+ private running;
466
+ private scaffoldCreated;
467
+ private bmadInstalled;
468
+ private lastSummary;
469
+ private readonly steps;
470
+ constructor(config: Config, emitter: EngineEmitter, deps?: Partial<EngineDeps>);
471
+ start(config?: Config): Promise<EngineRunResult>;
472
+ retry(step: StepId): Promise<EngineRunResult>;
473
+ cancel(): void;
474
+ private emit;
475
+ private runFrom;
476
+ private fail;
477
+ }
478
+
479
+ type NodeArch = 'x64' | 'arm64';
480
+ /** Official Node Windows .zip URL for a pinned version. Pure. */
481
+ declare function nodeDistUrl(version: string, arch?: NodeArch): string;
482
+ /** Absolute path to node.exe inside the extracted distribution. Pure. */
483
+ declare function nodeExePath(baseDir: string, version: string, arch?: NodeArch): string;
484
+ interface ProvisionNodeWindowsOptions {
485
+ /** Pinned Node version (pins.node). */
486
+ version: string;
487
+ /** Install root, e.g. %LOCALAPPDATA%\kindling\node. */
488
+ baseDir: string;
489
+ arch?: NodeArch;
490
+ emitter: EngineEmitter;
491
+ /** From 2.1 detection: a Node ≥ floor is already present → skip the download. */
492
+ alreadyOk?: boolean;
493
+ /** Injectable so tests run without network/Windows. Defaults below are spike-validation-pending. */
494
+ download?: (url: string, dest: string) => Promise<void>;
495
+ extract?: (zip: string, destDir: string) => Promise<void>;
496
+ exec?: (cmd: string, args: string[]) => Promise<ExecResult>;
497
+ now?: () => string;
498
+ }
499
+ interface ProvisionResult {
500
+ ok: boolean;
501
+ /**
502
+ * Absolute path to the provisioned node binary for the launch step (2.6) to invoke directly
503
+ * (the clean-runtime rule — never rely on a mutated PATH). `null` means an existing system
504
+ * Node was reused → the launch resolves `node` from PATH instead.
505
+ */
506
+ nodeExe: string | null;
507
+ }
508
+ declare function provisionNodeWindows(opts: ProvisionNodeWindowsOptions): Promise<ProvisionResult>;
509
+
510
+ declare const NVM_VERSION = "v0.40.3";
511
+ /** Absolute path to the nvm-installed node binary for a version. Pure. */
512
+ declare function nvmNodePath(nvmDir: string, version: string): string;
513
+ interface ProvisionNodeUnixOptions {
514
+ /** Pinned Node version (pins.node). */
515
+ version: string;
516
+ /** nvm install dir (default ~/.nvm). */
517
+ nvmDir: string;
518
+ emitter: EngineEmitter;
519
+ /** From 2.1 detection: a system Node ≥ floor is already present. Honored only OFF macOS — on
520
+ * macOS we always install the exact pin via nvm so the workshop is reproducible (AC2 reuse is
521
+ * Linux-only; AC3 resolved-version-equals-pins.node). */
522
+ alreadyOk?: boolean;
523
+ /** Host platform — gates the reuse rule above. */
524
+ platform?: NodeJS.Platform;
525
+ /** Injectable side effects so tests run without network/a real shell. Defaults below are
526
+ * shell-based and UNVALIDATED until the dress rehearsal (macOS + Linux). */
527
+ installNvm?: (nvmDir: string) => Promise<void>;
528
+ nvmInstallNode?: (nvmDir: string, version: string) => Promise<void>;
529
+ /** Idempotent re-run check (2.7): is the PINNED node already installed AND runnable? Default
530
+ * runs the resolved binary with --version. Verifying it RUNS (not just that a dir exists)
531
+ * means a partial/dirty prior install is repaired (re-installed), not skipped-into-broken. */
532
+ nodeInstalled?: (nvmDir: string, version: string) => Promise<boolean>;
533
+ exec?: (cmd: string, args: string[]) => Promise<ExecResult>;
534
+ now?: () => string;
535
+ }
536
+ /**
537
+ * Provision the pinned Node on macOS/Linux via nvm, returning the ABSOLUTE node path so the
538
+ * launch step (2.6) never depends on a mutated PATH (the clean-runtime rule). A system Node ≥
539
+ * floor is reused (Skipped, AC2). Emits provision.node events; Failed + rethrow on error.
540
+ *
541
+ * The real nvm install + same-shell source is validated at the dress rehearsal; here the shell
542
+ * effects are injectable and the orchestration/events/skip/fail paths are unit-tested.
543
+ */
544
+ declare function provisionNodeUnix(opts: ProvisionNodeUnixOptions): Promise<ProvisionResult>;
545
+
546
+ export { AGENT_CLI_TABLE, type AgentCliDescriptor, type AgentCliOptions, type AgentCliResult, type BmadInstallOptions, type BmadInstallResult, type CliPresence, Config, type DependencyState, Engine, EngineCommands, type EngineDeps, EngineEmitter, type EngineRunResult, ErrorCode, ExecResult, type FailureLogEntry, KindlingEvent, type LaunchCommand, type LaunchRuntime, NODE_FLOOR_MAJOR, NVM_VERSION, type NodeArch, type NodeState, Pins, type ProvisionGitResult, type ProvisionGitUnixOptions, type ProvisionNodeUnixOptions, type ProvisionNodeWindowsOptions, type ProvisionResult, SCHEMA_VERSION, type ScaffoldOptions, type ScaffoldOutcome, type SelfCheckOptions, StepId, type ToolState, type ValidationFacts, type ValidationSummary, bmadVersionLabel, buildValidationSummary, cliLoginGuidance, cliMissing, composeInstallArgs, composeLaunchCommand, defaultBmadInstalled, defaultLogDir, detectDependencies, eligibleAgentClis, errorMessages, installAgentCli, nodeDistUrl, nodeExePath, npxCliPath, nvmNodePath, parseMajor, pins, provisionGitUnix, provisionNodeUnix, provisionNodeWindows, readInstalledBmadVersion, recoveryGuidance, runBmadInstall, runSelfCheck, scaffold, stepMessages, writeFailureLog };