@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.
Files changed (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/dist/ast/ast-types.d.ts +469 -0
  4. package/dist/ast/ast-types.d.ts.map +1 -0
  5. package/dist/ast/ast-types.js +11 -0
  6. package/dist/ast/ast-types.js.map +1 -0
  7. package/dist/ast/builder.js +158 -0
  8. package/dist/ast/builder.js.map +1 -0
  9. package/dist/ast/extract.js +748 -0
  10. package/dist/ast/extract.js.map +1 -0
  11. package/dist/ast/node-at-position.d.ts +147 -0
  12. package/dist/ast/node-at-position.d.ts.map +1 -0
  13. package/dist/ast/node-at-position.js +382 -0
  14. package/dist/ast/node-at-position.js.map +1 -0
  15. package/dist/ast/visitor.js +232 -0
  16. package/dist/ast/visitor.js.map +1 -0
  17. package/dist/checker/check.d.ts +53 -0
  18. package/dist/checker/check.d.ts.map +1 -0
  19. package/dist/checker/check.js +105 -0
  20. package/dist/checker/check.js.map +1 -0
  21. package/dist/checker/rules/actualize-missing-updated.js +34 -0
  22. package/dist/checker/rules/actualize-missing-updated.js.map +1 -0
  23. package/dist/checker/rules/actualize-unresolved-target.js +42 -0
  24. package/dist/checker/rules/actualize-unresolved-target.js.map +1 -0
  25. package/dist/checker/rules/alter-before-define.js +53 -0
  26. package/dist/checker/rules/alter-before-define.js.map +1 -0
  27. package/dist/checker/rules/alter-undefined-entity.js +32 -0
  28. package/dist/checker/rules/alter-undefined-entity.js.map +1 -0
  29. package/dist/checker/rules/create-requires-section.js +34 -0
  30. package/dist/checker/rules/create-requires-section.js.map +1 -0
  31. package/dist/checker/rules/define-entity-requires-section.js +31 -0
  32. package/dist/checker/rules/define-entity-requires-section.js.map +1 -0
  33. package/dist/checker/rules/duplicate-entity-definition.js +37 -0
  34. package/dist/checker/rules/duplicate-entity-definition.js.map +1 -0
  35. package/dist/checker/rules/duplicate-field-in-schema.js +38 -0
  36. package/dist/checker/rules/duplicate-field-in-schema.js.map +1 -0
  37. package/dist/checker/rules/duplicate-link-id.js +52 -0
  38. package/dist/checker/rules/duplicate-link-id.js.map +1 -0
  39. package/dist/checker/rules/duplicate-metadata-key.js +21 -0
  40. package/dist/checker/rules/duplicate-metadata-key.js.map +1 -0
  41. package/dist/checker/rules/duplicate-section-heading.js +41 -0
  42. package/dist/checker/rules/duplicate-section-heading.js.map +1 -0
  43. package/dist/checker/rules/duplicate-section-in-schema.js +38 -0
  44. package/dist/checker/rules/duplicate-section-in-schema.js.map +1 -0
  45. package/dist/checker/rules/duplicate-timestamp.js +104 -0
  46. package/dist/checker/rules/duplicate-timestamp.js.map +1 -0
  47. package/dist/checker/rules/empty-required-value.js +45 -0
  48. package/dist/checker/rules/empty-required-value.js.map +1 -0
  49. package/dist/checker/rules/empty-section.js +21 -0
  50. package/dist/checker/rules/empty-section.js.map +1 -0
  51. package/dist/checker/rules/invalid-date-range-value.js +56 -0
  52. package/dist/checker/rules/invalid-date-range-value.js.map +1 -0
  53. package/dist/checker/rules/invalid-default-value.js +86 -0
  54. package/dist/checker/rules/invalid-default-value.js.map +1 -0
  55. package/dist/checker/rules/invalid-field-type.js +45 -0
  56. package/dist/checker/rules/invalid-field-type.js.map +1 -0
  57. package/dist/checker/rules/missing-required-field.js +48 -0
  58. package/dist/checker/rules/missing-required-field.js.map +1 -0
  59. package/dist/checker/rules/missing-required-section.js +51 -0
  60. package/dist/checker/rules/missing-required-section.js.map +1 -0
  61. package/dist/checker/rules/missing-title.js +56 -0
  62. package/dist/checker/rules/missing-title.js.map +1 -0
  63. package/dist/checker/rules/remove-undefined-field.js +42 -0
  64. package/dist/checker/rules/remove-undefined-field.js.map +1 -0
  65. package/dist/checker/rules/remove-undefined-section.js +42 -0
  66. package/dist/checker/rules/remove-undefined-section.js.map +1 -0
  67. package/dist/checker/rules/rules.d.ts +71 -0
  68. package/dist/checker/rules/rules.d.ts.map +1 -0
  69. package/dist/checker/rules/rules.js +102 -0
  70. package/dist/checker/rules/rules.js.map +1 -0
  71. package/dist/checker/rules/synthesis-empty-query.js +35 -0
  72. package/dist/checker/rules/synthesis-empty-query.js.map +1 -0
  73. package/dist/checker/rules/synthesis-missing-prompt.js +42 -0
  74. package/dist/checker/rules/synthesis-missing-prompt.js.map +1 -0
  75. package/dist/checker/rules/synthesis-missing-sources.js +32 -0
  76. package/dist/checker/rules/synthesis-missing-sources.js.map +1 -0
  77. package/dist/checker/rules/synthesis-unknown-query-entity.js +39 -0
  78. package/dist/checker/rules/synthesis-unknown-query-entity.js.map +1 -0
  79. package/dist/checker/rules/timestamp-out-of-order.js +55 -0
  80. package/dist/checker/rules/timestamp-out-of-order.js.map +1 -0
  81. package/dist/checker/rules/unknown-entity.js +32 -0
  82. package/dist/checker/rules/unknown-entity.js.map +1 -0
  83. package/dist/checker/rules/unknown-field.js +40 -0
  84. package/dist/checker/rules/unknown-field.js.map +1 -0
  85. package/dist/checker/rules/unknown-section.js +47 -0
  86. package/dist/checker/rules/unknown-section.js.map +1 -0
  87. package/dist/checker/rules/unresolved-link.js +34 -0
  88. package/dist/checker/rules/unresolved-link.js.map +1 -0
  89. package/dist/checker/rules/update-without-create.js +65 -0
  90. package/dist/checker/rules/update-without-create.js.map +1 -0
  91. package/dist/checker/visitor.d.ts +69 -0
  92. package/dist/checker/visitor.d.ts.map +1 -0
  93. package/dist/checker/visitor.js +67 -0
  94. package/dist/checker/visitor.js.map +1 -0
  95. package/dist/checker/workspace-index.d.ts +50 -0
  96. package/dist/checker/workspace-index.d.ts.map +1 -0
  97. package/dist/checker/workspace-index.js +108 -0
  98. package/dist/checker/workspace-index.js.map +1 -0
  99. package/dist/commands/actualize.d.ts +113 -0
  100. package/dist/commands/actualize.d.ts.map +1 -0
  101. package/dist/commands/actualize.js +111 -0
  102. package/dist/commands/actualize.js.map +1 -0
  103. package/dist/commands/check.d.ts +65 -0
  104. package/dist/commands/check.d.ts.map +1 -0
  105. package/dist/commands/check.js +61 -0
  106. package/dist/commands/check.js.map +1 -0
  107. package/dist/commands/format.d.ts +90 -0
  108. package/dist/commands/format.d.ts.map +1 -0
  109. package/dist/commands/format.js +80 -0
  110. package/dist/commands/format.js.map +1 -0
  111. package/dist/commands/query.d.ts +152 -0
  112. package/dist/commands/query.d.ts.map +1 -0
  113. package/dist/commands/query.js +151 -0
  114. package/dist/commands/query.js.map +1 -0
  115. package/dist/constants.d.ts +31 -0
  116. package/dist/constants.d.ts.map +1 -0
  117. package/dist/constants.js +51 -0
  118. package/dist/constants.js.map +1 -0
  119. package/dist/files.d.ts +58 -0
  120. package/dist/files.d.ts.map +1 -0
  121. package/dist/files.js +103 -0
  122. package/dist/files.js.map +1 -0
  123. package/dist/formatters.d.ts +39 -0
  124. package/dist/formatters.d.ts.map +1 -0
  125. package/dist/formatters.js +200 -0
  126. package/dist/formatters.js.map +1 -0
  127. package/dist/fragment.d.ts +22 -0
  128. package/dist/fragment.d.ts.map +1 -0
  129. package/dist/git/git.js +240 -0
  130. package/dist/git/git.js.map +1 -0
  131. package/dist/merge/conflict-detector.d.ts +89 -0
  132. package/dist/merge/conflict-detector.d.ts.map +1 -0
  133. package/dist/merge/conflict-detector.js +352 -0
  134. package/dist/merge/conflict-detector.js.map +1 -0
  135. package/dist/merge/conflict-formatter.js +143 -0
  136. package/dist/merge/conflict-formatter.js.map +1 -0
  137. package/dist/merge/driver.d.ts +54 -0
  138. package/dist/merge/driver.d.ts.map +1 -0
  139. package/dist/merge/driver.js +112 -0
  140. package/dist/merge/driver.js.map +1 -0
  141. package/dist/merge/entry-matcher.d.ts +50 -0
  142. package/dist/merge/entry-matcher.d.ts.map +1 -0
  143. package/dist/merge/entry-matcher.js +141 -0
  144. package/dist/merge/entry-matcher.js.map +1 -0
  145. package/dist/merge/entry-merger.js +194 -0
  146. package/dist/merge/entry-merger.js.map +1 -0
  147. package/dist/merge/merge-result-builder.d.ts +62 -0
  148. package/dist/merge/merge-result-builder.d.ts.map +1 -0
  149. package/dist/merge/merge-result-builder.js +89 -0
  150. package/dist/merge/merge-result-builder.js.map +1 -0
  151. package/dist/mod.d.ts +31 -0
  152. package/dist/mod.js +23 -0
  153. package/dist/model/document.d.ts +134 -0
  154. package/dist/model/document.d.ts.map +1 -0
  155. package/dist/model/document.js +275 -0
  156. package/dist/model/document.js.map +1 -0
  157. package/dist/model/line-index.d.ts +85 -0
  158. package/dist/model/line-index.d.ts.map +1 -0
  159. package/dist/model/line-index.js +159 -0
  160. package/dist/model/line-index.js.map +1 -0
  161. package/dist/model/workspace.d.ts +296 -0
  162. package/dist/model/workspace.d.ts.map +1 -0
  163. package/dist/model/workspace.js +562 -0
  164. package/dist/model/workspace.js.map +1 -0
  165. package/dist/parser.js +27 -0
  166. package/dist/parser.js.map +1 -0
  167. package/dist/parser.native.d.ts +51 -0
  168. package/dist/parser.native.d.ts.map +1 -0
  169. package/dist/parser.native.js +62 -0
  170. package/dist/parser.native.js.map +1 -0
  171. package/dist/parser.shared.d.ts +99 -0
  172. package/dist/parser.shared.d.ts.map +1 -0
  173. package/dist/parser.shared.js +124 -0
  174. package/dist/parser.shared.js.map +1 -0
  175. package/dist/parser.web.d.ts +67 -0
  176. package/dist/parser.web.d.ts.map +1 -0
  177. package/dist/parser.web.js +49 -0
  178. package/dist/parser.web.js.map +1 -0
  179. package/dist/schema/registry.d.ts +108 -0
  180. package/dist/schema/registry.d.ts.map +1 -0
  181. package/dist/schema/registry.js +281 -0
  182. package/dist/schema/registry.js.map +1 -0
  183. package/dist/semantic/analyzer.d.ts +107 -0
  184. package/dist/semantic/analyzer.d.ts.map +1 -0
  185. package/dist/semantic/analyzer.js +261 -0
  186. package/dist/semantic/analyzer.js.map +1 -0
  187. package/dist/services/change-tracker/change-tracker.d.ts +111 -0
  188. package/dist/services/change-tracker/change-tracker.d.ts.map +1 -0
  189. package/dist/services/change-tracker/change-tracker.js +62 -0
  190. package/dist/services/change-tracker/change-tracker.js.map +1 -0
  191. package/dist/services/change-tracker/create-tracker.d.ts +42 -0
  192. package/dist/services/change-tracker/create-tracker.d.ts.map +1 -0
  193. package/dist/services/change-tracker/create-tracker.js +53 -0
  194. package/dist/services/change-tracker/create-tracker.js.map +1 -0
  195. package/dist/services/change-tracker/git-tracker.d.ts +59 -0
  196. package/dist/services/change-tracker/git-tracker.d.ts.map +1 -0
  197. package/dist/services/change-tracker/git-tracker.js +218 -0
  198. package/dist/services/change-tracker/git-tracker.js.map +1 -0
  199. package/dist/services/change-tracker/timestamp-tracker.d.ts +22 -0
  200. package/dist/services/change-tracker/timestamp-tracker.d.ts.map +1 -0
  201. package/dist/services/change-tracker/timestamp-tracker.js +74 -0
  202. package/dist/services/change-tracker/timestamp-tracker.js.map +1 -0
  203. package/dist/services/definition.d.ts +37 -0
  204. package/dist/services/definition.d.ts.map +1 -0
  205. package/dist/services/definition.js +43 -0
  206. package/dist/services/definition.js.map +1 -0
  207. package/dist/services/entity-navigation.d.ts +200 -0
  208. package/dist/services/entity-navigation.d.ts.map +1 -0
  209. package/dist/services/entity-navigation.js +211 -0
  210. package/dist/services/entity-navigation.js.map +1 -0
  211. package/dist/services/hover.d.ts +81 -0
  212. package/dist/services/hover.d.ts.map +1 -0
  213. package/dist/services/hover.js +669 -0
  214. package/dist/services/hover.js.map +1 -0
  215. package/dist/services/query.d.ts +116 -0
  216. package/dist/services/query.d.ts.map +1 -0
  217. package/dist/services/query.js +225 -0
  218. package/dist/services/query.js.map +1 -0
  219. package/dist/services/references.d.ts +52 -0
  220. package/dist/services/references.d.ts.map +1 -0
  221. package/dist/services/references.js +66 -0
  222. package/dist/services/references.js.map +1 -0
  223. package/dist/services/semantic-tokens.d.ts +54 -0
  224. package/dist/services/semantic-tokens.d.ts.map +1 -0
  225. package/dist/services/semantic-tokens.js +213 -0
  226. package/dist/services/semantic-tokens.js.map +1 -0
  227. package/dist/services/synthesis.d.ts +90 -0
  228. package/dist/services/synthesis.d.ts.map +1 -0
  229. package/dist/services/synthesis.js +113 -0
  230. package/dist/services/synthesis.js.map +1 -0
  231. package/dist/source-map.d.ts +42 -0
  232. package/dist/source-map.d.ts.map +1 -0
  233. package/dist/source-map.js +170 -0
  234. package/dist/source-map.js.map +1 -0
  235. package/package.json +128 -0
  236. package/tree-sitter-thalo.wasm +0 -0
  237. package/web-tree-sitter.wasm +0 -0
@@ -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":"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"}
@@ -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"}