@thebassclef/lite 0.1.3 → 1.0.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 (293) hide show
  1. package/dist/cli.cjs +431 -135
  2. package/dist/cli.js +433 -137
  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/.bassclef-source.json +10 -0
  7. package/{substrate → dist/lite}/.claude/hooks/artifact-ingestion-gate.sh +0 -0
  8. package/{substrate → dist/lite}/.claude/hooks/assert-verify-steering.sh +0 -0
  9. package/{substrate → dist/lite}/.claude/hooks/bassclef-source-config-validate.sh +0 -0
  10. package/{substrate → dist/lite}/.claude/hooks/bassclef-sync.sh +0 -0
  11. package/{substrate → dist/lite}/.claude/hooks/compound-noun-scrub.sh +0 -0
  12. package/{substrate → dist/lite}/.claude/hooks/kiss-expansion-inject.sh +0 -0
  13. package/{substrate → dist/lite}/.claude/hooks/longrun-prep-compounding-sequence-check.sh +0 -0
  14. package/{substrate → dist/lite}/.claude/hooks/plain-english-steering.sh +0 -0
  15. package/{substrate → dist/lite}/.claude/hooks/post-skill-friction-check.sh +0 -0
  16. package/{substrate → dist/lite}/.claude/hooks/post-skill-telemetry.sh +0 -0
  17. package/{substrate → dist/lite}/.claude/hooks/pre-build-gate.sh +0 -0
  18. package/{substrate → dist/lite}/.claude/hooks/pre-commit-gate.sh +0 -0
  19. package/{substrate → dist/lite}/.claude/hooks/session-end.sh +0 -0
  20. package/{substrate → dist/lite}/.claude/hooks/session-reflection.sh +0 -0
  21. package/{substrate → dist/lite}/.claude/hooks/skill-body-grade-gate.sh +0 -0
  22. package/{substrate → dist/lite}/.claude/hooks/skill-body-intent-drift.sh +0 -0
  23. package/{substrate → dist/lite}/.claude/hooks/state-validate.sh +0 -0
  24. package/{substrate → dist/lite}/.claude/hooks/substrate-clarity-gate.sh +0 -0
  25. package/{substrate → dist/lite}/.claude/hooks/temperance-gate.sh +0 -0
  26. package/{substrate → dist/lite}/.claude/hooks/testing-tier-enforce.sh +0 -0
  27. package/{substrate → dist/lite}/.claude/hooks/turn-prose-grade-measure.sh +0 -0
  28. package/{substrate → dist/lite}/.claude/hooks/turn-prose-kiss-check.sh +0 -0
  29. package/{substrate → dist/lite}/.claude/hooks/vocabulary-migration-check.sh +0 -0
  30. package/{substrate → dist/lite}/.claude/hooks/whereami-utc-gate.sh +0 -0
  31. package/dist/lite/.claude/settings.json +212 -0
  32. package/dist/lite/CLAUDE.md +41 -0
  33. package/dist/lite/gitignore +58 -0
  34. package/dist/lite/standards/bassclef-wiring-manifest.json +497 -0
  35. package/dist/lite/whereami.md +24 -0
  36. package/package.json +8 -2
  37. package/substrate/.bassclef/lite-manifest.json +0 -2701
  38. package/substrate/.claude/agents/architect.md +0 -70
  39. package/substrate/.claude/agents/builder.md +0 -114
  40. package/substrate/.claude/agents/designer.md +0 -156
  41. package/substrate/.claude/agents/reviewer.md +0 -88
  42. package/substrate/.claude/hooks/longrun-prep-compounding-axis-check.sh +0 -492
  43. package/substrate/.claude/hooks/skill-step-list-check.sh +0 -171
  44. package/substrate/.claude/luminaries/alan-cooper.md +0 -170
  45. package/substrate/.claude/luminaries/alistair-cockburn.md +0 -140
  46. package/substrate/.claude/luminaries/amazon-pr-faq.md +0 -34
  47. package/substrate/.claude/luminaries/ash-maurya.md +0 -121
  48. package/substrate/.claude/luminaries/bill-buxton.md +0 -210
  49. package/substrate/.claude/luminaries/charles-sanders-peirce.md +0 -150
  50. package/substrate/.claude/luminaries/david-ogilvy.md +0 -192
  51. package/substrate/.claude/luminaries/don-norman.md +0 -173
  52. package/substrate/.claude/luminaries/edward-tufte.md +0 -179
  53. package/substrate/.claude/luminaries/eric-evans.md +0 -160
  54. package/substrate/.claude/luminaries/frederick-brooks.md +0 -50
  55. package/substrate/.claude/luminaries/gang-of-four.md +0 -157
  56. package/substrate/.claude/luminaries/glenford-myers.md +0 -230
  57. package/substrate/.claude/luminaries/hunt-thomas.md +0 -115
  58. package/substrate/.claude/luminaries/hyrum-wright.md +0 -94
  59. package/substrate/.claude/luminaries/jason-fried-dhh.md +0 -46
  60. package/substrate/.claude/luminaries/jesse-james-garrett.md +0 -154
  61. package/substrate/.claude/luminaries/john-ousterhout.md +0 -94
  62. package/substrate/.claude/luminaries/karl-popper.md +0 -132
  63. package/substrate/.claude/luminaries/kent-beck.md +0 -168
  64. package/substrate/.claude/luminaries/linus-torvalds.md +0 -218
  65. package/substrate/.claude/luminaries/martin-fowler.md +0 -164
  66. package/substrate/.claude/luminaries/michael-feathers.md +0 -125
  67. package/substrate/.claude/luminaries/michael-nygard.md +0 -118
  68. package/substrate/.claude/luminaries/robert-c-martin.md +0 -164
  69. package/substrate/.claude/luminaries/saltzer-schroeder.md +0 -194
  70. package/substrate/.claude/luminaries/sophia-prater.md +0 -193
  71. package/substrate/.claude/luminaries/stephen-toulmin.md +0 -131
  72. package/substrate/.claude/luminaries/tony-hoare.md +0 -170
  73. package/substrate/.claude/luminaries/vaughn-vernon.md +0 -50
  74. package/substrate/.claude/luminaries/w-edwards-deming.md +0 -158
  75. package/substrate/.claude/rules/accessor-library-discipline.md +0 -138
  76. package/substrate/.claude/rules/adr-discipline.md +0 -120
  77. package/substrate/.claude/rules/api-conventions.md +0 -125
  78. package/substrate/.claude/rules/artifact-ingestion.md +0 -179
  79. package/substrate/.claude/rules/assert-only-after-verify.md +0 -137
  80. package/substrate/.claude/rules/blocked-items.md +0 -146
  81. package/substrate/.claude/rules/bootstrap-pair-discipline.md +0 -141
  82. package/substrate/.claude/rules/branching.md +0 -28
  83. package/substrate/.claude/rules/cold-adopter-harness-discipline.md +0 -129
  84. package/substrate/.claude/rules/commit-conventions.md +0 -22
  85. package/substrate/.claude/rules/compounding-axis-fresh-analysis.md +0 -188
  86. package/substrate/.claude/rules/compounding-sequence-fresh-analysis.md +0 -188
  87. package/substrate/.claude/rules/context-engineering.md +0 -202
  88. package/substrate/.claude/rules/context-management.md +0 -85
  89. package/substrate/.claude/rules/defensive-bash.md +0 -68
  90. package/substrate/.claude/rules/deferred-actions.md +0 -233
  91. package/substrate/.claude/rules/destructive-operations.md +0 -69
  92. package/substrate/.claude/rules/diagnosis.md +0 -38
  93. package/substrate/.claude/rules/github-issue-flash-tweet.md +0 -156
  94. package/substrate/.claude/rules/guardrails.md +0 -73
  95. package/substrate/.claude/rules/hook-wire-on-author.md +0 -103
  96. package/substrate/.claude/rules/identifier-leak-prevention.md +0 -104
  97. package/substrate/.claude/rules/iteration-bet-brief-completeness.md +0 -54
  98. package/substrate/.claude/rules/lite-manifest-schema-change-discipline.md +0 -98
  99. package/substrate/.claude/rules/longrun-prep-plan-doc-compression.md +0 -89
  100. package/substrate/.claude/rules/loop-discipline.md +0 -81
  101. package/substrate/.claude/rules/manual-prod-approval.md +0 -100
  102. package/substrate/.claude/rules/marker-enrichment-discipline.md +0 -99
  103. package/substrate/.claude/rules/mobile-ephemeral-session.md +0 -109
  104. package/substrate/.claude/rules/new-dependency-check.md +0 -51
  105. package/substrate/.claude/rules/oo-ad-entry-point.md +0 -117
  106. package/substrate/.claude/rules/operator-facing-prose.md +0 -196
  107. package/substrate/.claude/rules/option-label-discipline.md +0 -108
  108. package/substrate/.claude/rules/pattern-annotation.md +0 -100
  109. package/substrate/.claude/rules/plain-english-discipline.md +0 -156
  110. package/substrate/.claude/rules/plan-enumeration-needs-value-props.md +0 -211
  111. package/substrate/.claude/rules/pr-body-shape.md +0 -317
  112. package/substrate/.claude/rules/pr-strategy.md +0 -167
  113. package/substrate/.claude/rules/pr-title-shape.md +0 -161
  114. package/substrate/.claude/rules/prototype-workflow.md +0 -65
  115. package/substrate/.claude/rules/reserved-skill-names.md +0 -123
  116. package/substrate/.claude/rules/schema-management.md +0 -49
  117. package/substrate/.claude/rules/sdlc-gates.md +0 -149
  118. package/substrate/.claude/rules/security.md +0 -37
  119. package/substrate/.claude/rules/session-artifacts.md +0 -236
  120. package/substrate/.claude/rules/skill-composition-declarations.md +0 -124
  121. package/substrate/.claude/rules/skill-description-clarity.md +0 -247
  122. package/substrate/.claude/rules/skill-procedure-step-list.md +0 -137
  123. package/substrate/.claude/rules/state-schema-validation.md +0 -162
  124. package/substrate/.claude/rules/stuck-signal-diagnostic.md +0 -140
  125. package/substrate/.claude/rules/substrate-config-schema.md +0 -98
  126. package/substrate/.claude/rules/test-list-discipline.md +0 -175
  127. package/substrate/.claude/rules/test-sufficiency.md +0 -210
  128. package/substrate/.claude/rules/testing-tier-config.md +0 -145
  129. package/substrate/.claude/rules/testing.md +0 -38
  130. package/substrate/.claude/rules/turn-estimate-grounding.md +0 -134
  131. package/substrate/.claude/rules/visual-hierarchy.md +0 -437
  132. package/substrate/.claude/rules/we-dont-break-adopters.md +0 -126
  133. package/substrate/.claude/rules/whereami-load-bearing.md +0 -202
  134. package/substrate/.claude/rules/writing-craft-discipline.md +0 -92
  135. package/substrate/.claude/rules/wu-sequencing-compounds.md +0 -145
  136. package/substrate/.claude/skills/build/SKILL.md +0 -640
  137. package/substrate/.claude/skills/chronicle/SKILL.md +0 -55
  138. package/substrate/.claude/skills/clean-artifacts/SKILL.md +0 -249
  139. package/substrate/.claude/skills/decompose/SKILL.md +0 -280
  140. package/substrate/.claude/skills/diagnose/SKILL.md +0 -297
  141. package/substrate/.claude/skills/feynman/SKILL.md +0 -90
  142. package/substrate/.claude/skills/howdoi/SKILL.md +0 -105
  143. package/substrate/.claude/skills/ia-model/SKILL.md +0 -108
  144. package/substrate/.claude/skills/interaction-design/SKILL.md +0 -112
  145. package/substrate/.claude/skills/interpret-input/SKILL.md +0 -180
  146. package/substrate/.claude/skills/journal/SKILL.md +0 -209
  147. package/substrate/.claude/skills/kiss/SKILL.md +0 -449
  148. package/substrate/.claude/skills/launch/SKILL.md +0 -915
  149. package/substrate/.claude/skills/lean-canvas/SKILL.md +0 -332
  150. package/substrate/.claude/skills/longrun/SKILL.md +0 -463
  151. package/substrate/.claude/skills/luminary/SKILL.md +0 -481
  152. package/substrate/.claude/skills/ogilvy-writing-audit/SKILL.md +0 -177
  153. package/substrate/.claude/skills/onboard-repo/SKILL.md +0 -1624
  154. package/substrate/.claude/skills/pattern-review/SKILL.md +0 -99
  155. package/substrate/.claude/skills/personas/SKILL.md +0 -207
  156. package/substrate/.claude/skills/promote/SKILL.md +0 -283
  157. package/substrate/.claude/skills/requirement/SKILL.md +0 -98
  158. package/substrate/.claude/skills/retro/SKILL.md +0 -117
  159. package/substrate/.claude/skills/riff/SKILL.md +0 -114
  160. package/substrate/.claude/skills/roadmap-reconcile/SKILL.md +0 -163
  161. package/substrate/.claude/skills/session-end/SKILL.md +0 -309
  162. package/substrate/.claude/skills/session-log/SKILL.md +0 -299
  163. package/substrate/.claude/skills/skills/SKILL.md +0 -228
  164. package/substrate/.claude/skills/spec/SKILL.md +0 -105
  165. package/substrate/.claude/skills/sprint/SKILL.md +0 -392
  166. package/substrate/.claude/skills/stage/SKILL.md +0 -384
  167. package/substrate/.claude/skills/state-a-problem/SKILL.md +0 -185
  168. package/substrate/.claude/skills/temperance/SKILL.md +0 -108
  169. package/substrate/.claude/skills/use-case/SKILL.md +0 -417
  170. package/substrate/.claude/skills/user-stories/SKILL.md +0 -268
  171. package/substrate/.claude/skills/value-prop/SKILL.md +0 -251
  172. package/substrate/.claude/skills/verify/SKILL.md +0 -160
  173. package/substrate/.claude/skills/visual-review/SKILL.md +0 -503
  174. package/substrate/.claude/skills/whats-the-plan/SKILL.md +0 -202
  175. package/substrate/.claude/skills/whereami/SKILL.md +0 -307
  176. package/substrate/AGENTS.md +0 -79
  177. package/substrate/CLAUDE-lite.md +0 -85
  178. package/substrate/CODE_OF_CONDUCT.md +0 -28
  179. package/substrate/CONTRIBUTING.md +0 -177
  180. package/substrate/README.md +0 -173
  181. package/substrate/SECURITY.md +0 -19
  182. package/substrate/architecture/decisions/ADR-029-release-pipeline.md +0 -79
  183. package/substrate/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +0 -139
  184. package/substrate/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +0 -192
  185. package/substrate/architecture/decisions/ADR-039-release-tagging-scheme.md +0 -145
  186. package/substrate/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +0 -155
  187. package/substrate/architecture/decisions/ADR-044-unified-skill-body-template.md +0 -162
  188. package/substrate/lib/clean-artifacts-sweep.sh +0 -112
  189. package/substrate/lib/code-comment-discipline.sh +0 -144
  190. package/substrate/lib/composer-preflight.sh +0 -459
  191. package/substrate/lib/hook-inject.sh +0 -255
  192. package/substrate/lib/luminary-pick.sh +0 -96
  193. package/substrate/lib/output-discipline.sh +0 -143
  194. package/substrate/lib/prose-scan-boundary.sh +0 -171
  195. package/substrate/lib/rewrite-check.sh +0 -214
  196. package/substrate/lib/state.sh +0 -1372
  197. package/substrate/lib/telemetry.sh +0 -205
  198. package/substrate/lib/tier-check.sh +0 -187
  199. package/substrate/lib/tier-dependency-audit.sh +0 -1088
  200. package/substrate/presence/install/bassclef-hook-connect.sh +0 -178
  201. package/substrate/presence/install/bassclef-sync.dispatcher.template.sh +0 -841
  202. package/substrate/presence/install/bassclef-sync.template.sh +0 -2076
  203. package/substrate/presence/install/schedule-auto-save.cron.sh +0 -88
  204. package/substrate/presence/install/schedule-auto-save.taskscheduler.md +0 -122
  205. package/substrate/scripts/aggregate-telemetry.sh +0 -217
  206. package/substrate/scripts/analyze-tier-dependencies.sh +0 -239
  207. package/substrate/scripts/generate-lite-manifest.sh +0 -505
  208. package/substrate/scripts/generate-tier-manifest.sh +0 -28
  209. package/substrate/scripts/intent-drift-check.sh +0 -456
  210. package/substrate/scripts/lite-manifest-drift-check.sh +0 -146
  211. package/substrate/scripts/render-lite-manifest-doc.sh +0 -150
  212. package/substrate/standards/adr-template.md +0 -86
  213. package/substrate/standards/api-conventions/nextjs.md +0 -84
  214. package/substrate/standards/artifact-composition.md +0 -209
  215. package/substrate/standards/bash-hook-safety.md +0 -246
  216. package/substrate/standards/bassclef-configs-schema.md +0 -232
  217. package/substrate/standards/bassclef-evolution.md +0 -143
  218. package/substrate/standards/bassclef-internal-jargon.md +0 -244
  219. package/substrate/standards/bassclef-managed-sentinel.md +0 -96
  220. package/substrate/standards/bassclef-source-config.md +0 -228
  221. package/substrate/standards/branch-stacking.md +0 -408
  222. package/substrate/standards/code-safety-principles.md +0 -176
  223. package/substrate/standards/composer-prerequisites.md +0 -155
  224. package/substrate/standards/deferred-actions-schema.md +0 -204
  225. package/substrate/standards/dependency-discipline/cargo.md +0 -39
  226. package/substrate/standards/dependency-discipline/gem.md +0 -43
  227. package/substrate/standards/dependency-discipline/go-mod.md +0 -41
  228. package/substrate/standards/dependency-discipline/npm.md +0 -42
  229. package/substrate/standards/dependency-discipline/pip.md +0 -42
  230. package/substrate/standards/deployment-topology/ec2-tailscale.md +0 -225
  231. package/substrate/standards/deployment-topology.md +0 -69
  232. package/substrate/standards/docs-sync-allowlist.md +0 -76
  233. package/substrate/standards/domain-and-dns.md +0 -145
  234. package/substrate/standards/frontend-stack.md +0 -67
  235. package/substrate/standards/frontmatter-schema.md +0 -154
  236. package/substrate/standards/graceful-exit.md +0 -227
  237. package/substrate/standards/hook-idempotency.md +0 -102
  238. package/substrate/standards/hook-injection-discipline.md +0 -202
  239. package/substrate/standards/hook-install-class.md +0 -215
  240. package/substrate/standards/input-handler-interface.md +0 -152
  241. package/substrate/standards/lite-manifest-schema-changes.md +0 -135
  242. package/substrate/standards/luminary-matching.md +0 -105
  243. package/substrate/standards/luminary-problem-patterns.md +0 -481
  244. package/substrate/standards/migration-discipline/active-record.md +0 -50
  245. package/substrate/standards/migration-discipline/alembic.md +0 -43
  246. package/substrate/standards/migration-discipline/gorm.md +0 -50
  247. package/substrate/standards/migration-discipline/prisma.md +0 -53
  248. package/substrate/standards/migration-discipline/sqlalchemy.md +0 -51
  249. package/substrate/standards/mobile-ephemeral-session.md +0 -167
  250. package/substrate/standards/model-routing-discipline.md +0 -160
  251. package/substrate/standards/ogilvy-writing-rules.md +0 -225
  252. package/substrate/standards/opener-discipline.md +0 -96
  253. package/substrate/standards/operator-facing-prose-discipline.md +0 -201
  254. package/substrate/standards/persona-schema.md +0 -229
  255. package/substrate/standards/pluggable-luminaries.md +0 -323
  256. package/substrate/standards/pr-body-discipline.md +0 -115
  257. package/substrate/standards/preview-state-schema.md +0 -189
  258. package/substrate/standards/project-directory-layout.md +0 -276
  259. package/substrate/standards/release-tagging.md +0 -137
  260. package/substrate/standards/reserved-skill-names.md +0 -120
  261. package/substrate/standards/scannable-multi-option-output.md +0 -261
  262. package/substrate/standards/sdlc-compliance.md +0 -286
  263. package/substrate/standards/sdlc-gates/typescript.md +0 -57
  264. package/substrate/standards/secrets-lifecycle.md +0 -210
  265. package/substrate/standards/security-scanner-adapter.md +0 -145
  266. package/substrate/standards/session-board.md +0 -256
  267. package/substrate/standards/skill-output-discipline.md +0 -90
  268. package/substrate/standards/state-spine-contract.md +0 -255
  269. package/substrate/standards/state-spine.md +0 -511
  270. package/substrate/standards/steering-hints/kiss-words.md +0 -11
  271. package/substrate/standards/substrate-config-schema.md +0 -267
  272. package/substrate/standards/tech-stack-config.md +0 -109
  273. package/substrate/standards/tier-dependency-analysis.md +0 -167
  274. package/substrate/standards/tier-runtime-deps/lite.md +0 -57
  275. package/substrate/standards/tier-tag-schema.md +0 -155
  276. package/substrate/standards/two-layer-config.md +0 -99
  277. package/substrate/standards/use-case-format.md +0 -292
  278. package/substrate/standards/user-story-invest.md +0 -268
  279. package/substrate/standards/velocity-and-appetite.md +0 -229
  280. package/substrate/standards/voice-input-pattern.md +0 -119
  281. package/substrate/standards/whereami-schema.md +0 -301
  282. package/substrate/standards/worktree-management.md +0 -211
  283. package/substrate/standards/writing-guide.md +0 -213
  284. package/substrate/templates/chronicle-template.md +0 -75
  285. package/substrate/templates/deferred-action-template.md +0 -45
  286. package/substrate/templates/memory-proposal-template.md +0 -77
  287. package/substrate/templates/persona-template.md +0 -200
  288. package/substrate/templates/pr-faq.md +0 -45
  289. package/substrate/templates/secret-rotation-template.md +0 -162
  290. package/substrate/templates/spec-template.md +0 -131
  291. package/substrate/templates/use-case-template.md +0 -194
  292. package/substrate/templates/user-story-template.md +0 -107
  293. package/substrate/templates/whereami-template.md +0 -101
