@thebassclef/lite 1.1.0 → 1.1.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/dist/cli.cjs CHANGED
@@ -313,14 +313,15 @@ function substrateConfigMdTemplate(pkgVersion) {
313
313
  ].join("\n");
314
314
  }
315
315
  const MANIFEST_SCHEMA_VERSION = "0.1.0";
316
- const MANIFEST_SHAPE_VERSION = 2;
316
+ const MANIFEST_SHAPE_VERSION = 3;
317
+ const GENERATED_BY = "@thebassclef/lite";
317
318
  function manifestTemplate(input) {
318
319
  const value = {
319
320
  schema_version: MANIFEST_SHAPE_VERSION,
320
321
  $bassclef: {
321
322
  template: "init.manifest.json",
322
323
  manifest_schema_version: MANIFEST_SCHEMA_VERSION,
323
- generated_by: "@thebassclef/core",
324
+ generated_by: GENERATED_BY,
324
325
  generated_by_version: input.pkgVersion
325
326
  },
326
327
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -388,7 +389,7 @@ function readManifest(targetDir) {
388
389
  if (compareSchemaVersion(schema, MANIFEST_SCHEMA_VERSION) > 0) {
389
390
  throw new ManifestReadError(
390
391
  "SchemaTooNew",
391
- `manifest schema version ${schema} is newer than this package understands (${MANIFEST_SCHEMA_VERSION}). Upgrade @thebassclef/core.`
392
+ `manifest schema version ${schema} is newer than this package understands (${MANIFEST_SCHEMA_VERSION}). Upgrade @thebassclef/lite.`
392
393
  );
393
394
  }
394
395
  const obj = parsed;
@@ -541,7 +542,10 @@ function copySubstrate(targetDir, options = {}) {
541
542
  hookCount: 0,
542
543
  wiringVersion: manifest.version
543
544
  };
544
- if (options.dryRun) result.wouldCopy = [];
545
+ if (options.dryRun) {
546
+ result.wouldCopy = [];
547
+ result.wouldCopyEntries = [];
548
+ }
545
549
  const files = walkDistTree(bundleRoot);
546
550
  const groups = groupByTopDirectory(files);
547
551
  const bundleHookRelPaths = new Set(files.filter(isHookFile));
@@ -697,6 +701,11 @@ function copyOne(relPath, bundleRoot, options, result, scopeDecision) {
697
701
  const outputContent = options.transform ? options.transform(adopterRelPath, content) : content;
698
702
  if (options.dryRun) {
699
703
  result.wouldCopy?.push(adopterRelPath);
704
+ result.wouldCopyEntries?.push({
705
+ path: adopterRelPath,
706
+ scope,
707
+ content_hash_sha256: hashContent(outputContent)
708
+ });
700
709
  return "wouldCopy";
701
710
  }
702
711
  try {
@@ -706,7 +715,11 @@ function copyOne(relPath, bundleRoot, options, result, scopeDecision) {
706
715
  setExecutable(targetPath);
707
716
  }
708
717
  result.copied.push(adopterRelPath);
709
- result.copiedEntries.push({ path: adopterRelPath, scope });
718
+ result.copiedEntries.push({
719
+ path: adopterRelPath,
720
+ scope,
721
+ content_hash_sha256: hashContent(outputContent)
722
+ });
710
723
  return "copied";
711
724
  } catch (e) {
712
725
  if (e instanceof WriteError) {
@@ -783,7 +796,92 @@ function dirExists(p) {
783
796
  return false;
784
797
  }
785
798
  }
799
+ const FAMILIES = [
800
+ "skills",
801
+ "rules",
802
+ "agents",
803
+ "luminaries",
804
+ "hooks",
805
+ "libs",
806
+ "adrs",
807
+ "standards",
808
+ "templates",
809
+ "presence-templates",
810
+ "scripts",
811
+ "root-docs",
812
+ "other"
813
+ ];
814
+ const UNDER_CLAUDE = `${CLAUDE_TARGET_ROOT}/`;
815
+ const PREFIX_RULES = [
816
+ [`${UNDER_CLAUDE}skills/`, "skills"],
817
+ [`${UNDER_CLAUDE}rules/`, "rules"],
818
+ [`${UNDER_CLAUDE}agents/`, "agents"],
819
+ [`${UNDER_CLAUDE}luminaries/`, "luminaries"],
820
+ [`${UNDER_CLAUDE}hooks/`, "hooks"],
821
+ ["lib/", "libs"],
822
+ ["architecture/decisions/", "adrs"],
823
+ ["standards/", "standards"],
824
+ ["templates/", "templates"],
825
+ ["presence/install/", "presence-templates"],
826
+ ["scripts/", "scripts"]
827
+ ];
828
+ function classifyEntry(path) {
829
+ for (const [prefix, family] of PREFIX_RULES) {
830
+ if (path.startsWith(prefix)) return family;
831
+ }
832
+ if (!path.includes("/") && /^[A-Z]/.test(path)) return "root-docs";
833
+ return "other";
834
+ }
835
+ function emptyCatalogCounts() {
836
+ const out = {};
837
+ for (const f of FAMILIES) out[f] = 0;
838
+ return out;
839
+ }
840
+ function buildInitReport(input) {
841
+ const catalog = emptyCatalogCounts();
842
+ let project = 0;
843
+ let user = 0;
844
+ for (const e of input.entries) {
845
+ catalog[classifyEntry(e.path)] += 1;
846
+ if (e.scope === "user") user += 1;
847
+ else project += 1;
848
+ }
849
+ const refused = input.refused.length;
850
+ const errored = input.errored.length;
851
+ const written = input.configs.length + input.entries.length;
852
+ return {
853
+ schema_version: 3,
854
+ tier: input.tier,
855
+ totals: {
856
+ files: written + refused + errored,
857
+ written,
858
+ project,
859
+ user,
860
+ configs: input.configs.length
861
+ },
862
+ catalog,
863
+ hooks: {
864
+ declared: input.hookCount,
865
+ copied: input.declaredHooksCopied,
866
+ files: catalog.hooks
867
+ },
868
+ refused,
869
+ errored,
870
+ failed: refused + errored
871
+ };
872
+ }
873
+ function renderJsonReport(report, write) {
874
+ write(JSON.stringify(report) + "\n");
875
+ }
786
876
  const RESOLVED_TIER = "lite";
877
+ const SAY_HUMAN = (s) => {
878
+ process.stdout.write(s);
879
+ };
880
+ const SAY_QUIET = () => {
881
+ };
882
+ function makeSay(json) {
883
+ return json ? SAY_QUIET : SAY_HUMAN;
884
+ }
787
885
  const PLACEHOLDER_FILES = /* @__PURE__ */ new Set([
788
886
  "CLAUDE.md",
789
887
  "docs/whereami.md",
@@ -825,7 +923,7 @@ function runInit(argv) {
825
923
  }
826
924
  throw e;
827
925
  }
828
- const advisoryOutcome = maybeEmitUpgradeAdvisory(targetDir, args.yes);
926
+ const advisoryOutcome = maybeEmitUpgradeAdvisory(targetDir, args.yes, args.json);
829
927
  if (advisoryOutcome === "refused") return 1;
830
928
  const upgradeApproved = advisoryOutcome === "upgrade-approved";
831
929
  if (!args.force && !args.dryRun && !upgradeApproved) {
@@ -847,26 +945,55 @@ function runInit(argv) {
847
945
  templateVersion: SUBSTRATE_CONFIG_TEMPLATE_VERSION
848
946
  }
849
947
  ];
948
+ const say = makeSay(args.json);
850
949
  if (args.dryRun) {
851
- runDryRun$1(plans);
852
- return dispatchSubstrateCopy(targetDir, args.force || upgradeApproved, args.verbose, true, args.allowRoot, args.json);
853
- }
854
- return runReal$1(plans, args.force || upgradeApproved, args.verbose, targetDir, args.allowRoot, args.json);
855
- }
856
- function maybeEmitUpgradeAdvisory(targetDir, yes) {
950
+ runDryRun$1(plans, say);
951
+ const outcome = dispatchSubstrateCopy(
952
+ targetDir,
953
+ args.force || upgradeApproved,
954
+ args.verbose,
955
+ true,
956
+ args.allowRoot,
957
+ say
958
+ );
959
+ if (args.json) {
960
+ const report = buildInitReport({
961
+ entries: outcome.result?.wouldCopyEntries ?? [],
962
+ configs: plans.map((pl) => ({ path: pl.relativePath })),
963
+ refused: outcome.result?.refused ?? [],
964
+ // A dry run still reads every source file, so it can still fail
965
+ // to read one. Hard-coding this empty hid those failures from
966
+ // the JSON (RFC-0005 A-3).
967
+ errored: outcome.result?.errored ?? [],
968
+ hookCount: outcome.result?.hookCount ?? 0,
969
+ declaredHooksCopied: 0,
970
+ tier: RESOLVED_TIER
971
+ });
972
+ renderJsonReport(report, (t) => {
973
+ process.stdout.write(t);
974
+ });
975
+ }
976
+ return outcome.code;
977
+ }
978
+ return runReal$1(plans, args.force || upgradeApproved, args.verbose, targetDir, args.allowRoot, args.json, say);
979
+ }
980
+ function maybeEmitUpgradeAdvisory(targetDir, yes, json) {
981
+ const prompt = json ? (t) => {
982
+ process.stderr.write(t);
983
+ } : SAY_HUMAN;
857
984
  const manifestPath = node_path.join(targetDir, MANIFEST_RELATIVE_PATH);
858
985
  if (!node_fs.existsSync(manifestPath)) return "ok";
859
986
  const version = readManifestShapeVersion(targetDir);
860
987
  if (version !== null && version >= 2) return "ok";
861
- process.stdout.write(
988
+ prompt(
862
989
  `bassclef init: cli 1.0.1 introduces user-scope hook installation at ~/${HOOKS_SUBPATH}. cli 1.0.0 did not write there.
863
990
  `
864
991
  );
865
992
  if (yes) return "upgrade-approved";
866
- process.stdout.write("bassclef init: continue? (y/N) ");
993
+ prompt("bassclef init: continue? (y/N) ");
867
994
  const answer = readOneLineFromStdin();
868
995
  if (answer === "" || answer === "y" || answer === "Y") return "upgrade-approved";
869
- process.stdout.write("bassclef init: aborted by adopter.\n");
996
+ prompt("bassclef init: aborted by adopter.\n");
870
997
  return "refused";
871
998
  }
872
999
  function readOneLineFromStdin() {
@@ -878,7 +1005,7 @@ function readOneLineFromStdin() {
878
1005
  return "";
879
1006
  }
880
1007
  }
881
- function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, json) {
1008
+ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, say) {
882
1009
  const substitute = makePlaceholderTransform(targetDir);
883
1010
  let result;
884
1011
  try {
@@ -892,9 +1019,9 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
892
1019
  if (e instanceof CopyFailure) {
893
1020
  process.stderr.write(`bassclef init: ${e.message}
894
1021
  `);
895
- if (e.kind === "ManifestMissing") return 4;
896
- if (e.kind === "SchemaIncompatible") return 5;
897
- return 2;
1022
+ if (e.kind === "ManifestMissing") return { code: 4 };
1023
+ if (e.kind === "SchemaIncompatible") return { code: 5 };
1024
+ return { code: 2 };
898
1025
  }
899
1026
  throw e;
900
1027
  }
@@ -902,23 +1029,23 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
902
1029
  const wouldCopy = result.wouldCopy ?? [];
903
1030
  for (const relativePath of wouldCopy) {
904
1031
  const targetPath = node_path.join(targetDir, relativePath);
905
- process.stdout.write(` ${"would create".padEnd(14)} ${targetPath}
1032
+ say(` ${"would create".padEnd(14)} ${targetPath}
906
1033
  `);
907
1034
  }
908
1035
  if (wouldCopy.length > 0) {
909
- process.stdout.write(
1036
+ say(
910
1037
  `bassclef init: ${wouldCopy.length} substrate files would be copied.
911
1038
  `
912
1039
  );
913
1040
  }
914
- process.stdout.write(
1041
+ say(
915
1042
  `bassclef init: ${result.hookCount} hooks armed (${RESOLVED_TIER} tier).
916
1043
  `
917
1044
  );
918
- return 0;
1045
+ return { code: 0, result };
919
1046
  }
920
1047
  if (result.copied.length === 0 && result.refused.length === 0 && result.errored.length === 0) {
921
- return 0;
1048
+ return { code: 0, result };
922
1049
  }
923
1050
  const groupCounts = /* @__PURE__ */ new Map();
924
1051
  for (const path of result.copied) {
@@ -927,17 +1054,17 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
927
1054
  groupCounts.set(top, (groupCounts.get(top) ?? 0) + 1);
928
1055
  }
929
1056
  for (const [directory, count] of groupCounts) {
930
- process.stdout.write(` ${directory}: ${count} files copied
1057
+ say(` ${directory}: ${count} files copied
931
1058
  `);
932
1059
  }
933
1060
  const parts = [];
934
1061
  if (result.copied.length > 0) parts.push(`${result.copied.length} substrate files copied`);
935
1062
  if (result.refused.length > 0) parts.push(`${result.refused.length} refused`);
936
1063
  if (result.errored.length > 0) parts.push(`${result.errored.length} error(s)`);
937
- process.stdout.write(`bassclef init: ${parts.join(", ")}.
1064
+ say(`bassclef init: ${parts.join(", ")}.
938
1065
  `);
939
1066
  const grandTotal = 1 + result.copied.length;
940
- process.stdout.write(
1067
+ say(
941
1068
  `bassclef init: ${grandTotal} files total (1 config + ${result.copied.length} substrate).
942
1069
  `
943
1070
  );
@@ -954,12 +1081,12 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
954
1081
  const projectScope = copiedHookEntries.filter((e) => e.scope === "project").length;
955
1082
  const scopeSuffix = userScope + projectScope > 0 ? ` ${projectScope} in <repo>/${HOOKS_SUBPATH.replace(/\/$/, "")}, ${userScope} in ~/${HOOKS_SUBPATH.replace(/\/$/, "")}.` : "";
956
1083
  if (failedCount > 0) {
957
- process.stdout.write(
1084
+ say(
958
1085
  `bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix} ${failedCount} failed — see errors above. Rerun bassclef init to retry.
959
1086
  `
960
1087
  );
961
1088
  } else {
962
- process.stdout.write(
1089
+ say(
963
1090
  `bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix}
964
1091
  `
965
1092
  );
@@ -1004,7 +1131,7 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
1004
1131
  catalogCounts.luminaries > 0 ? `${catalogCounts.luminaries} luminaries` : null
1005
1132
  ].filter((s) => s !== null);
1006
1133
  if (claudeCounts.length > 0) {
1007
- process.stdout.write(
1134
+ say(
1008
1135
  `bassclef init: Installed ${claudeCounts.join(", ")} under <repo>/.claude/.
1009
1136
  `
1010
1137
  );
@@ -1019,32 +1146,22 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
1019
1146
  catalogCounts.scripts > 0 ? `${catalogCounts.scripts} scripts` : null
1020
1147
  ].filter((s) => s !== null);
1021
1148
  if (otherCounts.length > 0) {
1022
- process.stdout.write(
1149
+ say(
1023
1150
  `bassclef init: Installed ${otherCounts.join(", ")} under <repo>/.
1024
1151
  `
1025
1152
  );
1026
1153
  }
1027
- process.stdout.write(
1154
+ say(
1028
1155
  `bassclef init: ${result.refused.length} files refused (path collision).` + (result.refused.length > 0 ? ` Use --force to overwrite existing files.` : ``) + `
1029
1156
  `
1030
1157
  );
1031
- if (json) {
1032
- const report = {
1033
- copied: copiedCount,
1034
- declared: declaredCount,
1035
- failed: failedCount,
1036
- scope_counts: { user: userScope, project: projectScope },
1037
- tier: RESOLVED_TIER
1038
- };
1039
- process.stderr.write(JSON.stringify(report) + "\n");
1040
- }
1041
1158
  if (verbose && result.erroredMessages) {
1042
1159
  for (const msg of result.erroredMessages) {
1043
1160
  process.stderr.write(` substrate: ${msg}
1044
1161
  `);
1045
1162
  }
1046
1163
  }
1047
- return result.errored.length > 0 ? 2 : 0;
1164
+ return { code: result.errored.length > 0 ? 2 : 0, result };
1048
1165
  }
1049
1166
  function makePlaceholderTransform(targetDir) {
1050
1167
  const repoName = node_path.basename(targetDir);
@@ -1054,7 +1171,7 @@ function makePlaceholderTransform(targetDir) {
1054
1171
  return content.replace(/\[REPO_NAME\]/g, repoName).replace(/\[ISO_TIMESTAMP\]/g, timestamp).replace(/\[TIER\]/g, RESOLVED_TIER).replace(/\[Repo name\]/g, repoName).replace(/<tier>/g, RESOLVED_TIER);
1055
1172
  };
1056
1173
  }
1057
- function runDryRun$1(plans) {
1174
+ function runDryRun$1(plans, say) {
1058
1175
  for (const p of plans) {
1059
1176
  let planned;
1060
1177
  let extra = "";
@@ -1074,12 +1191,12 @@ function runDryRun$1(plans) {
1074
1191
  planned = "would skip";
1075
1192
  }
1076
1193
  }
1077
- process.stdout.write(` ${planned.padEnd(14)} ${p.fullPath}${extra}
1194
+ say(` ${planned.padEnd(14)} ${p.fullPath}${extra}
1078
1195
  `);
1079
1196
  }
1080
1197
  return 0;
1081
1198
  }
1082
- function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1199
+ function runReal$1(plans, force, verbose, targetDir, allowRoot, json, say) {
1083
1200
  const results = [];
1084
1201
  let anyRefused = false;
1085
1202
  let anyError = false;
@@ -1127,7 +1244,7 @@ function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1127
1244
  if (r.outcome === "error") label = `error (${r.message ?? "unknown"})`;
1128
1245
  else if (r.outcome === "refused") label = "refused";
1129
1246
  else label = r.outcome;
1130
- process.stdout.write(` ${label.padEnd(10)} ${r.plan.fullPath}
1247
+ say(` ${label.padEnd(10)} ${r.plan.fullPath}
1131
1248
  `);
1132
1249
  }
1133
1250
  }
@@ -1149,38 +1266,59 @@ function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1149
1266
  return 2;
1150
1267
  }
1151
1268
  if (anyRefused && created > 0) {
1152
- process.stdout.write(
1269
+ say(
1153
1270
  `bassclef init: ${created} config files created, ${unchanged} unchanged. Pass --force to overwrite.
1154
1271
  `
1155
1272
  );
1156
1273
  } else if (created === 0 && unchanged === plans.length) {
1157
- process.stdout.write("bassclef init: already initialized. No changes.\n");
1274
+ say("bassclef init: already initialized. No changes.\n");
1158
1275
  } else {
1159
- process.stdout.write(`bassclef init: ${created} config files created, ${unchanged} unchanged.
1276
+ say(`bassclef init: ${created} config files created, ${unchanged} unchanged.
1160
1277
  `);
1161
1278
  }
1162
- const walkerExit = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, json);
1163
- writeManifest(targetDir, results);
1164
- if (walkerExit === 0) {
1165
- process.stdout.write(
1279
+ const walker = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, say);
1280
+ const declaredLeaves = readDeclaredCommandLeaves(
1281
+ node_path.join(targetDir, ".claude", "settings.json")
1282
+ );
1283
+ const declaredHooksCopied = (walker.result?.copiedEntries ?? []).filter(
1284
+ (e) => e.path.startsWith(HOOKS_SUBPATH) && e.path.endsWith(".sh") && declaredLeaves.has(node_path.basename(e.path))
1285
+ ).length;
1286
+ const report = buildInitReport({
1287
+ entries: walker.result?.copiedEntries ?? [],
1288
+ configs: results.map((r) => ({ path: r.plan.relativePath })),
1289
+ refused: walker.result?.refused ?? [],
1290
+ errored: walker.result?.errored ?? [],
1291
+ hookCount: walker.result?.hookCount ?? 0,
1292
+ declaredHooksCopied,
1293
+ tier: RESOLVED_TIER
1294
+ });
1295
+ writeManifest(targetDir, results, walker.result);
1296
+ if (walker.code === 0) {
1297
+ say(
1166
1298
  `bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
1167
1299
  `
1168
1300
  );
1169
1301
  }
1170
- return walkerExit;
1302
+ if (json) {
1303
+ renderJsonReport(report, (t) => {
1304
+ process.stdout.write(t);
1305
+ });
1306
+ }
1307
+ return walker.code;
1171
1308
  }
1172
1309
  function shouldRefuseRoot(currentUid, allowRoot) {
1173
1310
  if (currentUid === void 0) return false;
1174
1311
  if (currentUid !== 0) return false;
1175
1312
  return !allowRoot;
1176
1313
  }
1177
- function writeManifest(targetDir, results) {
1314
+ function writeManifest(targetDir, results, walkerResult) {
1178
1315
  const entries = results.map((r) => {
1179
1316
  const entry = {
1180
1317
  path: r.plan.relativePath,
1181
1318
  template: r.plan.templateName,
1182
1319
  template_version: r.plan.templateVersion,
1183
- outcome: r.outcome
1320
+ outcome: r.outcome,
1321
+ source: "config-composer"
1184
1322
  };
1185
1323
  if (r.outcome === "created") {
1186
1324
  entry.content_hash_sha256 = hashContent(r.plan.content);
@@ -1188,6 +1326,38 @@ function writeManifest(targetDir, results) {
1188
1326
  }
1189
1327
  return entry;
1190
1328
  });
1329
+ if (walkerResult) {
1330
+ for (const copied of walkerResult.copiedEntries) {
1331
+ entries.push({
1332
+ path: copied.path,
1333
+ template: `bundle:${copied.path}`,
1334
+ template_version: index.version,
1335
+ outcome: "created",
1336
+ source: "bundle",
1337
+ scope: copied.scope,
1338
+ ...copied.content_hash_sha256 ? { content_hash_sha256: copied.content_hash_sha256 } : {},
1339
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1340
+ });
1341
+ }
1342
+ for (const path of walkerResult.refused) {
1343
+ entries.push({
1344
+ path,
1345
+ template: `bundle:${path}`,
1346
+ template_version: index.version,
1347
+ outcome: "refused",
1348
+ source: "bundle"
1349
+ });
1350
+ }
1351
+ for (const path of walkerResult.errored) {
1352
+ entries.push({
1353
+ path,
1354
+ template: `bundle:${path}`,
1355
+ template_version: index.version,
1356
+ outcome: "error",
1357
+ source: "bundle"
1358
+ });
1359
+ }
1360
+ }
1191
1361
  const manifestDir = node_path.join(targetDir, ".bassclef");
1192
1362
  const manifestPath = node_path.join(manifestDir, "init.manifest.json");
1193
1363
  const content = manifestTemplate({
@@ -1198,7 +1368,12 @@ function writeManifest(targetDir, results) {
1198
1368
  try {
1199
1369
  mkdirSafely(manifestDir);
1200
1370
  writeSafely(manifestPath, content, { force: true });
1201
- } catch {
1371
+ } catch (e) {
1372
+ const reason = e instanceof Error ? e.message : String(e);
1373
+ process.stderr.write(
1374
+ `bassclef init: could not write ${manifestPath} (${reason}). The files were written. Run \`bassclef init --force\` to rebuild the record.
1375
+ `
1376
+ );
1202
1377
  }
1203
1378
  }
1204
1379
  function usage$2() {
@@ -1649,7 +1824,10 @@ function emitL2Output(decisions, verbose) {
1649
1824
  }
1650
1825
  }
1651
1826
  function updateManifestEntry(manifest, path, patch) {
1652
- const idx = manifest.files.findIndex((f) => f.path === path);
1827
+ const wantScope = patch.scope;
1828
+ const idx = manifest.files.findIndex(
1829
+ (f) => f.path === path && (wantScope === void 0 || f.scope === wantScope)
1830
+ );
1653
1831
  if (idx < 0) {
1654
1832
  manifest.files.push({
1655
1833
  path,
package/dist/cli.js CHANGED
@@ -290,14 +290,15 @@ function substrateConfigMdTemplate(pkgVersion) {
290
290
  ].join("\n");
291
291
  }
292
292
  const MANIFEST_SCHEMA_VERSION = "0.1.0";
293
- const MANIFEST_SHAPE_VERSION = 2;
293
+ const MANIFEST_SHAPE_VERSION = 3;
294
+ const GENERATED_BY = "@thebassclef/lite";
294
295
  function manifestTemplate(input) {
295
296
  const value = {
296
297
  schema_version: MANIFEST_SHAPE_VERSION,
297
298
  $bassclef: {
298
299
  template: "init.manifest.json",
299
300
  manifest_schema_version: MANIFEST_SCHEMA_VERSION,
300
- generated_by: "@thebassclef/core",
301
+ generated_by: GENERATED_BY,
301
302
  generated_by_version: input.pkgVersion
302
303
  },
303
304
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -365,7 +366,7 @@ function readManifest(targetDir) {
365
366
  if (compareSchemaVersion(schema, MANIFEST_SCHEMA_VERSION) > 0) {
366
367
  throw new ManifestReadError(
367
368
  "SchemaTooNew",
368
- `manifest schema version ${schema} is newer than this package understands (${MANIFEST_SCHEMA_VERSION}). Upgrade @thebassclef/core.`
369
+ `manifest schema version ${schema} is newer than this package understands (${MANIFEST_SCHEMA_VERSION}). Upgrade @thebassclef/lite.`
369
370
  );
370
371
  }
371
372
  const obj = parsed;
@@ -518,7 +519,10 @@ function copySubstrate(targetDir, options = {}) {
518
519
  hookCount: 0,
519
520
  wiringVersion: manifest.version
520
521
  };
521
- if (options.dryRun) result.wouldCopy = [];
522
+ if (options.dryRun) {
523
+ result.wouldCopy = [];
524
+ result.wouldCopyEntries = [];
525
+ }
522
526
  const files = walkDistTree(bundleRoot);
523
527
  const groups = groupByTopDirectory(files);
524
528
  const bundleHookRelPaths = new Set(files.filter(isHookFile));
@@ -674,6 +678,11 @@ function copyOne(relPath, bundleRoot, options, result, scopeDecision) {
674
678
  const outputContent = options.transform ? options.transform(adopterRelPath, content) : content;
675
679
  if (options.dryRun) {
676
680
  result.wouldCopy?.push(adopterRelPath);
681
+ result.wouldCopyEntries?.push({
682
+ path: adopterRelPath,
683
+ scope,
684
+ content_hash_sha256: hashContent(outputContent)
685
+ });
677
686
  return "wouldCopy";
678
687
  }
679
688
  try {
@@ -683,7 +692,11 @@ function copyOne(relPath, bundleRoot, options, result, scopeDecision) {
683
692
  setExecutable(targetPath);
684
693
  }
685
694
  result.copied.push(adopterRelPath);
686
- result.copiedEntries.push({ path: adopterRelPath, scope });
695
+ result.copiedEntries.push({
696
+ path: adopterRelPath,
697
+ scope,
698
+ content_hash_sha256: hashContent(outputContent)
699
+ });
687
700
  return "copied";
688
701
  } catch (e) {
689
702
  if (e instanceof WriteError) {
@@ -760,7 +773,92 @@ function dirExists(p) {
760
773
  return false;
761
774
  }
762
775
  }
776
+ const FAMILIES = [
777
+ "skills",
778
+ "rules",
779
+ "agents",
780
+ "luminaries",
781
+ "hooks",
782
+ "libs",
783
+ "adrs",
784
+ "standards",
785
+ "templates",
786
+ "presence-templates",
787
+ "scripts",
788
+ "root-docs",
789
+ "other"
790
+ ];
791
+ const UNDER_CLAUDE = `${CLAUDE_TARGET_ROOT}/`;
792
+ const PREFIX_RULES = [
793
+ [`${UNDER_CLAUDE}skills/`, "skills"],
794
+ [`${UNDER_CLAUDE}rules/`, "rules"],
795
+ [`${UNDER_CLAUDE}agents/`, "agents"],
796
+ [`${UNDER_CLAUDE}luminaries/`, "luminaries"],
797
+ [`${UNDER_CLAUDE}hooks/`, "hooks"],
798
+ ["lib/", "libs"],
799
+ ["architecture/decisions/", "adrs"],
800
+ ["standards/", "standards"],
801
+ ["templates/", "templates"],
802
+ ["presence/install/", "presence-templates"],
803
+ ["scripts/", "scripts"]
804
+ ];
805
+ function classifyEntry(path) {
806
+ for (const [prefix, family] of PREFIX_RULES) {
807
+ if (path.startsWith(prefix)) return family;
808
+ }
809
+ if (!path.includes("/") && /^[A-Z]/.test(path)) return "root-docs";
810
+ return "other";
811
+ }
812
+ function emptyCatalogCounts() {
813
+ const out = {};
814
+ for (const f of FAMILIES) out[f] = 0;
815
+ return out;
816
+ }
817
+ function buildInitReport(input) {
818
+ const catalog = emptyCatalogCounts();
819
+ let project = 0;
820
+ let user = 0;
821
+ for (const e of input.entries) {
822
+ catalog[classifyEntry(e.path)] += 1;
823
+ if (e.scope === "user") user += 1;
824
+ else project += 1;
825
+ }
826
+ const refused = input.refused.length;
827
+ const errored = input.errored.length;
828
+ const written = input.configs.length + input.entries.length;
829
+ return {
830
+ schema_version: 3,
831
+ tier: input.tier,
832
+ totals: {
833
+ files: written + refused + errored,
834
+ written,
835
+ project,
836
+ user,
837
+ configs: input.configs.length
838
+ },
839
+ catalog,
840
+ hooks: {
841
+ declared: input.hookCount,
842
+ copied: input.declaredHooksCopied,
843
+ files: catalog.hooks
844
+ },
845
+ refused,
846
+ errored,
847
+ failed: refused + errored
848
+ };
849
+ }
850
+ function renderJsonReport(report, write) {
851
+ write(JSON.stringify(report) + "\n");
852
+ }
763
853
  const RESOLVED_TIER = "lite";
854
+ const SAY_HUMAN = (s) => {
855
+ process.stdout.write(s);
856
+ };
857
+ const SAY_QUIET = () => {
858
+ };
859
+ function makeSay(json) {
860
+ return json ? SAY_QUIET : SAY_HUMAN;
861
+ }
764
862
  const PLACEHOLDER_FILES = /* @__PURE__ */ new Set([
765
863
  "CLAUDE.md",
766
864
  "docs/whereami.md",
@@ -802,7 +900,7 @@ function runInit(argv) {
802
900
  }
803
901
  throw e;
804
902
  }
805
- const advisoryOutcome = maybeEmitUpgradeAdvisory(targetDir, args.yes);
903
+ const advisoryOutcome = maybeEmitUpgradeAdvisory(targetDir, args.yes, args.json);
806
904
  if (advisoryOutcome === "refused") return 1;
807
905
  const upgradeApproved = advisoryOutcome === "upgrade-approved";
808
906
  if (!args.force && !args.dryRun && !upgradeApproved) {
@@ -824,26 +922,55 @@ function runInit(argv) {
824
922
  templateVersion: SUBSTRATE_CONFIG_TEMPLATE_VERSION
825
923
  }
826
924
  ];
925
+ const say = makeSay(args.json);
827
926
  if (args.dryRun) {
828
- runDryRun$1(plans);
829
- return dispatchSubstrateCopy(targetDir, args.force || upgradeApproved, args.verbose, true, args.allowRoot, args.json);
830
- }
831
- return runReal$1(plans, args.force || upgradeApproved, args.verbose, targetDir, args.allowRoot, args.json);
832
- }
833
- function maybeEmitUpgradeAdvisory(targetDir, yes) {
927
+ runDryRun$1(plans, say);
928
+ const outcome = dispatchSubstrateCopy(
929
+ targetDir,
930
+ args.force || upgradeApproved,
931
+ args.verbose,
932
+ true,
933
+ args.allowRoot,
934
+ say
935
+ );
936
+ if (args.json) {
937
+ const report = buildInitReport({
938
+ entries: outcome.result?.wouldCopyEntries ?? [],
939
+ configs: plans.map((pl) => ({ path: pl.relativePath })),
940
+ refused: outcome.result?.refused ?? [],
941
+ // A dry run still reads every source file, so it can still fail
942
+ // to read one. Hard-coding this empty hid those failures from
943
+ // the JSON (RFC-0005 A-3).
944
+ errored: outcome.result?.errored ?? [],
945
+ hookCount: outcome.result?.hookCount ?? 0,
946
+ declaredHooksCopied: 0,
947
+ tier: RESOLVED_TIER
948
+ });
949
+ renderJsonReport(report, (t) => {
950
+ process.stdout.write(t);
951
+ });
952
+ }
953
+ return outcome.code;
954
+ }
955
+ return runReal$1(plans, args.force || upgradeApproved, args.verbose, targetDir, args.allowRoot, args.json, say);
956
+ }
957
+ function maybeEmitUpgradeAdvisory(targetDir, yes, json) {
958
+ const prompt = json ? (t) => {
959
+ process.stderr.write(t);
960
+ } : SAY_HUMAN;
834
961
  const manifestPath = join(targetDir, MANIFEST_RELATIVE_PATH);
835
962
  if (!existsSync(manifestPath)) return "ok";
836
963
  const version2 = readManifestShapeVersion(targetDir);
837
964
  if (version2 !== null && version2 >= 2) return "ok";
838
- process.stdout.write(
965
+ prompt(
839
966
  `bassclef init: cli 1.0.1 introduces user-scope hook installation at ~/${HOOKS_SUBPATH}. cli 1.0.0 did not write there.
840
967
  `
841
968
  );
842
969
  if (yes) return "upgrade-approved";
843
- process.stdout.write("bassclef init: continue? (y/N) ");
970
+ prompt("bassclef init: continue? (y/N) ");
844
971
  const answer = readOneLineFromStdin();
845
972
  if (answer === "" || answer === "y" || answer === "Y") return "upgrade-approved";
846
- process.stdout.write("bassclef init: aborted by adopter.\n");
973
+ prompt("bassclef init: aborted by adopter.\n");
847
974
  return "refused";
848
975
  }
849
976
  function readOneLineFromStdin() {
@@ -855,7 +982,7 @@ function readOneLineFromStdin() {
855
982
  return "";
856
983
  }
857
984
  }
858
- function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, json) {
985
+ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, say) {
859
986
  const substitute = makePlaceholderTransform(targetDir);
860
987
  let result;
861
988
  try {
@@ -869,9 +996,9 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
869
996
  if (e instanceof CopyFailure) {
870
997
  process.stderr.write(`bassclef init: ${e.message}
871
998
  `);
872
- if (e.kind === "ManifestMissing") return 4;
873
- if (e.kind === "SchemaIncompatible") return 5;
874
- return 2;
999
+ if (e.kind === "ManifestMissing") return { code: 4 };
1000
+ if (e.kind === "SchemaIncompatible") return { code: 5 };
1001
+ return { code: 2 };
875
1002
  }
876
1003
  throw e;
877
1004
  }
@@ -879,23 +1006,23 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
879
1006
  const wouldCopy = result.wouldCopy ?? [];
880
1007
  for (const relativePath of wouldCopy) {
881
1008
  const targetPath = join(targetDir, relativePath);
882
- process.stdout.write(` ${"would create".padEnd(14)} ${targetPath}
1009
+ say(` ${"would create".padEnd(14)} ${targetPath}
883
1010
  `);
884
1011
  }
885
1012
  if (wouldCopy.length > 0) {
886
- process.stdout.write(
1013
+ say(
887
1014
  `bassclef init: ${wouldCopy.length} substrate files would be copied.
888
1015
  `
889
1016
  );
890
1017
  }
891
- process.stdout.write(
1018
+ say(
892
1019
  `bassclef init: ${result.hookCount} hooks armed (${RESOLVED_TIER} tier).
893
1020
  `
894
1021
  );
895
- return 0;
1022
+ return { code: 0, result };
896
1023
  }
897
1024
  if (result.copied.length === 0 && result.refused.length === 0 && result.errored.length === 0) {
898
- return 0;
1025
+ return { code: 0, result };
899
1026
  }
900
1027
  const groupCounts = /* @__PURE__ */ new Map();
901
1028
  for (const path of result.copied) {
@@ -904,17 +1031,17 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
904
1031
  groupCounts.set(top, (groupCounts.get(top) ?? 0) + 1);
905
1032
  }
906
1033
  for (const [directory, count] of groupCounts) {
907
- process.stdout.write(` ${directory}: ${count} files copied
1034
+ say(` ${directory}: ${count} files copied
908
1035
  `);
909
1036
  }
910
1037
  const parts = [];
911
1038
  if (result.copied.length > 0) parts.push(`${result.copied.length} substrate files copied`);
912
1039
  if (result.refused.length > 0) parts.push(`${result.refused.length} refused`);
913
1040
  if (result.errored.length > 0) parts.push(`${result.errored.length} error(s)`);
914
- process.stdout.write(`bassclef init: ${parts.join(", ")}.
1041
+ say(`bassclef init: ${parts.join(", ")}.
915
1042
  `);
916
1043
  const grandTotal = 1 + result.copied.length;
917
- process.stdout.write(
1044
+ say(
918
1045
  `bassclef init: ${grandTotal} files total (1 config + ${result.copied.length} substrate).
919
1046
  `
920
1047
  );
@@ -931,12 +1058,12 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
931
1058
  const projectScope = copiedHookEntries.filter((e) => e.scope === "project").length;
932
1059
  const scopeSuffix = userScope + projectScope > 0 ? ` ${projectScope} in <repo>/${HOOKS_SUBPATH.replace(/\/$/, "")}, ${userScope} in ~/${HOOKS_SUBPATH.replace(/\/$/, "")}.` : "";
933
1060
  if (failedCount > 0) {
934
- process.stdout.write(
1061
+ say(
935
1062
  `bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix} ${failedCount} failed — see errors above. Rerun bassclef init to retry.
936
1063
  `
937
1064
  );
938
1065
  } else {
939
- process.stdout.write(
1066
+ say(
940
1067
  `bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix}
941
1068
  `
942
1069
  );
@@ -981,7 +1108,7 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
981
1108
  catalogCounts.luminaries > 0 ? `${catalogCounts.luminaries} luminaries` : null
982
1109
  ].filter((s) => s !== null);
983
1110
  if (claudeCounts.length > 0) {
984
- process.stdout.write(
1111
+ say(
985
1112
  `bassclef init: Installed ${claudeCounts.join(", ")} under <repo>/.claude/.
986
1113
  `
987
1114
  );
@@ -996,32 +1123,22 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
996
1123
  catalogCounts.scripts > 0 ? `${catalogCounts.scripts} scripts` : null
997
1124
  ].filter((s) => s !== null);
998
1125
  if (otherCounts.length > 0) {
999
- process.stdout.write(
1126
+ say(
1000
1127
  `bassclef init: Installed ${otherCounts.join(", ")} under <repo>/.
1001
1128
  `
1002
1129
  );
1003
1130
  }
1004
- process.stdout.write(
1131
+ say(
1005
1132
  `bassclef init: ${result.refused.length} files refused (path collision).` + (result.refused.length > 0 ? ` Use --force to overwrite existing files.` : ``) + `
1006
1133
  `
1007
1134
  );
1008
- if (json) {
1009
- const report = {
1010
- copied: copiedCount,
1011
- declared: declaredCount,
1012
- failed: failedCount,
1013
- scope_counts: { user: userScope, project: projectScope },
1014
- tier: RESOLVED_TIER
1015
- };
1016
- process.stderr.write(JSON.stringify(report) + "\n");
1017
- }
1018
1135
  if (verbose && result.erroredMessages) {
1019
1136
  for (const msg of result.erroredMessages) {
1020
1137
  process.stderr.write(` substrate: ${msg}
1021
1138
  `);
1022
1139
  }
1023
1140
  }
1024
- return result.errored.length > 0 ? 2 : 0;
1141
+ return { code: result.errored.length > 0 ? 2 : 0, result };
1025
1142
  }
1026
1143
  function makePlaceholderTransform(targetDir) {
1027
1144
  const repoName = basename$1(targetDir);
@@ -1031,7 +1148,7 @@ function makePlaceholderTransform(targetDir) {
1031
1148
  return content.replace(/\[REPO_NAME\]/g, repoName).replace(/\[ISO_TIMESTAMP\]/g, timestamp).replace(/\[TIER\]/g, RESOLVED_TIER).replace(/\[Repo name\]/g, repoName).replace(/<tier>/g, RESOLVED_TIER);
1032
1149
  };
1033
1150
  }
1034
- function runDryRun$1(plans) {
1151
+ function runDryRun$1(plans, say) {
1035
1152
  for (const p of plans) {
1036
1153
  let planned;
1037
1154
  let extra = "";
@@ -1051,12 +1168,12 @@ function runDryRun$1(plans) {
1051
1168
  planned = "would skip";
1052
1169
  }
1053
1170
  }
1054
- process.stdout.write(` ${planned.padEnd(14)} ${p.fullPath}${extra}
1171
+ say(` ${planned.padEnd(14)} ${p.fullPath}${extra}
1055
1172
  `);
1056
1173
  }
1057
1174
  return 0;
1058
1175
  }
1059
- function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1176
+ function runReal$1(plans, force, verbose, targetDir, allowRoot, json, say) {
1060
1177
  const results = [];
1061
1178
  let anyRefused = false;
1062
1179
  let anyError = false;
@@ -1104,7 +1221,7 @@ function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1104
1221
  if (r.outcome === "error") label = `error (${r.message ?? "unknown"})`;
1105
1222
  else if (r.outcome === "refused") label = "refused";
1106
1223
  else label = r.outcome;
1107
- process.stdout.write(` ${label.padEnd(10)} ${r.plan.fullPath}
1224
+ say(` ${label.padEnd(10)} ${r.plan.fullPath}
1108
1225
  `);
1109
1226
  }
1110
1227
  }
@@ -1126,38 +1243,59 @@ function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1126
1243
  return 2;
1127
1244
  }
1128
1245
  if (anyRefused && created > 0) {
1129
- process.stdout.write(
1246
+ say(
1130
1247
  `bassclef init: ${created} config files created, ${unchanged} unchanged. Pass --force to overwrite.
1131
1248
  `
1132
1249
  );
1133
1250
  } else if (created === 0 && unchanged === plans.length) {
1134
- process.stdout.write("bassclef init: already initialized. No changes.\n");
1251
+ say("bassclef init: already initialized. No changes.\n");
1135
1252
  } else {
1136
- process.stdout.write(`bassclef init: ${created} config files created, ${unchanged} unchanged.
1253
+ say(`bassclef init: ${created} config files created, ${unchanged} unchanged.
1137
1254
  `);
1138
1255
  }
1139
- const walkerExit = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, json);
1140
- writeManifest(targetDir, results);
1141
- if (walkerExit === 0) {
1142
- process.stdout.write(
1256
+ const walker = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, say);
1257
+ const declaredLeaves = readDeclaredCommandLeaves(
1258
+ join(targetDir, ".claude", "settings.json")
1259
+ );
1260
+ const declaredHooksCopied = (walker.result?.copiedEntries ?? []).filter(
1261
+ (e) => e.path.startsWith(HOOKS_SUBPATH) && e.path.endsWith(".sh") && declaredLeaves.has(basename$1(e.path))
1262
+ ).length;
1263
+ const report = buildInitReport({
1264
+ entries: walker.result?.copiedEntries ?? [],
1265
+ configs: results.map((r) => ({ path: r.plan.relativePath })),
1266
+ refused: walker.result?.refused ?? [],
1267
+ errored: walker.result?.errored ?? [],
1268
+ hookCount: walker.result?.hookCount ?? 0,
1269
+ declaredHooksCopied,
1270
+ tier: RESOLVED_TIER
1271
+ });
1272
+ writeManifest(targetDir, results, walker.result);
1273
+ if (walker.code === 0) {
1274
+ say(
1143
1275
  `bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
1144
1276
  `
1145
1277
  );
1146
1278
  }
1147
- return walkerExit;
1279
+ if (json) {
1280
+ renderJsonReport(report, (t) => {
1281
+ process.stdout.write(t);
1282
+ });
1283
+ }
1284
+ return walker.code;
1148
1285
  }
1149
1286
  function shouldRefuseRoot(currentUid, allowRoot) {
1150
1287
  if (currentUid === void 0) return false;
1151
1288
  if (currentUid !== 0) return false;
1152
1289
  return !allowRoot;
1153
1290
  }
1154
- function writeManifest(targetDir, results) {
1291
+ function writeManifest(targetDir, results, walkerResult) {
1155
1292
  const entries = results.map((r) => {
1156
1293
  const entry = {
1157
1294
  path: r.plan.relativePath,
1158
1295
  template: r.plan.templateName,
1159
1296
  template_version: r.plan.templateVersion,
1160
- outcome: r.outcome
1297
+ outcome: r.outcome,
1298
+ source: "config-composer"
1161
1299
  };
1162
1300
  if (r.outcome === "created") {
1163
1301
  entry.content_hash_sha256 = hashContent(r.plan.content);
@@ -1165,6 +1303,38 @@ function writeManifest(targetDir, results) {
1165
1303
  }
1166
1304
  return entry;
1167
1305
  });
1306
+ if (walkerResult) {
1307
+ for (const copied of walkerResult.copiedEntries) {
1308
+ entries.push({
1309
+ path: copied.path,
1310
+ template: `bundle:${copied.path}`,
1311
+ template_version: version,
1312
+ outcome: "created",
1313
+ source: "bundle",
1314
+ scope: copied.scope,
1315
+ ...copied.content_hash_sha256 ? { content_hash_sha256: copied.content_hash_sha256 } : {},
1316
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1317
+ });
1318
+ }
1319
+ for (const path of walkerResult.refused) {
1320
+ entries.push({
1321
+ path,
1322
+ template: `bundle:${path}`,
1323
+ template_version: version,
1324
+ outcome: "refused",
1325
+ source: "bundle"
1326
+ });
1327
+ }
1328
+ for (const path of walkerResult.errored) {
1329
+ entries.push({
1330
+ path,
1331
+ template: `bundle:${path}`,
1332
+ template_version: version,
1333
+ outcome: "error",
1334
+ source: "bundle"
1335
+ });
1336
+ }
1337
+ }
1168
1338
  const manifestDir = join(targetDir, ".bassclef");
1169
1339
  const manifestPath = join(manifestDir, "init.manifest.json");
1170
1340
  const content = manifestTemplate({
@@ -1175,7 +1345,12 @@ function writeManifest(targetDir, results) {
1175
1345
  try {
1176
1346
  mkdirSafely(manifestDir);
1177
1347
  writeSafely(manifestPath, content, { force: true });
1178
- } catch {
1348
+ } catch (e) {
1349
+ const reason = e instanceof Error ? e.message : String(e);
1350
+ process.stderr.write(
1351
+ `bassclef init: could not write ${manifestPath} (${reason}). The files were written. Run \`bassclef init --force\` to rebuild the record.
1352
+ `
1353
+ );
1179
1354
  }
1180
1355
  }
1181
1356
  function usage$2() {
@@ -1626,7 +1801,10 @@ function emitL2Output(decisions, verbose) {
1626
1801
  }
1627
1802
  }
1628
1803
  function updateManifestEntry(manifest, path, patch) {
1629
- const idx = manifest.files.findIndex((f) => f.path === path);
1804
+ const wantScope = patch.scope;
1805
+ const idx = manifest.files.findIndex(
1806
+ (f) => f.path === path && (wantScope === void 0 || f.scope === wantScope)
1807
+ );
1630
1808
  if (idx < 0) {
1631
1809
  manifest.files.push({
1632
1810
  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.1.0";
3
+ const version = "1.1.1";
4
4
  exports.version = version;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version: "1.1.0";
1
+ export declare const version: "1.1.1";
2
2
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "1.1.0";
1
+ const version = "1.1.1";
2
2
  export {
3
3
  version
4
4
  };
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@thebassclef/lite",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Bassclef CLI — install and upgrade bassclef in your project with two commands.",
5
- "keywords": ["bassclef", "claude-code", "cli", "scaffolding"],
5
+ "keywords": [
6
+ "bassclef",
7
+ "claude-code",
8
+ "cli",
9
+ "scaffolding"
10
+ ],
6
11
  "homepage": "https://github.com/sunj-labs/bassclef-cli",
7
12
  "repository": {
8
13
  "type": "git",
@@ -37,10 +42,12 @@
37
42
  "LICENSE"
38
43
  ],
39
44
  "bassclef": {
40
- "upstream_tag": "v0.39.0",
45
+ "upstream_tag": "v0.42.0",
41
46
  "wiring_manifest_schema_major": 2,
42
- "bundle_paths": ["dist/lite/"],
43
- "phase_note": "Phase 3 (goal 2026-09-13c cli#73) — MAJOR bump. Cli init reads dist/lite/ per bassclef-upstream ADR-055 D1. substrate/ bundle path retired. Zero npm adopters at bump time (operator confirmed 2026-09-13); no compat-shim owed."
47
+ "bundle_paths": [
48
+ "dist/lite/"
49
+ ],
50
+ "phase_note": "cli 1.1.1 (goal 2026-09-17) — init reporting contract per ADR-010. Manifest names every file written; --json moves to stdout and names all catalog families."
44
51
  },
45
52
  "engines": {
46
53
  "node": ">=20"