claude-attribution 1.0.0 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-attribution",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "AI code attribution tracking for Claude Code sessions — checkpoint-based line diff approach",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -50,18 +50,40 @@ switch (cmd) {
50
50
  case "post-commit":
51
51
  await import("./attribution/commit.ts");
52
52
  break;
53
- case "pre-push":
54
- try {
55
- await execFileAsync("git", [
56
- "push",
57
- "origin",
58
- "refs/notes/claude-attribution",
59
- ]);
60
- } catch {
61
- // Ignore — notes push failure must not block git push
53
+ case "pre-push": {
54
+ // Git passes pushed refs on stdin: "<local-ref> <local-sha> <remote-ref> <remote-sha>\n..."
55
+ // Skip notes push for tag-only and notes pushes to avoid SSH connection conflicts.
56
+ const stdin = await new Promise<string>((resolve) => {
57
+ let data = "";
58
+ process.stdin.setEncoding("utf8");
59
+ process.stdin.on("data", (chunk: string) => (data += chunk));
60
+ process.stdin.on("end", () => resolve(data));
61
+ });
62
+ const isBranchPush = stdin
63
+ .trim()
64
+ .split("\n")
65
+ .filter(Boolean)
66
+ .some((line) => {
67
+ const remoteRef = line.split(" ")[2] ?? "";
68
+ return (
69
+ !remoteRef.startsWith("refs/tags/") &&
70
+ !remoteRef.startsWith("refs/notes/")
71
+ );
72
+ });
73
+ if (isBranchPush) {
74
+ try {
75
+ await execFileAsync("git", [
76
+ "push",
77
+ "origin",
78
+ "refs/notes/claude-attribution",
79
+ ]);
80
+ } catch {
81
+ // Ignore — notes push failure must not block git push
82
+ }
62
83
  }
63
84
  process.exit(0);
64
85
  break;
86
+ }
65
87
  default:
66
88
  console.error(`Unknown hook: ${rest[0]}`);
67
89
  process.exit(1);
@@ -86,7 +86,7 @@ function detectHookManager(repoRoot: string): "husky" | "lefthook" | "none" {
86
86
 
87
87
  async function installPrePushHook(repoRoot: string): Promise<void> {
88
88
  const manager = detectHookManager(repoRoot);
89
- const runLine = `"${CLI_BIN}" hook pre-push || true`;
89
+ const runLine = `claude-attribution hook pre-push || true`;
90
90
 
91
91
  if (manager === "husky") {
92
92
  const huskyHook = join(repoRoot, ".husky", "pre-push");
@@ -126,14 +126,12 @@ async function installPrePushHook(repoRoot: string): Promise<void> {
126
126
  join(ATTRIBUTION_ROOT, "src", "setup", "templates", "pre-push.sh"),
127
127
  "utf8",
128
128
  );
129
- const template = rawTemplate.replace(/\{\{CLI_BIN\}\}/g, () => CLI_BIN);
130
-
131
129
  if (existsSync(hookDest)) {
132
130
  const existing = await readFile(hookDest, "utf8");
133
131
  if (!existing.includes("claude-attribution")) {
134
132
  await writeFile(
135
133
  hookDest,
136
- existing.trimEnd() + "\n\n# claude-attribution\n" + template,
134
+ existing.trimEnd() + "\n\n# claude-attribution\n" + rawTemplate,
137
135
  );
138
136
  await chmod(hookDest, 0o755);
139
137
  return;
@@ -141,13 +139,13 @@ async function installPrePushHook(repoRoot: string): Promise<void> {
141
139
  // Already ours — replace
142
140
  }
143
141
 
144
- await writeFile(hookDest, template);
142
+ await writeFile(hookDest, rawTemplate);
145
143
  await chmod(hookDest, 0o755);
146
144
  }
147
145
 
148
146
  async function installGitHook(repoRoot: string): Promise<void> {
149
147
  const manager = detectHookManager(repoRoot);
150
- const runLine = `"${CLI_BIN}" hook post-commit || true`;
148
+ const runLine = `claude-attribution hook post-commit || true`;
151
149
 
152
150
  if (manager === "husky") {
153
151
  // Husky: add post-commit file to .husky/ directory
@@ -189,14 +187,13 @@ async function installGitHook(repoRoot: string): Promise<void> {
189
187
  join(ATTRIBUTION_ROOT, "src", "setup", "templates", "post-commit.sh"),
190
188
  "utf8",
191
189
  );
192
- const newContent = template.replace(/\{\{CLI_BIN\}\}/g, () => CLI_BIN);
193
190
 
194
191
  if (existsSync(hookDest)) {
195
192
  const existing = await readFile(hookDest, "utf8");
196
193
  if (!existing.includes("claude-attribution")) {
197
194
  await writeFile(
198
195
  hookDest,
199
- existing.trimEnd() + "\n\n# claude-attribution\n" + newContent,
196
+ existing.trimEnd() + "\n\n# claude-attribution\n" + template,
200
197
  );
201
198
  await chmod(hookDest, 0o755);
202
199
  return;
@@ -204,7 +201,7 @@ async function installGitHook(repoRoot: string): Promise<void> {
204
201
  // Already ours — replace
205
202
  }
206
203
 
207
- await writeFile(hookDest, newContent);
204
+ await writeFile(hookDest, template);
208
205
  await chmod(hookDest, 0o755);
209
206
  }
210
207
 
@@ -245,9 +242,7 @@ async function main() {
245
242
  join(ATTRIBUTION_ROOT, "src", "setup", "templates", "hooks.json"),
246
243
  "utf8",
247
244
  );
248
- const hooksConfig = JSON.parse(
249
- hooksTemplate.replace(/\{\{CLI_BIN\}\}/g, () => CLI_BIN),
250
- ) as HooksConfig;
245
+ const hooksConfig = JSON.parse(hooksTemplate) as HooksConfig;
251
246
 
252
247
  await mergeHooks(settingsPath, hooksConfig);
253
248
  console.log("✓ Merged hooks into .claude/settings.json");
@@ -288,27 +283,21 @@ async function main() {
288
283
  join(ATTRIBUTION_ROOT, "src", "setup", "templates", "metrics-command.md"),
289
284
  "utf8",
290
285
  );
291
- const metricsContent = metricsTemplate.replace(
292
- /\{\{CLI_BIN\}\}/g,
293
- () => CLI_BIN,
294
- );
295
- await writeFile(join(commandsDir, "metrics.md"), metricsContent);
286
+ await writeFile(join(commandsDir, "metrics.md"), metricsTemplate);
296
287
  console.log("✓ Installed .claude/commands/metrics.md (/metrics command)");
297
288
 
298
289
  const startTemplate = await readFile(
299
290
  join(ATTRIBUTION_ROOT, "src", "setup", "templates", "start-command.md"),
300
291
  "utf8",
301
292
  );
302
- const startContent = startTemplate.replace(/\{\{CLI_BIN\}\}/g, () => CLI_BIN);
303
- await writeFile(join(commandsDir, "start.md"), startContent);
293
+ await writeFile(join(commandsDir, "start.md"), startTemplate);
304
294
  console.log("✓ Installed .claude/commands/start.md (/start command)");
305
295
 
306
296
  const prTemplate = await readFile(
307
297
  join(ATTRIBUTION_ROOT, "src", "setup", "templates", "pr-command.md"),
308
298
  "utf8",
309
299
  );
310
- const prContent = prTemplate.replace(/\{\{CLI_BIN\}\}/g, () => CLI_BIN);
311
- await writeFile(join(commandsDir, "pr.md"), prContent);
300
+ await writeFile(join(commandsDir, "pr.md"), prTemplate);
312
301
  console.log("✓ Installed .claude/commands/pr.md (/pr command)");
313
302
 
314
303
  console.log("\nDone! claude-attribution is active for this repo.");
@@ -5,7 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "\"{{CLI_BIN}}\" hook pre-tool-use"
8
+ "command": "claude-attribution hook pre-tool-use"
9
9
  }
10
10
  ]
11
11
  }
@@ -16,7 +16,7 @@
16
16
  "hooks": [
17
17
  {
18
18
  "type": "command",
19
- "command": "\"{{CLI_BIN}}\" hook post-tool-use"
19
+ "command": "claude-attribution hook post-tool-use"
20
20
  }
21
21
  ]
22
22
  }
@@ -27,7 +27,7 @@
27
27
  "hooks": [
28
28
  {
29
29
  "type": "command",
30
- "command": "\"{{CLI_BIN}}\" hook subagent"
30
+ "command": "claude-attribution hook subagent"
31
31
  }
32
32
  ]
33
33
  }
@@ -38,7 +38,7 @@
38
38
  "hooks": [
39
39
  {
40
40
  "type": "command",
41
- "command": "\"{{CLI_BIN}}\" hook subagent"
41
+ "command": "claude-attribution hook subagent"
42
42
  }
43
43
  ]
44
44
  }
@@ -49,7 +49,7 @@
49
49
  "hooks": [
50
50
  {
51
51
  "type": "command",
52
- "command": "\"{{CLI_BIN}}\" hook stop"
52
+ "command": "claude-attribution hook stop"
53
53
  }
54
54
  ]
55
55
  }
@@ -7,7 +7,7 @@ Generate session metrics for PR documentation.
7
7
  Run the metrics calculator:
8
8
 
9
9
  ```bash
10
- "{{CLI_BIN}}" metrics
10
+ claude-attribution metrics
11
11
  ```
12
12
 
13
13
  This outputs PR-ready markdown covering:
@@ -21,7 +21,7 @@ This outputs PR-ready markdown covering:
21
21
  To run with a specific session ID:
22
22
 
23
23
  ```bash
24
- "{{CLI_BIN}}" metrics <session-id>
24
+ claude-attribution metrics <session-id>
25
25
  ```
26
26
 
27
27
  Session IDs are in `.claude/logs/tool-usage.jsonl`.
@@ -1,4 +1,4 @@
1
1
  #!/bin/sh
2
2
  # Auto-installed by claude-attribution
3
3
  # Runs attribution analysis after each git commit
4
- "{{CLI_BIN}}" hook post-commit || true
4
+ claude-attribution hook post-commit || true
@@ -5,7 +5,7 @@ Run this when you're ready to open a pull request. Collects AI metrics and creat
5
5
  ## Instructions
6
6
 
7
7
  ```bash
8
- "{{CLI_BIN}}" pr "feat: your PR title"
8
+ claude-attribution pr "feat: your PR title"
9
9
  ```
10
10
 
11
11
  This will:
@@ -17,9 +17,9 @@ This will:
17
17
  ## Options
18
18
 
19
19
  ```bash
20
- "{{CLI_BIN}}" pr "feat: title" --draft # Open as draft PR
21
- "{{CLI_BIN}}" pr "feat: title" --base develop # Target a different base branch
22
- "{{CLI_BIN}}" pr # Title derived from branch name
20
+ claude-attribution pr "feat: title" --draft # Open as draft PR
21
+ claude-attribution pr "feat: title" --base develop # Target a different base branch
22
+ claude-attribution pr # Title derived from branch name
23
23
  ```
24
24
 
25
25
  ## Requirements
@@ -1,4 +1,4 @@
1
1
  #!/bin/sh
2
2
  # Auto-installed by claude-attribution
3
3
  # Pushes attribution git notes to origin so GitHub Actions can read them
4
- "{{CLI_BIN}}" hook pre-push || true
4
+ claude-attribution hook pre-push || true
@@ -6,7 +6,7 @@ Records a start timestamp so /metrics only counts activity from this point forwa
6
6
  ## Instructions
7
7
 
8
8
  ```bash
9
- "{{CLI_BIN}}" start
9
+ claude-attribution start
10
10
  ```
11
11
 
12
12
  This writes a marker to `.claude/attribution-state/session-start` with the current