baldart 3.35.1 → 3.35.2

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,20 @@ 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.35.2] - 2026-05-30
9
+
10
+ Resolves the **3.35.1 known follow-up**: a flag introduced in a newer release (e.g. `--on-divergence`, v3.29.0) was rejected by an older global CLI with a cryptic `error: unknown option` **before** the `npx baldart@latest` auto-relaunch could self-upgrade — commander validates options during `program.parse()`, which runs *before* the action handler where the relaunch lives. The `update` command now parses **permissively**, so an unknown flag is captured instead of crashing the parser; the action then transparently relaunches under `@latest` (forwarding the raw argv so the unrecognized flag survives) or, when that isn't possible, emits an **actionable** error instead of commander's cryptic one. **No new `baldart.config.yml` keys** — the schema-propagation rule does not apply.
11
+
12
+ > **Forward-looking by nature.** This cannot retroactively fix CLIs *already* installed at a pre-3.35.2 version — their commander still throws before any code we control runs. The fix rescues flags introduced *after* this release for anyone on 3.35.2+. For older installs the only path remains `npm i -g baldart@latest` (now surfaced by the helpful error + the update notifier).
13
+
14
+ ### Fixed — self-upgrade survives flags newer than the installed CLI
15
+
16
+ - **[bin/baldart.js](bin/baldart.js)**: the `update` command gains `.allowUnknownOption()` + `.allowExcessArguments()` (scoped to `update` only — every other command keeps strict validation). Unknown tokens now land in `command.args` instead of throwing `commander.unknownOption` during parse; they are forwarded to `update(options, command.args)`.
17
+ - **[src/commands/update.js](src/commands/update.js)** (`update`): an early unknown-flag block runs **before** any work. When unknown tokens are present it relaunches under `npx baldart@latest`, forwarding `process.argv.slice(3)` (raw flags, with the leading `update` subcommand token stripped so the child isn't spawned as `update update …`). The unrecognized flag never enters `options`, so raw-argv forwarding is the only faithful path.
18
+ - **[src/commands/update.js](src/commands/update.js)** (`maybeRelaunchUnderLatest`): accepts `extra.rawArgs` (forward verbatim instead of reconstructing from parsed `options`) and `extra.unknown` (skip the interactive "proceed with current CLI" prompt — pointless when the CLI literally can't parse the flag; relaunch whenever a newer version exists). The normal stale-CLI relaunch path is unchanged (still reconstructs known flags, still forwards `--on-divergence` / `--json`).
19
+ - **[src/commands/update.js](src/commands/update.js)** (`failOnUnknownArgs`): new helpful terminal error replacing commander's cryptic message — hedges between "recently-added flag → `npm i -g baldart@latest`" and "typo", and **suppresses the upgrade suggestion** when `BALDART_RELAUNCHED=1` (we are already the `@latest` child, so a typo is the only explanation). `--json`-aware: emits a `usage-error` JSON object on stdout.
20
+ - **[src/commands/update.js](src/commands/update.js)**: `--json` mode is now **armed before** the unknown-flag block so the relaunch notice / helpful error route to STDERR — preserving the single-object STDOUT contract for `update --json --yes --<new-flag>` (the agent/CI scenario this machinery exists for).
21
+
8
22
  ## [3.35.1] - 2026-05-30
9
23
 
10
24
  Fixes a **HIGH-severity** bug where `baldart update --reset` could leave a consumer **committed-without-`.framework/`** (broken install, manual recovery only). The reset path wiped `.framework/`, committed the deletion, then handed off to `add()` **programmatically with no repository** — the bin-layer default (`antbald/BALDART`) only applies on the CLI path, so `repo` arrived `undefined`, crashing at `repo.startsWith(...)` ("Cannot read properties of undefined") *after* the wipe was already committed. Three layers now make this unreachable: (1) repo resolved **before** any destructive step, (2) reinstall failures roll back to the pre-reset backup tag, (3) defensive guards + legible errors. **No new `baldart.config.yml` keys** — the schema-propagation rule does not apply.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.35.1
1
+ 3.35.2
package/bin/baldart.js CHANGED
@@ -81,9 +81,24 @@ program
81
81
  .option('--i-know', 'Acknowledges --reset will wipe untracked/ignored files inside .framework/ (required to use --reset --yes).')
82
82
  .option('--on-divergence <strategy>', 'Non-interactive resolution when the consumer has local commits on .framework/ (for agents / CI): "scaffold-overlays" (auto-create overlay skeletons, then stop), "pull" (keep commits + merge — non-destructive), or "abort".')
83
83
  .option('--json', 'Machine-readable output (agents / CI): emit a single JSON result object on stdout, route all human output to stderr. Requires --yes (non-interactive); rejects --reset.')
84
- .action(async (options) => {
84
+ // Parse permissively so an UNKNOWN flag does not crash commander before the
85
+ // CLI self-upgrade can kick in (v3.35.2+). The whole point of `update` is to
86
+ // be the self-healing entrypoint: when a flag introduced in a newer release
87
+ // reaches an older-but-still-this-or-later CLI, commander used to throw
88
+ // `error: unknown option` during parse — i.e. BEFORE the action handler runs,
89
+ // so the auto-relaunch under `npx baldart@latest` (which lives in the action)
90
+ // never got a chance. With both guards set, unknown tokens land in
91
+ // `command.args` instead of throwing; the action forwards them to a newer CLI
92
+ // (or emits a helpful error). Scoped to `update` only — every other command
93
+ // keeps strict validation.
94
+ .allowUnknownOption()
95
+ .allowExcessArguments()
96
+ .action(async (options, command) => {
85
97
  const updateCommand = require('../src/commands/update');
86
- await updateCommand(options);
98
+ // command.args holds any tokens commander could not match to a known
99
+ // option/positional — for `update` (no positionals) that means unknown
100
+ // flags + their values. Forward them so update() can self-upgrade.
101
+ await updateCommand(options, command.args || []);
87
102
  });
88
103
 
89
104
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "3.35.1",
3
+ "version": "3.35.2",
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"
@@ -406,7 +406,16 @@ function snapshotCustomEntries(dir) {
406
406
  // child inherits stdio (preserves prompts), and BALDART_RELAUNCHED=1 in env
407
407
  // is a loop guard: if npm cache somehow serves an older version, the child
408
408
  // sees the flag and skips its own relaunch check.
409
- async function maybeRelaunchUnderLatest(options) {
409
+ // `extra.rawArgs` when set, forward these verbatim to the child instead of
410
+ // reconstructing flags from the parsed `options`. Used by the unknown-flag path
411
+ // (v3.35.2+): an option this CLI doesn't recognize never lands in `options`, so
412
+ // reconstruction would silently drop it — only the raw argv carries it intact.
413
+ // The caller MUST have already stripped the leading `update` subcommand token.
414
+ // `extra.unknown` — true when triggered by an unknown flag rather than a plain
415
+ // stale-CLI check. In that case the interactive "proceed with current CLI"
416
+ // branch is pointless (the current CLI literally cannot honor the flag), so we
417
+ // relaunch whenever a newer version exists and skip the prompt.
418
+ async function maybeRelaunchUnderLatest(options, extra = {}) {
410
419
  if (process.env.BALDART_RELAUNCHED === '1') return false;
411
420
  if (process.env.BALDART_NO_RELAUNCH === '1') return false;
412
421
 
@@ -424,7 +433,13 @@ async function maybeRelaunchUnderLatest(options) {
424
433
 
425
434
  const autoYes = options.yes === true;
426
435
  let proceed = autoYes;
427
- if (!autoYes) {
436
+ if (extra.unknown) {
437
+ // Unknown flag + a newer CLI exists: relaunch unconditionally. Asking
438
+ // "proceed with current CLI?" makes no sense — it can't parse the flag.
439
+ proceed = true;
440
+ UI.newline();
441
+ UI.warning(`Unknown option for the installed CLI (v${cliVersion}); a newer release (v${info.latest}) may support it.`);
442
+ } else if (!autoYes) {
428
443
  UI.newline();
429
444
  UI.warning(`The installed CLI is older than npm latest: v${cliVersion} → v${info.latest}.`);
430
445
  const choice = await UI.select('How would you like to proceed?', [
@@ -440,17 +455,24 @@ async function maybeRelaunchUnderLatest(options) {
440
455
  }
441
456
  if (!proceed) return false;
442
457
 
443
- // Reconstruct the original argv flags for the child.
444
- const flags = [];
445
- if (options.yes === true) flags.push('--yes');
446
- if (options.autoStash === true && options.yes !== true) flags.push('--auto-stash');
447
- if (options.commit === false) flags.push('--no-commit');
448
- if (options.reset === true) flags.push('--reset');
449
- if (options.iKnow === true) flags.push('--i-know');
450
- // Forward the non-interactive resolution + machine-readable flags too, else
451
- // a stale-CLI self-relaunch would silently drop them (v3.32.0+).
452
- if (options.onDivergence) flags.push('--on-divergence', options.onDivergence);
453
- if (options.json === true) flags.push('--json');
458
+ // Build the child argv. The unknown-flag path forwards the raw argv (already
459
+ // stripped of the `update` token); the normal stale-CLI path reconstructs
460
+ // from parsed options.
461
+ let flags;
462
+ if (Array.isArray(extra.rawArgs)) {
463
+ flags = extra.rawArgs;
464
+ } else {
465
+ flags = [];
466
+ if (options.yes === true) flags.push('--yes');
467
+ if (options.autoStash === true && options.yes !== true) flags.push('--auto-stash');
468
+ if (options.commit === false) flags.push('--no-commit');
469
+ if (options.reset === true) flags.push('--reset');
470
+ if (options.iKnow === true) flags.push('--i-know');
471
+ // Forward the non-interactive resolution + machine-readable flags too, else
472
+ // a stale-CLI self-relaunch would silently drop them (v3.32.0+).
473
+ if (options.onDivergence) flags.push('--on-divergence', options.onDivergence);
474
+ if (options.json === true) flags.push('--json');
475
+ }
454
476
 
455
477
  const spawn = require('child_process').spawnSync;
456
478
  UI.info(`Auto-relaunching via npx baldart@${info.latest}…`);
@@ -466,7 +488,30 @@ async function maybeRelaunchUnderLatest(options) {
466
488
  process.exit(res.status || 0);
467
489
  }
468
490
 
469
- async function update(options = {}) {
491
+ // Helpful terminal error when an unknown flag reached this CLI and a relaunch
492
+ // under a newer version was not possible (already on latest / offline / opted
493
+ // out, or we ARE the relaunched child). Replaces commander's cryptic
494
+ // `error: unknown option` with actionable guidance. (v3.35.2+)
495
+ function failOnUnknownArgs(unknownArgs, options) {
496
+ const tokens = unknownArgs.filter((a) => typeof a === 'string');
497
+ const firstOpt = tokens.find((a) => a.startsWith('-')) || tokens[0] || '(unknown)';
498
+ // In the relaunched child we are already on @latest, so "upgrade" is wrong —
499
+ // an unknown flag here means a genuine typo.
500
+ const alreadyLatest = process.env.BALDART_RELAUNCHED === '1';
501
+ const reason = alreadyLatest
502
+ ? `Unknown option ${firstOpt}. Even the latest CLI does not recognize it — check for a typo.`
503
+ : `Unknown option ${firstOpt}. If it is a recently-added flag, upgrade with \`npm i -g baldart@latest\`; otherwise check for a typo.`;
504
+ if (options.json === true) {
505
+ emitUpdateJson({ ok: false, action: 'usage-error', reason }, 2);
506
+ }
507
+ UI.error(reason);
508
+ if (!alreadyLatest) {
509
+ UI.info(`Then retry: npx baldart update ${tokens.join(' ')}`.trim());
510
+ }
511
+ process.exit(2);
512
+ }
513
+
514
+ async function update(options = {}, unknownArgs = []) {
470
515
  const git = new GitUtils();
471
516
  const symlinks = new SymlinkUtils();
472
517
  const repo = 'antbald/BALDART'; // Default repo
@@ -478,13 +523,35 @@ async function update(options = {}) {
478
523
  const autoStash = autoYes || options.autoStash === true;
479
524
  const confirm = async (msg, def = true) => (autoYes ? def : UI.confirm(msg, def));
480
525
 
526
+ // Arm machine-readable mode FIRST (v3.35.2+). The unknown-flag handling below
527
+ // can emit human lines (relaunch notice / helpful error); they must route to
528
+ // STDERR before anything reaches STDOUT, or a `--json --yes --new-flag` run
529
+ // would pollute the single-object stdout contract.
530
+ JSON_MODE = options.json === true;
531
+ if (JSON_MODE) UI.setJsonMode(true);
532
+
533
+ // Unknown-flag self-upgrade (v3.35.2+). Commander now parses `update`
534
+ // permissively (see bin/baldart.js), so a flag this CLI doesn't know lands
535
+ // in `unknownArgs` instead of crashing the parser. That flag was almost
536
+ // certainly introduced in a newer release — transparently relaunch under
537
+ // `npx baldart@latest`, forwarding the RAW argv (the unknown token never made
538
+ // it into `options`, so only raw argv carries it). If we can't relaunch
539
+ // (already latest / offline / opted out / we ARE the child), fail with an
540
+ // actionable message instead of a cryptic crash.
541
+ if (Array.isArray(unknownArgs) && unknownArgs.length > 0) {
542
+ // process.argv = [node, baldart, 'update', ...flags] — drop the first 3 so
543
+ // the child isn't spawned as `update update …` (it already gets 'update').
544
+ const rawArgs = process.argv.slice(3);
545
+ await maybeRelaunchUnderLatest(options, { rawArgs, unknown: true });
546
+ // Relaunch did not happen → this (latest-or-offline) CLI can't honor it.
547
+ failOnUnknownArgs(unknownArgs, options);
548
+ }
549
+
481
550
  // Machine-readable mode (v3.32.0+). Must be fully non-interactive: --json
482
551
  // requires --yes (so no UI.select/confirm can block) and rejects --reset
483
552
  // (the nuclear path is a deliberate destructive human escape hatch, not an
484
553
  // agent flow — see the autonomous /baldart-update skill, which never resets).
485
- JSON_MODE = options.json === true;
486
554
  if (JSON_MODE) {
487
- UI.setJsonMode(true);
488
555
  if (options.reset === true) {
489
556
  emitUpdateJson({ ok: false, action: 'usage-error',
490
557
  reason: '--json does not support --reset. Reset is an interactive destructive escape hatch; run it manually.' }, 2);