@thebassclef/lite 1.1.0 → 1.2.0-alpha.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.
Files changed (135) hide show
  1. package/dist/cli.cjs +258 -63
  2. package/dist/cli.js +258 -63
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/lite/.claude/bassclef-configs.jsonc +56 -0
  7. package/dist/lite/.claude/bassclef-orientation.md +120 -0
  8. package/dist/lite/.claude/hooks/adr-discipline-check.sh +132 -0
  9. package/dist/lite/.claude/hooks/atomic-pr-check.sh +169 -0
  10. package/dist/lite/.claude/hooks/auto-save-idle.sh +51 -0
  11. package/dist/lite/.claude/hooks/bassclef-sync.sh +51 -13
  12. package/dist/lite/.claude/hooks/bet-doc-gate.sh +283 -0
  13. package/dist/lite/.claude/hooks/bug-diagnosis.sh +64 -0
  14. package/dist/lite/.claude/hooks/destructive-command-guard.sh +136 -0
  15. package/dist/lite/.claude/hooks/file-rename-discipline-check.sh +147 -0
  16. package/dist/lite/.claude/hooks/hook-classifier-canary.sh +158 -0
  17. package/dist/lite/.claude/hooks/kiss-default-check.sh +129 -0
  18. package/dist/lite/.claude/hooks/multi-tenancy-check.sh +83 -0
  19. package/dist/lite/.claude/hooks/oo-ad-entry-point-check.sh +216 -0
  20. package/dist/lite/.claude/hooks/operator-private-path-check.sh +203 -0
  21. package/dist/lite/.claude/hooks/pattern-annotation-validate.sh +142 -0
  22. package/dist/lite/.claude/hooks/plain-english-steering.sh +15 -1
  23. package/dist/lite/.claude/hooks/post-write-trace.sh +66 -0
  24. package/dist/lite/.claude/hooks/pr-body-scrub-check.sh +446 -0
  25. package/dist/lite/.claude/hooks/pre-commit-identifier-leak-scrub.sh +137 -0
  26. package/dist/lite/.claude/hooks/pre-gh-pr-body-scrub.sh +192 -0
  27. package/dist/lite/.claude/hooks/pre-gh-pr-title-scrub.sh +172 -0
  28. package/dist/lite/.claude/hooks/role-entry-gate.sh +115 -0
  29. package/dist/lite/.claude/hooks/save-state.sh +268 -0
  30. package/dist/lite/.claude/hooks/session-reflection.d/08-settings-drift.sh +8 -1
  31. package/dist/lite/.claude/hooks/session-reflection.d/10-abrupt-stop-recovery.sh +52 -0
  32. package/dist/lite/.claude/hooks/session-reflection.d/55-orientation-gate.sh +40 -4
  33. package/dist/lite/.claude/hooks/session-reflection.d/80-hook-heartbeat-check.sh +12 -5
  34. package/dist/lite/.claude/hooks/session-reflection.d/81-hook-manifest-staleness.sh +18 -2
  35. package/dist/lite/.claude/hooks/session-reflection.d/91-npm-version-drift.sh +54 -0
  36. package/dist/lite/.claude/hooks/session-reflection.d/tests/10-abrupt-stop-recovery.test.sh +95 -0
  37. package/dist/lite/.claude/hooks/session-reflection.sh +62 -15
  38. package/dist/lite/.claude/hooks/tool-failure-diagnosis.sh +50 -0
  39. package/dist/lite/.claude/luminaries/andrej-karpathy.md +165 -0
  40. package/dist/lite/.claude/luminaries/dave-gray.md +155 -0
  41. package/dist/lite/.claude/luminaries/gary-klein.md +166 -0
  42. package/dist/lite/.claude/luminaries/kaoru-ishikawa.md +162 -0
  43. package/dist/lite/.claude/luminaries/lean-product-canvas.md +33 -0
  44. package/dist/lite/.claude/luminaries/luke-wroblewski.md +224 -0
  45. package/dist/lite/.claude/luminaries/ryan-singer.md +113 -0
  46. package/dist/lite/.claude/luminaries/steve-krug.md +168 -0
  47. package/dist/lite/.claude/luminaries/tony-ulwick.md +134 -0
  48. package/dist/lite/.claude/rules/arc-level-orientation-before-leaf.md +60 -0
  49. package/dist/lite/.claude/rules/architect-review-discipline.md +71 -0
  50. package/dist/lite/.claude/rules/auto-fix-criteria.md +126 -0
  51. package/dist/lite/.claude/rules/bassclef-hook-drift-blocker.md +144 -0
  52. package/dist/lite/.claude/rules/bassclef-models-discovery.md +107 -0
  53. package/dist/lite/.claude/rules/bassclef-workflow-parity.md +104 -0
  54. package/dist/lite/.claude/rules/cost-governance.md +154 -0
  55. package/dist/lite/.claude/rules/file-rename-discipline.md +95 -0
  56. package/dist/lite/.claude/rules/mechanism-fidelity.md +88 -0
  57. package/dist/lite/.claude/rules/prototype-production-coupling.md +125 -0
  58. package/dist/lite/.claude/rules/quick-fix-criteria.md +90 -0
  59. package/dist/lite/.claude/rules/session-artifacts.md +30 -0
  60. package/dist/lite/.claude/rules/sibling-smoke-after-substrate-change.md +107 -0
  61. package/dist/lite/.claude/rules/substrate-as-system.md +67 -0
  62. package/dist/lite/.claude/rules/usability.md +22 -0
  63. package/dist/lite/.claude/settings.json +20 -0
  64. package/dist/lite/.claude/skills/agent-research-spawn/SKILL.md +100 -0
  65. package/dist/lite/.claude/skills/architect-review/SKILL.md +201 -0
  66. package/dist/lite/.claude/skills/auto-fix/SKILL.md +297 -0
  67. package/dist/lite/.claude/skills/autonomous/SKILL.md +300 -0
  68. package/dist/lite/.claude/skills/bind-subdomain/SKILL.md +232 -0
  69. package/dist/lite/.claude/skills/canvas/SKILL.md +41 -0
  70. package/dist/lite/.claude/skills/deploy-prod/SKILL.md +106 -0
  71. package/dist/lite/.claude/skills/docs-sync/SKILL.md +188 -0
  72. package/dist/lite/.claude/skills/eisenhower/SKILL.md +273 -0
  73. package/dist/lite/.claude/skills/empathy-map/SKILL.md +168 -0
  74. package/dist/lite/.claude/skills/frontend-design/SKILL.md +53 -0
  75. package/dist/lite/.claude/skills/interview-me/SKILL.md +171 -0
  76. package/dist/lite/.claude/skills/journal-export/SKILL.md +293 -0
  77. package/dist/lite/.claude/skills/jtbd-tasks/SKILL.md +203 -0
  78. package/dist/lite/.claude/skills/objectory-decompose/SKILL.md +256 -0
  79. package/dist/lite/.claude/skills/pick-luminaries/SKILL.md +100 -0
  80. package/dist/lite/.claude/skills/pre-mortem/SKILL.md +180 -0
  81. package/dist/lite/.claude/skills/prototype-gallery/SKILL.md +264 -0
  82. package/dist/lite/.claude/skills/provision-deploy-host/SKILL.md +257 -0
  83. package/dist/lite/.claude/skills/quick-fix/SKILL.md +273 -0
  84. package/dist/lite/.claude/skills/release/SKILL.md +311 -0
  85. package/dist/lite/.claude/skills/release-notes/SKILL.md +202 -0
  86. package/dist/lite/.claude/skills/rfc/SKILL.md +222 -0
  87. package/dist/lite/.claude/skills/riff-prototypes/SKILL.md +637 -0
  88. package/dist/lite/.claude/skills/secrets-bootstrap/SKILL.md +209 -0
  89. package/dist/lite/.claude/skills/skill/SKILL.md +304 -0
  90. package/dist/lite/.claude/skills/smoke-test/SKILL.md +100 -0
  91. package/dist/lite/.claude/skills/state/SKILL.md +204 -0
  92. package/dist/lite/.claude/skills/substrate-check/SKILL.md +218 -0
  93. package/dist/lite/.claude/skills/synthetic-user/SKILL.md +381 -0
  94. package/dist/lite/.claude/skills/task-scenarios/SKILL.md +105 -0
  95. package/dist/lite/.claude/skills/tier-dependency-audit/SKILL.md +139 -0
  96. package/dist/lite/.claude/skills/ux-migration/SKILL.md +424 -0
  97. package/dist/lite/.claude/skills/value-prop-canvas/SKILL.md +297 -0
  98. package/dist/lite/README.md +5 -1
  99. package/dist/lite/gitignore +6 -0
  100. package/dist/lite/lib/bassclef-dir-resolver.sh +85 -0
  101. package/dist/lite/lib/clone-failure-classifier.sh +59 -0
  102. package/dist/lite/lib/fixture-builder.sh +190 -0
  103. package/dist/lite/lib/fresh-install-check.sh +49 -0
  104. package/dist/lite/lib/hook-heartbeat.sh +0 -1
  105. package/dist/lite/lib/mechanism-fidelity.sh +569 -0
  106. package/dist/lite/lib/npm-version-check.sh +112 -0
  107. package/dist/lite/lib/preview-state.sh +229 -0
  108. package/dist/lite/lib/session-rescue.sh +0 -1
  109. package/dist/lite/scripts/generate-lite-manifest.sh +145 -4
  110. package/dist/lite/scripts/lite-manifest-drift-check.sh +5 -1
  111. package/dist/lite/standards/architect-review-discipline.md +222 -0
  112. package/dist/lite/standards/bassclef-platform.md +249 -0
  113. package/dist/lite/standards/bassclef-test-repos.md +97 -0
  114. package/dist/lite/standards/bassclef-wiring-manifest.json +25 -1
  115. package/dist/lite/standards/cold-adopter-harness.md +205 -0
  116. package/dist/lite/standards/data-management.md +135 -0
  117. package/dist/lite/standards/frontend-stack/nextjs-prisma.md +124 -0
  118. package/dist/lite/standards/lite-manifest-schema-changes.md +27 -0
  119. package/dist/lite/standards/lite-manifest.json +1456 -14
  120. package/dist/lite/standards/mechanism-fidelity.md +205 -0
  121. package/dist/lite/standards/operator-private-discipline.md +200 -0
  122. package/dist/lite/standards/operator-to-public-flow.md +195 -0
  123. package/dist/lite/standards/prototype-gallery.md +251 -0
  124. package/dist/lite/standards/pseudonym-key-schema.md +109 -0
  125. package/dist/lite/standards/sibling-validation-checklist.md +181 -0
  126. package/dist/lite/standards/slack-notifications.md +161 -0
  127. package/dist/lite/standards/synthetic-user-testing.md +173 -0
  128. package/dist/lite/standards/vendor-bindings.md +165 -0
  129. package/dist/lite/standards/vendor-plugins/frontend-design.md +66 -0
  130. package/dist/lite/standards/vpc-fit-validation.md +167 -0
  131. package/dist/lite/templates/gallery-yml.md +113 -0
  132. package/dist/lite/templates/product-canvas.md +169 -0
  133. package/dist/lite/templates/value-prop-canvas.md +180 -0
  134. package/package.json +12 -5
  135. /package/dist/lite/{whereami.md → docs/whereami.md} +0 -0
