@mars167/git-ai 2.3.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 (122) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +364 -0
  3. package/README.zh-CN.md +361 -0
  4. package/assets/hooks/post-checkout +28 -0
  5. package/assets/hooks/post-merge +28 -0
  6. package/assets/hooks/pre-commit +17 -0
  7. package/assets/hooks/pre-push +29 -0
  8. package/dist/bin/git-ai.js +62 -0
  9. package/dist/src/commands/ai.js +30 -0
  10. package/dist/src/commands/checkIndex.js +19 -0
  11. package/dist/src/commands/dsr.js +156 -0
  12. package/dist/src/commands/graph.js +203 -0
  13. package/dist/src/commands/hooks.js +125 -0
  14. package/dist/src/commands/index.js +92 -0
  15. package/dist/src/commands/pack.js +31 -0
  16. package/dist/src/commands/query.js +139 -0
  17. package/dist/src/commands/semantic.js +134 -0
  18. package/dist/src/commands/serve.js +14 -0
  19. package/dist/src/commands/status.js +78 -0
  20. package/dist/src/commands/trae.js +75 -0
  21. package/dist/src/commands/unpack.js +28 -0
  22. package/dist/src/core/archive.js +91 -0
  23. package/dist/src/core/astGraph.js +127 -0
  24. package/dist/src/core/astGraphQuery.js +142 -0
  25. package/dist/src/core/cozo.js +266 -0
  26. package/dist/src/core/cpg/astLayer.js +56 -0
  27. package/dist/src/core/cpg/callGraph.js +483 -0
  28. package/dist/src/core/cpg/cfgLayer.js +490 -0
  29. package/dist/src/core/cpg/dfgLayer.js +237 -0
  30. package/dist/src/core/cpg/index.js +80 -0
  31. package/dist/src/core/cpg/types.js +108 -0
  32. package/dist/src/core/crypto.js +10 -0
  33. package/dist/src/core/dsr/generate.js +308 -0
  34. package/dist/src/core/dsr/gitContext.js +74 -0
  35. package/dist/src/core/dsr/indexMaterialize.js +106 -0
  36. package/dist/src/core/dsr/paths.js +26 -0
  37. package/dist/src/core/dsr/query.js +73 -0
  38. package/dist/src/core/dsr/snapshotParser.js +73 -0
  39. package/dist/src/core/dsr/state.js +27 -0
  40. package/dist/src/core/dsr/types.js +2 -0
  41. package/dist/src/core/embedding/fusion.js +52 -0
  42. package/dist/src/core/embedding/index.js +43 -0
  43. package/dist/src/core/embedding/parser.js +14 -0
  44. package/dist/src/core/embedding/semantic.js +254 -0
  45. package/dist/src/core/embedding/structural.js +97 -0
  46. package/dist/src/core/embedding/symbolic.js +117 -0
  47. package/dist/src/core/embedding/tokenizer.js +91 -0
  48. package/dist/src/core/embedding/types.js +2 -0
  49. package/dist/src/core/embedding.js +36 -0
  50. package/dist/src/core/git.js +49 -0
  51. package/dist/src/core/gitDiff.js +73 -0
  52. package/dist/src/core/indexCheck.js +131 -0
  53. package/dist/src/core/indexer.js +185 -0
  54. package/dist/src/core/indexerIncremental.js +303 -0
  55. package/dist/src/core/indexing/config.js +51 -0
  56. package/dist/src/core/indexing/hnsw.js +568 -0
  57. package/dist/src/core/indexing/index.js +17 -0
  58. package/dist/src/core/indexing/monitor.js +82 -0
  59. package/dist/src/core/indexing/parallel.js +252 -0
  60. package/dist/src/core/lancedb.js +111 -0
  61. package/dist/src/core/lfs.js +27 -0
  62. package/dist/src/core/log.js +62 -0
  63. package/dist/src/core/manifest.js +88 -0
  64. package/dist/src/core/parser/adapter.js +2 -0
  65. package/dist/src/core/parser/c.js +93 -0
  66. package/dist/src/core/parser/chunkRelations.js +178 -0
  67. package/dist/src/core/parser/chunker.js +274 -0
  68. package/dist/src/core/parser/go.js +98 -0
  69. package/dist/src/core/parser/java.js +80 -0
  70. package/dist/src/core/parser/markdown.js +76 -0
  71. package/dist/src/core/parser/python.js +81 -0
  72. package/dist/src/core/parser/rust.js +103 -0
  73. package/dist/src/core/parser/typescript.js +98 -0
  74. package/dist/src/core/parser/utils.js +62 -0
  75. package/dist/src/core/parser/yaml.js +53 -0
  76. package/dist/src/core/parser.js +75 -0
  77. package/dist/src/core/paths.js +10 -0
  78. package/dist/src/core/repoMap.js +164 -0
  79. package/dist/src/core/retrieval/cache.js +31 -0
  80. package/dist/src/core/retrieval/classifier.js +74 -0
  81. package/dist/src/core/retrieval/expander.js +80 -0
  82. package/dist/src/core/retrieval/fuser.js +40 -0
  83. package/dist/src/core/retrieval/index.js +32 -0
  84. package/dist/src/core/retrieval/reranker.js +304 -0
  85. package/dist/src/core/retrieval/types.js +2 -0
  86. package/dist/src/core/retrieval/weights.js +42 -0
  87. package/dist/src/core/search.js +41 -0
  88. package/dist/src/core/sq8.js +65 -0
  89. package/dist/src/core/symbolSearch.js +143 -0
  90. package/dist/src/core/types.js +2 -0
  91. package/dist/src/core/workspace.js +116 -0
  92. package/dist/src/mcp/server.js +794 -0
  93. package/docs/README.md +44 -0
  94. package/docs/cross-encoder.md +157 -0
  95. package/docs/embedding.md +158 -0
  96. package/docs/logo.png +0 -0
  97. package/docs/windows-setup.md +67 -0
  98. package/docs/zh-CN/DESIGN.md +102 -0
  99. package/docs/zh-CN/README.md +46 -0
  100. package/docs/zh-CN/advanced.md +26 -0
  101. package/docs/zh-CN/architecture_explained.md +116 -0
  102. package/docs/zh-CN/cli.md +109 -0
  103. package/docs/zh-CN/dsr.md +91 -0
  104. package/docs/zh-CN/graph_scenarios.md +173 -0
  105. package/docs/zh-CN/hooks.md +14 -0
  106. package/docs/zh-CN/manifests.md +136 -0
  107. package/docs/zh-CN/mcp.md +205 -0
  108. package/docs/zh-CN/quickstart.md +35 -0
  109. package/docs/zh-CN/rules.md +7 -0
  110. package/docs/zh-CN/technical-details.md +454 -0
  111. package/docs/zh-CN/troubleshooting.md +19 -0
  112. package/docs/zh-CN/windows-setup.md +67 -0
  113. package/install.sh +183 -0
  114. package/package.json +97 -0
  115. package/skills/git-ai-mcp/SKILL.md +86 -0
  116. package/skills/git-ai-mcp/references/constraints.md +143 -0
  117. package/skills/git-ai-mcp/references/tools.md +263 -0
  118. package/templates/agents/common/documents/Fix EISDIR error and enable multi-language indexing.md +14 -0
  119. package/templates/agents/common/documents/Fix git-ai index error in CodaGraph directory.md +13 -0
  120. package/templates/agents/common/skills/git-ai-mcp/SKILL.md +86 -0
  121. package/templates/agents/common/skills/git-ai-mcp/references/constraints.md +143 -0
  122. package/templates/agents/common/skills/git-ai-mcp/references/tools.md +263 -0
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateDsrForCommit = generateDsrForCommit;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const crypto_1 = require("../crypto");
10
+ const paths_1 = require("../paths");
11
+ const gitContext_1 = require("./gitContext");
12
+ const paths_2 = require("./paths");
13
+ const snapshotParser_1 = require("./snapshotParser");
14
+ function normalizeFilePath(p) {
15
+ return (0, paths_1.toPosixPath)(p);
16
+ }
17
+ function symbolContainerKey(s) {
18
+ if (!s.container)
19
+ return '';
20
+ return `${s.container.kind}:${s.container.name}`;
21
+ }
22
+ function symbolKeyFull(file, s) {
23
+ return `${file}|${symbolContainerKey(s)}|${s.kind}|${s.name}|${s.signature}`;
24
+ }
25
+ function symbolKeyNoSig(file, s) {
26
+ return `${file}|${symbolContainerKey(s)}|${s.kind}|${s.name}`;
27
+ }
28
+ function clampLine(n, min, max) {
29
+ if (!Number.isFinite(n))
30
+ return min;
31
+ if (n < min)
32
+ return min;
33
+ if (n > max)
34
+ return max;
35
+ return n;
36
+ }
37
+ function computeRangeHash(content, startLine, endLine) {
38
+ const lines = content.split('\n');
39
+ const maxLine = Math.max(1, lines.length);
40
+ const s = clampLine(startLine, 1, maxLine);
41
+ const e = clampLine(endLine, 1, maxLine);
42
+ const from = Math.min(s, e);
43
+ const to = Math.max(s, e);
44
+ const slice = lines.slice(from - 1, to).join('\n');
45
+ return (0, crypto_1.sha256Hex)(slice);
46
+ }
47
+ function toDescriptor(file, s) {
48
+ const out = {
49
+ file,
50
+ kind: String(s.kind),
51
+ name: String(s.name),
52
+ signature: String(s.signature ?? ''),
53
+ start_line: Number(s.startLine ?? 0),
54
+ end_line: Number(s.endLine ?? 0),
55
+ };
56
+ if (s.container?.name) {
57
+ out.container = {
58
+ kind: String(s.container.kind),
59
+ name: String(s.container.name),
60
+ signature: String(s.container.signature ?? ''),
61
+ };
62
+ }
63
+ return out;
64
+ }
65
+ function riskFromOps(ops) {
66
+ let max = 'low';
67
+ for (const op of ops) {
68
+ if (op.op === 'delete' || op.op === 'rename')
69
+ return 'high';
70
+ if (op.op === 'modify')
71
+ max = 'medium';
72
+ }
73
+ return max;
74
+ }
75
+ function semanticTypeFromOps(ops) {
76
+ if (ops.length === 0)
77
+ return 'no-op';
78
+ const kinds = new Set(ops.map((o) => o.op));
79
+ if (kinds.size === 1) {
80
+ const only = Array.from(kinds)[0];
81
+ if (only === 'add')
82
+ return 'additive';
83
+ if (only === 'modify')
84
+ return 'modification';
85
+ if (only === 'delete')
86
+ return 'deletion';
87
+ if (only === 'rename')
88
+ return 'rename';
89
+ }
90
+ return 'mixed';
91
+ }
92
+ function stableSortDescriptor(a, b) {
93
+ const ak = `${a.file}|${a.kind}|${a.name}|${a.signature}|${a.container?.kind ?? ''}|${a.container?.name ?? ''}`;
94
+ const bk = `${b.file}|${b.kind}|${b.name}|${b.signature}|${b.container?.kind ?? ''}|${b.container?.name ?? ''}`;
95
+ return ak.localeCompare(bk);
96
+ }
97
+ function stableSortOp(a, b) {
98
+ const ak = `${a.op}|${a.symbol.file}|${a.symbol.kind}|${a.symbol.name}|${a.symbol.signature}|${a.previous?.name ?? ''}|${a.previous?.signature ?? ''}|${a.content_hash}`;
99
+ const bk = `${b.op}|${b.symbol.file}|${b.symbol.kind}|${b.symbol.name}|${b.symbol.signature}|${b.previous?.name ?? ''}|${b.previous?.signature ?? ''}|${b.content_hash}`;
100
+ return ak.localeCompare(bk);
101
+ }
102
+ function canonDsr(dsr) {
103
+ const affected = [...dsr.affected_symbols].sort(stableSortDescriptor);
104
+ const ops = [...dsr.ast_operations].sort(stableSortOp);
105
+ const out = {
106
+ commit_hash: dsr.commit_hash,
107
+ affected_symbols: affected,
108
+ ast_operations: ops,
109
+ semantic_change_type: dsr.semantic_change_type,
110
+ };
111
+ if (dsr.summary)
112
+ out.summary = dsr.summary;
113
+ if (dsr.risk_level)
114
+ out.risk_level = dsr.risk_level;
115
+ return out;
116
+ }
117
+ function stringifyDsr(dsr) {
118
+ return JSON.stringify(canonDsr(dsr), null, 2) + '\n';
119
+ }
120
+ async function generateDsrForCommit(repoRoot, commitHash) {
121
+ const resolvedCommit = await (0, gitContext_1.resolveCommitHash)(repoRoot, commitHash);
122
+ await (0, gitContext_1.assertCommitExists)(repoRoot, resolvedCommit);
123
+ const parents = await (0, gitContext_1.getCommitParents)(repoRoot, resolvedCommit);
124
+ const parent = parents.length > 0 ? parents[0] : null;
125
+ const changes = await (0, gitContext_1.getNameStatusBetween)(repoRoot, parent, resolvedCommit);
126
+ const parser = new snapshotParser_1.SnapshotCodeParser();
127
+ const beforeSnaps = [];
128
+ const afterSnaps = [];
129
+ for (const ch of changes) {
130
+ const status = String(ch.status);
131
+ const file = normalizeFilePath(String(ch.path));
132
+ const includeBefore = status !== 'A';
133
+ const includeAfter = status !== 'D';
134
+ if (includeBefore && parent) {
135
+ const beforeContent = await (0, gitContext_1.gitShowFile)(repoRoot, parent, file);
136
+ if (beforeContent != null) {
137
+ const parsed = parser.parseContent(file, beforeContent);
138
+ for (const s of parsed.symbols) {
139
+ const desc = toDescriptor(file, s);
140
+ const content_hash = computeRangeHash(beforeContent, desc.start_line, desc.end_line);
141
+ beforeSnaps.push({ desc, content_hash });
142
+ }
143
+ }
144
+ }
145
+ if (includeAfter) {
146
+ const afterContent = await (0, gitContext_1.gitShowFile)(repoRoot, resolvedCommit, file);
147
+ if (afterContent != null) {
148
+ const parsed = parser.parseContent(file, afterContent);
149
+ for (const s of parsed.symbols) {
150
+ const desc = toDescriptor(file, s);
151
+ const content_hash = computeRangeHash(afterContent, desc.start_line, desc.end_line);
152
+ afterSnaps.push({ desc, content_hash });
153
+ }
154
+ }
155
+ }
156
+ }
157
+ const beforeByFull = new Map();
158
+ const afterByFull = new Map();
159
+ const beforeByNoSig = new Map();
160
+ const afterByNoSig = new Map();
161
+ for (const s of beforeSnaps) {
162
+ const file = s.desc.file;
163
+ const kFull = symbolKeyFull(file, s.desc);
164
+ const kNoSig = symbolKeyNoSig(file, s.desc);
165
+ beforeByFull.set(kFull, [...(beforeByFull.get(kFull) ?? []), s]);
166
+ beforeByNoSig.set(kNoSig, [...(beforeByNoSig.get(kNoSig) ?? []), s]);
167
+ }
168
+ for (const s of afterSnaps) {
169
+ const file = s.desc.file;
170
+ const kFull = symbolKeyFull(file, s.desc);
171
+ const kNoSig = symbolKeyNoSig(file, s.desc);
172
+ afterByFull.set(kFull, [...(afterByFull.get(kFull) ?? []), s]);
173
+ afterByNoSig.set(kNoSig, [...(afterByNoSig.get(kNoSig) ?? []), s]);
174
+ }
175
+ const usedBefore = new Set();
176
+ const usedAfter = new Set();
177
+ const ops = [];
178
+ for (const [kFull, bList] of beforeByFull.entries()) {
179
+ const aList = afterByFull.get(kFull) ?? [];
180
+ if (aList.length === 0)
181
+ continue;
182
+ const pairs = Math.min(bList.length, aList.length);
183
+ for (let i = 0; i < pairs; i++) {
184
+ const b = bList[i];
185
+ const a = aList[i];
186
+ usedBefore.add(b);
187
+ usedAfter.add(a);
188
+ if (b.content_hash !== a.content_hash) {
189
+ ops.push({
190
+ op: 'modify',
191
+ symbol: a.desc,
192
+ previous: { name: b.desc.name, signature: b.desc.signature },
193
+ content_hash: a.content_hash,
194
+ });
195
+ }
196
+ }
197
+ }
198
+ const remainingBefore = beforeSnaps.filter((s) => !usedBefore.has(s));
199
+ const remainingAfter = afterSnaps.filter((s) => !usedAfter.has(s));
200
+ const remainingAfterByNoSig = new Map();
201
+ for (const a of remainingAfter) {
202
+ const k = symbolKeyNoSig(a.desc.file, a.desc);
203
+ remainingAfterByNoSig.set(k, [...(remainingAfterByNoSig.get(k) ?? []), a]);
204
+ }
205
+ for (const b of remainingBefore) {
206
+ const k = symbolKeyNoSig(b.desc.file, b.desc);
207
+ const candidates = remainingAfterByNoSig.get(k) ?? [];
208
+ if (candidates.length !== 1)
209
+ continue;
210
+ const a = candidates[0];
211
+ if (usedAfter.has(a))
212
+ continue;
213
+ usedBefore.add(b);
214
+ usedAfter.add(a);
215
+ if (b.content_hash !== a.content_hash || b.desc.signature !== a.desc.signature) {
216
+ ops.push({
217
+ op: 'modify',
218
+ symbol: a.desc,
219
+ previous: { name: b.desc.name, signature: b.desc.signature },
220
+ content_hash: a.content_hash,
221
+ });
222
+ }
223
+ }
224
+ const remBefore2 = beforeSnaps.filter((s) => !usedBefore.has(s));
225
+ const remAfter2 = afterSnaps.filter((s) => !usedAfter.has(s));
226
+ const afterByHash = new Map();
227
+ for (const a of remAfter2) {
228
+ const k = `${a.desc.file}|${symbolContainerKey(a.desc)}|${a.desc.kind}|${a.content_hash}`;
229
+ afterByHash.set(k, [...(afterByHash.get(k) ?? []), a]);
230
+ }
231
+ for (const b of remBefore2) {
232
+ const k = `${b.desc.file}|${symbolContainerKey(b.desc)}|${b.desc.kind}|${b.content_hash}`;
233
+ const candidates = afterByHash.get(k) ?? [];
234
+ if (candidates.length !== 1)
235
+ continue;
236
+ const a = candidates[0];
237
+ if (usedAfter.has(a))
238
+ continue;
239
+ usedBefore.add(b);
240
+ usedAfter.add(a);
241
+ if (b.desc.name !== a.desc.name || b.desc.signature !== a.desc.signature) {
242
+ ops.push({
243
+ op: 'rename',
244
+ symbol: a.desc,
245
+ previous: { name: b.desc.name, signature: b.desc.signature },
246
+ content_hash: a.content_hash,
247
+ });
248
+ }
249
+ else if (b.content_hash !== a.content_hash) {
250
+ ops.push({
251
+ op: 'modify',
252
+ symbol: a.desc,
253
+ previous: { name: b.desc.name, signature: b.desc.signature },
254
+ content_hash: a.content_hash,
255
+ });
256
+ }
257
+ }
258
+ for (const a of afterSnaps) {
259
+ if (usedAfter.has(a))
260
+ continue;
261
+ ops.push({
262
+ op: 'add',
263
+ symbol: a.desc,
264
+ content_hash: a.content_hash,
265
+ });
266
+ }
267
+ for (const b of beforeSnaps) {
268
+ if (usedBefore.has(b))
269
+ continue;
270
+ ops.push({
271
+ op: 'delete',
272
+ symbol: b.desc,
273
+ content_hash: b.content_hash,
274
+ });
275
+ }
276
+ const affectedMap = new Map();
277
+ for (const op of ops) {
278
+ const s = op.symbol;
279
+ const k = symbolKeyFull(s.file, s);
280
+ affectedMap.set(k, s);
281
+ }
282
+ const subject = await (0, gitContext_1.getCommitSubject)(repoRoot, resolvedCommit).catch(() => '');
283
+ const risk_level = riskFromOps(ops);
284
+ const semantic_change_type = semanticTypeFromOps(ops);
285
+ const dsr = canonDsr({
286
+ commit_hash: resolvedCommit,
287
+ affected_symbols: Array.from(affectedMap.values()),
288
+ ast_operations: ops,
289
+ semantic_change_type,
290
+ summary: subject || undefined,
291
+ risk_level,
292
+ });
293
+ const dir = (0, paths_2.dsrDirectory)(repoRoot);
294
+ const file_path = (0, paths_2.dsrFilePath)(repoRoot, resolvedCommit);
295
+ await fs_extra_1.default.ensureDir(dir);
296
+ const rendered = stringifyDsr(dsr);
297
+ if (await fs_extra_1.default.pathExists(file_path)) {
298
+ const existing = await fs_extra_1.default.readFile(file_path, 'utf-8').catch(() => '');
299
+ if (existing.trimEnd() !== rendered.trimEnd()) {
300
+ throw new Error(`DSR already exists but differs: ${file_path}`);
301
+ }
302
+ return { dsr, file_path, existed: true };
303
+ }
304
+ const tmp = path_1.default.join(dir, `${commitHash}.json.tmp-${process.pid}-${Date.now()}`);
305
+ await fs_extra_1.default.writeFile(tmp, rendered, 'utf-8');
306
+ await fs_extra_1.default.move(tmp, file_path, { overwrite: false });
307
+ return { dsr, file_path, existed: false };
308
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectRepoGitContext = detectRepoGitContext;
7
+ exports.getCommitParents = getCommitParents;
8
+ exports.getCommitSubject = getCommitSubject;
9
+ exports.assertCommitExists = assertCommitExists;
10
+ exports.resolveCommitHash = resolveCommitHash;
11
+ exports.getNameStatusBetween = getNameStatusBetween;
12
+ exports.gitShowFile = gitShowFile;
13
+ const simple_git_1 = __importDefault(require("simple-git"));
14
+ async function detectRepoGitContext(startDir) {
15
+ const git = (0, simple_git_1.default)(startDir);
16
+ const repo_root = (await git.raw(['rev-parse', '--show-toplevel'])).trim();
17
+ const head_commit = (await (0, simple_git_1.default)(repo_root).raw(['rev-parse', 'HEAD'])).trim();
18
+ const branchRaw = (await (0, simple_git_1.default)(repo_root).raw(['rev-parse', '--abbrev-ref', 'HEAD'])).trim();
19
+ const detached = branchRaw === 'HEAD';
20
+ return {
21
+ repo_root,
22
+ head_commit,
23
+ branch: detached ? null : branchRaw,
24
+ detached,
25
+ };
26
+ }
27
+ async function getCommitParents(repoRoot, commitHash) {
28
+ const git = (0, simple_git_1.default)(repoRoot);
29
+ const out = (await git.raw(['show', '-s', '--format=%P', commitHash])).trim();
30
+ if (!out)
31
+ return [];
32
+ return out.split(/\s+/).map((s) => s.trim()).filter(Boolean);
33
+ }
34
+ async function getCommitSubject(repoRoot, commitHash) {
35
+ const git = (0, simple_git_1.default)(repoRoot);
36
+ return (await git.raw(['show', '-s', '--format=%s', commitHash])).trim();
37
+ }
38
+ async function assertCommitExists(repoRoot, commitHash) {
39
+ const git = (0, simple_git_1.default)(repoRoot);
40
+ await git.raw(['cat-file', '-e', `${commitHash}^{commit}`]);
41
+ }
42
+ async function resolveCommitHash(repoRoot, rev) {
43
+ const git = (0, simple_git_1.default)(repoRoot);
44
+ return (await git.raw(['rev-parse', `${rev}^{commit}`])).trim();
45
+ }
46
+ async function getNameStatusBetween(repoRoot, parent, commit) {
47
+ const git = (0, simple_git_1.default)(repoRoot);
48
+ const lines = parent
49
+ ? (await git.raw(['diff', '--name-status', '--no-renames', parent, commit])).trim().split('\n')
50
+ : (await git.raw(['diff-tree', '--root', '--no-commit-id', '--name-status', '-r', commit])).trim().split('\n');
51
+ const rows = [];
52
+ for (const line of lines) {
53
+ const trimmed = line.trim();
54
+ if (!trimmed)
55
+ continue;
56
+ const parts = trimmed.split('\t');
57
+ const status = (parts[0] ?? '').trim();
58
+ const p = (parts[1] ?? '').trim();
59
+ if (!status || !p)
60
+ continue;
61
+ rows.push({ status, path: p });
62
+ }
63
+ rows.sort((a, b) => (a.status + '\t' + a.path).localeCompare(b.status + '\t' + b.path));
64
+ return rows;
65
+ }
66
+ async function gitShowFile(repoRoot, commitHash, filePath) {
67
+ const git = (0, simple_git_1.default)(repoRoot);
68
+ try {
69
+ return await git.raw(['show', `${commitHash}:${filePath}`]);
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.materializeDsrIndex = materializeDsrIndex;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const cozo_1 = require("../cozo");
10
+ const paths_1 = require("./paths");
11
+ async function materializeDsrIndex(repoRoot) {
12
+ const dsrDir = (0, paths_1.dsrDirectory)(repoRoot);
13
+ if (!await fs_extra_1.default.pathExists(dsrDir)) {
14
+ return { enabled: false, skippedReason: `DSR directory missing: ${dsrDir}` };
15
+ }
16
+ const files = (await fs_extra_1.default.readdir(dsrDir).catch(() => []))
17
+ .filter((f) => f.endsWith('.json'))
18
+ .filter((f) => !f.endsWith('.export.json'))
19
+ .sort((a, b) => a.localeCompare(b));
20
+ const dsrs = [];
21
+ for (const f of files) {
22
+ const full = path_1.default.join(dsrDir, f);
23
+ const data = await fs_extra_1.default.readJSON(full).catch(() => null);
24
+ if (!data || typeof data !== 'object')
25
+ continue;
26
+ if (typeof data.commit_hash !== 'string')
27
+ continue;
28
+ dsrs.push(data);
29
+ }
30
+ const dbPath = (0, paths_1.dsrIndexDbPath)(repoRoot);
31
+ const exportPath = (0, paths_1.dsrIndexExportPath)(repoRoot);
32
+ const db = await (0, cozo_1.openCozoDbAtPath)(dbPath, exportPath);
33
+ if (!db)
34
+ return { enabled: false, skippedReason: 'Cozo backend not available (see cozo.error.json next to dsr-index.sqlite)' };
35
+ const commits = [];
36
+ const affected = [];
37
+ const ops = [];
38
+ for (const r of dsrs) {
39
+ const commit = String(r.commit_hash);
40
+ commits.push([
41
+ commit,
42
+ String(r.semantic_change_type ?? ''),
43
+ String(r.risk_level ?? ''),
44
+ String(r.summary ?? ''),
45
+ ]);
46
+ for (const s of Array.isArray(r.affected_symbols) ? r.affected_symbols : []) {
47
+ affected.push([
48
+ commit,
49
+ String(s.file ?? ''),
50
+ String(s.kind ?? ''),
51
+ String(s.name ?? ''),
52
+ String(s.signature ?? ''),
53
+ String(s.container?.kind ?? ''),
54
+ String(s.container?.name ?? ''),
55
+ String(s.container?.signature ?? ''),
56
+ ]);
57
+ }
58
+ for (const o of Array.isArray(r.ast_operations) ? r.ast_operations : []) {
59
+ const sym = o.symbol ?? {};
60
+ ops.push([
61
+ commit,
62
+ String(o.op ?? ''),
63
+ String(sym.file ?? ''),
64
+ String(sym.kind ?? ''),
65
+ String(sym.name ?? ''),
66
+ String(sym.signature ?? ''),
67
+ String(o.previous?.name ?? ''),
68
+ String(o.previous?.signature ?? ''),
69
+ String(o.content_hash ?? ''),
70
+ ]);
71
+ }
72
+ }
73
+ const script = `
74
+ {
75
+ ?[commit_hash, semantic_change_type, risk_level, summary] <- $commits
76
+ :replace dsr_commit { commit_hash: String => semantic_change_type: String, risk_level: String, summary: String }
77
+ }
78
+ {
79
+ ?[commit_hash, file, kind, name, signature, container_kind, container_name, container_signature] <- $affected
80
+ :replace dsr_affected_symbol { commit_hash: String, file: String, kind: String, name: String, signature: String, container_kind: String, container_name: String, container_signature: String }
81
+ }
82
+ {
83
+ ?[commit_hash, op, file, kind, name, signature, prev_name, prev_signature, content_hash] <- $ops
84
+ :replace dsr_ast_operation { commit_hash: String, op: String, file: String, kind: String, name: String, signature: String, prev_name: String, prev_signature: String, content_hash: String }
85
+ }
86
+ `;
87
+ await db.run(script, { commits, affected, ops });
88
+ if (db.engine !== 'sqlite' && db.exportRelations) {
89
+ const exported = await db.exportRelations(['dsr_commit', 'dsr_affected_symbol', 'dsr_ast_operation']);
90
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(exportPath));
91
+ await fs_extra_1.default.writeJSON(exportPath, exported, { spaces: 2 });
92
+ }
93
+ if (db.close)
94
+ await db.close();
95
+ return {
96
+ enabled: true,
97
+ engine: db.engine,
98
+ dbPath: db.dbPath,
99
+ exportPath: db.engine !== 'sqlite' ? exportPath : undefined,
100
+ counts: {
101
+ commits: commits.length,
102
+ affected_symbols: affected.length,
103
+ ast_operations: ops.length,
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.dsrCacheRoot = dsrCacheRoot;
7
+ exports.dsrDirectory = dsrDirectory;
8
+ exports.dsrFilePath = dsrFilePath;
9
+ exports.dsrIndexDbPath = dsrIndexDbPath;
10
+ exports.dsrIndexExportPath = dsrIndexExportPath;
11
+ const path_1 = __importDefault(require("path"));
12
+ function dsrCacheRoot(repoRoot) {
13
+ return path_1.default.join(repoRoot, '.git-ai');
14
+ }
15
+ function dsrDirectory(repoRoot) {
16
+ return path_1.default.join(dsrCacheRoot(repoRoot), 'dsr');
17
+ }
18
+ function dsrFilePath(repoRoot, commitHash) {
19
+ return path_1.default.join(dsrDirectory(repoRoot), `${commitHash}.json`);
20
+ }
21
+ function dsrIndexDbPath(repoRoot) {
22
+ return path_1.default.join(dsrDirectory(repoRoot), 'dsr-index.sqlite');
23
+ }
24
+ function dsrIndexExportPath(repoRoot) {
25
+ return path_1.default.join(dsrDirectory(repoRoot), 'dsr-index.export.json');
26
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listCommitsTopological = listCommitsTopological;
7
+ exports.symbolEvolution = symbolEvolution;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const simple_git_1 = __importDefault(require("simple-git"));
10
+ const paths_1 = require("./paths");
11
+ async function listCommitsTopological(repoRoot, opts) {
12
+ const git = (0, simple_git_1.default)(repoRoot);
13
+ const args = ['rev-list', '--topo-order'];
14
+ if (opts.limit && opts.limit > 0)
15
+ args.push('-n', String(opts.limit));
16
+ if (opts.all)
17
+ args.push('--all');
18
+ else
19
+ args.push(String(opts.start ?? 'HEAD'));
20
+ const out = (await git.raw(args)).trim();
21
+ if (!out)
22
+ return [];
23
+ return out.split('\n').map((l) => l.trim()).filter(Boolean);
24
+ }
25
+ async function symbolEvolution(repoRoot, symbol, opts) {
26
+ const commits = await listCommitsTopological(repoRoot, opts);
27
+ const missing_dsrs = [];
28
+ const hits = [];
29
+ const needle = String(symbol ?? '').trim();
30
+ if (!needle)
31
+ return { ok: true, hits: [] };
32
+ const matches = (name) => {
33
+ if (opts.contains)
34
+ return name.includes(needle);
35
+ return name === needle;
36
+ };
37
+ for (const c of commits) {
38
+ const p = (0, paths_1.dsrFilePath)(repoRoot, c);
39
+ if (!await fs_extra_1.default.pathExists(p)) {
40
+ missing_dsrs.push(c);
41
+ break;
42
+ }
43
+ const rec = await fs_extra_1.default.readJSON(p).catch(() => null);
44
+ if (!rec)
45
+ continue;
46
+ const ops = Array.isArray(rec.ast_operations) ? rec.ast_operations : [];
47
+ const matchedOps = ops
48
+ .filter((o) => matches(String(o?.symbol?.name ?? '')) || matches(String(o?.previous?.name ?? '')))
49
+ .map((o) => ({
50
+ op: String(o?.op ?? ''),
51
+ file: String(o?.symbol?.file ?? ''),
52
+ kind: String(o?.symbol?.kind ?? ''),
53
+ name: String(o?.symbol?.name ?? ''),
54
+ signature: String(o?.symbol?.signature ?? ''),
55
+ previous_name: o?.previous?.name ? String(o.previous.name) : undefined,
56
+ previous_signature: o?.previous?.signature ? String(o.previous.signature) : undefined,
57
+ content_hash: String(o?.content_hash ?? ''),
58
+ }))
59
+ .sort((a, b) => `${a.op}|${a.file}|${a.kind}|${a.name}|${a.signature}|${a.previous_name ?? ''}`.localeCompare(`${b.op}|${b.file}|${b.kind}|${b.name}|${b.signature}|${b.previous_name ?? ''}`));
60
+ if (matchedOps.length === 0)
61
+ continue;
62
+ hits.push({
63
+ commit_hash: String(rec.commit_hash),
64
+ semantic_change_type: String(rec.semantic_change_type ?? ''),
65
+ risk_level: rec.risk_level,
66
+ summary: rec.summary,
67
+ operations: matchedOps,
68
+ });
69
+ }
70
+ if (missing_dsrs.length > 0)
71
+ return { ok: false, missing_dsrs };
72
+ return { ok: true, hits };
73
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SnapshotCodeParser = void 0;
7
+ const tree_sitter_1 = __importDefault(require("tree-sitter"));
8
+ const typescript_1 = require("../parser/typescript");
9
+ const java_1 = require("../parser/java");
10
+ const c_1 = require("../parser/c");
11
+ const go_1 = require("../parser/go");
12
+ const python_1 = require("../parser/python");
13
+ const rust_1 = require("../parser/rust");
14
+ const markdown_1 = require("../parser/markdown");
15
+ const yaml_1 = require("../parser/yaml");
16
+ class SnapshotCodeParser {
17
+ constructor() {
18
+ this.parser = new tree_sitter_1.default();
19
+ this.adapters = [
20
+ new typescript_1.TypeScriptAdapter(false),
21
+ new typescript_1.TypeScriptAdapter(true),
22
+ new java_1.JavaAdapter(),
23
+ new c_1.CAdapter(),
24
+ new go_1.GoAdapter(),
25
+ new python_1.PythonAdapter(),
26
+ new rust_1.RustAdapter(),
27
+ ];
28
+ }
29
+ parseContent(filePath, content) {
30
+ if (isMarkdownFile(filePath))
31
+ return (0, markdown_1.parseMarkdown)(content, filePath);
32
+ if (isYamlFile(filePath))
33
+ return (0, yaml_1.parseYaml)(content, filePath);
34
+ const adapter = this.pickAdapter(filePath);
35
+ if (!adapter)
36
+ return { symbols: [], refs: [] };
37
+ try {
38
+ this.parser.setLanguage(adapter.getTreeSitterLanguage());
39
+ const tree = this.parser.parse(content);
40
+ return adapter.extractSymbolsAndRefs(tree.rootNode);
41
+ }
42
+ catch (e) {
43
+ const msg = String(e?.message ?? e);
44
+ if (msg.includes('Invalid language object'))
45
+ return { symbols: [], refs: [] };
46
+ if (!msg.includes('Invalid argument'))
47
+ return { symbols: [], refs: [] };
48
+ try {
49
+ const tree = this.parser.parse(content, undefined, { bufferSize: 1024 * 1024 });
50
+ return adapter.extractSymbolsAndRefs(tree.rootNode);
51
+ }
52
+ catch {
53
+ return { symbols: [], refs: [] };
54
+ }
55
+ }
56
+ }
57
+ pickAdapter(filePath) {
58
+ for (const adapter of this.adapters) {
59
+ for (const ext of adapter.getSupportedFileExtensions()) {
60
+ if (filePath.endsWith(ext))
61
+ return adapter;
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+ }
67
+ exports.SnapshotCodeParser = SnapshotCodeParser;
68
+ function isMarkdownFile(filePath) {
69
+ return filePath.endsWith('.md') || filePath.endsWith('.mdx');
70
+ }
71
+ function isYamlFile(filePath) {
72
+ return filePath.endsWith('.yml') || filePath.endsWith('.yaml');
73
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getDsrDirectoryState = getDsrDirectoryState;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const paths_1 = require("./paths");
10
+ async function getDsrDirectoryState(repoRoot) {
11
+ const cache_root = (0, paths_1.dsrCacheRoot)(repoRoot);
12
+ const dsr_dir = (0, paths_1.dsrDirectory)(repoRoot);
13
+ const cache_root_exists = await fs_extra_1.default.pathExists(cache_root);
14
+ const dsr_dir_exists = await fs_extra_1.default.pathExists(dsr_dir);
15
+ let dsr_file_count = 0;
16
+ if (dsr_dir_exists) {
17
+ const entries = await fs_extra_1.default.readdir(dsr_dir).catch(() => []);
18
+ dsr_file_count = entries.filter((e) => e.endsWith('.json') && !e.endsWith('.export.json')).length;
19
+ }
20
+ return {
21
+ cache_root: path_1.default.resolve(cache_root),
22
+ cache_root_exists,
23
+ dsr_dir: path_1.default.resolve(dsr_dir),
24
+ dsr_dir_exists,
25
+ dsr_file_count,
26
+ };
27
+ }