@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.
- package/dist/cli.cjs +431 -135
- package/dist/cli.js +433 -137
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lite/.bassclef-source.json +10 -0
- package/{substrate → dist/lite}/.claude/hooks/artifact-ingestion-gate.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/assert-verify-steering.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/bassclef-source-config-validate.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/bassclef-sync.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/compound-noun-scrub.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/kiss-expansion-inject.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/longrun-prep-compounding-sequence-check.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/plain-english-steering.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/post-skill-friction-check.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/post-skill-telemetry.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/pre-build-gate.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/pre-commit-gate.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/session-end.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/session-reflection.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/skill-body-grade-gate.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/skill-body-intent-drift.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/state-validate.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/substrate-clarity-gate.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/temperance-gate.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/testing-tier-enforce.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/turn-prose-grade-measure.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/turn-prose-kiss-check.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/vocabulary-migration-check.sh +0 -0
- package/{substrate → dist/lite}/.claude/hooks/whereami-utc-gate.sh +0 -0
- package/dist/lite/.claude/settings.json +212 -0
- package/dist/lite/CLAUDE.md +41 -0
- package/dist/lite/gitignore +58 -0
- package/dist/lite/standards/bassclef-wiring-manifest.json +497 -0
- package/dist/lite/whereami.md +24 -0
- package/package.json +8 -2
- package/substrate/.bassclef/lite-manifest.json +0 -2701
- package/substrate/.claude/agents/architect.md +0 -70
- package/substrate/.claude/agents/builder.md +0 -114
- package/substrate/.claude/agents/designer.md +0 -156
- package/substrate/.claude/agents/reviewer.md +0 -88
- package/substrate/.claude/hooks/longrun-prep-compounding-axis-check.sh +0 -492
- package/substrate/.claude/hooks/skill-step-list-check.sh +0 -171
- package/substrate/.claude/luminaries/alan-cooper.md +0 -170
- package/substrate/.claude/luminaries/alistair-cockburn.md +0 -140
- package/substrate/.claude/luminaries/amazon-pr-faq.md +0 -34
- package/substrate/.claude/luminaries/ash-maurya.md +0 -121
- package/substrate/.claude/luminaries/bill-buxton.md +0 -210
- package/substrate/.claude/luminaries/charles-sanders-peirce.md +0 -150
- package/substrate/.claude/luminaries/david-ogilvy.md +0 -192
- package/substrate/.claude/luminaries/don-norman.md +0 -173
- package/substrate/.claude/luminaries/edward-tufte.md +0 -179
- package/substrate/.claude/luminaries/eric-evans.md +0 -160
- package/substrate/.claude/luminaries/frederick-brooks.md +0 -50
- package/substrate/.claude/luminaries/gang-of-four.md +0 -157
- package/substrate/.claude/luminaries/glenford-myers.md +0 -230
- package/substrate/.claude/luminaries/hunt-thomas.md +0 -115
- package/substrate/.claude/luminaries/hyrum-wright.md +0 -94
- package/substrate/.claude/luminaries/jason-fried-dhh.md +0 -46
- package/substrate/.claude/luminaries/jesse-james-garrett.md +0 -154
- package/substrate/.claude/luminaries/john-ousterhout.md +0 -94
- package/substrate/.claude/luminaries/karl-popper.md +0 -132
- package/substrate/.claude/luminaries/kent-beck.md +0 -168
- package/substrate/.claude/luminaries/linus-torvalds.md +0 -218
- package/substrate/.claude/luminaries/martin-fowler.md +0 -164
- package/substrate/.claude/luminaries/michael-feathers.md +0 -125
- package/substrate/.claude/luminaries/michael-nygard.md +0 -118
- package/substrate/.claude/luminaries/robert-c-martin.md +0 -164
- package/substrate/.claude/luminaries/saltzer-schroeder.md +0 -194
- package/substrate/.claude/luminaries/sophia-prater.md +0 -193
- package/substrate/.claude/luminaries/stephen-toulmin.md +0 -131
- package/substrate/.claude/luminaries/tony-hoare.md +0 -170
- package/substrate/.claude/luminaries/vaughn-vernon.md +0 -50
- package/substrate/.claude/luminaries/w-edwards-deming.md +0 -158
- package/substrate/.claude/rules/accessor-library-discipline.md +0 -138
- package/substrate/.claude/rules/adr-discipline.md +0 -120
- package/substrate/.claude/rules/api-conventions.md +0 -125
- package/substrate/.claude/rules/artifact-ingestion.md +0 -179
- package/substrate/.claude/rules/assert-only-after-verify.md +0 -137
- package/substrate/.claude/rules/blocked-items.md +0 -146
- package/substrate/.claude/rules/bootstrap-pair-discipline.md +0 -141
- package/substrate/.claude/rules/branching.md +0 -28
- package/substrate/.claude/rules/cold-adopter-harness-discipline.md +0 -129
- package/substrate/.claude/rules/commit-conventions.md +0 -22
- package/substrate/.claude/rules/compounding-axis-fresh-analysis.md +0 -188
- package/substrate/.claude/rules/compounding-sequence-fresh-analysis.md +0 -188
- package/substrate/.claude/rules/context-engineering.md +0 -202
- package/substrate/.claude/rules/context-management.md +0 -85
- package/substrate/.claude/rules/defensive-bash.md +0 -68
- package/substrate/.claude/rules/deferred-actions.md +0 -233
- package/substrate/.claude/rules/destructive-operations.md +0 -69
- package/substrate/.claude/rules/diagnosis.md +0 -38
- package/substrate/.claude/rules/github-issue-flash-tweet.md +0 -156
- package/substrate/.claude/rules/guardrails.md +0 -73
- package/substrate/.claude/rules/hook-wire-on-author.md +0 -103
- package/substrate/.claude/rules/identifier-leak-prevention.md +0 -104
- package/substrate/.claude/rules/iteration-bet-brief-completeness.md +0 -54
- package/substrate/.claude/rules/lite-manifest-schema-change-discipline.md +0 -98
- package/substrate/.claude/rules/longrun-prep-plan-doc-compression.md +0 -89
- package/substrate/.claude/rules/loop-discipline.md +0 -81
- package/substrate/.claude/rules/manual-prod-approval.md +0 -100
- package/substrate/.claude/rules/marker-enrichment-discipline.md +0 -99
- package/substrate/.claude/rules/mobile-ephemeral-session.md +0 -109
- package/substrate/.claude/rules/new-dependency-check.md +0 -51
- package/substrate/.claude/rules/oo-ad-entry-point.md +0 -117
- package/substrate/.claude/rules/operator-facing-prose.md +0 -196
- package/substrate/.claude/rules/option-label-discipline.md +0 -108
- package/substrate/.claude/rules/pattern-annotation.md +0 -100
- package/substrate/.claude/rules/plain-english-discipline.md +0 -156
- package/substrate/.claude/rules/plan-enumeration-needs-value-props.md +0 -211
- package/substrate/.claude/rules/pr-body-shape.md +0 -317
- package/substrate/.claude/rules/pr-strategy.md +0 -167
- package/substrate/.claude/rules/pr-title-shape.md +0 -161
- package/substrate/.claude/rules/prototype-workflow.md +0 -65
- package/substrate/.claude/rules/reserved-skill-names.md +0 -123
- package/substrate/.claude/rules/schema-management.md +0 -49
- package/substrate/.claude/rules/sdlc-gates.md +0 -149
- package/substrate/.claude/rules/security.md +0 -37
- package/substrate/.claude/rules/session-artifacts.md +0 -236
- package/substrate/.claude/rules/skill-composition-declarations.md +0 -124
- package/substrate/.claude/rules/skill-description-clarity.md +0 -247
- package/substrate/.claude/rules/skill-procedure-step-list.md +0 -137
- package/substrate/.claude/rules/state-schema-validation.md +0 -162
- package/substrate/.claude/rules/stuck-signal-diagnostic.md +0 -140
- package/substrate/.claude/rules/substrate-config-schema.md +0 -98
- package/substrate/.claude/rules/test-list-discipline.md +0 -175
- package/substrate/.claude/rules/test-sufficiency.md +0 -210
- package/substrate/.claude/rules/testing-tier-config.md +0 -145
- package/substrate/.claude/rules/testing.md +0 -38
- package/substrate/.claude/rules/turn-estimate-grounding.md +0 -134
- package/substrate/.claude/rules/visual-hierarchy.md +0 -437
- package/substrate/.claude/rules/we-dont-break-adopters.md +0 -126
- package/substrate/.claude/rules/whereami-load-bearing.md +0 -202
- package/substrate/.claude/rules/writing-craft-discipline.md +0 -92
- package/substrate/.claude/rules/wu-sequencing-compounds.md +0 -145
- package/substrate/.claude/skills/build/SKILL.md +0 -640
- package/substrate/.claude/skills/chronicle/SKILL.md +0 -55
- package/substrate/.claude/skills/clean-artifacts/SKILL.md +0 -249
- package/substrate/.claude/skills/decompose/SKILL.md +0 -280
- package/substrate/.claude/skills/diagnose/SKILL.md +0 -297
- package/substrate/.claude/skills/feynman/SKILL.md +0 -90
- package/substrate/.claude/skills/howdoi/SKILL.md +0 -105
- package/substrate/.claude/skills/ia-model/SKILL.md +0 -108
- package/substrate/.claude/skills/interaction-design/SKILL.md +0 -112
- package/substrate/.claude/skills/interpret-input/SKILL.md +0 -180
- package/substrate/.claude/skills/journal/SKILL.md +0 -209
- package/substrate/.claude/skills/kiss/SKILL.md +0 -449
- package/substrate/.claude/skills/launch/SKILL.md +0 -915
- package/substrate/.claude/skills/lean-canvas/SKILL.md +0 -332
- package/substrate/.claude/skills/longrun/SKILL.md +0 -463
- package/substrate/.claude/skills/luminary/SKILL.md +0 -481
- package/substrate/.claude/skills/ogilvy-writing-audit/SKILL.md +0 -177
- package/substrate/.claude/skills/onboard-repo/SKILL.md +0 -1624
- package/substrate/.claude/skills/pattern-review/SKILL.md +0 -99
- package/substrate/.claude/skills/personas/SKILL.md +0 -207
- package/substrate/.claude/skills/promote/SKILL.md +0 -283
- package/substrate/.claude/skills/requirement/SKILL.md +0 -98
- package/substrate/.claude/skills/retro/SKILL.md +0 -117
- package/substrate/.claude/skills/riff/SKILL.md +0 -114
- package/substrate/.claude/skills/roadmap-reconcile/SKILL.md +0 -163
- package/substrate/.claude/skills/session-end/SKILL.md +0 -309
- package/substrate/.claude/skills/session-log/SKILL.md +0 -299
- package/substrate/.claude/skills/skills/SKILL.md +0 -228
- package/substrate/.claude/skills/spec/SKILL.md +0 -105
- package/substrate/.claude/skills/sprint/SKILL.md +0 -392
- package/substrate/.claude/skills/stage/SKILL.md +0 -384
- package/substrate/.claude/skills/state-a-problem/SKILL.md +0 -185
- package/substrate/.claude/skills/temperance/SKILL.md +0 -108
- package/substrate/.claude/skills/use-case/SKILL.md +0 -417
- package/substrate/.claude/skills/user-stories/SKILL.md +0 -268
- package/substrate/.claude/skills/value-prop/SKILL.md +0 -251
- package/substrate/.claude/skills/verify/SKILL.md +0 -160
- package/substrate/.claude/skills/visual-review/SKILL.md +0 -503
- package/substrate/.claude/skills/whats-the-plan/SKILL.md +0 -202
- package/substrate/.claude/skills/whereami/SKILL.md +0 -307
- package/substrate/AGENTS.md +0 -79
- package/substrate/CLAUDE-lite.md +0 -85
- package/substrate/CODE_OF_CONDUCT.md +0 -28
- package/substrate/CONTRIBUTING.md +0 -177
- package/substrate/README.md +0 -173
- package/substrate/SECURITY.md +0 -19
- package/substrate/architecture/decisions/ADR-029-release-pipeline.md +0 -79
- package/substrate/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +0 -139
- package/substrate/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +0 -192
- package/substrate/architecture/decisions/ADR-039-release-tagging-scheme.md +0 -145
- package/substrate/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +0 -155
- package/substrate/architecture/decisions/ADR-044-unified-skill-body-template.md +0 -162
- package/substrate/lib/clean-artifacts-sweep.sh +0 -112
- package/substrate/lib/code-comment-discipline.sh +0 -144
- package/substrate/lib/composer-preflight.sh +0 -459
- package/substrate/lib/hook-inject.sh +0 -255
- package/substrate/lib/luminary-pick.sh +0 -96
- package/substrate/lib/output-discipline.sh +0 -143
- package/substrate/lib/prose-scan-boundary.sh +0 -171
- package/substrate/lib/rewrite-check.sh +0 -214
- package/substrate/lib/state.sh +0 -1372
- package/substrate/lib/telemetry.sh +0 -205
- package/substrate/lib/tier-check.sh +0 -187
- package/substrate/lib/tier-dependency-audit.sh +0 -1088
- package/substrate/presence/install/bassclef-hook-connect.sh +0 -178
- package/substrate/presence/install/bassclef-sync.dispatcher.template.sh +0 -841
- package/substrate/presence/install/bassclef-sync.template.sh +0 -2076
- package/substrate/presence/install/schedule-auto-save.cron.sh +0 -88
- package/substrate/presence/install/schedule-auto-save.taskscheduler.md +0 -122
- package/substrate/scripts/aggregate-telemetry.sh +0 -217
- package/substrate/scripts/analyze-tier-dependencies.sh +0 -239
- package/substrate/scripts/generate-lite-manifest.sh +0 -505
- package/substrate/scripts/generate-tier-manifest.sh +0 -28
- package/substrate/scripts/intent-drift-check.sh +0 -456
- package/substrate/scripts/lite-manifest-drift-check.sh +0 -146
- package/substrate/scripts/render-lite-manifest-doc.sh +0 -150
- package/substrate/standards/adr-template.md +0 -86
- package/substrate/standards/api-conventions/nextjs.md +0 -84
- package/substrate/standards/artifact-composition.md +0 -209
- package/substrate/standards/bash-hook-safety.md +0 -246
- package/substrate/standards/bassclef-configs-schema.md +0 -232
- package/substrate/standards/bassclef-evolution.md +0 -143
- package/substrate/standards/bassclef-internal-jargon.md +0 -244
- package/substrate/standards/bassclef-managed-sentinel.md +0 -96
- package/substrate/standards/bassclef-source-config.md +0 -228
- package/substrate/standards/branch-stacking.md +0 -408
- package/substrate/standards/code-safety-principles.md +0 -176
- package/substrate/standards/composer-prerequisites.md +0 -155
- package/substrate/standards/deferred-actions-schema.md +0 -204
- package/substrate/standards/dependency-discipline/cargo.md +0 -39
- package/substrate/standards/dependency-discipline/gem.md +0 -43
- package/substrate/standards/dependency-discipline/go-mod.md +0 -41
- package/substrate/standards/dependency-discipline/npm.md +0 -42
- package/substrate/standards/dependency-discipline/pip.md +0 -42
- package/substrate/standards/deployment-topology/ec2-tailscale.md +0 -225
- package/substrate/standards/deployment-topology.md +0 -69
- package/substrate/standards/docs-sync-allowlist.md +0 -76
- package/substrate/standards/domain-and-dns.md +0 -145
- package/substrate/standards/frontend-stack.md +0 -67
- package/substrate/standards/frontmatter-schema.md +0 -154
- package/substrate/standards/graceful-exit.md +0 -227
- package/substrate/standards/hook-idempotency.md +0 -102
- package/substrate/standards/hook-injection-discipline.md +0 -202
- package/substrate/standards/hook-install-class.md +0 -215
- package/substrate/standards/input-handler-interface.md +0 -152
- package/substrate/standards/lite-manifest-schema-changes.md +0 -135
- package/substrate/standards/luminary-matching.md +0 -105
- package/substrate/standards/luminary-problem-patterns.md +0 -481
- package/substrate/standards/migration-discipline/active-record.md +0 -50
- package/substrate/standards/migration-discipline/alembic.md +0 -43
- package/substrate/standards/migration-discipline/gorm.md +0 -50
- package/substrate/standards/migration-discipline/prisma.md +0 -53
- package/substrate/standards/migration-discipline/sqlalchemy.md +0 -51
- package/substrate/standards/mobile-ephemeral-session.md +0 -167
- package/substrate/standards/model-routing-discipline.md +0 -160
- package/substrate/standards/ogilvy-writing-rules.md +0 -225
- package/substrate/standards/opener-discipline.md +0 -96
- package/substrate/standards/operator-facing-prose-discipline.md +0 -201
- package/substrate/standards/persona-schema.md +0 -229
- package/substrate/standards/pluggable-luminaries.md +0 -323
- package/substrate/standards/pr-body-discipline.md +0 -115
- package/substrate/standards/preview-state-schema.md +0 -189
- package/substrate/standards/project-directory-layout.md +0 -276
- package/substrate/standards/release-tagging.md +0 -137
- package/substrate/standards/reserved-skill-names.md +0 -120
- package/substrate/standards/scannable-multi-option-output.md +0 -261
- package/substrate/standards/sdlc-compliance.md +0 -286
- package/substrate/standards/sdlc-gates/typescript.md +0 -57
- package/substrate/standards/secrets-lifecycle.md +0 -210
- package/substrate/standards/security-scanner-adapter.md +0 -145
- package/substrate/standards/session-board.md +0 -256
- package/substrate/standards/skill-output-discipline.md +0 -90
- package/substrate/standards/state-spine-contract.md +0 -255
- package/substrate/standards/state-spine.md +0 -511
- package/substrate/standards/steering-hints/kiss-words.md +0 -11
- package/substrate/standards/substrate-config-schema.md +0 -267
- package/substrate/standards/tech-stack-config.md +0 -109
- package/substrate/standards/tier-dependency-analysis.md +0 -167
- package/substrate/standards/tier-runtime-deps/lite.md +0 -57
- package/substrate/standards/tier-tag-schema.md +0 -155
- package/substrate/standards/two-layer-config.md +0 -99
- package/substrate/standards/use-case-format.md +0 -292
- package/substrate/standards/user-story-invest.md +0 -268
- package/substrate/standards/velocity-and-appetite.md +0 -229
- package/substrate/standards/voice-input-pattern.md +0 -119
- package/substrate/standards/whereami-schema.md +0 -301
- package/substrate/standards/worktree-management.md +0 -211
- package/substrate/standards/writing-guide.md +0 -213
- package/substrate/templates/chronicle-template.md +0 -75
- package/substrate/templates/deferred-action-template.md +0 -45
- package/substrate/templates/memory-proposal-template.md +0 -77
- package/substrate/templates/persona-template.md +0 -200
- package/substrate/templates/pr-faq.md +0 -45
- package/substrate/templates/secret-rotation-template.md +0 -162
- package/substrate/templates/spec-template.md +0 -131
- package/substrate/templates/use-case-template.md +0 -194
- package/substrate/templates/user-story-template.md +0 -107
- 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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
430
|
-
|
|
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
|
|
433
|
-
const outcome = copyOne(
|
|
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
|
|
441
|
-
const
|
|
442
|
-
|
|
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 = `${
|
|
449
|
-
result.errored.push(
|
|
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(
|
|
677
|
+
result.wouldCopy?.push(adopterRelPath);
|
|
463
678
|
return "wouldCopy";
|
|
464
679
|
}
|
|
465
680
|
try {
|
|
466
681
|
mkdirSafely(node_path.dirname(targetPath));
|
|
467
|
-
writeSafely(targetPath,
|
|
468
|
-
|
|
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(
|
|
692
|
+
result.refused.push(adopterRelPath);
|
|
474
693
|
return "refused";
|
|
475
694
|
}
|
|
476
695
|
if (e.kind === "SymlinkRefused") {
|
|
477
|
-
result.refused.push(
|
|
696
|
+
result.refused.push(adopterRelPath);
|
|
697
|
+
process.stderr.write(`bassclef init: ${e.message}
|
|
698
|
+
`);
|
|
478
699
|
return "refused";
|
|
479
700
|
}
|
|
480
|
-
const message = `${
|
|
481
|
-
result.errored.push(
|
|
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
|
-
|
|
505
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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(
|
|
726
|
+
function groupByTopDirectory(files) {
|
|
519
727
|
const groups = /* @__PURE__ */ new Map();
|
|
520
|
-
for (const
|
|
521
|
-
const parts =
|
|
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(
|
|
732
|
+
groups.get(top).push(p);
|
|
525
733
|
}
|
|
526
734
|
return groups;
|
|
527
735
|
}
|
|
528
|
-
function
|
|
529
|
-
|
|
530
|
-
|
|
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
|
|
533
|
-
|
|
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
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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, {
|
|
863
|
+
result = copySubstrate(targetDir, {
|
|
864
|
+
force,
|
|
865
|
+
dryRun,
|
|
866
|
+
transform: substitute,
|
|
867
|
+
allowRoot
|
|
868
|
+
});
|
|
612
869
|
} catch (e) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
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 =
|
|
917
|
+
const grandTotal = 1 + result.copied.length;
|
|
659
918
|
process.stdout.write(
|
|
660
|
-
`bassclef init: ${grandTotal} files total (
|
|
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
|
-
|
|
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
|
|
839
|
-
" .claude/
|
|
840
|
-
"
|
|
841
|
-
"
|
|
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",
|