@m2015agg/git-skill 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.
Files changed (112) hide show
  1. package/dist/commands/approve.d.ts +2 -0
  2. package/dist/commands/approve.js +56 -0
  3. package/dist/commands/approve.js.map +1 -0
  4. package/dist/commands/blame.d.ts +2 -0
  5. package/dist/commands/blame.js +139 -0
  6. package/dist/commands/blame.js.map +1 -0
  7. package/dist/commands/capture.d.ts +2 -0
  8. package/dist/commands/capture.js +68 -0
  9. package/dist/commands/capture.js.map +1 -0
  10. package/dist/commands/coupling.d.ts +2 -0
  11. package/dist/commands/coupling.js +48 -0
  12. package/dist/commands/coupling.js.map +1 -0
  13. package/dist/commands/cron.d.ts +2 -0
  14. package/dist/commands/cron.js +70 -0
  15. package/dist/commands/cron.js.map +1 -0
  16. package/dist/commands/decisions.d.ts +2 -0
  17. package/dist/commands/decisions.js +62 -0
  18. package/dist/commands/decisions.js.map +1 -0
  19. package/dist/commands/diff-summary.d.ts +2 -0
  20. package/dist/commands/diff-summary.js +151 -0
  21. package/dist/commands/diff-summary.js.map +1 -0
  22. package/dist/commands/docs.d.ts +3 -0
  23. package/dist/commands/docs.js +38 -0
  24. package/dist/commands/docs.js.map +1 -0
  25. package/dist/commands/doctor.d.ts +2 -0
  26. package/dist/commands/doctor.js +60 -0
  27. package/dist/commands/doctor.js.map +1 -0
  28. package/dist/commands/embed.d.ts +2 -0
  29. package/dist/commands/embed.js +81 -0
  30. package/dist/commands/embed.js.map +1 -0
  31. package/dist/commands/enrich.d.ts +2 -0
  32. package/dist/commands/enrich.js +148 -0
  33. package/dist/commands/enrich.js.map +1 -0
  34. package/dist/commands/experts.d.ts +2 -0
  35. package/dist/commands/experts.js +55 -0
  36. package/dist/commands/experts.js.map +1 -0
  37. package/dist/commands/hotspots.d.ts +2 -0
  38. package/dist/commands/hotspots.js +46 -0
  39. package/dist/commands/hotspots.js.map +1 -0
  40. package/dist/commands/init.d.ts +2 -0
  41. package/dist/commands/init.js +91 -0
  42. package/dist/commands/init.js.map +1 -0
  43. package/dist/commands/install.d.ts +2 -0
  44. package/dist/commands/install.js +59 -0
  45. package/dist/commands/install.js.map +1 -0
  46. package/dist/commands/metric.d.ts +2 -0
  47. package/dist/commands/metric.js +26 -0
  48. package/dist/commands/metric.js.map +1 -0
  49. package/dist/commands/regression.d.ts +2 -0
  50. package/dist/commands/regression.js +166 -0
  51. package/dist/commands/regression.js.map +1 -0
  52. package/dist/commands/release-notes.d.ts +2 -0
  53. package/dist/commands/release-notes.js +244 -0
  54. package/dist/commands/release-notes.js.map +1 -0
  55. package/dist/commands/search.d.ts +2 -0
  56. package/dist/commands/search.js +93 -0
  57. package/dist/commands/search.js.map +1 -0
  58. package/dist/commands/snapshot.d.ts +6 -0
  59. package/dist/commands/snapshot.js +154 -0
  60. package/dist/commands/snapshot.js.map +1 -0
  61. package/dist/commands/timeline.d.ts +2 -0
  62. package/dist/commands/timeline.js +78 -0
  63. package/dist/commands/timeline.js.map +1 -0
  64. package/dist/commands/trends.d.ts +2 -0
  65. package/dist/commands/trends.js +92 -0
  66. package/dist/commands/trends.js.map +1 -0
  67. package/dist/commands/uninstall.d.ts +2 -0
  68. package/dist/commands/uninstall.js +79 -0
  69. package/dist/commands/uninstall.js.map +1 -0
  70. package/dist/commands/update.d.ts +2 -0
  71. package/dist/commands/update.js +59 -0
  72. package/dist/commands/update.js.map +1 -0
  73. package/dist/commands/why.d.ts +2 -0
  74. package/dist/commands/why.js +111 -0
  75. package/dist/commands/why.js.map +1 -0
  76. package/dist/index.d.ts +2 -0
  77. package/dist/index.js +65 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/templates/walkthrough.d.ts +1 -0
  80. package/dist/templates/walkthrough.js +13 -0
  81. package/dist/templates/walkthrough.js.map +1 -0
  82. package/dist/util/analytics.d.ts +32 -0
  83. package/dist/util/analytics.js +308 -0
  84. package/dist/util/analytics.js.map +1 -0
  85. package/dist/util/claude-md.d.ts +2 -0
  86. package/dist/util/claude-md.js +41 -0
  87. package/dist/util/claude-md.js.map +1 -0
  88. package/dist/util/config.d.ts +21 -0
  89. package/dist/util/config.js +26 -0
  90. package/dist/util/config.js.map +1 -0
  91. package/dist/util/db.d.ts +3 -0
  92. package/dist/util/db.js +183 -0
  93. package/dist/util/db.js.map +1 -0
  94. package/dist/util/detect.d.ts +2 -0
  95. package/dist/util/detect.js +14 -0
  96. package/dist/util/detect.js.map +1 -0
  97. package/dist/util/embedding.d.ts +6 -0
  98. package/dist/util/embedding.js +48 -0
  99. package/dist/util/embedding.js.map +1 -0
  100. package/dist/util/git.d.ts +45 -0
  101. package/dist/util/git.js +191 -0
  102. package/dist/util/git.js.map +1 -0
  103. package/dist/util/hooks.d.ts +3 -0
  104. package/dist/util/hooks.js +43 -0
  105. package/dist/util/hooks.js.map +1 -0
  106. package/dist/util/metrics.d.ts +2 -0
  107. package/dist/util/metrics.js +42 -0
  108. package/dist/util/metrics.js.map +1 -0
  109. package/dist/util/search-hybrid.d.ts +10 -0
  110. package/dist/util/search-hybrid.js +33 -0
  111. package/dist/util/search-hybrid.js.map +1 -0
  112. package/package.json +35 -0