package/dist/cli.cjs CHANGED
@@ -27,7 +27,6 @@ const node_path = require("node:path");
27
27
  const node_os = require("node:os");
28
28
  const node_crypto = require("node:crypto");
29
29
  const node_url = require("node:url");
30
- const node_module = require("node:module");
31
30
  const promises = require("node:readline/promises");
32
31
  const node_process = require("node:process");
33
32
  var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
@@ -37,7 +36,9 @@ const DEFAULTS$2 = {
37
36
  verbose: false,
38
37
  allowRoot: false,
39
38
  allowAnyDir: false,
40
- dir: void 0
39
+ dir: void 0,
40
+ yes: false,
41
+ json: false
41
42
  };
42
43
  let ArgvError$1 = class ArgvError extends Error {
43
44
  name = "ArgvError";
@@ -72,6 +73,16 @@ function parseInitArgs(argv) {
72
73
  i += 1;
73
74
  continue;
74
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
+ }
75
86
  if (token === "--dir") {
76
87
  const value = argv[i + 1];
77
88
  if (value === void 0 || value.startsWith("--")) {
@@ -140,6 +151,13 @@ function isUnder(child, parent) {
140
151
  if (rel === par) return true;
141
152
  return rel.startsWith(par + "/");
142
153
  }
154
+ function readlinkOrNull(path) {
155
+ try {
156
+ return node_fs.readlinkSync(path);
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
143
161
  class WriteError extends Error {
144
162
  name = "WriteError";
145
163
  kind;
@@ -173,7 +191,12 @@ function writeSafely(path, content, opts = {}) {
173
191
  else throw new WriteError("Unknown", `cannot stat ${path}: ${err.code ?? "unknown"}`);
174
192
  }
175
193
  if (existing === "symlink") {
176
- throw new WriteError("SymlinkRefused", `refusing to follow symlink at target path: ${path}`);
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
+ );
177
200
  }
178
201
  if (existing !== "none" && !opts.force) {
179
202
  throw new WriteError("AlreadyExists", `file already exists (pass --force to overwrite): ${path}`);
@@ -243,22 +266,6 @@ function hashContent(content) {
243
266
  normalized = normalized.replace(/\r\n/g, "\n");
244
267
  return node_crypto.createHash("sha256").update(normalized, "utf8").digest("hex");
245
268
  }
246
- const SETTINGS_TEMPLATE_VERSION = "0.0.1";
247
- function settingsJsonTemplate(pkgVersion) {
248
- const value = {
249
- $bassclef: {
250
- template: "settings.json",
251
- template_version: SETTINGS_TEMPLATE_VERSION,
252
- generated_by: "@thebassclef/core",
253
- generated_by_version: pkgVersion
254
- },
255
- permissions: {
256
- additionalDirectories: []
257
- },
258
- hooks: {}
259
- };
260
- return JSON.stringify(value, null, 2) + "\n";
261
- }
262
269
  const SUBSTRATE_CONFIG_TEMPLATE_VERSION = "0.0.1";
263
270
  function substrateConfigMdTemplate(pkgVersion) {
264
271
  return [
@@ -306,8 +313,10 @@ function substrateConfigMdTemplate(pkgVersion) {
306
313
  ].join("\n");
307
314
  }
308
315
  const MANIFEST_SCHEMA_VERSION = "0.1.0";
316
+ const MANIFEST_SHAPE_VERSION = 2;
309
317
  function manifestTemplate(input) {
310
318
  const value = {
319
+ schema_version: MANIFEST_SHAPE_VERSION,
311
320
  $bassclef: {
312
321
  template: "init.manifest.json",
313
322
  manifest_schema_version: MANIFEST_SCHEMA_VERSION,
@@ -329,6 +338,15 @@ class ManifestReadError extends Error {
329
338
  this.kind = kind;
330
339
  }
331
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
+ }
332
350
  function readManifest(targetDir) {
333
351
  const path = node_path.join(targetDir, MANIFEST_RELATIVE_PATH);
334
352
  let raw;
@@ -409,129 +427,347 @@ async function computeConfigHashes(targetDir, paths) {
409
427
  }
410
428
  return out;
411
429
  }
412
- const SUBSTRATE_ROOT = "substrate";
430
+ class CopyFailure extends Error {
431
+ constructor(kind, message) {
432
+ super(message);
433
+ this.kind = kind;
434
+ this.name = "CopyFailure";
435
+ }
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";
413
523
  const CONFIG_FILES = [
414
524
  ".claude/settings.json",
415
525
  "substrate.config.md",
416
526
  "substrate.secrets.md"
417
527
  ];
418
528
  const CURRENT_ENTRY_COUNT = 149;
529
+ const EXPECTED_WIRING_SCHEMA_MAJOR = 2;
530
+ const WIRING_MANIFEST_RELATIVE = "standards/bassclef-wiring-manifest.json";
419
531
  function copySubstrate(targetDir, options = {}) {
420
532
  const bundleRoot = resolveBundleRoot(options.bundleRoot);
421
- const manifest = loadBundledManifest(bundleRoot);
533
+ const manifest = readWiringManifest(bundleRoot);
422
534
  const result = {
423
535
  copied: [],
536
+ copiedEntries: [],
424
537
  refused: [],
425
538
  errored: [],
426
- erroredMessages: []
539
+ erroredMessages: [],
540
+ hookCount: 0,
541
+ wiringVersion: manifest.version
427
542
  };
428
543
  if (options.dryRun) result.wouldCopy = [];
429
- const groups = groupByTopDirectory(manifest.entries);
430
- for (const [directory, entries] of groups) {
544
+ const files = walkDistTree(bundleRoot);
545
+ const groups = groupByTopDirectory(files);
546
+ const bundleHookRelPaths = new Set(files.filter(isHookFile));
547
+ const scopeMap = buildScopeMap(bundleRoot, targetDir, options, bundleHookRelPaths);
548
+ for (const [directory, groupFiles] of groups) {
431
549
  let completedInGroup = 0;
432
- for (const entry of entries) {
433
- const outcome = copyOne(entry, bundleRoot, targetDir, options, result);
550
+ for (const relPath of groupFiles) {
551
+ const outcome = copyOne(relPath, bundleRoot, targetDir, options, result, scopeMap);
434
552
  if (outcome !== "skipped") completedInGroup += 1;
435
553
  }
436
554
  if (options.onProgress) options.onProgress(directory, completedInGroup);
437
555
  }
556
+ result.hookCount = readHookCount(
557
+ options.dryRun ? node_path.join(bundleRoot, ".claude/settings.json") : node_path.join(targetDir, ".claude/settings.json")
558
+ );
438
559
  return result;
439
560
  }
440
- function copyOne(entry, bundleRoot, targetDir, options, result) {
441
- const sourcePath = node_path.join(bundleRoot, entry.path);
442
- const targetPath = node_path.join(targetDir, entry.path);
561
+ function readWiringManifest(bundleRoot) {
562
+ const manifestPath = node_path.join(bundleRoot, WIRING_MANIFEST_RELATIVE);
563
+ if (!fileExists(manifestPath)) {
564
+ throw new CopyFailure(
565
+ "ManifestMissing",
566
+ `Wiring manifest missing at ${manifestPath}. Cli built for schema_version ${EXPECTED_WIRING_SCHEMA_MAJOR}.x. Run \`npm install @thebassclef/lite@latest\` to fix.`
567
+ );
568
+ }
569
+ let manifest;
570
+ let raw;
571
+ try {
572
+ raw = node_fs.readFileSync(manifestPath, "utf8");
573
+ } catch (e) {
574
+ const err = e;
575
+ throw new CopyFailure(
576
+ "ManifestMissing",
577
+ `Wiring manifest at ${manifestPath} unreadable (${err.code ?? "unknown"}). Reinstall @thebassclef/lite to restore the bundle.`
578
+ );
579
+ }
580
+ try {
581
+ manifest = JSON.parse(raw);
582
+ } catch (e) {
583
+ const err = e;
584
+ throw new CopyFailure(
585
+ "SchemaIncompatible",
586
+ `Wiring manifest at ${manifestPath} is not valid JSON: ${err.message}. Reinstall @thebassclef/lite to restore the bundle.`
587
+ );
588
+ }
589
+ if (typeof manifest.version !== "string" || manifest.version.length === 0) {
590
+ throw new CopyFailure(
591
+ "SchemaIncompatible",
592
+ `Wiring manifest at ${manifestPath} missing 'version' field. Cli requires schema_version ${EXPECTED_WIRING_SCHEMA_MAJOR}.x. Reinstall @thebassclef/lite to fix.`
593
+ );
594
+ }
595
+ const major = parseInt(manifest.version.split(".")[0] ?? "0", 10);
596
+ if (major !== EXPECTED_WIRING_SCHEMA_MAJOR) {
597
+ throw new CopyFailure(
598
+ "SchemaIncompatible",
599
+ `Wiring manifest at ${manifestPath} has version ${manifest.version}. Cli built for schema_version ${EXPECTED_WIRING_SCHEMA_MAJOR}.x. Upgrade cli OR downgrade @thebassclef/lite to a compatible version.`
600
+ );
601
+ }
602
+ return { version: manifest.version };
603
+ }
604
+ function walkDistTree(bundleRoot) {
605
+ if (!dirExists(bundleRoot)) {
606
+ throw new CopyFailure(
607
+ "BundleMissing",
608
+ `Bundled substrate missing at ${bundleRoot}. Reinstall @thebassclef/lite to restore the bundle.`
609
+ );
610
+ }
611
+ const results = [];
612
+ function walk(dir) {
613
+ for (const name of node_fs.readdirSync(dir)) {
614
+ const full = node_path.join(dir, name);
615
+ const st = node_fs.statSync(full);
616
+ if (st.isDirectory()) walk(full);
617
+ else results.push(node_path.relative(bundleRoot, full));
618
+ }
619
+ }
620
+ walk(bundleRoot);
621
+ results.sort();
622
+ return results;
623
+ }
624
+ const GITIGNORE_BUNDLE_NAME = "gitignore";
625
+ const GITIGNORE_ADOPTER_NAME = ".gitignore";
626
+ function mapAdopterPath(relPath) {
627
+ if (relPath === GITIGNORE_BUNDLE_NAME) return GITIGNORE_ADOPTER_NAME;
628
+ return relPath;
629
+ }
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) {
660
+ const sourcePath = node_path.join(bundleRoot, relPath);
661
+ const adopterRelPath = mapAdopterPath(relPath);
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";
443
665
  let content;
444
666
  try {
445
667
  content = node_fs.readFileSync(sourcePath, "utf8");
446
668
  } catch (e) {
447
669
  const err = e;
448
- const message = `${entry.path} — cannot read bundled source (${err.code ?? "unknown"}). Reinstall @thebassclef/core to restore the bundle.`;
449
- result.errored.push(entry.path);
450
- result.erroredMessages?.push(message);
451
- return "errored";
452
- }
453
- const declaredHash = stripHashPrefix(entry.content_hash);
454
- const actualHash = sha256Hex(content);
455
- if (declaredHash !== actualHash) {
456
- const message = `${entry.path} — bundled content hash does not match the manifest. Reinstall @thebassclef/core to repair the bundle, then rerun bassclef init --force.`;
457
- result.errored.push(entry.path);
670
+ const message = `${relPath} — cannot read bundled source (${err.code ?? "unknown"}). Reinstall @thebassclef/lite to restore the bundle.`;
671
+ result.errored.push(adopterRelPath);
458
672
  result.erroredMessages?.push(message);
459
673
  return "errored";
460
674
  }
675
+ const outputContent = options.transform ? options.transform(adopterRelPath, content) : content;
461
676
  if (options.dryRun) {
462
- result.wouldCopy?.push(entry.path);
677
+ result.wouldCopy?.push(adopterRelPath);
463
678
  return "wouldCopy";
464
679
  }
465
680
  try {
466
681
  mkdirSafely(node_path.dirname(targetPath));
467
- writeSafely(targetPath, content, { force: options.force ?? false });
468
- result.copied.push(entry.path);
682
+ writeSafely(targetPath, outputContent, { force: options.force ?? false });
683
+ if (isHookFile(relPath)) {
684
+ setExecutable(targetPath);
685
+ }
686
+ result.copied.push(adopterRelPath);
687
+ result.copiedEntries.push({ path: adopterRelPath, scope });
469
688
  return "copied";
470
689
  } catch (e) {
471
690
  if (e instanceof WriteError) {
472
691
  if (e.kind === "AlreadyExists") {
473
- result.refused.push(entry.path);
692
+ result.refused.push(adopterRelPath);
474
693
  return "refused";
475
694
  }
476
695
  if (e.kind === "SymlinkRefused") {
477
- result.refused.push(entry.path);
696
+ result.refused.push(adopterRelPath);
697
+ process.stderr.write(`bassclef init: ${e.message}
698
+ `);
478
699
  return "refused";
479
700
  }
480
- const message = `${entry.path} — write failed (${e.kind}): ${e.message}. Check the target directory exists and is writable, then rerun.`;
481
- result.errored.push(entry.path);
701
+ const message = `${adopterRelPath} — write failed (${e.kind}): ${e.message}. Check the target directory exists and is writable, then rerun.`;
702
+ result.errored.push(adopterRelPath);
482
703
  result.erroredMessages?.push(message);
483
704
  return "errored";
484
705
  }
485
706
  throw e;
486
707
  }
487
708
  }
488
- function loadBundledManifest(bundleRoot) {
489
- const manifestPath = node_path.join(bundleRoot, ".bassclef", "lite-manifest.json");
490
- const raw = node_fs.readFileSync(manifestPath, "utf8");
491
- const parsed = JSON.parse(raw);
492
- if (!Array.isArray(parsed.entries)) {
493
- throw new Error(
494
- `bundled manifest at ${manifestPath} has no entries[] array. Reinstall @thebassclef/core to restore the bundle.`
495
- );
496
- }
497
- return parsed;
498
- }
499
709
  function resolveBundleRoot(explicit) {
500
710
  if (explicit && explicit.length > 0) return node_path.resolve(explicit);
501
711
  const thisFile = node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("cli.cjs", document.baseURI).href);
502
712
  let dir = node_path.dirname(thisFile);
503
713
  for (let i = 0; i < 5; i += 1) {
504
- try {
505
- node_fs.readFileSync(node_path.join(dir, "package.json"), "utf8");
506
- return node_path.join(dir, SUBSTRATE_ROOT);
507
- } catch {
508
- const parent = node_path.dirname(dir);
509
- if (parent === dir) break;
510
- dir = parent;
714
+ if (fileExists(node_path.join(dir, "package.json"))) {
715
+ return node_path.join(dir, "dist", "lite");
511
716
  }
717
+ const parent = node_path.dirname(dir);
718
+ if (parent === dir) break;
719
+ dir = parent;
512
720
  }
513
- const require$1 = node_module.createRequire(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("cli.cjs", document.baseURI).href);
514
- const pkgJsonPath = require$1.resolve("@thebassclef/core/package.json");
515
- const pkgRoot = node_path.dirname(pkgJsonPath);
516
- return node_path.join(pkgRoot, SUBSTRATE_ROOT);
721
+ throw new CopyFailure(
722
+ "BundleMissing",
723
+ `Cannot resolve bundled substrate root — package.json unreachable from ${thisFile}. Reinstall @thebassclef/lite.`
724
+ );
517
725
  }
518
- function groupByTopDirectory(entries) {
726
+ function groupByTopDirectory(files) {
519
727
  const groups = /* @__PURE__ */ new Map();
520
- for (const entry of entries) {
521
- const parts = entry.path.split("/");
728
+ for (const p of files) {
729
+ const parts = p.split("/");
522
730
  const top = parts.length >= 2 ? parts.slice(0, 2).join("/") : parts[0] ?? "";
523
731
  if (!groups.has(top)) groups.set(top, []);
524
- groups.get(top).push(entry);
732
+ groups.get(top).push(p);
525
733
  }
526
734
  return groups;
527
735
  }
528
- function stripHashPrefix(value) {
529
- const prefix = "sha256:";
530
- return value.startsWith(prefix) ? value.slice(prefix.length) : value;
736
+ function readHookCount(settingsPath) {
737
+ try {
738
+ const parsed = JSON.parse(node_fs.readFileSync(settingsPath, "utf8"));
739
+ let count = 0;
740
+ for (const matcherBlocks of Object.values(parsed.hooks ?? {})) {
741
+ for (const block of matcherBlocks) {
742
+ count += (block.hooks ?? []).length;
743
+ }
744
+ }
745
+ return count;
746
+ } catch {
747
+ return 0;
748
+ }
749
+ }
750
+ function fileExists(p) {
751
+ try {
752
+ return node_fs.statSync(p).isFile();
753
+ } catch {
754
+ return false;
755
+ }
531
756
  }
532
- function sha256Hex(content) {
533
- return node_crypto.createHash("sha256").update(content).digest("hex");
757
+ function dirExists(p) {
758
+ try {
759
+ return node_fs.statSync(p).isDirectory();
760
+ } catch {
761
+ return false;
762
+ }
534
763
  }
764
+ const RESOLVED_TIER = "lite";
765
+ const PLACEHOLDER_FILES = /* @__PURE__ */ new Set([
766
+ "CLAUDE.md",
767
+ "docs/whereami.md",
768
+ "whereami.md",
769
+ ".bassclef-source.json"
770
+ ]);
535
771
  function runInit(argv) {
536
772
  let args;
537
773
  try {
@@ -567,7 +803,10 @@ function runInit(argv) {
567
803
  }
568
804
  throw e;
569
805
  }
570
- if (!args.force && !args.dryRun) {
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) {
571
810
  const manifestPath = node_path.join(targetDir, MANIFEST_RELATIVE_PATH);
572
811
  if (node_fs.existsSync(manifestPath)) {
573
812
  process.stderr.write(
@@ -577,14 +816,6 @@ function runInit(argv) {
577
816
  }
578
817
  }
579
818
  const plans = [
580
- {
581
- label: "settings.json",
582
- relativePath: ".claude/settings.json",
583
- fullPath: node_path.join(targetDir, ".claude", "settings.json"),
584
- content: settingsJsonTemplate(index.version),
585
- templateName: "settings.json",
586
- templateVersion: SETTINGS_TEMPLATE_VERSION
587
- },
588
819
  {
589
820
  label: "substrate.config.md",
590
821
  relativePath: "substrate.config.md",
@@ -596,30 +827,54 @@ function runInit(argv) {
596
827
  ];
597
828
  if (args.dryRun) {
598
829
  runDryRun$1(plans);
599
- dispatchSubstrateCopy(targetDir, args.force, args.verbose, true);
600
- return 0;
830
+ return dispatchSubstrateCopy(targetDir, args.force || upgradeApproved, args.verbose, true, args.allowRoot, args.json);
601
831
  }
602
- const exitCode = runReal$1(plans, args.force, args.verbose, targetDir);
603
- if (exitCode === 0) {
604
- dispatchSubstrateCopy(targetDir, args.force, args.verbose, false);
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 "";
605
857
  }
606
- return exitCode;
607
858
  }
608
- function dispatchSubstrateCopy(targetDir, force, verbose, dryRun) {
859
+ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, json) {
860
+ const substitute = makePlaceholderTransform(targetDir);
609
861
  let result;
610
862
  try {
611
- result = copySubstrate(targetDir, { force, dryRun });
863
+ result = copySubstrate(targetDir, {
864
+ force,
865
+ dryRun,
866
+ transform: substitute,
867
+ allowRoot
868
+ });
612
869
  } catch (e) {
613
- const err = e;
614
- process.stderr.write(
615
- `bassclef init: substrate copy failed — ${err.message}
616
- cause: the installed @thebassclef/core package is missing the bundled substrate manifest.
617
- fix: reinstall with \`npm install -g @thebassclef/core@latest --force\`.
618
- If the reinstall does not help, file an issue at
619
- https://github.com/sunj-labs/bassclef-cli/issues with your package version.
620
- `
621
- );
622
- process.exit(2);
870
+ if (e instanceof CopyFailure) {
871
+ process.stderr.write(`bassclef init: ${e.message}
872
+ `);
873
+ if (e.kind === "ManifestMissing") return 4;
874
+ if (e.kind === "SchemaIncompatible") return 5;
875
+ return 2;
876
+ }
877
+ throw e;
623
878
  }
624
879
  if (dryRun) {
625
880
  const wouldCopy = result.wouldCopy ?? [];
@@ -634,10 +889,14 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun) {
634
889
  `
635
890
  );
636
891
  }
637
- return;
892
+ process.stdout.write(
893
+ `bassclef init: ${result.hookCount} hooks armed (${RESOLVED_TIER} tier).
894
+ `
895
+ );
896
+ return 0;
638
897
  }
639
898
  if (result.copied.length === 0 && result.refused.length === 0 && result.errored.length === 0) {
640
- return;
899
+ return 0;
641
900
  }
642
901
  const groupCounts = /* @__PURE__ */ new Map();
643
902
  for (const path of result.copied) {
@@ -655,17 +914,56 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun) {
655
914
  if (result.errored.length > 0) parts.push(`${result.errored.length} error(s)`);
656
915
  process.stdout.write(`bassclef init: ${parts.join(", ")}.
657
916
  `);
658
- const grandTotal = 2 + result.copied.length;
917
+ const grandTotal = 1 + result.copied.length;
659
918
  process.stdout.write(
660
- `bassclef init: ${grandTotal} files total (2 config + ${result.copied.length} substrate).
919
+ `bassclef init: ${grandTotal} files total (1 config + ${result.copied.length} substrate).
661
920
  `
662
921
  );
922
+ const copiedHookEntries = result.copiedEntries.filter(
923
+ (e) => e.path.startsWith(HOOKS_SUBPATH) && e.path.endsWith(".sh")
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
+ }
663
952
  if (verbose && result.erroredMessages) {
664
953
  for (const msg of result.erroredMessages) {
665
954
  process.stderr.write(` substrate: ${msg}
666
955
  `);
667
956
  }
668
957
  }
958
+ return result.errored.length > 0 ? 2 : 0;
959
+ }
960
+ function makePlaceholderTransform(targetDir) {
961
+ const repoName = node_path.basename(targetDir);
962
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
963
+ return (relPath, content) => {
964
+ if (!PLACEHOLDER_FILES.has(relPath)) return content;
965
+ 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);
966
+ };
669
967
  }
670
968
  function runDryRun$1(plans) {
671
969
  for (const p of plans) {
@@ -692,7 +990,7 @@ function runDryRun$1(plans) {
692
990
  }
693
991
  return 0;
694
992
  }
695
- function runReal$1(plans, force, verbose, targetDir) {
993
+ function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
696
994
  const results = [];
697
995
  let anyRefused = false;
698
996
  let anyError = false;
@@ -749,8 +1047,8 @@ function runReal$1(plans, force, verbose, targetDir) {
749
1047
  process.stderr.write(`bassclef init: ${tag}: ${r.message ?? "write failed"}: ${r.plan.fullPath}
750
1048
  `);
751
1049
  }
752
- writeManifest(targetDir, results);
753
1050
  if (anyError) {
1051
+ writeManifest(targetDir, results);
754
1052
  const parts = [];
755
1053
  if (created > 0) parts.push(`${created} created`);
756
1054
  if (unchanged > 0) parts.push(`${unchanged} unchanged`);
@@ -761,28 +1059,26 @@ function runReal$1(plans, force, verbose, targetDir) {
761
1059
  `);
762
1060
  return 2;
763
1061
  }
764
- if (created === 0 && unchanged === plans.length) {
765
- process.stdout.write("bassclef init: already initialized. No changes.\n");
766
- return 0;
767
- }
768
1062
  if (anyRefused && created > 0) {
769
1063
  process.stdout.write(
770
1064
  `bassclef init: ${created} config files created, ${unchanged} unchanged. Pass --force to overwrite.
771
1065
  `
772
1066
  );
1067
+ } else if (created === 0 && unchanged === plans.length) {
1068
+ process.stdout.write("bassclef init: already initialized. No changes.\n");
1069
+ } else {
1070
+ process.stdout.write(`bassclef init: ${created} config files created, ${unchanged} unchanged.
1071
+ `);
1072
+ }
1073
+ const walkerExit = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, json);
1074
+ writeManifest(targetDir, results);
1075
+ if (walkerExit === 0) {
773
1076
  process.stdout.write(
774
1077
  `bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
775
1078
  `
776
1079
  );
777
- return 0;
778
1080
  }
779
- process.stdout.write(`bassclef init: ${created} config files created, ${unchanged} unchanged.
780
- `);
781
- process.stdout.write(
782
- `bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
783
- `
784
- );
785
- return 0;
1081
+ return walkerExit;
786
1082
  }
787
1083
  function shouldRefuseRoot(currentUid, allowRoot) {
788
1084
  if (currentUid === void 0) return false;
@@ -830,17 +1126,22 @@ function usage$2() {
830
1126
  " --allow-any-dir Allow --dir outside your home directory. Default: refuse.",
831
1127
  " --verbose Print per-file result.",
832
1128
  "",
1129
+ "Exit codes:",
1130
+ " 0 — success",
1131
+ " 1 — refused by policy (existing file; running as root; outside HOME)",
1132
+ " 2 — safety check failed at write (symlink; parent not writable)",
1133
+ " 3 — invalid args",
1134
+ " 4 — wiring manifest missing from bundled substrate (reinstall @thebassclef/lite)",
1135
+ " 5 — wiring manifest schema major version incompatible with this cli",
1136
+ "",
833
1137
  "Files written under <target>:",
834
- " .claude/settings.json Claude Code settings (minimal, opt-in blocks)",
835
1138
  " substrate.config.md Bassclef project manifest",
836
1139
  " .bassclef/init.manifest.json Record of what init wrote (used by sync)",
837
1140
  "",
838
- " Plus the bundled substrate tree (~280 files):",
839
- " .claude/{agents,hooks,luminaries,rules,skills}/",
840
- " standards/, templates/, scripts/, lib/, presence/install/",
841
- " architecture/decisions/",
842
- " AGENTS.md, CLAUDE-lite.md, README.md, CONTRIBUTING.md,",
843
- " SECURITY.md, CODE_OF_CONDUCT.md",
1141
+ " Plus the bundled substrate tree from dist/lite/:",
1142
+ " .claude/settings.json Claude Code settings (verbatim from bundle)",
1143
+ " CLAUDE.md, whereami.md, .bassclef-source.json, .gitignore",
1144
+ " Templates with placeholders substituted",
844
1145
  "",
845
1146
  " Run with --dry-run first to preview the full file list before",
846
1147
  " writing anything to disk.",
@@ -922,12 +1223,6 @@ function parseSyncArgs(argv) {
922
1223
  return out;
923
1224
  }
924
1225
  const TEMPLATES = [
925
- {
926
- templateName: "settings.json",
927
- currentVersion: SETTINGS_TEMPLATE_VERSION,
928
- render: settingsJsonTemplate,
929
- hasMarker: (c) => c.includes('"$bassclef"')
930
- },
931
1226
  {
932
1227
  templateName: "substrate.config.md",
933
1228
  currentVersion: SUBSTRATE_CONFIG_TEMPLATE_VERSION,
@@ -1428,6 +1723,7 @@ Proceed?`,
1428
1723
  );
1429
1724
  const errored = copyResult.errored;
1430
1725
  const newManifest = {
1726
+ schema_version: MANIFEST_SHAPE_VERSION,
1431
1727
  $bassclef: {
1432
1728
  template: "init.manifest.json",
1433
1729
  manifest_schema_version: "0.1.0",