@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 +237 -59
- package/dist/cli.js +237 -59
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +12 -5
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 =
|
|
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:
|
|
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/
|
|
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)
|
|
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({
|
|
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
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
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
|
-
|
|
993
|
+
prompt("bassclef init: continue? (y/N) ");
|
|
867
994
|
const answer = readOneLineFromStdin();
|
|
868
995
|
if (answer === "" || answer === "y" || answer === "Y") return "upgrade-approved";
|
|
869
|
-
|
|
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,
|
|
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
|
-
|
|
1032
|
+
say(` ${"would create".padEnd(14)} ${targetPath}
|
|
906
1033
|
`);
|
|
907
1034
|
}
|
|
908
1035
|
if (wouldCopy.length > 0) {
|
|
909
|
-
|
|
1036
|
+
say(
|
|
910
1037
|
`bassclef init: ${wouldCopy.length} substrate files would be copied.
|
|
911
1038
|
`
|
|
912
1039
|
);
|
|
913
1040
|
}
|
|
914
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1064
|
+
say(`bassclef init: ${parts.join(", ")}.
|
|
938
1065
|
`);
|
|
939
1066
|
const grandTotal = 1 + result.copied.length;
|
|
940
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1149
|
+
say(
|
|
1023
1150
|
`bassclef init: Installed ${otherCounts.join(", ")} under <repo>/.
|
|
1024
1151
|
`
|
|
1025
1152
|
);
|
|
1026
1153
|
}
|
|
1027
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1274
|
+
say("bassclef init: already initialized. No changes.\n");
|
|
1158
1275
|
} else {
|
|
1159
|
-
|
|
1276
|
+
say(`bassclef init: ${created} config files created, ${unchanged} unchanged.
|
|
1160
1277
|
`);
|
|
1161
1278
|
}
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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/
|
|
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)
|
|
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({
|
|
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
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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
|
-
|
|
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
|
-
|
|
970
|
+
prompt("bassclef init: continue? (y/N) ");
|
|
844
971
|
const answer = readOneLineFromStdin();
|
|
845
972
|
if (answer === "" || answer === "y" || answer === "Y") return "upgrade-approved";
|
|
846
|
-
|
|
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,
|
|
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
|
-
|
|
1009
|
+
say(` ${"would create".padEnd(14)} ${targetPath}
|
|
883
1010
|
`);
|
|
884
1011
|
}
|
|
885
1012
|
if (wouldCopy.length > 0) {
|
|
886
|
-
|
|
1013
|
+
say(
|
|
887
1014
|
`bassclef init: ${wouldCopy.length} substrate files would be copied.
|
|
888
1015
|
`
|
|
889
1016
|
);
|
|
890
1017
|
}
|
|
891
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1041
|
+
say(`bassclef init: ${parts.join(", ")}.
|
|
915
1042
|
`);
|
|
916
1043
|
const grandTotal = 1 + result.copied.length;
|
|
917
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1126
|
+
say(
|
|
1000
1127
|
`bassclef init: Installed ${otherCounts.join(", ")} under <repo>/.
|
|
1001
1128
|
`
|
|
1002
1129
|
);
|
|
1003
1130
|
}
|
|
1004
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1251
|
+
say("bassclef init: already initialized. No changes.\n");
|
|
1135
1252
|
} else {
|
|
1136
|
-
|
|
1253
|
+
say(`bassclef init: ${created} config files created, ${unchanged} unchanged.
|
|
1137
1254
|
`);
|
|
1138
1255
|
}
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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
|
-
|
|
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
|
|
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
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version: "1.1.
|
|
1
|
+
export declare const version: "1.1.1";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thebassclef/lite",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Bassclef CLI — install and upgrade bassclef in your project with two commands.",
|
|
5
|
-
"keywords": [
|
|
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.
|
|
45
|
+
"upstream_tag": "v0.42.0",
|
|
41
46
|
"wiring_manifest_schema_major": 2,
|
|
42
|
-
"bundle_paths": [
|
|
43
|
-
|
|
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"
|