claude-spp 0.1.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/LICENSE.txt +21 -0
- package/README.md +147 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +164 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/loader.d.ts +29 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +81 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +84 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +104 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/git/history.d.ts +55 -0
- package/dist/git/history.d.ts.map +1 -0
- package/dist/git/history.js +376 -0
- package/dist/git/history.js.map +1 -0
- package/dist/hooks/file-matcher.d.ts +22 -0
- package/dist/hooks/file-matcher.d.ts.map +1 -0
- package/dist/hooks/file-matcher.js +86 -0
- package/dist/hooks/file-matcher.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/pre-tool-use.d.ts +35 -0
- package/dist/hooks/pre-tool-use.d.ts.map +1 -0
- package/dist/hooks/pre-tool-use.js +132 -0
- package/dist/hooks/pre-tool-use.js.map +1 -0
- package/dist/hooks/system-prompt.d.ts +9 -0
- package/dist/hooks/system-prompt.d.ts.map +1 -0
- package/dist/hooks/system-prompt.js +88 -0
- package/dist/hooks/system-prompt.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +25 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +274 -0
- package/dist/init.js.map +1 -0
- package/dist/stats.d.ts +40 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +146 -0
- package/dist/stats.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAW;IAC3B,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,gBAAgB,EAAE;IAChF,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACzF,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACzF,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,oBAAoB,EAAE;IACtF,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,mBAAmB,EAAE;CACrF,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AACxE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AAG1E;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;AAG/D;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAiC;IAChE,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,eAAe;CACvB,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAgC;IAC9D,MAAM,EAAE,eAAe;IACvB,OAAO,EAAE,aAAa;IACtB,OAAO,EAAE,UAAU;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAmB;IACtD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACvD,KAAK,SAAS;YACZ,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC3D,KAAK,SAAS;YACZ,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,2CAA2C;IAC3C,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAElC,oBAAoB;IACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAE/C,4CAA4C;IAC5C,WAAW,EAAE,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC;IAEjD,sDAAsD;IACtD,YAAY,EAAE,kBAAkB,CAAC,OAAO,CAAC,SAAS,CAAC;IAEnD,8DAA8D;IAC9D,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAElC,8DAA8D;IAC9D,qEAAqE;IACrE,4CAA4C;IAC5C,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC3C,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAW;IACpC,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,CAAC;IACP,WAAW,EAAE,SAAS;IACtB,YAAY,EAAE,SAAS;CACxB,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB;AACtE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,WAAW;IACX,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC,UAAU,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line count result
|
|
3
|
+
*/
|
|
4
|
+
export interface LineCounts {
|
|
5
|
+
humanLines: number;
|
|
6
|
+
claudeLines: number;
|
|
7
|
+
humanCommits: number;
|
|
8
|
+
claudeCommits: number;
|
|
9
|
+
fromCache: boolean;
|
|
10
|
+
commitsScanned: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get the current HEAD commit hash (exported version)
|
|
14
|
+
*/
|
|
15
|
+
export declare function getHeadCommitHash(projectPath: string): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Get total number of commits in the repo
|
|
18
|
+
*/
|
|
19
|
+
export declare function getTotalCommitCount(projectPath: string): number;
|
|
20
|
+
/**
|
|
21
|
+
* Commit info for display
|
|
22
|
+
*/
|
|
23
|
+
export interface CommitInfo {
|
|
24
|
+
shortHash: string;
|
|
25
|
+
title: string;
|
|
26
|
+
date: Date;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get info about a specific commit
|
|
30
|
+
* Returns null if the commit doesn't exist
|
|
31
|
+
*/
|
|
32
|
+
export declare function getCommitInfo(projectPath: string, commitHash: string): CommitInfo | null;
|
|
33
|
+
/**
|
|
34
|
+
* Get the hash of the Nth commit (1-indexed, chronological order)
|
|
35
|
+
* Returns null if there aren't enough commits
|
|
36
|
+
*/
|
|
37
|
+
export declare function getNthCommitHash(projectPath: string, n: number): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Clear the cache
|
|
40
|
+
*/
|
|
41
|
+
export declare function clearCache(projectPath: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Calculate line counts from git history with caching
|
|
44
|
+
*/
|
|
45
|
+
export declare function getLineCounts(projectPath: string): LineCounts;
|
|
46
|
+
/**
|
|
47
|
+
* Get line counts with optional filters
|
|
48
|
+
* @param since If null, no time filter. If Date, filters commits after this date.
|
|
49
|
+
* @param afterCommit If provided, only includes commits after this commit hash (exclusive).
|
|
50
|
+
*/
|
|
51
|
+
export declare function getLineCountsWithWindow(projectPath: string, options: {
|
|
52
|
+
since: Date | null;
|
|
53
|
+
afterCommit?: string | null;
|
|
54
|
+
}): LineCounts;
|
|
55
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/git/history.ts"],"names":[],"mappings":"AAqBA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AA4BD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEpE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAU/D;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAiBxF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB9E;AAyDD;;GAEG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAKpD;AAkHD;;GAEG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,CA6E7D;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IAAE,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC3D,UAAU,CA6CZ"}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const CACHE_FILE = ".claude-spp/.git_history_cache.json";
|
|
6
|
+
/**
|
|
7
|
+
* Cache schema for git history line counts
|
|
8
|
+
*/
|
|
9
|
+
const GitHistoryCacheSchema = z.object({
|
|
10
|
+
userEmail: z.string(),
|
|
11
|
+
lastCommit: z.string(),
|
|
12
|
+
humanLines: z.number().int().min(0),
|
|
13
|
+
claudeLines: z.number().int().min(0),
|
|
14
|
+
humanCommits: z.number().int().min(0),
|
|
15
|
+
claudeCommits: z.number().int().min(0),
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Check if we're in a git repository
|
|
19
|
+
*/
|
|
20
|
+
function isGitRepo(projectPath) {
|
|
21
|
+
try {
|
|
22
|
+
execSync("git rev-parse --git-dir", { cwd: projectPath, stdio: "ignore" });
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the current HEAD commit hash
|
|
31
|
+
*/
|
|
32
|
+
function getHeadCommit(projectPath) {
|
|
33
|
+
try {
|
|
34
|
+
return execSync("git rev-parse HEAD", {
|
|
35
|
+
cwd: projectPath,
|
|
36
|
+
encoding: "utf-8",
|
|
37
|
+
}).trim();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get the current HEAD commit hash (exported version)
|
|
45
|
+
*/
|
|
46
|
+
export function getHeadCommitHash(projectPath) {
|
|
47
|
+
return getHeadCommit(projectPath);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get total number of commits in the repo
|
|
51
|
+
*/
|
|
52
|
+
export function getTotalCommitCount(projectPath) {
|
|
53
|
+
try {
|
|
54
|
+
const output = execSync("git rev-list --count HEAD", {
|
|
55
|
+
cwd: projectPath,
|
|
56
|
+
encoding: "utf-8",
|
|
57
|
+
});
|
|
58
|
+
return parseInt(output.trim(), 10) || 0;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get info about a specific commit
|
|
66
|
+
* Returns null if the commit doesn't exist
|
|
67
|
+
*/
|
|
68
|
+
export function getCommitInfo(projectPath, commitHash) {
|
|
69
|
+
// Format: short hash, subject (title), ISO date
|
|
70
|
+
const output = execSync(`git log -1 --format="%h%x00%s%x00%cI" ${commitHash}`, {
|
|
71
|
+
cwd: projectPath,
|
|
72
|
+
encoding: "utf-8",
|
|
73
|
+
}).trim();
|
|
74
|
+
if (!output) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const [shortHash, title, dateStr] = output.split("\0");
|
|
78
|
+
return {
|
|
79
|
+
shortHash,
|
|
80
|
+
title,
|
|
81
|
+
date: new Date(dateStr),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the hash of the Nth commit (1-indexed, chronological order)
|
|
86
|
+
* Returns null if there aren't enough commits
|
|
87
|
+
*/
|
|
88
|
+
export function getNthCommitHash(projectPath, n) {
|
|
89
|
+
try {
|
|
90
|
+
// Get all commits in chronological order (oldest first)
|
|
91
|
+
const output = execSync(`git rev-list --reverse HEAD`, {
|
|
92
|
+
cwd: projectPath,
|
|
93
|
+
encoding: "utf-8",
|
|
94
|
+
});
|
|
95
|
+
const commits = output.trim().split("\n").filter(Boolean);
|
|
96
|
+
if (commits.length >= n) {
|
|
97
|
+
return commits[n - 1]; // n is 1-indexed
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if a commit is an ancestor of HEAD
|
|
107
|
+
*/
|
|
108
|
+
function isAncestor(projectPath, commit) {
|
|
109
|
+
try {
|
|
110
|
+
execSync(`git merge-base --is-ancestor ${commit} HEAD`, {
|
|
111
|
+
cwd: projectPath,
|
|
112
|
+
stdio: "ignore",
|
|
113
|
+
});
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get the cache file path
|
|
122
|
+
*/
|
|
123
|
+
function getCachePath(projectPath) {
|
|
124
|
+
return path.join(projectPath, CACHE_FILE);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Load cache from file
|
|
128
|
+
*/
|
|
129
|
+
function loadCache(projectPath) {
|
|
130
|
+
const cachePath = getCachePath(projectPath);
|
|
131
|
+
if (!fs.existsSync(cachePath)) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const raw = fs.readFileSync(cachePath, "utf-8");
|
|
136
|
+
const json = JSON.parse(raw);
|
|
137
|
+
return GitHistoryCacheSchema.parse(json);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Save cache to file
|
|
145
|
+
*/
|
|
146
|
+
function saveCache(projectPath, cache) {
|
|
147
|
+
const cachePath = getCachePath(projectPath);
|
|
148
|
+
const sppDir = path.dirname(cachePath);
|
|
149
|
+
if (!fs.existsSync(sppDir)) {
|
|
150
|
+
fs.mkdirSync(sppDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Clear the cache
|
|
156
|
+
*/
|
|
157
|
+
export function clearCache(projectPath) {
|
|
158
|
+
const cachePath = getCachePath(projectPath);
|
|
159
|
+
if (fs.existsSync(cachePath)) {
|
|
160
|
+
fs.unlinkSync(cachePath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Parse a commit to get line counts and attribution
|
|
165
|
+
*/
|
|
166
|
+
function parseCommit(projectPath, commitHash, parentHash) {
|
|
167
|
+
// Check if commit is co-authored by Claude
|
|
168
|
+
const message = execSync(`git log -1 --format="%B" ${commitHash}`, {
|
|
169
|
+
cwd: projectPath,
|
|
170
|
+
encoding: "utf-8",
|
|
171
|
+
});
|
|
172
|
+
const isClaude = /Co-Authored-By:.*Claude/i.test(message);
|
|
173
|
+
// Get lines added in this commit
|
|
174
|
+
let linesAdded = 0;
|
|
175
|
+
let linesDeleted = 0;
|
|
176
|
+
try {
|
|
177
|
+
let diffCmd;
|
|
178
|
+
if (parentHash) {
|
|
179
|
+
diffCmd = `git diff --numstat ${parentHash} ${commitHash}`;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// First commit - diff against empty tree
|
|
183
|
+
diffCmd = `git diff --numstat 4b825dc642cb6eb9a060e54bf8d69288fbee4904 ${commitHash}`;
|
|
184
|
+
}
|
|
185
|
+
const numstat = execSync(diffCmd, {
|
|
186
|
+
cwd: projectPath,
|
|
187
|
+
encoding: "utf-8",
|
|
188
|
+
});
|
|
189
|
+
// Parse numstat output: "added\tdeleted\tfilename"
|
|
190
|
+
for (const line of numstat.split("\n")) {
|
|
191
|
+
if (!line.trim())
|
|
192
|
+
continue;
|
|
193
|
+
const parts = line.split("\t");
|
|
194
|
+
if (parts.length >= 2) {
|
|
195
|
+
const added = parseInt(parts[0], 10);
|
|
196
|
+
const deleted = parseInt(parts[1], 10);
|
|
197
|
+
if (!isNaN(added)) {
|
|
198
|
+
linesAdded += added;
|
|
199
|
+
}
|
|
200
|
+
if (!isNaN(deleted)) {
|
|
201
|
+
linesDeleted += deleted;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Ignore errors (e.g., binary files)
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
humanLines: isClaude ? 0 : linesAdded + linesDeleted,
|
|
211
|
+
claudeLines: isClaude ? linesAdded + linesDeleted : 0,
|
|
212
|
+
isClaudeCommit: isClaude,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function getCurrentGitUser(projectPath) {
|
|
216
|
+
const gitUser = execSync("git config user.email", {
|
|
217
|
+
cwd: projectPath,
|
|
218
|
+
encoding: "utf-8",
|
|
219
|
+
}).trim();
|
|
220
|
+
if (!gitUser) {
|
|
221
|
+
throw new Error("git user.email is not set.");
|
|
222
|
+
}
|
|
223
|
+
return gitUser;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get commit hashes from startCommit (exclusive) to endCommit (inclusive)
|
|
227
|
+
* Returns array of { hash, parent } objects, oldest first
|
|
228
|
+
* @param since Optional date to filter commits (git --since flag)
|
|
229
|
+
*/
|
|
230
|
+
function getCommitRange(projectPath, startCommit, endCommit, since) {
|
|
231
|
+
try {
|
|
232
|
+
const range = startCommit ? `${startCommit}..${endCommit}` : endCommit;
|
|
233
|
+
// Build git log command with optional --since flag
|
|
234
|
+
const sinceArg = since ? `--since="${since.toISOString()}"` : "";
|
|
235
|
+
const userEmail = getCurrentGitUser(projectPath);
|
|
236
|
+
const authorArg = userEmail ? `--author="${userEmail}"` : "";
|
|
237
|
+
const cmd = `git log --reverse --format="%H %P" ${authorArg} ${sinceArg} ${range}`.trim();
|
|
238
|
+
// Get commits with their parents
|
|
239
|
+
const output = execSync(cmd, {
|
|
240
|
+
cwd: projectPath,
|
|
241
|
+
encoding: "utf-8",
|
|
242
|
+
});
|
|
243
|
+
const commits = [];
|
|
244
|
+
for (const line of output.split("\n")) {
|
|
245
|
+
if (!line.trim())
|
|
246
|
+
continue;
|
|
247
|
+
const parts = line.trim().split(" ");
|
|
248
|
+
const hash = parts[0];
|
|
249
|
+
const parent = parts[1] || null;
|
|
250
|
+
commits.push({ hash, parent });
|
|
251
|
+
}
|
|
252
|
+
return commits;
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Calculate line counts from git history with caching
|
|
260
|
+
*/
|
|
261
|
+
export function getLineCounts(projectPath) {
|
|
262
|
+
// Default result for non-git repos
|
|
263
|
+
if (!isGitRepo(projectPath)) {
|
|
264
|
+
throw new Error("Must be a git repo");
|
|
265
|
+
}
|
|
266
|
+
const head = getHeadCommit(projectPath);
|
|
267
|
+
if (!head) {
|
|
268
|
+
throw new Error("Head commit not found");
|
|
269
|
+
}
|
|
270
|
+
const currentGitUser = getCurrentGitUser(projectPath);
|
|
271
|
+
// Try to load cache
|
|
272
|
+
const cache = loadCache(projectPath);
|
|
273
|
+
// Check if cache is valid and up to date
|
|
274
|
+
if (cache && cache.lastCommit === head && cache.userEmail === currentGitUser) {
|
|
275
|
+
return {
|
|
276
|
+
humanLines: cache.humanLines,
|
|
277
|
+
claudeLines: cache.claudeLines,
|
|
278
|
+
humanCommits: cache.humanCommits,
|
|
279
|
+
claudeCommits: cache.claudeCommits,
|
|
280
|
+
fromCache: true,
|
|
281
|
+
commitsScanned: 0,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
// Check if cache is valid but needs incremental update
|
|
285
|
+
let startCommit = null;
|
|
286
|
+
let humanLines = 0;
|
|
287
|
+
let claudeLines = 0;
|
|
288
|
+
let humanCommits = 0;
|
|
289
|
+
let claudeCommits = 0;
|
|
290
|
+
if (cache && cache.userEmail === currentGitUser && isAncestor(projectPath, cache.lastCommit)) {
|
|
291
|
+
// Cache is valid, start from there
|
|
292
|
+
startCommit = cache.lastCommit;
|
|
293
|
+
humanLines = cache.humanLines;
|
|
294
|
+
claudeLines = cache.claudeLines;
|
|
295
|
+
humanCommits = cache.humanCommits;
|
|
296
|
+
claudeCommits = cache.claudeCommits;
|
|
297
|
+
}
|
|
298
|
+
// Get commits to process
|
|
299
|
+
const commits = getCommitRange(projectPath, startCommit, head);
|
|
300
|
+
// Process each commit
|
|
301
|
+
for (const { hash, parent } of commits) {
|
|
302
|
+
const result = parseCommit(projectPath, hash, parent);
|
|
303
|
+
humanLines += result.humanLines;
|
|
304
|
+
claudeLines += result.claudeLines;
|
|
305
|
+
if (result.isClaudeCommit) {
|
|
306
|
+
claudeCommits++;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
humanCommits++;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Save updated cache
|
|
313
|
+
saveCache(projectPath, {
|
|
314
|
+
userEmail: currentGitUser,
|
|
315
|
+
lastCommit: head,
|
|
316
|
+
humanLines,
|
|
317
|
+
claudeLines,
|
|
318
|
+
humanCommits,
|
|
319
|
+
claudeCommits,
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
humanLines,
|
|
323
|
+
claudeLines,
|
|
324
|
+
humanCommits,
|
|
325
|
+
claudeCommits,
|
|
326
|
+
fromCache: false,
|
|
327
|
+
commitsScanned: commits.length,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get line counts with optional filters
|
|
332
|
+
* @param since If null, no time filter. If Date, filters commits after this date.
|
|
333
|
+
* @param afterCommit If provided, only includes commits after this commit hash (exclusive).
|
|
334
|
+
*/
|
|
335
|
+
export function getLineCountsWithWindow(projectPath, options) {
|
|
336
|
+
// If no filters at all, use the cached version
|
|
337
|
+
if (options.since === null && !options.afterCommit) {
|
|
338
|
+
return getLineCounts(projectPath);
|
|
339
|
+
}
|
|
340
|
+
// Filtered query bypasses cache
|
|
341
|
+
if (!isGitRepo(projectPath)) {
|
|
342
|
+
throw new Error("Must be a git repo");
|
|
343
|
+
}
|
|
344
|
+
const head = getHeadCommit(projectPath);
|
|
345
|
+
if (!head) {
|
|
346
|
+
throw new Error("Head commit not found");
|
|
347
|
+
}
|
|
348
|
+
// Get commits with optional start commit filter
|
|
349
|
+
const startCommit = options.afterCommit ?? null;
|
|
350
|
+
const commits = getCommitRange(projectPath, startCommit, head, options.since ?? undefined);
|
|
351
|
+
let humanLines = 0;
|
|
352
|
+
let claudeLines = 0;
|
|
353
|
+
let humanCommits = 0;
|
|
354
|
+
let claudeCommits = 0;
|
|
355
|
+
// Process each commit
|
|
356
|
+
for (const { hash, parent } of commits) {
|
|
357
|
+
const result = parseCommit(projectPath, hash, parent);
|
|
358
|
+
humanLines += result.humanLines;
|
|
359
|
+
claudeLines += result.claudeLines;
|
|
360
|
+
if (result.isClaudeCommit) {
|
|
361
|
+
claudeCommits++;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
humanCommits++;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
humanLines,
|
|
369
|
+
claudeLines,
|
|
370
|
+
humanCommits,
|
|
371
|
+
claudeCommits,
|
|
372
|
+
fromCache: false,
|
|
373
|
+
commitsScanned: commits.length,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/git/history.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a file path to be relative to the project root
|
|
3
|
+
* Handles both absolute and relative paths
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeFilePath(filePath: string, projectPath: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Check if a file path matches a pattern
|
|
8
|
+
* Pattern can be:
|
|
9
|
+
* - Exact path: "src/test.ts"
|
|
10
|
+
* - Directory prefix: "src/components/" (matches files in that dir)
|
|
11
|
+
* - Glob pattern: "src/**\/*.ts", "src/*.ts"
|
|
12
|
+
*/
|
|
13
|
+
export declare function fileMatchesPattern(filePath: string, pattern: string, projectPath: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Check if a file matches any of the given patterns
|
|
16
|
+
*/
|
|
17
|
+
export declare function fileMatchesPatterns(filePath: string, patterns: string[], projectPath: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Check if a file path is within the .claude-spp directory
|
|
20
|
+
*/
|
|
21
|
+
export declare function isSppInternalFile(filePath: string, projectPath: string): boolean;
|
|
22
|
+
//# sourceMappingURL=file-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-matcher.d.ts","sourceRoot":"","sources":["../../src/hooks/file-matcher.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAe/E;AA2BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAClB,OAAO,CA6BT;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAAE,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAIT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAGhF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Normalize a file path to be relative to the project root
|
|
4
|
+
* Handles both absolute and relative paths
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeFilePath(filePath, projectPath) {
|
|
7
|
+
// If already relative, return as-is
|
|
8
|
+
if (!path.isAbsolute(filePath)) {
|
|
9
|
+
return filePath;
|
|
10
|
+
}
|
|
11
|
+
// Make absolute path relative to project
|
|
12
|
+
const relativePath = path.relative(projectPath, filePath);
|
|
13
|
+
// If the path goes outside the project (starts with ..), return original
|
|
14
|
+
if (relativePath.startsWith("..")) {
|
|
15
|
+
return filePath;
|
|
16
|
+
}
|
|
17
|
+
return relativePath;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Convert a simple glob pattern to a regex
|
|
21
|
+
* Supports: * (any chars except /), ** (any chars including /), ? (single char)
|
|
22
|
+
*/
|
|
23
|
+
function globToRegex(pattern) {
|
|
24
|
+
let regex = pattern
|
|
25
|
+
// Escape special regex chars except * and ?
|
|
26
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
27
|
+
// **/ at the start or after / should match zero or more directories
|
|
28
|
+
.replace(/\*\*\//g, "<<<GLOBSTAR_SLASH>>>")
|
|
29
|
+
// Remaining ** matches anything
|
|
30
|
+
.replace(/\*\*/g, "<<<GLOBSTAR>>>")
|
|
31
|
+
// * matches anything except path separators
|
|
32
|
+
.replace(/\*/g, "[^/]*")
|
|
33
|
+
// ? matches single character
|
|
34
|
+
.replace(/\?/g, ".")
|
|
35
|
+
// **/ should match zero or more directories (including empty)
|
|
36
|
+
.replace(/<<<GLOBSTAR_SLASH>>>/g, "(?:.*\\/)?")
|
|
37
|
+
// ** matches anything
|
|
38
|
+
.replace(/<<<GLOBSTAR>>>/g, ".*");
|
|
39
|
+
// Anchor to start and end
|
|
40
|
+
return new RegExp(`^${regex}$`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if a file path matches a pattern
|
|
44
|
+
* Pattern can be:
|
|
45
|
+
* - Exact path: "src/test.ts"
|
|
46
|
+
* - Directory prefix: "src/components/" (matches files in that dir)
|
|
47
|
+
* - Glob pattern: "src/**\/*.ts", "src/*.ts"
|
|
48
|
+
*/
|
|
49
|
+
export function fileMatchesPattern(filePath, pattern, projectPath) {
|
|
50
|
+
// Normalize the file path
|
|
51
|
+
const normalizedFile = normalizeFilePath(filePath, projectPath);
|
|
52
|
+
// Normalize the pattern (remove leading ./)
|
|
53
|
+
let normalizedPattern = pattern.replace(/^\.\//, "");
|
|
54
|
+
// If pattern ends with /, it's a directory prefix - match anything inside
|
|
55
|
+
if (normalizedPattern.endsWith("/")) {
|
|
56
|
+
return normalizedFile.startsWith(normalizedPattern);
|
|
57
|
+
}
|
|
58
|
+
// If pattern contains glob characters, use glob matching
|
|
59
|
+
if (normalizedPattern.includes("*") || normalizedPattern.includes("?")) {
|
|
60
|
+
const regex = globToRegex(normalizedPattern);
|
|
61
|
+
return regex.test(normalizedFile);
|
|
62
|
+
}
|
|
63
|
+
// Otherwise, exact match or directory prefix match
|
|
64
|
+
if (normalizedFile === normalizedPattern) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
// Also match if pattern is a directory and file is inside it
|
|
68
|
+
if (normalizedFile.startsWith(normalizedPattern + "/")) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if a file matches any of the given patterns
|
|
75
|
+
*/
|
|
76
|
+
export function fileMatchesPatterns(filePath, patterns, projectPath) {
|
|
77
|
+
return patterns.some((pattern) => fileMatchesPattern(filePath, pattern, projectPath));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if a file path is within the .claude-spp directory
|
|
81
|
+
*/
|
|
82
|
+
export function isSppInternalFile(filePath, projectPath) {
|
|
83
|
+
const normalizedFile = normalizeFilePath(filePath, projectPath);
|
|
84
|
+
return normalizedFile.startsWith(".claude-spp/") || normalizedFile === ".claude-spp";
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=file-matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-matcher.js","sourceRoot":"","sources":["../../src/hooks/file-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,WAAmB;IACrE,oCAAoC;IACpC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,yCAAyC;IACzC,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAE1D,yEAAyE;IACzE,IAAI,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,OAAe;IAClC,IAAI,KAAK,GAAG,OAAO;QACjB,4CAA4C;SAC3C,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC;QACrC,oEAAoE;SACnE,OAAO,CAAC,SAAS,EAAE,sBAAsB,CAAC;QAC3C,gCAAgC;SAC/B,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC;QACnC,4CAA4C;SAC3C,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC;QACxB,6BAA6B;SAC5B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;QACpB,8DAA8D;SAC7D,OAAO,CAAC,uBAAuB,EAAE,YAAY,CAAC;QAC/C,sBAAsB;SACrB,OAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;IAEpC,0BAA0B;IAC1B,OAAO,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,OAAe,EACf,WAAmB;IAEnB,0BAA0B;IAC1B,MAAM,cAAc,GAAG,iBAAiB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAEhE,4CAA4C;IAC5C,IAAI,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAErD,0EAA0E;IAC1E,IAAI,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,OAAO,cAAc,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACtD,CAAC;IAED,yDAAyD;IACzD,IAAI,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvE,MAAM,KAAK,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAAC;QAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACpC,CAAC;IAED,mDAAmD;IACnD,IAAI,cAAc,KAAK,iBAAiB,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6DAA6D;IAC7D,IAAI,cAAc,CAAC,UAAU,CAAC,iBAAiB,GAAG,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,QAAkB,EAClB,WAAmB;IAEnB,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAC/B,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,WAAW,CAAC,CACnD,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,WAAmB;IACrE,MAAM,cAAc,GAAG,iBAAiB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAChE,OAAO,cAAc,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,cAAc,KAAK,aAAa,CAAC;AACvF,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { generateSystemPrompt, generateStatusLine } from "./system-prompt.js";
|
|
2
|
+
export { preToolUseHook, runPreToolUseHook, type PreToolUseHookInput, type PreToolUseHookOutput, } from "./pre-tool-use.js";
|
|
3
|
+
export { normalizeFilePath, fileMatchesPattern, fileMatchesPatterns, isSppInternalFile, } from "./file-matcher.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { generateSystemPrompt, generateStatusLine } from "./system-prompt.js";
|
|
2
|
+
export { preToolUseHook, runPreToolUseHook, } from "./pre-tool-use.js";
|
|
3
|
+
export { normalizeFilePath, fileMatchesPattern, fileMatchesPatterns, isSppInternalFile, } from "./file-matcher.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EACL,cAAc,EACd,iBAAiB,GAGlB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook input from Claude Code (actual format)
|
|
3
|
+
*/
|
|
4
|
+
export interface PreToolUseHookInput {
|
|
5
|
+
session_id?: string;
|
|
6
|
+
transcript_path?: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
permission_mode?: string;
|
|
9
|
+
hook_event_name?: string;
|
|
10
|
+
tool_name: string;
|
|
11
|
+
tool_input: Record<string, unknown>;
|
|
12
|
+
tool_use_id?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Hook output to Claude Code (correct format)
|
|
16
|
+
*/
|
|
17
|
+
export interface PreToolUseHookOutput {
|
|
18
|
+
hookSpecificOutput: {
|
|
19
|
+
hookEventName: "PreToolUse";
|
|
20
|
+
permissionDecision: "allow" | "deny" | "ask";
|
|
21
|
+
permissionDecisionReason?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Pre-tool-use hook
|
|
26
|
+
* Called before Claude uses a tool
|
|
27
|
+
* Checks the work ratio before allowing writes
|
|
28
|
+
*/
|
|
29
|
+
export declare function preToolUseHook(input: PreToolUseHookInput): PreToolUseHookOutput;
|
|
30
|
+
/**
|
|
31
|
+
* CLI entry point for pre-tool-use hook
|
|
32
|
+
* Reads input from stdin, writes output to stdout
|
|
33
|
+
*/
|
|
34
|
+
export declare function runPreToolUseHook(): Promise<void>;
|
|
35
|
+
//# sourceMappingURL=pre-tool-use.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-tool-use.d.ts","sourceRoot":"","sources":["../../src/hooks/pre-tool-use.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,kBAAkB,EAAE;QAClB,aAAa,EAAE,YAAY,CAAC;QAC5B,kBAAkB,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;QAC7C,wBAAwB,CAAC,EAAE,MAAM,CAAC;KACnC,CAAC;CACH;AAiDD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAkD/E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAoCvD"}
|