diffintel 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/README.md +59 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/explain.d.ts +7 -0
- package/dist/commands/explain.js +60 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/explain/ast-diff.d.ts +10 -0
- package/dist/explain/ast-diff.js +169 -0
- package/dist/explain/ast-diff.js.map +1 -0
- package/dist/explain/git-diff.d.ts +13 -0
- package/dist/explain/git-diff.js +112 -0
- package/dist/explain/git-diff.js.map +1 -0
- package/dist/explain/html-report.d.ts +2 -0
- package/dist/explain/html-report.js +131 -0
- package/dist/explain/html-report.js.map +1 -0
- package/dist/explain/llm-explain.d.ts +2 -0
- package/dist/explain/llm-explain.js +87 -0
- package/dist/explain/llm-explain.js.map +1 -0
- package/dist/explain/types.d.ts +53 -0
- package/dist/explain/types.js +3 -0
- package/dist/explain/types.js.map +1 -0
- package/dist/parsing/index.d.ts +2 -0
- package/dist/parsing/index.js +10 -0
- package/dist/parsing/index.js.map +1 -0
- package/dist/parsing/parser.d.ts +18 -0
- package/dist/parsing/parser.js +49 -0
- package/dist/parsing/parser.js.map +1 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# diffintel
|
|
2
|
+
|
|
3
|
+
Experimantal(!) structural diff explainer for pull requests. Parses changed JS/TS files with Tree-sitter, detects structural changes (functions, classes, imports, exports), and uses an LLM to generate a concise explanation with risk assessment. Outputs a self-contained HTML report. Helpful for getting a concise explanation on diffs. Inspired by the hard-to-ingest-code-masses the ai-agents write.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g diffintel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
15
|
+
diffintel explain --base main --out report.html
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Flag | Default | Description |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `--base <ref>` | `origin/main` | Base git ref |
|
|
21
|
+
| `--head <ref>` | `HEAD` | Head git ref |
|
|
22
|
+
| `--out <file>` | `explain-report.html` | Output path |
|
|
23
|
+
|
|
24
|
+
## GitHub Action
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
name: PR Explain
|
|
28
|
+
on:
|
|
29
|
+
pull_request:
|
|
30
|
+
types: [opened, synchronize]
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
explain:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
with:
|
|
38
|
+
fetch-depth: 0
|
|
39
|
+
- uses: actions/setup-node@v4
|
|
40
|
+
with:
|
|
41
|
+
node-version: 20
|
|
42
|
+
- run: npm install -g diffintel
|
|
43
|
+
- name: Generate report
|
|
44
|
+
env:
|
|
45
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
46
|
+
run: diffintel explain --base origin/${{ github.base_ref }}
|
|
47
|
+
- uses: actions/upload-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: explain-report
|
|
50
|
+
path: explain-report.html
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install
|
|
57
|
+
npm test
|
|
58
|
+
npx tsx src/cli.ts explain --base HEAD~3
|
|
59
|
+
```
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
9
|
+
commander_1.program
|
|
10
|
+
.name("diffintel")
|
|
11
|
+
.description("AI-powered structural diff explainer for pull requests")
|
|
12
|
+
.version(package_json_1.default.version);
|
|
13
|
+
commander_1.program
|
|
14
|
+
.command("explain")
|
|
15
|
+
.description("Generate an HTML report explaining code changes between refs")
|
|
16
|
+
.option("--base <ref>", "Base ref to diff from (default: origin/main)")
|
|
17
|
+
.option("--head <ref>", "Head ref to diff to (default: HEAD)")
|
|
18
|
+
.option("--out <file>", "Output HTML file path (default: explain-report.html)")
|
|
19
|
+
.action(async (opts) => {
|
|
20
|
+
require("dotenv").config();
|
|
21
|
+
const { run } = require("./commands/explain");
|
|
22
|
+
const code = await run(opts);
|
|
23
|
+
process.exit(code);
|
|
24
|
+
});
|
|
25
|
+
commander_1.program.parse();
|
|
26
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;AAEA,yCAAoC;AACpC,mEAAkC;AAElC,mBAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,sBAAG,CAAC,OAAO,CAAC,CAAC;AAExB,mBAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,8DAA8D,CAAC;KAC3E,MAAM,CAAC,cAAc,EAAE,8CAA8C,CAAC;KACtE,MAAM,CAAC,cAAc,EAAE,qCAAqC,CAAC;KAC7D,MAAM,CAAC,cAAc,EAAE,sDAAsD,CAAC;KAC9E,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;IAC3B,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC,CAAC,CAAC;AAEL,mBAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
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.run = run;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const git_diff_1 = require("../explain/git-diff");
|
|
9
|
+
const ast_diff_1 = require("../explain/ast-diff");
|
|
10
|
+
const llm_explain_1 = require("../explain/llm-explain");
|
|
11
|
+
const html_report_1 = require("../explain/html-report");
|
|
12
|
+
async function run(opts) {
|
|
13
|
+
const baseRef = opts.base || "origin/main";
|
|
14
|
+
const headRef = opts.head || "HEAD";
|
|
15
|
+
const outFile = opts.out || "explain-report.html";
|
|
16
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
17
|
+
console.error("Error: ANTHROPIC_API_KEY environment variable is required.");
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
console.error(`Analyzing diff: ${baseRef}...${headRef}`);
|
|
22
|
+
const { files: fileDiffs, rawDiff } = (0, git_diff_1.getDiff)(baseRef, headRef);
|
|
23
|
+
if (fileDiffs.length === 0) {
|
|
24
|
+
console.error("No changes found between refs.");
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
console.error(`Found ${fileDiffs.length} changed file(s). Analyzing...`);
|
|
28
|
+
const fileAnalyses = fileDiffs.map(ast_diff_1.analyzeFile);
|
|
29
|
+
const totalChanges = fileAnalyses.reduce((sum, f) => sum + f.structuralChanges.length, 0);
|
|
30
|
+
console.error(`Detected ${totalChanges} structural change(s). Calling LLM...`);
|
|
31
|
+
const explanation = await (0, llm_explain_1.explainChanges)(fileAnalyses, rawDiff);
|
|
32
|
+
const totalAdditions = fileDiffs.reduce((sum, f) => sum + f.additions, 0);
|
|
33
|
+
const totalDeletions = fileDiffs.reduce((sum, f) => sum + f.deletions, 0);
|
|
34
|
+
const report = {
|
|
35
|
+
generatedAt: new Date().toISOString(),
|
|
36
|
+
baseRef,
|
|
37
|
+
headRef,
|
|
38
|
+
summary: {
|
|
39
|
+
filesChanged: fileDiffs.length,
|
|
40
|
+
additions: totalAdditions,
|
|
41
|
+
deletions: totalDeletions,
|
|
42
|
+
},
|
|
43
|
+
explanation,
|
|
44
|
+
files: fileAnalyses,
|
|
45
|
+
};
|
|
46
|
+
const html = (0, html_report_1.renderReport)(report);
|
|
47
|
+
fs_1.default.writeFileSync(outFile, html, "utf-8");
|
|
48
|
+
console.error(`\nTitle: ${explanation.title}`);
|
|
49
|
+
console.error(`Files: ${fileDiffs.length} | +${totalAdditions} -${totalDeletions}`);
|
|
50
|
+
console.error(`Risks: ${explanation.risks.length > 0 ? explanation.risks.map((r) => `${r.level}: ${r.description}`).join("; ") : "none"}`);
|
|
51
|
+
console.error(`Tokens: ${explanation.tokenUsage.input} in / ${explanation.tokenUsage.output} out`);
|
|
52
|
+
console.error(`\nReport written to: ${outFile}`);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(`Error: ${err.message}`);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FileDiff, FileAnalysis, ChangeType } from "./types";
|
|
2
|
+
interface Declaration {
|
|
3
|
+
name: string;
|
|
4
|
+
type: ChangeType;
|
|
5
|
+
text: string;
|
|
6
|
+
startLine: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function analyzeFile(diff: FileDiff): FileAnalysis;
|
|
9
|
+
export declare function extractDeclarations(source: string, ext: string): Declaration[];
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,169 @@
|
|
|
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.analyzeFile = analyzeFile;
|
|
7
|
+
exports.extractDeclarations = extractDeclarations;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const parser_1 = require("../parsing/parser");
|
|
10
|
+
const SUPPORTED_EXTS = new Set([".js", ".ts", ".tsx", ".jsx"]);
|
|
11
|
+
function analyzeFile(diff) {
|
|
12
|
+
const ext = path_1.default.extname(diff.path);
|
|
13
|
+
if (!SUPPORTED_EXTS.has(ext)) {
|
|
14
|
+
return {
|
|
15
|
+
path: diff.path,
|
|
16
|
+
status: diff.status,
|
|
17
|
+
language: null,
|
|
18
|
+
structuralChanges: [],
|
|
19
|
+
rawDiff: diff.hunks,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const language = ext.replace(".", "");
|
|
23
|
+
const structuralChanges = [];
|
|
24
|
+
if (diff.status === "added" && diff.newContent) {
|
|
25
|
+
const decls = extractDeclarations(diff.newContent, ext);
|
|
26
|
+
for (const d of decls) {
|
|
27
|
+
structuralChanges.push({
|
|
28
|
+
file: diff.path,
|
|
29
|
+
type: d.type,
|
|
30
|
+
action: "added",
|
|
31
|
+
name: d.name,
|
|
32
|
+
startLine: d.startLine,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (diff.status === "deleted" && diff.oldContent) {
|
|
37
|
+
const decls = extractDeclarations(diff.oldContent, ext);
|
|
38
|
+
for (const d of decls) {
|
|
39
|
+
structuralChanges.push({
|
|
40
|
+
file: diff.path,
|
|
41
|
+
type: d.type,
|
|
42
|
+
action: "removed",
|
|
43
|
+
name: d.name,
|
|
44
|
+
startLine: d.startLine,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if ((diff.status === "modified" || diff.status === "renamed") && diff.oldContent && diff.newContent) {
|
|
49
|
+
const oldDecls = extractDeclarations(diff.oldContent, ext);
|
|
50
|
+
const newDecls = extractDeclarations(diff.newContent, ext);
|
|
51
|
+
const oldMap = new Map(oldDecls.map((d) => [d.name, d]));
|
|
52
|
+
const newMap = new Map(newDecls.map((d) => [d.name, d]));
|
|
53
|
+
// Removed declarations
|
|
54
|
+
for (const [name, decl] of oldMap) {
|
|
55
|
+
if (!newMap.has(name)) {
|
|
56
|
+
structuralChanges.push({
|
|
57
|
+
file: diff.path,
|
|
58
|
+
type: decl.type,
|
|
59
|
+
action: "removed",
|
|
60
|
+
name,
|
|
61
|
+
startLine: decl.startLine,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Added or modified declarations
|
|
66
|
+
for (const [name, decl] of newMap) {
|
|
67
|
+
const old = oldMap.get(name);
|
|
68
|
+
if (!old) {
|
|
69
|
+
structuralChanges.push({
|
|
70
|
+
file: diff.path,
|
|
71
|
+
type: decl.type,
|
|
72
|
+
action: "added",
|
|
73
|
+
name,
|
|
74
|
+
startLine: decl.startLine,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else if (old.text !== decl.text) {
|
|
78
|
+
structuralChanges.push({
|
|
79
|
+
file: diff.path,
|
|
80
|
+
type: decl.type,
|
|
81
|
+
action: "modified",
|
|
82
|
+
name,
|
|
83
|
+
startLine: decl.startLine,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
path: diff.path,
|
|
90
|
+
status: diff.status,
|
|
91
|
+
language,
|
|
92
|
+
structuralChanges,
|
|
93
|
+
rawDiff: diff.hunks,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function extractDeclarations(source, ext) {
|
|
97
|
+
if (!source.trim())
|
|
98
|
+
return [];
|
|
99
|
+
const { tree } = (0, parser_1.parseSource)(source, ext);
|
|
100
|
+
const decls = [];
|
|
101
|
+
const root = tree.rootNode;
|
|
102
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
103
|
+
const node = root.child(i);
|
|
104
|
+
const extracted = extractFromNode(node);
|
|
105
|
+
if (extracted) {
|
|
106
|
+
decls.push(...extracted);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return decls;
|
|
110
|
+
}
|
|
111
|
+
function extractFromNode(node) {
|
|
112
|
+
const type = node.type;
|
|
113
|
+
if (type === "function_declaration") {
|
|
114
|
+
const name = node.childForFieldName("name")?.text || "<anonymous>";
|
|
115
|
+
return [{ name, type: "function", text: node.text, startLine: node.startPosition.row + 1 }];
|
|
116
|
+
}
|
|
117
|
+
if (type === "class_declaration") {
|
|
118
|
+
const name = node.childForFieldName("name")?.text || "<anonymous>";
|
|
119
|
+
return [{ name, type: "class", text: node.text, startLine: node.startPosition.row + 1 }];
|
|
120
|
+
}
|
|
121
|
+
if (type === "import_statement") {
|
|
122
|
+
const source = node.childForFieldName("source")?.text || node.text;
|
|
123
|
+
return [{ name: source, type: "import", text: node.text, startLine: node.startPosition.row + 1 }];
|
|
124
|
+
}
|
|
125
|
+
if (type === "export_statement") {
|
|
126
|
+
// Named export with declaration inside
|
|
127
|
+
const decl = node.childForFieldName("declaration");
|
|
128
|
+
if (decl) {
|
|
129
|
+
const inner = extractFromNode(decl);
|
|
130
|
+
if (inner) {
|
|
131
|
+
return inner.map((d) => ({ ...d, type: "export" }));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const name = node.text.slice(0, 60);
|
|
135
|
+
return [{ name, type: "export", text: node.text, startLine: node.startPosition.row + 1 }];
|
|
136
|
+
}
|
|
137
|
+
if (type === "variable_declaration" || type === "lexical_declaration") {
|
|
138
|
+
const results = [];
|
|
139
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
140
|
+
const child = node.child(i);
|
|
141
|
+
if (child.type === "variable_declarator") {
|
|
142
|
+
const name = child.childForFieldName("name")?.text || "<unknown>";
|
|
143
|
+
// Check if value is an arrow function or function expression
|
|
144
|
+
const value = child.childForFieldName("value");
|
|
145
|
+
const isFn = value && (value.type === "arrow_function" || value.type === "function");
|
|
146
|
+
results.push({
|
|
147
|
+
name,
|
|
148
|
+
type: isFn ? "function" : "variable",
|
|
149
|
+
text: node.text,
|
|
150
|
+
startLine: node.startPosition.row + 1,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (results.length)
|
|
155
|
+
return results;
|
|
156
|
+
}
|
|
157
|
+
// Expression statements like module.exports = ...
|
|
158
|
+
if (type === "expression_statement") {
|
|
159
|
+
const expr = node.child(0);
|
|
160
|
+
if (expr?.type === "assignment_expression") {
|
|
161
|
+
const left = expr.childForFieldName("left");
|
|
162
|
+
if (left?.text?.startsWith("module.exports")) {
|
|
163
|
+
return [{ name: "module.exports", type: "export", text: node.text, startLine: node.startPosition.row + 1 }];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=ast-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-diff.js","sourceRoot":"","sources":["../../src/explain/ast-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FileDiff } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Get the unified diff between two refs.
|
|
4
|
+
* If headRef is omitted, diffs against the working tree.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getDiff(baseRef: string, headRef?: string): {
|
|
7
|
+
files: FileDiff[];
|
|
8
|
+
rawDiff: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Parse unified diff text into per-file FileDiff objects.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseDiffText(text: string): Array<Pick<FileDiff, "path" | "hunks" | "additions" | "deletions">>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDiff = getDiff;
|
|
4
|
+
exports.parseDiffText = parseDiffText;
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
/**
|
|
7
|
+
* Get the unified diff between two refs.
|
|
8
|
+
* If headRef is omitted, diffs against the working tree.
|
|
9
|
+
*/
|
|
10
|
+
function getDiff(baseRef, headRef) {
|
|
11
|
+
const range = headRef ? `${baseRef}...${headRef}` : baseRef;
|
|
12
|
+
const nameStatus = (0, child_process_1.execSync)(`git diff --name-status ${range}`, { encoding: "utf-8" });
|
|
13
|
+
const rawDiff = (0, child_process_1.execSync)(`git diff ${range}`, { encoding: "utf-8" });
|
|
14
|
+
const fileStatuses = parseNameStatus(nameStatus);
|
|
15
|
+
const fileDiffs = parseDiffText(rawDiff);
|
|
16
|
+
// Merge name-status info with parsed diffs
|
|
17
|
+
const merged = [];
|
|
18
|
+
for (const fs of fileStatuses) {
|
|
19
|
+
const diff = fileDiffs.find((d) => d.path === fs.path) || {
|
|
20
|
+
path: fs.path,
|
|
21
|
+
hunks: "",
|
|
22
|
+
additions: 0,
|
|
23
|
+
deletions: 0,
|
|
24
|
+
};
|
|
25
|
+
const oldContent = fs.status !== "added"
|
|
26
|
+
? getFileContent(baseRef, fs.oldPath || fs.path)
|
|
27
|
+
: undefined;
|
|
28
|
+
const newContent = fs.status !== "deleted"
|
|
29
|
+
? getFileContent(headRef || "HEAD", fs.path)
|
|
30
|
+
: undefined;
|
|
31
|
+
merged.push({
|
|
32
|
+
path: fs.path,
|
|
33
|
+
oldPath: fs.oldPath,
|
|
34
|
+
status: fs.status,
|
|
35
|
+
hunks: diff.hunks,
|
|
36
|
+
oldContent,
|
|
37
|
+
newContent,
|
|
38
|
+
additions: diff.additions,
|
|
39
|
+
deletions: diff.deletions,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return { files: merged, rawDiff };
|
|
43
|
+
}
|
|
44
|
+
function parseNameStatus(text) {
|
|
45
|
+
const results = [];
|
|
46
|
+
for (const line of text.trim().split("\n")) {
|
|
47
|
+
if (!line.trim())
|
|
48
|
+
continue;
|
|
49
|
+
const parts = line.split("\t");
|
|
50
|
+
const code = parts[0].charAt(0);
|
|
51
|
+
if (code === "R") {
|
|
52
|
+
results.push({ path: parts[2], oldPath: parts[1], status: "renamed" });
|
|
53
|
+
}
|
|
54
|
+
else if (code === "A") {
|
|
55
|
+
results.push({ path: parts[1], status: "added" });
|
|
56
|
+
}
|
|
57
|
+
else if (code === "D") {
|
|
58
|
+
results.push({ path: parts[1], status: "deleted" });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
results.push({ path: parts[1], status: "modified" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Parse unified diff text into per-file FileDiff objects.
|
|
68
|
+
*/
|
|
69
|
+
function parseDiffText(text) {
|
|
70
|
+
if (!text.trim())
|
|
71
|
+
return [];
|
|
72
|
+
const files = [];
|
|
73
|
+
// Split on diff headers
|
|
74
|
+
const parts = text.split(/^diff --git /m).filter(Boolean);
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
const lines = part.split("\n");
|
|
77
|
+
// First line: a/path b/path
|
|
78
|
+
const headerMatch = lines[0].match(/a\/(.+?) b\/(.+)/);
|
|
79
|
+
if (!headerMatch)
|
|
80
|
+
continue;
|
|
81
|
+
const path = headerMatch[2];
|
|
82
|
+
// Find the start of hunks (@@)
|
|
83
|
+
let hunkStart = 0;
|
|
84
|
+
for (let i = 1; i < lines.length; i++) {
|
|
85
|
+
if (lines[i].startsWith("@@")) {
|
|
86
|
+
hunkStart = i;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const hunkLines = hunkStart > 0 ? lines.slice(hunkStart) : [];
|
|
91
|
+
const hunks = hunkLines.join("\n");
|
|
92
|
+
let additions = 0;
|
|
93
|
+
let deletions = 0;
|
|
94
|
+
for (const line of hunkLines) {
|
|
95
|
+
if (line.startsWith("+") && !line.startsWith("+++"))
|
|
96
|
+
additions++;
|
|
97
|
+
if (line.startsWith("-") && !line.startsWith("---"))
|
|
98
|
+
deletions++;
|
|
99
|
+
}
|
|
100
|
+
files.push({ path, hunks, additions, deletions });
|
|
101
|
+
}
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
function getFileContent(ref, filePath) {
|
|
105
|
+
try {
|
|
106
|
+
return (0, child_process_1.execSync)(`git show ${ref}:${filePath}`, { encoding: "utf-8" });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=git-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-diff.js","sourceRoot":"","sources":["../../src/explain/git-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderReport = renderReport;
|
|
4
|
+
function escapeHtml(str) {
|
|
5
|
+
return str
|
|
6
|
+
.replace(/&/g, "&")
|
|
7
|
+
.replace(/</g, "<")
|
|
8
|
+
.replace(/>/g, ">")
|
|
9
|
+
.replace(/"/g, """);
|
|
10
|
+
}
|
|
11
|
+
const STATUS_ICON = {
|
|
12
|
+
added: "+", // +
|
|
13
|
+
modified: "✎", // pencil
|
|
14
|
+
deleted: "✕", // x
|
|
15
|
+
renamed: "→", // arrow
|
|
16
|
+
};
|
|
17
|
+
const ACTION_BADGE = {
|
|
18
|
+
added: { label: "added", color: "#22c55e" },
|
|
19
|
+
removed: { label: "removed", color: "#ef4444" },
|
|
20
|
+
modified: { label: "modified", color: "#f59e0b" },
|
|
21
|
+
};
|
|
22
|
+
const RISK_COLORS = {
|
|
23
|
+
low: { bg: "#dcfce7", text: "#166534" },
|
|
24
|
+
medium: { bg: "#fef3c7", text: "#92400e" },
|
|
25
|
+
high: { bg: "#fee2e2", text: "#991b1b" },
|
|
26
|
+
};
|
|
27
|
+
function renderReport(report) {
|
|
28
|
+
const { explanation, summary, files } = report;
|
|
29
|
+
const riskBadges = explanation.risks.length > 0
|
|
30
|
+
? explanation.risks.map((r) => {
|
|
31
|
+
const c = RISK_COLORS[r.level] || RISK_COLORS.low;
|
|
32
|
+
return `<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:13px;background:${c.bg};color:${c.text};margin-right:6px;">${r.level.toUpperCase()}: ${escapeHtml(r.description)}</span>`;
|
|
33
|
+
}).join("\n")
|
|
34
|
+
: '<span style="color:#166534;">No risks identified</span>';
|
|
35
|
+
const filesTable = files.map((f) => {
|
|
36
|
+
const icon = STATUS_ICON[f.status] || "";
|
|
37
|
+
const changeCount = f.structuralChanges.length;
|
|
38
|
+
return `<tr>
|
|
39
|
+
<td>${icon}</td>
|
|
40
|
+
<td style="font-family:monospace;font-size:13px;">${escapeHtml(f.path)}</td>
|
|
41
|
+
<td>${f.status}</td>
|
|
42
|
+
<td>${f.language || "—"}</td>
|
|
43
|
+
<td>${changeCount}</td>
|
|
44
|
+
</tr>`;
|
|
45
|
+
}).join("\n");
|
|
46
|
+
const fileDetails = files.map((f) => {
|
|
47
|
+
const changesList = f.structuralChanges.length > 0
|
|
48
|
+
? "<ul style=\"margin:8px 0;padding-left:20px;\">" +
|
|
49
|
+
f.structuralChanges.map((c) => {
|
|
50
|
+
const badge = ACTION_BADGE[c.action] || ACTION_BADGE.modified;
|
|
51
|
+
return `<li><span style="display:inline-block;padding:1px 6px;border-radius:3px;font-size:11px;background:${badge.color};color:white;margin-right:4px;">${badge.label}</span> <code>${escapeHtml(c.name)}</code> <span style="color:#666;">(${c.type})</span></li>`;
|
|
52
|
+
}).join("\n") +
|
|
53
|
+
"</ul>"
|
|
54
|
+
: "<p style=\"color:#888;margin:8px 0;\">No structural changes detected</p>";
|
|
55
|
+
const diffBlock = f.rawDiff
|
|
56
|
+
? `<pre style="background:#1e1e1e;color:#d4d4d4;padding:12px;border-radius:6px;overflow-x:auto;font-size:12px;line-height:1.5;margin:8px 0;">${colorDiff(f.rawDiff)}</pre>`
|
|
57
|
+
: "<p style=\"color:#888;\">No diff available</p>";
|
|
58
|
+
return `<details style="margin-bottom:12px;border:1px solid #e5e7eb;border-radius:8px;padding:0;">
|
|
59
|
+
<summary style="padding:10px 14px;cursor:pointer;font-family:monospace;font-size:14px;background:#f9fafb;border-radius:8px;">${STATUS_ICON[f.status] || ""} ${escapeHtml(f.path)} <span style="color:#888;">(${f.structuralChanges.length} changes)</span></summary>
|
|
60
|
+
<div style="padding:10px 14px;">
|
|
61
|
+
${changesList}
|
|
62
|
+
${diffBlock}
|
|
63
|
+
</div>
|
|
64
|
+
</details>`;
|
|
65
|
+
}).join("\n");
|
|
66
|
+
return `<!DOCTYPE html>
|
|
67
|
+
<html lang="en">
|
|
68
|
+
<head>
|
|
69
|
+
<meta charset="utf-8">
|
|
70
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
71
|
+
<title>${escapeHtml(explanation.title)}</title>
|
|
72
|
+
<style>
|
|
73
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
74
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 900px; margin: 0 auto; padding: 24px; color: #1f2937; background: #fff; line-height: 1.6; }
|
|
75
|
+
h1 { font-size: 22px; margin-bottom: 6px; }
|
|
76
|
+
h2 { font-size: 16px; margin: 24px 0 10px; color: #374151; border-bottom: 1px solid #e5e7eb; padding-bottom: 6px; }
|
|
77
|
+
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
|
|
78
|
+
th, td { text-align: left; padding: 6px 10px; border-bottom: 1px solid #f3f4f6; font-size: 13px; }
|
|
79
|
+
th { background: #f9fafb; font-weight: 600; }
|
|
80
|
+
.stats { display: flex; gap: 16px; margin: 12px 0; }
|
|
81
|
+
.stat { padding: 8px 14px; border-radius: 6px; background: #f3f4f6; font-size: 14px; }
|
|
82
|
+
.stat b { font-size: 18px; }
|
|
83
|
+
.meta { color: #6b7280; font-size: 12px; margin-bottom: 16px; }
|
|
84
|
+
</style>
|
|
85
|
+
</head>
|
|
86
|
+
<body>
|
|
87
|
+
<h1>${escapeHtml(explanation.title)}</h1>
|
|
88
|
+
<div class="meta">${escapeHtml(report.baseRef)} → ${escapeHtml(report.headRef)} · ${escapeHtml(report.generatedAt)}</div>
|
|
89
|
+
|
|
90
|
+
<div class="stats">
|
|
91
|
+
<div class="stat"><b>${summary.filesChanged}</b> files changed</div>
|
|
92
|
+
<div class="stat" style="color:#166534;"><b>+${summary.additions}</b> additions</div>
|
|
93
|
+
<div class="stat" style="color:#991b1b;"><b>-${summary.deletions}</b> deletions</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<h2>Description</h2>
|
|
97
|
+
<p style="margin:8px 0;">${escapeHtml(explanation.description)}</p>
|
|
98
|
+
|
|
99
|
+
<h2>Risk Assessment</h2>
|
|
100
|
+
<div style="margin:8px 0;">${riskBadges}</div>
|
|
101
|
+
|
|
102
|
+
<h2>Files Overview</h2>
|
|
103
|
+
<table>
|
|
104
|
+
<thead><tr><th></th><th>File</th><th>Status</th><th>Language</th><th>Changes</th></tr></thead>
|
|
105
|
+
<tbody>${filesTable}</tbody>
|
|
106
|
+
</table>
|
|
107
|
+
|
|
108
|
+
<h2>File Details</h2>
|
|
109
|
+
${fileDetails}
|
|
110
|
+
|
|
111
|
+
<div style="margin-top:32px;padding-top:12px;border-top:1px solid #e5e7eb;color:#9ca3af;font-size:11px;">
|
|
112
|
+
Generated by intent-spec explain · Tokens: ${explanation.tokenUsage.input} in / ${explanation.tokenUsage.output} out
|
|
113
|
+
</div>
|
|
114
|
+
</body>
|
|
115
|
+
</html>`;
|
|
116
|
+
}
|
|
117
|
+
function colorDiff(diff) {
|
|
118
|
+
return escapeHtml(diff)
|
|
119
|
+
.split("\n")
|
|
120
|
+
.map((line) => {
|
|
121
|
+
if (line.startsWith("+"))
|
|
122
|
+
return `<span style="color:#4ade80;">${line}</span>`;
|
|
123
|
+
if (line.startsWith("-"))
|
|
124
|
+
return `<span style="color:#f87171;">${line}</span>`;
|
|
125
|
+
if (line.startsWith("@@"))
|
|
126
|
+
return `<span style="color:#60a5fa;">${line}</span>`;
|
|
127
|
+
return line;
|
|
128
|
+
})
|
|
129
|
+
.join("\n");
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=html-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-report.js","sourceRoot":"","sources":["../../src/explain/html-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
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.explainChanges = explainChanges;
|
|
7
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
8
|
+
const SYSTEM_PROMPT = "You analyze code changes. Respond ONLY with valid JSON. Be extremely concise.";
|
|
9
|
+
const ACTION_ICON = {
|
|
10
|
+
added: "+",
|
|
11
|
+
removed: "-",
|
|
12
|
+
modified: "~",
|
|
13
|
+
};
|
|
14
|
+
async function explainChanges(files, rawDiff) {
|
|
15
|
+
const structuralSummary = files
|
|
16
|
+
.filter((f) => f.structuralChanges.length > 0)
|
|
17
|
+
.map((f) => {
|
|
18
|
+
const changes = f.structuralChanges
|
|
19
|
+
.map((c) => `${ACTION_ICON[c.action]}${c.name} (${c.type})`)
|
|
20
|
+
.join(", ");
|
|
21
|
+
return `- ${f.path} (${f.status}): ${changes}`;
|
|
22
|
+
})
|
|
23
|
+
.join("\n");
|
|
24
|
+
// Truncate diff to ~4000 chars, prioritizing modified files
|
|
25
|
+
const truncatedDiff = truncateDiff(rawDiff, 4000);
|
|
26
|
+
const prompt = `## Structural changes
|
|
27
|
+
${structuralSummary || "(no structural changes detected)"}
|
|
28
|
+
|
|
29
|
+
## Diff (truncated)
|
|
30
|
+
${truncatedDiff || "(empty diff)"}
|
|
31
|
+
|
|
32
|
+
Respond with JSON:
|
|
33
|
+
{ "title": "<60 char PR title>",
|
|
34
|
+
"description": "<2-4 sentences summarizing the changes>",
|
|
35
|
+
"risks": [{"level":"low|medium|high","description":"<one sentence>"}] }`;
|
|
36
|
+
const client = new sdk_1.default();
|
|
37
|
+
const message = await client.messages.create({
|
|
38
|
+
model: "claude-sonnet-4-5-20250929",
|
|
39
|
+
max_tokens: 512,
|
|
40
|
+
system: SYSTEM_PROMPT,
|
|
41
|
+
messages: [{ role: "user", content: prompt }],
|
|
42
|
+
});
|
|
43
|
+
const text = message.content
|
|
44
|
+
.filter((block) => block.type === "text")
|
|
45
|
+
.map((block) => block.text)
|
|
46
|
+
.join("\n");
|
|
47
|
+
const tokenUsage = {
|
|
48
|
+
input: message.usage?.input_tokens || 0,
|
|
49
|
+
output: message.usage?.output_tokens || 0,
|
|
50
|
+
};
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
const cleaned = text.replace(/^```json\s*/m, "").replace(/```\s*$/m, "").trim();
|
|
54
|
+
parsed = JSON.parse(cleaned);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Fallback if LLM doesn't return valid JSON
|
|
58
|
+
parsed = {
|
|
59
|
+
title: "Code changes",
|
|
60
|
+
description: text.slice(0, 200),
|
|
61
|
+
risks: [{ level: "low", description: "LLM response was not valid JSON; showing raw text." }],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
title: parsed.title || "Code changes",
|
|
66
|
+
description: parsed.description || "",
|
|
67
|
+
risks: Array.isArray(parsed.risks) ? parsed.risks : [],
|
|
68
|
+
tokenUsage,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function truncateDiff(diff, maxLen) {
|
|
72
|
+
if (diff.length <= maxLen)
|
|
73
|
+
return diff;
|
|
74
|
+
// Split by file sections, keep as many as fit
|
|
75
|
+
const sections = diff.split(/^diff --git /m).filter(Boolean);
|
|
76
|
+
let result = "";
|
|
77
|
+
for (const section of sections) {
|
|
78
|
+
const chunk = `diff --git ${section}`;
|
|
79
|
+
if (result.length + chunk.length > maxLen) {
|
|
80
|
+
result += "\n... (diff truncated)";
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
result += chunk;
|
|
84
|
+
}
|
|
85
|
+
return result || diff.slice(0, maxLen) + "\n... (truncated)";
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=llm-explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-explain.js","sourceRoot":"","sources":["../../src/explain/llm-explain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type FileStatus = "added" | "modified" | "deleted" | "renamed";
|
|
2
|
+
export interface FileDiff {
|
|
3
|
+
path: string;
|
|
4
|
+
oldPath?: string;
|
|
5
|
+
status: FileStatus;
|
|
6
|
+
hunks: string;
|
|
7
|
+
oldContent?: string;
|
|
8
|
+
newContent?: string;
|
|
9
|
+
additions: number;
|
|
10
|
+
deletions: number;
|
|
11
|
+
}
|
|
12
|
+
export type ChangeAction = "added" | "removed" | "modified";
|
|
13
|
+
export type ChangeType = "function" | "import" | "export" | "class" | "variable" | "route" | "other";
|
|
14
|
+
export interface StructuralChange {
|
|
15
|
+
file: string;
|
|
16
|
+
type: ChangeType;
|
|
17
|
+
action: ChangeAction;
|
|
18
|
+
name: string;
|
|
19
|
+
startLine?: number;
|
|
20
|
+
detail?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface FileAnalysis {
|
|
23
|
+
path: string;
|
|
24
|
+
status: FileStatus;
|
|
25
|
+
language: string | null;
|
|
26
|
+
structuralChanges: StructuralChange[];
|
|
27
|
+
rawDiff: string;
|
|
28
|
+
}
|
|
29
|
+
export interface Risk {
|
|
30
|
+
level: "low" | "medium" | "high";
|
|
31
|
+
description: string;
|
|
32
|
+
}
|
|
33
|
+
export interface LLMExplanation {
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
risks: Risk[];
|
|
37
|
+
tokenUsage: {
|
|
38
|
+
input: number;
|
|
39
|
+
output: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface ExplainReport {
|
|
43
|
+
generatedAt: string;
|
|
44
|
+
baseRef: string;
|
|
45
|
+
headRef: string;
|
|
46
|
+
summary: {
|
|
47
|
+
filesChanged: number;
|
|
48
|
+
additions: number;
|
|
49
|
+
deletions: number;
|
|
50
|
+
};
|
|
51
|
+
explanation: LLMExplanation;
|
|
52
|
+
files: FileAnalysis[];
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/explain/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLanguageForExt = exports.language = exports.Query = exports.parseSource = exports.parseFile = void 0;
|
|
4
|
+
var parser_1 = require("./parser");
|
|
5
|
+
Object.defineProperty(exports, "parseFile", { enumerable: true, get: function () { return parser_1.parseFile; } });
|
|
6
|
+
Object.defineProperty(exports, "parseSource", { enumerable: true, get: function () { return parser_1.parseSource; } });
|
|
7
|
+
Object.defineProperty(exports, "Query", { enumerable: true, get: function () { return parser_1.Query; } });
|
|
8
|
+
Object.defineProperty(exports, "language", { enumerable: true, get: function () { return parser_1.language; } });
|
|
9
|
+
Object.defineProperty(exports, "getLanguageForExt", { enumerable: true, get: function () { return parser_1.getLanguageForExt; } });
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parsing/index.ts"],"names":[],"mappings":";;;AAAA,mCAAsF;AAA7E,mGAAA,SAAS,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,+FAAA,KAAK,OAAA;AAAE,kGAAA,QAAQ,OAAA;AAAE,2GAAA,iBAAiB,OAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Parser from "tree-sitter";
|
|
2
|
+
export type { Parser };
|
|
3
|
+
export type Tree = Parser.Tree;
|
|
4
|
+
export type SyntaxNode = Parser.SyntaxNode;
|
|
5
|
+
export type QueryMatch = Parser.QueryMatch;
|
|
6
|
+
export type QueryCapture = Parser.QueryCapture;
|
|
7
|
+
export declare const Query: typeof Parser.Query;
|
|
8
|
+
/** Backward-compat export: the JavaScript language */
|
|
9
|
+
export declare const language: Parser.Language;
|
|
10
|
+
export declare function getLanguageForExt(ext: string): Parser.Language;
|
|
11
|
+
export declare function parseSource(source: string, ext?: string): {
|
|
12
|
+
tree: Tree;
|
|
13
|
+
source: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function parseFile(filePath: string): {
|
|
16
|
+
tree: Tree;
|
|
17
|
+
source: string;
|
|
18
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
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.language = exports.Query = void 0;
|
|
7
|
+
exports.getLanguageForExt = getLanguageForExt;
|
|
8
|
+
exports.parseSource = parseSource;
|
|
9
|
+
exports.parseFile = parseFile;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const tree_sitter_1 = __importDefault(require("tree-sitter"));
|
|
13
|
+
const tree_sitter_javascript_1 = __importDefault(require("tree-sitter-javascript"));
|
|
14
|
+
const tree_sitter_typescript_1 = __importDefault(require("tree-sitter-typescript"));
|
|
15
|
+
exports.Query = tree_sitter_1.default.Query;
|
|
16
|
+
const jsLanguage = tree_sitter_javascript_1.default;
|
|
17
|
+
const tsLanguage = tree_sitter_typescript_1.default.typescript;
|
|
18
|
+
const tsxLanguage = tree_sitter_typescript_1.default.tsx;
|
|
19
|
+
/** Backward-compat export: the JavaScript language */
|
|
20
|
+
exports.language = jsLanguage;
|
|
21
|
+
const parsers = new Map();
|
|
22
|
+
function getParser(lang) {
|
|
23
|
+
let p = parsers.get(lang);
|
|
24
|
+
if (!p) {
|
|
25
|
+
p = new tree_sitter_1.default();
|
|
26
|
+
p.setLanguage(lang);
|
|
27
|
+
parsers.set(lang, p);
|
|
28
|
+
}
|
|
29
|
+
return p;
|
|
30
|
+
}
|
|
31
|
+
const extToLanguage = {
|
|
32
|
+
".js": jsLanguage,
|
|
33
|
+
".ts": tsLanguage,
|
|
34
|
+
".tsx": tsxLanguage,
|
|
35
|
+
};
|
|
36
|
+
function getLanguageForExt(ext) {
|
|
37
|
+
return extToLanguage[ext] || jsLanguage;
|
|
38
|
+
}
|
|
39
|
+
function parseSource(source, ext = ".js") {
|
|
40
|
+
const lang = getLanguageForExt(ext);
|
|
41
|
+
const tree = getParser(lang).parse(source);
|
|
42
|
+
return { tree, source };
|
|
43
|
+
}
|
|
44
|
+
function parseFile(filePath) {
|
|
45
|
+
const source = fs_1.default.readFileSync(filePath, "utf-8");
|
|
46
|
+
const ext = path_1.default.extname(filePath);
|
|
47
|
+
return parseSource(source, ext);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/parsing/parser.ts"],"names":[],"mappings":";;;;;;AAsCA,8CAEC;AAED,kCAOC;AAED,8BAIC;AAvDD,4CAAoB;AACpB,gDAAwB;AACxB,8DAAiC;AACjC,oFAAgD;AAChD,oFAAyD;AAO5C,QAAA,KAAK,GAAG,qBAAM,CAAC,KAAK,CAAC;AAElC,MAAM,UAAU,GAAG,gCAAwC,CAAC;AAC5D,MAAM,UAAU,GAAG,gCAAmB,CAAC,UAAwC,CAAC;AAChF,MAAM,WAAW,GAAG,gCAAmB,CAAC,GAAiC,CAAC;AAE1E,sDAAsD;AACzC,QAAA,QAAQ,GAAG,UAAU,CAAC;AAEnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEnD,SAAS,SAAS,CAAC,IAAqB;IACtC,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,GAAG,IAAI,qBAAM,EAAE,CAAC;QACjB,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,aAAa,GAAoC;IACrD,KAAK,EAAE,UAAU;IACjB,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,WAAW;CACpB,CAAC;AAEF,SAAgB,iBAAiB,CAAC,GAAW;IAC3C,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC;AAC1C,CAAC;AAED,SAAgB,WAAW,CACzB,MAAc,EACd,MAAc,KAAK;IAEnB,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC;AAED,SAAgB,SAAS,CAAC,QAAgB;IACxC,MAAM,MAAM,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,OAAO,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAClC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "diffintel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-powered structural diff explainer for pull requests",
|
|
5
|
+
"bin": {
|
|
6
|
+
"diffintel": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test": "tsc && node --import tsx --test test/unit/*.test.ts",
|
|
12
|
+
"prepublishOnly": "tsc"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["diff", "pull-request", "code-review", "ast", "tree-sitter", "ai", "llm"],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
18
|
+
"commander": "^14.0.3",
|
|
19
|
+
"dotenv": "^17.2.4",
|
|
20
|
+
"tree-sitter": "^0.25.0",
|
|
21
|
+
"tree-sitter-javascript": "^0.25.0",
|
|
22
|
+
"tree-sitter-typescript": "^0.23.2"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.19.11",
|
|
26
|
+
"tsx": "^4.21.0",
|
|
27
|
+
"typescript": "^5.9.3"
|
|
28
|
+
}
|
|
29
|
+
}
|