claude-attribution 1.0.0

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,431 @@
1
+ # claude-attribution
2
+
3
+ > **Hey team** — we've built a tool that automatically tracks and documents AI contribution in your PRs so you don't have to do it manually.
4
+ >
5
+ > **One-time setup:**
6
+ > ```bash
7
+ > npm install -g claude-attribution
8
+ > claude-attribution install ~/Code/your-repo
9
+ > git add .claude/settings.json .gitignore && git commit -m "chore: install claude-attribution hooks"
10
+ > ```
11
+ > From then on, just work normally. After each `git commit` you'll see a one-line attribution summary in your terminal. When you're ready to open a PR, run `/pr` in Claude Code (or `claude-attribution pr "feat: your title"`) — it fills in the metrics automatically, no copy-paste needed.
12
+ >
13
+ > **Using Copilot?** The tool still works for tracking Claude usage alongside Copilot. Copilot line-level attribution isn't supported yet — for Copilot-specific stats, use the [GitHub Copilot usage dashboard](https://github.com/organizations/DTS-Productivity-Engineering/settings/copilot/seat_management). Both tools' org-level data flows into the VP Datadog dashboard automatically on every PR merge.
14
+ >
15
+ > **Requirements:** [Bun](https://bun.sh) (preferred) or Node 18+, and `gh` (GitHub CLI) authenticated for the `/pr` command.
16
+
17
+ ---
18
+
19
+ AI code attribution tracking for Claude Code. Measures which lines in a commit were written by Claude vs. a human — using checkpoint snapshots and line-level SHA-256 comparison, not gross write-operation counts.
20
+
21
+ ---
22
+
23
+ ## For Repo Maintainers: Installing Into a Repo
24
+
25
+ ### Prerequisites
26
+
27
+ - [Bun](https://bun.sh) is the preferred runtime (`curl -fsSL https://bun.sh/install | bash`)
28
+ - If Bun isn't available, `tsx` also works: `npm install -g tsx`
29
+ The tool auto-detects the runtime (bun → tsx → npx tsx).
30
+
31
+ ### One-time setup (per developer machine)
32
+
33
+ **Option A — npm (recommended):**
34
+
35
+ ```bash
36
+ npm install -g claude-attribution
37
+ ```
38
+
39
+ **Option B — clone (if npm isn't available):**
40
+
41
+ ```bash
42
+ git clone git@github.com:DTS-Productivity-Engineering/claude-attribution.git ~/Code/claude-attribution
43
+ cd ~/Code/claude-attribution
44
+ bun install
45
+ ```
46
+
47
+ ### Install into a repo (per repo, per developer)
48
+
49
+ **npm install:**
50
+ ```bash
51
+ claude-attribution install ~/Code/your-repo
52
+ ```
53
+
54
+ **Clone install:**
55
+ ```bash
56
+ bun ~/Code/claude-attribution/src/setup/install.ts ~/Code/your-repo
57
+ ```
58
+
59
+ The installer makes four changes to the target repo:
60
+
61
+ **`.claude/settings.json`** — merges five Claude Code hooks:
62
+
63
+ | Event | Hook | What it does |
64
+ |-------|------|--------------|
65
+ | PreToolUse (Edit/Write/MultiEdit/NotebookEdit) | `pre-tool-use.ts` | Snapshot file content before Claude touches it |
66
+ | PostToolUse (all tools) | `post-tool-use.ts` | Snapshot file after Claude writes + log tool call |
67
+ | SubagentStart / SubagentStop | `subagent.ts` | Log subagent activity |
68
+ | Stop | `stop.ts` | No-op; registered for future use |
69
+
70
+ **`.git/hooks/post-commit`** — runs attribution after every commit. If the repo already has a `post-commit` hook from Husky or another tool, the call is appended rather than replacing it. For Lefthook repos, the installer prints the config snippet to add manually.
71
+
72
+ **`.git/hooks/pre-push`** — pushes `refs/notes/claude-attribution` to origin whenever you push, so GitHub Actions can read the notes on PR merge. The installer also runs `git config --add remote.origin.push refs/notes/claude-attribution:refs/notes/claude-attribution` so `git push origin` includes notes automatically.
73
+
74
+ **`.claude/commands/`** — installs two slash commands:
75
+ - `/metrics` — generate a PR metrics report
76
+ - `/start` — mark the start of a new Jira ticket session
77
+
78
+ **`.gitignore`** — adds `.claude/logs/` so tool usage logs don't end up in version control.
79
+
80
+ ### Committing the settings change
81
+
82
+ The `.claude/settings.json` change should be committed to the repo so all developers get the hooks automatically. The `.git/hooks/post-commit` hook is local only (`.git/` is not committed) — each developer runs the installer once.
83
+
84
+ ```bash
85
+ # After running the installer:
86
+ git add .claude/settings.json .gitignore
87
+ git commit -m "chore: install claude-attribution hooks"
88
+ ```
89
+
90
+ ### Re-installing after moving this directory
91
+
92
+ If you move `~/Code/claude-attribution` to a different path, re-run the installer — it updates the absolute paths in `settings.json` and the git hook:
93
+
94
+ ```bash
95
+ # npm install:
96
+ claude-attribution install ~/Code/your-repo
97
+
98
+ # clone install:
99
+ bun src/setup/install.ts ~/Code/your-repo
100
+ ```
101
+
102
+ ### Uninstalling
103
+
104
+ To remove claude-attribution from a repo:
105
+
106
+ ```bash
107
+ # npm install:
108
+ claude-attribution uninstall ~/Code/your-repo
109
+
110
+ # clone install:
111
+ bun ~/Code/claude-attribution/src/setup/uninstall.ts ~/Code/your-repo
112
+ ```
113
+
114
+ This removes hooks from `.claude/settings.json`, removes `.git/hooks/post-commit` and `.git/hooks/pre-push`, and removes the `/metrics` and `/start` slash commands. Attribution state (`.claude/attribution-state/`) and logs (`.claude/logs/`) are left in place.
115
+
116
+ ---
117
+
118
+ ## For Developers: Day-to-Day Usage
119
+
120
+ ### What changes for you
121
+
122
+ Nothing changes in how you work. The hooks run silently in the background. After each `git commit` you'll see a one-line summary in the terminal:
123
+
124
+ ```
125
+ [claude-attribution] a3f1b2c — 142 AI / 38 human / 4 mixed lines (77% AI)
126
+ ```
127
+
128
+ That's it. No other interaction required.
129
+
130
+ ### Starting a new Jira ticket
131
+
132
+ When you check out a new branch for a new ticket, run `/start` in Claude Code:
133
+
134
+ ```
135
+ /start
136
+ ```
137
+
138
+ This writes a timestamp to `.claude/attribution-state/session-start`. The `/metrics` command uses this to scope tool counts, token usage, and attribution data to only the activity after this marker — so a long-running Claude Code session doesn't inflate metrics across multiple tickets.
139
+
140
+ ### Creating a PR with metrics
141
+
142
+ When you're ready to create a PR, use the `/pr` slash command in Claude Code. It collects metrics and creates the PR in one step — no copy-paste needed:
143
+
144
+ ```
145
+ /pr "feat: COMM-1234 add user authentication"
146
+ ```
147
+
148
+ Or run directly:
149
+
150
+ ```bash
151
+ claude-attribution pr "feat: COMM-1234 add user authentication"
152
+ claude-attribution pr "feat: my feature" --draft # Open as draft
153
+ claude-attribution pr "feat: my feature" --base develop # Different base branch
154
+ claude-attribution pr # Title from branch name
155
+ ```
156
+
157
+ This reads `.github/PULL_REQUEST_TEMPLATE.md` if it exists, injects the metrics block at a `<!-- claude-attribution metrics -->` placeholder (or appends if missing), and creates the PR via `gh`. Requires `gh` to be installed and authenticated (`gh auth status`).
158
+
159
+ ### Viewing metrics without creating a PR
160
+
161
+ To see the metrics output without creating a PR, use `/metrics` or run directly:
162
+
163
+ ```bash
164
+ claude-attribution metrics
165
+ # or: bun ~/Code/claude-attribution/src/metrics/calculate.ts
166
+ ```
167
+
168
+ The output is markdown you paste into your PR description:
169
+
170
+ ```markdown
171
+ ## Claude Code Metrics
172
+
173
+ **Session ID:** `abc-123-...`
174
+
175
+ ### Tools Used
176
+ - **Edit:** 47 calls
177
+ - **Read:** 31 calls
178
+ ...
179
+
180
+ ### Model Usage
181
+ | Model | API Calls | Input Tokens | Output Tokens | Cache Tokens |
182
+ ...
183
+
184
+ **Human prompts (steering effort):** 12
185
+
186
+ ### Code Attribution
187
+ - **AI-authored lines:** 142
188
+ - **Human-authored lines:** 38
189
+ - **Mixed lines (AI wrote, human modified):** 4
190
+ - **Total committed lines:** 184
191
+ - **AI contribution:** ~77%
192
+
193
+ #### Files Modified by Claude
194
+ - `src/components/Foo.tsx` — 89% AI (82 lines)
195
+ - `src/hooks/useBar.ts` — 61% AI (44 lines)
196
+ ```
197
+
198
+ ### Running with a specific session ID
199
+
200
+ If you have multiple sessions and want metrics for a specific one:
201
+
202
+ ```bash
203
+ claude-attribution metrics <session-id>
204
+ ```
205
+
206
+ Session IDs are shown in `.claude/logs/tool-usage.jsonl`.
207
+
208
+ ### Checking raw attribution data
209
+
210
+ Attribution results are stored as git notes and queryable directly:
211
+
212
+ ```bash
213
+ # View attribution for the last commit
214
+ git notes --ref=claude-attribution show HEAD
215
+
216
+ # List all attributed commits in the repo
217
+ git notes --ref=claude-attribution list
218
+ ```
219
+
220
+ Example output:
221
+
222
+ ```json
223
+ {
224
+ "commit": "a3f1b2c",
225
+ "session": "abc-123-...",
226
+ "branch": "feature/COMM-1234",
227
+ "timestamp": "2026-03-26T15:32:00.000Z",
228
+ "files": [
229
+ { "path": "src/components/Foo.tsx", "ai": 82, "human": 10, "mixed": 2, "total": 94, "pctAi": 87 }
230
+ ],
231
+ "totals": { "ai": 142, "human": 38, "mixed": 4, "total": 184, "pctAi": 77 }
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ## How Attribution Works
238
+
239
+ ### The algorithm
240
+
241
+ Every time Claude writes to a file, two snapshots are captured:
242
+
243
+ - **Before snapshot** — file content before Claude's *first* edit in the session (saved by the PreToolUse hook, preserved on subsequent edits)
244
+ - **After snapshot** — file content after Claude's *last* edit (saved by the PostToolUse hook, updated on every edit)
245
+
246
+ After you `git commit`, the post-commit hook runs and compares Claude's **last after-snapshot** for each changed file against what was actually committed, line by line.
247
+
248
+ Each committed line is classified:
249
+
250
+ | Label | Rule | Meaning |
251
+ |-------|------|---------|
252
+ | **AI** | Hash in after-snapshot, not in before-snapshot | Claude wrote this line and it survived to the commit unchanged |
253
+ | **HUMAN** | Not in after-snapshot, or existed before Claude touched the file | You wrote it, or it predates the session |
254
+ | **MIXED** | Claude wrote a line at this position but the committed content differs | Claude wrote it, you modified it before committing |
255
+
256
+ Empty lines are always HUMAN — they carry no attribution signal.
257
+
258
+ ### Worked example
259
+
260
+ Say a file originally contains two lines:
261
+
262
+ ```
263
+ before: ["const a = 1;", "const b = 2;"]
264
+ ```
265
+
266
+ Claude edits it and the after-snapshot is:
267
+
268
+ ```
269
+ after: ["const a = 1;", "const b = 2;", "const c = 3;", "export default fn;"]
270
+ ```
271
+
272
+ You then modify `fn` to `main` before committing:
273
+
274
+ ```
275
+ committed: ["const a = 1;", "const b = 2;", "const c = 3;", "export default main;"]
276
+ ```
277
+
278
+ Line-by-line classification:
279
+
280
+ | Line | Before | After | Committed | Label |
281
+ |------|--------|-------|-----------|-------|
282
+ | `const a = 1;` | ✓ | ✓ | ✓ | **HUMAN** (existed before Claude) |
283
+ | `const b = 2;` | ✓ | ✓ | ✓ | **HUMAN** (existed before Claude) |
284
+ | `const c = 3;` | ✗ | ✓ | ✓ | **AI** (Claude wrote it, committed unchanged) |
285
+ | `export default main;` | ✗ | hash differs (`fn` vs `main`) | ✓ | **MIXED** (Claude wrote `fn`, you changed to `main`) |
286
+
287
+ Result: 1 AI / 2 HUMAN / 1 MIXED = 25% AI contribution.
288
+
289
+ ### Why this is correct (unlike the previous approach)
290
+
291
+ The old `calculate-metrics.sh` summed every line Claude ever wrote across all Edit/Write operations and divided by the net git diff. If Claude edited a file four times and you rewrote it entirely, the script claimed ~100% AI. That was wrong.
292
+
293
+ This tool compares Claude's **final written state** against **what was committed**. If you rewrote it entirely, none of your lines hash-match Claude's snapshot → 0% AI. Correct.
294
+
295
+ ### Multi-commit workflow
296
+
297
+ Each commit is attributed independently at commit time (when checkpoints are freshest). The `/metrics` command aggregates across all commits on the current branch using "last-write-wins" per file: for files touched in multiple commits, the stats from the most recent commit are used. This represents the final state of each file and prevents double-counting when the same file appears in multiple commits.
298
+
299
+ Running `/start` scopes both tool/token metrics AND attribution data to commits made after the start marker. Use it when beginning a new Jira ticket to get per-ticket metrics.
300
+
301
+ ---
302
+
303
+ ## Known Limitations
304
+
305
+ - **Binary files** are skipped (git shows them as binary, content can't be compared line-by-line).
306
+
307
+ - **Identical-line content** — the algorithm is set-based. Two lines with the same trimmed content produce the same hash. If Claude and a human both write a line like `}` (a closing brace) and `}` didn't exist in the before-snapshot, the tool attributes it as AI regardless of who actually wrote it. This is a conservative bias toward AI for identical content — an acceptable trade-off since lines with identical content are indistinguishable without tracking insertion history.
308
+
309
+ - **MIXED detection is positional (best-effort)** — MIXED is detected by checking whether Claude's i-th line was changed in the committed file. If a human inserts or deletes lines above position `i`, the commit's line positions shift while the after-snapshot's positions don't, causing false MIXED classifications. MIXED is most accurate when human edits are small in-place tweaks (e.g., changing a value on a line Claude wrote) rather than bulk insertions or deletions.
310
+
311
+ - **Sessions without checkpoints** — commits made outside an active Claude session (no `current-session` file, or checkpoints already cleaned up) are attributed 100% HUMAN. This is correct.
312
+
313
+ - **`git commit --amend`** — when a commit is amended, the original SHA is replaced but the old git note (pointing to the now-orphaned SHA) remains in the notes object store. `/metrics` reads notes across the entire branch, so an amended commit's lines may appear twice. Avoid amending published commits; if you do, run `/metrics` knowing totals may be slightly inflated for that commit's files.
314
+
315
+ - **Multiple sessions on the same branch** — if two different Claude sessions both touch the same file, the last write wins. Only the session whose `after` checkpoint was written most recently contributes to attribution for that file.
316
+
317
+ - **Hash collisions** — uses a 16-character SHA-256 prefix (2^64 space); collision threshold is ~4 billion unique lines (negligible in practice).
318
+
319
+ ---
320
+
321
+ ## Security Model
322
+
323
+ - **The package directory is a trusted location** — installed hooks embed an absolute path to `bin/claude-attribution` at install time. Compromise of the package directory would affect all repos that have hooks installed. Keep it in a location with normal user-only permissions (npm global installs and `~/Code/` clones are both fine).
324
+
325
+ - **`session_id` is validated** — checkpoint paths include the session ID as a directory component. The tool validates session IDs against `[a-zA-Z0-9_-]{1,128}` before use, preventing path traversal attacks.
326
+
327
+ - **Checkpoint directories are `chmod 0700`** — `/tmp/claude-attribution/<session>/` is created with owner-only read/write so other users on shared machines cannot access file snapshots.
328
+
329
+ - **`.claude/logs/` is gitignored** — the installer adds `.claude/logs/` to `.gitignore` automatically. Tool usage logs contain session IDs and tool inputs that should not be committed to version control.
330
+
331
+ ---
332
+
333
+ ## Checkpoints and Temp Files
334
+
335
+ Checkpoints are stored in `/tmp/claude-attribution/<session-id>/` as JSON files. They are intentionally **not** cleaned up when your Claude Code session ends — you may close Claude Code and commit later, and the checkpoints need to survive until you do. The OS clears `/tmp/` on reboot, which is sufficient. Stale checkpoints from old sessions are harmless.
336
+
337
+ To force re-detection of the TypeScript runtime (e.g., after installing Bun): `rm /tmp/claude-attribution-runtime`
338
+
339
+ ---
340
+
341
+ ## VP Dashboard (Datadog)
342
+
343
+ Attribution data is pushed to Datadog automatically on every PR merge via GitHub Actions (`.github/workflows/claude-attribution.yml`). No developer holds the API key — it lives in an org-level GHA secret.
344
+
345
+ **Metrics pushed on each merged PR:**
346
+
347
+ | Metric | Description |
348
+ |--------|-------------|
349
+ | `claude_attribution.ai_lines` | Lines written by Claude and committed unchanged |
350
+ | `claude_attribution.human_lines` | Lines written or left unchanged by the developer |
351
+ | `claude_attribution.total_lines` | Total committed lines in the PR |
352
+ | `claude_attribution.pct_ai` | Percentage of lines attributed to Claude |
353
+ | `github_copilot.acceptance_rate` | Org-level Copilot suggestion acceptance rate |
354
+ | `github_copilot.lines_accepted` | Copilot lines accepted org-wide |
355
+ | `github_copilot.lines_suggested` | Copilot lines suggested org-wide |
356
+
357
+ All metrics are tagged `repo:`, `pr:`, `branch:`, `author:`, `tool:` — enabling side-by-side Claude vs. Copilot comparison on a single dashboard.
358
+
359
+ > **Important limitation — Copilot metrics are org-level aggregates, not per-PR or per-developer:**
360
+ > GitHub's Copilot usage API returns org-wide daily totals (suggestions shown, lines accepted, acceptance rate). These are the same numbers regardless of which PR triggered the push. The Copilot rows in Datadog reflect organization-wide Copilot activity at the time of the push — they cannot be scoped to a specific PR, branch, or developer. In contrast, `claude_attribution.*` metrics are per-PR and per-file, derived from git notes written at commit time.
361
+ >
362
+ > If you need per-seat or per-repo Copilot breakdowns, use the GitHub Copilot usage dashboard directly. The org-level Copilot numbers here are useful as a trend signal alongside Claude attribution data.
363
+
364
+ **Required secrets (set once at org level):**
365
+ - `DATADOG_API_KEY` — Datadog API key
366
+ - `DATADOG_SITE` variable — e.g. `datadoghq.com`
367
+
368
+ **Future:** When Faros is purchased, ADMPLAT-9609 will provide a Faros pipeline. Switching destinations is a one-line change to `src/export/pr-summary.ts` — the data collection, notes format, and GHA trigger all stay the same.
369
+
370
+ ---
371
+
372
+ ## OTel Traces (optional)
373
+
374
+ claude-attribution can export OpenTelemetry traces to any OTLP-compatible backend (Datadog APM, Jaeger, Tempo, etc.). This is **opt-in** — set one env var to enable it.
375
+
376
+ ### Env vars
377
+
378
+ | Variable | Required | Example | Description |
379
+ |----------|----------|---------|-------------|
380
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Yes (to enable) | `http://localhost:4318` | OTLP/HTTP receiver. Unset = OTel disabled. |
381
+ | `OTEL_EXPORTER_OTLP_HEADERS` | No | `DD-Api-Key=abc123` | Comma-separated `key=value` headers (required for Datadog OTLP intake, not needed for local Agent) |
382
+ | `OTEL_SERVICE_NAME` | No | `claude-code` | Service name in APM. Defaults to `claude-code`. |
383
+
384
+ **Datadog via local Agent** (Agent with OTLP enabled on port 4318 — no API key needed):
385
+ ```bash
386
+ export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
387
+ ```
388
+
389
+ **Datadog OTLP intake** (direct, without Agent):
390
+ ```bash
391
+ export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.datadoghq.com
392
+ export OTEL_EXPORTER_OTLP_HEADERS=DD-Api-Key=<your-api-key>
393
+ ```
394
+
395
+ ### What gets traced
396
+
397
+ Two span types are emitted:
398
+
399
+ **`tool_call/{toolName}`** (child span) — one per tool call. Emitted at the end of each `PostToolUse` hook invocation. Attributes: `tool.name`, `file.path` (if applicable), `session.id`.
400
+
401
+ **`claude-session`** (root span) — one per commit. Emitted at the end of the `post-commit` hook. Covers `startTime` (first tool call in the session) → commit time. Attributes: `session.id`, `git.branch`, `git.commit`, `attribution.ai_lines`, `attribution.human_lines`, `attribution.mixed_lines`, `attribution.total_lines`, `attribution.pct_ai`.
402
+
403
+ ### How context is persisted
404
+
405
+ Each hook invocation is a short-lived process that exits immediately. The trace context (`traceId`, `rootSpanId`, `startTime`, etc.) is persisted to `.claude/attribution-state/otel-context.json` and read by each subsequent hook so all spans share the same trace. The context file is deleted when the root session span is exported at commit time.
406
+
407
+ ---
408
+
409
+ ## Troubleshooting
410
+
411
+ **"No attribution data found" in the metrics output**
412
+
413
+ The post-commit hook may not have run. Check:
414
+ 1. Is `bun` (or `tsx`) on your PATH in a git hook context? Run `which bun` from your shell, then check if that path is in `.git/hooks/post-commit`.
415
+ 2. Did you run `bun src/setup/install.ts <repo>` for this specific repo?
416
+ 3. Check `.claude/logs/attribution.jsonl` — if it's empty, the hook isn't firing.
417
+
418
+ **Attribution is 0% AI even though Claude wrote everything**
419
+
420
+ Most likely the commit happened in a different terminal session from where Claude is running. The `current-session` file in `.claude/attribution-state/` needs to match the session that created the checkpoints. Start Claude, make your changes, and commit without switching sessions.
421
+
422
+ **The hook is slowing down commits**
423
+
424
+ The post-commit hook runs attribution after the commit is already recorded — it can't block the commit. If you see a pause, it's likely runtime startup time on a cold start (~100ms for Bun, ~300ms for npx tsx). Subsequent runs use the cached runtime from `/tmp/claude-attribution-runtime` and are faster.
425
+
426
+ **Runtime cache is stale (wrong runtime used after installing Bun)**
427
+
428
+ Delete the cache file to force re-detection:
429
+ ```bash
430
+ rm /tmp/claude-attribution-runtime
431
+ ```
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ # Resolve symlinks portably (macOS readlink doesn't support -f)
3
+ SELF="$0"
4
+ while [ -L "$SELF" ]; do
5
+ SELF_DIR="$(cd "$(dirname "$SELF")" && pwd)"
6
+ SELF="$SELF_DIR/$(readlink "$SELF")"
7
+ done
8
+ PACKAGE_ROOT="$(cd "$(dirname "$SELF")/.." && pwd)"
9
+ exec "$PACKAGE_ROOT/src/run.sh" "$PACKAGE_ROOT/src/cli.ts" "$@"
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "claude-attribution",
3
+ "version": "1.0.0",
4
+ "description": "AI code attribution tracking for Claude Code sessions — checkpoint-based line diff approach",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-attribution": "bin/claude-attribution"
8
+ },
9
+ "files": [
10
+ "src/",
11
+ "bin/"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "scripts": {
17
+ "install-repo": "bun src/setup/install.ts",
18
+ "test": "bun test",
19
+ "typecheck": "tsc --noEmit",
20
+ "prepare": "chmod 755 bin/claude-attribution"
21
+ },
22
+ "devDependencies": {
23
+ "bun-types": "^1.3.11",
24
+ "typescript": "^6.0.2"
25
+ }
26
+ }