@thebassclef/lite 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +240 -23
- package/dist/cli.js +242 -25
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lite/.claude/hooks/artifact-ingestion-gate.sh +357 -0
- package/dist/lite/.claude/hooks/assert-verify-steering.sh +77 -0
- package/dist/lite/.claude/hooks/bassclef-source-config-validate.sh +215 -0
- package/dist/lite/.claude/hooks/bassclef-sync.sh +716 -0
- package/dist/lite/.claude/hooks/compound-noun-scrub.sh +292 -0
- package/dist/lite/.claude/hooks/kiss-expansion-inject.sh +69 -0
- package/dist/lite/.claude/hooks/longrun-prep-compounding-sequence-check.sh +492 -0
- package/dist/lite/.claude/hooks/plain-english-steering.sh +156 -0
- package/dist/lite/.claude/hooks/post-skill-friction-check.sh +177 -0
- package/dist/lite/.claude/hooks/post-skill-telemetry.sh +62 -0
- package/dist/lite/.claude/hooks/pre-build-gate.sh +511 -0
- package/dist/lite/.claude/hooks/pre-commit-gate.sh +451 -0
- package/dist/lite/.claude/hooks/session-end.sh +433 -0
- package/dist/lite/.claude/hooks/session-reflection.sh +303 -0
- package/dist/lite/.claude/hooks/skill-body-grade-gate.sh +219 -0
- package/dist/lite/.claude/hooks/skill-body-intent-drift.sh +107 -0
- package/dist/lite/.claude/hooks/state-validate.sh +271 -0
- package/dist/lite/.claude/hooks/substrate-clarity-gate.sh +1110 -0
- package/dist/lite/.claude/hooks/temperance-gate.sh +147 -0
- package/dist/lite/.claude/hooks/testing-tier-enforce.sh +233 -0
- package/dist/lite/.claude/hooks/turn-prose-grade-measure.sh +219 -0
- package/dist/lite/.claude/hooks/turn-prose-kiss-check.sh +463 -0
- package/dist/lite/.claude/hooks/vocabulary-migration-check.sh +171 -0
- package/dist/lite/.claude/hooks/whereami-utc-gate.sh +142 -0
- package/dist/lite/CLAUDE.md +2 -2
- package/dist/lite/whereami.md +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -36,7 +36,9 @@ const DEFAULTS$2 = {
|
|
|
36
36
|
verbose: false,
|
|
37
37
|
allowRoot: false,
|
|
38
38
|
allowAnyDir: false,
|
|
39
|
-
dir: void 0
|
|
39
|
+
dir: void 0,
|
|
40
|
+
yes: false,
|
|
41
|
+
json: false
|
|
40
42
|
};
|
|
41
43
|
let ArgvError$1 = class ArgvError extends Error {
|
|
42
44
|
name = "ArgvError";
|
|
@@ -71,6 +73,16 @@ function parseInitArgs(argv) {
|
|
|
71
73
|
i += 1;
|
|
72
74
|
continue;
|
|
73
75
|
}
|
|
76
|
+
if (token === "--yes") {
|
|
77
|
+
out.yes = true;
|
|
78
|
+
i += 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (token === "--json") {
|
|
82
|
+
out.json = true;
|
|
83
|
+
i += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
74
86
|
if (token === "--dir") {
|
|
75
87
|
const value = argv[i + 1];
|
|
76
88
|
if (value === void 0 || value.startsWith("--")) {
|
|
@@ -139,6 +151,13 @@ function isUnder(child, parent) {
|
|
|
139
151
|
if (rel === par) return true;
|
|
140
152
|
return rel.startsWith(par + "/");
|
|
141
153
|
}
|
|
154
|
+
function readlinkOrNull(path) {
|
|
155
|
+
try {
|
|
156
|
+
return node_fs.readlinkSync(path);
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
142
161
|
class WriteError extends Error {
|
|
143
162
|
name = "WriteError";
|
|
144
163
|
kind;
|
|
@@ -172,7 +191,12 @@ function writeSafely(path, content, opts = {}) {
|
|
|
172
191
|
else throw new WriteError("Unknown", `cannot stat ${path}: ${err.code ?? "unknown"}`);
|
|
173
192
|
}
|
|
174
193
|
if (existing === "symlink") {
|
|
175
|
-
|
|
194
|
+
const target = readlinkOrNull(path);
|
|
195
|
+
const targetClause = target ? ` (points to: ${target})` : "";
|
|
196
|
+
throw new WriteError(
|
|
197
|
+
"SymlinkRefused",
|
|
198
|
+
`refusing to follow symlink at target path: ${path}${targetClause}. Delete or move the symlink and rerun bassclef init.`
|
|
199
|
+
);
|
|
176
200
|
}
|
|
177
201
|
if (existing !== "none" && !opts.force) {
|
|
178
202
|
throw new WriteError("AlreadyExists", `file already exists (pass --force to overwrite): ${path}`);
|
|
@@ -289,8 +313,10 @@ function substrateConfigMdTemplate(pkgVersion) {
|
|
|
289
313
|
].join("\n");
|
|
290
314
|
}
|
|
291
315
|
const MANIFEST_SCHEMA_VERSION = "0.1.0";
|
|
316
|
+
const MANIFEST_SHAPE_VERSION = 2;
|
|
292
317
|
function manifestTemplate(input) {
|
|
293
318
|
const value = {
|
|
319
|
+
schema_version: MANIFEST_SHAPE_VERSION,
|
|
294
320
|
$bassclef: {
|
|
295
321
|
template: "init.manifest.json",
|
|
296
322
|
manifest_schema_version: MANIFEST_SCHEMA_VERSION,
|
|
@@ -312,6 +338,15 @@ class ManifestReadError extends Error {
|
|
|
312
338
|
this.kind = kind;
|
|
313
339
|
}
|
|
314
340
|
}
|
|
341
|
+
function readManifestShapeVersion(targetDir) {
|
|
342
|
+
try {
|
|
343
|
+
const parsed = readManifest(targetDir);
|
|
344
|
+
if (typeof parsed.schema_version === "number") return parsed.schema_version;
|
|
345
|
+
return null;
|
|
346
|
+
} catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
315
350
|
function readManifest(targetDir) {
|
|
316
351
|
const path = node_path.join(targetDir, MANIFEST_RELATIVE_PATH);
|
|
317
352
|
let raw;
|
|
@@ -392,8 +427,6 @@ async function computeConfigHashes(targetDir, paths) {
|
|
|
392
427
|
}
|
|
393
428
|
return out;
|
|
394
429
|
}
|
|
395
|
-
const EXPECTED_WIRING_SCHEMA_MAJOR = 2;
|
|
396
|
-
const WIRING_MANIFEST_RELATIVE = "standards/bassclef-wiring-manifest.json";
|
|
397
430
|
class CopyFailure extends Error {
|
|
398
431
|
constructor(kind, message) {
|
|
399
432
|
super(message);
|
|
@@ -401,11 +434,106 @@ class CopyFailure extends Error {
|
|
|
401
434
|
this.name = "CopyFailure";
|
|
402
435
|
}
|
|
403
436
|
}
|
|
437
|
+
function resolveHome(opts) {
|
|
438
|
+
const raw = process.env.HOME;
|
|
439
|
+
if (raw === void 0 || raw === "") {
|
|
440
|
+
throw new CopyFailure(
|
|
441
|
+
"EnvironmentIncomplete",
|
|
442
|
+
"HOME environment variable is unset. bassclef init needs HOME set to resolve user-scope hook targets. Set HOME to your home directory and rerun."
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
if (raw === "/root" && !opts.allowRoot) {
|
|
446
|
+
throw new CopyFailure(
|
|
447
|
+
"SudoBypassRefused",
|
|
448
|
+
"HOME resolves to /root — sudo bypass detected. Routing user-scope hooks to /root breaks the adopter maintenance model. Run bassclef init without sudo, or pass --allow-root if this is intentional."
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const canonical = raw === node_os.homedir() ? node_os.homedir() : raw;
|
|
452
|
+
try {
|
|
453
|
+
return node_fs.realpathSync(canonical);
|
|
454
|
+
} catch {
|
|
455
|
+
return canonical.endsWith("/") ? canonical.slice(0, -1) : canonical;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const PREFIX_HOME = "$HOME/";
|
|
459
|
+
const PREFIX_PROJECT = "$CLAUDE_PROJECT_DIR/";
|
|
460
|
+
function classify$1(hook, opts) {
|
|
461
|
+
const cmd = hook.command;
|
|
462
|
+
if (typeof cmd !== "string" || cmd.length === 0) {
|
|
463
|
+
throw new CopyFailure(
|
|
464
|
+
"UnknownScopePrefix",
|
|
465
|
+
"Hook command is empty. settings.json must supply a non-empty command string."
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
if (cmd.startsWith(PREFIX_HOME)) {
|
|
469
|
+
return classifyUser(cmd.slice(PREFIX_HOME.length), opts);
|
|
470
|
+
}
|
|
471
|
+
if (cmd.startsWith(PREFIX_PROJECT)) {
|
|
472
|
+
return classifyProject(cmd.slice(PREFIX_PROJECT.length), opts);
|
|
473
|
+
}
|
|
474
|
+
throw new CopyFailure(
|
|
475
|
+
"UnknownScopePrefix",
|
|
476
|
+
`Hook command "${cmd}" uses an unknown scope prefix. cli 1.0.1 handles "$HOME/..." and "$CLAUDE_PROJECT_DIR/..." only. Upgrade cli to a version that supports this prefix.`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
function classifyUser(relPath, opts) {
|
|
480
|
+
const home = resolveHome({ allowRoot: opts.allowRoot });
|
|
481
|
+
const cleaned = normalizeRel(relPath);
|
|
482
|
+
const targetPath = node_path.normalize(node_path.join(home, cleaned));
|
|
483
|
+
assertContained(targetPath, home, opts.allowRoot ? "/root" : null);
|
|
484
|
+
return { scope: "user", targetPath };
|
|
485
|
+
}
|
|
486
|
+
function classifyProject(relPath, opts) {
|
|
487
|
+
const cleaned = normalizeRel(relPath);
|
|
488
|
+
const targetPath = node_path.normalize(node_path.join(opts.targetDir, cleaned));
|
|
489
|
+
assertContained(targetPath, opts.targetDir);
|
|
490
|
+
return { scope: "project", targetPath };
|
|
491
|
+
}
|
|
492
|
+
function normalizeRel(rel) {
|
|
493
|
+
return rel.replace(/\/{2,}/g, "/");
|
|
494
|
+
}
|
|
495
|
+
function assertContained(target, root, altRoot) {
|
|
496
|
+
const resolvedTarget = node_path.resolve(target);
|
|
497
|
+
const resolvedRoot = node_path.resolve(root);
|
|
498
|
+
if (resolvedTarget === resolvedRoot) return;
|
|
499
|
+
if (resolvedTarget.startsWith(resolvedRoot + "/")) return;
|
|
500
|
+
if (altRoot) {
|
|
501
|
+
const resolvedAlt = node_path.resolve(altRoot);
|
|
502
|
+
if (resolvedTarget === resolvedAlt) return;
|
|
503
|
+
if (resolvedTarget.startsWith(resolvedAlt + "/")) return;
|
|
504
|
+
}
|
|
505
|
+
throw new CopyFailure(
|
|
506
|
+
"PathTraversalRefused",
|
|
507
|
+
`Hook target "${target}" escapes ${root}. settings.json commands must resolve inside the declared scope root. If this is a bassclef-upstream bundle defect, file at sunj-labs/bassclef-upstream.`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
const HOOK_EXECUTABLE_MODE = 493;
|
|
511
|
+
function setExecutable(targetPath) {
|
|
512
|
+
if (process.platform === "win32") {
|
|
513
|
+
process.stderr.write(
|
|
514
|
+
`bassclef init: executable bit not applicable on this OS (${targetPath}). Claude Code will still invoke the hook if a POSIX shell layer is present.
|
|
515
|
+
`
|
|
516
|
+
);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
node_fs.chmodSync(targetPath, HOOK_EXECUTABLE_MODE);
|
|
520
|
+
}
|
|
521
|
+
const HOOKS_SUBPATH = ".claude/hooks/";
|
|
522
|
+
const SETTINGS_SUBPATH = ".claude/settings.json";
|
|
523
|
+
const CONFIG_FILES = [
|
|
524
|
+
".claude/settings.json",
|
|
525
|
+
"substrate.config.md",
|
|
526
|
+
"substrate.secrets.md"
|
|
527
|
+
];
|
|
528
|
+
const CURRENT_ENTRY_COUNT = 149;
|
|
529
|
+
const EXPECTED_WIRING_SCHEMA_MAJOR = 2;
|
|
530
|
+
const WIRING_MANIFEST_RELATIVE = "standards/bassclef-wiring-manifest.json";
|
|
404
531
|
function copySubstrate(targetDir, options = {}) {
|
|
405
532
|
const bundleRoot = resolveBundleRoot(options.bundleRoot);
|
|
406
533
|
const manifest = readWiringManifest(bundleRoot);
|
|
407
534
|
const result = {
|
|
408
535
|
copied: [],
|
|
536
|
+
copiedEntries: [],
|
|
409
537
|
refused: [],
|
|
410
538
|
errored: [],
|
|
411
539
|
erroredMessages: [],
|
|
@@ -415,10 +543,12 @@ function copySubstrate(targetDir, options = {}) {
|
|
|
415
543
|
if (options.dryRun) result.wouldCopy = [];
|
|
416
544
|
const files = walkDistTree(bundleRoot);
|
|
417
545
|
const groups = groupByTopDirectory(files);
|
|
546
|
+
const bundleHookRelPaths = new Set(files.filter(isHookFile));
|
|
547
|
+
const scopeMap = buildScopeMap(bundleRoot, targetDir, options, bundleHookRelPaths);
|
|
418
548
|
for (const [directory, groupFiles] of groups) {
|
|
419
549
|
let completedInGroup = 0;
|
|
420
550
|
for (const relPath of groupFiles) {
|
|
421
|
-
const outcome = copyOne(relPath, bundleRoot, targetDir, options, result);
|
|
551
|
+
const outcome = copyOne(relPath, bundleRoot, targetDir, options, result, scopeMap);
|
|
422
552
|
if (outcome !== "skipped") completedInGroup += 1;
|
|
423
553
|
}
|
|
424
554
|
if (options.onProgress) options.onProgress(directory, completedInGroup);
|
|
@@ -497,10 +627,41 @@ function mapAdopterPath(relPath) {
|
|
|
497
627
|
if (relPath === GITIGNORE_BUNDLE_NAME) return GITIGNORE_ADOPTER_NAME;
|
|
498
628
|
return relPath;
|
|
499
629
|
}
|
|
500
|
-
function
|
|
630
|
+
function buildScopeMap(bundleRoot, targetDir, options, bundleHookRelPaths) {
|
|
631
|
+
const map = /* @__PURE__ */ new Map();
|
|
632
|
+
const settingsPath = node_path.join(bundleRoot, SETTINGS_SUBPATH);
|
|
633
|
+
if (!fileExists(settingsPath)) return map;
|
|
634
|
+
let parsed;
|
|
635
|
+
try {
|
|
636
|
+
parsed = JSON.parse(node_fs.readFileSync(settingsPath, "utf8"));
|
|
637
|
+
} catch {
|
|
638
|
+
return map;
|
|
639
|
+
}
|
|
640
|
+
const allowRoot = options.allowRoot ?? false;
|
|
641
|
+
for (const eventBlocks of Object.values(parsed.hooks ?? {})) {
|
|
642
|
+
for (const block of eventBlocks) {
|
|
643
|
+
for (const entry of block.hooks ?? []) {
|
|
644
|
+
const cmd = entry.command;
|
|
645
|
+
if (typeof cmd !== "string" || cmd.length === 0) continue;
|
|
646
|
+
if (!cmd.startsWith("$")) continue;
|
|
647
|
+
const decision = classify$1({ command: cmd }, { targetDir, allowRoot });
|
|
648
|
+
const relSource = cmd.replace(/^\$HOME\//, "").replace(/^\$CLAUDE_PROJECT_DIR\//, "").replace(/\/{2,}/g, "/");
|
|
649
|
+
if (!bundleHookRelPaths.has(relSource)) continue;
|
|
650
|
+
map.set(relSource, decision);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return map;
|
|
655
|
+
}
|
|
656
|
+
function isHookFile(relPath) {
|
|
657
|
+
return relPath.startsWith(HOOKS_SUBPATH) && relPath.endsWith(".sh");
|
|
658
|
+
}
|
|
659
|
+
function copyOne(relPath, bundleRoot, targetDir, options, result, scopeMap) {
|
|
501
660
|
const sourcePath = node_path.join(bundleRoot, relPath);
|
|
502
661
|
const adopterRelPath = mapAdopterPath(relPath);
|
|
503
|
-
const
|
|
662
|
+
const scopeDecision = isHookFile(relPath) ? scopeMap.get(relPath) : void 0;
|
|
663
|
+
const targetPath = scopeDecision ? scopeDecision.targetPath : node_path.join(targetDir, adopterRelPath);
|
|
664
|
+
const scope = scopeDecision ? scopeDecision.scope : "project";
|
|
504
665
|
let content;
|
|
505
666
|
try {
|
|
506
667
|
content = node_fs.readFileSync(sourcePath, "utf8");
|
|
@@ -519,7 +680,11 @@ function copyOne(relPath, bundleRoot, targetDir, options, result) {
|
|
|
519
680
|
try {
|
|
520
681
|
mkdirSafely(node_path.dirname(targetPath));
|
|
521
682
|
writeSafely(targetPath, outputContent, { force: options.force ?? false });
|
|
683
|
+
if (isHookFile(relPath)) {
|
|
684
|
+
setExecutable(targetPath);
|
|
685
|
+
}
|
|
522
686
|
result.copied.push(adopterRelPath);
|
|
687
|
+
result.copiedEntries.push({ path: adopterRelPath, scope });
|
|
523
688
|
return "copied";
|
|
524
689
|
} catch (e) {
|
|
525
690
|
if (e instanceof WriteError) {
|
|
@@ -529,6 +694,8 @@ function copyOne(relPath, bundleRoot, targetDir, options, result) {
|
|
|
529
694
|
}
|
|
530
695
|
if (e.kind === "SymlinkRefused") {
|
|
531
696
|
result.refused.push(adopterRelPath);
|
|
697
|
+
process.stderr.write(`bassclef init: ${e.message}
|
|
698
|
+
`);
|
|
532
699
|
return "refused";
|
|
533
700
|
}
|
|
534
701
|
const message = `${adopterRelPath} — write failed (${e.kind}): ${e.message}. Check the target directory exists and is writable, then rerun.`;
|
|
@@ -636,7 +803,10 @@ function runInit(argv) {
|
|
|
636
803
|
}
|
|
637
804
|
throw e;
|
|
638
805
|
}
|
|
639
|
-
|
|
806
|
+
const advisoryOutcome = maybeEmitUpgradeAdvisory(targetDir, args.yes);
|
|
807
|
+
if (advisoryOutcome === "refused") return 1;
|
|
808
|
+
const upgradeApproved = advisoryOutcome === "upgrade-approved";
|
|
809
|
+
if (!args.force && !args.dryRun && !upgradeApproved) {
|
|
640
810
|
const manifestPath = node_path.join(targetDir, MANIFEST_RELATIVE_PATH);
|
|
641
811
|
if (node_fs.existsSync(manifestPath)) {
|
|
642
812
|
process.stderr.write(
|
|
@@ -657,18 +827,44 @@ function runInit(argv) {
|
|
|
657
827
|
];
|
|
658
828
|
if (args.dryRun) {
|
|
659
829
|
runDryRun$1(plans);
|
|
660
|
-
return dispatchSubstrateCopy(targetDir, args.force, args.verbose, true);
|
|
830
|
+
return dispatchSubstrateCopy(targetDir, args.force || upgradeApproved, args.verbose, true, args.allowRoot, args.json);
|
|
831
|
+
}
|
|
832
|
+
return runReal$1(plans, args.force || upgradeApproved, args.verbose, targetDir, args.allowRoot, args.json);
|
|
833
|
+
}
|
|
834
|
+
function maybeEmitUpgradeAdvisory(targetDir, yes) {
|
|
835
|
+
const manifestPath = node_path.join(targetDir, MANIFEST_RELATIVE_PATH);
|
|
836
|
+
if (!node_fs.existsSync(manifestPath)) return "ok";
|
|
837
|
+
const version = readManifestShapeVersion(targetDir);
|
|
838
|
+
if (version !== null && version >= 2) return "ok";
|
|
839
|
+
process.stdout.write(
|
|
840
|
+
`bassclef init: cli 1.0.1 introduces user-scope hook installation at ~/${HOOKS_SUBPATH}. cli 1.0.0 did not write there.
|
|
841
|
+
`
|
|
842
|
+
);
|
|
843
|
+
if (yes) return "upgrade-approved";
|
|
844
|
+
process.stdout.write("bassclef init: continue? (y/N) ");
|
|
845
|
+
const answer = readOneLineFromStdin();
|
|
846
|
+
if (answer === "" || answer === "y" || answer === "Y") return "upgrade-approved";
|
|
847
|
+
process.stdout.write("bassclef init: aborted by adopter.\n");
|
|
848
|
+
return "refused";
|
|
849
|
+
}
|
|
850
|
+
function readOneLineFromStdin() {
|
|
851
|
+
try {
|
|
852
|
+
const buf = Buffer.alloc(256);
|
|
853
|
+
const n = require("node:fs").readSync(0, buf, 0, 256, null);
|
|
854
|
+
return n > 0 ? buf.slice(0, n).toString("utf8").trim() : "";
|
|
855
|
+
} catch {
|
|
856
|
+
return "";
|
|
661
857
|
}
|
|
662
|
-
return runReal$1(plans, args.force, args.verbose, targetDir);
|
|
663
858
|
}
|
|
664
|
-
function dispatchSubstrateCopy(targetDir, force, verbose, dryRun) {
|
|
859
|
+
function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, json) {
|
|
665
860
|
const substitute = makePlaceholderTransform(targetDir);
|
|
666
861
|
let result;
|
|
667
862
|
try {
|
|
668
863
|
result = copySubstrate(targetDir, {
|
|
669
864
|
force,
|
|
670
865
|
dryRun,
|
|
671
|
-
transform: substitute
|
|
866
|
+
transform: substitute,
|
|
867
|
+
allowRoot
|
|
672
868
|
});
|
|
673
869
|
} catch (e) {
|
|
674
870
|
if (e instanceof CopyFailure) {
|
|
@@ -723,10 +919,36 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun) {
|
|
|
723
919
|
`bassclef init: ${grandTotal} files total (1 config + ${result.copied.length} substrate).
|
|
724
920
|
`
|
|
725
921
|
);
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
`
|
|
922
|
+
const copiedHookEntries = result.copiedEntries.filter(
|
|
923
|
+
(e) => e.path.startsWith(HOOKS_SUBPATH) && e.path.endsWith(".sh")
|
|
729
924
|
);
|
|
925
|
+
const copiedCount = copiedHookEntries.length;
|
|
926
|
+
const declaredCount = result.hookCount;
|
|
927
|
+
const failedCount = result.refused.length + result.errored.length;
|
|
928
|
+
const userScope = copiedHookEntries.filter((e) => e.scope === "user").length;
|
|
929
|
+
const projectScope = copiedHookEntries.filter((e) => e.scope === "project").length;
|
|
930
|
+
const scopeSuffix = userScope + projectScope > 0 ? ` ${projectScope} in <repo>/${HOOKS_SUBPATH.replace(/\/$/, "")}, ${userScope} in ~/${HOOKS_SUBPATH.replace(/\/$/, "")}.` : "";
|
|
931
|
+
if (failedCount > 0) {
|
|
932
|
+
process.stdout.write(
|
|
933
|
+
`bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix} ${failedCount} failed — see errors above. Rerun bassclef init to retry.
|
|
934
|
+
`
|
|
935
|
+
);
|
|
936
|
+
} else {
|
|
937
|
+
process.stdout.write(
|
|
938
|
+
`bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix}
|
|
939
|
+
`
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
if (json) {
|
|
943
|
+
const report = {
|
|
944
|
+
copied: copiedCount,
|
|
945
|
+
declared: declaredCount,
|
|
946
|
+
failed: failedCount,
|
|
947
|
+
scope_counts: { user: userScope, project: projectScope },
|
|
948
|
+
tier: RESOLVED_TIER
|
|
949
|
+
};
|
|
950
|
+
process.stderr.write(JSON.stringify(report) + "\n");
|
|
951
|
+
}
|
|
730
952
|
if (verbose && result.erroredMessages) {
|
|
731
953
|
for (const msg of result.erroredMessages) {
|
|
732
954
|
process.stderr.write(` substrate: ${msg}
|
|
@@ -768,7 +990,7 @@ function runDryRun$1(plans) {
|
|
|
768
990
|
}
|
|
769
991
|
return 0;
|
|
770
992
|
}
|
|
771
|
-
function runReal$1(plans, force, verbose, targetDir) {
|
|
993
|
+
function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
|
|
772
994
|
const results = [];
|
|
773
995
|
let anyRefused = false;
|
|
774
996
|
let anyError = false;
|
|
@@ -848,7 +1070,7 @@ function runReal$1(plans, force, verbose, targetDir) {
|
|
|
848
1070
|
process.stdout.write(`bassclef init: ${created} config files created, ${unchanged} unchanged.
|
|
849
1071
|
`);
|
|
850
1072
|
}
|
|
851
|
-
const walkerExit = dispatchSubstrateCopy(targetDir, force, verbose, false);
|
|
1073
|
+
const walkerExit = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, json);
|
|
852
1074
|
writeManifest(targetDir, results);
|
|
853
1075
|
if (walkerExit === 0) {
|
|
854
1076
|
process.stdout.write(
|
|
@@ -1452,12 +1674,6 @@ async function confirm(question, opts = {}) {
|
|
|
1452
1674
|
rl.close();
|
|
1453
1675
|
}
|
|
1454
1676
|
}
|
|
1455
|
-
const CONFIG_FILES = [
|
|
1456
|
-
".claude/settings.json",
|
|
1457
|
-
"substrate.config.md",
|
|
1458
|
-
"substrate.secrets.md"
|
|
1459
|
-
];
|
|
1460
|
-
const CURRENT_ENTRY_COUNT = 149;
|
|
1461
1677
|
async function detectAdopterState(targetDir) {
|
|
1462
1678
|
let manifest;
|
|
1463
1679
|
try {
|
|
@@ -1507,6 +1723,7 @@ Proceed?`,
|
|
|
1507
1723
|
);
|
|
1508
1724
|
const errored = copyResult.errored;
|
|
1509
1725
|
const newManifest = {
|
|
1726
|
+
schema_version: MANIFEST_SHAPE_VERSION,
|
|
1510
1727
|
$bassclef: {
|
|
1511
1728
|
template: "init.manifest.json",
|
|
1512
1729
|
manifest_schema_version: "0.1.0",
|