@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.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { version } from "./index.js";
|
|
3
|
-
import { realpathSync, statSync, constants, lstatSync, mkdirSync, accessSync, unlinkSync, openSync, writeSync, closeSync, readFileSync, existsSync } from "node:fs";
|
|
4
|
-
import { isAbsolute, resolve, dirname, join } from "node:path";
|
|
3
|
+
import { realpathSync, statSync, constants, lstatSync, mkdirSync, accessSync, unlinkSync, openSync, writeSync, closeSync, readlinkSync, readFileSync, chmodSync, readdirSync, existsSync } from "node:fs";
|
|
4
|
+
import { isAbsolute, resolve, dirname, join, normalize, relative, basename as basename$1 } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { createRequire } from "node:module";
|
|
9
8
|
import { createInterface } from "node:readline/promises";
|
|
10
9
|
import { stdin, stderr, stdout } from "node:process";
|
|
11
10
|
const DEFAULTS$2 = {
|
|
@@ -14,7 +13,9 @@ const DEFAULTS$2 = {
|
|
|
14
13
|
verbose: false,
|
|
15
14
|
allowRoot: false,
|
|
16
15
|
allowAnyDir: false,
|
|
17
|
-
dir: void 0
|
|
16
|
+
dir: void 0,
|
|
17
|
+
yes: false,
|
|
18
|
+
json: false
|
|
18
19
|
};
|
|
19
20
|
let ArgvError$1 = class ArgvError extends Error {
|
|
20
21
|
name = "ArgvError";
|
|
@@ -49,6 +50,16 @@ function parseInitArgs(argv) {
|
|
|
49
50
|
i += 1;
|
|
50
51
|
continue;
|
|
51
52
|
}
|
|
53
|
+
if (token === "--yes") {
|
|
54
|
+
out.yes = true;
|
|
55
|
+
i += 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (token === "--json") {
|
|
59
|
+
out.json = true;
|
|
60
|
+
i += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
52
63
|
if (token === "--dir") {
|
|
53
64
|
const value = argv[i + 1];
|
|
54
65
|
if (value === void 0 || value.startsWith("--")) {
|
|
@@ -117,6 +128,13 @@ function isUnder(child, parent) {
|
|
|
117
128
|
if (rel === par) return true;
|
|
118
129
|
return rel.startsWith(par + "/");
|
|
119
130
|
}
|
|
131
|
+
function readlinkOrNull(path) {
|
|
132
|
+
try {
|
|
133
|
+
return readlinkSync(path);
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
120
138
|
class WriteError extends Error {
|
|
121
139
|
name = "WriteError";
|
|
122
140
|
kind;
|
|
@@ -150,7 +168,12 @@ function writeSafely(path, content, opts = {}) {
|
|
|
150
168
|
else throw new WriteError("Unknown", `cannot stat ${path}: ${err.code ?? "unknown"}`);
|
|
151
169
|
}
|
|
152
170
|
if (existing === "symlink") {
|
|
153
|
-
|
|
171
|
+
const target = readlinkOrNull(path);
|
|
172
|
+
const targetClause = target ? ` (points to: ${target})` : "";
|
|
173
|
+
throw new WriteError(
|
|
174
|
+
"SymlinkRefused",
|
|
175
|
+
`refusing to follow symlink at target path: ${path}${targetClause}. Delete or move the symlink and rerun bassclef init.`
|
|
176
|
+
);
|
|
154
177
|
}
|
|
155
178
|
if (existing !== "none" && !opts.force) {
|
|
156
179
|
throw new WriteError("AlreadyExists", `file already exists (pass --force to overwrite): ${path}`);
|
|
@@ -220,22 +243,6 @@ function hashContent(content) {
|
|
|
220
243
|
normalized = normalized.replace(/\r\n/g, "\n");
|
|
221
244
|
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
222
245
|
}
|
|
223
|
-
const SETTINGS_TEMPLATE_VERSION = "0.0.1";
|
|
224
|
-
function settingsJsonTemplate(pkgVersion) {
|
|
225
|
-
const value = {
|
|
226
|
-
$bassclef: {
|
|
227
|
-
template: "settings.json",
|
|
228
|
-
template_version: SETTINGS_TEMPLATE_VERSION,
|
|
229
|
-
generated_by: "@thebassclef/core",
|
|
230
|
-
generated_by_version: pkgVersion
|
|
231
|
-
},
|
|
232
|
-
permissions: {
|
|
233
|
-
additionalDirectories: []
|
|
234
|
-
},
|
|
235
|
-
hooks: {}
|
|
236
|
-
};
|
|
237
|
-
return JSON.stringify(value, null, 2) + "\n";
|
|
238
|
-
}
|
|
239
246
|
const SUBSTRATE_CONFIG_TEMPLATE_VERSION = "0.0.1";
|
|
240
247
|
function substrateConfigMdTemplate(pkgVersion) {
|
|
241
248
|
return [
|
|
@@ -283,8 +290,10 @@ function substrateConfigMdTemplate(pkgVersion) {
|
|
|
283
290
|
].join("\n");
|
|
284
291
|
}
|
|
285
292
|
const MANIFEST_SCHEMA_VERSION = "0.1.0";
|
|
293
|
+
const MANIFEST_SHAPE_VERSION = 2;
|
|
286
294
|
function manifestTemplate(input) {
|
|
287
295
|
const value = {
|
|
296
|
+
schema_version: MANIFEST_SHAPE_VERSION,
|
|
288
297
|
$bassclef: {
|
|
289
298
|
template: "init.manifest.json",
|
|
290
299
|
manifest_schema_version: MANIFEST_SCHEMA_VERSION,
|
|
@@ -306,6 +315,15 @@ class ManifestReadError extends Error {
|
|
|
306
315
|
this.kind = kind;
|
|
307
316
|
}
|
|
308
317
|
}
|
|
318
|
+
function readManifestShapeVersion(targetDir) {
|
|
319
|
+
try {
|
|
320
|
+
const parsed = readManifest(targetDir);
|
|
321
|
+
if (typeof parsed.schema_version === "number") return parsed.schema_version;
|
|
322
|
+
return null;
|
|
323
|
+
} catch {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
309
327
|
function readManifest(targetDir) {
|
|
310
328
|
const path = join(targetDir, MANIFEST_RELATIVE_PATH);
|
|
311
329
|
let raw;
|
|
@@ -386,129 +404,347 @@ async function computeConfigHashes(targetDir, paths) {
|
|
|
386
404
|
}
|
|
387
405
|
return out;
|
|
388
406
|
}
|
|
389
|
-
|
|
407
|
+
class CopyFailure extends Error {
|
|
408
|
+
constructor(kind, message) {
|
|
409
|
+
super(message);
|
|
410
|
+
this.kind = kind;
|
|
411
|
+
this.name = "CopyFailure";
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function resolveHome(opts) {
|
|
415
|
+
const raw = process.env.HOME;
|
|
416
|
+
if (raw === void 0 || raw === "") {
|
|
417
|
+
throw new CopyFailure(
|
|
418
|
+
"EnvironmentIncomplete",
|
|
419
|
+
"HOME environment variable is unset. bassclef init needs HOME set to resolve user-scope hook targets. Set HOME to your home directory and rerun."
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (raw === "/root" && !opts.allowRoot) {
|
|
423
|
+
throw new CopyFailure(
|
|
424
|
+
"SudoBypassRefused",
|
|
425
|
+
"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."
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
const canonical = raw === homedir() ? homedir() : raw;
|
|
429
|
+
try {
|
|
430
|
+
return realpathSync(canonical);
|
|
431
|
+
} catch {
|
|
432
|
+
return canonical.endsWith("/") ? canonical.slice(0, -1) : canonical;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const PREFIX_HOME = "$HOME/";
|
|
436
|
+
const PREFIX_PROJECT = "$CLAUDE_PROJECT_DIR/";
|
|
437
|
+
function classify$1(hook, opts) {
|
|
438
|
+
const cmd = hook.command;
|
|
439
|
+
if (typeof cmd !== "string" || cmd.length === 0) {
|
|
440
|
+
throw new CopyFailure(
|
|
441
|
+
"UnknownScopePrefix",
|
|
442
|
+
"Hook command is empty. settings.json must supply a non-empty command string."
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
if (cmd.startsWith(PREFIX_HOME)) {
|
|
446
|
+
return classifyUser(cmd.slice(PREFIX_HOME.length), opts);
|
|
447
|
+
}
|
|
448
|
+
if (cmd.startsWith(PREFIX_PROJECT)) {
|
|
449
|
+
return classifyProject(cmd.slice(PREFIX_PROJECT.length), opts);
|
|
450
|
+
}
|
|
451
|
+
throw new CopyFailure(
|
|
452
|
+
"UnknownScopePrefix",
|
|
453
|
+
`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.`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
function classifyUser(relPath, opts) {
|
|
457
|
+
const home = resolveHome({ allowRoot: opts.allowRoot });
|
|
458
|
+
const cleaned = normalizeRel(relPath);
|
|
459
|
+
const targetPath = normalize(join(home, cleaned));
|
|
460
|
+
assertContained(targetPath, home, opts.allowRoot ? "/root" : null);
|
|
461
|
+
return { scope: "user", targetPath };
|
|
462
|
+
}
|
|
463
|
+
function classifyProject(relPath, opts) {
|
|
464
|
+
const cleaned = normalizeRel(relPath);
|
|
465
|
+
const targetPath = normalize(join(opts.targetDir, cleaned));
|
|
466
|
+
assertContained(targetPath, opts.targetDir);
|
|
467
|
+
return { scope: "project", targetPath };
|
|
468
|
+
}
|
|
469
|
+
function normalizeRel(rel) {
|
|
470
|
+
return rel.replace(/\/{2,}/g, "/");
|
|
471
|
+
}
|
|
472
|
+
function assertContained(target, root, altRoot) {
|
|
473
|
+
const resolvedTarget = resolve(target);
|
|
474
|
+
const resolvedRoot = resolve(root);
|
|
475
|
+
if (resolvedTarget === resolvedRoot) return;
|
|
476
|
+
if (resolvedTarget.startsWith(resolvedRoot + "/")) return;
|
|
477
|
+
if (altRoot) {
|
|
478
|
+
const resolvedAlt = resolve(altRoot);
|
|
479
|
+
if (resolvedTarget === resolvedAlt) return;
|
|
480
|
+
if (resolvedTarget.startsWith(resolvedAlt + "/")) return;
|
|
481
|
+
}
|
|
482
|
+
throw new CopyFailure(
|
|
483
|
+
"PathTraversalRefused",
|
|
484
|
+
`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.`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
const HOOK_EXECUTABLE_MODE = 493;
|
|
488
|
+
function setExecutable(targetPath) {
|
|
489
|
+
if (process.platform === "win32") {
|
|
490
|
+
process.stderr.write(
|
|
491
|
+
`bassclef init: executable bit not applicable on this OS (${targetPath}). Claude Code will still invoke the hook if a POSIX shell layer is present.
|
|
492
|
+
`
|
|
493
|
+
);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
chmodSync(targetPath, HOOK_EXECUTABLE_MODE);
|
|
497
|
+
}
|
|
498
|
+
const HOOKS_SUBPATH = ".claude/hooks/";
|
|
499
|
+
const SETTINGS_SUBPATH = ".claude/settings.json";
|
|
390
500
|
const CONFIG_FILES = [
|
|
391
501
|
".claude/settings.json",
|
|
392
502
|
"substrate.config.md",
|
|
393
503
|
"substrate.secrets.md"
|
|
394
504
|
];
|
|
395
505
|
const CURRENT_ENTRY_COUNT = 149;
|
|
506
|
+
const EXPECTED_WIRING_SCHEMA_MAJOR = 2;
|
|
507
|
+
const WIRING_MANIFEST_RELATIVE = "standards/bassclef-wiring-manifest.json";
|
|
396
508
|
function copySubstrate(targetDir, options = {}) {
|
|
397
509
|
const bundleRoot = resolveBundleRoot(options.bundleRoot);
|
|
398
|
-
const manifest =
|
|
510
|
+
const manifest = readWiringManifest(bundleRoot);
|
|
399
511
|
const result = {
|
|
400
512
|
copied: [],
|
|
513
|
+
copiedEntries: [],
|
|
401
514
|
refused: [],
|
|
402
515
|
errored: [],
|
|
403
|
-
erroredMessages: []
|
|
516
|
+
erroredMessages: [],
|
|
517
|
+
hookCount: 0,
|
|
518
|
+
wiringVersion: manifest.version
|
|
404
519
|
};
|
|
405
520
|
if (options.dryRun) result.wouldCopy = [];
|
|
406
|
-
const
|
|
407
|
-
|
|
521
|
+
const files = walkDistTree(bundleRoot);
|
|
522
|
+
const groups = groupByTopDirectory(files);
|
|
523
|
+
const bundleHookRelPaths = new Set(files.filter(isHookFile));
|
|
524
|
+
const scopeMap = buildScopeMap(bundleRoot, targetDir, options, bundleHookRelPaths);
|
|
525
|
+
for (const [directory, groupFiles] of groups) {
|
|
408
526
|
let completedInGroup = 0;
|
|
409
|
-
for (const
|
|
410
|
-
const outcome = copyOne(
|
|
527
|
+
for (const relPath of groupFiles) {
|
|
528
|
+
const outcome = copyOne(relPath, bundleRoot, targetDir, options, result, scopeMap);
|
|
411
529
|
if (outcome !== "skipped") completedInGroup += 1;
|
|
412
530
|
}
|
|
413
531
|
if (options.onProgress) options.onProgress(directory, completedInGroup);
|
|
414
532
|
}
|
|
533
|
+
result.hookCount = readHookCount(
|
|
534
|
+
options.dryRun ? join(bundleRoot, ".claude/settings.json") : join(targetDir, ".claude/settings.json")
|
|
535
|
+
);
|
|
415
536
|
return result;
|
|
416
537
|
}
|
|
417
|
-
function
|
|
418
|
-
const
|
|
419
|
-
|
|
538
|
+
function readWiringManifest(bundleRoot) {
|
|
539
|
+
const manifestPath = join(bundleRoot, WIRING_MANIFEST_RELATIVE);
|
|
540
|
+
if (!fileExists(manifestPath)) {
|
|
541
|
+
throw new CopyFailure(
|
|
542
|
+
"ManifestMissing",
|
|
543
|
+
`Wiring manifest missing at ${manifestPath}. Cli built for schema_version ${EXPECTED_WIRING_SCHEMA_MAJOR}.x. Run \`npm install @thebassclef/lite@latest\` to fix.`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
let manifest;
|
|
547
|
+
let raw;
|
|
548
|
+
try {
|
|
549
|
+
raw = readFileSync(manifestPath, "utf8");
|
|
550
|
+
} catch (e) {
|
|
551
|
+
const err = e;
|
|
552
|
+
throw new CopyFailure(
|
|
553
|
+
"ManifestMissing",
|
|
554
|
+
`Wiring manifest at ${manifestPath} unreadable (${err.code ?? "unknown"}). Reinstall @thebassclef/lite to restore the bundle.`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
manifest = JSON.parse(raw);
|
|
559
|
+
} catch (e) {
|
|
560
|
+
const err = e;
|
|
561
|
+
throw new CopyFailure(
|
|
562
|
+
"SchemaIncompatible",
|
|
563
|
+
`Wiring manifest at ${manifestPath} is not valid JSON: ${err.message}. Reinstall @thebassclef/lite to restore the bundle.`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
|
567
|
+
throw new CopyFailure(
|
|
568
|
+
"SchemaIncompatible",
|
|
569
|
+
`Wiring manifest at ${manifestPath} missing 'version' field. Cli requires schema_version ${EXPECTED_WIRING_SCHEMA_MAJOR}.x. Reinstall @thebassclef/lite to fix.`
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
const major = parseInt(manifest.version.split(".")[0] ?? "0", 10);
|
|
573
|
+
if (major !== EXPECTED_WIRING_SCHEMA_MAJOR) {
|
|
574
|
+
throw new CopyFailure(
|
|
575
|
+
"SchemaIncompatible",
|
|
576
|
+
`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.`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return { version: manifest.version };
|
|
580
|
+
}
|
|
581
|
+
function walkDistTree(bundleRoot) {
|
|
582
|
+
if (!dirExists(bundleRoot)) {
|
|
583
|
+
throw new CopyFailure(
|
|
584
|
+
"BundleMissing",
|
|
585
|
+
`Bundled substrate missing at ${bundleRoot}. Reinstall @thebassclef/lite to restore the bundle.`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
const results = [];
|
|
589
|
+
function walk(dir) {
|
|
590
|
+
for (const name of readdirSync(dir)) {
|
|
591
|
+
const full = join(dir, name);
|
|
592
|
+
const st = statSync(full);
|
|
593
|
+
if (st.isDirectory()) walk(full);
|
|
594
|
+
else results.push(relative(bundleRoot, full));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
walk(bundleRoot);
|
|
598
|
+
results.sort();
|
|
599
|
+
return results;
|
|
600
|
+
}
|
|
601
|
+
const GITIGNORE_BUNDLE_NAME = "gitignore";
|
|
602
|
+
const GITIGNORE_ADOPTER_NAME = ".gitignore";
|
|
603
|
+
function mapAdopterPath(relPath) {
|
|
604
|
+
if (relPath === GITIGNORE_BUNDLE_NAME) return GITIGNORE_ADOPTER_NAME;
|
|
605
|
+
return relPath;
|
|
606
|
+
}
|
|
607
|
+
function buildScopeMap(bundleRoot, targetDir, options, bundleHookRelPaths) {
|
|
608
|
+
const map = /* @__PURE__ */ new Map();
|
|
609
|
+
const settingsPath = join(bundleRoot, SETTINGS_SUBPATH);
|
|
610
|
+
if (!fileExists(settingsPath)) return map;
|
|
611
|
+
let parsed;
|
|
612
|
+
try {
|
|
613
|
+
parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
614
|
+
} catch {
|
|
615
|
+
return map;
|
|
616
|
+
}
|
|
617
|
+
const allowRoot = options.allowRoot ?? false;
|
|
618
|
+
for (const eventBlocks of Object.values(parsed.hooks ?? {})) {
|
|
619
|
+
for (const block of eventBlocks) {
|
|
620
|
+
for (const entry of block.hooks ?? []) {
|
|
621
|
+
const cmd = entry.command;
|
|
622
|
+
if (typeof cmd !== "string" || cmd.length === 0) continue;
|
|
623
|
+
if (!cmd.startsWith("$")) continue;
|
|
624
|
+
const decision = classify$1({ command: cmd }, { targetDir, allowRoot });
|
|
625
|
+
const relSource = cmd.replace(/^\$HOME\//, "").replace(/^\$CLAUDE_PROJECT_DIR\//, "").replace(/\/{2,}/g, "/");
|
|
626
|
+
if (!bundleHookRelPaths.has(relSource)) continue;
|
|
627
|
+
map.set(relSource, decision);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return map;
|
|
632
|
+
}
|
|
633
|
+
function isHookFile(relPath) {
|
|
634
|
+
return relPath.startsWith(HOOKS_SUBPATH) && relPath.endsWith(".sh");
|
|
635
|
+
}
|
|
636
|
+
function copyOne(relPath, bundleRoot, targetDir, options, result, scopeMap) {
|
|
637
|
+
const sourcePath = join(bundleRoot, relPath);
|
|
638
|
+
const adopterRelPath = mapAdopterPath(relPath);
|
|
639
|
+
const scopeDecision = isHookFile(relPath) ? scopeMap.get(relPath) : void 0;
|
|
640
|
+
const targetPath = scopeDecision ? scopeDecision.targetPath : join(targetDir, adopterRelPath);
|
|
641
|
+
const scope = scopeDecision ? scopeDecision.scope : "project";
|
|
420
642
|
let content;
|
|
421
643
|
try {
|
|
422
644
|
content = readFileSync(sourcePath, "utf8");
|
|
423
645
|
} catch (e) {
|
|
424
646
|
const err = e;
|
|
425
|
-
const message = `${
|
|
426
|
-
result.errored.push(
|
|
427
|
-
result.erroredMessages?.push(message);
|
|
428
|
-
return "errored";
|
|
429
|
-
}
|
|
430
|
-
const declaredHash = stripHashPrefix(entry.content_hash);
|
|
431
|
-
const actualHash = sha256Hex(content);
|
|
432
|
-
if (declaredHash !== actualHash) {
|
|
433
|
-
const message = `${entry.path} — bundled content hash does not match the manifest. Reinstall @thebassclef/core to repair the bundle, then rerun bassclef init --force.`;
|
|
434
|
-
result.errored.push(entry.path);
|
|
647
|
+
const message = `${relPath} — cannot read bundled source (${err.code ?? "unknown"}). Reinstall @thebassclef/lite to restore the bundle.`;
|
|
648
|
+
result.errored.push(adopterRelPath);
|
|
435
649
|
result.erroredMessages?.push(message);
|
|
436
650
|
return "errored";
|
|
437
651
|
}
|
|
652
|
+
const outputContent = options.transform ? options.transform(adopterRelPath, content) : content;
|
|
438
653
|
if (options.dryRun) {
|
|
439
|
-
result.wouldCopy?.push(
|
|
654
|
+
result.wouldCopy?.push(adopterRelPath);
|
|
440
655
|
return "wouldCopy";
|
|
441
656
|
}
|
|
442
657
|
try {
|
|
443
658
|
mkdirSafely(dirname(targetPath));
|
|
444
|
-
writeSafely(targetPath,
|
|
445
|
-
|
|
659
|
+
writeSafely(targetPath, outputContent, { force: options.force ?? false });
|
|
660
|
+
if (isHookFile(relPath)) {
|
|
661
|
+
setExecutable(targetPath);
|
|
662
|
+
}
|
|
663
|
+
result.copied.push(adopterRelPath);
|
|
664
|
+
result.copiedEntries.push({ path: adopterRelPath, scope });
|
|
446
665
|
return "copied";
|
|
447
666
|
} catch (e) {
|
|
448
667
|
if (e instanceof WriteError) {
|
|
449
668
|
if (e.kind === "AlreadyExists") {
|
|
450
|
-
result.refused.push(
|
|
669
|
+
result.refused.push(adopterRelPath);
|
|
451
670
|
return "refused";
|
|
452
671
|
}
|
|
453
672
|
if (e.kind === "SymlinkRefused") {
|
|
454
|
-
result.refused.push(
|
|
673
|
+
result.refused.push(adopterRelPath);
|
|
674
|
+
process.stderr.write(`bassclef init: ${e.message}
|
|
675
|
+
`);
|
|
455
676
|
return "refused";
|
|
456
677
|
}
|
|
457
|
-
const message = `${
|
|
458
|
-
result.errored.push(
|
|
678
|
+
const message = `${adopterRelPath} — write failed (${e.kind}): ${e.message}. Check the target directory exists and is writable, then rerun.`;
|
|
679
|
+
result.errored.push(adopterRelPath);
|
|
459
680
|
result.erroredMessages?.push(message);
|
|
460
681
|
return "errored";
|
|
461
682
|
}
|
|
462
683
|
throw e;
|
|
463
684
|
}
|
|
464
685
|
}
|
|
465
|
-
function loadBundledManifest(bundleRoot) {
|
|
466
|
-
const manifestPath = join(bundleRoot, ".bassclef", "lite-manifest.json");
|
|
467
|
-
const raw = readFileSync(manifestPath, "utf8");
|
|
468
|
-
const parsed = JSON.parse(raw);
|
|
469
|
-
if (!Array.isArray(parsed.entries)) {
|
|
470
|
-
throw new Error(
|
|
471
|
-
`bundled manifest at ${manifestPath} has no entries[] array. Reinstall @thebassclef/core to restore the bundle.`
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
return parsed;
|
|
475
|
-
}
|
|
476
686
|
function resolveBundleRoot(explicit) {
|
|
477
687
|
if (explicit && explicit.length > 0) return resolve(explicit);
|
|
478
688
|
const thisFile = fileURLToPath(import.meta.url);
|
|
479
689
|
let dir = dirname(thisFile);
|
|
480
690
|
for (let i = 0; i < 5; i += 1) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
return join(dir, SUBSTRATE_ROOT);
|
|
484
|
-
} catch {
|
|
485
|
-
const parent = dirname(dir);
|
|
486
|
-
if (parent === dir) break;
|
|
487
|
-
dir = parent;
|
|
691
|
+
if (fileExists(join(dir, "package.json"))) {
|
|
692
|
+
return join(dir, "dist", "lite");
|
|
488
693
|
}
|
|
694
|
+
const parent = dirname(dir);
|
|
695
|
+
if (parent === dir) break;
|
|
696
|
+
dir = parent;
|
|
489
697
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
698
|
+
throw new CopyFailure(
|
|
699
|
+
"BundleMissing",
|
|
700
|
+
`Cannot resolve bundled substrate root — package.json unreachable from ${thisFile}. Reinstall @thebassclef/lite.`
|
|
701
|
+
);
|
|
494
702
|
}
|
|
495
|
-
function groupByTopDirectory(
|
|
703
|
+
function groupByTopDirectory(files) {
|
|
496
704
|
const groups = /* @__PURE__ */ new Map();
|
|
497
|
-
for (const
|
|
498
|
-
const parts =
|
|
705
|
+
for (const p of files) {
|
|
706
|
+
const parts = p.split("/");
|
|
499
707
|
const top = parts.length >= 2 ? parts.slice(0, 2).join("/") : parts[0] ?? "";
|
|
500
708
|
if (!groups.has(top)) groups.set(top, []);
|
|
501
|
-
groups.get(top).push(
|
|
709
|
+
groups.get(top).push(p);
|
|
502
710
|
}
|
|
503
711
|
return groups;
|
|
504
712
|
}
|
|
505
|
-
function
|
|
506
|
-
|
|
507
|
-
|
|
713
|
+
function readHookCount(settingsPath) {
|
|
714
|
+
try {
|
|
715
|
+
const parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
716
|
+
let count = 0;
|
|
717
|
+
for (const matcherBlocks of Object.values(parsed.hooks ?? {})) {
|
|
718
|
+
for (const block of matcherBlocks) {
|
|
719
|
+
count += (block.hooks ?? []).length;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return count;
|
|
723
|
+
} catch {
|
|
724
|
+
return 0;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function fileExists(p) {
|
|
728
|
+
try {
|
|
729
|
+
return statSync(p).isFile();
|
|
730
|
+
} catch {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
508
733
|
}
|
|
509
|
-
function
|
|
510
|
-
|
|
734
|
+
function dirExists(p) {
|
|
735
|
+
try {
|
|
736
|
+
return statSync(p).isDirectory();
|
|
737
|
+
} catch {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
511
740
|
}
|
|
741
|
+
const RESOLVED_TIER = "lite";
|
|
742
|
+
const PLACEHOLDER_FILES = /* @__PURE__ */ new Set([
|
|
743
|
+
"CLAUDE.md",
|
|
744
|
+
"docs/whereami.md",
|
|
745
|
+
"whereami.md",
|
|
746
|
+
".bassclef-source.json"
|
|
747
|
+
]);
|
|
512
748
|
function runInit(argv) {
|
|
513
749
|
let args;
|
|
514
750
|
try {
|
|
@@ -544,7 +780,10 @@ function runInit(argv) {
|
|
|
544
780
|
}
|
|
545
781
|
throw e;
|
|
546
782
|
}
|
|
547
|
-
|
|
783
|
+
const advisoryOutcome = maybeEmitUpgradeAdvisory(targetDir, args.yes);
|
|
784
|
+
if (advisoryOutcome === "refused") return 1;
|
|
785
|
+
const upgradeApproved = advisoryOutcome === "upgrade-approved";
|
|
786
|
+
if (!args.force && !args.dryRun && !upgradeApproved) {
|
|
548
787
|
const manifestPath = join(targetDir, MANIFEST_RELATIVE_PATH);
|
|
549
788
|
if (existsSync(manifestPath)) {
|
|
550
789
|
process.stderr.write(
|
|
@@ -554,14 +793,6 @@ function runInit(argv) {
|
|
|
554
793
|
}
|
|
555
794
|
}
|
|
556
795
|
const plans = [
|
|
557
|
-
{
|
|
558
|
-
label: "settings.json",
|
|
559
|
-
relativePath: ".claude/settings.json",
|
|
560
|
-
fullPath: join(targetDir, ".claude", "settings.json"),
|
|
561
|
-
content: settingsJsonTemplate(version),
|
|
562
|
-
templateName: "settings.json",
|
|
563
|
-
templateVersion: SETTINGS_TEMPLATE_VERSION
|
|
564
|
-
},
|
|
565
796
|
{
|
|
566
797
|
label: "substrate.config.md",
|
|
567
798
|
relativePath: "substrate.config.md",
|
|
@@ -573,30 +804,54 @@ function runInit(argv) {
|
|
|
573
804
|
];
|
|
574
805
|
if (args.dryRun) {
|
|
575
806
|
runDryRun$1(plans);
|
|
576
|
-
dispatchSubstrateCopy(targetDir, args.force, args.verbose, true);
|
|
577
|
-
return 0;
|
|
807
|
+
return dispatchSubstrateCopy(targetDir, args.force || upgradeApproved, args.verbose, true, args.allowRoot, args.json);
|
|
578
808
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
809
|
+
return runReal$1(plans, args.force || upgradeApproved, args.verbose, targetDir, args.allowRoot, args.json);
|
|
810
|
+
}
|
|
811
|
+
function maybeEmitUpgradeAdvisory(targetDir, yes) {
|
|
812
|
+
const manifestPath = join(targetDir, MANIFEST_RELATIVE_PATH);
|
|
813
|
+
if (!existsSync(manifestPath)) return "ok";
|
|
814
|
+
const version2 = readManifestShapeVersion(targetDir);
|
|
815
|
+
if (version2 !== null && version2 >= 2) return "ok";
|
|
816
|
+
process.stdout.write(
|
|
817
|
+
`bassclef init: cli 1.0.1 introduces user-scope hook installation at ~/${HOOKS_SUBPATH}. cli 1.0.0 did not write there.
|
|
818
|
+
`
|
|
819
|
+
);
|
|
820
|
+
if (yes) return "upgrade-approved";
|
|
821
|
+
process.stdout.write("bassclef init: continue? (y/N) ");
|
|
822
|
+
const answer = readOneLineFromStdin();
|
|
823
|
+
if (answer === "" || answer === "y" || answer === "Y") return "upgrade-approved";
|
|
824
|
+
process.stdout.write("bassclef init: aborted by adopter.\n");
|
|
825
|
+
return "refused";
|
|
826
|
+
}
|
|
827
|
+
function readOneLineFromStdin() {
|
|
828
|
+
try {
|
|
829
|
+
const buf = Buffer.alloc(256);
|
|
830
|
+
const n = require("node:fs").readSync(0, buf, 0, 256, null);
|
|
831
|
+
return n > 0 ? buf.slice(0, n).toString("utf8").trim() : "";
|
|
832
|
+
} catch {
|
|
833
|
+
return "";
|
|
582
834
|
}
|
|
583
|
-
return exitCode;
|
|
584
835
|
}
|
|
585
|
-
function dispatchSubstrateCopy(targetDir, force, verbose, dryRun) {
|
|
836
|
+
function dispatchSubstrateCopy(targetDir, force, verbose, dryRun, allowRoot, json) {
|
|
837
|
+
const substitute = makePlaceholderTransform(targetDir);
|
|
586
838
|
let result;
|
|
587
839
|
try {
|
|
588
|
-
result = copySubstrate(targetDir, {
|
|
840
|
+
result = copySubstrate(targetDir, {
|
|
841
|
+
force,
|
|
842
|
+
dryRun,
|
|
843
|
+
transform: substitute,
|
|
844
|
+
allowRoot
|
|
845
|
+
});
|
|
589
846
|
} catch (e) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
);
|
|
599
|
-
process.exit(2);
|
|
847
|
+
if (e instanceof CopyFailure) {
|
|
848
|
+
process.stderr.write(`bassclef init: ${e.message}
|
|
849
|
+
`);
|
|
850
|
+
if (e.kind === "ManifestMissing") return 4;
|
|
851
|
+
if (e.kind === "SchemaIncompatible") return 5;
|
|
852
|
+
return 2;
|
|
853
|
+
}
|
|
854
|
+
throw e;
|
|
600
855
|
}
|
|
601
856
|
if (dryRun) {
|
|
602
857
|
const wouldCopy = result.wouldCopy ?? [];
|
|
@@ -611,10 +866,14 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun) {
|
|
|
611
866
|
`
|
|
612
867
|
);
|
|
613
868
|
}
|
|
614
|
-
|
|
869
|
+
process.stdout.write(
|
|
870
|
+
`bassclef init: ${result.hookCount} hooks armed (${RESOLVED_TIER} tier).
|
|
871
|
+
`
|
|
872
|
+
);
|
|
873
|
+
return 0;
|
|
615
874
|
}
|
|
616
875
|
if (result.copied.length === 0 && result.refused.length === 0 && result.errored.length === 0) {
|
|
617
|
-
return;
|
|
876
|
+
return 0;
|
|
618
877
|
}
|
|
619
878
|
const groupCounts = /* @__PURE__ */ new Map();
|
|
620
879
|
for (const path of result.copied) {
|
|
@@ -632,17 +891,56 @@ function dispatchSubstrateCopy(targetDir, force, verbose, dryRun) {
|
|
|
632
891
|
if (result.errored.length > 0) parts.push(`${result.errored.length} error(s)`);
|
|
633
892
|
process.stdout.write(`bassclef init: ${parts.join(", ")}.
|
|
634
893
|
`);
|
|
635
|
-
const grandTotal =
|
|
894
|
+
const grandTotal = 1 + result.copied.length;
|
|
636
895
|
process.stdout.write(
|
|
637
|
-
`bassclef init: ${grandTotal} files total (
|
|
896
|
+
`bassclef init: ${grandTotal} files total (1 config + ${result.copied.length} substrate).
|
|
638
897
|
`
|
|
639
898
|
);
|
|
899
|
+
const copiedHookEntries = result.copiedEntries.filter(
|
|
900
|
+
(e) => e.path.startsWith(HOOKS_SUBPATH) && e.path.endsWith(".sh")
|
|
901
|
+
);
|
|
902
|
+
const copiedCount = copiedHookEntries.length;
|
|
903
|
+
const declaredCount = result.hookCount;
|
|
904
|
+
const failedCount = result.refused.length + result.errored.length;
|
|
905
|
+
const userScope = copiedHookEntries.filter((e) => e.scope === "user").length;
|
|
906
|
+
const projectScope = copiedHookEntries.filter((e) => e.scope === "project").length;
|
|
907
|
+
const scopeSuffix = userScope + projectScope > 0 ? ` ${projectScope} in <repo>/${HOOKS_SUBPATH.replace(/\/$/, "")}, ${userScope} in ~/${HOOKS_SUBPATH.replace(/\/$/, "")}.` : "";
|
|
908
|
+
if (failedCount > 0) {
|
|
909
|
+
process.stdout.write(
|
|
910
|
+
`bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix} ${failedCount} failed — see errors above. Rerun bassclef init to retry.
|
|
911
|
+
`
|
|
912
|
+
);
|
|
913
|
+
} else {
|
|
914
|
+
process.stdout.write(
|
|
915
|
+
`bassclef init: Installed ${copiedCount} of ${declaredCount} hooks (${RESOLVED_TIER} tier).${scopeSuffix}
|
|
916
|
+
`
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
if (json) {
|
|
920
|
+
const report = {
|
|
921
|
+
copied: copiedCount,
|
|
922
|
+
declared: declaredCount,
|
|
923
|
+
failed: failedCount,
|
|
924
|
+
scope_counts: { user: userScope, project: projectScope },
|
|
925
|
+
tier: RESOLVED_TIER
|
|
926
|
+
};
|
|
927
|
+
process.stderr.write(JSON.stringify(report) + "\n");
|
|
928
|
+
}
|
|
640
929
|
if (verbose && result.erroredMessages) {
|
|
641
930
|
for (const msg of result.erroredMessages) {
|
|
642
931
|
process.stderr.write(` substrate: ${msg}
|
|
643
932
|
`);
|
|
644
933
|
}
|
|
645
934
|
}
|
|
935
|
+
return result.errored.length > 0 ? 2 : 0;
|
|
936
|
+
}
|
|
937
|
+
function makePlaceholderTransform(targetDir) {
|
|
938
|
+
const repoName = basename$1(targetDir);
|
|
939
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
940
|
+
return (relPath, content) => {
|
|
941
|
+
if (!PLACEHOLDER_FILES.has(relPath)) return content;
|
|
942
|
+
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);
|
|
943
|
+
};
|
|
646
944
|
}
|
|
647
945
|
function runDryRun$1(plans) {
|
|
648
946
|
for (const p of plans) {
|
|
@@ -669,7 +967,7 @@ function runDryRun$1(plans) {
|
|
|
669
967
|
}
|
|
670
968
|
return 0;
|
|
671
969
|
}
|
|
672
|
-
function runReal$1(plans, force, verbose, targetDir) {
|
|
970
|
+
function runReal$1(plans, force, verbose, targetDir, allowRoot, json) {
|
|
673
971
|
const results = [];
|
|
674
972
|
let anyRefused = false;
|
|
675
973
|
let anyError = false;
|
|
@@ -726,8 +1024,8 @@ function runReal$1(plans, force, verbose, targetDir) {
|
|
|
726
1024
|
process.stderr.write(`bassclef init: ${tag}: ${r.message ?? "write failed"}: ${r.plan.fullPath}
|
|
727
1025
|
`);
|
|
728
1026
|
}
|
|
729
|
-
writeManifest(targetDir, results);
|
|
730
1027
|
if (anyError) {
|
|
1028
|
+
writeManifest(targetDir, results);
|
|
731
1029
|
const parts = [];
|
|
732
1030
|
if (created > 0) parts.push(`${created} created`);
|
|
733
1031
|
if (unchanged > 0) parts.push(`${unchanged} unchanged`);
|
|
@@ -738,28 +1036,26 @@ function runReal$1(plans, force, verbose, targetDir) {
|
|
|
738
1036
|
`);
|
|
739
1037
|
return 2;
|
|
740
1038
|
}
|
|
741
|
-
if (created === 0 && unchanged === plans.length) {
|
|
742
|
-
process.stdout.write("bassclef init: already initialized. No changes.\n");
|
|
743
|
-
return 0;
|
|
744
|
-
}
|
|
745
1039
|
if (anyRefused && created > 0) {
|
|
746
1040
|
process.stdout.write(
|
|
747
1041
|
`bassclef init: ${created} config files created, ${unchanged} unchanged. Pass --force to overwrite.
|
|
748
1042
|
`
|
|
749
1043
|
);
|
|
1044
|
+
} else if (created === 0 && unchanged === plans.length) {
|
|
1045
|
+
process.stdout.write("bassclef init: already initialized. No changes.\n");
|
|
1046
|
+
} else {
|
|
1047
|
+
process.stdout.write(`bassclef init: ${created} config files created, ${unchanged} unchanged.
|
|
1048
|
+
`);
|
|
1049
|
+
}
|
|
1050
|
+
const walkerExit = dispatchSubstrateCopy(targetDir, force, verbose, false, allowRoot, json);
|
|
1051
|
+
writeManifest(targetDir, results);
|
|
1052
|
+
if (walkerExit === 0) {
|
|
750
1053
|
process.stdout.write(
|
|
751
1054
|
`bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
|
|
752
1055
|
`
|
|
753
1056
|
);
|
|
754
|
-
return 0;
|
|
755
1057
|
}
|
|
756
|
-
|
|
757
|
-
`);
|
|
758
|
-
process.stdout.write(
|
|
759
|
-
`bassclef init: your substrate lives under .claude/. Add .claude/ to .gitignore if you have not.
|
|
760
|
-
`
|
|
761
|
-
);
|
|
762
|
-
return 0;
|
|
1058
|
+
return walkerExit;
|
|
763
1059
|
}
|
|
764
1060
|
function shouldRefuseRoot(currentUid, allowRoot) {
|
|
765
1061
|
if (currentUid === void 0) return false;
|
|
@@ -807,17 +1103,22 @@ function usage$2() {
|
|
|
807
1103
|
" --allow-any-dir Allow --dir outside your home directory. Default: refuse.",
|
|
808
1104
|
" --verbose Print per-file result.",
|
|
809
1105
|
"",
|
|
1106
|
+
"Exit codes:",
|
|
1107
|
+
" 0 — success",
|
|
1108
|
+
" 1 — refused by policy (existing file; running as root; outside HOME)",
|
|
1109
|
+
" 2 — safety check failed at write (symlink; parent not writable)",
|
|
1110
|
+
" 3 — invalid args",
|
|
1111
|
+
" 4 — wiring manifest missing from bundled substrate (reinstall @thebassclef/lite)",
|
|
1112
|
+
" 5 — wiring manifest schema major version incompatible with this cli",
|
|
1113
|
+
"",
|
|
810
1114
|
"Files written under <target>:",
|
|
811
|
-
" .claude/settings.json Claude Code settings (minimal, opt-in blocks)",
|
|
812
1115
|
" substrate.config.md Bassclef project manifest",
|
|
813
1116
|
" .bassclef/init.manifest.json Record of what init wrote (used by sync)",
|
|
814
1117
|
"",
|
|
815
|
-
" Plus the bundled substrate tree
|
|
816
|
-
" .claude/
|
|
817
|
-
"
|
|
818
|
-
"
|
|
819
|
-
" AGENTS.md, CLAUDE-lite.md, README.md, CONTRIBUTING.md,",
|
|
820
|
-
" SECURITY.md, CODE_OF_CONDUCT.md",
|
|
1118
|
+
" Plus the bundled substrate tree from dist/lite/:",
|
|
1119
|
+
" .claude/settings.json Claude Code settings (verbatim from bundle)",
|
|
1120
|
+
" CLAUDE.md, whereami.md, .bassclef-source.json, .gitignore",
|
|
1121
|
+
" Templates with placeholders substituted",
|
|
821
1122
|
"",
|
|
822
1123
|
" Run with --dry-run first to preview the full file list before",
|
|
823
1124
|
" writing anything to disk.",
|
|
@@ -899,12 +1200,6 @@ function parseSyncArgs(argv) {
|
|
|
899
1200
|
return out;
|
|
900
1201
|
}
|
|
901
1202
|
const TEMPLATES = [
|
|
902
|
-
{
|
|
903
|
-
templateName: "settings.json",
|
|
904
|
-
currentVersion: SETTINGS_TEMPLATE_VERSION,
|
|
905
|
-
render: settingsJsonTemplate,
|
|
906
|
-
hasMarker: (c) => c.includes('"$bassclef"')
|
|
907
|
-
},
|
|
908
1203
|
{
|
|
909
1204
|
templateName: "substrate.config.md",
|
|
910
1205
|
currentVersion: SUBSTRATE_CONFIG_TEMPLATE_VERSION,
|
|
@@ -1405,6 +1700,7 @@ Proceed?`,
|
|
|
1405
1700
|
);
|
|
1406
1701
|
const errored = copyResult.errored;
|
|
1407
1702
|
const newManifest = {
|
|
1703
|
+
schema_version: MANIFEST_SHAPE_VERSION,
|
|
1408
1704
|
$bassclef: {
|
|
1409
1705
|
template: "init.manifest.json",
|
|
1410
1706
|
manifest_schema_version: "0.1.0",
|