@aiviatic/kindling 0.1.3 → 0.1.4
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/bootstrap/setup.ps1 +1 -1
- package/bootstrap/setup.sh +1 -1
- package/dist/{chunk-G2SKKJWZ.js → chunk-AAYLG7QL.js} +4 -2
- package/dist/chunk-AAYLG7QL.js.map +1 -0
- package/dist/cli/main.js +1 -1
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.js +1 -1
- package/dist/ui/assets/index-BTForsG1.css +1 -0
- package/dist/ui/assets/index-CueVUoA2.js +40 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-G2SKKJWZ.js.map +0 -1
- package/dist/ui/assets/index-B1p-7hfY.css +0 -1
- package/dist/ui/assets/index-DBBYdu67.js +0 -40
package/bootstrap/setup.ps1
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
$ErrorActionPreference = 'Stop'
|
|
11
11
|
|
|
12
12
|
$KindlingNodeVersion = '24.16.0' # == pins.node
|
|
13
|
-
$KindlingVersion = '0.1.
|
|
13
|
+
$KindlingVersion = '0.1.4' # == pins.kindling
|
|
14
14
|
$NodeFloorMajor = 20
|
|
15
15
|
|
|
16
16
|
# --- Inline helpers (were bootstrap/lib/common.ps1; inlined for the file-less delivery) ----------
|
package/bootstrap/setup.sh
CHANGED
|
@@ -40,7 +40,7 @@ var pins = Object.freeze({
|
|
|
40
40
|
node: "24.16.0",
|
|
41
41
|
bmad: "6.9.0",
|
|
42
42
|
// frozen for the Cohort #1 cycle (matches this repo's BMad install; tools + install flags verified vs the real 6.9.0 CLI 2026-07-02); bump between cohorts
|
|
43
|
-
kindling: "0.1.
|
|
43
|
+
kindling: "0.1.4"
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
// engine/messages.ts
|
|
@@ -475,6 +475,7 @@ function buildValidationSummary(facts) {
|
|
|
475
475
|
os: facts.os,
|
|
476
476
|
arch: facts.arch,
|
|
477
477
|
osVersion: facts.osVersion,
|
|
478
|
+
projectDir: facts.projectDir,
|
|
478
479
|
node: facts.node,
|
|
479
480
|
git: facts.git,
|
|
480
481
|
bmad: facts.bmad,
|
|
@@ -548,6 +549,7 @@ async function runSelfCheck(opts) {
|
|
|
548
549
|
os: platform.os,
|
|
549
550
|
arch: platform.arch,
|
|
550
551
|
osVersion: platform.osVersion,
|
|
552
|
+
projectDir: opts.projectDir,
|
|
551
553
|
node: {
|
|
552
554
|
present: nodeVersion !== null,
|
|
553
555
|
version: nodeVersion,
|
|
@@ -957,4 +959,4 @@ export {
|
|
|
957
959
|
writeFailureLog,
|
|
958
960
|
Engine
|
|
959
961
|
};
|
|
960
|
-
//# sourceMappingURL=chunk-
|
|
962
|
+
//# sourceMappingURL=chunk-AAYLG7QL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../engine/emitter.ts","../engine/pins.ts","../engine/messages.ts","../engine/orchestrate/scaffold.ts","../engine/orchestrate/flags.ts","../engine/orchestrate/bmad-install.ts","../engine/orchestrate/agent-cli.ts","../engine/probe.ts","../engine/orchestrate/launch.ts","../engine/bmad-manifest.ts","../engine/validation-summary.ts","../engine/self-check.ts","../engine/provision/detect.ts","../engine/provision/git-unix.ts","../engine/log.ts","../engine/engine.ts"],"sourcesContent":["import type { KindlingEvent } from './contract';\n\nexport type EventListener = (event: KindlingEvent) => void;\n\n// Typed, append-only event emitter. The engine is the sole producer. Each emitted event is\n// frozen and never mutated; the log only grows (architecture: append-only event stream).\nexport class EngineEmitter {\n private readonly log: KindlingEvent[] = [];\n private readonly listeners = new Set<EventListener>();\n\n /** Subscribe to events. Returns an unsubscribe function. */\n on(listener: EventListener): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** Emit an event: freeze it, append to the log, then notify listeners in subscription order. */\n emit(event: KindlingEvent): void {\n // KindlingEvent fields are all primitives by contract, so a shallow freeze fully locks it.\n const frozen = Object.freeze({ ...event });\n this.log.push(frozen);\n // Snapshot listeners and isolate failures: one throwing sink must not stop delivery to the\n // others (Epic 3 runs the browser SSE sink alongside CLI sinks). A bad sink never crashes\n // the engine. (Story 1.7 wires the on-disk failure log to capture such errors.)\n for (const listener of [...this.listeners]) {\n try {\n listener(frozen);\n } catch {\n // swallow — delivery to remaining listeners continues\n }\n }\n }\n\n /** A snapshot of the append-only event log, in emission order. */\n events(): readonly KindlingEvent[] {\n return [...this.log];\n }\n}\n","import type { Pins } from './contract';\n\n// Frozen pin manifest — the single source of truth for version pins, consumed by the\n// bootstrap copy, install orchestration, the Welcome screen, and the Validation Page.\n// Frozen for the duration of a cohort cycle so home == workshop (NFR-3).\n//\n// `node` is the version Kindling provisions for the user (engine runtime floor is 20+).\n// `bmad` MUST be pinned to the cohort's tested bmad-method version before a real install\n// is shipped (see Story 1.5 / Epic 2). `kindling` is this package's own version.\nexport const pins: Readonly<Pins> = Object.freeze({\n node: '24.16.0',\n bmad: '6.9.0', // frozen for the Cohort #1 cycle (matches this repo's BMad install; tools + install flags verified vs the real 6.9.0 CLI 2026-07-02); bump between cohorts\n kindling: '0.1.4',\n});\n","import { StepId, ErrorCode } from './contract';\n\n// Human-facing, kitchen-table copy. The engine resolves these into events so frontends\n// never compose text (architecture: Communication Patterns). Keyed by StepId; error copy\n// keyed by a stable error code. Keys reference the StepId enum (never raw strings) so this\n// catalog stays in sync with the contract.\nexport const stepMessages: Record<StepId, string> = {\n [StepId.ProvisionNode]: 'Setting up Node - the engine your project runs on.',\n [StepId.ProvisionGit]: 'Setting up Git - it keeps the history of your project safe.',\n [StepId.ProvisionXcodeClt]:\n 'macOS is installing some developer tools. A system dialog popped up: click Install. ' +\n 'This is completely normal and usually takes about 5 minutes. You can grab a coffee.',\n [StepId.ScaffoldGitInit]: 'Creating your project folder and starting its history.',\n [StepId.InstallBmad]:\n 'Installing BMad - the toolkit that powers your project. This is a sizeable download, ' +\n 'so it can take a couple of minutes. Nothing is stuck; hang tight.',\n [StepId.InstallAgentCli]:\n 'Installing your AI coding assistant so it’s ready to run right after setup.',\n [StepId.FinalizeSelfCheck]: 'Double-checking everything is in place.',\n};\n\n// Optional agent-CLI install copy (Story 6.1), one line per per-CLI outcome. `name` is the\n// friendly agent name (e.g. \"Claude Code\"); `manualInstall(pkg)` is the exact fallback command\n// shown when an install fails — the install is machine-global, so a manual run fixes it for good.\nexport const agentCliMessages = {\n working: (name: string): string => `Installing ${name} so you can start right away.`,\n done: (name: string): string => `${name} is installed and ready to run.`,\n skipped: (name: string): string => `${name} is already installed, reusing it.`,\n failed: (name: string): string =>\n `${name} didn’t finish installing - your project is still set up and ready. ` +\n `You can press Retry, or install it yourself later.`,\n manualInstall: (pkg: string): string => `To install it yourself later, run: npm install -g ${pkg}`,\n} as const;\n\n// Scaffold-step outcome copy (one message per terminal outcome — the step has several).\nexport const scaffoldMessages = {\n done: 'Your project folder is ready.',\n skipped: 'Project already set up, nothing to do.',\n blocked:\n 'This folder already has files in it. Kindling won’t change anything without your OK, ' +\n 'pick an empty folder, or confirm before continuing.',\n failed: 'Setting up your project folder ran into a problem. Check the details, then press Retry.',\n} as const;\n\n// Provisioning detection copy (per tool, by detected state).\nexport const provisionMessages = {\n nodePresent: 'Node is already installed, reusing it.',\n nodeQueued: 'Node needs setting up - the engine your project runs on.',\n gitPresent: 'Git is already installed, reusing it.',\n gitQueued: 'Git needs setting up - it keeps the history of your project.',\n gitInstalled: 'Git is set up.',\n xcodeWaiting:\n 'Still installing developer tools… the macOS dialog is doing its thing. This can take a few minutes, hang tight, nothing is stuck.',\n xcodeDone: 'Developer tools are ready - Git is set up.',\n xcodeTimeout:\n 'The developer-tools install is taking longer than expected. If the macOS dialog is still open, let it finish, then press Retry.',\n xcodeInstallFailed:\n 'We couldn’t start the developer-tools install. Make sure you’re connected, then press Retry.',\n gitInstallFailed:\n 'Setting up Git ran into a problem, it may need permission to install. Check the details, then press Retry.',\n} as const;\n\n// Post-install self-check step copy.\nexport const selfCheckMessages = {\n done: 'Everything checks out, you’re ready.',\n failed: 'Some checks didn’t pass, see the readiness details.',\n};\n\n// BMad install step copy.\nexport const installMessages = {\n notPinned: 'BMad isn’t pinned to a version yet, set the cohort version before installing.',\n done: (version: string): string => `BMad ${version} installed.`,\n};\n\n// Error copy: always plain-language, never blames the user, always implies a next step.\n// Keyed by the ErrorCode enum (not raw strings) so it stays in sync with the contract.\nexport const errorMessages: Record<ErrorCode, string> = {\n [ErrorCode.ExecFailed]: 'Something needs a quick fix: a step did not finish. Check the next step below, then press Retry.',\n [ErrorCode.NetworkLost]: 'We lost the connection. Reconnect to the internet, then press Retry.',\n [ErrorCode.BmadInstallFailed]: 'BMad didn’t finish installing. The details are below, press Retry.',\n [ErrorCode.AgentCliInstallFailed]:\n 'Your AI coding assistant didn’t finish installing - your project is still ready. Press Retry, or install it yourself later.',\n [ErrorCode.ExecPolicyBlocked]: 'Windows blocked the script because it’s unsigned, that’s expected, safe, and reversible.',\n [ErrorCode.SmartScreenBlocked]: 'Windows SmartScreen (or your antivirus) paused the script, that’s expected, safe, and reversible.',\n [ErrorCode.ProjectConflict]: 'That folder already has files in it that Kindling didn’t create.',\n};\n\n/**\n * Structured recovery guidance for the in-UI error screen (Story 3.6). The UI RENDERS this; it\n * never composes its own copy. Each entry names the cause and a concrete next step; some carry a\n * one-line fix command (shown with a copy button) or route to a non-destructive choice.\n * - `recovery: 'retry'` → show a Retry button (re-run the failed step).\n * - `recovery: 'choose-folder'` → no destructive default; let the user pick another folder (FR-9).\n */\nexport interface RecoveryGuidance {\n /** Short reassuring framing headline. */\n title: string;\n /** The cause + the concrete next step, plain language. */\n detail: string;\n /** Optional one-line command the user runs, then retries (e.g. the Windows policy fix). */\n fixCommand?: string;\n recovery: 'retry' | 'choose-folder';\n}\n\nexport const recoveryGuidance: Record<ErrorCode, RecoveryGuidance> = {\n [ErrorCode.ExecFailed]: {\n title: 'Something needs a quick fix.',\n detail: 'A step didn’t finish. This usually clears up on a second try, press Retry.',\n recovery: 'retry',\n },\n [ErrorCode.NetworkLost]: {\n title: 'We lost the connection.',\n detail:\n 'A download was interrupted. Reconnect to the internet, then press Retry, Kindling resumes where it left off.',\n recovery: 'retry',\n },\n [ErrorCode.BmadInstallFailed]: {\n title: 'BMad didn’t install.',\n detail:\n 'The install step didn’t finish. This is usually temporary, press Retry. The step details below show what happened.',\n recovery: 'retry',\n },\n [ErrorCode.AgentCliInstallFailed]: {\n // No `fixCommand` here: the exact package differs per CLI (claude-code vs codex), so the\n // concrete, copy-pasteable `npm install -g <package>` line ships in the failed step's own\n // message (agentCliMessages.manualInstall) rather than in this static, error-code-keyed entry.\n title: 'Your AI assistant didn’t install.',\n detail:\n 'Your project is set up and ready either way, this step is optional. Press Retry to try again, or install it yourself later using the command shown in the step details below.',\n recovery: 'retry',\n },\n [ErrorCode.ExecPolicyBlocked]: {\n title: 'Something needs a quick fix.',\n detail:\n 'Windows blocked the script because it’s unsigned, that’s expected, and it’s safe and reversible. Run this one line in PowerShell, then come back and press Retry:',\n fixCommand: 'Set-ExecutionPolicy -Scope Process Bypass',\n recovery: 'retry',\n },\n [ErrorCode.SmartScreenBlocked]: {\n title: 'Windows asked you to confirm.',\n detail:\n 'SmartScreen or your antivirus paused the script, this is expected and safe. Choose “More info”, then “Run anyway”, and press Retry. Nothing was changed on your computer.',\n recovery: 'retry',\n },\n [ErrorCode.ProjectConflict]: {\n title: 'That folder isn’t empty.',\n detail:\n 'It already contains files Kindling didn’t create, so we won’t touch them. Pick a different (empty or new) folder and we’ll set up there.',\n recovery: 'choose-folder',\n },\n};\n","import { mkdir, readdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport { exec } from '../exec';\nimport type { EngineEmitter } from '../emitter';\nimport { Phase, StepId, Status, ErrorCode, type Level } from '../contract';\nimport { stepMessages, scaffoldMessages } from '../messages';\n\nconst MARKER = '.kindling.json';\n\n// OS metadata files that don't make a directory \"non-empty\" from the user's perspective.\nconst OS_CRUFT = new Set(['.DS_Store', 'Thumbs.db']);\n\nexport type ScaffoldOutcome = 'created' | 'resumed' | 'skipped' | 'blocked';\n\nexport interface ScaffoldOptions {\n projectDir: string;\n projectName: string;\n emitter: EngineEmitter;\n /** Absolute path to the provisioned git (Epic 2); defaults to PATH lookup. */\n git?: string;\n /** Injectable clock for deterministic tests. */\n now?: () => string;\n}\n\n// A dir counts as \"Kindling's own\" (resumable, not a user project) if it holds only the\n// marker, a .git directory, and/or OS metadata files.\nfunction isTrivial(entry: string): boolean {\n return entry === MARKER || entry === '.git' || OS_CRUFT.has(entry);\n}\n\nexport async function scaffold(opts: ScaffoldOptions): Promise<ScaffoldOutcome> {\n const git = opts.git ?? 'git';\n const now = opts.now ?? (() => new Date().toISOString());\n\n const emit = (\n status: Status,\n humanMessage: string,\n level: Level = 'info',\n errorCode?: ErrorCode,\n ): void => {\n opts.emitter.emit({\n id: randomUUID(),\n phase: Phase.Scaffold,\n step: StepId.ScaffoldGitInit,\n status,\n humanMessage,\n level,\n timestamp: now(),\n errorCode,\n });\n };\n\n // Emit Working, do the work, emit a terminal Done — or emit Failed and rethrow so the step\n // always reaches a terminal status (the Epic 3 UI folds events into a per-step state machine).\n const doInit = async (): Promise<void> => {\n emit(Status.Working, stepMessages[StepId.ScaffoldGitInit]);\n try {\n await mkdir(opts.projectDir, { recursive: true });\n await initRepo(git, opts);\n } catch (err) {\n emit(Status.Failed, scaffoldMessages.failed, 'error', ErrorCode.ExecFailed);\n throw err;\n }\n emit(Status.Done, scaffoldMessages.done);\n };\n\n const entries = await readDirSafe(opts.projectDir);\n\n if (entries !== null) {\n const hasMarker = entries.includes(MARKER);\n const foreign = entries.filter((e) => !isTrivial(e));\n\n // Pre-existing, non-Kindling project: refuse to touch it (FR9 safety).\n if (!hasMarker && foreign.length > 0) {\n emit(Status.Failed, scaffoldMessages.blocked, 'error', ErrorCode.ProjectConflict);\n return 'blocked';\n }\n // Already fully scaffolded by Kindling → idempotent skip.\n if (hasMarker && (await hasCommit(git, opts.projectDir))) {\n emit(Status.Skipped, scaffoldMessages.skipped);\n return 'skipped';\n }\n // Marker present (or trivially-empty dir) but no commit yet → (re)build the repo.\n await doInit();\n return hasMarker ? 'resumed' : 'created';\n }\n\n // Fresh: directory does not exist (mkdir happens inside doInit).\n await doInit();\n return 'created';\n}\n\nasync function initRepo(git: string, opts: ScaffoldOptions): Promise<void> {\n await run(git, ['-C', opts.projectDir, 'init', '-b', 'main']);\n await writeFile(\n join(opts.projectDir, MARKER),\n JSON.stringify({ scaffoldedBy: 'kindling', project: opts.projectName }, null, 2) + '\\n',\n );\n await run(git, ['-C', opts.projectDir, 'add', '-A']);\n // This is Kindling's own initial commit: set identity inline (works without global git config),\n // and disable GPG signing + user hooks so a developer's global config can't break setup.\n await run(git, [\n '-C',\n opts.projectDir,\n '-c',\n 'user.name=Kindling',\n '-c',\n 'user.email=kindling@local',\n '-c',\n 'commit.gpgsign=false',\n '-c',\n 'core.hooksPath=',\n 'commit',\n '-m',\n 'Initial commit',\n ]);\n}\n\nasync function hasCommit(git: string, dir: string): Promise<boolean> {\n try {\n const r = await exec(git, ['-C', dir, 'rev-parse', '--verify', 'HEAD']);\n return r.code === 0;\n } catch {\n return false;\n }\n}\n\nasync function run(git: string, args: string[]): Promise<void> {\n const r = await exec(git, args);\n if (r.code !== 0) {\n throw new Error(`git ${args.join(' ')} failed (code ${r.code}): ${r.stderr.trim()}`);\n }\n}\n\n// readdir that returns null when the directory does not exist (vs. throwing).\nasync function readDirSafe(dir: string): Promise<string[] | null> {\n try {\n return await readdir(dir);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n}\n","import type { Config } from '../contract';\n\n// Composes the `bmad-method install` argument array from the user's config.\n// Pure (no I/O) so the exact flag contract is unit-testable and changeable in one place.\n//\n// [ASSUMPTION] Flag spelling/multiplicity follows the PRD's documented contract\n// (--directory, --modules, --tools, --pin, --yes, --set k=v). The exact `bmad-method` CLI\n// must be verified when the cohort `pins.bmad` version is frozen (currently a TODO).\nexport type InstallAction = 'install' | 'update';\n\n/**\n * Compose the `bmad-method install` argv. `action` (2.7) selects a fresh install vs a re-run:\n * on a project that already has a BMad install, pass `--action update` so the re-run updates in\n * place rather than erroring/duplicating; a fresh project omits it (defaults to install).\n */\nexport function composeInstallArgs(config: Config, action: InstallAction = 'install'): string[] {\n // Values are joined CSV for --modules/--tools, so a value containing a comma would split\n // into extra tokens. Reject it (defense-in-depth; the UI shouldn't allow it either).\n for (const value of [...config.modules, ...config.ides]) {\n if (value.includes(',')) {\n throw new Error(`module/IDE value may not contain a comma: \"${value}\"`);\n }\n }\n\n const args = ['install', '--yes', '--directory', config.projectDir];\n\n // Re-run on an existing install → update in place (verified flag: --action install|update).\n if (action === 'update') {\n args.push('--action', 'update');\n }\n\n if (config.modules.length > 0) {\n args.push('--modules', config.modules.join(','));\n }\n // NOTE: --tools is required by bmad-method for fresh non-interactive (--yes) installs; the\n // UI's IDE picker guarantees a non-empty selection before Start.\n if (config.ides.length > 0) {\n args.push('--tools', config.ides.join(','));\n }\n // The BMad *version* is pinned via the npx package spec (`bmad-method@<version>` in\n // bmad-install.ts) — verified against the bmad-method 6.9.0 CLI. The `--pin CODE=TAG` flag\n // is for pinning individual module tags (not the core version), so it is NOT emitted here.\n\n if (config.set) {\n // [ASSUMPTION] bmad-method splits `--set key=value` on the FIRST `=`, and `--pin` ordering\n // (appended after --modules/--tools) is positional-agnostic. Verify both when the real CLI\n // is pinned (pins.bmad TODO); composition is isolated here so spelling changes touch one file.\n for (const [key, value] of Object.entries(config.set)) {\n args.push('--set', `${key}=${value}`);\n }\n }\n return args;\n}\n","import { randomUUID } from 'node:crypto';\nimport { stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { exec as defaultExec, type ExecResult } from '../exec';\nimport type { EngineEmitter } from '../emitter';\nimport { Phase, StepId, Status, ErrorCode, type Level, type Config } from '../contract';\nimport { stepMessages, errorMessages, installMessages } from '../messages';\nimport { composeInstallArgs } from './flags';\n\n// Default \"is BMad already installed here?\" check — a `_bmad` DIRECTORY under the project (the\n// marker `bmad-method install` writes). Require it to be a directory, not just any fs entry, so\n// a stray `_bmad` file/symlink from another tool can't trigger a spurious --action update.\nexport async function defaultBmadInstalled(projectDir: string): Promise<boolean> {\n try {\n return (await stat(join(projectDir, '_bmad'))).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Result of an install attempt.\n *\n * Failure contract: a **non-zero exit** resolves with `{ ok: false }` (the caller must check\n * `.ok`); a **spawn error** (e.g. npx not found) emits `Failed` then *throws*. Either way a\n * terminal `Failed` event is emitted exactly once. (Note: `scaffold()` throws on a non-zero\n * exit — the orchestrator story must reconcile these two error models; see deferred-work.md.)\n *\n * Version target (Story 7.1 / FR27): `config.bmadTarget` selects the npx package tag. `'pinned'`\n * (default) is byte-for-byte the historical behavior — `bmad-method@<pins.bmad>` with\n * `--action install` (fresh) vs `--action update` (existing `_bmad`, Story 2.7). `'latest'` is the\n * explicit opt-in update variant — `bmad-method@latest` with `--action update` FORCED regardless of\n * the `_bmad`-present detection. `bmadVersion` in the result is the resolved TAG (`<pins.bmad>` or\n * the literal `'latest'`); the concrete version `@latest` resolves to is picked up by the\n * self-check's manifest read (FR26), not here.\n */\nexport interface BmadInstallResult {\n ok: boolean;\n bmadVersion: string;\n}\n\nexport interface BmadInstallOptions {\n config: Config;\n emitter: EngineEmitter;\n /** Injectable subprocess runner (tests pass a fake; never runs a real install). */\n exec?: (cmd: string, args: string[]) => Promise<ExecResult>;\n /**\n * Command used to invoke npx. Default 'npx' (macOS/Linux dev). On Windows this must be\n * the provisioned `node` + npm-cli.js (Epic 2) since `npx.cmd` can't spawn with shell:false\n * — see deferred-work.md.\n */\n npxCommand?: string;\n /**\n * Args prepended to the exec argv, before the `bmad-method@<tag>` spec. Default `[]`. On Windows\n * `npxCommand` is the provisioned `node` and this carries `[npxCliPath(node)]`, so the effective\n * invocation is `node npx-cli.js bmad-method@<tag> …` — the shell:false-safe equivalent of the\n * bare `npx.cmd` shim (which spawn can't find by bare name on Windows). Empty on macOS/Linux.\n */\n npxPrefixArgs?: string[];\n /**\n * Idempotent re-run (2.7): is BMad already installed in this project? When true, the install\n * runs with `--action update` (update in place) instead of a fresh install. Default detects a\n * `_bmad` dir under config.projectDir; injectable for tests.\n */\n bmadAlreadyInstalled?: (projectDir: string) => Promise<boolean>;\n now?: () => string;\n}\n\n// Runs `npx bmad-method@<pin> install …` with composed flags, emitting install.bmad events.\n// Never reports success on failure (FR10): non-zero exit or spawn error → Failed.\nexport async function runBmadInstall(opts: BmadInstallOptions): Promise<BmadInstallResult> {\n const exec = opts.exec ?? defaultExec;\n const npx = opts.npxCommand ?? 'npx';\n const now = opts.now ?? (() => new Date().toISOString());\n // Version target (7.1): default 'pinned'. 'latest' resolves the tag to `latest` and forces an\n // update; 'pinned' keeps the historical `<pins.bmad>` tag + install/update-by-detection behavior.\n const bmadTarget = opts.config.bmadTarget ?? 'pinned';\n const versionTag = bmadTarget === 'latest' ? 'latest' : opts.config.pins.bmad;\n\n const emit = (\n status: Status,\n humanMessage: string,\n level: Level = 'info',\n errorCode?: ErrorCode,\n ): void => {\n opts.emitter.emit({\n id: randomUUID(),\n phase: Phase.Install,\n step: StepId.InstallBmad,\n status,\n humanMessage,\n level,\n timestamp: now(),\n errorCode,\n });\n };\n\n emit(Status.Working, stepMessages[StepId.InstallBmad]);\n\n // Fail fast with a clear message if the cohort BMad version was never frozen (don't ship a\n // cryptic `npx bmad-method@0.0.0-TODO` 404). Pinned-target concern only — `latest` never\n // contains TODO.\n if (bmadTarget === 'pinned' && versionTag.includes('TODO')) {\n emit(Status.Failed, installMessages.notPinned, 'error', ErrorCode.BmadInstallFailed);\n return { ok: false, bmadVersion: versionTag };\n }\n\n let result: ExecResult;\n try {\n // Action selection: the `latest` opt-in FORCES `--action update` (you're deliberately moving\n // off the frozen pin). The default `pinned` path keeps 2.7 re-run idempotency: if BMad is\n // already installed here, update in place rather than a fresh install that would error/duplicate.\n let action: 'install' | 'update';\n if (bmadTarget === 'latest') {\n action = 'update';\n } else {\n const alreadyInstalled = opts.bmadAlreadyInstalled ?? defaultBmadInstalled;\n action = (await alreadyInstalled(opts.config.projectDir)) ? 'update' : 'install';\n }\n // Compose inside the try so a composition error (e.g. comma in a module name) still\n // reaches a terminal Failed event.\n // On Windows npxPrefixArgs is `[npxCliPath(node)]` and npx is the provisioned node, so the\n // effective invocation is `node npx-cli.js bmad-method@<tag> …` (shell:false-safe). Empty elsewhere.\n const args = [\n ...(opts.npxPrefixArgs ?? []),\n `bmad-method@${versionTag}`,\n ...composeInstallArgs(opts.config, action),\n ];\n result = await exec(npx, args);\n } catch (err) {\n // spawn error (e.g. npx not found) or composition error — surface a terminal failure with the\n // real error message appended (so a Windows ENOENT isn't a blank \"details below\"), then rethrow.\n const detail = err instanceof Error ? err.message : String(err);\n emit(\n Status.Failed,\n `${errorMessages[ErrorCode.BmadInstallFailed]} (${detail})`,\n 'error',\n ErrorCode.BmadInstallFailed,\n );\n throw err;\n }\n\n if (result.code !== 0) {\n // Surface a truncated tail of the child's stderr (fall back to stdout) so the failure carries\n // the actual npx/bmad error instead of a generic message with nothing behind it.\n const raw = (result.stderr.trim() ? result.stderr : result.stdout).trim();\n const detail = raw.slice(-600).trim();\n const humanMessage = detail\n ? `${errorMessages[ErrorCode.BmadInstallFailed]}\\n\\nDetails:\\n${detail}`\n : errorMessages[ErrorCode.BmadInstallFailed];\n emit(Status.Failed, humanMessage, 'error', ErrorCode.BmadInstallFailed);\n return { ok: false, bmadVersion: versionTag };\n }\n\n emit(Status.Done, installMessages.done(versionTag));\n return { ok: true, bmadVersion: versionTag };\n}\n","import { randomUUID } from 'node:crypto';\nimport { exec as defaultExec, type ExecResult } from '../exec';\nimport type { EngineEmitter } from '../emitter';\nimport { Phase, StepId, Status, ErrorCode, type Level, type Config } from '../contract';\nimport { agentCliMessages } from '../messages';\nimport { probeCliVersion } from '../probe';\n\n/**\n * Explicit id → npm package table. The scope guard: only these two picked-tool ids are eligible\n * for an agent-CLI install; every other `--tools` id (vscode, cursor, …) is ignored and stays\n * skill-files-only. Kept explicit and in one place so the mapping — and the DELIBERATE unpinned\n * (latest) package spelling — is one edit to change / verify against the registry.\n *\n * `[DECISION]` Unpinned (latest): everywhere else Kindling pins, but the agent CLIs are the\n * deliberate exception — they self-update, and pinning would strand cohort users on a stale\n * binary. So the pkg strings carry NO `@version`; `exec(npm, ['install','-g', pkg])` installs\n * latest.\n */\nexport const AGENT_CLI_TABLE: Record<string, { pkg: string; bin: string; name: string }> = {\n 'claude-code': { pkg: '@anthropic-ai/claude-code', bin: 'claude', name: 'Claude Code' },\n codex: { pkg: '@openai/codex', bin: 'codex', name: 'Codex' },\n};\n\n/** A resolved, eligible agent-CLI descriptor (the id folded into its table row). */\nexport interface AgentCliDescriptor {\n id: string;\n pkg: string;\n bin: string;\n name: string;\n}\n\n/**\n * SSOT accessor: map the requested `installCli` ids to their eligible descriptors, applying the\n * same scope guard + de-dupe as `installAgentCli`. The single source both the install step AND the\n * self-check (Story 6.2) read, so the id→{pkg,bin,name} mapping is never hard-coded twice. A\n * non-eligible id (vscode, cursor, …) is dropped; an empty/absent list yields `[]`.\n */\nexport function eligibleAgentClis(installCli: string[] | undefined): AgentCliDescriptor[] {\n return [...new Set((installCli ?? []).filter((id) => id in AGENT_CLI_TABLE))].map((id) => ({\n id,\n ...AGENT_CLI_TABLE[id],\n }));\n}\n\n/**\n * Outcome of the install-agent-cli step. `ok` reflects only that the step RAN TO COMPLETION —\n * it is INDEPENDENT of individual install failures (which land in `failed`). This is the step's\n * defining property versus every other engine step: it is NON-FATAL, so the engine reaches the\n * self-check / Welcome even if a CLI failed to install (AC-3). The failed ids are surfaced solely\n * as `Failed` events (keyed by ErrorCode.AgentCliInstallFailed) for the error surface / Story 6.2.\n */\nexport interface AgentCliResult {\n ok: boolean;\n installed: string[];\n skipped: string[];\n failed: string[];\n}\n\nexport interface AgentCliOptions {\n config: Config;\n emitter: EngineEmitter;\n /** Injectable subprocess runner (tests pass a fake; NEVER runs a real npm install). */\n exec?: (cmd: string, args: string[]) => Promise<ExecResult>;\n /**\n * Command used to invoke npm. Default 'npm' (macOS/Linux dev). On Windows this must be the\n * provisioned `node` + npm-cli.js (Epic 2) since `npm.cmd` can't spawn with shell:false — the\n * same absolute-node strategy `launch.ts` uses via `npxCliPath` (the npm equivalent is\n * `<dir>/node_modules/npm/bin/npm-cli.js`). Not over-plumbed in 6.1: like bmad-install's\n * `npxCommand`, the seam defaults to 'npm' and the rehearsal / Story 6.2 plugs the abs path in.\n */\n npmCommand?: string;\n /**\n * Args prepended to the exec argv, before `install -g <pkg>`. Default `[]`. On Windows `npmCommand`\n * is the provisioned `node` and this carries `[npmCliPath(node)]`, so the effective invocation is\n * `node npm-cli.js install -g <pkg>` — the shell:false-safe equivalent of the bare `npm.cmd` shim\n * (which spawn can't find by bare name on Windows). Empty on macOS/Linux. Mirrors bmad-install's\n * `npxPrefixArgs`.\n */\n npmPrefixArgs?: string[];\n /**\n * Whether the host is Windows. Default false. On Windows the idempotent-skip probe must run\n * `cmd /c <bin> --version` (via `probeCliVersion`) because a global `npm install -g` writes a\n * `claude.cmd` shim, not a bare `claude` executable, and `exec` uses `shell:false` — which Node\n * refuses to use for `.cmd`, so a bare-bin probe would wrongly report \"absent\" and reinstall on\n * every run. macOS/Linux keep the direct `<bin> --version`.\n */\n isWindows?: boolean;\n now?: () => string;\n}\n\n/**\n * Optionally installs the picked agent's CLI (Claude Code / Codex) globally at latest, AFTER the\n * BMad install and BEFORE the self-check. Mirrors `bmad-install.ts` (injectable `exec`/`now`, an\n * `npmCommand` seam) but with the opposite failure contract:\n *\n * - **Idempotent skip** (AC-2): probe `<bin> --version` first; if present, emit `Skipped` and do\n * not re-install. The install is machine-global (`npm -g`), so a second run / other project\n * simply skips — which is what makes repeated runs and multiple projects safe.\n * - **Scope guard** (AC-4): only ids in AGENT_CLI_TABLE install; others are ignored.\n * - **NON-fatal failure** (AC-3): a non-zero exit OR a caught spawn error emits ONE `Failed`\n * event (with the manual-install fallback) and CONTINUES to the next CLI — it NEVER throws.\n * A CLI-install failure must not invalidate the successful BMad install.\n *\n * Returns `{ ok: true, ... }` whenever the step ran to completion, regardless of individual\n * failures — the engine step returns `true` unconditionally off the back of this.\n */\nexport async function installAgentCli(opts: AgentCliOptions): Promise<AgentCliResult> {\n const exec = opts.exec ?? defaultExec;\n const npm = opts.npmCommand ?? 'npm';\n const isWindows = opts.isWindows ?? false;\n const now = opts.now ?? (() => new Date().toISOString());\n\n const emit = (\n status: Status,\n humanMessage: string,\n level: Level = 'info',\n errorCode?: ErrorCode,\n ): void => {\n opts.emitter.emit({\n id: randomUUID(),\n phase: Phase.Install,\n step: StepId.InstallAgentCli,\n status,\n humanMessage,\n level,\n timestamp: now(),\n errorCode,\n });\n };\n\n const installed: string[] = [];\n const skipped: string[] = [];\n const failed: string[] = [];\n\n // Scope guard: keep only eligible ids (AC-4), de-duped so a repeated id can't double-install.\n // Empty/absent installCli ⇒ no eligible ids ⇒ no events, no exec — a silent no-op unchanged.\n const eligible = eligibleAgentClis(opts.config.installCli);\n\n for (const { id, pkg, bin, name } of eligible) {\n\n // Idempotent skip: already present anywhere on the machine → don't reinstall (AC-2). Probe via\n // `probeCliVersion`, which on Windows runs `cmd /c <bin> --version` so the shim (claude.cmd) is\n // reachable (Node won't spawn `.cmd` with shell:false) — otherwise it reinstalls on every run.\n if ((await probeCliVersion(exec, bin, isWindows)) !== null) {\n emit(Status.Skipped, agentCliMessages.skipped(name));\n skipped.push(id);\n continue;\n }\n\n emit(Status.Working, agentCliMessages.working(name));\n\n // Non-fatal: capture BOTH the non-zero-exit and the spawn-error paths, emit a single Failed\n // event including the manual-install fallback, and continue to the next CLI (never throw).\n const failMessage = `${agentCliMessages.failed(name)} ${agentCliMessages.manualInstall(pkg)}`;\n try {\n // On Windows npmPrefixArgs is `[npmCliPath(node)]` and npm is the provisioned node, so the\n // effective invocation is `node npm-cli.js install -g <pkg>` (shell:false-safe). Empty elsewhere.\n const result = await exec(npm, [...(opts.npmPrefixArgs ?? []), 'install', '-g', pkg]); // NO @version — latest (deliberate)\n if (result.code === 0) {\n emit(Status.Done, agentCliMessages.done(name));\n installed.push(id);\n } else {\n // Surface a truncated tail of the child's stderr (fall back to stdout) so the failure carries\n // the actual npm error instead of only the generic message + manual-install fallback.\n const raw = (result.stderr.trim() ? result.stderr : result.stdout).trim();\n const detail = raw.slice(-600).trim();\n emit(\n Status.Failed,\n detail ? `${failMessage}\\n\\nDetails:\\n${detail}` : failMessage,\n 'error',\n ErrorCode.AgentCliInstallFailed,\n );\n failed.push(id);\n }\n } catch (err) {\n // spawn error (e.g. npm not found by bare name on Windows) — append the real error message\n // so it isn't a blank failure.\n const detail = err instanceof Error ? err.message : String(err);\n emit(\n Status.Failed,\n `${failMessage} (${detail})`,\n 'error',\n ErrorCode.AgentCliInstallFailed,\n );\n failed.push(id);\n }\n }\n\n // ok = \"the step ran to completion\", INDEPENDENT of individual failures (AC-3).\n return { ok: true, installed, skipped, failed };\n}\n","import type { ExecResult } from './exec';\n\nexport type Exec = (cmd: string, args: string[]) => Promise<ExecResult>;\n\n// Shared \"is this tool present, and what version?\" probe. SSOT for the \"present\" definition:\n// the tool counts as present only if `<cmd> --version` exits 0 AND prints non-empty output\n// (stderr used as a fallback for tools that print there). Returns null when absent/unspawnable.\nexport async function probeVersion(exec: Exec, cmd: string): Promise<string | null> {\n try {\n const r = await exec(cmd, ['--version']);\n const out = r.stdout.trim() || r.stderr.trim();\n return r.code === 0 && out ? out : null;\n } catch {\n return null;\n }\n}\n\n// Present-and-version probe for an agent CLI bin (claude/codex) — the Windows-aware sibling of\n// `probeVersion`. On Windows an `npm install -g` lays down a `claude.cmd` shim, and `exec` spawns\n// with `shell:false`, which Node refuses to use for `.cmd`/`.bat`; a bare-bin probe would then\n// always fail and wrongly report the CLI \"absent\". `cmd.exe` IS a real executable that spawns fine,\n// so we run `cmd /c <bin> --version` to reach the shim. macOS/Linux keep the direct `<bin> --version`.\n// \"Present\" is defined identically to `probeVersion`: exit 0 AND non-empty stdout-or-stderr; catch → null.\nexport async function probeCliVersion(\n exec: Exec,\n bin: string,\n isWindows: boolean,\n): Promise<string | null> {\n try {\n const r = isWindows\n ? await exec('cmd', ['/c', bin, '--version'])\n : await exec(bin, ['--version']);\n const out = r.stdout.trim() || r.stderr.trim();\n return r.code === 0 && out ? out : null;\n } catch {\n return null;\n }\n}\n","import { dirname, join } from 'node:path';\n\nexport interface LaunchRuntime {\n /** Absolute path to the provisioned node binary, or null to use `node`/`npx` on PATH\n * (a reused system Node, or nvm sourced same-shell). See ProvisionResult.nodeExe. */\n nodeExe: string | null;\n /** Pinned Kindling version (pins.kindling). */\n kindlingVersion: string;\n}\n\nexport interface LaunchCommand {\n cmd: string;\n args: string[];\n}\n\n/** Path to npx's CLI script beside a provisioned node binary (the npm bundled with the dist). */\nexport function npxCliPath(nodeExe: string): string {\n return join(dirname(nodeExe), 'node_modules', 'npm', 'bin', 'npx-cli.js');\n}\n\n/** Path to npm's CLI script beside a provisioned node binary (the npm bundled with the dist). */\nexport function npmCliPath(nodeExe: string): string {\n return join(dirname(nodeExe), 'node_modules', 'npm', 'bin', 'npm-cli.js');\n}\n\n/**\n * Compose the command to launch Kindling once Node is provisioned (Story 2.6). The clean-runtime\n * rule (AR6): never depend on a freshly-mutated PATH —\n * - `nodeExe === null` → Node is already on PATH (nvm sourced same-shell, or a reused system\n * Node) → plain `npx -y @aiviatic/kindling@<pin>`.\n * - `nodeExe` set (Windows portable, absolute) → invoke npx's CLI through that exact node binary,\n * so the launch works without the portable Node ever being on PATH.\n * Pure — the bootstrap (or a future Windows launcher) spawns the returned command.\n */\nexport function composeLaunchCommand({ nodeExe, kindlingVersion }: LaunchRuntime): LaunchCommand {\n const spec = `@aiviatic/kindling@${kindlingVersion}`;\n if (nodeExe === null) {\n return { cmd: 'npx', args: ['-y', spec] };\n }\n return { cmd: nodeExe, args: [npxCliPath(nodeExe), '-y', spec] };\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { load } from 'js-yaml';\n\n/**\n * Read the ACTUAL installed BMad version from a project's manifest (FR26).\n *\n * Reads `<projectDir>/_bmad/_config/manifest.yaml` and returns `installation.version` as a string,\n * or `null` on ANY of: file absent, YAML parse error, a non-object document, or a\n * missing/non-string `installation.version`. NEVER throws — it mirrors the tolerance of\n * `probeVersion`/`defaultBmadInstalled`: a \"couldn't read disk reality\" degrades to `null`\n * (honest not-ready on the Validation Page), never a crash or a false pin.\n *\n * Node-side only (uses `node:fs/promises` + `js-yaml`). The PURE summary module\n * (`validation-summary.ts`) must not import this — the browser imports that module.\n */\nexport async function readInstalledBmadVersion(projectDir: string): Promise<string | null> {\n try {\n const raw = await readFile(join(projectDir, '_bmad', '_config', 'manifest.yaml'), 'utf8');\n const doc = load(raw);\n if (typeof doc !== 'object' || doc === null) return null;\n const installation = (doc as { installation?: unknown }).installation;\n if (typeof installation !== 'object' || installation === null) return null;\n const version = (installation as { version?: unknown }).version;\n return typeof version === 'string' ? version : null;\n } catch {\n return null;\n }\n}\n","// The Validation Summary: the structured, copyable proof a successful install produces (FR21).\n// The Epic 4 Validation Page checks this client-side against the cohort's expected pin.\n// Bump SCHEMA_VERSION on any field change.\n\n// v2 (Story 6.2): added the non-blocking `cli` presence field (FR24). A v1 summary pasted into a\n// v2 Validation Page reads as `malformed` (the cohort is a single frozen build, so this is safe).\n// v3 (Story 7.1): added `bmad.installedVersion` — the ACTUAL on-disk BMad version read from the\n// manifest (FR26), distinct from the requested `bmad.pinnedVersion`. Same single-frozen-build\n// safety: a v2 summary pasted into a v3 Validation Page reads as `malformed`.\nexport const SCHEMA_VERSION = 3;\n\n/** Node runtime floor (BMad's hard requirement). */\nexport const NODE_FLOOR_MAJOR = 20;\n\n/**\n * Presence of one requested agent CLI in the final self-check (FR24). Self-describing — it carries\n * `name`/`bin`/`pkg` so the browser Validation Page and both Welcome renderers render copy WITHOUT\n * re-importing the engine's `AGENT_CLI_TABLE`. `pkg` lets the \"install-it-yourself\" notice (AC-8)\n * show the exact `npm install -g <pkg>` command for an absent CLI. NON-blocking: this never feeds\n * `success` (AC-6). Empty list when no CLI was requested (present for schema stability).\n */\nexport interface CliPresence {\n id: string;\n name: string;\n bin: string;\n pkg: string;\n present: boolean;\n}\n\nexport interface ValidationSummary {\n schemaVersion: number;\n kindlingVersion: string;\n os: string;\n arch: string;\n osVersion: string;\n /** The project directory the setup targeted — surfaced on the Welcome screen as \"where is my project\". */\n projectDir: string;\n node: { present: boolean; version: string | null; satisfiesFloor: boolean };\n git: { present: boolean; version: string | null };\n /**\n * `pinnedVersion` = the REQUESTED cohort pin (`pins.bmad`); `installed` = the `_bmad` dir is\n * present; `installedVersion` (FR26) = the ACTUAL version read from the manifest — `null` when\n * the manifest is absent/unreadable/unparseable (honest \"couldn't read disk reality\", never a\n * false pin). The Validation Page (Story 7.1) compares `installedVersion` against the expected pin.\n */\n bmad: { pinnedVersion: string; installed: boolean; installedVersion: string | null };\n scaffold: { created: boolean };\n /** Requested agent CLIs + their presence (FR24). NON-blocking — never part of `success`. */\n cli: CliPresence[];\n success: boolean;\n generatedAt: string;\n}\n\nexport interface ValidationFacts {\n kindlingVersion: string;\n os: string;\n arch: string;\n osVersion: string;\n projectDir: string;\n node: { present: boolean; version: string | null; satisfiesFloor: boolean };\n git: { present: boolean; version: string | null };\n bmad: { pinnedVersion: string; installed: boolean; installedVersion: string | null };\n scaffold: { created: boolean };\n cli: CliPresence[];\n generatedAt: string;\n}\n\n// Pure. `success` is a strict AND of the real checks — no false green (FR21 / SM-C1). The `cli`\n// field is DELIBERATELY excluded from `success` (FR24 / AC-6): a CLI that didn't install is\n// reported but never flips the verdict.\nexport function buildValidationSummary(facts: ValidationFacts): ValidationSummary {\n const success =\n facts.node.present &&\n facts.node.satisfiesFloor &&\n facts.git.present &&\n facts.bmad.installed &&\n facts.scaffold.created;\n\n return {\n schemaVersion: SCHEMA_VERSION,\n kindlingVersion: facts.kindlingVersion,\n os: facts.os,\n arch: facts.arch,\n osVersion: facts.osVersion,\n projectDir: facts.projectDir,\n node: facts.node,\n git: facts.git,\n bmad: facts.bmad,\n scaffold: facts.scaffold,\n cli: facts.cli,\n success,\n generatedAt: facts.generatedAt,\n };\n}\n\n/**\n * Runtime shape guard for one `cli` element. The Validation Page parses an UNTRUSTED pasted string\n * whose `cli` can be any JSON (e.g. an adversarial `\"cli\":[null]`) — `looksLikeSummary` never\n * inspects `cli` element shape — so the presence helpers below must not assume it. Dropping a\n * malformed element (rather than throwing) keeps the public page and the Welcome render resilient.\n */\nexport function isCliPresence(c: unknown): c is CliPresence {\n return (\n typeof c === 'object' &&\n c !== null &&\n typeof (c as CliPresence).name === 'string' &&\n typeof (c as CliPresence).bin === 'string' &&\n typeof (c as CliPresence).pkg === 'string' &&\n typeof (c as CliPresence).present === 'boolean'\n );\n}\n\n/**\n * The requested CLIs that ARE present after the run — drives the FR25 login guidance (\"run `claude`\n * and log in\"). Pure + shared so the React Welcome and the static welcome.html render identical copy\n * from one source. Tolerant of a missing/legacy `cli` field or a malformed element (returns []/skips).\n */\nexport function cliLoginGuidance(summary: Pick<ValidationSummary, 'cli'>): CliPresence[] {\n return (summary.cli ?? []).filter(isCliPresence).filter((c) => c.present);\n}\n\n/**\n * The requested CLIs that are ABSENT after the run — drives the calm, non-blocking \"didn't finish\n * installing — here's the one line to install it, then log in\" notice (AC-8). Order-robust: derived\n * from the summary's actual presence, not the transient step row. Skips malformed elements.\n */\nexport function cliMissing(summary: Pick<ValidationSummary, 'cli'>): CliPresence[] {\n return (summary.cli ?? []).filter(isCliPresence).filter((c) => !c.present);\n}\n\n/**\n * The honest BMad version chip (Story 7.2 / FR26). Pure + shared so the React Welcome and the static\n * welcome.html render identical copy from ONE source. When the summary carries an `installedVersion`\n * (7.1's manifest read) that is a non-empty string DIFFERING from the pinned fallback, the run was an\n * update-to-latest, so the pinned \"a stable, tested version\" chip would be a false statement; show\n * the real installed version + \"updated to latest\". Otherwise (absent/null/equal, the default run,\n * unchanged) fall back to the pinned chip. Never blank, never a crash. Node-free (browser-safe).\n */\nexport function bmadVersionLabel(\n summary: { bmad?: { installedVersion?: string | null } } | null | undefined,\n pinnedFallback: string,\n): { version: string; note: string } {\n const installed = summary?.bmad?.installedVersion;\n if (typeof installed === 'string' && installed.length > 0 && installed !== pinnedFallback) {\n return { version: installed, note: 'updated to latest' };\n }\n return { version: pinnedFallback, note: 'a stable, tested version' };\n}\n\n/**\n * Parse a major version from version strings: `v24.16.0`, `24.16.0`, `v20.0`, `v20`,\n * `git version 2.43.0.windows.1`, `v24.0.0-nightly…`. Null if no leading integer is found.\n */\nexport function parseMajor(version: string | null): number | null {\n if (!version) return null;\n const m = /(\\d+)(?:\\.\\d+){0,2}/.exec(version);\n return m ? Number(m[1]) : null;\n}\n","import { release } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { exec as defaultExec, type ExecResult } from './exec';\nimport { probeVersion, probeCliVersion } from './probe';\nimport { readInstalledBmadVersion as defaultReadInstalledBmadVersion } from './bmad-manifest';\nimport type { EngineEmitter } from './emitter';\nimport { Phase, StepId, Status, ErrorCode, type Level } from './contract';\nimport { pins } from './pins';\nimport { stepMessages, selfCheckMessages } from './messages';\nimport {\n buildValidationSummary,\n parseMajor,\n NODE_FLOOR_MAJOR,\n type ValidationSummary,\n type CliPresence,\n} from './validation-summary';\nimport type { AgentCliDescriptor } from './orchestrate/agent-cli';\n\nexport interface SelfCheckOptions {\n /** Outcomes threaded from prior steps (Stories 1.4 / 1.5). */\n scaffoldCreated: boolean;\n bmadInstalled: boolean;\n /** Project directory — where the BMad manifest lives (`<projectDir>/_bmad/_config/manifest.yaml`). */\n projectDir: string;\n emitter: EngineEmitter;\n /** Provisioned binaries (Epic 2); default PATH lookup. */\n node?: string;\n git?: string;\n /**\n * The requested eligible agent CLIs (Story 6.2), threaded from the engine's install-agent-cli\n * step. Each is probed for presence and reported in the summary's non-blocking `cli` field.\n * Absent/empty ⇒ `cli: []`.\n */\n agentClis?: AgentCliDescriptor[];\n exec?: (cmd: string, args: string[]) => Promise<ExecResult>;\n now?: () => string;\n /**\n * Read the ACTUAL installed BMad version from the project manifest (FR26). Injectable so unit\n * tests never touch a real fs/manifest (mirrors the `bmadAlreadyInstalled`/`exec` seams).\n * Default: the node-side `readInstalledBmadVersion` helper. Returns `null` on any read/parse\n * failure — reported as `bmad.installedVersion: null`, never a false pin.\n */\n readInstalledBmadVersion?: (projectDir: string) => Promise<string | null>;\n /** Platform facts (injectable for tests); default the running process. */\n platform?: { os: string; arch: string; osVersion: string };\n}\n\n// Verifies Node ≥ floor, Git present, scaffold + BMad done; emits finalize.self-check and\n// returns the Validation Summary. `success` is strict — a \"green\" summary means a real setup.\nexport async function runSelfCheck(opts: SelfCheckOptions): Promise<ValidationSummary> {\n const exec = opts.exec ?? defaultExec;\n const now = opts.now ?? (() => new Date().toISOString());\n const readInstalledBmadVersion =\n opts.readInstalledBmadVersion ?? defaultReadInstalledBmadVersion;\n const platform = opts.platform ?? {\n os: process.platform,\n arch: process.arch,\n osVersion: release(),\n };\n\n const emit = (\n status: Status,\n humanMessage: string,\n level: Level = 'info',\n errorCode?: ErrorCode,\n summaryJson?: string,\n ): void => {\n opts.emitter.emit({\n id: randomUUID(),\n phase: Phase.Finalize,\n step: StepId.FinalizeSelfCheck,\n status,\n humanMessage,\n level,\n timestamp: now(),\n errorCode,\n summaryJson,\n });\n };\n\n // Stamp the summary once so generatedAt doesn't drift across the probe awaits / events.\n const generatedAt = now();\n\n emit(Status.Working, stepMessages[StepId.FinalizeSelfCheck]);\n\n const nodeVersion = await probeVersion(exec, opts.node ?? 'node');\n const gitVersion = await probeVersion(exec, opts.git ?? 'git');\n const nodeMajor = parseMajor(nodeVersion);\n\n // Probe each requested eligible CLI for presence. Node/git use `probeVersion`; the agent CLIs use\n // `probeCliVersion`, which on Windows runs `cmd /c <bin> --version` so the `claude.cmd` shim (which\n // Node won't spawn with shell:false) is reachable — a bare-bin probe there would wrongly report\n // \"absent\". Non-blocking (AC-6): the result feeds only the `cli` field, never `success`.\n const isWindows = platform.os === 'win32';\n const cli: CliPresence[] = [];\n for (const c of opts.agentClis ?? []) {\n const present = (await probeCliVersion(exec, c.bin, isWindows)) !== null;\n cli.push({ id: c.id, name: c.name, bin: c.bin, pkg: c.pkg, present });\n }\n\n // Read disk reality (FR26): the ACTUAL installed BMad version from the manifest. `null` on any\n // absent/unreadable/unparseable manifest — reported honestly, never gated into `success` here.\n const installedVersion = await readInstalledBmadVersion(opts.projectDir);\n\n const summary = buildValidationSummary({\n kindlingVersion: pins.kindling,\n os: platform.os,\n arch: platform.arch,\n osVersion: platform.osVersion,\n projectDir: opts.projectDir,\n node: {\n present: nodeVersion !== null,\n version: nodeVersion,\n satisfiesFloor: nodeMajor !== null && nodeMajor >= NODE_FLOOR_MAJOR,\n },\n git: { present: gitVersion !== null, version: gitVersion },\n bmad: { pinnedVersion: pins.bmad, installed: opts.bmadInstalled, installedVersion },\n scaffold: { created: opts.scaffoldCreated },\n cli,\n generatedAt,\n });\n\n if (summary.success) {\n // Carry the Validation Summary JSON on the success event so the Welcome screen can offer a\n // one-click copy (the exact text the Validation Page expects).\n emit(Status.Done, selfCheckMessages.done, 'info', undefined, JSON.stringify(summary));\n } else {\n emit(Status.Failed, selfCheckMessages.failed, 'error', ErrorCode.ExecFailed);\n }\n\n return summary;\n}\n","import { randomUUID } from 'node:crypto';\nimport { exec as defaultExec, type ExecResult } from '../exec';\nimport { probeVersion } from '../probe';\nimport type { EngineEmitter } from '../emitter';\nimport { Phase, StepId, Status, type Level } from '../contract';\nimport { provisionMessages } from '../messages';\nimport { parseMajor, NODE_FLOOR_MAJOR } from '../validation-summary';\n\nexport interface ToolState {\n present: boolean;\n version: string | null;\n}\nexport interface NodeState extends ToolState {\n satisfiesFloor: boolean;\n}\nexport interface DependencyState {\n node: NodeState;\n git: ToolState;\n}\n\nexport interface DetectOptions {\n exec?: (cmd: string, args: string[]) => Promise<ExecResult>;\n node?: string;\n git?: string;\n emitter?: EngineEmitter;\n now?: () => string;\n}\n\n// Detects Git + Node (and whether Node meets the floor) so provisioners can skip vs install.\n// When an emitter is passed, reports each tool's state: reuse → Skipped, needs-install → Queued.\nexport async function detectDependencies(opts: DetectOptions = {}): Promise<DependencyState> {\n const exec = opts.exec ?? defaultExec;\n const now = opts.now ?? (() => new Date().toISOString());\n\n const nodeVersion = await probeVersion(exec, opts.node ?? 'node');\n const nodeMajor = parseMajor(nodeVersion);\n const node: NodeState = {\n present: nodeVersion !== null,\n version: nodeVersion,\n satisfiesFloor: nodeMajor !== null && nodeMajor >= NODE_FLOOR_MAJOR,\n };\n\n const gitVersion = await probeVersion(exec, opts.git ?? 'git');\n const git: ToolState = { present: gitVersion !== null, version: gitVersion };\n\n const emitter = opts.emitter;\n if (emitter) {\n const emit = (step: StepId, status: Status, humanMessage: string, level: Level = 'info'): void => {\n emitter.emit({\n id: randomUUID(),\n phase: Phase.Provision,\n step,\n status,\n humanMessage,\n level,\n timestamp: now(),\n });\n };\n // Node reuse requires meeting the floor; an old Node must be upgraded.\n if (node.present && node.satisfiesFloor) {\n emit(StepId.ProvisionNode, Status.Skipped, provisionMessages.nodePresent);\n } else {\n emit(StepId.ProvisionNode, Status.Queued, provisionMessages.nodeQueued);\n }\n if (git.present) {\n emit(StepId.ProvisionGit, Status.Skipped, provisionMessages.gitPresent);\n } else {\n emit(StepId.ProvisionGit, Status.Queued, provisionMessages.gitQueued);\n }\n }\n\n return { node, git };\n}\n","import { randomUUID } from 'node:crypto';\nimport { exec as defaultExec, type ExecResult } from '../exec';\nimport type { EngineEmitter } from '../emitter';\nimport { Phase, StepId, Status, ErrorCode, type Level } from '../contract';\nimport { stepMessages, provisionMessages } from '../messages';\n\nexport interface ProvisionGitUnixOptions {\n platform: NodeJS.Platform;\n emitter: EngineEmitter;\n /** From 2.1 detection: Git is already present → reuse it (Skipped). */\n alreadyOk?: boolean;\n // Injectable side effects (real shell run is rehearsal-bound; tests inject these):\n /** macOS: trigger the Xcode Command Line Tools install (opens the system dialog). */\n triggerXcodeInstall?: () => Promise<void>;\n /** macOS: true once `xcode-select -p` resolves (CLT — hence Git — installed). */\n checkXcode?: () => Promise<boolean>;\n /** Linux: install Git via the distro package manager (apt primary). */\n installLinuxGit?: () => Promise<void>;\n /** Poll cadence + cap for the Xcode wait (injected so tests don't actually wait). */\n sleep?: (ms: number) => Promise<void>;\n pollIntervalMs?: number;\n maxPolls?: number;\n exec?: (cmd: string, args: string[]) => Promise<ExecResult>;\n now?: () => string;\n}\n\nexport interface ProvisionGitResult {\n ok: boolean;\n}\n\nfunction makeDefaults(exec: (cmd: string, args: string[]) => Promise<ExecResult>) {\n return {\n triggerXcodeInstall: async (): Promise<void> => {\n // `xcode-select --install` returns non-zero if the tools are already installed; that's not\n // an error for us (the alreadyOk guard covers the present case, and the poll confirms).\n await exec('xcode-select', ['--install']);\n },\n checkXcode: async (): Promise<boolean> => (await exec('xcode-select', ['-p'])).code === 0,\n installLinuxGit: async (): Promise<void> => {\n // apt primary (Debian/Ubuntu — the best-effort Linux target). `sudo -n` is non-interactive:\n // with no cached creds it fails fast (clear error) instead of hanging on a password prompt\n // we can't answer (exec's stdin is /dev/null). Real run rehearsal-bound.\n const r = await exec('sudo', ['-n', 'apt-get', 'install', '-y', 'git']);\n if (r.code !== 0) throw new Error(`apt-get install git failed (code ${r.code}): ${r.stderr.trim()}`);\n },\n };\n}\n\n/**\n * Provision Git on macOS (via the Xcode Command Line Tools) or Linux (via apt). macOS triggers\n * the CLT install early and POLLS `xcode-select -p`, emitting a Working event on every tick so the\n * un-silenceable Apple dialog never looks frozen (AC1/AC2). Git already present → Skipped (AC).\n *\n * The real `xcode-select`/apt run is validated at the dress rehearsal; here the shell effects +\n * the clock are injected and the orchestration/poll/events/skip/fail paths are unit-tested.\n */\nexport async function provisionGitUnix(opts: ProvisionGitUnixOptions): Promise<ProvisionGitResult> {\n const exec = opts.exec ?? defaultExec;\n const d = makeDefaults(exec);\n const triggerXcodeInstall = opts.triggerXcodeInstall ?? d.triggerXcodeInstall;\n const checkXcode = opts.checkXcode ?? d.checkXcode;\n const installLinuxGit = opts.installLinuxGit ?? d.installLinuxGit;\n const sleep = opts.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n const pollIntervalMs = opts.pollIntervalMs ?? 3000;\n const maxPolls = opts.maxPolls ?? 200; // ~10 min at 3s — the dialog can be slow; never frozen\n const now = opts.now ?? (() => new Date().toISOString());\n const isMac = opts.platform === 'darwin';\n const step = isMac ? StepId.ProvisionXcodeClt : StepId.ProvisionGit;\n\n const emit = (\n status: Status,\n humanMessage: string,\n level: Level = 'info',\n errorCode?: ErrorCode,\n ): void => {\n opts.emitter.emit({\n id: randomUUID(),\n phase: Phase.Provision,\n step,\n status,\n humanMessage,\n level,\n timestamp: now(),\n errorCode,\n });\n };\n\n if (opts.alreadyOk) {\n // On macOS the row is the Xcode-CLT step, so use CLT-flavored copy (not \"Git is installed\").\n emit(Status.Skipped, isMac ? provisionMessages.xcodeDone : provisionMessages.gitPresent);\n return { ok: true };\n }\n\n if (isMac) {\n emit(Status.Working, stepMessages[StepId.ProvisionXcodeClt]); // the \"dialog popped up, ~5 min\" copy\n try {\n await triggerXcodeInstall();\n } catch (err) {\n emit(Status.Failed, provisionMessages.xcodeInstallFailed, 'error', ErrorCode.ExecFailed);\n throw err;\n }\n // Poll until the CLT resolve — emitting Working each tick so the UI never looks frozen.\n for (let i = 0; i < maxPolls; i++) {\n if (await checkXcode()) {\n emit(Status.Done, provisionMessages.xcodeDone);\n return { ok: true };\n }\n emit(Status.Working, provisionMessages.xcodeWaiting);\n if (i < maxPolls - 1) await sleep(pollIntervalMs); // no wasted wait before the timeout\n }\n // Soft timeout — not a throw; the dialog may still be open, so Retry re-polls.\n emit(Status.Failed, provisionMessages.xcodeTimeout, 'error', ErrorCode.ExecFailed);\n return { ok: false };\n }\n\n // Linux: install via the distro package manager.\n emit(Status.Working, stepMessages[StepId.ProvisionGit]);\n try {\n await installLinuxGit();\n } catch (err) {\n emit(Status.Failed, provisionMessages.gitInstallFailed, 'error', ErrorCode.ExecFailed);\n throw err;\n }\n emit(Status.Done, provisionMessages.gitInstalled);\n return { ok: true };\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport type { KindlingEvent, StepId } from './contract';\n\n// Where failure logs live: %LOCALAPPDATA%\\kindling\\logs on Windows, else ~/.kindling/logs.\nexport function defaultLogDir(): string {\n if (process.platform === 'win32' && process.env.LOCALAPPDATA) {\n return join(process.env.LOCALAPPDATA, 'kindling', 'logs');\n }\n return join(homedir(), '.kindling', 'logs');\n}\n\nexport interface FailureLogEntry {\n step: StepId;\n error: string;\n events: readonly KindlingEvent[];\n}\n\nexport interface WriteLogOptions {\n dir?: string;\n now?: () => string;\n}\n\nfunction render(entry: FailureLogEntry, timestamp: string): string {\n const lines: string[] = [\n `Kindling failure report`,\n `Generated: ${timestamp}`,\n `Failed step: ${entry.step}`,\n ``,\n `Error:`,\n entry.error,\n ``,\n `Event log:`,\n ...entry.events.map((e) => ` [${e.status}] ${e.step} - ${e.humanMessage}`),\n ``,\n ];\n return lines.join('\\n');\n}\n\n/**\n * Writes a local, human-readable failure report and returns its path. Local-only (no network).\n * Deliberately does NOT include the config object, environment variables, or any credentials\n * (NFR7) — just the failed step, the error message, and the event log.\n */\nexport async function writeFailureLog(entry: FailureLogEntry, opts: WriteLogOptions = {}): Promise<string> {\n const dir = opts.dir ?? defaultLogDir();\n const timestamp = (opts.now ?? (() => new Date().toISOString()))();\n const safeStamp = timestamp.replace(/[:.]/g, '-');\n // Short random suffix so two failures in the same millisecond don't overwrite each other.\n const suffix = randomUUID().slice(0, 8);\n await mkdir(dir, { recursive: true });\n const path = join(dir, `kindling-report-${safeStamp}-${suffix}.log`);\n await writeFile(path, render(entry, timestamp));\n return path;\n}\n","import { randomUUID } from 'node:crypto';\nimport type { EngineEmitter } from './emitter';\nimport { Phase, StepId, Status, ErrorCode, type Config, type EngineCommands, type Level } from './contract';\nimport { scaffold as defaultScaffold, type ScaffoldOptions, type ScaffoldOutcome } from './orchestrate/scaffold';\nimport { runBmadInstall as defaultRunBmadInstall, type BmadInstallOptions, type BmadInstallResult } from './orchestrate/bmad-install';\nimport { installAgentCli as defaultInstallAgentCli, eligibleAgentClis, type AgentCliOptions, type AgentCliResult } from './orchestrate/agent-cli';\nimport { npxCliPath, npmCliPath } from './orchestrate/launch';\nimport { runSelfCheck as defaultRunSelfCheck, type SelfCheckOptions } from './self-check';\nimport { detectDependencies as defaultDetect, type DetectOptions, type DependencyState } from './provision/detect';\nimport { provisionGitUnix as defaultProvisionGit, type ProvisionGitUnixOptions, type ProvisionGitResult } from './provision/git-unix';\nimport { provisionMessages } from './messages';\nimport type { ValidationSummary } from './validation-summary';\nimport { writeFailureLog as defaultWriteFailureLog, type FailureLogEntry } from './log';\nimport { expandTilde } from './expand-tilde';\n\n// Injectable step implementations so the orchestration logic is unit-testable without real\n// git/npx/shell. Defaults are the real engine functions.\nexport interface EngineDeps {\n detect: (opts: DetectOptions) => Promise<DependencyState>;\n provisionGit: (opts: ProvisionGitUnixOptions) => Promise<ProvisionGitResult>;\n scaffold: (opts: ScaffoldOptions) => Promise<ScaffoldOutcome>;\n runBmadInstall: (opts: BmadInstallOptions) => Promise<BmadInstallResult>;\n installAgentCli: (opts: AgentCliOptions) => Promise<AgentCliResult>;\n runSelfCheck: (opts: SelfCheckOptions) => Promise<ValidationSummary>;\n writeFailureLog: (entry: FailureLogEntry) => Promise<string>;\n /** Host platform — selects the Git provisioning step id (xcode-clt on macOS, else git). */\n platform: NodeJS.Platform;\n}\n\nconst defaultDeps: EngineDeps = {\n detect: defaultDetect,\n provisionGit: defaultProvisionGit,\n scaffold: defaultScaffold,\n runBmadInstall: defaultRunBmadInstall,\n installAgentCli: defaultInstallAgentCli,\n runSelfCheck: defaultRunSelfCheck,\n writeFailureLog: (entry) => defaultWriteFailureLog(entry),\n platform: process.platform,\n};\n\ninterface Step {\n id: StepId;\n run: () => Promise<boolean>; // emits its own events; returns success\n}\n\nexport interface EngineRunResult {\n ok: boolean;\n failedStep: StepId | null;\n summary: ValidationSummary | null;\n logPath: string | null;\n}\n\n/**\n * Orchestrates the full step sequence — provision Node (verify) → provision Git/Xcode →\n * scaffold → install → install-agent-cli (optional, non-fatal) → self-check — tracking which\n * steps completed so retry() can resume without re-running done steps. On a step failure it\n * writes the on-disk failure report (the\n * step itself already emitted its Failed event) and stops. The bootstrap provisions Node before\n * launch; Git/Xcode is provisioned here so the browser shows the never-frozen progress.\n */\nexport class Engine implements EngineCommands<EngineRunResult> {\n private readonly config: Config;\n private readonly deps: EngineDeps;\n private readonly completed = new Set<StepId>();\n private cancelled = false;\n private running = false;\n\n // Outputs threaded between steps.\n private scaffoldCreated = false;\n private bmadInstalled = false;\n private lastSummary: ValidationSummary | null = null;\n\n private readonly steps: Step[];\n\n constructor(\n config: Config,\n private readonly emitter: EngineEmitter,\n deps: Partial<EngineDeps> = {},\n ) {\n // Expand a leading `~` (the UI default is `~/kindling-project`) once, up front, so every step —\n // scaffold, install, self-check — works with a real path instead of a literal `~` folder.\n this.config = { ...config, projectDir: expandTilde(config.projectDir) };\n this.deps = { ...defaultDeps, ...deps };\n // Git provisioning surfaces on the xcode-clt step on macOS (the un-silenceable dialog), the\n // git step elsewhere — so a Retry targets the right row.\n const gitStepId =\n this.deps.platform === 'darwin' ? StepId.ProvisionXcodeClt : StepId.ProvisionGit;\n this.steps = [\n {\n // Node was provisioned by the bootstrap (kindling is running on it). Verify it actually\n // meets the floor before showing a green row — a sub-floor Node shows honestly (Failed)\n // instead of a false Skipped that would only surface as a confusing self-check failure.\n id: StepId.ProvisionNode,\n run: async () => {\n const state = await this.deps.detect({}); // probe only; we drive the row ourselves\n if (state.node.present && state.node.satisfiesFloor) {\n this.emit(StepId.ProvisionNode, Status.Skipped, provisionMessages.nodePresent);\n return true;\n }\n this.emit(StepId.ProvisionNode, Status.Failed, provisionMessages.nodeQueued, 'error', ErrorCode.ExecFailed);\n return false;\n },\n },\n {\n // Git/Xcode CLT runs IN the engine so the browser shows the never-frozen progress (the\n // ~5-min macOS dialog). On Windows, Git is provisioned by the bootstrap (PortableGit) —\n // probe + reflect it (Failed if the bootstrap didn't lay it down, rather than a cryptic\n // scaffold crash later).\n id: gitStepId,\n run: async () => {\n const state = await this.deps.detect({}); // probe only; no emitter (we drive the rows)\n if (this.deps.platform === 'win32') {\n if (state.git.present) {\n this.emit(gitStepId, Status.Skipped, provisionMessages.gitPresent);\n return true;\n }\n this.emit(gitStepId, Status.Failed, provisionMessages.gitInstallFailed, 'error', ErrorCode.ExecFailed);\n return false;\n }\n const result = await this.deps.provisionGit({\n platform: this.deps.platform,\n emitter: this.emitter,\n alreadyOk: state.git.present,\n });\n return result.ok;\n },\n },\n {\n id: StepId.ScaffoldGitInit,\n run: async () => {\n const outcome = await this.deps.scaffold({\n projectDir: this.config.projectDir,\n projectName: this.config.projectName,\n emitter: this.emitter,\n });\n this.scaffoldCreated = outcome !== 'blocked';\n return outcome !== 'blocked';\n },\n },\n {\n id: StepId.InstallBmad,\n run: async () => {\n // Windows: `npx` is a `.cmd` shim that spawn(shell:false) can't find by bare name → ENOENT.\n // The engine runs on the provisioned node (process.execPath), with npx-cli.js beside it, so\n // invoke `node npx-cli.js …` instead. macOS/Linux keep the bare-`npx` default untouched.\n const result = await this.deps.runBmadInstall({\n config: this.config,\n emitter: this.emitter,\n ...(this.deps.platform === 'win32'\n ? { npxCommand: process.execPath, npxPrefixArgs: [npxCliPath(process.execPath)] }\n : {}),\n });\n this.bmadInstalled = result.ok;\n return result.ok;\n },\n },\n {\n // Optional agent-CLI install (Story 6.1). NON-fatal by design: it awaits the step but\n // returns `true` UNCONDITIONALLY, so an individual CLI-install failure never invalidates\n // the successful BMad install — the run still reaches self-check / Welcome. The failure is\n // surfaced only as a Failed event (ErrorCode.AgentCliInstallFailed) for the error surface /\n // Story 6.2 UI. The row is retryable via engine.retry(StepId.InstallAgentCli) — but note a\n // future \"Retry install\" affordance must ALSO re-run self-check to refresh the Welcome's\n // CLI-presence guidance (retry() skips the already-completed self-check). See deferred-work.md.\n id: StepId.InstallAgentCli,\n run: async () => {\n // Non-fatal: the step returns true unconditionally. The install RESULT is intentionally\n // not stored — the self-check re-derives CLI presence by probing (Story 6.2), so the\n // presence report is robust to a mid-run failure regardless of this step's outcome.\n // Windows: same `.cmd`-shim problem as BMad — `npm` can't spawn by bare name (shell:false),\n // so route through `node npm-cli.js …`. macOS/Linux keep the bare-`npm` default.\n await this.deps.installAgentCli({\n config: this.config,\n emitter: this.emitter,\n // Windows: the idempotent-skip probe must go through `cmd /c <bin> --version` so the\n // installed `claude.cmd` shim is detected (Node won't spawn `.cmd` with shell:false).\n isWindows: this.deps.platform === 'win32',\n ...(this.deps.platform === 'win32'\n ? { npmCommand: process.execPath, npmPrefixArgs: [npmCliPath(process.execPath)] }\n : {}),\n });\n return true;\n },\n },\n {\n id: StepId.FinalizeSelfCheck,\n run: async () => {\n const summary = await this.deps.runSelfCheck({\n scaffoldCreated: this.scaffoldCreated,\n bmadInstalled: this.bmadInstalled,\n // Where the BMad manifest lives — self-check reads the ACTUAL installed version (FR26).\n projectDir: this.config.projectDir,\n // Thread the requested eligible CLI descriptors (SSOT accessor over config.installCli)\n // the same way scaffold/bmad outcomes are threaded — self-check probes each for presence.\n agentClis: eligibleAgentClis(this.config.installCli),\n emitter: this.emitter,\n });\n this.lastSummary = summary;\n return summary.success;\n },\n },\n ];\n }\n\n start(config?: Config): Promise<EngineRunResult> {\n void config; // config is fixed at construction; param kept for the EngineCommands contract\n return this.runFrom(0);\n }\n\n // Re-runs from `step` forward, skipping other completed steps. The named step is always\n // re-executed (cleared from `completed`). Returns a structured result for an unknown step\n // rather than throwing, so callers handle one shape.\n retry(step: StepId): Promise<EngineRunResult> {\n const idx = this.steps.findIndex((s) => s.id === step);\n if (idx < 0) {\n return Promise.resolve({ ok: false, failedStep: step, summary: this.lastSummary, logPath: null });\n }\n this.cancelled = false;\n this.completed.delete(step);\n return this.runFrom(idx);\n }\n\n cancel(): void {\n this.cancelled = true;\n }\n\n // Emit a provision-phase event the engine authors directly (the Node row; the Windows\n // git-present row). Step orchestrators emit their own events.\n private emit(\n step: StepId,\n status: Status,\n humanMessage: string,\n level: Level = 'info',\n errorCode?: ErrorCode,\n ): void {\n this.emitter.emit({\n id: randomUUID(),\n phase: Phase.Provision,\n step,\n status,\n humanMessage,\n level,\n timestamp: new Date().toISOString(),\n errorCode,\n });\n }\n\n private async runFrom(startIdx: number): Promise<EngineRunResult> {\n // Single-flight: the engine mutates shared state, so overlapping runs are a programming error.\n if (this.running) throw new Error('Engine is already running');\n this.running = true;\n try {\n for (let i = startIdx; i < this.steps.length; i++) {\n const step = this.steps[i];\n if (this.completed.has(step.id)) continue; // resume: skip already-done steps\n if (this.cancelled) {\n return { ok: false, failedStep: null, summary: this.lastSummary, logPath: null };\n }\n\n let ok: boolean;\n try {\n ok = await step.run();\n } catch (err) {\n const logPath = await this.fail(step.id, err);\n return { ok: false, failedStep: step.id, summary: this.lastSummary, logPath };\n }\n\n // Re-check cancellation after the (awaited) step so a mid-step cancel doesn't commit state.\n if (this.cancelled) {\n return { ok: false, failedStep: null, summary: this.lastSummary, logPath: null };\n }\n\n if (!ok) {\n const logPath = await this.fail(step.id, new Error(`Step ${step.id} did not succeed`));\n return { ok: false, failedStep: step.id, summary: this.lastSummary, logPath };\n }\n this.completed.add(step.id);\n }\n return { ok: true, failedStep: null, summary: this.lastSummary, logPath: null };\n } finally {\n this.running = false;\n }\n }\n\n // Writes the failure report; a log-write error must not mask the underlying step failure,\n // so it degrades to logPath: null rather than rejecting the run.\n private async fail(step: StepId, err: unknown): Promise<string | null> {\n const error =\n err instanceof Error\n ? (err.stack ?? err.message)\n : err !== null && typeof err === 'object'\n ? JSON.stringify(err)\n : String(err);\n try {\n return await this.deps.writeFailureLog({ step, error, events: this.emitter.events() });\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"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","names":["randomUUID","join","join","exec","randomUUID","randomUUID","exec","exec","randomUUID","join","join","randomUUID","exec","readInstalledBmadVersion","randomUUID","randomUUID","exec","randomUUID","randomUUID","exec","randomUUID","mkdir","writeFile","join","randomUUID","randomUUID","randomUUID"]}
|
package/dist/cli/main.js
CHANGED
package/dist/engine/index.d.ts
CHANGED
|
@@ -233,6 +233,8 @@ interface ValidationSummary {
|
|
|
233
233
|
os: string;
|
|
234
234
|
arch: string;
|
|
235
235
|
osVersion: string;
|
|
236
|
+
/** The project directory the setup targeted — surfaced on the Welcome screen as "where is my project". */
|
|
237
|
+
projectDir: string;
|
|
236
238
|
node: {
|
|
237
239
|
present: boolean;
|
|
238
240
|
version: string | null;
|
|
@@ -266,6 +268,7 @@ interface ValidationFacts {
|
|
|
266
268
|
os: string;
|
|
267
269
|
arch: string;
|
|
268
270
|
osVersion: string;
|
|
271
|
+
projectDir: string;
|
|
269
272
|
node: {
|
|
270
273
|
present: boolean;
|
|
271
274
|
version: string | null;
|
package/dist/engine/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--color-stage-deep:#16100c;--color-stage:#1b1410;--color-surface:#221913;--color-surface-tint:#2c1c12;--color-surface-raised:#2c211a;--color-surface-raised-hi:#34271e;--color-surface-overlay:#463429;--color-text-primary:#fff6ee;--color-text-secondary:#d6b9a3;--color-text-muted:#9a8170;--color-accent:#ff6a1f;--color-accent-gold:#ffc24a;--color-accent-pink:#ff3d6e;--color-on-flame:#1b1410;--color-success:#4fd784;--color-border:#463429;--color-border-warm:#5a3c20;--color-focus-ring:#ffc24a;--gradient-flame:linear-gradient(92deg, #ffc24a 0%, #ff6a1f 45%, #ff3d6e 100%);--gradient-flame-2:linear-gradient(90deg, #ffc24a, #ff6a1f);--gradient-stage:linear-gradient(135deg, #2c1c12 0%, #221913 60%);--radius-sm:6px;--radius-md:8px;--radius-lg:10px;--radius-xl:12px;--radius-2xl:14px;--radius-full:999px;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:24px;--space-6:32px;--space-7:44px;--space-8:56px;--font-family:-apple-system, "Segoe UI Variable", "Segoe UI", Roboto, Helvetica, Arial, sans-serif}*{box-sizing:border-box}@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto!important}*,:before,:after{transition-duration:.001ms!important;animation-duration:.001ms!important;animation-iteration-count:1!important;animation-play-state:paused!important}}html,body{height:100%;margin:0}body{font-family:var(--font-family);color:var(--color-text-primary);background:var(--color-stage);font-size:16px;font-weight:500}.app-shell{background:var(--color-stage);justify-content:center;align-items:flex-start;min-height:100vh;display:flex}.app-window{width:100%;max-width:1000px;margin:var(--space-6) var(--space-4);border-radius:var(--radius-2xl);border:1px solid var(--color-border);background:var(--color-surface);overflow:hidden;box-shadow:0 24px 60px #00000080}.app-titlebar{align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-4);background:var(--color-stage-deep);border-bottom:1px solid var(--color-border);display:flex}.app-traffic{gap:var(--space-2);display:flex}.app-traffic span{border-radius:var(--radius-full);background:var(--color-surface-overlay);width:12px;height:12px}.app-urlbar{padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);background:var(--color-surface-raised-hi);color:var(--color-text-secondary);flex:1;font-size:13px}.app-stage{padding:var(--space-8) var(--space-7);background:var(--gradient-stage);min-height:420px}.btn{font-family:var(--font-family);cursor:pointer;padding:var(--space-3) var(--space-5);border-radius:var(--radius-xl);border:0;font-size:16px;font-weight:800;transition:filter .12s}.btn:hover{filter:brightness(1.08)}.btn:focus-visible{outline:3px solid var(--color-focus-ring);outline-offset:3px}.btn--primary{background:var(--gradient-flame-2);color:var(--color-on-flame);box-shadow:0 10px 30px #ff6a1f73}.btn--secondary{color:var(--color-text-primary);border:2px solid var(--color-accent);background:0 0}.btn--ghost{background:var(--color-surface-raised-hi);color:var(--color-text-primary);border:1px solid var(--color-border-warm);border-radius:var(--radius-lg)}.field{gap:var(--space-2);flex-direction:column;display:flex}.field-label{color:var(--color-text-secondary);font-size:13px;font-weight:800}.field-input{font-family:var(--font-family);padding:var(--space-3) var(--space-4);border-radius:var(--radius-lg);background:var(--color-stage);color:var(--color-text-primary);border:1px solid var(--color-border);font-size:16px}.field-input::placeholder{color:var(--color-text-muted)}input:-webkit-autofill{-webkit-text-fill-color:var(--color-text-primary);-webkit-box-shadow:0 0 0 1000px var(--color-stage) inset;caret-color:var(--color-text-primary);transition:background-color 9999s ease-in-out}input:-webkit-autofill:hover{-webkit-text-fill-color:var(--color-text-primary);-webkit-box-shadow:0 0 0 1000px var(--color-stage) inset;caret-color:var(--color-text-primary);transition:background-color 9999s ease-in-out}input:-webkit-autofill:focus{-webkit-text-fill-color:var(--color-text-primary);-webkit-box-shadow:0 0 0 1000px var(--color-stage) inset;caret-color:var(--color-text-primary);transition:background-color 9999s ease-in-out}.field-input:focus-visible{outline:3px solid var(--color-focus-ring);outline-offset:2px}.screen{gap:var(--space-4);flex-direction:column;display:flex}.eyebrow{letter-spacing:.16em;text-transform:uppercase;color:var(--color-accent-gold);margin:0;font-size:12px;font-weight:800}.screen h1{letter-spacing:-.02em;color:var(--color-text-primary);margin:0;font-size:34px;font-weight:900}.lede{color:var(--color-text-secondary);margin:0;font-size:20px}.lede b{color:var(--color-text-primary)}.field legend.field-label,.field-label{padding:0}.field-note{color:var(--color-text-secondary);margin:0;font-size:13px}fieldset.field{border:0;margin:0;padding:0}.picker,.modules{margin:var(--space-2) 0 0;gap:var(--space-2);flex-direction:column;padding:0;list-style:none;display:flex}.picker-row,.mod-row{align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius-lg);background:var(--color-surface-raised);border:1px solid var(--color-border);cursor:pointer;display:flex}.picker-name,.mod-name{color:var(--color-text-primary);font-weight:700}.mod-desc{color:var(--color-text-secondary);font-size:13px}.picker-tag{letter-spacing:.08em;text-transform:uppercase;color:var(--color-accent-gold);font-size:11px;font-weight:800}.picker-check{color:var(--color-success);margin-left:auto;font-weight:800}.disclosure{gap:var(--space-3);flex-direction:column;display:flex}.disclosure-panel{gap:var(--space-4);padding:var(--space-4);border-radius:var(--radius-2xl);background:var(--color-surface-raised);border:1px solid var(--color-border);flex-direction:column;display:flex}.screen-actions{align-items:center;gap:var(--space-4);margin-top:var(--space-4);flex-wrap:wrap;display:flex}.start-note{color:var(--color-text-secondary);font-size:15px}.start-note a{color:var(--color-accent)}.versions{border-collapse:collapse;width:100%;margin:var(--space-4) 0;text-align:left}.versions caption{text-align:left;color:var(--color-text-secondary);margin-bottom:var(--space-2);font-weight:700}.versions th,.versions td{padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--color-border)}.versions th{color:var(--color-text-secondary);font-weight:700}.versions td{color:var(--color-text-primary);font-variant-numeric:tabular-nums}.field-row{align-items:center;gap:var(--space-3);margin-top:var(--space-2);display:flex}.field-value{color:var(--color-text-primary);font-weight:700}.default-pill{letter-spacing:.06em;text-transform:uppercase;color:var(--color-success);padding:var(--space-1) var(--space-3);border-radius:var(--radius-full);background:var(--color-surface-raised-hi);border:1px solid var(--color-border-warm);font-size:11px;font-weight:800}.optin-form{gap:var(--space-3);margin-top:var(--space-3);flex-direction:column;align-items:flex-start;display:flex}.optin-row{flex-wrap:wrap;gap:12px;width:100%;display:flex}.optin-row>.field{flex:1;min-width:0}.start-error{padding:var(--space-3) var(--space-4);border-radius:var(--radius-lg);background:var(--color-surface-raised);border:1px solid var(--color-border-warm);color:var(--color-text-primary);margin:0}.progressbar{border-radius:var(--radius-lg);background:var(--color-surface-raised);border:1px solid var(--color-border);height:12px;overflow:hidden}.progressbar-fill{background:var(--gradient-flame);height:100%;transition:width .32s}.progressbar--activity .progressbar-fill{animation:1.4s ease-in-out infinite kindling-pulse}@keyframes kindling-pulse{0%,to{opacity:.55}50%{opacity:1}}.sr-live{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.steps{margin:var(--space-4) 0 0;gap:var(--space-2);flex-direction:column;padding:0;list-style:none;display:flex}.step-row{align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius-2xl);background:var(--color-surface-raised);border:1px solid var(--color-border);display:flex}.step-icon{text-align:center;width:1.4em;font-size:18px}.step-word{letter-spacing:.04em;text-transform:uppercase;min-width:7.5em;font-size:12px;font-weight:900}.step-message{color:var(--color-text-secondary);flex:1}.step-row[data-tone=wait] .step-word{color:var(--color-text-secondary)}.step-row[data-tone=busy] .step-word{color:var(--color-accent-gold)}.step-row[data-tone=success] .step-word{color:var(--color-success)}.step-row[data-tone=error] .step-word{color:var(--color-text-primary)}.step-row[data-tone=busy] .step-icon{color:var(--color-accent)}.step-row[data-tone=error] .step-icon{color:var(--color-accent-pink)}.step-activity{color:var(--color-accent-gold);animation:1.2s ease-in-out infinite kindling-pulse}.steps--compact .step-row{padding:var(--space-2) var(--space-3);background:var(--color-surface-raised-hi)}.fix-command{align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius-lg);background:var(--color-stage);border:1px solid var(--color-border-warm);flex-wrap:wrap;display:flex}.fix-command-code{color:var(--color-accent-gold);word-break:break-all;flex:1;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-size:14px}
|