@rejot-dev/thalo 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +396 -0
- package/dist/ast/ast-types.d.ts +469 -0
- package/dist/ast/ast-types.d.ts.map +1 -0
- package/dist/ast/ast-types.js +11 -0
- package/dist/ast/ast-types.js.map +1 -0
- package/dist/ast/builder.js +158 -0
- package/dist/ast/builder.js.map +1 -0
- package/dist/ast/extract.js +748 -0
- package/dist/ast/extract.js.map +1 -0
- package/dist/ast/node-at-position.d.ts +147 -0
- package/dist/ast/node-at-position.d.ts.map +1 -0
- package/dist/ast/node-at-position.js +382 -0
- package/dist/ast/node-at-position.js.map +1 -0
- package/dist/ast/visitor.js +232 -0
- package/dist/ast/visitor.js.map +1 -0
- package/dist/checker/check.d.ts +53 -0
- package/dist/checker/check.d.ts.map +1 -0
- package/dist/checker/check.js +105 -0
- package/dist/checker/check.js.map +1 -0
- package/dist/checker/rules/actualize-missing-updated.js +34 -0
- package/dist/checker/rules/actualize-missing-updated.js.map +1 -0
- package/dist/checker/rules/actualize-unresolved-target.js +42 -0
- package/dist/checker/rules/actualize-unresolved-target.js.map +1 -0
- package/dist/checker/rules/alter-before-define.js +53 -0
- package/dist/checker/rules/alter-before-define.js.map +1 -0
- package/dist/checker/rules/alter-undefined-entity.js +32 -0
- package/dist/checker/rules/alter-undefined-entity.js.map +1 -0
- package/dist/checker/rules/create-requires-section.js +34 -0
- package/dist/checker/rules/create-requires-section.js.map +1 -0
- package/dist/checker/rules/define-entity-requires-section.js +31 -0
- package/dist/checker/rules/define-entity-requires-section.js.map +1 -0
- package/dist/checker/rules/duplicate-entity-definition.js +37 -0
- package/dist/checker/rules/duplicate-entity-definition.js.map +1 -0
- package/dist/checker/rules/duplicate-field-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-field-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-link-id.js +52 -0
- package/dist/checker/rules/duplicate-link-id.js.map +1 -0
- package/dist/checker/rules/duplicate-metadata-key.js +21 -0
- package/dist/checker/rules/duplicate-metadata-key.js.map +1 -0
- package/dist/checker/rules/duplicate-section-heading.js +41 -0
- package/dist/checker/rules/duplicate-section-heading.js.map +1 -0
- package/dist/checker/rules/duplicate-section-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-section-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-timestamp.js +104 -0
- package/dist/checker/rules/duplicate-timestamp.js.map +1 -0
- package/dist/checker/rules/empty-required-value.js +45 -0
- package/dist/checker/rules/empty-required-value.js.map +1 -0
- package/dist/checker/rules/empty-section.js +21 -0
- package/dist/checker/rules/empty-section.js.map +1 -0
- package/dist/checker/rules/invalid-date-range-value.js +56 -0
- package/dist/checker/rules/invalid-date-range-value.js.map +1 -0
- package/dist/checker/rules/invalid-default-value.js +86 -0
- package/dist/checker/rules/invalid-default-value.js.map +1 -0
- package/dist/checker/rules/invalid-field-type.js +45 -0
- package/dist/checker/rules/invalid-field-type.js.map +1 -0
- package/dist/checker/rules/missing-required-field.js +48 -0
- package/dist/checker/rules/missing-required-field.js.map +1 -0
- package/dist/checker/rules/missing-required-section.js +51 -0
- package/dist/checker/rules/missing-required-section.js.map +1 -0
- package/dist/checker/rules/missing-title.js +56 -0
- package/dist/checker/rules/missing-title.js.map +1 -0
- package/dist/checker/rules/remove-undefined-field.js +42 -0
- package/dist/checker/rules/remove-undefined-field.js.map +1 -0
- package/dist/checker/rules/remove-undefined-section.js +42 -0
- package/dist/checker/rules/remove-undefined-section.js.map +1 -0
- package/dist/checker/rules/rules.d.ts +71 -0
- package/dist/checker/rules/rules.d.ts.map +1 -0
- package/dist/checker/rules/rules.js +102 -0
- package/dist/checker/rules/rules.js.map +1 -0
- package/dist/checker/rules/synthesis-empty-query.js +35 -0
- package/dist/checker/rules/synthesis-empty-query.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-prompt.js +42 -0
- package/dist/checker/rules/synthesis-missing-prompt.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-sources.js +32 -0
- package/dist/checker/rules/synthesis-missing-sources.js.map +1 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js +39 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js.map +1 -0
- package/dist/checker/rules/timestamp-out-of-order.js +55 -0
- package/dist/checker/rules/timestamp-out-of-order.js.map +1 -0
- package/dist/checker/rules/unknown-entity.js +32 -0
- package/dist/checker/rules/unknown-entity.js.map +1 -0
- package/dist/checker/rules/unknown-field.js +40 -0
- package/dist/checker/rules/unknown-field.js.map +1 -0
- package/dist/checker/rules/unknown-section.js +47 -0
- package/dist/checker/rules/unknown-section.js.map +1 -0
- package/dist/checker/rules/unresolved-link.js +34 -0
- package/dist/checker/rules/unresolved-link.js.map +1 -0
- package/dist/checker/rules/update-without-create.js +65 -0
- package/dist/checker/rules/update-without-create.js.map +1 -0
- package/dist/checker/visitor.d.ts +69 -0
- package/dist/checker/visitor.d.ts.map +1 -0
- package/dist/checker/visitor.js +67 -0
- package/dist/checker/visitor.js.map +1 -0
- package/dist/checker/workspace-index.d.ts +50 -0
- package/dist/checker/workspace-index.d.ts.map +1 -0
- package/dist/checker/workspace-index.js +108 -0
- package/dist/checker/workspace-index.js.map +1 -0
- package/dist/commands/actualize.d.ts +113 -0
- package/dist/commands/actualize.d.ts.map +1 -0
- package/dist/commands/actualize.js +111 -0
- package/dist/commands/actualize.js.map +1 -0
- package/dist/commands/check.d.ts +65 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +61 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/format.d.ts +90 -0
- package/dist/commands/format.d.ts.map +1 -0
- package/dist/commands/format.js +80 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/query.d.ts +152 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +151 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/constants.d.ts +31 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/files.d.ts +58 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +103 -0
- package/dist/files.js.map +1 -0
- package/dist/formatters.d.ts +39 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +200 -0
- package/dist/formatters.js.map +1 -0
- package/dist/fragment.d.ts +22 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/git/git.js +240 -0
- package/dist/git/git.js.map +1 -0
- package/dist/merge/conflict-detector.d.ts +89 -0
- package/dist/merge/conflict-detector.d.ts.map +1 -0
- package/dist/merge/conflict-detector.js +352 -0
- package/dist/merge/conflict-detector.js.map +1 -0
- package/dist/merge/conflict-formatter.js +143 -0
- package/dist/merge/conflict-formatter.js.map +1 -0
- package/dist/merge/driver.d.ts +54 -0
- package/dist/merge/driver.d.ts.map +1 -0
- package/dist/merge/driver.js +112 -0
- package/dist/merge/driver.js.map +1 -0
- package/dist/merge/entry-matcher.d.ts +50 -0
- package/dist/merge/entry-matcher.d.ts.map +1 -0
- package/dist/merge/entry-matcher.js +141 -0
- package/dist/merge/entry-matcher.js.map +1 -0
- package/dist/merge/entry-merger.js +194 -0
- package/dist/merge/entry-merger.js.map +1 -0
- package/dist/merge/merge-result-builder.d.ts +62 -0
- package/dist/merge/merge-result-builder.d.ts.map +1 -0
- package/dist/merge/merge-result-builder.js +89 -0
- package/dist/merge/merge-result-builder.js.map +1 -0
- package/dist/mod.d.ts +31 -0
- package/dist/mod.js +23 -0
- package/dist/model/document.d.ts +134 -0
- package/dist/model/document.d.ts.map +1 -0
- package/dist/model/document.js +275 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/line-index.d.ts +85 -0
- package/dist/model/line-index.d.ts.map +1 -0
- package/dist/model/line-index.js +159 -0
- package/dist/model/line-index.js.map +1 -0
- package/dist/model/workspace.d.ts +296 -0
- package/dist/model/workspace.d.ts.map +1 -0
- package/dist/model/workspace.js +562 -0
- package/dist/model/workspace.js.map +1 -0
- package/dist/parser.js +27 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.native.d.ts +51 -0
- package/dist/parser.native.d.ts.map +1 -0
- package/dist/parser.native.js +62 -0
- package/dist/parser.native.js.map +1 -0
- package/dist/parser.shared.d.ts +99 -0
- package/dist/parser.shared.d.ts.map +1 -0
- package/dist/parser.shared.js +124 -0
- package/dist/parser.shared.js.map +1 -0
- package/dist/parser.web.d.ts +67 -0
- package/dist/parser.web.d.ts.map +1 -0
- package/dist/parser.web.js +49 -0
- package/dist/parser.web.js.map +1 -0
- package/dist/schema/registry.d.ts +108 -0
- package/dist/schema/registry.d.ts.map +1 -0
- package/dist/schema/registry.js +281 -0
- package/dist/schema/registry.js.map +1 -0
- package/dist/semantic/analyzer.d.ts +107 -0
- package/dist/semantic/analyzer.d.ts.map +1 -0
- package/dist/semantic/analyzer.js +261 -0
- package/dist/semantic/analyzer.js.map +1 -0
- package/dist/services/change-tracker/change-tracker.d.ts +111 -0
- package/dist/services/change-tracker/change-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/change-tracker.js +62 -0
- package/dist/services/change-tracker/change-tracker.js.map +1 -0
- package/dist/services/change-tracker/create-tracker.d.ts +42 -0
- package/dist/services/change-tracker/create-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/create-tracker.js +53 -0
- package/dist/services/change-tracker/create-tracker.js.map +1 -0
- package/dist/services/change-tracker/git-tracker.d.ts +59 -0
- package/dist/services/change-tracker/git-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/git-tracker.js +218 -0
- package/dist/services/change-tracker/git-tracker.js.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts +22 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.js +74 -0
- package/dist/services/change-tracker/timestamp-tracker.js.map +1 -0
- package/dist/services/definition.d.ts +37 -0
- package/dist/services/definition.d.ts.map +1 -0
- package/dist/services/definition.js +43 -0
- package/dist/services/definition.js.map +1 -0
- package/dist/services/entity-navigation.d.ts +200 -0
- package/dist/services/entity-navigation.d.ts.map +1 -0
- package/dist/services/entity-navigation.js +211 -0
- package/dist/services/entity-navigation.js.map +1 -0
- package/dist/services/hover.d.ts +81 -0
- package/dist/services/hover.d.ts.map +1 -0
- package/dist/services/hover.js +669 -0
- package/dist/services/hover.js.map +1 -0
- package/dist/services/query.d.ts +116 -0
- package/dist/services/query.d.ts.map +1 -0
- package/dist/services/query.js +225 -0
- package/dist/services/query.js.map +1 -0
- package/dist/services/references.d.ts +52 -0
- package/dist/services/references.d.ts.map +1 -0
- package/dist/services/references.js +66 -0
- package/dist/services/references.js.map +1 -0
- package/dist/services/semantic-tokens.d.ts +54 -0
- package/dist/services/semantic-tokens.d.ts.map +1 -0
- package/dist/services/semantic-tokens.js +213 -0
- package/dist/services/semantic-tokens.js.map +1 -0
- package/dist/services/synthesis.d.ts +90 -0
- package/dist/services/synthesis.d.ts.map +1 -0
- package/dist/services/synthesis.js +113 -0
- package/dist/services/synthesis.js.map +1 -0
- package/dist/source-map.d.ts +42 -0
- package/dist/source-map.d.ts.map +1 -0
- package/dist/source-map.js +170 -0
- package/dist/source-map.js.map +1 -0
- package/package.json +128 -0
- package/tree-sitter-thalo.wasm +0 -0
- package/web-tree-sitter.wasm +0 -0
package/dist/git/git.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
|
|
6
|
+
//#region src/git/git.ts
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
/**
|
|
9
|
+
* Run a git command and return stdout, or null if it fails
|
|
10
|
+
*/
|
|
11
|
+
async function runGit(args, cwd) {
|
|
12
|
+
try {
|
|
13
|
+
return await execFileAsync("git", args, {
|
|
14
|
+
cwd,
|
|
15
|
+
maxBuffer: 10 * 1024 * 1024
|
|
16
|
+
});
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Detect git context for a directory.
|
|
23
|
+
*
|
|
24
|
+
* Returns information about whether the directory is in a git repo,
|
|
25
|
+
* the repo root, and current commit hash.
|
|
26
|
+
*
|
|
27
|
+
* @param cwd - Directory to check
|
|
28
|
+
* @returns Git context information
|
|
29
|
+
*/
|
|
30
|
+
async function detectGitContext(cwd) {
|
|
31
|
+
const revParseResult = await runGit(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
32
|
+
if (!revParseResult || revParseResult.stdout.trim() !== "true") return { isGitRepo: false };
|
|
33
|
+
return {
|
|
34
|
+
isGitRepo: true,
|
|
35
|
+
rootDir: (await runGit(["rev-parse", "--show-toplevel"], cwd))?.stdout.trim(),
|
|
36
|
+
currentCommit: (await runGit(["rev-parse", "HEAD"], cwd))?.stdout.trim()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the current HEAD commit hash.
|
|
41
|
+
*
|
|
42
|
+
* @param cwd - Directory to check
|
|
43
|
+
* @returns Commit hash or null if not in a git repo
|
|
44
|
+
*/
|
|
45
|
+
async function getCurrentCommit(cwd) {
|
|
46
|
+
return (await runGit(["rev-parse", "HEAD"], cwd))?.stdout.trim() ?? null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get list of files changed since a commit with rename detection.
|
|
50
|
+
*
|
|
51
|
+
* Uses `git diff -M --name-status` to detect renames and returns structured change info.
|
|
52
|
+
*
|
|
53
|
+
* @param commit - Base commit to compare against
|
|
54
|
+
* @param cwd - Working directory
|
|
55
|
+
* @returns Array of file changes (relative to repo root)
|
|
56
|
+
*/
|
|
57
|
+
async function getFilesChangedSince(commit, cwd) {
|
|
58
|
+
const result = await runGit([
|
|
59
|
+
"diff",
|
|
60
|
+
"-M",
|
|
61
|
+
"--name-status",
|
|
62
|
+
`${commit}..HEAD`
|
|
63
|
+
], cwd);
|
|
64
|
+
if (!result) return [];
|
|
65
|
+
return result.stdout.trim().split("\n").filter((line) => line.length > 0).map((line) => {
|
|
66
|
+
const parts = line.split(" ");
|
|
67
|
+
const statusCode = parts[0];
|
|
68
|
+
if (statusCode.startsWith("R")) {
|
|
69
|
+
if (parts.length < 3) return {
|
|
70
|
+
status: "modified",
|
|
71
|
+
path: parts[1] ?? ""
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
status: "renamed",
|
|
75
|
+
oldPath: parts[1],
|
|
76
|
+
path: parts[2]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const status = statusCode === "A" ? "added" : statusCode === "D" ? "deleted" : "modified";
|
|
80
|
+
if (parts.length < 2) return {
|
|
81
|
+
status: "modified",
|
|
82
|
+
path: ""
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
status,
|
|
86
|
+
path: parts[1]
|
|
87
|
+
};
|
|
88
|
+
}).filter((change) => change.path.length > 0);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get file content at a specific commit.
|
|
92
|
+
*
|
|
93
|
+
* @param file - File path (relative to repo root or absolute)
|
|
94
|
+
* @param commit - Commit hash to retrieve content from
|
|
95
|
+
* @param cwd - Working directory (for finding repo root)
|
|
96
|
+
* @returns File content at that commit, or null if file doesn't exist at commit
|
|
97
|
+
*/
|
|
98
|
+
async function getFileAtCommit(file, commit, cwd) {
|
|
99
|
+
const rootResult = await runGit(["rev-parse", "--show-toplevel"], cwd);
|
|
100
|
+
if (!rootResult) return null;
|
|
101
|
+
const repoRoot = rootResult.stdout.trim();
|
|
102
|
+
let gitPath;
|
|
103
|
+
if (path.isAbsolute(file)) {
|
|
104
|
+
const relativePath = path.relative(repoRoot, file);
|
|
105
|
+
if (relativePath.startsWith("..")) return null;
|
|
106
|
+
gitPath = relativePath.split(path.sep).join("/");
|
|
107
|
+
} else gitPath = file.replace(/\\/g, "/");
|
|
108
|
+
return (await runGit(["show", `${commit}:${gitPath}`], cwd))?.stdout ?? null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Read the set of ignored revs for blame-like operations, if configured.
|
|
112
|
+
*
|
|
113
|
+
* Prefers the de-facto standard `.git-blame-ignore-revs` at the repo root, and
|
|
114
|
+
* falls back to `blame.ignoreRevsFile` git config if set.
|
|
115
|
+
*
|
|
116
|
+
* Returns the rev SHAs (one per line) or null if no ignore revs are configured.
|
|
117
|
+
*
|
|
118
|
+
* @param cwd - Working directory
|
|
119
|
+
* @returns Array of ignored revs (as strings) or null
|
|
120
|
+
*/
|
|
121
|
+
async function getBlameIgnoreRevs(cwd) {
|
|
122
|
+
const rootResult = await runGit(["rev-parse", "--show-toplevel"], cwd);
|
|
123
|
+
if (!rootResult) return null;
|
|
124
|
+
const repoRoot = rootResult.stdout.trim();
|
|
125
|
+
async function readIgnoreFile(filePath) {
|
|
126
|
+
try {
|
|
127
|
+
const revs = (await fs.readFile(filePath, "utf8")).split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#")).map((l) => l.split(/\s+/)[0]);
|
|
128
|
+
return revs.length > 0 ? revs : null;
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const standardRevs = await readIgnoreFile(path.join(repoRoot, ".git-blame-ignore-revs"));
|
|
134
|
+
if (standardRevs) return standardRevs;
|
|
135
|
+
const configPathRaw = (await runGit([
|
|
136
|
+
"config",
|
|
137
|
+
"--get",
|
|
138
|
+
"blame.ignoreRevsFile"
|
|
139
|
+
], cwd))?.stdout.trim();
|
|
140
|
+
if (!configPathRaw) return null;
|
|
141
|
+
return await readIgnoreFile(path.isAbsolute(configPathRaw) ? configPathRaw : path.resolve(repoRoot, configPathRaw));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check whether `ancestorCommit` is an ancestor of `descendantCommit`.
|
|
145
|
+
*
|
|
146
|
+
* Uses `git merge-base ancestor descendant` and compares the result to the ancestor.
|
|
147
|
+
*/
|
|
148
|
+
async function isCommitAncestorOf(ancestorCommit, descendantCommit, cwd) {
|
|
149
|
+
const result = await runGit([
|
|
150
|
+
"merge-base",
|
|
151
|
+
ancestorCommit,
|
|
152
|
+
descendantCommit
|
|
153
|
+
], cwd);
|
|
154
|
+
return result !== null && result.stdout.trim() === ancestorCommit;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get the set of commits currently blamed for a line range.
|
|
158
|
+
*
|
|
159
|
+
* @param file - File path (relative to repo root or absolute)
|
|
160
|
+
* @param startLine - 1-based inclusive start line
|
|
161
|
+
* @param endLine - 1-based inclusive end line
|
|
162
|
+
* @param cwd - Working directory
|
|
163
|
+
* @param ignoreRevsFile - Optional absolute path to ignore revs file
|
|
164
|
+
*/
|
|
165
|
+
async function getBlameCommitsForLineRange(file, startLine, endLine, cwd, ignoreRevs) {
|
|
166
|
+
const rootResult = await runGit(["rev-parse", "--show-toplevel"], cwd);
|
|
167
|
+
if (!rootResult) return /* @__PURE__ */ new Set();
|
|
168
|
+
const repoRoot = rootResult.stdout.trim();
|
|
169
|
+
let gitPath;
|
|
170
|
+
if (path.isAbsolute(file)) {
|
|
171
|
+
const relativePath = path.relative(repoRoot, file);
|
|
172
|
+
if (relativePath.startsWith("..")) return /* @__PURE__ */ new Set();
|
|
173
|
+
gitPath = relativePath.split(path.sep).join("/");
|
|
174
|
+
} else gitPath = file.replace(/\\/g, "/");
|
|
175
|
+
const args = [
|
|
176
|
+
"blame",
|
|
177
|
+
"--porcelain",
|
|
178
|
+
"-L",
|
|
179
|
+
`${startLine},${endLine}`
|
|
180
|
+
];
|
|
181
|
+
if (ignoreRevs && ignoreRevs.length > 0) for (const rev of ignoreRevs) args.push("--ignore-rev", rev);
|
|
182
|
+
args.push("HEAD", "--", gitPath);
|
|
183
|
+
const result = await runGit(args, cwd);
|
|
184
|
+
if (!result) return /* @__PURE__ */ new Set();
|
|
185
|
+
const commits = /* @__PURE__ */ new Set();
|
|
186
|
+
for (const line of result.stdout.split("\n")) if (/^[0-9a-f]{40}\s/.test(line)) commits.add(line.slice(0, 40));
|
|
187
|
+
return commits;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check if a commit exists in the repository.
|
|
191
|
+
*
|
|
192
|
+
* @param commit - Commit hash to check
|
|
193
|
+
* @param cwd - Working directory
|
|
194
|
+
* @returns true if commit exists, false otherwise
|
|
195
|
+
*/
|
|
196
|
+
async function commitExists(commit, cwd) {
|
|
197
|
+
const result = await runGit([
|
|
198
|
+
"cat-file",
|
|
199
|
+
"-t",
|
|
200
|
+
commit
|
|
201
|
+
], cwd);
|
|
202
|
+
return result !== null && result.stdout.trim() === "commit";
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get list of files with uncommitted changes (staged or unstaged).
|
|
206
|
+
*
|
|
207
|
+
* @param cwd - Working directory
|
|
208
|
+
* @param files - Optional list of files to filter (relative to repo root or absolute)
|
|
209
|
+
* @returns Array of file paths (relative to repo root) with uncommitted changes
|
|
210
|
+
*/
|
|
211
|
+
async function getUncommittedFiles(cwd, files) {
|
|
212
|
+
const rootResult = await runGit(["rev-parse", "--show-toplevel"], cwd);
|
|
213
|
+
if (!rootResult) return [];
|
|
214
|
+
const repoRoot = rootResult.stdout.trim();
|
|
215
|
+
const args = [
|
|
216
|
+
"status",
|
|
217
|
+
"--porcelain",
|
|
218
|
+
"--"
|
|
219
|
+
];
|
|
220
|
+
if (files && files.length > 0) for (const file of files) {
|
|
221
|
+
let gitPath;
|
|
222
|
+
if (path.isAbsolute(file)) {
|
|
223
|
+
const relativePath = path.relative(repoRoot, file);
|
|
224
|
+
if (relativePath.startsWith("..")) continue;
|
|
225
|
+
gitPath = relativePath.split(path.sep).join("/");
|
|
226
|
+
} else gitPath = file.replace(/\\/g, "/");
|
|
227
|
+
args.push(gitPath);
|
|
228
|
+
}
|
|
229
|
+
const result = await runGit(args, cwd);
|
|
230
|
+
if (!result) return [];
|
|
231
|
+
return result.stdout.trim().split("\n").filter((line) => line.length > 0).map((line) => {
|
|
232
|
+
const filePart = line.slice(3);
|
|
233
|
+
if (filePart.includes(" -> ")) return filePart.split(" -> ")[1];
|
|
234
|
+
return filePart;
|
|
235
|
+
}).filter((f) => f.length > 0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
export { commitExists, detectGitContext, getBlameCommitsForLineRange, getBlameIgnoreRevs, getCurrentCommit, getFileAtCommit, getFilesChangedSince, getUncommittedFiles, isCommitAncestorOf };
|
|
240
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","names":["gitPath: string"],"sources":["../../src/git/git.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport * as path from \"node:path\";\nimport * as fs from \"node:fs/promises\";\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Git context information for the current working directory\n */\nexport interface GitContext {\n /** Whether the directory is inside a git repository */\n isGitRepo: boolean;\n /** Root directory of the git repository (if isGitRepo is true) */\n rootDir?: string;\n /** Current HEAD commit hash (if isGitRepo is true) */\n currentCommit?: string;\n}\n\n/**\n * Run a git command and return stdout, or null if it fails\n */\nasync function runGit(\n args: string[],\n cwd: string,\n): Promise<{ stdout: string; stderr: string } | null> {\n try {\n const result = await execFileAsync(\"git\", args, {\n cwd,\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large diffs\n });\n return result;\n } catch {\n return null;\n }\n}\n\n/**\n * Detect git context for a directory.\n *\n * Returns information about whether the directory is in a git repo,\n * the repo root, and current commit hash.\n *\n * @param cwd - Directory to check\n * @returns Git context information\n */\nexport async function detectGitContext(cwd: string): Promise<GitContext> {\n // Check if inside a git repo\n const revParseResult = await runGit([\"rev-parse\", \"--is-inside-work-tree\"], cwd);\n\n if (!revParseResult || revParseResult.stdout.trim() !== \"true\") {\n return { isGitRepo: false };\n }\n\n // Get the root directory\n const rootResult = await runGit([\"rev-parse\", \"--show-toplevel\"], cwd);\n const rootDir = rootResult?.stdout.trim();\n\n // Get current commit hash\n const commitResult = await runGit([\"rev-parse\", \"HEAD\"], cwd);\n const currentCommit = commitResult?.stdout.trim();\n\n return {\n isGitRepo: true,\n rootDir,\n currentCommit,\n };\n}\n\n/**\n * Get the current HEAD commit hash.\n *\n * @param cwd - Directory to check\n * @returns Commit hash or null if not in a git repo\n */\nexport async function getCurrentCommit(cwd: string): Promise<string | null> {\n const result = await runGit([\"rev-parse\", \"HEAD\"], cwd);\n return result?.stdout.trim() ?? null;\n}\n\n/**\n * Represents a file change between commits.\n */\nexport interface FileChange {\n /** Type of change */\n status: \"added\" | \"modified\" | \"renamed\" | \"deleted\";\n /** Current path (or new path for renames) */\n path: string;\n /** Original path before rename (only for renamed files) */\n oldPath?: string;\n}\n\n/**\n * Get list of files changed since a commit with rename detection.\n *\n * Uses `git diff -M --name-status` to detect renames and returns structured change info.\n *\n * @param commit - Base commit to compare against\n * @param cwd - Working directory\n * @returns Array of file changes (relative to repo root)\n */\nexport async function getFilesChangedSince(commit: string, cwd: string): Promise<FileChange[]> {\n const result = await runGit([\"diff\", \"-M\", \"--name-status\", `${commit}..HEAD`], cwd);\n\n if (!result) {\n return [];\n }\n\n return result.stdout\n .trim()\n .split(\"\\n\")\n .filter((line) => line.length > 0)\n .map((line): FileChange => {\n const parts = line.split(\"\\t\");\n const statusCode = parts[0];\n\n // Rename: R100\\told/path\\tnew/path (R followed by similarity percentage)\n if (statusCode.startsWith(\"R\")) {\n if (parts.length < 3) {\n // Invalid format, treat as modified with available path\n return { status: \"modified\", path: parts[1] ?? \"\" };\n }\n return {\n status: \"renamed\",\n oldPath: parts[1],\n path: parts[2],\n };\n }\n\n // Other statuses: A (added), M (modified), D (deleted)\n const status = statusCode === \"A\" ? \"added\" : statusCode === \"D\" ? \"deleted\" : \"modified\";\n\n if (parts.length < 2) {\n // Invalid format, skip this entry\n return { status: \"modified\", path: \"\" };\n }\n\n return {\n status,\n path: parts[1],\n };\n })\n .filter((change) => change.path.length > 0);\n}\n\n/**\n * Get file content at a specific commit.\n *\n * @param file - File path (relative to repo root or absolute)\n * @param commit - Commit hash to retrieve content from\n * @param cwd - Working directory (for finding repo root)\n * @returns File content at that commit, or null if file doesn't exist at commit\n */\nexport async function getFileAtCommit(\n file: string,\n commit: string,\n cwd: string,\n): Promise<string | null> {\n // Get repo root to compute relative path\n const rootResult = await runGit([\"rev-parse\", \"--show-toplevel\"], cwd);\n if (!rootResult) {\n return null;\n }\n const repoRoot = rootResult.stdout.trim();\n\n let gitPath: string;\n\n if (path.isAbsolute(file)) {\n // For absolute paths, compute relative to repo root\n const relativePath = path.relative(repoRoot, file);\n\n // Guard against paths outside repo root\n if (relativePath.startsWith(\"..\")) {\n return null;\n }\n\n // Normalize to POSIX forward slashes (git requires forward slashes on all platforms)\n gitPath = relativePath.split(path.sep).join(\"/\");\n } else {\n // For relative paths (e.g., from git diff output), they are already repo-root-relative\n // Just normalize backslashes to forward slashes\n gitPath = file.replace(/\\\\/g, \"/\");\n }\n\n const result = await runGit([\"show\", `${commit}:${gitPath}`], cwd);\n return result?.stdout ?? null;\n}\n\n/**\n * Read the set of ignored revs for blame-like operations, if configured.\n *\n * Prefers the de-facto standard `.git-blame-ignore-revs` at the repo root, and\n * falls back to `blame.ignoreRevsFile` git config if set.\n *\n * Returns the rev SHAs (one per line) or null if no ignore revs are configured.\n *\n * @param cwd - Working directory\n * @returns Array of ignored revs (as strings) or null\n */\nexport async function getBlameIgnoreRevs(cwd: string): Promise<string[] | null> {\n const rootResult = await runGit([\"rev-parse\", \"--show-toplevel\"], cwd);\n if (!rootResult) {\n return null;\n }\n const repoRoot = rootResult.stdout.trim();\n\n async function readIgnoreFile(filePath: string): Promise<string[] | null> {\n try {\n const raw = await fs.readFile(filePath, \"utf8\");\n const revs = raw\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0 && !l.startsWith(\"#\"))\n // Git accepts abbreviated SHAs in ignore revs; keep them as-is\n .map((l) => l.split(/\\s+/)[0]);\n return revs.length > 0 ? revs : null;\n } catch {\n return null;\n }\n }\n\n const standardPath = path.join(repoRoot, \".git-blame-ignore-revs\");\n const standardRevs = await readIgnoreFile(standardPath);\n if (standardRevs) {\n return standardRevs;\n }\n\n const configResult = await runGit([\"config\", \"--get\", \"blame.ignoreRevsFile\"], cwd);\n const configPathRaw = configResult?.stdout.trim();\n if (!configPathRaw) {\n return null;\n }\n\n const resolved = path.isAbsolute(configPathRaw)\n ? configPathRaw\n : path.resolve(repoRoot, configPathRaw);\n return await readIgnoreFile(resolved);\n}\n\n/**\n * Check whether `ancestorCommit` is an ancestor of `descendantCommit`.\n *\n * Uses `git merge-base ancestor descendant` and compares the result to the ancestor.\n */\nexport async function isCommitAncestorOf(\n ancestorCommit: string,\n descendantCommit: string,\n cwd: string,\n): Promise<boolean> {\n const result = await runGit([\"merge-base\", ancestorCommit, descendantCommit], cwd);\n return result !== null && result.stdout.trim() === ancestorCommit;\n}\n\n/**\n * Get the set of commits currently blamed for a line range.\n *\n * @param file - File path (relative to repo root or absolute)\n * @param startLine - 1-based inclusive start line\n * @param endLine - 1-based inclusive end line\n * @param cwd - Working directory\n * @param ignoreRevsFile - Optional absolute path to ignore revs file\n */\nexport async function getBlameCommitsForLineRange(\n file: string,\n startLine: number,\n endLine: number,\n cwd: string,\n ignoreRevs?: string[] | null,\n): Promise<Set<string>> {\n // Get repo root to compute relative path\n const rootResult = await runGit([\"rev-parse\", \"--show-toplevel\"], cwd);\n if (!rootResult) {\n return new Set();\n }\n const repoRoot = rootResult.stdout.trim();\n\n let gitPath: string;\n if (path.isAbsolute(file)) {\n const relativePath = path.relative(repoRoot, file);\n if (relativePath.startsWith(\"..\")) {\n return new Set();\n }\n gitPath = relativePath.split(path.sep).join(\"/\");\n } else {\n gitPath = file.replace(/\\\\/g, \"/\");\n }\n\n const args = [\"blame\", \"--porcelain\", \"-L\", `${startLine},${endLine}`];\n if (ignoreRevs && ignoreRevs.length > 0) {\n for (const rev of ignoreRevs) {\n args.push(\"--ignore-rev\", rev);\n }\n }\n args.push(\"HEAD\", \"--\", gitPath);\n\n const result = await runGit(args, cwd);\n if (!result) {\n return new Set();\n }\n\n const commits = new Set<string>();\n for (const line of result.stdout.split(\"\\n\")) {\n // Porcelain blame header line: \"<sha> <orig-line> <final-line> <num-lines>\"\n if (/^[0-9a-f]{40}\\s/.test(line)) {\n commits.add(line.slice(0, 40));\n }\n }\n return commits;\n}\n\n/**\n * Check if a file was modified since a commit.\n *\n * Uses --follow to track the file through renames.\n *\n * @param file - File path to check\n * @param commit - Base commit to compare against\n * @param cwd - Working directory\n * @returns true if file was modified, false otherwise\n */\nexport async function wasFileModifiedSince(\n file: string,\n commit: string,\n cwd: string,\n): Promise<boolean> {\n // Get repo root to compute relative path\n const rootResult = await runGit([\"rev-parse\", \"--show-toplevel\"], cwd);\n if (!rootResult) {\n return false;\n }\n const repoRoot = rootResult.stdout.trim();\n\n let gitPath: string;\n\n if (path.isAbsolute(file)) {\n // For absolute paths, compute relative to repo root\n const relativePath = path.relative(repoRoot, file);\n\n // Guard against paths outside repo root\n if (relativePath.startsWith(\"..\")) {\n return false;\n }\n\n // Normalize to POSIX forward slashes (git requires forward slashes on all platforms)\n gitPath = relativePath.split(path.sep).join(\"/\");\n } else {\n // For relative paths (e.g., from git diff output), they are already repo-root-relative\n // Just normalize backslashes to forward slashes\n gitPath = file.replace(/\\\\/g, \"/\");\n }\n\n // Use git log with --follow to track through renames\n const result = await runGit(\n [\"log\", \"--follow\", \"-1\", \"--format=%H\", `${commit}..HEAD`, \"--\", gitPath],\n cwd,\n );\n\n // If we get a commit hash, the file was modified\n return result !== null && result.stdout.trim().length > 0;\n}\n\n/**\n * Check if a commit exists in the repository.\n *\n * @param commit - Commit hash to check\n * @param cwd - Working directory\n * @returns true if commit exists, false otherwise\n */\nexport async function commitExists(commit: string, cwd: string): Promise<boolean> {\n const result = await runGit([\"cat-file\", \"-t\", commit], cwd);\n return result !== null && result.stdout.trim() === \"commit\";\n}\n\n/**\n * Get list of files with uncommitted changes (staged or unstaged).\n *\n * @param cwd - Working directory\n * @param files - Optional list of files to filter (relative to repo root or absolute)\n * @returns Array of file paths (relative to repo root) with uncommitted changes\n */\nexport async function getUncommittedFiles(cwd: string, files?: string[]): Promise<string[]> {\n // Get repo root for path normalization\n const rootResult = await runGit([\"rev-parse\", \"--show-toplevel\"], cwd);\n if (!rootResult) {\n return [];\n }\n const repoRoot = rootResult.stdout.trim();\n\n // Build git status command - porcelain gives us stable, parseable output\n const args = [\"status\", \"--porcelain\", \"--\"];\n\n if (files && files.length > 0) {\n // Normalize file paths to be relative to repo root\n for (const file of files) {\n let gitPath: string;\n if (path.isAbsolute(file)) {\n const relativePath = path.relative(repoRoot, file);\n if (relativePath.startsWith(\"..\")) {\n continue; // Skip files outside repo\n }\n gitPath = relativePath.split(path.sep).join(\"/\");\n } else {\n gitPath = file.replace(/\\\\/g, \"/\");\n }\n args.push(gitPath);\n }\n }\n\n const result = await runGit(args, cwd);\n if (!result) {\n return [];\n }\n\n // Parse porcelain output: \"XY filename\" or \"XY old -> new\" for renames\n // X = index status, Y = worktree status\n // We want files where either X or Y indicates a change\n return result.stdout\n .trim()\n .split(\"\\n\")\n .filter((line) => line.length > 0)\n .map((line) => {\n // Skip the status codes (first 3 chars: XY + space)\n const filePart = line.slice(3);\n // Handle renames: \"old -> new\"\n if (filePart.includes(\" -> \")) {\n return filePart.split(\" -> \")[1];\n }\n return filePart;\n })\n .filter((f) => f.length > 0);\n}\n"],"mappings":";;;;;;AAKA,MAAM,gBAAgB,UAAU,SAAS;;;;AAiBzC,eAAe,OACb,MACA,KACoD;AACpD,KAAI;AAKF,SAJe,MAAM,cAAc,OAAO,MAAM;GAC9C;GACA,WAAW,KAAK,OAAO;GACxB,CAAC;SAEI;AACN,SAAO;;;;;;;;;;;;AAaX,eAAsB,iBAAiB,KAAkC;CAEvE,MAAM,iBAAiB,MAAM,OAAO,CAAC,aAAa,wBAAwB,EAAE,IAAI;AAEhF,KAAI,CAAC,kBAAkB,eAAe,OAAO,MAAM,KAAK,OACtD,QAAO,EAAE,WAAW,OAAO;AAW7B,QAAO;EACL,WAAW;EACX,UATiB,MAAM,OAAO,CAAC,aAAa,kBAAkB,EAAE,IAAI,GAC1C,OAAO,MAAM;EASvC,gBANmB,MAAM,OAAO,CAAC,aAAa,OAAO,EAAE,IAAI,GACzB,OAAO,MAAM;EAMhD;;;;;;;;AASH,eAAsB,iBAAiB,KAAqC;AAE1E,SADe,MAAM,OAAO,CAAC,aAAa,OAAO,EAAE,IAAI,GACxC,OAAO,MAAM,IAAI;;;;;;;;;;;AAwBlC,eAAsB,qBAAqB,QAAgB,KAAoC;CAC7F,MAAM,SAAS,MAAM,OAAO;EAAC;EAAQ;EAAM;EAAiB,GAAG,OAAO;EAAQ,EAAE,IAAI;AAEpF,KAAI,CAAC,OACH,QAAO,EAAE;AAGX,QAAO,OAAO,OACX,MAAM,CACN,MAAM,KAAK,CACX,QAAQ,SAAS,KAAK,SAAS,EAAE,CACjC,KAAK,SAAqB;EACzB,MAAM,QAAQ,KAAK,MAAM,IAAK;EAC9B,MAAM,aAAa,MAAM;AAGzB,MAAI,WAAW,WAAW,IAAI,EAAE;AAC9B,OAAI,MAAM,SAAS,EAEjB,QAAO;IAAE,QAAQ;IAAY,MAAM,MAAM,MAAM;IAAI;AAErD,UAAO;IACL,QAAQ;IACR,SAAS,MAAM;IACf,MAAM,MAAM;IACb;;EAIH,MAAM,SAAS,eAAe,MAAM,UAAU,eAAe,MAAM,YAAY;AAE/E,MAAI,MAAM,SAAS,EAEjB,QAAO;GAAE,QAAQ;GAAY,MAAM;GAAI;AAGzC,SAAO;GACL;GACA,MAAM,MAAM;GACb;GACD,CACD,QAAQ,WAAW,OAAO,KAAK,SAAS,EAAE;;;;;;;;;;AAW/C,eAAsB,gBACpB,MACA,QACA,KACwB;CAExB,MAAM,aAAa,MAAM,OAAO,CAAC,aAAa,kBAAkB,EAAE,IAAI;AACtE,KAAI,CAAC,WACH,QAAO;CAET,MAAM,WAAW,WAAW,OAAO,MAAM;CAEzC,IAAIA;AAEJ,KAAI,KAAK,WAAW,KAAK,EAAE;EAEzB,MAAM,eAAe,KAAK,SAAS,UAAU,KAAK;AAGlD,MAAI,aAAa,WAAW,KAAK,CAC/B,QAAO;AAIT,YAAU,aAAa,MAAM,KAAK,IAAI,CAAC,KAAK,IAAI;OAIhD,WAAU,KAAK,QAAQ,OAAO,IAAI;AAIpC,SADe,MAAM,OAAO,CAAC,QAAQ,GAAG,OAAO,GAAG,UAAU,EAAE,IAAI,GACnD,UAAU;;;;;;;;;;;;;AAc3B,eAAsB,mBAAmB,KAAuC;CAC9E,MAAM,aAAa,MAAM,OAAO,CAAC,aAAa,kBAAkB,EAAE,IAAI;AACtE,KAAI,CAAC,WACH,QAAO;CAET,MAAM,WAAW,WAAW,OAAO,MAAM;CAEzC,eAAe,eAAe,UAA4C;AACxE,MAAI;GAEF,MAAM,QADM,MAAM,GAAG,SAAS,UAAU,OAAO,EAE5C,MAAM,KAAK,CACX,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC,EAAE,WAAW,IAAI,CAAC,CAEjD,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC,GAAG;AAChC,UAAO,KAAK,SAAS,IAAI,OAAO;UAC1B;AACN,UAAO;;;CAKX,MAAM,eAAe,MAAM,eADN,KAAK,KAAK,UAAU,yBAAyB,CACX;AACvD,KAAI,aACF,QAAO;CAIT,MAAM,iBADe,MAAM,OAAO;EAAC;EAAU;EAAS;EAAuB,EAAE,IAAI,GAC/C,OAAO,MAAM;AACjD,KAAI,CAAC,cACH,QAAO;AAMT,QAAO,MAAM,eAHI,KAAK,WAAW,cAAc,GAC3C,gBACA,KAAK,QAAQ,UAAU,cAAc,CACJ;;;;;;;AAQvC,eAAsB,mBACpB,gBACA,kBACA,KACkB;CAClB,MAAM,SAAS,MAAM,OAAO;EAAC;EAAc;EAAgB;EAAiB,EAAE,IAAI;AAClF,QAAO,WAAW,QAAQ,OAAO,OAAO,MAAM,KAAK;;;;;;;;;;;AAYrD,eAAsB,4BACpB,MACA,WACA,SACA,KACA,YACsB;CAEtB,MAAM,aAAa,MAAM,OAAO,CAAC,aAAa,kBAAkB,EAAE,IAAI;AACtE,KAAI,CAAC,WACH,wBAAO,IAAI,KAAK;CAElB,MAAM,WAAW,WAAW,OAAO,MAAM;CAEzC,IAAIA;AACJ,KAAI,KAAK,WAAW,KAAK,EAAE;EACzB,MAAM,eAAe,KAAK,SAAS,UAAU,KAAK;AAClD,MAAI,aAAa,WAAW,KAAK,CAC/B,wBAAO,IAAI,KAAK;AAElB,YAAU,aAAa,MAAM,KAAK,IAAI,CAAC,KAAK,IAAI;OAEhD,WAAU,KAAK,QAAQ,OAAO,IAAI;CAGpC,MAAM,OAAO;EAAC;EAAS;EAAe;EAAM,GAAG,UAAU,GAAG;EAAU;AACtE,KAAI,cAAc,WAAW,SAAS,EACpC,MAAK,MAAM,OAAO,WAChB,MAAK,KAAK,gBAAgB,IAAI;AAGlC,MAAK,KAAK,QAAQ,MAAM,QAAQ;CAEhC,MAAM,SAAS,MAAM,OAAO,MAAM,IAAI;AACtC,KAAI,CAAC,OACH,wBAAO,IAAI,KAAK;CAGlB,MAAM,0BAAU,IAAI,KAAa;AACjC,MAAK,MAAM,QAAQ,OAAO,OAAO,MAAM,KAAK,CAE1C,KAAI,kBAAkB,KAAK,KAAK,CAC9B,SAAQ,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC;AAGlC,QAAO;;;;;;;;;AA6DT,eAAsB,aAAa,QAAgB,KAA+B;CAChF,MAAM,SAAS,MAAM,OAAO;EAAC;EAAY;EAAM;EAAO,EAAE,IAAI;AAC5D,QAAO,WAAW,QAAQ,OAAO,OAAO,MAAM,KAAK;;;;;;;;;AAUrD,eAAsB,oBAAoB,KAAa,OAAqC;CAE1F,MAAM,aAAa,MAAM,OAAO,CAAC,aAAa,kBAAkB,EAAE,IAAI;AACtE,KAAI,CAAC,WACH,QAAO,EAAE;CAEX,MAAM,WAAW,WAAW,OAAO,MAAM;CAGzC,MAAM,OAAO;EAAC;EAAU;EAAe;EAAK;AAE5C,KAAI,SAAS,MAAM,SAAS,EAE1B,MAAK,MAAM,QAAQ,OAAO;EACxB,IAAIA;AACJ,MAAI,KAAK,WAAW,KAAK,EAAE;GACzB,MAAM,eAAe,KAAK,SAAS,UAAU,KAAK;AAClD,OAAI,aAAa,WAAW,KAAK,CAC/B;AAEF,aAAU,aAAa,MAAM,KAAK,IAAI,CAAC,KAAK,IAAI;QAEhD,WAAU,KAAK,QAAQ,OAAO,IAAI;AAEpC,OAAK,KAAK,QAAQ;;CAItB,MAAM,SAAS,MAAM,OAAO,MAAM,IAAI;AACtC,KAAI,CAAC,OACH,QAAO,EAAE;AAMX,QAAO,OAAO,OACX,MAAM,CACN,MAAM,KAAK,CACX,QAAQ,SAAS,KAAK,SAAS,EAAE,CACjC,KAAK,SAAS;EAEb,MAAM,WAAW,KAAK,MAAM,EAAE;AAE9B,MAAI,SAAS,SAAS,OAAO,CAC3B,QAAO,SAAS,MAAM,OAAO,CAAC;AAEhC,SAAO;GACP,CACD,QAAQ,MAAM,EAAE,SAAS,EAAE"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Entry } from "../ast/ast-types.js";
|
|
2
|
+
import { EntryIdentity, EntryMatch } from "./entry-matcher.js";
|
|
3
|
+
|
|
4
|
+
//#region src/merge/conflict-detector.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Types of conflicts that can be detected
|
|
8
|
+
*/
|
|
9
|
+
type ConflictType = "duplicate-link-id" | "concurrent-metadata-update" | "incompatible-schema-change" | "concurrent-content-edit" | "concurrent-title-change" | "parse-error" | "merge-error";
|
|
10
|
+
/**
|
|
11
|
+
* Additional context for conflicts
|
|
12
|
+
*/
|
|
13
|
+
interface ConflictContext {
|
|
14
|
+
/**
|
|
15
|
+
* The metadata key involved (for metadata conflicts)
|
|
16
|
+
*/
|
|
17
|
+
metadataKey?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The link ID involved (for link conflicts)
|
|
20
|
+
*/
|
|
21
|
+
linkId?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The entity name involved (for schema conflicts)
|
|
24
|
+
*/
|
|
25
|
+
entityName?: string;
|
|
26
|
+
/**
|
|
27
|
+
* The field name involved (for schema field conflicts)
|
|
28
|
+
*/
|
|
29
|
+
fieldName?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Error message (for parse-error and merge-error conflicts)
|
|
32
|
+
*/
|
|
33
|
+
errorMessage?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A conflict detected during merging
|
|
37
|
+
*/
|
|
38
|
+
interface MergeConflict {
|
|
39
|
+
/**
|
|
40
|
+
* Type of conflict detected
|
|
41
|
+
*/
|
|
42
|
+
type: ConflictType;
|
|
43
|
+
/**
|
|
44
|
+
* Human-readable description of the conflict
|
|
45
|
+
*/
|
|
46
|
+
message: string;
|
|
47
|
+
/**
|
|
48
|
+
* Line number in the merged output where conflict appears
|
|
49
|
+
* (Computed during result building)
|
|
50
|
+
*/
|
|
51
|
+
location: number;
|
|
52
|
+
/**
|
|
53
|
+
* Identity of the conflicting entry (for unique keying)
|
|
54
|
+
*/
|
|
55
|
+
identity: EntryIdentity;
|
|
56
|
+
/**
|
|
57
|
+
* The base entry (common ancestor), if applicable
|
|
58
|
+
*/
|
|
59
|
+
base?: Entry;
|
|
60
|
+
/**
|
|
61
|
+
* The ours entry (local/current version)
|
|
62
|
+
*/
|
|
63
|
+
ours?: Entry;
|
|
64
|
+
/**
|
|
65
|
+
* The theirs entry (incoming version)
|
|
66
|
+
*/
|
|
67
|
+
theirs?: Entry;
|
|
68
|
+
/**
|
|
69
|
+
* Additional context about the conflict
|
|
70
|
+
*/
|
|
71
|
+
context?: ConflictContext;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A conflict detection rule
|
|
75
|
+
*/
|
|
76
|
+
interface ConflictRule {
|
|
77
|
+
/**
|
|
78
|
+
* Rule identifier
|
|
79
|
+
*/
|
|
80
|
+
name: string;
|
|
81
|
+
/**
|
|
82
|
+
* Check if this rule applies to a match
|
|
83
|
+
* @returns MergeConflict if conflict detected, null otherwise
|
|
84
|
+
*/
|
|
85
|
+
detect: (match: EntryMatch) => MergeConflict | null;
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ConflictContext, ConflictRule, ConflictType, MergeConflict };
|
|
89
|
+
//# sourceMappingURL=conflict-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflict-detector.d.ts","names":[],"sources":["../../src/merge/conflict-detector.ts"],"sourcesContent":[],"mappings":";;;;;;AAOA;AAYA;AA8BiB,KA1CL,YAAA,GA0CkB,mBAAA,GAAA,4BAAA,GAAA,4BAAA,GAAA,yBAAA,GAAA,yBAAA,GAAA,aAAA,GAAA,aAAA;;;;AA8BrB,UA5DQ,eAAA,CA4DR;EAKE;;;EAWM,WAAA,CAAA,EAAA,MAAY;;;;;;;;;;;;;;;;;;;;;UA9CZ,aAAA;;;;QAIT;;;;;;;;;;;;;YAgBI;;;;SAKH;;;;SAKA;;;;WAKE;;;;YAKC;;;;;UAMK,YAAA;;;;;;;;;kBAUC,eAAe"}
|