package/dist/cli.js CHANGED
@@ -290,14 +290,21 @@ 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,
298
+ // Top-level `version` — read by the v0.45.0 npm-version-drift hook
299
+ // (fragment 91 under session-reflection) via lib/npm-version-check.sh
300
+ // `jq -r '.version // ""' "$manifest"`. Duplicates
301
+ // $bassclef.generated_by_version on purpose — the hook does not walk
302
+ // into $bassclef. Per bassclef-upstream#1749.
303
+ version: input.pkgVersion,
297
304
  $bassclef: {
298
305
  template: "init.manifest.json",
299
306
  manifest_schema_version: MANIFEST_SCHEMA_VERSION,
300
- generated_by: "@thebassclef/core",
307
+ generated_by: GENERATED_BY,
301
308
  generated_by_version: input.pkgVersion
302
309
  },
303
310
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -365,7 +372,7 @@ function readManifest(targetDir) {
365
372
  if (compareSchemaVersion(schema, MANIFEST_SCHEMA_VERSION) > 0) {
366
373
  throw new ManifestReadError(
367
374
  "SchemaTooNew",
368
- `manifest schema version ${schema} is newer than this package understands (${MANIFEST_SCHEMA_VERSION}). Upgrade @thebassclef/core.`
375
+ `manifest schema version ${schema} is newer than this package understands (${MANIFEST_SCHEMA_VERSION}). Upgrade @thebassclef/lite.`
369
376
  );
370
377
  }
371
378
  const obj = parsed;
@@ -518,7 +525,10 @@ function copySubstrate(targetDir, options = {}) {
518
525
  hookCount: 0,
519
526
  wiringVersion: manifest.version
520
527
  };
521
- if (options.dryRun) result.wouldCopy = [];
528
+ if (options.dryRun) {
529
+ result.wouldCopy = [];
530
+ result.wouldCopyEntries = [];
531
+ }
522
532
  const files = walkDistTree(bundleRoot);
523
533
  const groups = groupByTopDirectory(files);
524
534
  const bundleHookRelPaths = new Set(files.filter(isHookFile));
@@ -674,6 +684,11 @@ function copyOne(relPath, bundleRoot, options, result, scopeDecision) {
674
684
  const outputContent = options.transform ? options.transform(adopterRelPath, content) : content;
675
685
  if (options.dryRun) {
676
686
  result.wouldCopy?.push(adopterRelPath);
687
+ result.wouldCopyEntries?.push({
688
+ path: adopterRelPath,
689
+ scope,
690
+ content_hash_sha256: hashContent(outputContent)
691
+ });
677
692
  return "wouldCopy";
678
693
  }
679
694
  try {
@@ -683,7 +698,11 @@ function copyOne(relPath, bundleRoot, options, result, scopeDecision) {
683
698
  setExecutable(targetPath);
684
699
  }
685
700
  result.copied.push(adopterRelPath);
686
- result.copiedEntries.push({ path: adopterRelPath, scope });
701
+ result.copiedEntries.push({
702
+ path: adopterRelPath,
703
+ scope,
704
+ content_hash_sha256: hashContent(outputContent)
705
+ });
687
706
  return "copied";
688
707
  } catch (e) {
689
708
  if (e instanceof WriteError) {
@@ -760,7 +779,92 @@ function dirExists(p) {
760
779
  return false;
761
780
  }
762
781
  }
782
+ const FAMILIES = [
783
+ "skills",
784
+ "rules",
785
+ "agents",
786
+ "luminaries",
787
+ "hooks",
788
+ "libs",
789
+ "adrs",
790
+ "standards",
791
+ "templates",
792
+ "presence-templates",
793
+ "scripts",
794
+ "root-docs",
795
+ "other"
796
+ ];
797
+ const UNDER_CLAUDE = `${CLAUDE_TARGET_ROOT}/`;
798
+ const PREFIX_RULES = [
799
+ [`${UNDER_CLAUDE}skills/`, "skills"],
800
+ [`${UNDER_CLAUDE}rules/`, "rules"],
801
+ [`${UNDER_CLAUDE}agents/`, "agents"],
802
+ [`${UNDER_CLAUDE}luminaries/`, "luminaries"],
803
+ [`${UNDER_CLAUDE}hooks/`, "hooks"],
804
+ ["lib/", "libs"],
805
+ ["architecture/decisions/", "adrs"],
806
+ ["standards/", "standards"],
807
+ ["templates/", "templates"],
808
+ ["presence/install/", "presence-templates"],
809
+ ["scripts/", "scripts"]
810
+ ];
811
+ function classifyEntry(path) {
812
+ for (const [prefix, family] of PREFIX_RULES) {
813
+ if (path.startsWith(prefix)) return family;
814
+ }
815
+ if (!path.includes("/") && /^[A-Z]/.test(path)) return "root-docs";
816
+ return "other";
817
+ }
818
+ function emptyCatalogCounts() {
819
+ const out = {};
820
+ for (const f of FAMILIES) out[f] = 0;
821
+ return out;
822
+ }
823
+ function buildInitReport(input) {
824
+ const catalog = emptyCatalogCounts();
825
+ let project = 0;
826
+ let user = 0;
827
+ for (const e of input.entries) {
828
+ catalog[classifyEntry(e.path)] += 1;
829
+ if (e.scope === "user") user += 1;
830
+ else project += 1;
831
+ }
832
+ const refused = input.refused.length;
833
+ const errored = input.errored.length;
834
+ const written = input.configs.length + input.entries.length;
835
+ return {
836
+ schema_version: 3,
837
+ tier: input.tier,
838
+ totals: {
839
+ files: written + refused + errored,
840
+ written,
841
+ project,
842
+ user,
843
+ configs: input.configs.length
844
+ },
845
+ catalog,
846
+ hooks: {
847
+ declared: input.hookCount,
848
+ copied: input.declaredHooksCopied,
849
+ files: catalog.hooks
850
+ },
851
+ refused,
852
+ errored,
853
+ failed: refused + errored
854
+ };
855
+ }
856
+ function renderJsonReport(report, write) {
857
+ write(JSON.stringify(report) + "\n");
858
+ }
763
859
  const RESOLVED_TIER = "lite";
860
+ const SAY_HUMAN = (s) => {
861
+ process.stdout.write(s);
862
+ };
863
+ const SAY_QUIET = () => {
864
+ };
865
+ function makeSay(json) {
866
+ return json ? SAY_QUIET : SAY_HUMAN;
867
+ }
764
868
  const PLACEHOLDER_FILES = /* @__PURE__ */ new Set([
765
869
  "CLAUDE.md",
766
870
  "docs/whereami.md",
@@ -802,7 +906,7 @@ function runInit(argv) {
802
906
  }
803
907
  throw e;
804
908
  }
805
- const advisoryOutcome = maybeEmitUpgradeAdvisory(targetDir, args.yes);
909
+ const advisoryOutcome = maybeEmitUpgradeAdvisory(targetDir, args.yes, args.json);
806
910
  if (advisoryOutcome === "refused") return 1;
807
911
  const upgradeApproved = advisoryOutcome === "upgrade-approved";
808
912
  if (!args.force && !args.dryRun && !upgradeApproved) {
@@ -824,26 +928,55 @@ function runInit(argv) {
824
928
  templateVersion: SUBSTRATE_CONFIG_TEMPLATE_VERSION
825
929
  }
826
930
  ];
931
+ const say = makeSay(args.json);
827
932
  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) {
933
+ runDryRun$1(plans, say);
934
+ const outcome = dispatchSubstrateCopy(
935
+ targetDir,
936
+ args.force || upgradeApproved,
937
+ args.verbose,
938
+ true,
939
+ args.allowRoot,
940
+ say
941
+ );
942
+ if (args.json) {
943
+ const report = buildInitReport({
944
+ entries: outcome.result?.wouldCopyEntries ?? [],
945
+ configs: plans.map((pl) => ({ path: pl.relativePath })),
946
+ refused: outcome.result?.refused ?? [],
947
+ // A dry run still reads every source file, so it can still fail
948
+ // to read one. Hard-coding this empty hid those failures from
949
+ // the JSON (RFC-0005 A-3).
950
+ errored: outcome.result?.errored ?? [],
951
+ hookCount: outcome.result?.hookCount ?? 0,
952
+ declaredHooksCopied: 0,
953
+ tier: RESOLVED_TIER
954
+ });
955
+ renderJsonReport(report, (t) => {
956
+ process.stdout.write(t);
957
+ });
958
+ }
959
+ return outcome.code;
960
+ }
961
+ return runReal$1(plans, args.force || upgradeApproved, args.verbose, targetDir, args.allowRoot, args.json, say);
962
+ }
963
+ function maybeEmitUpgradeAdvisory(targetDir, yes, json) {
964
+ const prompt = json ? (t) => {
965
+ process.stderr.write(t);
966
+ } : SAY_HUMAN;
834
967
  const manifestPath = join(targetDir, MANIFEST_RELATIVE_PATH);
835
968
  if (!existsSync(manifestPath)) return "ok";
836
969
  const version2 = readManifestShapeVersion(targetDir);
837
970
  if (version2 !== null && version2 >= 2) return "ok";
838
- process.stdout.write(
971
+ prompt(
839
972
  `bassclef init: cli 1.0.1 introduces user-scope hook installation at ~/${HOOKS_SUBPATH}. cli 1.0.0 did not write there.
840
973
  `
841
974
  );
842
975
  if (yes) return "upgrade-approved";
843
- process.stdout.write("bassclef init: continue? (y/N) ");
976
+ prompt("bassclef init: continue? (y/N) ");
844
977
  const answer = readOneLineFromStdin();
845
978
  if (answer === "" || answer === "y" || answer === "Y") return "upgrade-approved";
846
- process.stdout.write("bassclef init: aborted by adopter.\n");
979
+ prompt("bassclef init: aborted by adopter.\n");
847
980
  return "refused";
848
981
  }
849
982
  function readOneLineFromStdin() {
@@ -855,7 +988,7 @@ function readOneLineFromStdin() {
855
988
  return "";
856
989
  }
857
990
  }
858
- function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, json) {
991
+ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, say) {
859
992
  const substitute = makePlaceholderTransform(targetDir);
860
993
  let result;
861
994
  try {
@@ -869,9 +1002,9 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
869
1002
  if (e instanceof CopyFailure) {
870
1003
  process.stderr.write(`bassclef init: ${e.message}
871
1004
  `);
872
- if (e.kind === "ManifestMissing") return 4;
873
- if (e.kind === "SchemaIncompatible") return 5;
874
- return 2;
1005
+ if (e.kind === "ManifestMissing") return { code: 4 };
1006
+ if (e.kind === "SchemaIncompatible") return { code: 5 };
1007
+ return { code: 2 };
875
1008
  }
876
1009
  throw e;
877
1010
  }
@@ -879,23 +1012,23 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
879
1012
  const wouldCopy = result.wouldCopy ?? [];
880
1013
  for (const relativePath of wouldCopy) {
881
1014
  const targetPath = join(targetDir, relativePath);
882
- process.stdout.write(` ${"would create".padEnd(14)} ${targetPath}
1015
+ say(` ${"would create".padEnd(14)} ${targetPath}
883
1016
  `);
884
1017
  }
885
1018
  if (wouldCopy.length > 0) {
886
- process.stdout.write(
1019
+ say(
887
1020
  `bassclef init: ${wouldCopy.length} substrate files would be copied.
888
1021
  `
889
1022
  );
890
1023
  }
891
- process.stdout.write(
1024
+ say(
892
1025
  `bassclef init: ${result.hookCount} hooks armed (${RESOLVED_TIER} tier).
893
1026
  `
894
1027
  );
895
- return 0;
1028
+ return { code: 0, result };
896
1029
  }
897
1030
  if (result.copied.length === 0 && result.refused.length === 0 && result.errored.length === 0) {
898
- return 0;
1031
+ return { code: 0, result };
899
1032
  }
900
1033
  const groupCounts = /* @__PURE__ */ new Map();
901
1034
  for (const path of result.copied) {
@@ -904,17 +1037,17 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
904
1037
  groupCounts.set(top, (groupCounts.get(top) ?? 0) + 1);
905
1038
  }
906
1039
  for (const [directory, count] of groupCounts) {
907
- process.stdout.write(` ${directory}: ${count} files copied
1040
+ say(` ${directory}: ${count} files copied
908
1041
  `);
909
1042
  }
910
1043
  const parts = [];
911
1044
  if (result.copied.length > 0) parts.push(`${result.copied.length} substrate files copied`);
912
1045
  if (result.refused.length > 0) parts.push(`${result.refused.length} refused`);
913
1046
  if (result.errored.length > 0) parts.push(`${result.errored.length} error(s)`);
914
- process.stdout.write(`bassclef init: ${parts.join(", ")}.
1047
+ say(`bassclef init: ${parts.join(", ")}.
915
1048
  `);
916
1049
  const grandTotal = 1 + result.copied.length;
917
- process.stdout.write(
1050
+ say(
918
1051
  `bassclef init: ${grandTotal} files total (1 config + ${result.copied.length} substrate).
919
1052
  `
920
1053
  );
@@ -926,17 +1059,17 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
926
1059
  );
927
1060
  const copiedCount = copiedHookEntries.length;
928
1061
  const declaredCount = result.hookCount;
929
- const failedCount = result.refused.length + result.errored.length;
1062
+ const erroredCount = result.errored.length;
930
1063
  const userScope = copiedHookEntries.filter((e) => e.scope === "user").length;
931
1064
  const projectScope = copiedHookEntries.filter((e) => e.scope === "project").length;
932
1065
  const scopeSuffix = userScope + projectScope > 0 ? ` ${projectScope} in <repo>/${HOOKS_SUBPATH.replace(/\/$/, "")}, ${userScope} in ~/${HOOKS_SUBPATH.replace(/\/$/, "")}.` : "";
933
- if (failedCount > 0) {
934
- process.stdout.write(
935
- `bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix} ${failedCount} failed — see errors above. Rerun bassclef init to retry.
1066
+ if (erroredCount > 0) {
1067
+ say(
1068
+ `bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix} ${erroredCount} failed — see errors above. Rerun bassclef init to retry.
936
1069
  `
937
1070
  );
938
1071
  } else {
939
- process.stdout.write(
1072
+ say(
940
1073
  `bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix}
941
1074
  `
942
1075
  );
@@ -981,7 +1114,7 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
981
1114
  catalogCounts.luminaries > 0 ? `${catalogCounts.luminaries} luminaries` : null
982
1115
  ].filter((s) => s !== null);
983
1116
  if (claudeCounts.length > 0) {
984
- process.stdout.write(
1117
+ say(
985
1118
  `bassclef init: Installed ${claudeCounts.join(", ")} under <repo>/.claude/.
986
1119
  `
987
1120
  );
@@ -996,24 +1129,16 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
996
1129
  catalogCounts.scripts > 0 ? `${catalogCounts.scripts} scripts` : null
997
1130
  ].filter((s) => s !== null);
998
1131
  if (otherCounts.length > 0) {
999
- process.stdout.write(
1132
+ say(
1000
1133
  `bassclef init: Installed ${otherCounts.join(", ")} under <repo>/.
1001
1134
  `
1002
1135
  );
1003
1136
  }
1004
- process.stdout.write(
1005
- `bassclef init: ${result.refused.length} files refused (path collision).` + (result.refused.length > 0 ? ` Use --force to overwrite existing files.` : ``) + `
1137
+ if (result.refused.length > 0) {
1138
+ say(
1139
+ `bassclef init: ${result.refused.length} files refused (path collision). Use --force to overwrite existing files.
1006
1140
  `
1007
- );
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");
1141
+ );
1017
1142
  }
1018
1143
  if (verbose && result.erroredMessages) {
1019
1144
  for (const msg of result.erroredMessages) {
@@ -1021,7 +1146,7 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, jso
1021
1146
  `);
1022
1147
  }
1023
1148
  }
1024
- return result.errored.length > 0 ? 2 : 0;
1149
+ return { code: result.errored.length > 0 ? 2 : 0, result };
1025
1150
  }
1026
1151
  function makePlaceholderTransform(targetDir) {
1027
1152
  const repoName = basename$1(targetDir);
@@ -1031,7 +1156,7 @@ function makePlaceholderTransform(targetDir) {
1031
1156
  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
1157
  };
1033
1158
  }
1034
- function runDryRun$1(plans) {
1159
+ function runDryRun$1(plans, say) {
1035
1160
  for (const p of plans) {
1036
1161
  let planned;
1037
1162
  let extra = "";
@@ -1051,12 +1176,12 @@ function runDryRun$1(plans) {
1051
1176
  planned = "would skip";
1052
1177
  }
1053
1178
  }
1054
- process.stdout.write(` ${planned.padEnd(14)} ${p.fullPath}${extra}
1179
+ say(` ${planned.padEnd(14)} ${p.fullPath}${extra}
1055
1180
  `);
1056
1181
  }
1057
1182
  return 0;
1058
1183
  }
1059
- function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1184
+ function runReal$1(plans, force, verbose, targetDir, allowRoot, json, say) {
1060
1185
  const results = [];
1061
1186
  let anyRefused = false;
1062
1187
  let anyError = false;
@@ -1104,7 +1229,7 @@ function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1104
1229
  if (r.outcome === "error") label = `error (${r.message ?? "unknown"})`;
1105
1230
  else if (r.outcome === "refused") label = "refused";
1106
1231
  else label = r.outcome;
1107
- process.stdout.write(` ${label.padEnd(10)} ${r.plan.fullPath}
1232
+ say(` ${label.padEnd(10)} ${r.plan.fullPath}
1108
1233
  `);
1109
1234
  }
1110
1235
  }
@@ -1126,38 +1251,59 @@ function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
1126
1251
  return 2;
1127
1252
  }
1128
1253
  if (anyRefused && created > 0) {
1129
- process.stdout.write(
1254
+ say(
1130
1255
  `bassclef init: ${created} config files created, ${unchanged} unchanged. Pass --force to overwrite.
1131
1256
  `
1132
1257
  );
1133
1258
  } else if (created === 0 && unchanged === plans.length) {
1134
- process.stdout.write("bassclef init: already initialized. No changes.\n");
1259
+ say("bassclef init: already initialized. No changes.\n");
1135
1260
  } else {
1136
- process.stdout.write(`bassclef init: ${created} config files created, ${unchanged} unchanged.
1261
+ say(`bassclef init: ${created} config files created, ${unchanged} unchanged.
1137
1262
  `);
1138
1263
  }
1139
- const walkerExit = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, json);
1140
- writeManifest(targetDir, results);
1141
- if (walkerExit === 0) {
1142
- process.stdout.write(
1264
+ const walker = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, say);
1265
+ const declaredLeaves = readDeclaredCommandLeaves(
1266
+ join(targetDir, ".claude", "settings.json")
1267
+ );
1268
+ const declaredHooksCopied = (walker.result?.copiedEntries ?? []).filter(
1269
+ (e) => e.path.startsWith(HOOKS_SUBPATH) && e.path.endsWith(".sh") && declaredLeaves.has(basename$1(e.path))
1270
+ ).length;
1271
+ const report = buildInitReport({
1272
+ entries: walker.result?.copiedEntries ?? [],
1273
+ configs: results.map((r) => ({ path: r.plan.relativePath })),
1274
+ refused: walker.result?.refused ?? [],
1275
+ errored: walker.result?.errored ?? [],
1276
+ hookCount: walker.result?.hookCount ?? 0,
1277
+ declaredHooksCopied,
1278
+ tier: RESOLVED_TIER
1279
+ });
1280
+ writeManifest(targetDir, results, walker.result);
1281
+ if (walker.code === 0) {
1282
+ say(
1143
1283
  `bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
1144
1284
  `
1145
1285
  );
1146
1286
  }
1147
- return walkerExit;
1287
+ if (json) {
1288
+ renderJsonReport(report, (t) => {
1289
+ process.stdout.write(t);
1290
+ });
1291
+ }
1292
+ return walker.code;
1148
1293
  }
1149
1294
  function shouldRefuseRoot(currentUid, allowRoot) {
1150
1295
  if (currentUid === void 0) return false;
1151
1296
  if (currentUid !== 0) return false;
1152
1297
  return !allowRoot;
1153
1298
  }
1154
- function writeManifest(targetDir, results) {
1299
+ function writeManifest(targetDir, results, walkerResult) {
1155
1300
  const entries = results.map((r) => {
1156
1301
  const entry = {
1157
1302
  path: r.plan.relativePath,
1158
1303
  template: r.plan.templateName,
1159
1304
  template_version: r.plan.templateVersion,
1160
- outcome: r.outcome
1305
+ outcome: r.outcome,
1306
+ source: "config-composer"
1161
1307
  };
1162
1308
  if (r.outcome === "created") {
1163
1309
  entry.content_hash_sha256 = hashContent(r.plan.content);
@@ -1165,6 +1311,38 @@ function writeManifest(targetDir, results) {
1165
1311
  }
1166
1312
  return entry;
1167
1313
  });
1314
+ if (walkerResult) {
1315
+ for (const copied of walkerResult.copiedEntries) {
1316
+ entries.push({
1317
+ path: copied.path,
1318
+ template: `bundle:${copied.path}`,
1319
+ template_version: version,
1320
+ outcome: "created",
1321
+ source: "bundle",
1322
+ scope: copied.scope,
1323
+ ...copied.content_hash_sha256 ? { content_hash_sha256: copied.content_hash_sha256 } : {},
1324
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1325
+ });
1326
+ }
1327
+ for (const path of walkerResult.refused) {
1328
+ entries.push({
1329
+ path,
1330
+ template: `bundle:${path}`,
1331
+ template_version: version,
1332
+ outcome: "refused",
1333
+ source: "bundle"
1334
+ });
1335
+ }
1336
+ for (const path of walkerResult.errored) {
1337
+ entries.push({
1338
+ path,
1339
+ template: `bundle:${path}`,
1340
+ template_version: version,
1341
+ outcome: "error",
1342
+ source: "bundle"
1343
+ });
1344
+ }
1345
+ }
1168
1346
  const manifestDir = join(targetDir, ".bassclef");
1169
1347
  const manifestPath = join(manifestDir, "init.manifest.json");
1170
1348
  const content = manifestTemplate({
@@ -1175,7 +1353,12 @@ function writeManifest(targetDir, results) {
1175
1353
  try {
1176
1354
  mkdirSafely(manifestDir);
1177
1355
  writeSafely(manifestPath, content, { force: true });
1178
- } catch {
1356
+ } catch (e) {
1357
+ const reason = e instanceof Error ? e.message : String(e);
1358
+ process.stderr.write(
1359
+ `bassclef init: could not write ${manifestPath} (${reason}). The files were written. Run \`bassclef init --force\` to rebuild the record.
1360
+ `
1361
+ );
1179
1362
  }
1180
1363
  }
1181
1364
  function usage$2() {
@@ -1626,7 +1809,10 @@ function emitL2Output(decisions, verbose) {
1626
1809
  }
1627
1810
  }
1628
1811
  function updateManifestEntry(manifest, path, patch) {
1629
- const idx = manifest.files.findIndex((f) => f.path === path);
1812
+ const wantScope = patch.scope;
1813
+ const idx = manifest.files.findIndex(
1814
+ (f) => f.path === path && (wantScope === void 0 || f.scope === wantScope)
1815
+ );
1630
1816
  if (idx < 0) {
1631
1817
  manifest.files.push({
1632
1818
  path,
@@ -1810,6 +1996,12 @@ Proceed?`,
1810
1996
  const errored = copyResult.errored;
1811
1997
  const newManifest = {
1812
1998
  schema_version: MANIFEST_SHAPE_VERSION,
1999
+ // Top-level `.version` — read by the v0.45.0 npm-version-drift hook
2000
+ // (fragment 91 under session-reflection) via lib/npm-version-check.sh
2001
+ // `jq -r '.version // ""' "$manifest"`. Duplicates
2002
+ // $bassclef.generated_by_version on purpose — the hook does not walk
2003
+ // into $bassclef. Per bassclef-upstream#1749; bassclef-cli#129.
2004
+ version,
1813
2005
  $bassclef: {
1814
2006
  template: "init.manifest.json",
1815
2007
  manifest_schema_version: "0.1.0",
@@ -1977,6 +2169,9 @@ Run \`bassclef migrate --help\` for migrate options + the upgrade paths.
1977
2169
  Version: ${version}
1978
2170
  Docs: https://github.com/sunj-labs/bassclef-cli
1979
2171
  `;
2172
+ for (const stream of [process.stdout, process.stderr]) {
2173
+ stream._handle?.setBlocking?.(true);
2174
+ }
1980
2175
  async function main(argv) {
1981
2176
  const first = argv[0];
1982
2177
  if (first === void 0 || first === "--help" || first === "-h" || first === "help") {
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.2.0-alpha.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.2.0-alpha.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.2.0-alpha.1";
2
2
  export {
3
3
  version
4
4
  };
@@ -0,0 +1,56 @@
1
+ // tier: lite
2
+ // bassclef adopter config — tune substrate rules from one file.
3
+ // Reference schema: standards/bassclef-configs-schema.md
4
+ // Every block below matches what a substrate rule reads. Values shown
5
+ // are the defaults; delete a block and the default kicks in.
6
+ {
7
+ // What your project runs. Frontend framework the /shadcn-first and
8
+ // /new-dependency-check rules key off. Common values: "vite-react",
9
+ // "nextjs", "remix", "vue", "svelte", "none".
10
+ "tech_stack": {
11
+ "frontend": "none"
12
+ },
13
+
14
+ // Prose discipline. The kiss-check Stop hook watches your writing
15
+ // for jargon. Value "true" = advisory (findings to stderr, no block).
16
+ // Value "strict" = BLOCK the stop, forcing a rewrite turn.
17
+ // Value false = disabled.
18
+ "prose_discipline": {
19
+ "kiss_words_turn_prose": true
20
+ },
21
+
22
+ // Where /promote sends proposals. Two shapes:
23
+ // "<owner>/<repo>" — gh issue create --repo <value>
24
+ // "email:<address>" — mailto: URL printed for adopter to send
25
+ // Absent → /promote files in the current repo (backward-compat).
26
+ // "promote_target": "your-org/your-repo",
27
+
28
+ // Testing tiers per .claude/rules/testing-tier-config.md. Global
29
+ // floor sets the baseline for every path that no matcher covers.
30
+ // Tier 0 = strict TDD (test-first, BLOCK on missing). Tier 1 =
31
+ // test-with (WARN on missing). Tier 2 = smoke test only. Tier 3 =
32
+ // manual verify (no test discipline).
33
+ "testing": {
34
+ "global_floor": 1,
35
+ "path_matchers": [
36
+ { "match": ".claude/rules/*.md", "tier": 3 },
37
+ { "match": "state/markers/*/*", "tier": 3 },
38
+ { "match": "chronicle/*.md", "tier": 3 },
39
+ { "match": "docs/iteration-bets/*.md", "tier": 3 },
40
+ { "match": "docs/decompositions/*.md", "tier": 3 }
41
+ ]
42
+ },
43
+
44
+ // /longrun preset picker per .claude/skills/longrun/SKILL.md. Values:
45
+ // "converged" — plan doc drives; lean prep output
46
+ // "exploratory" — scan-table with no cards until asked
47
+ // "reversible-small" — three-chunk compact for small scope
48
+ // "auto" — picker decides per session signals (default)
49
+ "longrun": {
50
+ "preset": "auto"
51
+ },
52
+
53
+ // Adopter defaults surface. Reserved for future rules. Absent =
54
+ // downstream rules keep their own defaults.
55
+ "adopter_defaults": {}
56
+ }