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 +18 -0
- package/VERSION +1 -1
- package/bin/baldart.js +34 -3
- package/package.json +1 -1
- package/src/commands/status.js +36 -4
- package/src/commands/update.js +33 -14
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.
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
package/src/commands/status.js
CHANGED
|
@@ -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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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',
|
package/src/commands/update.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
380
|
+
const recreate = await confirm('Reconcile symlinks now?', true);
|
|
365
381
|
if (recreate) {
|
|
366
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|