baldart 3.17.0 → 3.17.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.17.1] - 2026-05-23
9
+
10
+ CLI quality-of-life patch shaken loose by a hands-on consumer update simulation (mayo, 3.13.0 → 3.16.0). Four real bugs surfaced when actually driving `baldart update` end-to-end from a scripted context. Two were show-stoppers for any non-interactive use (CI, AI-driven, headless update agent), one was cosmetic but ugly, one was a stale stub that lied to the user. All four are fixed; nothing in this release changes framework payload behaviour for end-users running BALDART interactively in a TTY.
11
+
12
+ ### Fixed
13
+
14
+ - **`baldart update` gains `--yes`/`-y` and `--auto-stash` flags** in [bin/baldart.js](bin/baldart.js) and [src/commands/update.js](src/commands/update.js). Until now, every `UI.confirm` and the `UI.select` arrow-key stash prompt blocked indefinitely on a non-TTY stdin (inquirer's pattern detection mis-handles re-rendered prompts when fed via pipe or `expect`). Both flags route through a single internal `confirm()` wrapper that short-circuits to the prompt's default when `--yes` is set; `--auto-stash` is the narrower variant for users who want manual control over confirms but auto-stash on dirty trees. `--yes` implies `--auto-stash` and additionally passes `--non-interactive` down to the `baldart configure` sub-invocation triggered by the schema-drift detector, plus switches symlink reconcile from `mode: 'prompt'` to `mode: 'force'` (the conflict-resolution prompts inside `SymlinkUtils` would otherwise reintroduce blocking I/O even with `--yes`).
15
+ - **`baldart status` actually checks the framework against the upstream tip** in [src/commands/status.js](src/commands/status.js). Previously the "Update Status" section was a hard-coded `UI.success('Up to date')` stub regardless of the installed version — a 4-versions-stale consumer was told everything was fine. The fix calls `git.fetch('antbald/BALDART')` + `git.getRemoteVersion(...)` (already exported by `src/utils/git.js`), compares semver with a tiny inline `cmpSemver` helper, and renders one of four outcomes: update available (`X → Y`), local ahead of remote (unreleased commit, common during BALDART self-development), up to date, or "Cannot check (offline?)" on fetch failure. The SUMMARY box now carries the update line too, so it's visible without scrolling past the per-section output.
16
+ - **`baldart --version` and `baldart --help` exit cleanly** in [bin/baldart.js](bin/baldart.js). Commander 9 throws a `CommanderError` with `exitCode: 0` from these flags as its internal flow-control mechanism; the previous `bin/baldart.js` had no try/catch around `program.parse(process.argv)`, so the throw escaped to Node and printed a 12-line `CommanderError: 3.17.0` stack trace right after the version itself. Now wrapped in try/catch that recognises the three known sentinel codes (`commander.version`, `commander.help`, `commander.helpDisplayed`) and exits 0 silently; real errors keep the previous behaviour (log message + non-zero exit).
17
+ - **CLI version is read from the `VERSION` file, not `package.json`** in [bin/baldart.js](bin/baldart.js). `package.json.version` is only synced at publish time via `npm run sync-version` (see `scripts/sync-version`), so in dev / local-source / pre-publish contexts it lagged the actual `VERSION` file — `node bin/baldart.js --version` was printing `3.14.1` while VERSION was `3.17.0`. A new `resolveVersion()` reads `VERSION` at module load with `package.json.version` as fallback. Published `baldart@<x.y.z>` on npm is unaffected (the workflow still runs `sync-version` first, so both files agree in published artefacts).
18
+
19
+ ### Notes
20
+
21
+ - **No framework payload changes** — all four fixes are inside `bin/` and `src/`. Skills, agents, templates, hooks, routines, docs are untouched. The next `baldart update` for consumers picks up the new flags and the working status check without any change to the framework files they install.
22
+ - **Not a v3.18.0 because no new capability shipped** — this is purely "what should already have worked". The new flags are additive (zero-config end-users in a TTY see no change) and the status fix is replacing a stub with the real check it was always supposed to be.
23
+ - **Bug #4 from the mayo simulation (schema-drift detector skipped with `--no-commit`) was a false positive** — the drift detector at lines 462–503 runs before `postUpdateAutoCommit` (line 506), so `--no-commit` doesn't reach it. Verified by reading the call sequence, no code change needed.
24
+ - **Verification** (manual, no test suite): (1) `node bin/baldart.js --version` prints `3.17.1` and exits 0 silently; (2) `node bin/baldart.js update --help` shows the three options (`--no-commit`, `--yes/-y`, `--auto-stash`); (3) in a stale consumer repo, `baldart status` reports "Update available" with the version diff; (4) `baldart update --yes` on a dirty consumer auto-stashes, pulls, reconciles symlinks in force mode, runs `configure --non-interactive`, and exits 0 with no blocking prompts.
25
+
8
26
  ## [3.17.0] - 2026-05-23
9
27
 
10
28
  The `/prd` skill gains a third option in the design phase. Until v3.16.0, when a user reached Step 3 without having provided mockups (`mockups.status: none`), the only path forward was internal generation via the `ui-design` skill (3 HTML options → iteration → save). With Claude Design now offering a design-system-sync'd visual generator, users who prefer iterating in that surface had to abandon the PRD flow halfway, work externally, and then re-enter as if they'd had mockups from the start. This release adds **Step 3.0 — Mockup Source Decision**: when `mockups.status: none` AND UI impact ≠ N/A, the skill explicitly asks "internal generation or handoff to Claude Design?". The handoff branch generates a context-rich prompt (discovery + brand voice + design-system tokens + screens in scope + stack target) ready to paste, then STOPs. On user re-entry with local paths, it re-uses the existing Step 1.6 Mockup Intake verbatim — copy → analyze → populate state — and Step 3 falls naturally into Hybrid mode. Zero code paths downstream of the bivio are new; the entire feature is a decision point + a prompt generator on top of pre-existing infrastructure.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.17.0
1
+ 3.17.1
package/bin/baldart.js CHANGED
@@ -2,9 +2,27 @@
2
2
 
3
3
  const { Command } = require('commander');
4
4
  const chalk = require('chalk');
5
+ const fs = require('fs');
6
+ const path = require('path');
5
7
  const packageJson = require('../package.json');
6
8
  const updateNotifier = require('../src/utils/update-notifier');
7
9
 
10
+ // Source of truth for the running version is the VERSION file at the repo
11
+ // root. package.json.version is only synced at publish time (see scripts/
12
+ // sync-version), so in dev / local-source / pre-publish contexts it lags.
13
+ // Reading VERSION here keeps `baldart --version` honest in every scenario.
14
+ function resolveVersion() {
15
+ try {
16
+ const versionFile = path.join(__dirname, '..', 'VERSION');
17
+ const fromFile = fs.readFileSync(versionFile, 'utf8').trim();
18
+ if (fromFile) return fromFile;
19
+ } catch (_) { /* fall through */ }
20
+ // Fall back to package.json (already required above). Avoids the TDZ trap
21
+ // of self-referencing `CLI_VERSION` before its initialiser runs.
22
+ return packageJson.version;
23
+ }
24
+ const CLI_VERSION = resolveVersion();
25
+
8
26
  // CLI self-update notifier (v3.13.0+). Best-effort; non-blocking; suppressed
9
27
  // in CI / non-TTY / via BALDART_NO_UPDATE_CHECK=1 / when --offline is passed.
10
28
  // Prints a one-shot hint at the top of the run if a newer npm version was
@@ -14,7 +32,7 @@ const updateNotifier = require('../src/utils/update-notifier');
14
32
  const rawArgsForNotifier = process.argv.slice(2);
15
33
  const offlineForNotifier = rawArgsForNotifier.includes('--offline');
16
34
  updateNotifier.announceAndRefresh({
17
- currentVersion: packageJson.version,
35
+ currentVersion: CLI_VERSION,
18
36
  offline: offlineForNotifier,
19
37
  });
20
38
 
@@ -23,7 +41,7 @@ const program = new Command();
23
41
  program
24
42
  .name('baldart')
25
43
  .description('Claude Agent Framework - AI agent coordination for software projects')
26
- .version(packageJson.version);
44
+ .version(CLI_VERSION);
27
45
 
28
46
  program
29
47
  .command('add [repo]')
@@ -38,6 +56,8 @@ program
38
56
  .command('update')
39
57
  .description('Update the framework to the latest version')
40
58
  .option('--no-commit', 'Skip the post-update auto-commit prompt')
59
+ .option('-y, --yes', 'Auto-confirm all prompts (CI / scripting). Implies --auto-stash.')
60
+ .option('--auto-stash', 'When the working tree is dirty, stash automatically instead of prompting.')
41
61
  .action(async (options) => {
42
62
  const updateCommand = require('../src/commands/update');
43
63
  await updateCommand(options);
@@ -153,5 +173,16 @@ if (isDoctorShortcut) {
153
173
  process.exit(1);
154
174
  });
155
175
  } else {
156
- program.parse(process.argv);
176
+ try {
177
+ program.parse(process.argv);
178
+ } catch (err) {
179
+ // Commander throws CommanderError(exitCode: 0) for --version / --help.
180
+ // That's a normal exit, not a crash — swallow the throw and exit cleanly.
181
+ if (err && err.code === 'commander.version') process.exit(0);
182
+ if (err && err.code === 'commander.help') process.exit(0);
183
+ if (err && err.code === 'commander.helpDisplayed') process.exit(0);
184
+ // Real error: log and exit non-zero like before.
185
+ console.error(err && err.message ? err.message : err);
186
+ process.exit(err && Number.isInteger(err.exitCode) ? err.exitCode : 1);
187
+ }
157
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "3.17.0",
3
+ "version": "3.17.1",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"
@@ -4,6 +4,19 @@ const UI = require('../utils/ui');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
+ // Lightweight semver compare. Returns positive if a > b, negative if a < b,
8
+ // zero if equal. Treats malformed input as 0 — degrade open rather than throw.
9
+ function cmpSemver(a, b) {
10
+ if (!a || !b) return 0;
11
+ const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
12
+ const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
13
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
14
+ const d = (pa[i] || 0) - (pb[i] || 0);
15
+ if (d !== 0) return d;
16
+ }
17
+ return 0;
18
+ }
19
+
7
20
  async function status() {
8
21
  const git = new GitUtils();
9
22
  const symlinks = new SymlinkUtils();
@@ -94,24 +107,43 @@ async function status() {
94
107
  }
95
108
  }
96
109
 
97
- // Check for pending updates
110
+ // Check for pending updates (since v3.17.1 — was a no-op stub before).
98
111
  UI.newline();
99
112
  UI.section('Update Status');
100
113
 
114
+ let updateAvailable = false;
115
+ let remoteVersion = null;
101
116
  try {
102
117
  await git.fetch('antbald/BALDART');
103
- UI.info('Checking for updates...');
104
- // Simplified - would need more sophisticated version comparison
105
- UI.success('Up to date (check with: baldart update)');
118
+ remoteVersion = await git.getRemoteVersion('antbald/BALDART', 'main');
119
+ if (!remoteVersion || remoteVersion === 'unknown') {
120
+ UI.warning('Could not read remote VERSION — check `baldart update` manually.');
121
+ } else if (cmpSemver(remoteVersion, version) > 0) {
122
+ updateAvailable = true;
123
+ UI.warning(`Update available: ${version} → ${remoteVersion}. Run: baldart update`);
124
+ } else if (cmpSemver(remoteVersion, version) < 0) {
125
+ UI.info(`Local v${version} is ahead of remote v${remoteVersion} (likely an unreleased commit).`);
126
+ } else {
127
+ UI.success(`Up to date (v${version})`);
128
+ }
106
129
  } catch (error) {
107
130
  UI.warning('Cannot check for updates (offline?)');
108
131
  }
109
132
 
110
133
  // Summary
111
134
  UI.newline();
135
+ let updateLine;
136
+ if (updateAvailable && remoteVersion) {
137
+ updateLine = `Update: ${version} → ${remoteVersion} available`;
138
+ } else if (remoteVersion && remoteVersion !== 'unknown') {
139
+ updateLine = `Update: up to date`;
140
+ } else {
141
+ updateLine = `Update: unknown (could not reach remote)`;
142
+ }
112
143
  UI.box('SUMMARY', [
113
144
  `Version: ${version}`,
114
145
  `Installation: ${symlinkValid ? 'Valid' : 'Issues detected'}`,
146
+ updateLine,
115
147
  '',
116
148
  'Commands available:',
117
149
  ' • baldart update - Update framework',
@@ -81,7 +81,10 @@ async function postUpdateAutoCommit(git, newVersion, options) {
81
81
  if (userOwned.length > 0) {
82
82
  UI.warning(`${userOwned.length} other dirty path(s) will be left untouched (not BALDART-managed).`);
83
83
  }
84
- const ok = await UI.confirm(`Commit them as \`chore(baldart): post-update reconcile to v${newVersion}\`?`, true);
84
+ const autoYes = options && options.yes === true;
85
+ const ok = autoYes
86
+ ? true
87
+ : await UI.confirm(`Commit them as \`chore(baldart): post-update reconcile to v${newVersion}\`?`, true);
85
88
  if (!ok) {
86
89
  UI.info('Auto-commit declined. Your worktree keeps the changes for manual review.');
87
90
  return;
@@ -110,6 +113,13 @@ async function update(options = {}) {
110
113
  const symlinks = new SymlinkUtils();
111
114
  const repo = 'antbald/BALDART'; // Default repo
112
115
 
116
+ // Non-interactive flags (since v3.17.1). `--yes` implies `--auto-stash`.
117
+ // When `yes` is set, every UI.confirm/UI.select downstream short-circuits
118
+ // to its default — the same value an unattended user would have produced.
119
+ const autoYes = options.yes === true;
120
+ const autoStash = autoYes || options.autoStash === true;
121
+ const confirm = async (msg, def = true) => (autoYes ? def : UI.confirm(msg, def));
122
+
113
123
  try {
114
124
  // Step 1: Verify installation
115
125
  UI.header('STEP 1/5: Verify Installation');
@@ -194,14 +204,14 @@ async function update(options = {}) {
194
204
  const diff = await git.diffWithRemote();
195
205
  if (diff) {
196
206
  UI.info('Changes detected. Review recommended.');
197
- const showDiff = await UI.confirm('Show detailed diff?', false);
207
+ const showDiff = await confirm('Show detailed diff?', false);
198
208
 
199
209
  if (showDiff) {
200
210
  console.log(diff);
201
211
  }
202
212
  }
203
213
 
204
- const proceedUpdate = await UI.confirm('Proceed with update?', true);
214
+ const proceedUpdate = await confirm('Proceed with update?', true);
205
215
  if (!proceedUpdate) {
206
216
  UI.info('Update cancelled');
207
217
  process.exit(0);
@@ -225,10 +235,16 @@ async function update(options = {}) {
225
235
  UI.list(unique.slice(0, 8), 'yellow');
226
236
  if (unique.length > 8) UI.info(`…and ${unique.length - 8} more.`);
227
237
 
228
- const action = await UI.select('How would you like to proceed?', [
229
- { name: 'Auto-stash now and re-apply after the update (recommended)', value: 'stash' },
230
- { name: 'Abort I want to commit or stash manually first', value: 'abort' }
231
- ]);
238
+ let action;
239
+ if (autoStash) {
240
+ UI.info('Auto-stashing dirty paths (--yes / --auto-stash).');
241
+ action = 'stash';
242
+ } else {
243
+ action = await UI.select('How would you like to proceed?', [
244
+ { name: 'Auto-stash now and re-apply after the update (recommended)', value: 'stash' },
245
+ { name: 'Abort — I want to commit or stash manually first', value: 'abort' }
246
+ ]);
247
+ }
232
248
  if (action === 'abort') {
233
249
  UI.info('Aborted. Commit or `git stash -u`, then re-run `npx baldart update`.');
234
250
  process.exit(0);
@@ -361,9 +377,12 @@ async function update(options = {}) {
361
377
  'Convert legacy .claude/skills/ bulk symlink (v2.0.x) into the new per-item layout.',
362
378
  `Merge framework skills into per-tool skill dirs (${enabledTools.join(', ')}) without touching your personal skills.`
363
379
  ]);
364
- const recreate = await UI.confirm('Reconcile symlinks now?', true);
380
+ const recreate = await confirm('Reconcile symlinks now?', true);
365
381
  if (recreate) {
366
- await symlinks.createAllSymlinks({ mode: 'prompt', tools: enabledTools });
382
+ // In auto-yes mode pass 'force' instead of 'prompt' so per-conflict
383
+ // prompts inside SymlinkUtils don't reintroduce blocking I/O.
384
+ const mode = autoYes ? 'force' : 'prompt';
385
+ await symlinks.createAllSymlinks({ mode, tools: enabledTools });
367
386
  }
368
387
  } else {
369
388
  // Re-run the per-item merges for every enabled tool so newly-shipped
@@ -403,10 +422,10 @@ async function update(options = {}) {
403
422
  if (!fs.existsSync(configPath)) {
404
423
  UI.newline();
405
424
  UI.warning('No baldart.config.yml in this project — skills installed by this framework version require it.');
406
- const runConfigure = await UI.confirm('Run `baldart configure` now?', true);
425
+ const runConfigure = await confirm('Run `baldart configure` now?', true);
407
426
  if (runConfigure) {
408
427
  const configureCmd = require('./configure');
409
- await configureCmd();
428
+ await configureCmd(autoYes ? { nonInteractive: true } : undefined);
410
429
  }
411
430
  } else {
412
431
  // Schema migration: backfill `tools.enabled` for configs created
@@ -425,7 +444,7 @@ async function update(options = {}) {
425
444
  UI.warning('Pre-v3.7.0 config detected — `tools.enabled` is missing.');
426
445
  UI.info(`From v3.7.0, BALDART can install framework skills for multiple AI tools (Claude Code, OpenAI Codex CLI, …) from the same source.`);
427
446
  UI.info(`Autodetected on this machine: ${suggested.join(', ')}`);
428
- const ok = await UI.confirm(`Backfill \`tools.enabled: [${suggested.join(', ')}]\` into baldart.config.yml?`, true);
447
+ const ok = await confirm(`Backfill \`tools.enabled: [${suggested.join(', ')}]\` into baldart.config.yml?`, true);
429
448
  if (ok) {
430
449
  cur.tools = cur.tools || {};
431
450
  cur.tools.enabled = suggested;
@@ -470,10 +489,10 @@ async function update(options = {}) {
470
489
  UI.warning(`New config keys in this version: ${allMissing.join(', ')}.`);
471
490
  // Auto-offer configure so the user actually answers, instead of
472
491
  // silently falling back to template defaults on first use.
473
- const runConfigure = await UI.confirm('Run `baldart configure` now to populate them? (existing values are preserved)', true);
492
+ const runConfigure = await confirm('Run `baldart configure` now to populate them? (existing values are preserved)', true);
474
493
  if (runConfigure) {
475
494
  const configureCmd = require('./configure');
476
- await configureCmd();
495
+ await configureCmd(autoYes ? { nonInteractive: true } : undefined);
477
496
  } else {
478
497
  UI.info('Skipped. Skills will prompt for the missing keys on first use.');
479
498
  }