@thebassclef/lite 1.9.11 → 1.9.12

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/README.md CHANGED
@@ -67,7 +67,7 @@ your browser.
67
67
 
68
68
  ## Current release
69
69
 
70
- <!-- version-start -->1.9.11<!-- version-end -->
70
+ <!-- version-start -->1.9.12<!-- version-end -->
71
71
 
72
72
  See [CHANGELOG.md](CHANGELOG.md) for the full changelog. Adopter-facing
73
73
  release notes live at [docs.bassclef.dev/docs/whats-new/releases](https://docs.bassclef.dev/docs/whats-new/releases).
package/dist/cli.cjs CHANGED
@@ -272,6 +272,47 @@ function hashContent(content) {
272
272
  normalized = normalized.replace(/\r\n/g, "\n");
273
273
  return node_crypto.createHash("sha256").update(normalized, "utf8").digest("hex");
274
274
  }
275
+ const SCHEMA_VERSION = "1.0.0";
276
+ const WRITER_SLUG = "bassclef-init-npm";
277
+ function registerInstallWrittenPaths(targetDir, paths, now = /* @__PURE__ */ new Date()) {
278
+ if (paths.length === 0) return;
279
+ const manifestPath = node_path.join(targetDir, "state", "install-written-paths.json");
280
+ let manifest = {
281
+ schema_version: SCHEMA_VERSION,
282
+ entries: []
283
+ };
284
+ if (node_fs.existsSync(manifestPath)) {
285
+ try {
286
+ const parsed = JSON.parse(node_fs.readFileSync(manifestPath, "utf8"));
287
+ if (parsed && typeof parsed === "object" && "entries" in parsed && Array.isArray(parsed.entries)) {
288
+ manifest = parsed;
289
+ manifest.schema_version = SCHEMA_VERSION;
290
+ }
291
+ } catch {
292
+ }
293
+ }
294
+ const writtenAt = now.toISOString().replace(/\.\d{3}Z$/, "Z");
295
+ const uniqueInputs = /* @__PURE__ */ new Map();
296
+ for (const p of paths) {
297
+ uniqueInputs.set(p.relativePath, p);
298
+ }
299
+ const incomingPaths = new Set(uniqueInputs.keys());
300
+ manifest.entries = manifest.entries.filter((e) => !incomingPaths.has(e.path));
301
+ for (const p of uniqueInputs.values()) {
302
+ manifest.entries.push({
303
+ path: p.relativePath,
304
+ writer: WRITER_SLUG,
305
+ written_at: writtenAt,
306
+ content_hash: `sha256:${p.contentHashSha256}`
307
+ });
308
+ }
309
+ node_fs.mkdirSync(node_path.dirname(manifestPath), { recursive: true });
310
+ node_fs.writeFileSync(
311
+ manifestPath,
312
+ JSON.stringify(manifest, null, 2) + "\n",
313
+ "utf8"
314
+ );
315
+ }
275
316
  const SUBSTRATE_CONFIG_TEMPLATE_VERSION = "0.0.1";
276
317
  function substrateConfigMdTemplate(pkgVersion) {
277
318
  return [
@@ -549,6 +590,7 @@ function copySubstrate(targetDir, options = {}) {
549
590
  copied: [],
550
591
  copiedEntries: [],
551
592
  refused: [],
593
+ unchanged: [],
552
594
  errored: [],
553
595
  erroredMessages: [],
554
596
  hookCount: 0,
@@ -736,6 +778,16 @@ function copyOne(relPath, bundleRoot, options, result, scopeDecision) {
736
778
  } catch (e) {
737
779
  if (e instanceof WriteError) {
738
780
  if (e.kind === "AlreadyExists") {
781
+ if (scope === "user") {
782
+ try {
783
+ const existing = node_fs.readFileSync(targetPath, "utf8");
784
+ if (hashContent(existing) === hashContent(outputContent)) {
785
+ result.unchanged.push(adopterRelPath);
786
+ return "unchanged";
787
+ }
788
+ } catch {
789
+ }
790
+ }
739
791
  result.refused.push(adopterRelPath);
740
792
  return "refused";
741
793
  }
@@ -1079,13 +1131,14 @@ function buildInitReport(input) {
1079
1131
  else project += 1;
1080
1132
  }
1081
1133
  const refused = input.refused.length;
1134
+ const unchanged = input.unchanged?.length ?? 0;
1082
1135
  const errored = input.errored.length;
1083
1136
  const written = input.configs.length + input.entries.length;
1084
1137
  return {
1085
1138
  schema_version: 3,
1086
1139
  tier: input.tier,
1087
1140
  totals: {
1088
- files: written + refused + errored,
1141
+ files: written + refused + unchanged + errored,
1089
1142
  written,
1090
1143
  project,
1091
1144
  user,
@@ -1098,6 +1151,7 @@ function buildInitReport(input) {
1098
1151
  files: catalog.hooks
1099
1152
  },
1100
1153
  refused,
1154
+ unchanged,
1101
1155
  errored,
1102
1156
  failed: refused + errored
1103
1157
  };
@@ -1203,6 +1257,7 @@ function runInit(argv) {
1203
1257
  entries: outcome.result?.wouldCopyEntries ?? [],
1204
1258
  configs: plans.map((pl) => ({ path: pl.relativePath })),
1205
1259
  refused: outcome.result?.refused ?? [],
1260
+ unchanged: outcome.result?.unchanged ?? [],
1206
1261
  // A dry run still reads every source file, so it can still fail
1207
1262
  // to read one. Hard-coding this empty hid those failures from
1208
1263
  // the JSON (RFC-0005 A-3).
@@ -1301,6 +1356,7 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, say
1301
1356
  }
1302
1357
  const parts = [];
1303
1358
  if (result.copied.length > 0) parts.push(`${result.copied.length} substrate files copied`);
1359
+ if (result.unchanged.length > 0) parts.push(`${result.unchanged.length} already-present`);
1304
1360
  if (result.refused.length > 0) parts.push(`${result.refused.length} refused`);
1305
1361
  if (result.errored.length > 0) parts.push(`${result.errored.length} error(s)`);
1306
1362
  say(`bassclef init: ${parts.join(", ")}.
@@ -1531,12 +1587,25 @@ function runReal$1(plans, force, verbose, targetDir, allowRoot, json, skipStatus
1531
1587
  entries: walker.result?.copiedEntries ?? [],
1532
1588
  configs: results.map((r) => ({ path: r.plan.relativePath })),
1533
1589
  refused: walker.result?.refused ?? [],
1590
+ unchanged: walker.result?.unchanged ?? [],
1534
1591
  errored: walker.result?.errored ?? [],
1535
1592
  hookCount: walker.result?.hookCount ?? 0,
1536
1593
  declaredHooksCopied,
1537
1594
  tier: RESOLVED_TIER
1538
1595
  });
1539
1596
  writeManifest(targetDir, results, walker.result);
1597
+ const createdConfigPaths = results.filter((r) => r.outcome === "created").map((r) => ({
1598
+ relativePath: r.plan.relativePath,
1599
+ contentHashSha256: hashContent(r.plan.content)
1600
+ }));
1601
+ const createdBundlePaths = (walker.result?.copiedEntries ?? []).filter((e) => typeof e.content_hash_sha256 === "string").map((e) => ({
1602
+ relativePath: e.path,
1603
+ contentHashSha256: e.content_hash_sha256
1604
+ }));
1605
+ registerInstallWrittenPaths(targetDir, [
1606
+ ...createdConfigPaths,
1607
+ ...createdBundlePaths
1608
+ ]);
1540
1609
  if (walker.code === 0) {
1541
1610
  say(
1542
1611
  `bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
@@ -1606,6 +1675,15 @@ function writeManifest(targetDir, results, walkerResult) {
1606
1675
  source: "bundle"
1607
1676
  });
1608
1677
  }
1678
+ for (const path of walkerResult.unchanged) {
1679
+ entries.push({
1680
+ path,
1681
+ template: `bundle:${path}`,
1682
+ template_version: index.version,
1683
+ outcome: "unchanged",
1684
+ source: "bundle"
1685
+ });
1686
+ }
1609
1687
  for (const path of walkerResult.errored) {
1610
1688
  entries.push({
1611
1689
  path,
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { version } from "./index.js";
3
- import { realpathSync, statSync, constants, lstatSync, mkdirSync, accessSync, unlinkSync, openSync, writeSync, closeSync, readlinkSync, readFileSync, chmodSync, readdirSync, existsSync, writeFileSync, renameSync } from "node:fs";
3
+ import { realpathSync, statSync, constants, lstatSync, mkdirSync, accessSync, unlinkSync, openSync, writeSync, closeSync, readlinkSync, existsSync, readFileSync, writeFileSync, chmodSync, readdirSync, renameSync } from "node:fs";
4
4
  import { isAbsolute, resolve, dirname, join, normalize, relative, basename as basename$1 } from "node:path";
5
5
  import { homedir } from "node:os";
6
6
  import { createHash } from "node:crypto";
@@ -249,6 +249,47 @@ function hashContent(content) {
249
249
  normalized = normalized.replace(/\r\n/g, "\n");
250
250
  return createHash("sha256").update(normalized, "utf8").digest("hex");
251
251
  }
252
+ const SCHEMA_VERSION = "1.0.0";
253
+ const WRITER_SLUG = "bassclef-init-npm";
254
+ function registerInstallWrittenPaths(targetDir, paths, now = /* @__PURE__ */ new Date()) {
255
+ if (paths.length === 0) return;
256
+ const manifestPath = join(targetDir, "state", "install-written-paths.json");
257
+ let manifest = {
258
+ schema_version: SCHEMA_VERSION,
259
+ entries: []
260
+ };
261
+ if (existsSync(manifestPath)) {
262
+ try {
263
+ const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
264
+ if (parsed && typeof parsed === "object" && "entries" in parsed && Array.isArray(parsed.entries)) {
265
+ manifest = parsed;
266
+ manifest.schema_version = SCHEMA_VERSION;
267
+ }
268
+ } catch {
269
+ }
270
+ }
271
+ const writtenAt = now.toISOString().replace(/\.\d{3}Z$/, "Z");
272
+ const uniqueInputs = /* @__PURE__ */ new Map();
273
+ for (const p of paths) {
274
+ uniqueInputs.set(p.relativePath, p);
275
+ }
276
+ const incomingPaths = new Set(uniqueInputs.keys());
277
+ manifest.entries = manifest.entries.filter((e) => !incomingPaths.has(e.path));
278
+ for (const p of uniqueInputs.values()) {
279
+ manifest.entries.push({
280
+ path: p.relativePath,
281
+ writer: WRITER_SLUG,
282
+ written_at: writtenAt,
283
+ content_hash: `sha256:${p.contentHashSha256}`
284
+ });
285
+ }
286
+ mkdirSync(dirname(manifestPath), { recursive: true });
287
+ writeFileSync(
288
+ manifestPath,
289
+ JSON.stringify(manifest, null, 2) + "\n",
290
+ "utf8"
291
+ );
292
+ }
252
293
  const SUBSTRATE_CONFIG_TEMPLATE_VERSION = "0.0.1";
253
294
  function substrateConfigMdTemplate(pkgVersion) {
254
295
  return [
@@ -526,6 +567,7 @@ function copySubstrate(targetDir, options = {}) {
526
567
  copied: [],
527
568
  copiedEntries: [],
528
569
  refused: [],
570
+ unchanged: [],
529
571
  errored: [],
530
572
  erroredMessages: [],
531
573
  hookCount: 0,
@@ -713,6 +755,16 @@ function copyOne(relPath, bundleRoot, options, result, scopeDecision) {
713
755
  } catch (e) {
714
756
  if (e instanceof WriteError) {
715
757
  if (e.kind === "AlreadyExists") {
758
+ if (scope === "user") {
759
+ try {
760
+ const existing = readFileSync(targetPath, "utf8");
761
+ if (hashContent(existing) === hashContent(outputContent)) {
762
+ result.unchanged.push(adopterRelPath);
763
+ return "unchanged";
764
+ }
765
+ } catch {
766
+ }
767
+ }
716
768
  result.refused.push(adopterRelPath);
717
769
  return "refused";
718
770
  }
@@ -1056,13 +1108,14 @@ function buildInitReport(input) {
1056
1108
  else project += 1;
1057
1109
  }
1058
1110
  const refused = input.refused.length;
1111
+ const unchanged = input.unchanged?.length ?? 0;
1059
1112
  const errored = input.errored.length;
1060
1113
  const written = input.configs.length + input.entries.length;
1061
1114
  return {
1062
1115
  schema_version: 3,
1063
1116
  tier: input.tier,
1064
1117
  totals: {
1065
- files: written + refused + errored,
1118
+ files: written + refused + unchanged + errored,
1066
1119
  written,
1067
1120
  project,
1068
1121
  user,
@@ -1075,6 +1128,7 @@ function buildInitReport(input) {
1075
1128
  files: catalog.hooks
1076
1129
  },
1077
1130
  refused,
1131
+ unchanged,
1078
1132
  errored,
1079
1133
  failed: refused + errored
1080
1134
  };
@@ -1180,6 +1234,7 @@ function runInit(argv) {
1180
1234
  entries: outcome.result?.wouldCopyEntries ?? [],
1181
1235
  configs: plans.map((pl) => ({ path: pl.relativePath })),
1182
1236
  refused: outcome.result?.refused ?? [],
1237
+ unchanged: outcome.result?.unchanged ?? [],
1183
1238
  // A dry run still reads every source file, so it can still fail
1184
1239
  // to read one. Hard-coding this empty hid those failures from
1185
1240
  // the JSON (RFC-0005 A-3).
@@ -1278,6 +1333,7 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, say
1278
1333
  }
1279
1334
  const parts = [];
1280
1335
  if (result.copied.length > 0) parts.push(`${result.copied.length} substrate files copied`);
1336
+ if (result.unchanged.length > 0) parts.push(`${result.unchanged.length} already-present`);
1281
1337
  if (result.refused.length > 0) parts.push(`${result.refused.length} refused`);
1282
1338
  if (result.errored.length > 0) parts.push(`${result.errored.length} error(s)`);
1283
1339
  say(`bassclef init: ${parts.join(", ")}.
@@ -1508,12 +1564,25 @@ function runReal$1(plans, force, verbose, targetDir, allowRoot, json, skipStatus
1508
1564
  entries: walker.result?.copiedEntries ?? [],
1509
1565
  configs: results.map((r) => ({ path: r.plan.relativePath })),
1510
1566
  refused: walker.result?.refused ?? [],
1567
+ unchanged: walker.result?.unchanged ?? [],
1511
1568
  errored: walker.result?.errored ?? [],
1512
1569
  hookCount: walker.result?.hookCount ?? 0,
1513
1570
  declaredHooksCopied,
1514
1571
  tier: RESOLVED_TIER
1515
1572
  });
1516
1573
  writeManifest(targetDir, results, walker.result);
1574
+ const createdConfigPaths = results.filter((r) => r.outcome === "created").map((r) => ({
1575
+ relativePath: r.plan.relativePath,
1576
+ contentHashSha256: hashContent(r.plan.content)
1577
+ }));
1578
+ const createdBundlePaths = (walker.result?.copiedEntries ?? []).filter((e) => typeof e.content_hash_sha256 === "string").map((e) => ({
1579
+ relativePath: e.path,
1580
+ contentHashSha256: e.content_hash_sha256
1581
+ }));
1582
+ registerInstallWrittenPaths(targetDir, [
1583
+ ...createdConfigPaths,
1584
+ ...createdBundlePaths
1585
+ ]);
1517
1586
  if (walker.code === 0) {
1518
1587
  say(
1519
1588
  `bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
@@ -1583,6 +1652,15 @@ function writeManifest(targetDir, results, walkerResult) {
1583
1652
  source: "bundle"
1584
1653
  });
1585
1654
  }
1655
+ for (const path of walkerResult.unchanged) {
1656
+ entries.push({
1657
+ path,
1658
+ template: `bundle:${path}`,
1659
+ template_version: version,
1660
+ outcome: "unchanged",
1661
+ source: "bundle"
1662
+ });
1663
+ }
1586
1664
  for (const path of walkerResult.errored) {
1587
1665
  entries.push({
1588
1666
  path,
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const version = "1.9.11";
3
+ const version = "1.9.12";
4
4
  exports.version = version;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version: "1.9.11";
1
+ export declare const version: "1.9.12";
2
2
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "1.9.11";
1
+ const version = "1.9.12";
2
2
  export {
3
3
  version
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thebassclef/lite",
3
- "version": "1.9.11",
3
+ "version": "1.9.12",
4
4
  "description": "Bassclef CLI — install and upgrade bassclef in your project with two commands.",
5
5
  "keywords": [
6
6
  "bassclef",