baldart 3.35.0 → 3.35.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 +20 -0
- package/VERSION +1 -1
- package/package.json +1 -1
- package/src/commands/add.js +38 -3
- package/src/commands/doctor.js +4 -3
- package/src/commands/update.js +45 -6
- package/src/commands/version.js +1 -2
- package/src/utils/git.js +11 -0
- package/src/utils/state.js +24 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ 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.1] - 2026-05-30
|
|
9
|
+
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
### Fixed — `update --reset` never leaves the consumer without `.framework/`
|
|
13
|
+
|
|
14
|
+
- **[src/commands/update.js](src/commands/update.js)** (`runReset`): the upstream repo is now resolved via `State.resolveRepo()` **before** the backup tag / `rm -rf` / reset commit, then threaded **explicitly** into `add()` (was `addCmd(undefined, { yes: true })` — the root cause). The stale v3.28.1 comment claiming "undefined → use default from package.json" (which never existed in `add.js`) is corrected.
|
|
15
|
+
- **[src/commands/update.js](src/commands/update.js)**: the reinstall is wrapped in a `try/catch` that, on **any** failure (undefined repo, network, ignored path, missing hooks), auto-runs `git reset --hard <backupTag>` to restore the pre-reset `.framework/` and prints an explicit recovery command. Safe because reset is gated on a clean working tree — the backup tag holds everything but the reset commit.
|
|
16
|
+
- **[src/commands/add.js](src/commands/add.js)**: new `options.throwOnError` makes `add()` **re-throw** instead of `process.exit(1)` at every reachable failure point (download catch, gitignore-still-ignored, hooks-missing post-flight), so a programmatic caller can recover. CLI behavior is unchanged (flag absent → historical exit-1). Also resolves a falsy `repo` from the ledger as a last resort and fails fast **before** download with a recovery hint.
|
|
17
|
+
- **[src/utils/git.js](src/utils/git.js)** (`normalizeRepoUrl`): guards a falsy/non-string `repo` with a legible `Error` ("repository non specificato …") instead of the cryptic `Cannot read properties of undefined (reading 'startsWith')`.
|
|
18
|
+
|
|
19
|
+
### Changed — shared repo resolver (de-dup)
|
|
20
|
+
|
|
21
|
+
- **[src/utils/state.js](src/utils/state.js)**: new exported `resolveRepo(cwd)` (cascade `state.framework_repo → DEFAULT_REPO`, never `undefined`, preserves a consumer's fork) + exported `DEFAULT_REPO` constant. `recordInstall` now uses the constant.
|
|
22
|
+
- **[src/commands/version.js](src/commands/version.js)**: drops its local `REPO_DEFAULT` duplicate in favor of the shared `State.DEFAULT_REPO`.
|
|
23
|
+
|
|
24
|
+
### Known follow-up (separate fix)
|
|
25
|
+
|
|
26
|
+
- `--on-divergence` (v3.29.0) is rejected by an **older global CLI** with "unknown option" *before* the `npx baldart@latest` auto-relaunch can self-upgrade (commander validates options first). Tracked separately — workaround: `npm i -g baldart@latest` before using new flags.
|
|
27
|
+
|
|
8
28
|
## [3.35.0] - 2026-05-30
|
|
9
29
|
|
|
10
30
|
De-duplicates the review chain in `/new` and adds a **Review Profile Selector** so the per-card pre-merge gate scales its *depth* by risk instead of running the full multi-agent `/codexreview` on every card — including trivial ones. The per-card gate stays **unconditional** (it always runs); only its breadth varies, and the safety-critical Codex adversarial pass + false-positive gate run on every card regardless of profile. Copertura invariata: every line is still code-reviewed once, every doc doc-reviewed once. **No new `baldart.config.yml` keys** — the profile is computed at runtime from signals already present (the Phase 3.7 high-risk-trigger detector + the QA profile), and lean mode is an internal caller→skill file contract, so the schema-propagation rule does not apply.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.35.
|
|
1
|
+
3.35.1
|
package/package.json
CHANGED
package/src/commands/add.js
CHANGED
|
@@ -14,6 +14,24 @@ async function add(repo, options) {
|
|
|
14
14
|
// rm -rf `.framework/` themselves, so `git.frameworkExists()` is false
|
|
15
15
|
// here and the destructive prompt is never reached.
|
|
16
16
|
const nonInteractive = options && options.yes === true;
|
|
17
|
+
// When `add` is invoked programmatically (e.g. `update --reset`) the caller
|
|
18
|
+
// wants failures to bubble up so it can roll back, instead of add()'s default
|
|
19
|
+
// `process.exit(1)` which would kill the parent before it can recover.
|
|
20
|
+
const throwOnError = options && options.throwOnError === true;
|
|
21
|
+
|
|
22
|
+
// Repo resolution safety net. The CLI entrypoint (bin/baldart.js) defaults
|
|
23
|
+
// `repo` to the upstream before calling here, but programmatic callers may
|
|
24
|
+
// pass undefined. Resolve from the consumer's ledger (preserves forks) so
|
|
25
|
+
// we never reach git.addSubtree() with an undefined repo (the crash that
|
|
26
|
+
// broke `update --reset`: "Cannot read properties of undefined").
|
|
27
|
+
if (!repo) repo = State.resolveRepo();
|
|
28
|
+
if (!repo) {
|
|
29
|
+
UI.error('No framework repository to install from.');
|
|
30
|
+
UI.info('Expected from .baldart/state.json → framework_repo. Recover with:');
|
|
31
|
+
UI.info(' npx baldart add <owner/repo> --yes');
|
|
32
|
+
if (throwOnError) throw new Error('add: repository unresolved (state.json framework_repo empty)');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
17
35
|
|
|
18
36
|
try {
|
|
19
37
|
// Step 1: Verify environment
|
|
@@ -114,10 +132,16 @@ async function add(repo, options) {
|
|
|
114
132
|
UI.error('.framework/ is STILL ignored after auto-heal — install would fail.');
|
|
115
133
|
UI.info(`Source: ${heal.recheck.source}:${heal.recheck.line} (${heal.recheck.pattern})`);
|
|
116
134
|
UI.info('Resolve by removing that rule, then re-run `baldart add`.');
|
|
117
|
-
|
|
135
|
+
// Deliberate abort — flag it so the local catch propagates instead of
|
|
136
|
+
// downgrading to a warning, and let the single outer catch decide
|
|
137
|
+
// throw (throwOnError) vs process.exit (CLI).
|
|
138
|
+
const e = new Error('.framework/ still ignored after auto-heal');
|
|
139
|
+
e.fatalInstall = true;
|
|
140
|
+
throw e;
|
|
118
141
|
}
|
|
119
142
|
}
|
|
120
143
|
} catch (err) {
|
|
144
|
+
if (err && err.fatalInstall) throw err;
|
|
121
145
|
UI.warning(`Could not check .gitignore for .framework: ${err.message}`);
|
|
122
146
|
}
|
|
123
147
|
|
|
@@ -139,11 +163,12 @@ async function add(repo, options) {
|
|
|
139
163
|
' • No internet connection',
|
|
140
164
|
' • Repository not accessible',
|
|
141
165
|
` • Invalid repository: ${repo}`,
|
|
166
|
+
' (resolved from .baldart/state.json → framework_repo, else default)',
|
|
142
167
|
'',
|
|
143
168
|
'Solution:',
|
|
144
169
|
' • Check your internet connection',
|
|
145
|
-
' • Verify repository exists',
|
|
146
|
-
' •
|
|
170
|
+
' • Verify the repository exists / is reachable',
|
|
171
|
+
' • Recover with an explicit repo: npx baldart add <owner/repo> --yes'
|
|
147
172
|
]);
|
|
148
173
|
throw error;
|
|
149
174
|
}
|
|
@@ -293,6 +318,13 @@ async function add(repo, options) {
|
|
|
293
318
|
: `Hooks missing after install: ${verify.missing.join(', ')}. Install is NOT complete.`
|
|
294
319
|
);
|
|
295
320
|
UI.info('Run `npx baldart doctor` to repair, then re-check `npx baldart status`.');
|
|
321
|
+
if (throwOnError) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
verify.malformed
|
|
324
|
+
? 'settings.json malformed — hooks unverifiable'
|
|
325
|
+
: `hooks missing after install: ${verify.missing.join(', ')}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
296
328
|
process.exit(1);
|
|
297
329
|
}
|
|
298
330
|
|
|
@@ -339,6 +371,9 @@ async function add(repo, options) {
|
|
|
339
371
|
|
|
340
372
|
} catch (error) {
|
|
341
373
|
UI.error(`Installation failed: ${error.message}`);
|
|
374
|
+
// Programmatic callers (update --reset) need the failure to bubble so they
|
|
375
|
+
// can roll back; the CLI entrypoint keeps the historical exit-1 behavior.
|
|
376
|
+
if (throwOnError) throw error;
|
|
342
377
|
process.exit(1);
|
|
343
378
|
}
|
|
344
379
|
}
|
package/src/commands/doctor.js
CHANGED
|
@@ -36,7 +36,6 @@ const UpdateNotifier = require('../utils/update-notifier');
|
|
|
36
36
|
const cliPackageJson = require('../../package.json');
|
|
37
37
|
|
|
38
38
|
const FRAMEWORK_DIR = '.framework';
|
|
39
|
-
const REPO_DEFAULT = 'antbald/BALDART';
|
|
40
39
|
const CONFIG_FILE = 'baldart.config.yml';
|
|
41
40
|
|
|
42
41
|
// ──────────────────────────────────────────────────────────────────────
|
|
@@ -214,7 +213,7 @@ async function detectState(cwd, opts = {}) {
|
|
|
214
213
|
};
|
|
215
214
|
}
|
|
216
215
|
|
|
217
|
-
state.remote = await describeRemote(git, ledger.framework_repo ||
|
|
216
|
+
state.remote = await describeRemote(git, ledger.framework_repo || State.DEFAULT_REPO, !!opts.offline);
|
|
218
217
|
state.commitsAhead = await commitsAheadOfRemote(git, state.remote);
|
|
219
218
|
state.workingTreeDirty = await localFrameworkChanges(git);
|
|
220
219
|
|
|
@@ -360,7 +359,9 @@ function planActions(state) {
|
|
|
360
359
|
autoOk: false,
|
|
361
360
|
run: async () => {
|
|
362
361
|
const add = require('./add');
|
|
363
|
-
|
|
362
|
+
// Fork-aware: resolve from the ledger (preserves a consumer's fork)
|
|
363
|
+
// rather than hardcoding the upstream default.
|
|
364
|
+
await add(State.resolveRepo(state.cwd), { branch: 'main' });
|
|
364
365
|
},
|
|
365
366
|
});
|
|
366
367
|
return actions;
|
package/src/commands/update.js
CHANGED
|
@@ -255,6 +255,15 @@ async function runReset(git, options, autoYes) {
|
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
// Resolve the upstream repo BEFORE anything destructive. The reinstall hands
|
|
259
|
+
// off to add() programmatically (no bin-layer default), so an unresolved repo
|
|
260
|
+
// would surface as "Downloading from undefined" / "Cannot read properties of
|
|
261
|
+
// undefined" AFTER .framework/ was already wiped and committed. Resolving here
|
|
262
|
+
// (state.framework_repo → default, never undefined) makes that unreachable and
|
|
263
|
+
// preserves a consumer's fork.
|
|
264
|
+
const repo = State.resolveRepo();
|
|
265
|
+
UI.info(`Reinstall source: ${repo}`);
|
|
266
|
+
|
|
258
267
|
// Safety gate #3: backup tag before doing anything destructive.
|
|
259
268
|
const backupTag = await git.createBackupTag();
|
|
260
269
|
UI.success(`Backup tag created: ${backupTag}`);
|
|
@@ -310,12 +319,42 @@ async function runReset(git, options, autoYes) {
|
|
|
310
319
|
UI.newline();
|
|
311
320
|
UI.info('Re-installing framework via `baldart add`…');
|
|
312
321
|
const addCmd = require('./add');
|
|
313
|
-
// `add(repo, options)` —
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
|
|
322
|
+
// `add(repo, options)` — the repo MUST be threaded explicitly: the bin-layer
|
|
323
|
+
// default ('antbald/BALDART') only applies when add is invoked from the CLI,
|
|
324
|
+
// not on this programmatic path (the v3.28.1 comment claiming "undefined →
|
|
325
|
+
// default from package.json" was wrong and caused the undefined-repo crash).
|
|
326
|
+
// `throwOnError` makes add() re-throw instead of process.exit(1), so a failed
|
|
327
|
+
// reinstall lands in the catch below and we can roll back to the backup tag —
|
|
328
|
+
// the consumer is NEVER left committed-without-.framework/.
|
|
329
|
+
try {
|
|
330
|
+
await addCmd(repo, { yes: true, throwOnError: true });
|
|
331
|
+
} catch (err) {
|
|
332
|
+
UI.newline();
|
|
333
|
+
UI.error(`Reinstall failed: ${err.message}`);
|
|
334
|
+
UI.warning('Rolling back to pre-reset state…');
|
|
335
|
+
try {
|
|
336
|
+
await git.git.raw(['reset', '--hard', backupTag]);
|
|
337
|
+
// The clean-tree gate (hasCleanWorkingTree → status.isClean(), which counts
|
|
338
|
+
// untracked) guaranteed ZERO untracked files at backup-tag time, so every
|
|
339
|
+
// untracked file now is partial-install cruft created by the failed add()
|
|
340
|
+
// (symlinks, copied templates) — safe to sweep. `-d` dirs, no `-x` so
|
|
341
|
+
// gitignored paths are left untouched. Non-fatal: .framework/ is already
|
|
342
|
+
// restored by the reset above regardless.
|
|
343
|
+
try {
|
|
344
|
+
await git.git.raw(['clean', '-fd']);
|
|
345
|
+
} catch (cleanErr) {
|
|
346
|
+
UI.warning(`Could not sweep partial-install leftovers (${cleanErr.message}). .framework/ is restored; run \`git clean -fd\` to tidy.`);
|
|
347
|
+
}
|
|
348
|
+
UI.success(`Restored .framework/ from ${backupTag}. Working tree back to pre-reset state.`);
|
|
349
|
+
} catch (rollbackErr) {
|
|
350
|
+
UI.error(`Auto-rollback FAILED: ${rollbackErr.message}`);
|
|
351
|
+
UI.info(`Recover manually: git reset --hard ${backupTag}`);
|
|
352
|
+
}
|
|
353
|
+
UI.newline();
|
|
354
|
+
UI.info('Then retry, or reinstall explicitly:');
|
|
355
|
+
UI.info(` npx baldart add ${repo} --yes`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
319
358
|
|
|
320
359
|
// Post-restore sanity check — confirm nothing user-owned got clobbered.
|
|
321
360
|
const stillPresent = {
|
package/src/commands/version.js
CHANGED
|
@@ -3,7 +3,6 @@ const UI = require('../utils/ui');
|
|
|
3
3
|
const State = require('../utils/state');
|
|
4
4
|
const UpdateNotifier = require('../utils/update-notifier');
|
|
5
5
|
|
|
6
|
-
const REPO_DEFAULT = 'antbald/BALDART';
|
|
7
6
|
const FRAMEWORK_DIR = '.framework';
|
|
8
7
|
|
|
9
8
|
function fmtDate(iso) {
|
|
@@ -61,7 +60,7 @@ async function version(opts = {}) {
|
|
|
61
60
|
const verbose = opts.verbose === true;
|
|
62
61
|
|
|
63
62
|
// SSOT (v3.25.0+): one query, version-compare authority.
|
|
64
|
-
const repo = state.framework_repo ||
|
|
63
|
+
const repo = state.framework_repo || State.DEFAULT_REPO;
|
|
65
64
|
let status;
|
|
66
65
|
if (opts.offline === true) {
|
|
67
66
|
status = {
|
package/src/utils/git.js
CHANGED
|
@@ -144,6 +144,17 @@ class GitUtils {
|
|
|
144
144
|
// - "https://github.com/owner/repo" -> "https://github.com/owner/repo.git"
|
|
145
145
|
// - "https://github.com/owner/repo.git" -> unchanged
|
|
146
146
|
|
|
147
|
+
// Fail loud and legible instead of "Cannot read properties of undefined
|
|
148
|
+
// (reading 'startsWith')". A falsy repo here means a caller skipped repo
|
|
149
|
+
// resolution (see State.resolveRepo) — surface that, don't crash cryptically.
|
|
150
|
+
if (!repo || typeof repo !== 'string') {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`normalizeRepoUrl: repository non specificato (got ${JSON.stringify(repo)}). ` +
|
|
153
|
+
`Risolvilo via .baldart/state.json → framework_repo o passalo esplicito ` +
|
|
154
|
+
`(es. npx baldart add <owner/repo> --yes).`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
147
158
|
if (repo.startsWith('http://') || repo.startsWith('https://')) {
|
|
148
159
|
return repo.endsWith('.git') ? repo : `${repo}.git`;
|
|
149
160
|
}
|
package/src/utils/state.js
CHANGED
|
@@ -35,6 +35,13 @@ const STATE_FILE = path.join(STATE_DIR, 'state.json');
|
|
|
35
35
|
const STATE_VERSION = 1;
|
|
36
36
|
const HISTORY_LIMIT = 20;
|
|
37
37
|
|
|
38
|
+
// Upstream framework repository. SSOT for the "where do we install from"
|
|
39
|
+
// default — duplicated literals in version.js / doctor.js / recordInstall
|
|
40
|
+
// used to drift independently. Always resolve through `resolveRepo()` so a
|
|
41
|
+
// consumer's fork (recorded in state.framework_repo at install time) is
|
|
42
|
+
// preserved instead of being silently overridden by this constant.
|
|
43
|
+
const DEFAULT_REPO = 'antbald/BALDART';
|
|
44
|
+
|
|
38
45
|
function defaultState() {
|
|
39
46
|
return {
|
|
40
47
|
state_version: STATE_VERSION,
|
|
@@ -87,7 +94,7 @@ function recordInstall({ version, repo }, cwd = process.cwd()) {
|
|
|
87
94
|
const now = new Date().toISOString();
|
|
88
95
|
const wasFirstInstall = !state.installed_version;
|
|
89
96
|
state.installed_version = version;
|
|
90
|
-
state.framework_repo = repo || state.framework_repo ||
|
|
97
|
+
state.framework_repo = repo || state.framework_repo || DEFAULT_REPO;
|
|
91
98
|
if (wasFirstInstall) state.install_date = now;
|
|
92
99
|
state.history = appendHistory(state, {
|
|
93
100
|
event: wasFirstInstall ? 'install' : 'reinstall',
|
|
@@ -142,6 +149,20 @@ function recordPush({ from, to, description }, cwd = process.cwd()) {
|
|
|
142
149
|
return state;
|
|
143
150
|
}
|
|
144
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Resolve the upstream framework repository to install/update from. Cascade:
|
|
154
|
+
* `state.framework_repo` (recorded at install — preserves a consumer's fork)
|
|
155
|
+
* → DEFAULT_REPO. Never returns undefined, so callers that thread the result
|
|
156
|
+
* into git.addSubtree() / normalizeRepoUrl() can't trip the "undefined repo"
|
|
157
|
+
* crash that broke `update --reset` (it called add() with no repo).
|
|
158
|
+
*
|
|
159
|
+
* `baldart.config.yml` and the subtree's git remote are intentionally NOT
|
|
160
|
+
* consulted — neither stores the repo, so they'd be dead lookups.
|
|
161
|
+
*/
|
|
162
|
+
function resolveRepo(cwd = process.cwd()) {
|
|
163
|
+
return load(cwd).framework_repo || DEFAULT_REPO;
|
|
164
|
+
}
|
|
165
|
+
|
|
145
166
|
/**
|
|
146
167
|
* Read the framework's VERSION file from .framework/VERSION. Used by version /
|
|
147
168
|
* status commands to cross-check what the symlinked framework reports vs what
|
|
@@ -164,7 +185,9 @@ module.exports = {
|
|
|
164
185
|
recordUpdate,
|
|
165
186
|
recordPush,
|
|
166
187
|
reconcileInstalledVersion,
|
|
188
|
+
resolveRepo,
|
|
167
189
|
readFrameworkVersion,
|
|
190
|
+
DEFAULT_REPO,
|
|
168
191
|
STATE_FILE,
|
|
169
192
|
STATE_VERSION,
|
|
170
193
|
};
|