git-span 1.0.131

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/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # git span
2
+
3
+ `git span` tracks implicit semantic dependencies in a git repository — line-range or whole-file anchors that participate in a coupling no schema, type, or test enforces. Each span names its anchors, optionally carries a `why` sentence defining the subsystem they collectively form (recommended on every span), and surfaces drift via `git span stale` when those anchors diverge from their anchored state.
4
+
5
+ The primary CLI surface lives in `src/cli/mod.rs`. Run `git span --help` or `git span stale --help` for flag reference.
6
+
7
+ ### Upgrading from the sqlite cache
8
+
9
+ The trail cache now lives at `<common_dir>/span/cache/v1/` as a BLAKE3-keyed content-addressed filesystem store. The previous sqlite-backed cache at `<common_dir>/span/cache/span_cache.sqlite` (or `<git_dir>/span/cache/span_cache.sqlite` on older per-worktree installations), along with its `-shm` and `-wal` companions, is unused and can be removed with a single `rm -f <common_dir>/span/cache/span_cache.sqlite*` (and the `<git_dir>` variant if present). Nothing reads those files anymore; `git span doctor --gc-trail-cache` operates only on the new store.
10
+
11
+ ## Profiling
12
+
13
+ Perf investigation tooling is documented in [`./docs/profiling.md`](./docs/profiling.md):
14
+
15
+ - **Flame graph capture** — `perf record` + `inferno-flamegraph` recipe for identifying hot functions.
16
+ - **`--perf-trace <path>`** — opt-in per-anchor wall-clock CSV emitter for `git span stale`; CSV schema, usage constraints, and quick analysis snippets are documented there.
@@ -0,0 +1,6 @@
1
+ git-span placeholder. scripts/postinstall.js replaces this file
2
+ with the native binary for your platform. The name ends in .exe and the
3
+ first line intentionally has NO "#!" shebang so npm/yarn/pnpm cmd-shim
4
+ generates a direct-exec wrapper that runs on Windows; Unix ignores the
5
+ .exe suffix and execs by mode bit. If you are seeing this text, the
6
+ postinstall step did not run.
package/man/git-span.1 ADDED
@@ -0,0 +1,172 @@
1
+ .ie \n(.g .ds Aq \(aq
2
+ .el .ds Aq '
3
+ .TH git-span 1 "git-span 1.0.131"
4
+ .ie \n(.g .ds Aq \(aq
5
+ .el .ds Aq '
6
+ .SH NAME
7
+ git\-span \- Track implicit semantic dependencies in a git repo.
8
+ .ie \n(.g .ds Aq \(aq
9
+ .el .ds Aq '
10
+ .SH SYNOPSIS
11
+ \fBgit\-span\fR [\fB\-\-perf\fR] [\fB\-\-span\-dir\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIsubcommands\fR]
12
+ .SH DESCRIPTION
13
+ .B git span
14
+ tracks implicit semantic dependencies in a Git repository: couplings between
15
+ line ranges (or whole files), in code or prose, that no schema, type, or test
16
+ enforces.
17
+ Each
18
+ .B span
19
+ anchors the participating anchors and carries a durable
20
+ .I why
21
+ \[em] one prose sentence that names the relationship the anchored set holds
22
+ and survives a rewrite of either side.
23
+ .PP
24
+ The standing question at commit time: did this change create or rely on a
25
+ coupling that is not visible from the lines themselves?
26
+ .PP
27
+ .B Writing the why.
28
+ Name the relationship the anchors hold in one prose sentence, written so it
29
+ survives a rewrite of either side.
30
+ Describe the relationship in role\-words (\(lqthe doc,\(rq \(lqthe parser,\(rq
31
+ \(lqthe runbook,\(rq \(lqthe migration\(rq) rather than repeating filenames.
32
+ For asymmetric relationships, name which side is normative:
33
+ \(lqthe doc is the source of truth when they disagree.\(rq
34
+ Do not restate the span name, embed incidental implementation details, or
35
+ bundle ownership and review triggers in the why.
36
+ .PP
37
+ .B Re\-anchoring on drift.
38
+ When
39
+ .B git span stale
40
+ reports drift, review the change at each anchor.
41
+ If the relationship still holds, re-anchor with
42
+ .B git span add
43
+ and commit.
44
+ If the relationship has changed, update the why with
45
+ .B git span why
46
+ before re-anchoring.
47
+ .PP
48
+ Each span is an ordinary tracked file under the span root (default
49
+ .IR .span ,
50
+ overridable with
51
+ .BR \-\-span\-dir ,
52
+ the
53
+ .I GIT_SPAN_DIR
54
+ environment variable, or
55
+ .IR "git config git-span.dir" ).
56
+ Spans are versioned, fetched, and pushed exactly like any other
57
+ tracked file \[em] stage and commit edits with
58
+ .B git add .span && git commit
59
+ \&.
60
+ .PP
61
+ Bare invocations:
62
+ .RS 4
63
+ .TP
64
+ .B git span
65
+ List every span in the repository.
66
+ .TP
67
+ .BI git\ span\ <name>
68
+ Show one span (anchors, why, config).
69
+ .RE
70
+ .ie \n(.g .ds Aq \(aq
71
+ .el .ds Aq '
72
+ .SH OPTIONS
73
+ .TP
74
+ \fB\-\-perf\fR
75
+ Emit performance timings for major git\-span operation groups to stderr.
76
+
77
+ Can also be enabled with `GIT_SPAN_PERF=1`.
78
+ .TP
79
+ \fB\-\-span\-dir\fR \fI<SPAN_DIR>\fR
80
+ Span root directory (default: .span). Overrides GIT_SPAN_DIR and git config git\-span.dir
81
+ .TP
82
+ \fB\-h\fR, \fB\-\-help\fR
83
+ Print help (see a summary with \*(Aq\-h\*(Aq)
84
+ .TP
85
+ \fB\-V\fR, \fB\-\-version\fR
86
+ Print version
87
+ .ie \n(.g .ds Aq \(aq
88
+ .el .ds Aq '
89
+ .SH SUBCOMMANDS
90
+ .TP
91
+ git\-span\-show(1)
92
+ Show the named span — its anchors, why, and config. Equivalent to the bare `git span <name>` positional form
93
+ .TP
94
+ git\-span\-list(1)
95
+ List files and anchors currently tracked by a span
96
+ .TP
97
+ git\-span\-stale(1)
98
+ Report anchors whose content has drifted from their anchored state
99
+ .TP
100
+ git\-span\-add(1)
101
+ Add anchors to a span, writing the span file under the span root. Stage and commit the change with `git add .span && git commit`
102
+ .TP
103
+ git\-span\-remove(1)
104
+ Remove anchors from a span, editing the span file under the span root. Stage and commit the change with `git add .span && git commit`
105
+ .TP
106
+ git\-span\-why(1)
107
+ Read or stage the span\*(Aqs why — a one\-sentence definition of the subsystem, flow, or concern the anchors collectively form
108
+ .TP
109
+ git\-span\-delete(1)
110
+ Delete a span
111
+ .TP
112
+ git\-span\-move(1)
113
+ Rename a span
114
+ .TP
115
+ git\-span\-doctor(1)
116
+ Audit the local span setup
117
+ .TP
118
+ git\-span\-tree(1)
119
+ Trace blast radius: render a clique\-grouped impact tree rooted at the files matched by the given paths/globs
120
+ .TP
121
+ git\-span\-merge\-driver(1)
122
+ Resolve `.span/` merge conflicts structurally when invoked by git as a merge driver. Receives three clean blob temp files and the marker length from git. Resolves only what is structurally derivable without trusting the worktree (which may be mid\-merge); defers same\-anchor range/hash divergence by writing minimal conflict markers and exiting non\-zero so `git span stale \-\-fix` can finish authoritatively
123
+ .TP
124
+ git\-span\-history(1)
125
+ Show a chronological timeline of a span file\*(Aqs git history: each commit where the span changed, rendered oldest→newest, with the content of any anchor that was added, modified, or removed, and an optional `current` entry describing how the working tree has drifted from HEAD
126
+ .TP
127
+ git\-span\-help(1)
128
+ Print this message or the help of the given subcommand(s)
129
+ .ie \n(.g .ds Aq \(aq
130
+ .el .ds Aq '
131
+ .SH VERSION
132
+ v1.0.131
133
+ .SH EXAMPLES
134
+ Anchor a new span alongside a code change:
135
+ .PP
136
+ .RS 4
137
+ .nf
138
+ git span add billing/charge-request-contract \e
139
+ docs/api/charge.md#L40-L88 api/charge.ts#L30-L76
140
+ git span why billing/charge-request-contract \e
141
+ -m "The doc states the request body shape the parser honors; \e
142
+ the doc is the source of truth when they disagree."
143
+ git add .span
144
+ git commit -m "Wire checkout to charge API"
145
+ .fi
146
+ .RE
147
+ .PP
148
+ Document an existing relationship anchored at HEAD:
149
+ .PP
150
+ .RS 4
151
+ .nf
152
+ git span add auth/token-contract --at HEAD \e
153
+ packages/auth/token.ts#L88-L104 \e
154
+ packages/auth/crypto.ts#L12-L40
155
+ git span why auth/token-contract \e
156
+ -m "Token verification depends on signature verification."
157
+ git add .span && git commit -m "Document token/crypto coupling"
158
+ .fi
159
+ .RE
160
+ .PP
161
+ Check for drift and inspect a span:
162
+ .PP
163
+ .RS 4
164
+ .nf
165
+ git span stale
166
+ git span billing/charge-request-contract
167
+ git span show billing/charge-request-contract
168
+ .fi
169
+ .RE
170
+ .SH SEE ALSO
171
+ .BR git (1),
172
+ .BR gitcli (7)
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "git-span",
3
+ "version": "1.0.131",
4
+ "bin": "bin/git-span.exe",
5
+ "man": [
6
+ "man/git-span.1"
7
+ ],
8
+ "scripts": {
9
+ "postinstall": "node scripts/postinstall.js",
10
+ "build:man": "bash scripts/with-target-lock.sh shared env CARGO_BUILD_JOBS=1 CARGO_TARGET_DIR=\"${GIT_SPAN_CARGO_TARGET_ROOT:-$HOME/.cache/git-span/cargo-target}/git-span/build\" cargo run --quiet --locked --bin gen-manpage -- man/git-span.1",
11
+ "build": "yarn build:man && bash scripts/with-target-lock.sh shared env CARGO_BUILD_JOBS=1 CARGO_TARGET_DIR=\"${GIT_SPAN_CARGO_TARGET_ROOT:-$HOME/.cache/git-span/cargo-target}/git-span/build\" cargo build --release --locked",
12
+ "build:local": "bash scripts/build-local.sh",
13
+ "build:clean": "bash scripts/cleanup-stale-target.sh && bash scripts/clean-shared-build.sh && yarn build",
14
+ "test": "bash scripts/with-target-lock.sh shared env CARGO_TARGET_DIR=\"${GIT_SPAN_CARGO_TARGET_ROOT:-$HOME/.cache/git-span/cargo-target}/git-span/build\" cargo nextest run --build-jobs 1 --locked",
15
+ "lint": "bash scripts/with-target-lock.sh shared env CARGO_BUILD_JOBS=1 RUSTFLAGS=\"-W unused -W dead-code\" CARGO_TARGET_DIR=\"${GIT_SPAN_CARGO_TARGET_ROOT:-$HOME/.cache/git-span/cargo-target}/git-span/check\" cargo clippy --quiet --locked -- -D warnings",
16
+ "typecheck": "bash scripts/with-target-lock.sh shared env CARGO_BUILD_JOBS=1 RUSTFLAGS=\"-W unused -W dead-code\" CARGO_TARGET_DIR=\"${GIT_SPAN_CARGO_TARGET_ROOT:-$HOME/.cache/git-span/cargo-target}/git-span/check\" cargo check --quiet --locked",
17
+ "bench:check": "bash scripts/with-target-lock.sh shared env CARGO_BUILD_JOBS=1 CARGO_TARGET_DIR=\"${GIT_SPAN_CARGO_TARGET_ROOT:-$HOME/.cache/git-span/cargo-target}/git-span/build\" cargo bench --no-run --locked --features bench-corpus",
18
+ "bench": "bash scripts/with-target-lock.sh shared env CARGO_TARGET_DIR=\"${GIT_SPAN_CARGO_TARGET_ROOT:-$HOME/.cache/git-span/cargo-target}/git-span/build\" cargo bench --locked",
19
+ "udeps": "bash scripts/with-target-lock.sh shared env CARGO_BUILD_JOBS=1 CARGO_TARGET_DIR=\"${GIT_SPAN_CARGO_TARGET_ROOT:-$HOME/.cache/git-span/cargo-target}/git-span/udeps\" cargo +nightly udeps"
20
+ },
21
+ "files": [
22
+ "bin/git-span.exe",
23
+ "man/",
24
+ "scripts/postinstall.js"
25
+ ],
26
+ "optionalDependencies": {
27
+ "git-span-darwin-arm64": "1.0.131",
28
+ "git-span-darwin-x64": "1.0.131",
29
+ "git-span-linux-arm64": "1.0.131",
30
+ "git-span-linux-x64": "1.0.131",
31
+ "git-span-win32-x64": "1.0.131"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/goodfoot-io/git-span.git"
36
+ }
37
+ }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+
7
+ const PLATFORM_MAP = {
8
+ linux: 'linux',
9
+ darwin: 'darwin',
10
+ win32: 'win32'
11
+ };
12
+
13
+ const ARCH_MAP = {
14
+ x64: 'x64',
15
+ arm64: 'arm64'
16
+ };
17
+
18
+ function fail(message, error) {
19
+ console.error(message);
20
+ if (error) {
21
+ console.error(error.message);
22
+ }
23
+ process.exit(1);
24
+ }
25
+
26
+ function buildFromSource(destBinary, binaryName) {
27
+ const cargoToml = path.join(__dirname, '..', 'Cargo.toml');
28
+ if (!fs.existsSync(cargoToml)) {
29
+ fail(`git-span: Binary not found at ${destBinary} and no Cargo.toml available to build from source.`);
30
+ }
31
+
32
+ console.log(`git-span: Prebuilt binary missing; building from source via cargo...`);
33
+
34
+ const targetDir = path.join(__dirname, '..', 'target-cache', 'build');
35
+ const result = spawnSync('cargo', ['build', '--release', '--manifest-path', cargoToml], {
36
+ stdio: 'inherit',
37
+ env: { ...process.env, CARGO_BUILD_JOBS: '1', CARGO_TARGET_DIR: targetDir }
38
+ });
39
+
40
+ if (result.error || result.status !== 0) {
41
+ fail(
42
+ `git-span: Failed to build binary from source. Install Rust/cargo or publish the platform package.`,
43
+ result.error
44
+ );
45
+ }
46
+
47
+ const builtBinary = path.join(targetDir, 'release', binaryName);
48
+ if (!fs.existsSync(builtBinary)) {
49
+ fail(`git-span: cargo build succeeded but binary not found at ${builtBinary}.`);
50
+ }
51
+
52
+ fs.mkdirSync(path.dirname(destBinary), { recursive: true });
53
+ fs.copyFileSync(builtBinary, destBinary);
54
+ fs.chmodSync(destBinary, 0o755);
55
+ }
56
+
57
+ function isInsideSourceTree() {
58
+ // Walk up from this script looking for a `.git` entry. If found, this is
59
+ // the git-span source repo (developer doing `yarn install`), not
60
+ // a consumer install under node_modules. Mutating the tracked `bin/git-span`
61
+ // shim in that case dirties the working tree and — if committed — ships a
62
+ // dangling symlink in the published tarball (see git-span-v1.0.25 regression).
63
+ let dir = path.resolve(__dirname, '..');
64
+ for (let i = 0; i < 16; i++) {
65
+ if (fs.existsSync(path.join(dir, '.git'))) return true;
66
+ const parent = path.dirname(dir);
67
+ if (parent === dir) return false;
68
+ if (path.basename(parent) === 'node_modules') return false;
69
+ dir = parent;
70
+ }
71
+ return false;
72
+ }
73
+
74
+ function main() {
75
+ if (isInsideSourceTree()) {
76
+ console.log('git-span: postinstall skipped (running inside source tree).');
77
+ return;
78
+ }
79
+
80
+ const platform = PLATFORM_MAP[process.platform];
81
+ const arch = ARCH_MAP[process.arch];
82
+
83
+ if (!platform || !arch) {
84
+ fail(`git-span: No prebuilt binary available for ${process.platform}-${process.arch}.`);
85
+ }
86
+
87
+ const packageName = `git-span-${platform}-${arch}`;
88
+ const sourceBinaryName = process.platform === 'win32' ? 'git-span.exe' : 'git-span';
89
+
90
+ let packageDir;
91
+ try {
92
+ packageDir = path.dirname(require.resolve(`${packageName}/package.json`));
93
+ } catch (error) {
94
+ fail(`git-span: Required platform package ${packageName} not found.`, error);
95
+ }
96
+
97
+ const sourceBinary = path.join(packageDir, 'bin', sourceBinaryName);
98
+ if (!fs.existsSync(sourceBinary)) {
99
+ buildFromSource(sourceBinary, sourceBinaryName);
100
+ }
101
+
102
+ // Always write to bin/git-span.exe — the package.json `bin` field points
103
+ // here on every platform. The `.exe` extension plus a no-shebang stub makes
104
+ // npm's cmd-shim (generated at install time, before this postinstall) emit
105
+ // a direct exec on Windows; Unix executes by mode bit + ELF/Mach-O header
106
+ // and ignores the `.exe` suffix entirely. No resident Node process — the
107
+ // `git-span` command execs the native binary directly. Same pattern as
108
+ // Bun's and Claude Code's npm packages.
109
+ const binGitSpan = path.join(__dirname, '..', 'bin', 'git-span.exe');
110
+
111
+ // Hardlink first (instant, zero extra disk for the ~16MB binary; src and
112
+ // dest are both under node_modules so same-filesystem is the common case),
113
+ // then fall back to copy across devices / on link-permission errors.
114
+ try {
115
+ fs.linkSync(sourceBinary, binGitSpan);
116
+ } catch (linkError) {
117
+ if (linkError.code === 'EEXIST') {
118
+ try {
119
+ fs.unlinkSync(binGitSpan);
120
+ fs.linkSync(sourceBinary, binGitSpan);
121
+ } catch {
122
+ fs.copyFileSync(sourceBinary, binGitSpan);
123
+ }
124
+ } else if (linkError.code === 'EXDEV' || linkError.code === 'EPERM') {
125
+ fs.copyFileSync(sourceBinary, binGitSpan);
126
+ } else {
127
+ try {
128
+ fs.copyFileSync(sourceBinary, binGitSpan);
129
+ } catch (copyError) {
130
+ fail(
131
+ `git-span: Could not install binary from ${sourceBinary} to ${binGitSpan}.`,
132
+ new Error(`link failed: ${linkError.message}\ncopy failed: ${copyError.message}`)
133
+ );
134
+ }
135
+ }
136
+ }
137
+ if (process.platform !== 'win32') {
138
+ fs.chmodSync(binGitSpan, 0o755);
139
+ }
140
+
141
+ console.log(`git-span: Installed git-span from ${packageName}`);
142
+ }
143
+
144
+ main();