@@ -0,0 +1,48 @@
1
+ import { readConfig } from "./config.js";
2
+ export async function generateEmbedding(text) {
3
+ const config = readConfig();
4
+ if (!config?.embedding?.enabled || !config.embedding.url)
5
+ return null;
6
+ const apiKey = resolveEnvVar(config.embedding.apiKey);
7
+ try {
8
+ const response = await fetch(config.embedding.url, {
9
+ method: "POST",
10
+ headers: { "Content-Type": "application/json", ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}) },
11
+ body: JSON.stringify({ model: config.embedding.model, input: text }),
12
+ });
13
+ if (!response.ok)
14
+ return null;
15
+ const data = await response.json();
16
+ // OpenAI format
17
+ if (data.data?.[0]?.embedding)
18
+ return { vector: data.data[0].embedding, model: config.embedding.model };
19
+ // Ollama /api/embed format (embeddings array)
20
+ if (data.embeddings?.[0])
21
+ return { vector: data.embeddings[0], model: config.embedding.model };
22
+ // Ollama legacy format
23
+ if (data.embedding)
24
+ return { vector: data.embedding, model: config.embedding.model };
25
+ return null;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ export function cosineSimilarity(a, b) {
32
+ if (a.length !== b.length)
33
+ return 0;
34
+ let dot = 0, normA = 0, normB = 0;
35
+ for (let i = 0; i < a.length; i++) {
36
+ dot += a[i] * b[i];
37
+ normA += a[i] * a[i];
38
+ normB += b[i] * b[i];
39
+ }
40
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB) || 1);
41
+ }
42
+ function resolveEnvVar(val) {
43
+ if (!val)
44
+ return undefined;
45
+ const match = val.match(/^\$\{(.+)\}$/);
46
+ return match ? process.env[match[1]] : val;
47
+ }
48
+ //# sourceMappingURL=embedding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedding.js","sourceRoot":"","sources":["../../src/util/embedding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ export interface GitCommit {
2
+ hash: string;
3
+ message: string;
4
+ author: string;
5
+ email: string;
6
+ timestamp: string;
7
+ branch: string;
8
+ parentHash: string;
9
+ mergeCommit: boolean;
10
+ insertions: number;
11
+ deletions: number;
12
+ filesChanged: number;
13
+ }
14
+ export interface GitFile {
15
+ path: string;
16
+ status: string;
17
+ insertions: number;
18
+ deletions: number;
19
+ oldPath: string | null;
20
+ }
21
+ export interface GitBranch {
22
+ name: string;
23
+ headHash: string;
24
+ isActive: boolean;
25
+ }
26
+ export interface GitTag {
27
+ name: string;
28
+ hash: string;
29
+ timestamp: string;
30
+ message: string;
31
+ }
32
+ interface LogOptions {
33
+ limit?: number;
34
+ since?: string;
35
+ until?: string;
36
+ author?: string;
37
+ branch?: string;
38
+ }
39
+ export declare function isGitRepo(dir: string): boolean;
40
+ export declare function getLastCommitHash(cwd: string): string;
41
+ export declare function getLog(cwd: string, opts?: LogOptions): GitCommit[];
42
+ export declare function getDiffTree(cwd: string, hash: string): GitFile[];
43
+ export declare function getBranches(cwd: string): GitBranch[];
44
+ export declare function getTags(cwd: string): GitTag[];
45
+ export {};
@@ -0,0 +1,191 @@
1
+ import { execSync } from "child_process";
2
+ const EXEC_OPTS = { timeout: 60000, encoding: "utf-8", maxBuffer: 50 * 1024 * 1024 };
3
+ const SEP = "---GIT-SKILL-SEP---";
4
+ export function isGitRepo(dir) {
5
+ try {
6
+ execSync("git rev-parse --git-dir", { ...EXEC_OPTS, cwd: dir, stdio: "pipe" });
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ export function getLastCommitHash(cwd) {
14
+ try {
15
+ return execSync("git rev-parse HEAD", { ...EXEC_OPTS, cwd }).trim();
16
+ }
17
+ catch {
18
+ return "";
19
+ }
20
+ }
21
+ export function getLog(cwd, opts = {}) {
22
+ try {
23
+ const args = [
24
+ "git",
25
+ "log",
26
+ `--format=${SEP}%H${SEP}%s${SEP}%an${SEP}%ae${SEP}%aI${SEP}%P`,
27
+ "--numstat",
28
+ ];
29
+ if (opts.limit)
30
+ args.push(`-n`, String(opts.limit));
31
+ if (opts.since)
32
+ args.push(`--since=${opts.since}`);
33
+ if (opts.until)
34
+ args.push(`--until=${opts.until}`);
35
+ if (opts.author)
36
+ args.push(`--author=${opts.author}`);
37
+ if (opts.branch)
38
+ args.push(opts.branch);
39
+ const raw = execSync(args.join(" "), { ...EXEC_OPTS, cwd });
40
+ // Split on the SEP that begins each commit header
41
+ // Each commit block starts with SEP
42
+ const blocks = raw.split(new RegExp(`^${SEP}`, "m")).filter(Boolean);
43
+ const commits = [];
44
+ for (const block of blocks) {
45
+ // First line is the fields (after stripping the leading SEP from split)
46
+ const newlineIdx = block.indexOf("\n");
47
+ const headerLine = newlineIdx === -1 ? block : block.slice(0, newlineIdx);
48
+ const numstatSection = newlineIdx === -1 ? "" : block.slice(newlineIdx + 1);
49
+ // Fields: hash SEP message SEP author SEP email SEP timestamp SEP parentHash
50
+ const parts = headerLine.split(SEP);
51
+ if (parts.length < 6)
52
+ continue;
53
+ const [hash, message, author, email, timestamp, parentHashRaw] = parts;
54
+ const parentHash = parentHashRaw?.trim() ?? "";
55
+ const mergeCommit = parentHash.includes(" ");
56
+ // Parse numstat lines: "<insertions>\t<deletions>\t<filepath>"
57
+ let insertions = 0;
58
+ let deletions = 0;
59
+ let filesChanged = 0;
60
+ const numstatLines = numstatSection
61
+ .split("\n")
62
+ .map((l) => l.trim())
63
+ .filter(Boolean);
64
+ for (const line of numstatLines) {
65
+ const parts = line.split("\t");
66
+ if (parts.length < 3)
67
+ continue;
68
+ const ins = parseInt(parts[0], 10);
69
+ const del = parseInt(parts[1], 10);
70
+ if (!isNaN(ins))
71
+ insertions += ins;
72
+ if (!isNaN(del))
73
+ deletions += del;
74
+ filesChanged++;
75
+ }
76
+ commits.push({
77
+ hash: hash.trim(),
78
+ message: message.trim(),
79
+ author: author.trim(),
80
+ email: email.trim(),
81
+ timestamp: timestamp.trim(),
82
+ branch: "",
83
+ parentHash,
84
+ mergeCommit,
85
+ insertions,
86
+ deletions,
87
+ filesChanged,
88
+ });
89
+ }
90
+ return commits;
91
+ }
92
+ catch {
93
+ return [];
94
+ }
95
+ }
96
+ export function getDiffTree(cwd, hash) {
97
+ try {
98
+ const numstatRaw = execSync(`git diff-tree --no-commit-id -r --numstat -M ${hash}`, { ...EXEC_OPTS, cwd });
99
+ const nameStatusRaw = execSync(`git diff-tree --no-commit-id -r --name-status -M ${hash}`, { ...EXEC_OPTS, cwd });
100
+ // Build a map from path -> status
101
+ const statusMap = new Map();
102
+ for (const line of nameStatusRaw.split("\n")) {
103
+ const trimmed = line.trim();
104
+ if (!trimmed)
105
+ continue;
106
+ const parts = trimmed.split("\t");
107
+ if (parts.length < 2)
108
+ continue;
109
+ const statusCode = parts[0][0]; // e.g. R100 -> R, M -> M
110
+ if (parts.length === 3) {
111
+ // Rename: old\tnew
112
+ statusMap.set(parts[2], { status: statusCode, oldPath: parts[1] });
113
+ }
114
+ else {
115
+ statusMap.set(parts[1], { status: statusCode, oldPath: null });
116
+ }
117
+ }
118
+ const files = [];
119
+ for (const line of numstatRaw.split("\n")) {
120
+ const trimmed = line.trim();
121
+ if (!trimmed)
122
+ continue;
123
+ const parts = trimmed.split("\t");
124
+ if (parts.length < 3)
125
+ continue;
126
+ const ins = parseInt(parts[0], 10);
127
+ const del = parseInt(parts[1], 10);
128
+ // For renames, numstat uses "old => new" path or two tabs
129
+ const filePath = parts[2];
130
+ const statusInfo = statusMap.get(filePath) ?? { status: "M", oldPath: null };
131
+ files.push({
132
+ path: filePath,
133
+ status: statusInfo.status,
134
+ insertions: isNaN(ins) ? 0 : ins,
135
+ deletions: isNaN(del) ? 0 : del,
136
+ oldPath: statusInfo.oldPath,
137
+ });
138
+ }
139
+ return files;
140
+ }
141
+ catch {
142
+ return [];
143
+ }
144
+ }
145
+ export function getBranches(cwd) {
146
+ try {
147
+ const raw = execSync(`git branch -a --format="%(refname:short)\t%(HEAD)\t%(objectname:short)"`, { ...EXEC_OPTS, cwd });
148
+ const branches = [];
149
+ for (const line of raw.split("\n")) {
150
+ const trimmed = line.trim().replace(/^"|"$/g, "");
151
+ if (!trimmed)
152
+ continue;
153
+ const parts = trimmed.split("\t");
154
+ if (parts.length < 3)
155
+ continue;
156
+ const name = parts[0].trim();
157
+ const isActive = parts[1].trim() === "*";
158
+ const headHash = parts[2].trim();
159
+ branches.push({ name, headHash, isActive });
160
+ }
161
+ return branches;
162
+ }
163
+ catch {
164
+ return [];
165
+ }
166
+ }
167
+ export function getTags(cwd) {
168
+ try {
169
+ const raw = execSync(`git tag -l --format="%(refname:short)\t%(objectname:short)\t%(creatordate:iso-strict)\t%(subject)"`, { ...EXEC_OPTS, cwd });
170
+ const tags = [];
171
+ for (const line of raw.split("\n")) {
172
+ const trimmed = line.trim().replace(/^"|"$/g, "");
173
+ if (!trimmed)
174
+ continue;
175
+ const parts = trimmed.split("\t");
176
+ if (parts.length < 4)
177
+ continue;
178
+ tags.push({
179
+ name: parts[0].trim(),
180
+ hash: parts[1].trim(),
181
+ timestamp: parts[2].trim(),
182
+ message: parts[3].trim(),
183
+ });
184
+ }
185
+ return tags;
186
+ }
187
+ catch {
188
+ return [];
189
+ }
190
+ }
191
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/util/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function installHook(gitDir: string): "installed" | "updated" | "already_installed";
2
+ export declare function removeHook(gitDir: string): "removed" | "not_found";
3
+ export declare function hasHook(gitDir: string): boolean;
@@ -0,0 +1,43 @@
1
+ import { readFileSync, writeFileSync, existsSync, chmodSync, unlinkSync } from "fs";
2
+ import { join } from "path";
3
+ const HOOK_MARKER = "# git-skill hook";
4
+ const HOOK_CONTENT = `\n${HOOK_MARKER}\ngit-skill capture --hook 2>/dev/null &\n`;
5
+ export function installHook(gitDir) {
6
+ const hookPath = join(gitDir, "hooks", "post-commit");
7
+ if (existsSync(hookPath)) {
8
+ const content = readFileSync(hookPath, "utf-8");
9
+ if (content.includes(HOOK_MARKER))
10
+ return "already_installed";
11
+ writeFileSync(hookPath, content + HOOK_CONTENT);
12
+ chmodSync(hookPath, 0o755);
13
+ return "updated";
14
+ }
15
+ writeFileSync(hookPath, `#!/bin/sh\n${HOOK_CONTENT}`);
16
+ chmodSync(hookPath, 0o755);
17
+ return "installed";
18
+ }
19
+ export function removeHook(gitDir) {
20
+ const hookPath = join(gitDir, "hooks", "post-commit");
21
+ if (!existsSync(hookPath))
22
+ return "not_found";
23
+ const content = readFileSync(hookPath, "utf-8");
24
+ if (!content.includes(HOOK_MARKER))
25
+ return "not_found";
26
+ const lines = content.split("\n");
27
+ const filtered = lines.filter(line => !line.includes(HOOK_MARKER) && !line.includes("git-skill capture"));
28
+ const result = filtered.join("\n").trim();
29
+ if (result === "#!/bin/sh" || result === "") {
30
+ unlinkSync(hookPath);
31
+ }
32
+ else {
33
+ writeFileSync(hookPath, result + "\n");
34
+ }
35
+ return "removed";
36
+ }
37
+ export function hasHook(gitDir) {
38
+ const hookPath = join(gitDir, "hooks", "post-commit");
39
+ if (!existsSync(hookPath))
40
+ return false;
41
+ return readFileSync(hookPath, "utf-8").includes(HOOK_MARKER);
42
+ }
43
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/util/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,WAAW,GAAG,kBAAkB,CAAC;AACvC,MAAM,YAAY,GAAG,KAAK,WAAW,4CAA4C,CAAC;AAElF,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IAEtD,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChD,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,OAAO,mBAAmB,CAAC;QAC9D,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,YAAY,CAAC,CAAC;QAChD,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,aAAa,CAAC,QAAQ,EAAE,cAAc,YAAY,EAAE,CAAC,CAAC;IACtD,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,WAAW,CAAC;IAC9C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,WAAW,CAAC;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC1G,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAC5C,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvB,CAAC;SAAM,CAAC;QACN,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAc;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type Database from "better-sqlite3";
2
+ export declare function computeBuiltinMetrics(db: Database.Database): void;
@@ -0,0 +1,42 @@
1
+ export function computeBuiltinMetrics(db) {
2
+ // Clear existing built-in metrics
3
+ db.prepare("DELETE FROM metric_values WHERE metric_name IN ('revert_rate', 'fix_on_fix_rate', 'scope_creep', 'time_to_commit', 'same_file_churn', 'dependency_churn')").run();
4
+ const commits = db.prepare("SELECT hash, message, timestamp, files_changed FROM commits ORDER BY timestamp ASC").all();
5
+ const insert = db.prepare("INSERT INTO metric_values (commit_hash, metric_name, value, captured_at) VALUES (?, ?, ?, ?)");
6
+ const now = new Date().toISOString();
7
+ const window = 10;
8
+ db.transaction(() => {
9
+ for (let i = 0; i < commits.length; i++) {
10
+ const c = commits[i];
11
+ const windowStart = Math.max(0, i - window + 1);
12
+ const windowCommits = commits.slice(windowStart, i + 1);
13
+ // Revert rate (rolling)
14
+ const reverts = windowCommits.filter(wc => /revert/i.test(wc.message)).length;
15
+ insert.run(c.hash, "revert_rate", reverts / windowCommits.length, now);
16
+ // Fix-on-fix rate
17
+ const fixes = windowCommits.filter(wc => /\bfix\b/i.test(wc.message)).length;
18
+ insert.run(c.hash, "fix_on_fix_rate", fixes / windowCommits.length, now);
19
+ // Scope creep (files per commit)
20
+ insert.run(c.hash, "scope_creep", c.files_changed, now);
21
+ // Time-to-commit (minutes since previous)
22
+ if (i > 0) {
23
+ const minutes = (new Date(c.timestamp).getTime() - new Date(commits[i - 1].timestamp).getTime()) / 60000;
24
+ insert.run(c.hash, "time_to_commit", minutes, now);
25
+ }
26
+ // Same-file churn (files edited 3+ times in last 5 commits)
27
+ if (i >= 2) {
28
+ const recentHashes = commits.slice(Math.max(0, i - 4), i + 1).map(rc => rc.hash);
29
+ const placeholders = recentHashes.map(() => "?").join(",");
30
+ const churnFiles = db.prepare(`
31
+ SELECT file_path, COUNT(*) as cnt FROM commit_files
32
+ WHERE commit_hash IN (${placeholders}) GROUP BY file_path HAVING cnt >= 3
33
+ `).all(...recentHashes);
34
+ insert.run(c.hash, "same_file_churn", churnFiles.length, now);
35
+ }
36
+ else {
37
+ insert.run(c.hash, "same_file_churn", 0, now);
38
+ }
39
+ }
40
+ })();
41
+ }
42
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/util/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type Database from "better-sqlite3";
2
+ export interface SearchResult {
3
+ hash: string;
4
+ type: string;
5
+ path: string;
6
+ message: string;
7
+ detail: string;
8
+ score: number;
9
+ }
10
+ export declare function searchBM25(db: Database.Database, query: string, limit?: number): SearchResult[];
@@ -0,0 +1,33 @@
1
+ export function searchBM25(db, query, limit = 20) {
2
+ const safeQuery = query.replace(/['"]/g, "").trim();
3
+ if (!safeQuery)
4
+ return [];
5
+ try {
6
+ // Try FTS5 phrase + prefix match
7
+ const results = db.prepare(`
8
+ SELECT hash, type, path, message, detail, rank as score
9
+ FROM history_fts WHERE history_fts MATCH ? ORDER BY rank LIMIT ?
10
+ `).all(`"${safeQuery}"*`, limit);
11
+ if (results.length > 0)
12
+ return results;
13
+ // Fallback: individual terms
14
+ const terms = safeQuery.split(/\s+/).map(t => `"${t}"*`).join(" OR ");
15
+ return db.prepare(`
16
+ SELECT hash, type, path, message, detail, rank as score
17
+ FROM history_fts WHERE history_fts MATCH ? ORDER BY rank LIMIT ?
18
+ `).all(terms, limit);
19
+ }
20
+ catch {
21
+ // Final fallback: LIKE on base tables (FTS5 doesn't support LIKE)
22
+ const commitMatches = db.prepare(`
23
+ SELECT hash, 'commit' as type, '' as path, message, '' as detail, 0 as score
24
+ FROM commits WHERE message LIKE ? LIMIT ?
25
+ `).all(`%${safeQuery}%`, limit);
26
+ const fileMatches = db.prepare(`
27
+ SELECT commit_hash as hash, 'file' as type, file_path as path, '' as message, status as detail, 0 as score
28
+ FROM commit_files WHERE file_path LIKE ? LIMIT ?
29
+ `).all(`%${safeQuery}%`, limit);
30
+ return [...commitMatches, ...fileMatches].slice(0, limit);
31
+ }
32
+ }
33
+ //# sourceMappingURL=search-hybrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-hybrid.js","sourceRoot":"","sources":["../../src/util/search-hybrid.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,UAAU,CAAC,EAAqB,EAAE,KAAa,EAAE,KAAK,GAAG,EAAE;IACzE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAE1B,IAAI,CAAC;QACH,iCAAiC;QACjC,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC;;;KAG1B,CAAC,CAAC,GAAG,CAAC,IAAI,SAAS,IAAI,EAAE,KAAK,CAAmB,CAAC;QACnD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC;QAEvC,6BAA6B;QAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtE,OAAO,EAAE,CAAC,OAAO,CAAC;;;KAGjB,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAmB,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,kEAAkE;QAClE,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC;;;KAGhC,CAAC,CAAC,GAAG,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,CAAmB,CAAC;QAClD,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC;;;KAG9B,CAAC,CAAC,GAAG,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,CAAmB,CAAC;QAClD,OAAO,CAAC,GAAG,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@m2015agg/git-skill",
3
+ "version": "0.1.0",
4
+ "description": "Git history intelligence for LLMs — institutional memory over codebase evolution",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "git-skill": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest",
15
+ "test:coverage": "vitest run --coverage",
16
+ "link": "npm run build && npm link",
17
+ "prepublishOnly": "npm run build",
18
+ "postinstall": "node -e \"process.stdout.write('\\n git-skill installed! Run: git-skill install\\n\\n')\""
19
+ },
20
+ "dependencies": {
21
+ "better-sqlite3": "^12.8.0",
22
+ "commander": "^13.1.0",
23
+ "simple-statistics": "^7.8.7"
24
+ },
25
+ "devDependencies": {
26
+ "@types/better-sqlite3": "^7.6.13",
27
+ "@types/node": "^25.5.0",
28
+ "typescript": "^5.7.0",
29
+ "vitest": "^3.0.0"
30
+ },
31
+ "files": ["dist", "README.md"],
32
+ "keywords": ["git", "history", "ai", "llm", "cli", "intelligence"],
33
+ "author": "m2015agg",
34
+ "license": "MIT"
35
+ }