diffdoc 0.6.2 → 0.6.4
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/.diffdocrc.example +19 -17
- package/dist/commands/prune.js +186 -0
- package/dist/commands/status.js +43 -8
- package/dist/commands/summarize.js +11 -102
- package/dist/commands/validate.js +5 -0
- package/dist/config.js +1 -0
- package/dist/index.js +22 -2
- package/dist/schemas.js +1 -0
- package/dist/utils/scan.js +105 -0
- package/package.json +52 -52
- package/schemas/v2/diffdocrc.schema.json +3 -0
package/.diffdocrc.example
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/sullyTheDev/diffdoc/main/schemas/v2/diffdocrc.schema.json",
|
|
3
|
+
"baseDir": "./.diffdoc",
|
|
4
|
+
"repoPath": ".",
|
|
5
|
+
"aiProvider": "local",
|
|
6
|
+
"localLlmEndpoint": "http://localhost:11434/v1",
|
|
7
|
+
"localEmbedEndpoint": "http://localhost:11434/v1/embeddings",
|
|
8
|
+
"localChatModel": "qwen2.5-coder:7b",
|
|
9
|
+
"localEmbedModel": "nomic-embed-code",
|
|
10
|
+
"cloudLlmEndpoint": "https://api.openai.com/v1",
|
|
11
|
+
"cloudChatModel": "gpt-4o-mini",
|
|
12
|
+
"cloudEmbedModel": "text-embedding-3-small",
|
|
13
|
+
"embedBatchSize": 25,
|
|
14
|
+
"summarizeConcurrency": 2,
|
|
15
|
+
"openaiApiKey": "",
|
|
16
|
+
"includeGlobs": [],
|
|
17
|
+
"excludeGlobs": [],
|
|
18
|
+
"ignoreFile": ".diffdocignore"
|
|
19
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
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.runPrune = runPrune;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const promises_2 = require("node:readline/promises");
|
|
10
|
+
const node_process_1 = require("node:process");
|
|
11
|
+
const artifacts_1 = require("../types/artifacts");
|
|
12
|
+
const paths_1 = require("../utils/paths");
|
|
13
|
+
const scan_1 = require("../utils/scan");
|
|
14
|
+
function getSummaryDir(manifestPath) {
|
|
15
|
+
return node_path_1.default.resolve(node_path_1.default.dirname(manifestPath), "summaries");
|
|
16
|
+
}
|
|
17
|
+
function getSummaryPath(summaryDir, hash) {
|
|
18
|
+
return node_path_1.default.resolve(summaryDir, `${hash}.json`);
|
|
19
|
+
}
|
|
20
|
+
async function readManifest(manifestPath) {
|
|
21
|
+
const raw = JSON.parse(await promises_1.default.readFile(manifestPath, "utf8"));
|
|
22
|
+
const result = artifacts_1.RepoManifestSchema.safeParse(raw);
|
|
23
|
+
if (!result.success) {
|
|
24
|
+
const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
25
|
+
throw new Error(`Invalid manifest in ${manifestPath}:\n${issues}`);
|
|
26
|
+
}
|
|
27
|
+
return result.data;
|
|
28
|
+
}
|
|
29
|
+
async function writeManifest(manifestPath, manifest) {
|
|
30
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(manifestPath), { recursive: true });
|
|
31
|
+
await promises_1.default.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
32
|
+
}
|
|
33
|
+
function countHashRefs(files) {
|
|
34
|
+
const refs = new Map();
|
|
35
|
+
for (const hash of Object.values(files)) {
|
|
36
|
+
refs.set(hash, (refs.get(hash) || 0) + 1);
|
|
37
|
+
}
|
|
38
|
+
return refs;
|
|
39
|
+
}
|
|
40
|
+
async function fileExists(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
await promises_1.default.access(filePath);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function promptConfirm(message) {
|
|
50
|
+
const rl = (0, promises_2.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
51
|
+
try {
|
|
52
|
+
const answer = await rl.question(`${message} (y/N): `);
|
|
53
|
+
return answer.trim().toLowerCase() === "y";
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
rl.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function runPrune(options, config) {
|
|
60
|
+
const commandCwd = process.cwd();
|
|
61
|
+
const repoPath = node_path_1.default.resolve(commandCwd, options.path || config.repoPath);
|
|
62
|
+
const manifestPath = (0, paths_1.resolveDiffdocArtifactPath)(options.manifest, config.baseDir);
|
|
63
|
+
const summaryDir = getSummaryDir(manifestPath);
|
|
64
|
+
const manifest = await readManifest(manifestPath);
|
|
65
|
+
const manifestFilePaths = Object.keys(manifest.files);
|
|
66
|
+
// Scan eligible files
|
|
67
|
+
const includePatterns = (0, scan_1.compileGlobs)(config.summarize.includeGlobs.map(scan_1.normalizeGlobPattern));
|
|
68
|
+
const excludePatterns = (0, scan_1.compileGlobs)(config.summarize.excludeGlobs.map(scan_1.normalizeGlobPattern));
|
|
69
|
+
const ignoreMatcher = await (0, scan_1.readIgnoreMatcher)(repoPath, config.summarize.ignoreFile);
|
|
70
|
+
const scannedFiles = await (0, scan_1.walkCodeFiles)(repoPath, includePatterns, excludePatterns, ignoreMatcher);
|
|
71
|
+
const scannedFileSet = new Set(scannedFiles);
|
|
72
|
+
// Identify entries to prune
|
|
73
|
+
const toPrune = [];
|
|
74
|
+
for (const filePath of manifestFilePaths) {
|
|
75
|
+
if (scannedFileSet.has(filePath)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Check if the file is deleted from disk or just excluded by rules
|
|
79
|
+
const absolutePath = node_path_1.default.resolve(repoPath, filePath);
|
|
80
|
+
const exists = await fileExists(absolutePath);
|
|
81
|
+
toPrune.push({
|
|
82
|
+
filePath,
|
|
83
|
+
reason: exists ? "excluded" : "deleted"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (toPrune.length === 0) {
|
|
87
|
+
if (options.json) {
|
|
88
|
+
const report = {
|
|
89
|
+
scannedFileCount: scannedFiles.length,
|
|
90
|
+
manifestFileCount: manifestFilePaths.length,
|
|
91
|
+
pruned: [],
|
|
92
|
+
manifestEntriesRemoved: 0,
|
|
93
|
+
summaryAssetsDeleted: 0
|
|
94
|
+
};
|
|
95
|
+
console.log(JSON.stringify(report, null, 2));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(`Scanned files: ${scannedFiles.length}`);
|
|
99
|
+
console.log(`Manifest files: ${manifestFilePaths.length}`);
|
|
100
|
+
console.log("Nothing to prune.");
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Dry run: report and exit
|
|
105
|
+
if (options.dryRun) {
|
|
106
|
+
if (options.json) {
|
|
107
|
+
const report = {
|
|
108
|
+
scannedFileCount: scannedFiles.length,
|
|
109
|
+
manifestFileCount: manifestFilePaths.length,
|
|
110
|
+
pruned: toPrune,
|
|
111
|
+
manifestEntriesRemoved: toPrune.length,
|
|
112
|
+
summaryAssetsDeleted: 0 // unknown until actual execution
|
|
113
|
+
};
|
|
114
|
+
console.log(JSON.stringify(report, null, 2));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(`Scanned files: ${scannedFiles.length}`);
|
|
118
|
+
console.log(`Manifest files: ${manifestFilePaths.length}`);
|
|
119
|
+
console.log(`Files to prune: ${toPrune.length}`);
|
|
120
|
+
for (const entry of toPrune) {
|
|
121
|
+
console.log(` - ${entry.filePath} (${entry.reason})`);
|
|
122
|
+
}
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log("Dry run complete. Use without --dry-run to execute.");
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Confirmation prompt
|
|
129
|
+
if (!options.yes) {
|
|
130
|
+
if (!options.json) {
|
|
131
|
+
console.log(`Scanned files: ${scannedFiles.length}`);
|
|
132
|
+
console.log(`Manifest files: ${manifestFilePaths.length}`);
|
|
133
|
+
console.log(`Files to prune: ${toPrune.length}`);
|
|
134
|
+
for (const entry of toPrune) {
|
|
135
|
+
console.log(` - ${entry.filePath} (${entry.reason})`);
|
|
136
|
+
}
|
|
137
|
+
console.log("");
|
|
138
|
+
}
|
|
139
|
+
const confirmed = await promptConfirm(`Remove ${toPrune.length} entries from manifest?`);
|
|
140
|
+
if (!confirmed) {
|
|
141
|
+
console.log("Aborted.");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Execute pruning
|
|
146
|
+
const refs = countHashRefs(manifest.files);
|
|
147
|
+
let summaryAssetsDeleted = 0;
|
|
148
|
+
for (const entry of toPrune) {
|
|
149
|
+
const hash = manifest.files[entry.filePath];
|
|
150
|
+
if (!hash) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
delete manifest.files[entry.filePath];
|
|
154
|
+
const newRefCount = Math.max((refs.get(hash) || 1) - 1, 0);
|
|
155
|
+
refs.set(hash, newRefCount);
|
|
156
|
+
// Delete summary asset if no longer referenced
|
|
157
|
+
if (newRefCount === 0) {
|
|
158
|
+
const summaryPath = getSummaryPath(summaryDir, hash);
|
|
159
|
+
try {
|
|
160
|
+
await promises_1.default.unlink(summaryPath);
|
|
161
|
+
summaryAssetsDeleted += 1;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
const nodeError = error;
|
|
165
|
+
if (nodeError.code !== "ENOENT") {
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
await writeManifest(manifestPath, manifest);
|
|
172
|
+
const report = {
|
|
173
|
+
scannedFileCount: scannedFiles.length,
|
|
174
|
+
manifestFileCount: manifestFilePaths.length,
|
|
175
|
+
pruned: toPrune,
|
|
176
|
+
manifestEntriesRemoved: toPrune.length,
|
|
177
|
+
summaryAssetsDeleted
|
|
178
|
+
};
|
|
179
|
+
if (options.json) {
|
|
180
|
+
console.log(JSON.stringify(report, null, 2));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.log(`Removed ${toPrune.length} manifest entries.`);
|
|
184
|
+
console.log(`Deleted ${summaryAssetsDeleted} orphaned summary assets.`);
|
|
185
|
+
}
|
|
186
|
+
}
|
package/dist/commands/status.js
CHANGED
|
@@ -11,6 +11,7 @@ const embed_1 = require("./embed");
|
|
|
11
11
|
const artifacts_1 = require("../types/artifacts");
|
|
12
12
|
const paths_1 = require("../utils/paths");
|
|
13
13
|
const llm_1 = require("../utils/llm");
|
|
14
|
+
const scan_1 = require("../utils/scan");
|
|
14
15
|
function getSummaryDir(manifestPath) {
|
|
15
16
|
return node_path_1.default.resolve(node_path_1.default.dirname(manifestPath), "summaries");
|
|
16
17
|
}
|
|
@@ -140,6 +141,12 @@ async function getIndexFreshness(manifest, config) {
|
|
|
140
141
|
extra
|
|
141
142
|
};
|
|
142
143
|
}
|
|
144
|
+
async function scanEligibleFiles(repoPath, config) {
|
|
145
|
+
const includePatterns = (0, scan_1.compileGlobs)(config.summarize.includeGlobs.map(scan_1.normalizeGlobPattern));
|
|
146
|
+
const excludePatterns = (0, scan_1.compileGlobs)(config.summarize.excludeGlobs.map(scan_1.normalizeGlobPattern));
|
|
147
|
+
const ignoreMatcher = await (0, scan_1.readIgnoreMatcher)(repoPath, config.summarize.ignoreFile);
|
|
148
|
+
return (0, scan_1.walkCodeFiles)(repoPath, includePatterns, excludePatterns, ignoreMatcher);
|
|
149
|
+
}
|
|
143
150
|
function formatSummaryFreshness(stats) {
|
|
144
151
|
if (stats.missingFromManifestCount === 0 && stats.staleCount === 0) {
|
|
145
152
|
return "fresh";
|
|
@@ -147,6 +154,10 @@ function formatSummaryFreshness(stats) {
|
|
|
147
154
|
return `stale (missing: ${stats.missingFromManifestCount}, stale: ${stats.staleCount})`;
|
|
148
155
|
}
|
|
149
156
|
function buildSummarizeCommand(manifestOption) {
|
|
157
|
+
const command = "diffdoc summarize --mode delta";
|
|
158
|
+
return manifestOption === "manifest.json" ? command : `${command} --out ${manifestOption}`;
|
|
159
|
+
}
|
|
160
|
+
function buildSummarizeRefreshCommand(manifestOption) {
|
|
150
161
|
const command = "diffdoc summarize --mode all --refresh";
|
|
151
162
|
return manifestOption === "manifest.json" ? command : `${command} --out ${manifestOption}`;
|
|
152
163
|
}
|
|
@@ -154,10 +165,22 @@ function buildEmbedCommand(manifestOption) {
|
|
|
154
165
|
const command = "diffdoc embed";
|
|
155
166
|
return manifestOption === "manifest.json" ? command : `${command} --manifest ${manifestOption}`;
|
|
156
167
|
}
|
|
157
|
-
function getNextCommand(manifestOption, summaryStats, indexFreshness) {
|
|
158
|
-
if (
|
|
168
|
+
function getNextCommand(manifestOption, summaryStats, indexFreshness, untrackedCount, newlyExcludedCount) {
|
|
169
|
+
if (newlyExcludedCount > 0) {
|
|
170
|
+
return {
|
|
171
|
+
command: "diffdoc prune",
|
|
172
|
+
reason: `${newlyExcludedCount} file(s) in manifest are no longer eligible`
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (untrackedCount > 0) {
|
|
159
176
|
return {
|
|
160
177
|
command: buildSummarizeCommand(manifestOption),
|
|
178
|
+
reason: `${untrackedCount} eligible file(s) not yet summarized`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (summaryStats.missingFromManifestCount > 0 || summaryStats.staleCount > 0) {
|
|
182
|
+
return {
|
|
183
|
+
command: buildSummarizeRefreshCommand(manifestOption),
|
|
161
184
|
reason: "summary artifacts are missing or stale"
|
|
162
185
|
};
|
|
163
186
|
}
|
|
@@ -178,11 +201,18 @@ function getNextCommand(manifestOption, summaryStats, indexFreshness) {
|
|
|
178
201
|
reason: "summaries and index are fresh"
|
|
179
202
|
};
|
|
180
203
|
}
|
|
181
|
-
function buildStatusReport(manifest, summaryStats, indexFreshness, manifestOption) {
|
|
182
|
-
const
|
|
204
|
+
function buildStatusReport(manifest, scannedFiles, summaryStats, indexFreshness, manifestOption) {
|
|
205
|
+
const manifestFilePaths = new Set(Object.keys(manifest.files));
|
|
206
|
+
const scannedFileSet = new Set(scannedFiles);
|
|
207
|
+
const untrackedCount = scannedFiles.filter((f) => !manifestFilePaths.has(f)).length;
|
|
208
|
+
const newlyExcludedCount = [...manifestFilePaths].filter((f) => !scannedFileSet.has(f)).length;
|
|
209
|
+
const nextCommand = getNextCommand(manifestOption, summaryStats, indexFreshness, untrackedCount, newlyExcludedCount);
|
|
183
210
|
return {
|
|
184
211
|
manifestSchema: manifest.schemaVersion,
|
|
185
|
-
|
|
212
|
+
scannedFileCount: scannedFiles.length,
|
|
213
|
+
trackedFileCount: manifestFilePaths.size,
|
|
214
|
+
untrackedCount,
|
|
215
|
+
newlyExcludedCount,
|
|
186
216
|
summaryFileCount: summaryStats.summaryFileCount,
|
|
187
217
|
orphanCount: summaryStats.orphanCount,
|
|
188
218
|
summaryFreshness: {
|
|
@@ -207,18 +237,23 @@ function formatIndexFreshness(freshness) {
|
|
|
207
237
|
async function runStatus(options, config) {
|
|
208
238
|
const manifestPath = (0, paths_1.resolveDiffdocArtifactPath)(options.manifest, config.baseDir);
|
|
209
239
|
const manifest = await readManifest(manifestPath);
|
|
240
|
+
const commandCwd = process.cwd();
|
|
241
|
+
const repoPath = node_path_1.default.resolve(commandCwd, options.path || config.repoPath);
|
|
242
|
+
const scannedFiles = await scanEligibleFiles(repoPath, config);
|
|
210
243
|
const summaryStats = await getSummaryStats(manifestPath, manifest);
|
|
211
244
|
const indexFreshness = await getIndexFreshness(manifest, config);
|
|
212
|
-
const report = buildStatusReport(manifest, summaryStats, indexFreshness, options.manifest);
|
|
245
|
+
const report = buildStatusReport(manifest, scannedFiles, summaryStats, indexFreshness, options.manifest);
|
|
213
246
|
if (options.json) {
|
|
214
247
|
console.log(JSON.stringify(report, null, 2));
|
|
215
248
|
return;
|
|
216
249
|
}
|
|
217
250
|
console.log(`manifest schema: ${report.manifestSchema}`);
|
|
218
|
-
console.log(`
|
|
251
|
+
console.log(`scanned files: ${report.scannedFileCount}`);
|
|
252
|
+
console.log(`manifest files: ${report.trackedFileCount}`);
|
|
253
|
+
console.log(`untracked: ${report.untrackedCount}`);
|
|
254
|
+
console.log(`newly excluded: ${report.newlyExcludedCount}`);
|
|
219
255
|
console.log(`summary files: ${report.summaryFileCount}`);
|
|
220
256
|
console.log(`orphans: ${report.orphanCount}`);
|
|
221
|
-
console.log(`stale summaries: ${report.summaryFreshness.stale}`);
|
|
222
257
|
console.log(`summary freshness: ${formatSummaryFreshness(summaryStats)}`);
|
|
223
258
|
console.log(`index freshness: ${formatIndexFreshness(indexFreshness)}`);
|
|
224
259
|
console.log("");
|
|
@@ -6,74 +6,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.runSummarize = runSummarize;
|
|
7
7
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const ignore_1 = __importDefault(require("ignore"));
|
|
10
9
|
const artifacts_1 = require("../types/artifacts");
|
|
11
10
|
const schemas_1 = require("../schemas");
|
|
12
11
|
const git_1 = require("../utils/git");
|
|
13
12
|
const hashing_1 = require("../utils/hashing");
|
|
14
13
|
const llm_1 = require("../utils/llm");
|
|
15
14
|
const paths_1 = require("../utils/paths");
|
|
15
|
+
const scan_1 = require("../utils/scan");
|
|
16
16
|
const MANIFEST_SCHEMA_URL = `${schemas_1.SCHEMA_BASE_URL}/manifest.schema.json`;
|
|
17
17
|
const SUMMARY_ASSET_SCHEMA_URL = `${schemas_1.SCHEMA_BASE_URL}/summary-asset.schema.json`;
|
|
18
|
-
function normalizeRelativePath(filePath) {
|
|
19
|
-
return filePath.split(node_path_1.default.sep).join("/");
|
|
20
|
-
}
|
|
21
18
|
function getSummaryDir(manifestPath) {
|
|
22
19
|
return node_path_1.default.resolve(node_path_1.default.dirname(manifestPath), "summaries");
|
|
23
20
|
}
|
|
24
21
|
function getSummaryPath(summaryDir, hash) {
|
|
25
22
|
return node_path_1.default.resolve(summaryDir, `${hash}.json`);
|
|
26
23
|
}
|
|
27
|
-
function normalizeGlobPattern(pattern) {
|
|
28
|
-
return pattern.split(node_path_1.default.sep).join("/");
|
|
29
|
-
}
|
|
30
|
-
function escapeRegex(value) {
|
|
31
|
-
return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
32
|
-
}
|
|
33
|
-
function globToRegExp(pattern) {
|
|
34
|
-
const normalized = normalizeGlobPattern(pattern);
|
|
35
|
-
let regexBody = "";
|
|
36
|
-
for (let i = 0; i < normalized.length; i += 1) {
|
|
37
|
-
const char = normalized[i];
|
|
38
|
-
const next = normalized[i + 1];
|
|
39
|
-
if (char === "*" && next === "*") {
|
|
40
|
-
regexBody += ".*";
|
|
41
|
-
i += 1;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (char === "*") {
|
|
45
|
-
regexBody += "[^/]*";
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (char === "?") {
|
|
49
|
-
regexBody += "[^/]";
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
regexBody += escapeRegex(char);
|
|
53
|
-
}
|
|
54
|
-
return new RegExp(`^${regexBody}$`);
|
|
55
|
-
}
|
|
56
|
-
function compileGlobs(patterns) {
|
|
57
|
-
return patterns.filter(Boolean).map(globToRegExp);
|
|
58
|
-
}
|
|
59
|
-
function matchesAny(filePath, patterns) {
|
|
60
|
-
return patterns.some((pattern) => pattern.test(filePath));
|
|
61
|
-
}
|
|
62
|
-
function shouldIncludeFile(filePath, includeGlobs, excludeGlobs, ignoreMatcher) {
|
|
63
|
-
if (ignoreMatcher.ignores(filePath)) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
if (excludeGlobs.length > 0 && matchesAny(filePath, excludeGlobs)) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
if (includeGlobs.length > 0 && !matchesAny(filePath, includeGlobs)) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
function isIgnoredDirectory(dirPath, ignoreMatcher) {
|
|
75
|
-
return ignoreMatcher.ignores(dirPath) || ignoreMatcher.ignores(`${dirPath}/`);
|
|
76
|
-
}
|
|
77
24
|
async function atomicWriteUtf8(targetPath, content) {
|
|
78
25
|
await promises_1.default.mkdir(node_path_1.default.dirname(targetPath), { recursive: true });
|
|
79
26
|
const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
|
|
@@ -182,44 +129,6 @@ async function readManifest(manifestPath) {
|
|
|
182
129
|
throw error;
|
|
183
130
|
}
|
|
184
131
|
}
|
|
185
|
-
async function readIgnoreMatcher(repoPath, ignoreFilePath) {
|
|
186
|
-
const matcher = (0, ignore_1.default)();
|
|
187
|
-
const absolutePath = node_path_1.default.isAbsolute(ignoreFilePath)
|
|
188
|
-
? ignoreFilePath
|
|
189
|
-
: node_path_1.default.resolve(repoPath, ignoreFilePath);
|
|
190
|
-
try {
|
|
191
|
-
const raw = await promises_1.default.readFile(absolutePath, "utf8");
|
|
192
|
-
return matcher.add(raw);
|
|
193
|
-
}
|
|
194
|
-
catch (error) {
|
|
195
|
-
const nodeError = error;
|
|
196
|
-
if (nodeError.code === "ENOENT") {
|
|
197
|
-
return matcher;
|
|
198
|
-
}
|
|
199
|
-
throw error;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
async function walkCodeFiles(rootPath, includeGlobs, excludeGlobs, ignoreMatcher, currentPath = rootPath) {
|
|
203
|
-
const entries = await promises_1.default.readdir(currentPath, { withFileTypes: true });
|
|
204
|
-
const files = [];
|
|
205
|
-
for (const entry of entries) {
|
|
206
|
-
const entryPath = node_path_1.default.join(currentPath, entry.name);
|
|
207
|
-
if (entry.isDirectory()) {
|
|
208
|
-
const relativePath = normalizeRelativePath(node_path_1.default.relative(rootPath, entryPath));
|
|
209
|
-
if (!isIgnoredDirectory(relativePath, ignoreMatcher)) {
|
|
210
|
-
files.push(...await walkCodeFiles(rootPath, includeGlobs, excludeGlobs, ignoreMatcher, entryPath));
|
|
211
|
-
}
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (entry.isFile()) {
|
|
215
|
-
const relativePath = normalizeRelativePath(node_path_1.default.relative(rootPath, entryPath));
|
|
216
|
-
if (shouldIncludeFile(relativePath, includeGlobs, excludeGlobs, ignoreMatcher)) {
|
|
217
|
-
files.push(relativePath);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return files.sort();
|
|
222
|
-
}
|
|
223
132
|
function countHashRefs(files) {
|
|
224
133
|
const refs = new Map();
|
|
225
134
|
for (const hash of Object.values(files)) {
|
|
@@ -330,19 +239,19 @@ async function runSummarize(options, config) {
|
|
|
330
239
|
}
|
|
331
240
|
const startedAt = new Date();
|
|
332
241
|
const commandCwd = process.cwd();
|
|
333
|
-
const repoPath = node_path_1.default.resolve(commandCwd, options.path);
|
|
242
|
+
const repoPath = node_path_1.default.resolve(commandCwd, options.path || config.repoPath);
|
|
334
243
|
const manifestPath = (0, paths_1.resolveDiffdocArtifactPath)(options.out, config.baseDir);
|
|
335
244
|
const summaryDir = getSummaryDir(manifestPath);
|
|
336
245
|
const manifest = await readManifest(manifestPath);
|
|
337
246
|
const refs = countHashRefs(manifest.files);
|
|
338
|
-
const includePatterns = compileGlobs((options.includeGlobs && options.includeGlobs.length > 0)
|
|
339
|
-
? options.includeGlobs.map(normalizeGlobPattern)
|
|
340
|
-
: config.summarize.includeGlobs.map(normalizeGlobPattern));
|
|
341
|
-
const excludePatterns = compileGlobs((options.excludeGlobs && options.excludeGlobs.length > 0)
|
|
342
|
-
? options.excludeGlobs.map(normalizeGlobPattern)
|
|
343
|
-
: config.summarize.excludeGlobs.map(normalizeGlobPattern));
|
|
247
|
+
const includePatterns = (0, scan_1.compileGlobs)((options.includeGlobs && options.includeGlobs.length > 0)
|
|
248
|
+
? options.includeGlobs.map(scan_1.normalizeGlobPattern)
|
|
249
|
+
: config.summarize.includeGlobs.map(scan_1.normalizeGlobPattern));
|
|
250
|
+
const excludePatterns = (0, scan_1.compileGlobs)((options.excludeGlobs && options.excludeGlobs.length > 0)
|
|
251
|
+
? options.excludeGlobs.map(scan_1.normalizeGlobPattern)
|
|
252
|
+
: config.summarize.excludeGlobs.map(scan_1.normalizeGlobPattern));
|
|
344
253
|
const ignoreFile = options.ignoreFile || config.summarize.ignoreFile;
|
|
345
|
-
const ignoreMatcher = await readIgnoreMatcher(repoPath, ignoreFile);
|
|
254
|
+
const ignoreMatcher = await (0, scan_1.readIgnoreMatcher)(repoPath, ignoreFile);
|
|
346
255
|
const customPromptHash = getPromptHash(config);
|
|
347
256
|
const customPromptSource = customPromptHash ? config.summarize.summaryPromptSource : undefined;
|
|
348
257
|
const summaryFreshnessExpected = (hash) => ({
|
|
@@ -400,7 +309,7 @@ async function runSummarize(options, config) {
|
|
|
400
309
|
manifest.files = {};
|
|
401
310
|
refs.clear();
|
|
402
311
|
await writeManifest(manifestPath, manifest);
|
|
403
|
-
const files = await walkCodeFiles(repoPath, includePatterns, excludePatterns, ignoreMatcher);
|
|
312
|
+
const files = await (0, scan_1.walkCodeFiles)(repoPath, includePatterns, excludePatterns, ignoreMatcher);
|
|
404
313
|
const totalFiles = files.length;
|
|
405
314
|
let completedFiles = 0;
|
|
406
315
|
if (!isJson) {
|
|
@@ -462,7 +371,7 @@ async function runSummarize(options, config) {
|
|
|
462
371
|
totals.scanned += 1;
|
|
463
372
|
});
|
|
464
373
|
try {
|
|
465
|
-
if (!shouldIncludeFile(filePath, includePatterns, excludePatterns, ignoreMatcher)) {
|
|
374
|
+
if (!(0, scan_1.shouldIncludeFile)(filePath, includePatterns, excludePatterns, ignoreMatcher)) {
|
|
466
375
|
await withManifestLock(async () => {
|
|
467
376
|
const removed = await removeManifestPath(filePath, manifest, manifestPath, summaryDir, refs);
|
|
468
377
|
if (removed) {
|
|
@@ -7,6 +7,7 @@ exports.runValidate = runValidate;
|
|
|
7
7
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const artifacts_1 = require("../types/artifacts");
|
|
10
|
+
const schemas_1 = require("../schemas");
|
|
10
11
|
const paths_1 = require("../utils/paths");
|
|
11
12
|
function getSummaryDir(manifestPath) {
|
|
12
13
|
return node_path_1.default.resolve(node_path_1.default.dirname(manifestPath), "summaries");
|
|
@@ -80,6 +81,8 @@ async function runValidate(options, config) {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
const report = {
|
|
84
|
+
schemaVersion: schemas_1.SCHEMA_DIR_VERSION,
|
|
85
|
+
schemaBaseUri: schemas_1.SCHEMA_BASE_URL,
|
|
83
86
|
valid: manifestValid && issues.length === 0,
|
|
84
87
|
manifestPath,
|
|
85
88
|
manifestValid,
|
|
@@ -91,6 +94,8 @@ async function runValidate(options, config) {
|
|
|
91
94
|
console.log(JSON.stringify(report, null, 2));
|
|
92
95
|
}
|
|
93
96
|
else {
|
|
97
|
+
console.log(`Schema version: v${schemas_1.SCHEMA_DIR_VERSION}`);
|
|
98
|
+
console.log(`Schema URI: ${schemas_1.SCHEMA_BASE_URL}`);
|
|
94
99
|
console.log(`Manifest: ${manifestPath}`);
|
|
95
100
|
console.log(`Manifest valid: ${manifestValid ? "yes" : "NO"}`);
|
|
96
101
|
console.log(`Summary assets checked: ${summaryAssetsChecked}`);
|
package/dist/config.js
CHANGED
|
@@ -123,6 +123,7 @@ function buildRuntimeConfig(options, needs = { chat: true, embeddings: true }) {
|
|
|
123
123
|
}
|
|
124
124
|
return {
|
|
125
125
|
baseDir: readOption(mergedOptions.baseDir, "DIFFDOC_BASE_DIR", "./.diffdoc"),
|
|
126
|
+
repoPath: readOption(mergedOptions.repoPath, "DIFFDOC_REPO_PATH", "."),
|
|
126
127
|
provider,
|
|
127
128
|
chat: {
|
|
128
129
|
apiKey,
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const commander_1 = require("commander");
|
|
|
5
5
|
const config_1 = require("./config");
|
|
6
6
|
const embed_1 = require("./commands/embed");
|
|
7
7
|
const init_1 = require("./commands/init");
|
|
8
|
+
const prune_1 = require("./commands/prune");
|
|
8
9
|
const query_1 = require("./commands/query");
|
|
9
10
|
const status_1 = require("./commands/status");
|
|
10
11
|
const summarize_1 = require("./commands/summarize");
|
|
@@ -43,7 +44,7 @@ function addCloudEndpointAndKeyOptions(command) {
|
|
|
43
44
|
program
|
|
44
45
|
.name("diffdoc")
|
|
45
46
|
.description("Translate repository code shifts into plain-English business context")
|
|
46
|
-
.version("0.6.
|
|
47
|
+
.version("0.6.4");
|
|
47
48
|
program
|
|
48
49
|
.command("init")
|
|
49
50
|
.description("Initialize DiffDoc configuration for this repository")
|
|
@@ -146,12 +147,31 @@ addCloudEndpointAndKeyOptions(addEmbeddingOptions(addBaseOptions(program
|
|
|
146
147
|
addBaseOptions(program
|
|
147
148
|
.command("status"))
|
|
148
149
|
.description("Show manifest and index sync status")
|
|
150
|
+
.option("--path <path>", "repository or code path to scan")
|
|
149
151
|
.option("--manifest <path>", "manifest input path under --base-dir", "manifest.json")
|
|
150
152
|
.option("--json", "print status as JSON for CI", false)
|
|
151
153
|
.action(async (options) => {
|
|
152
154
|
try {
|
|
153
155
|
const config = (0, config_1.buildRuntimeConfig)(options, { embeddings: false, chat: false });
|
|
154
|
-
await (0, status_1.runStatus)({ manifest: options.manifest, json: options.json }, config);
|
|
156
|
+
await (0, status_1.runStatus)({ path: options.path, manifest: options.manifest, json: options.json }, config);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error(error instanceof Error ? error.message : error);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
addBaseOptions(program
|
|
164
|
+
.command("prune"))
|
|
165
|
+
.description("Remove manifest entries for deleted or excluded files")
|
|
166
|
+
.option("--path <path>", "repository or code path to scan")
|
|
167
|
+
.option("--manifest <path>", "manifest input path under --base-dir", "manifest.json")
|
|
168
|
+
.option("--dry-run", "show what would be pruned without executing", false)
|
|
169
|
+
.option("--yes", "skip confirmation prompt", false)
|
|
170
|
+
.option("--json", "print prune report as JSON for CI", false)
|
|
171
|
+
.action(async (options) => {
|
|
172
|
+
try {
|
|
173
|
+
const config = (0, config_1.buildRuntimeConfig)(options, { embeddings: false, chat: false });
|
|
174
|
+
await (0, prune_1.runPrune)({ path: options.path, manifest: options.manifest, dryRun: options.dryRun, yes: options.yes, json: options.json }, config);
|
|
155
175
|
}
|
|
156
176
|
catch (error) {
|
|
157
177
|
console.error(error instanceof Error ? error.message : error);
|
package/dist/schemas.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.SCHEMA_BASE_URL = `https://raw.githubusercontent.com/sullyTheDev/diffdoc
|
|
|
15
15
|
exports.DiffdocConfigSchema = zod_1.z.object({
|
|
16
16
|
$schema: zod_1.z.string().optional(),
|
|
17
17
|
baseDir: zod_1.z.string().optional(),
|
|
18
|
+
repoPath: zod_1.z.string().optional(),
|
|
18
19
|
aiProvider: zod_1.z.enum(["local", "cloud"]).optional(),
|
|
19
20
|
localLlmEndpoint: zod_1.z.string().optional(),
|
|
20
21
|
localEmbedEndpoint: zod_1.z.string().optional(),
|
|
@@ -0,0 +1,105 @@
|
|
|
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.normalizeRelativePath = normalizeRelativePath;
|
|
7
|
+
exports.normalizeGlobPattern = normalizeGlobPattern;
|
|
8
|
+
exports.compileGlobs = compileGlobs;
|
|
9
|
+
exports.shouldIncludeFile = shouldIncludeFile;
|
|
10
|
+
exports.readIgnoreMatcher = readIgnoreMatcher;
|
|
11
|
+
exports.walkCodeFiles = walkCodeFiles;
|
|
12
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
15
|
+
function normalizeRelativePath(filePath) {
|
|
16
|
+
return filePath.split(node_path_1.default.sep).join("/");
|
|
17
|
+
}
|
|
18
|
+
function normalizeGlobPattern(pattern) {
|
|
19
|
+
return pattern.split(node_path_1.default.sep).join("/");
|
|
20
|
+
}
|
|
21
|
+
function escapeRegex(value) {
|
|
22
|
+
return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
23
|
+
}
|
|
24
|
+
function globToRegExp(pattern) {
|
|
25
|
+
const normalized = normalizeGlobPattern(pattern);
|
|
26
|
+
let regexBody = "";
|
|
27
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
28
|
+
const char = normalized[i];
|
|
29
|
+
const next = normalized[i + 1];
|
|
30
|
+
if (char === "*" && next === "*") {
|
|
31
|
+
regexBody += ".*";
|
|
32
|
+
i += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === "*") {
|
|
36
|
+
regexBody += "[^/]*";
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (char === "?") {
|
|
40
|
+
regexBody += "[^/]";
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
regexBody += escapeRegex(char);
|
|
44
|
+
}
|
|
45
|
+
return new RegExp(`^${regexBody}$`);
|
|
46
|
+
}
|
|
47
|
+
function compileGlobs(patterns) {
|
|
48
|
+
return patterns.filter(Boolean).map(globToRegExp);
|
|
49
|
+
}
|
|
50
|
+
function matchesAny(filePath, patterns) {
|
|
51
|
+
return patterns.some((pattern) => pattern.test(filePath));
|
|
52
|
+
}
|
|
53
|
+
function shouldIncludeFile(filePath, includeGlobs, excludeGlobs, ignoreMatcher) {
|
|
54
|
+
if (ignoreMatcher.ignores(filePath)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (excludeGlobs.length > 0 && matchesAny(filePath, excludeGlobs)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (includeGlobs.length > 0 && !matchesAny(filePath, includeGlobs)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
function isIgnoredDirectory(dirPath, ignoreMatcher) {
|
|
66
|
+
return ignoreMatcher.ignores(dirPath) || ignoreMatcher.ignores(`${dirPath}/`);
|
|
67
|
+
}
|
|
68
|
+
async function readIgnoreMatcher(repoPath, ignoreFilePath) {
|
|
69
|
+
const matcher = (0, ignore_1.default)();
|
|
70
|
+
const absolutePath = node_path_1.default.isAbsolute(ignoreFilePath)
|
|
71
|
+
? ignoreFilePath
|
|
72
|
+
: node_path_1.default.resolve(repoPath, ignoreFilePath);
|
|
73
|
+
try {
|
|
74
|
+
const raw = await promises_1.default.readFile(absolutePath, "utf8");
|
|
75
|
+
return matcher.add(raw);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const nodeError = error;
|
|
79
|
+
if (nodeError.code === "ENOENT") {
|
|
80
|
+
return matcher;
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function walkCodeFiles(rootPath, includeGlobs, excludeGlobs, ignoreMatcher, currentPath = rootPath) {
|
|
86
|
+
const entries = await promises_1.default.readdir(currentPath, { withFileTypes: true });
|
|
87
|
+
const files = [];
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const entryPath = node_path_1.default.join(currentPath, entry.name);
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
const relativePath = normalizeRelativePath(node_path_1.default.relative(rootPath, entryPath));
|
|
92
|
+
if (!isIgnoredDirectory(relativePath, ignoreMatcher)) {
|
|
93
|
+
files.push(...await walkCodeFiles(rootPath, includeGlobs, excludeGlobs, ignoreMatcher, entryPath));
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (entry.isFile()) {
|
|
98
|
+
const relativePath = normalizeRelativePath(node_path_1.default.relative(rootPath, entryPath));
|
|
99
|
+
if (shouldIncludeFile(relativePath, includeGlobs, excludeGlobs, ignoreMatcher)) {
|
|
100
|
+
files.push(relativePath);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return files.sort();
|
|
105
|
+
}
|
package/package.json
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "diffdoc",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "Translate repository code shifts into plain-English business context",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "Christopher Sullivan",
|
|
7
|
-
"homepage": "https://github.com/sullyTheDev/diffdoc#readme",
|
|
8
|
-
"bugs": {
|
|
9
|
-
"url": "https://github.com/sullyTheDev/diffdoc/issues"
|
|
10
|
-
},
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/sullyTheDev/diffdoc.git"
|
|
14
|
-
},
|
|
15
|
-
"type": "commonjs",
|
|
16
|
-
"main": "dist/index.js",
|
|
17
|
-
"bin": {
|
|
18
|
-
"diffdoc": "dist/index.js",
|
|
19
|
-
"diffdoc-mcp": "dist/mcp.js"
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"dist",
|
|
23
|
-
"schemas",
|
|
24
|
-
"README.md",
|
|
25
|
-
"LICENSE",
|
|
26
|
-
".diffdocrc.example"
|
|
27
|
-
],
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=22"
|
|
30
|
-
},
|
|
31
|
-
"scripts": {
|
|
32
|
-
"build": "tsc && node dist/scripts/generate-schemas.js",
|
|
33
|
-
"generate:schemas": "node dist/scripts/generate-schemas.js",
|
|
34
|
-
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
35
|
-
"start": "tsc && node ./dist/index.js",
|
|
36
|
-
"prepare": "npm run build"
|
|
37
|
-
},
|
|
38
|
-
"dependencies": {
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
40
|
-
"commander": "^12.0.0",
|
|
41
|
-
"ignore": "^7.0.5",
|
|
42
|
-
"openai": "^4.28.0",
|
|
43
|
-
"simple-git": "^3.24.0",
|
|
44
|
-
"vectra": "^0.14.0",
|
|
45
|
-
"zod": "^3.25.76"
|
|
46
|
-
},
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"@types/node": "^20.19.41",
|
|
49
|
-
"typescript": "^5.3.3",
|
|
50
|
-
"zod-to-json-schema": "^3.25.2"
|
|
51
|
-
}
|
|
52
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "diffdoc",
|
|
3
|
+
"version": "0.6.4",
|
|
4
|
+
"description": "Translate repository code shifts into plain-English business context",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Christopher Sullivan",
|
|
7
|
+
"homepage": "https://github.com/sullyTheDev/diffdoc#readme",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/sullyTheDev/diffdoc/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/sullyTheDev/diffdoc.git"
|
|
14
|
+
},
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"bin": {
|
|
18
|
+
"diffdoc": "dist/index.js",
|
|
19
|
+
"diffdoc-mcp": "dist/mcp.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"schemas",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
".diffdocrc.example"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc && node dist/scripts/generate-schemas.js",
|
|
33
|
+
"generate:schemas": "node dist/scripts/generate-schemas.js",
|
|
34
|
+
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
35
|
+
"start": "tsc && node ./dist/index.js",
|
|
36
|
+
"prepare": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
40
|
+
"commander": "^12.0.0",
|
|
41
|
+
"ignore": "^7.0.5",
|
|
42
|
+
"openai": "^4.28.0",
|
|
43
|
+
"simple-git": "^3.24.0",
|
|
44
|
+
"vectra": "^0.14.0",
|
|
45
|
+
"zod": "^3.25.76"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^20.19.41",
|
|
49
|
+
"typescript": "^5.3.3",
|
|
50
|
+
"zod-to-json-schema": "^3.25.2"
|
|
51
|
+
}
|
|
52
|
+
}
|