@smwb/onreel-git-mcp 0.0.0-stage → 0.2.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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-3YP22YLA.js +50 -0
  3. package/dist/chunk-3YP22YLA.js.map +1 -0
  4. package/dist/chunk-4ZGRXIVA.js +259 -0
  5. package/dist/chunk-4ZGRXIVA.js.map +1 -0
  6. package/dist/chunk-C6PHNSDC.js +112 -0
  7. package/dist/chunk-C6PHNSDC.js.map +1 -0
  8. package/dist/chunk-GPRMI4PG.js +421 -0
  9. package/dist/chunk-GPRMI4PG.js.map +1 -0
  10. package/dist/chunk-L7CPE2SO.js +166 -0
  11. package/dist/chunk-L7CPE2SO.js.map +1 -0
  12. package/dist/contract/contract.const.d.ts +190 -0
  13. package/dist/contract/contract.lib.d.ts +22 -0
  14. package/dist/contract/contract.schema.d.ts +279 -0
  15. package/dist/contract/contract.types.d.ts +23 -0
  16. package/dist/files/files.const.d.ts +6 -0
  17. package/dist/files/files.lib.d.ts +14 -0
  18. package/dist/files/files.types.d.ts +13 -0
  19. package/dist/files/index.d.ts +3 -0
  20. package/dist/files/index.js +163 -0
  21. package/dist/files/index.js.map +1 -0
  22. package/dist/git/git.const.d.ts +4 -0
  23. package/dist/git/git.lib.d.ts +90 -0
  24. package/dist/git/git.types.d.ts +33 -0
  25. package/dist/git/index.d.ts +7 -0
  26. package/dist/git/index.js +97 -0
  27. package/dist/git/index.js.map +1 -0
  28. package/dist/git/paths.const.d.ts +5 -0
  29. package/dist/git/paths.lib.d.ts +39 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.js +117 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/kernel/log.types.d.ts +5 -0
  34. package/dist/kernel/redact.lib.d.ts +25 -0
  35. package/dist/kernel/store.types.d.ts +109 -0
  36. package/dist/kernel/umask.lib.d.ts +7 -0
  37. package/dist/mcp/args.lib.d.ts +17 -0
  38. package/dist/mcp/envelope.lib.d.ts +20 -0
  39. package/dist/mcp/index.d.ts +8 -0
  40. package/dist/mcp/index.js +744 -0
  41. package/dist/mcp/index.js.map +1 -0
  42. package/dist/mcp/mcp.const.d.ts +23 -0
  43. package/dist/mcp/mcp.schema.d.ts +64 -0
  44. package/dist/mcp/mcp.types.d.ts +125 -0
  45. package/dist/mcp/principal.lib.d.ts +22 -0
  46. package/dist/mcp/server.lib.d.ts +3 -0
  47. package/dist/mcp/tools.lib.d.ts +16 -0
  48. package/dist/tracker/builtin.lib.d.ts +1 -0
  49. package/dist/tracker/github.lib.d.ts +2 -0
  50. package/dist/tracker/gitlab.lib.d.ts +2 -0
  51. package/dist/tracker/http.lib.d.ts +4 -0
  52. package/dist/tracker/index.d.ts +15 -0
  53. package/dist/tracker/index.js +840 -0
  54. package/dist/tracker/index.js.map +1 -0
  55. package/dist/tracker/jira.lib.d.ts +2 -0
  56. package/dist/tracker/local.lib.d.ts +2 -0
  57. package/dist/tracker/mapping.const.d.ts +26 -0
  58. package/dist/tracker/registry.lib.d.ts +5 -0
  59. package/dist/tracker/secrets.lib.d.ts +7 -0
  60. package/dist/tracker/sourcecraft.lib.d.ts +2 -0
  61. package/dist/tracker/tracker.const.d.ts +10 -0
  62. package/dist/tracker/tracker.lib.d.ts +21 -0
  63. package/dist/tracker/tracker.types.d.ts +77 -0
  64. package/dist/worktree/index.d.ts +4 -0
  65. package/dist/worktree/index.js +203 -0
  66. package/dist/worktree/index.js.map +1 -0
  67. package/dist/worktree/lifecycle.const.d.ts +2 -0
  68. package/dist/worktree/lifecycle.lib.d.ts +30 -0
  69. package/dist/worktree/lifecycle.types.d.ts +14 -0
  70. package/package.json +74 -4
  71. package/README.md +0 -3
@@ -0,0 +1,279 @@
1
+ import { z } from 'zod';
2
+ /** Идентификатор сущности: Run, Task, Dispatch, письма, gate, сессии и т. п. */
3
+ export declare const entityIdSchema: z.ZodString;
4
+ /** Ключ идемпотентности запроса: его создаёт клиент, повтор с тем же ключом возвращает receipt. */
5
+ export declare const requestIdSchema: z.ZodString;
6
+ export declare const projectIdSchema: z.ZodString;
7
+ export declare const taskIdSchema: z.ZodString;
8
+ /** Время — миллисекунды Unix epoch, как INTEGER в хранилище. */
9
+ export declare const timestampSchema: z.ZodNumber;
10
+ export declare const contractVersionSchema: z.ZodObject<{
11
+ major: z.ZodNumber;
12
+ minor: z.ZodNumber;
13
+ }, z.core.$strip>;
14
+ export declare const errorCodeSchema: z.ZodEnum<{
15
+ agent_unavailable: "agent_unavailable";
16
+ agent_untrusted: "agent_untrusted";
17
+ budget_exceeded: "budget_exceeded";
18
+ capability_unsupported: "capability_unsupported";
19
+ claim_conflict: "claim_conflict";
20
+ claim_limit: "claim_limit";
21
+ consumer_fenced: "consumer_fenced";
22
+ contract_incompatible: "contract_incompatible";
23
+ git_failed: "git_failed";
24
+ host_not_live: "host_not_live";
25
+ inactive_dispatch: "inactive_dispatch";
26
+ internal: "internal";
27
+ invalid_request: "invalid_request";
28
+ lease_conflict: "lease_conflict";
29
+ lease_expired: "lease_expired";
30
+ lease_fenced: "lease_fenced";
31
+ lifecycle_conflict: "lifecycle_conflict";
32
+ member_name_taken: "member_name_taken";
33
+ messaging_denied: "messaging_denied";
34
+ not_assignee: "not_assignee";
35
+ not_found: "not_found";
36
+ ownership_conflict: "ownership_conflict";
37
+ permission_denied: "permission_denied";
38
+ plan_required: "plan_required";
39
+ project_exists: "project_exists";
40
+ rate_limited: "rate_limited";
41
+ request_id_reused: "request_id_reused";
42
+ sandbox_unavailable: "sandbox_unavailable";
43
+ session_exited: "session_exited";
44
+ spawn_failed: "spawn_failed";
45
+ stale_dispatch: "stale_dispatch";
46
+ task_dispatch_mismatch: "task_dispatch_mismatch";
47
+ tracker_failed: "tracker_failed";
48
+ unauthorized: "unauthorized";
49
+ unavailable: "unavailable";
50
+ unknown_dispatch: "unknown_dispatch";
51
+ unknown_issue: "unknown_issue";
52
+ unknown_project: "unknown_project";
53
+ unknown_session: "unknown_session";
54
+ unknown_task: "unknown_task";
55
+ }>;
56
+ /** Код на проводе: известный код каталога или код более новой версии контракта. */
57
+ export declare const wireErrorCodeSchema: z.ZodString;
58
+ export declare const contractErrorSchema: z.ZodObject<{
59
+ code: z.ZodString;
60
+ message: z.ZodString;
61
+ nextSteps: z.ZodArray<z.ZodString>;
62
+ data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
63
+ }, z.core.$strip>;
64
+ /** Вид трекера — открытый слаг: новый трекер добавляется подключением на host без правки контракта. */
65
+ export declare const trackerKindSchema: z.ZodString;
66
+ /** Ключ задачи в своём трекере: `76`, `PROJ-12`, `#5`; для клиента непрозрачная строка. */
67
+ export declare const issueKeySchema: z.ZodString;
68
+ /** Категория статуса единой модели; своё имя статуса трекер отдаёт отдельно, в `status.name`. */
69
+ export declare const issueStatusCategorySchema: z.ZodEnum<{
70
+ declined: "declined";
71
+ done: "done";
72
+ in_progress: "in_progress";
73
+ open: "open";
74
+ }>;
75
+ /** Приоритет единой модели; трекер без приоритетов не заполняет поле. */
76
+ export declare const issuePrioritySchema: z.ZodEnum<{
77
+ blocker: "blocker";
78
+ critical: "critical";
79
+ minor: "minor";
80
+ normal: "normal";
81
+ trivial: "trivial";
82
+ }>;
83
+ export declare const issueLinkSchema: z.ZodObject<{
84
+ url: z.ZodString;
85
+ title: z.ZodOptional<z.ZodString>;
86
+ }, z.core.$strip>;
87
+ export declare const issueSchema: z.ZodObject<{
88
+ key: z.ZodString;
89
+ title: z.ZodString;
90
+ description: z.ZodOptional<z.ZodString>;
91
+ status: z.ZodObject<{
92
+ category: z.ZodEnum<{
93
+ declined: "declined";
94
+ done: "done";
95
+ in_progress: "in_progress";
96
+ open: "open";
97
+ }>;
98
+ name: z.ZodString;
99
+ }, z.core.$strip>;
100
+ priority: z.ZodOptional<z.ZodEnum<{
101
+ blocker: "blocker";
102
+ critical: "critical";
103
+ minor: "minor";
104
+ normal: "normal";
105
+ trivial: "trivial";
106
+ }>>;
107
+ labels: z.ZodArray<z.ZodString>;
108
+ assignee: z.ZodOptional<z.ZodString>;
109
+ links: z.ZodArray<z.ZodObject<{
110
+ url: z.ZodString;
111
+ title: z.ZodOptional<z.ZodString>;
112
+ }, z.core.$strip>>;
113
+ createdAt: z.ZodOptional<z.ZodNumber>;
114
+ updatedAt: z.ZodOptional<z.ZodNumber>;
115
+ }, z.core.$strip>;
116
+ export declare const issueCommentSchema: z.ZodObject<{
117
+ id: z.ZodString;
118
+ author: z.ZodOptional<z.ZodString>;
119
+ body: z.ZodString;
120
+ createdAt: z.ZodOptional<z.ZodNumber>;
121
+ }, z.core.$strip>;
122
+ /** Поля подключения, которые descriptor может запросить у пользователя. */
123
+ export declare const trackerFieldNameSchema: z.ZodEnum<{
124
+ target: "target";
125
+ token: "token";
126
+ url: "url";
127
+ user: "user";
128
+ }>;
129
+ /** Что трекер умеет: клиент прячет недоступные действия, а host отказывает в них `invalid_request`. */
130
+ export declare const trackerFeaturesSchema: z.ZodObject<{
131
+ create: z.ZodBoolean;
132
+ update: z.ZodBoolean;
133
+ comment: z.ZodBoolean;
134
+ priority: z.ZodBoolean;
135
+ labels: z.ZodBoolean;
136
+ assignee: z.ZodBoolean;
137
+ statuses: z.ZodArray<z.ZodEnum<{
138
+ declined: "declined";
139
+ done: "done";
140
+ in_progress: "in_progress";
141
+ open: "open";
142
+ }>>;
143
+ }, z.core.$strip>;
144
+ export declare const trackerDescriptorSchema: z.ZodObject<{
145
+ tracker: z.ZodString;
146
+ title: z.ZodString;
147
+ fields: z.ZodArray<z.ZodObject<{
148
+ name: z.ZodEnum<{
149
+ target: "target";
150
+ token: "token";
151
+ url: "url";
152
+ user: "user";
153
+ }>;
154
+ required: z.ZodBoolean;
155
+ secret: z.ZodBoolean;
156
+ }, z.core.$strip>>;
157
+ features: z.ZodObject<{
158
+ create: z.ZodBoolean;
159
+ update: z.ZodBoolean;
160
+ comment: z.ZodBoolean;
161
+ priority: z.ZodBoolean;
162
+ labels: z.ZodBoolean;
163
+ assignee: z.ZodBoolean;
164
+ statuses: z.ZodArray<z.ZodEnum<{
165
+ declined: "declined";
166
+ done: "done";
167
+ in_progress: "in_progress";
168
+ open: "open";
169
+ }>>;
170
+ }, z.core.$strip>;
171
+ }, z.core.$strip>;
172
+ /** Идентификатор Worktree: `wt-<uuid>`, как другие entityId контракта. */
173
+ export declare const worktreeIdSchema: z.ZodString;
174
+ /**
175
+ * Имя worktree проекта без задачи (onreel D-345): часть каталога `<repo>/_project/<name>` и ветки
176
+ * `onreel/_project/<name>`, поэтому только строчные латинские буквы, цифры и `-`; уникально среди
177
+ * неочищенных worktree проекта.
178
+ */
179
+ export declare const worktreeNameSchema: z.ZodString;
180
+ /** Коммит git: 40 hex (SHA-1) или 64 hex (SHA-256 репозитории). */
181
+ export declare const gitShaSchema: z.ZodString;
182
+ /** Состояние Worktree (onreel D-050): preparing — receipt принят, git ещё создаёт; ready — рабочий;
183
+ * cleaning — очистка принята, диск ещё освобождается; cleaned — очищен (терминальное);
184
+ * failed — создание или очистка не справились, причина в error, ждёт решения (worktree.remove). */
185
+ export declare const worktreeStatusSchema: z.ZodEnum<{
186
+ cleaned: "cleaned";
187
+ cleaning: "cleaning";
188
+ failed: "failed";
189
+ preparing: "preparing";
190
+ ready: "ready";
191
+ }>;
192
+ /** Запись Worktree в хранилище: привязка к Task или проекту плюс git-факты (ветка, база, пути, голова). */
193
+ export declare const worktreeSchema: z.ZodObject<{
194
+ id: z.ZodString;
195
+ taskId: z.ZodOptional<z.ZodString>;
196
+ projectId: z.ZodOptional<z.ZodString>;
197
+ name: z.ZodOptional<z.ZodString>;
198
+ branch: z.ZodString;
199
+ baseRef: z.ZodString;
200
+ baseSha: z.ZodString;
201
+ repoPath: z.ZodString;
202
+ path: z.ZodString;
203
+ status: z.ZodEnum<{
204
+ cleaned: "cleaned";
205
+ cleaning: "cleaning";
206
+ failed: "failed";
207
+ preparing: "preparing";
208
+ ready: "ready";
209
+ }>;
210
+ headSha: z.ZodOptional<z.ZodString>;
211
+ error: z.ZodOptional<z.ZodString>;
212
+ createdAt: z.ZodNumber;
213
+ updatedAt: z.ZodNumber;
214
+ cleanedAt: z.ZodOptional<z.ZodNumber>;
215
+ }, z.core.$strip>;
216
+ /** Одна строка `git status --porcelain=v1`: две колонки XY и путь. */
217
+ export declare const worktreeStatusEntrySchema: z.ZodObject<{
218
+ path: z.ZodString;
219
+ index: z.ZodString;
220
+ worktree: z.ZodString;
221
+ }, z.core.$strip>;
222
+ /** Путь внутри worktree: posix, относительный, без «..», NUL, обратных слэшей и ведущего «/». */
223
+ export declare const reviewPathSchema: z.ZodString;
224
+ export declare const worktreeChangeStatusSchema: z.ZodEnum<{
225
+ added: "added";
226
+ conflicted: "conflicted";
227
+ copied: "copied";
228
+ deleted: "deleted";
229
+ modified: "modified";
230
+ renamed: "renamed";
231
+ type_changed: "type_changed";
232
+ }>;
233
+ export declare const worktreeChangeSchema: z.ZodObject<{
234
+ path: z.ZodString;
235
+ oldPath: z.ZodOptional<z.ZodString>;
236
+ status: z.ZodEnum<{
237
+ added: "added";
238
+ conflicted: "conflicted";
239
+ copied: "copied";
240
+ deleted: "deleted";
241
+ modified: "modified";
242
+ renamed: "renamed";
243
+ type_changed: "type_changed";
244
+ }>;
245
+ untracked: z.ZodOptional<z.ZodBoolean>;
246
+ binary: z.ZodOptional<z.ZodBoolean>;
247
+ additions: z.ZodOptional<z.ZodNumber>;
248
+ deletions: z.ZodOptional<z.ZodNumber>;
249
+ }, z.core.$strip>;
250
+ /** Одна сторона файла; текста нет при `binary` или `truncated`. */
251
+ export declare const worktreeFileSideSchema: z.ZodObject<{
252
+ size: z.ZodNumber;
253
+ binary: z.ZodBoolean;
254
+ truncated: z.ZodBoolean;
255
+ text: z.ZodOptional<z.ZodString>;
256
+ }, z.core.$strip>;
257
+ /**
258
+ * Путь worktree.files — каталога запроса и записи ответа: путь ревью (D-320) без компоненты `.git` в любом
259
+ * регистре и без компоненты «.». У каждой записи одно написание пути: `./a` и `a` — не два файла.
260
+ */
261
+ export declare const worktreeDirPathSchema: z.ZodString;
262
+ /** Вид записи: `dir` раскрывается, `file` и `symlink` открываются просмотром, `other` (fifo, сокет, устройство) — нет. */
263
+ export declare const worktreeEntryKindSchema: z.ZodEnum<{
264
+ dir: "dir";
265
+ file: "file";
266
+ other: "other";
267
+ symlink: "symlink";
268
+ }>;
269
+ export declare const worktreeEntrySchema: z.ZodObject<{
270
+ name: z.ZodString;
271
+ path: z.ZodString;
272
+ kind: z.ZodEnum<{
273
+ dir: "dir";
274
+ file: "file";
275
+ other: "other";
276
+ symlink: "symlink";
277
+ }>;
278
+ size: z.ZodOptional<z.ZodNumber>;
279
+ }, z.core.$strip>;
@@ -0,0 +1,23 @@
1
+ import type { z } from 'zod';
2
+ import type { contractErrorSchema, contractVersionSchema, entityIdSchema, issueCommentSchema, issueLinkSchema, issuePrioritySchema, issueSchema, issueStatusCategorySchema, requestIdSchema, timestampSchema, trackerDescriptorSchema, trackerFeaturesSchema, trackerKindSchema, worktreeChangeSchema, worktreeChangeStatusSchema, worktreeEntryKindSchema, worktreeEntrySchema, worktreeFileSideSchema, worktreeSchema, worktreeStatusEntrySchema, worktreeStatusSchema } from './contract.schema.ts';
3
+ export type EntityId = z.infer<typeof entityIdSchema>;
4
+ export type RequestId = z.infer<typeof requestIdSchema>;
5
+ export type Timestamp = z.infer<typeof timestampSchema>;
6
+ export type ContractVersion = z.infer<typeof contractVersionSchema>;
7
+ export type ContractError = z.infer<typeof contractErrorSchema>;
8
+ export type TrackerKind = z.infer<typeof trackerKindSchema>;
9
+ export type IssueStatusCategory = z.infer<typeof issueStatusCategorySchema>;
10
+ export type IssuePriority = z.infer<typeof issuePrioritySchema>;
11
+ export type IssueLink = z.infer<typeof issueLinkSchema>;
12
+ export type Issue = z.infer<typeof issueSchema>;
13
+ export type IssueComment = z.infer<typeof issueCommentSchema>;
14
+ export type TrackerFeatures = z.infer<typeof trackerFeaturesSchema>;
15
+ export type TrackerDescriptor = z.infer<typeof trackerDescriptorSchema>;
16
+ export type Worktree = z.infer<typeof worktreeSchema>;
17
+ export type WorktreeStatus = z.infer<typeof worktreeStatusSchema>;
18
+ export type WorktreeStatusEntry = z.infer<typeof worktreeStatusEntrySchema>;
19
+ export type WorktreeChange = z.infer<typeof worktreeChangeSchema>;
20
+ export type WorktreeChangeStatus = z.infer<typeof worktreeChangeStatusSchema>;
21
+ export type WorktreeFileSide = z.infer<typeof worktreeFileSideSchema>;
22
+ export type WorktreeEntryKind = z.infer<typeof worktreeEntryKindSchema>;
23
+ export type WorktreeEntry = z.infer<typeof worktreeEntrySchema>;
@@ -0,0 +1,6 @@
1
+ /** Сколько записей каталога host просматривает за вызов worktree.files (D-367). */
2
+ export declare const WORKTREE_FILES_SCAN_MAX = 10000;
3
+ /** Сколько битых симлинков подряд host проходит, выясняя, не ведёт ли путь за worktree: столько же, сколько ядро до ELOOP. */
4
+ export declare const WORKTREE_FILES_LINK_HOPS = 40;
5
+ /** Служебный каталог git: не показывается ни на каком уровне, без учёта регистра. */
6
+ export declare const GIT_ENTRY_NAME = ".git";
@@ -0,0 +1,14 @@
1
+ import type { DirListing, DirListLimits, DirRefusalReason } from './files.types.ts';
2
+ /** Отказ чтения каталога; controller переводит причину в ошибку контракта по матрице D-367. */
3
+ export declare class DirRefusal extends Error {
4
+ readonly reason: DirRefusalReason;
5
+ constructor(reason: DirRefusalReason, message: string);
6
+ }
7
+ /**
8
+ * Записи каталога `path` worktree (без `path` — корня). Каталог worktree проверяется как перед очисткой
9
+ * (checkManagedPath), realpath каталога обязан лежать в realpath worktree и не внутри `.git`. Записи `.git` без
10
+ * учёта регистра пропускаются; вид берётся из dirent (семантика lstat), поэтому симлинк остаётся листом.
11
+ * Просматривается не больше `scanMax` записей; имя не в UTF-8 или запись, не прошедшая схему, пропускается с
12
+ * `truncated`, как и всё сверх `maxEntries` после сортировки.
13
+ */
14
+ export declare function listWorktreeDir(dataDir: string, worktreePath: string, path: string | undefined, limits?: DirListLimits): Promise<DirListing>;
@@ -0,0 +1,13 @@
1
+ import type { WorktreeFilesReason } from '../contract/contract.const.ts';
2
+ import type { WorktreeEntry } from '../contract/contract.types.ts';
3
+ /** Пределы одного вызова: записей в ответе и просмотренных записей каталога. */
4
+ export type DirListLimits = {
5
+ maxEntries: number;
6
+ scanMax: number;
7
+ };
8
+ /** Причина отказа чтения каталога: `not_found` — каталога нет, остальное — `data.reason` контракта. */
9
+ export type DirRefusalReason = 'not_found' | WorktreeFilesReason;
10
+ export type DirListing = {
11
+ entries: WorktreeEntry[];
12
+ truncated: boolean;
13
+ };
@@ -0,0 +1,3 @@
1
+ export * from './files.const.ts';
2
+ export * from './files.types.ts';
3
+ export * from './files.lib.ts';
@@ -0,0 +1,163 @@
1
+ import {
2
+ PathEscapeError,
3
+ checkManagedPath,
4
+ isInside
5
+ } from "../chunk-C6PHNSDC.js";
6
+ import {
7
+ WORKTREE_FILES_MAX,
8
+ worktreeEntrySchema
9
+ } from "../chunk-4ZGRXIVA.js";
10
+
11
+ // src/files/files.const.ts
12
+ var WORKTREE_FILES_SCAN_MAX = 1e4;
13
+ var WORKTREE_FILES_LINK_HOPS = 40;
14
+ var GIT_ENTRY_NAME = ".git";
15
+
16
+ // src/files/files.lib.ts
17
+ import { lstat, opendir, readlink, realpath } from "fs/promises";
18
+ import { dirname, join, relative, resolve, sep } from "path";
19
+ var DirRefusal = class extends Error {
20
+ reason;
21
+ constructor(reason, message) {
22
+ super(message);
23
+ this.name = "DirRefusal";
24
+ this.reason = reason;
25
+ }
26
+ };
27
+ async function listWorktreeDir(dataDir, worktreePath, path, limits = { maxEntries: WORKTREE_FILES_MAX, scanMax: WORKTREE_FILES_SCAN_MAX }) {
28
+ const root = await worktreeRoot(dataDir, worktreePath);
29
+ const target = path === void 0 ? root : resolve(root, path);
30
+ if (target !== root && !isInside(root, target)) throw new DirRefusal("outside_worktree", `\u043F\u0443\u0442\u044C '${String(path)}' \u0432\u044B\u0445\u043E\u0434\u0438\u0442 \u0437\u0430 worktree`);
31
+ const real = target === root ? root : await realTarget(root, target, path);
32
+ if (real !== root && !isInside(root, real)) throw new DirRefusal("outside_worktree", `realpath \u043F\u0443\u0442\u0438 '${String(path)}' \u0432\u044B\u0445\u043E\u0434\u0438\u0442 \u0437\u0430 worktree`);
33
+ if (relative(root, real).split(sep).some(isGitName)) throw new DirRefusal("excluded", `\u043F\u0443\u0442\u044C '${String(path)}' \u0432\u0435\u0434\u0451\u0442 \u0432 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 .git`);
34
+ const info = await fsStep(() => lstat(real));
35
+ if (!info.isDirectory()) throw new DirRefusal("not_directory", `\u043F\u0443\u0442\u044C '${String(path)}' \u2014 \u043D\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433`);
36
+ let truncated = false;
37
+ let scanned = 0;
38
+ const found = [];
39
+ const decoder = new TextDecoder("utf-8", { fatal: true });
40
+ const dir = await fsStep(() => opendir(real, { encoding: "buffer" }));
41
+ for await (const dirent of dir) {
42
+ if (scanned >= limits.scanMax) {
43
+ truncated = true;
44
+ break;
45
+ }
46
+ scanned += 1;
47
+ const name = decodeName(dirent.name, decoder);
48
+ if (name === void 0) {
49
+ truncated = true;
50
+ continue;
51
+ }
52
+ if (isGitName(name)) continue;
53
+ const entry = worktreeEntrySchema.safeParse({ name, path: path === void 0 ? name : `${path}/${name}`, kind: kindOf(dirent) });
54
+ if (!entry.success) {
55
+ truncated = true;
56
+ continue;
57
+ }
58
+ found.push(entry.data);
59
+ }
60
+ found.sort(byKindAndName);
61
+ if (found.length > limits.maxEntries) truncated = true;
62
+ const entries = await Promise.all(found.slice(0, limits.maxEntries).map((entry) => entry.kind === "file" ? withSize(real, entry) : Promise.resolve(entry)));
63
+ return { entries, truncated };
64
+ }
65
+ async function worktreeRoot(dataDir, worktreePath) {
66
+ let present;
67
+ try {
68
+ present = checkManagedPath(dataDir, worktreePath);
69
+ } catch (error) {
70
+ if (error instanceof PathEscapeError) throw new DirRefusal("unsafe_path", error.message);
71
+ throw error;
72
+ }
73
+ if (!present) throw new DirRefusal("missing_on_disk", `\u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 worktree ${worktreePath} \u043D\u0435\u0442 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435`);
74
+ try {
75
+ return await realpath(worktreePath);
76
+ } catch (error) {
77
+ if (fsCode(error) === "ENOENT") throw new DirRefusal("missing_on_disk", `\u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 worktree ${worktreePath} \u043D\u0435\u0442 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435`);
78
+ throw error;
79
+ }
80
+ }
81
+ async function realTarget(root, target, path) {
82
+ try {
83
+ return await realpath(target);
84
+ } catch (error) {
85
+ const refusal = fsRefusal(error);
86
+ if (refusal === void 0) throw error;
87
+ if (await leadsOutside(root, target)) throw new DirRefusal("outside_worktree", `\u043F\u0443\u0442\u044C '${String(path)}' \u0432\u044B\u0445\u043E\u0434\u0438\u0442 \u0437\u0430 worktree`);
88
+ throw refusal;
89
+ }
90
+ }
91
+ async function leadsOutside(root, start) {
92
+ let current = start;
93
+ for (let hops = 0; current !== root; ) {
94
+ if (!isInside(root, current)) return true;
95
+ const real = await realpath(current).catch(() => void 0);
96
+ if (real !== void 0) return real !== root && !isInside(root, real);
97
+ const link = hops < WORKTREE_FILES_LINK_HOPS ? await readlink(current).catch(() => void 0) : void 0;
98
+ if (link === void 0) {
99
+ current = dirname(current);
100
+ continue;
101
+ }
102
+ hops += 1;
103
+ const base = await realpath(dirname(current)).catch(() => dirname(current));
104
+ current = resolve(base, link);
105
+ }
106
+ return false;
107
+ }
108
+ async function fsStep(step) {
109
+ try {
110
+ return await step();
111
+ } catch (error) {
112
+ throw fsRefusal(error) ?? error;
113
+ }
114
+ }
115
+ function fsRefusal(error) {
116
+ const code = fsCode(error);
117
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP" || code === "ENAMETOOLONG") return new DirRefusal("not_found", messageOf(error));
118
+ if (code === "EACCES" || code === "EPERM") return new DirRefusal("unreadable", messageOf(error));
119
+ return void 0;
120
+ }
121
+ function fsCode(error) {
122
+ return error?.code;
123
+ }
124
+ function messageOf(error) {
125
+ return error instanceof Error ? error.message : String(error);
126
+ }
127
+ function isGitName(name) {
128
+ return name.toLowerCase() === GIT_ENTRY_NAME;
129
+ }
130
+ function decodeName(raw, decoder) {
131
+ if (typeof raw === "string") return raw;
132
+ try {
133
+ return decoder.decode(raw);
134
+ } catch {
135
+ return void 0;
136
+ }
137
+ }
138
+ function kindOf(dirent) {
139
+ if (dirent.isDirectory()) return "dir";
140
+ if (dirent.isFile()) return "file";
141
+ if (dirent.isSymbolicLink()) return "symlink";
142
+ return "other";
143
+ }
144
+ function byKindAndName(a, b) {
145
+ const rank = Number(a.kind !== "dir") - Number(b.kind !== "dir");
146
+ if (rank !== 0) return rank;
147
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
148
+ }
149
+ async function withSize(dir, entry) {
150
+ try {
151
+ return { ...entry, size: (await lstat(join(dir, entry.name))).size };
152
+ } catch {
153
+ return entry;
154
+ }
155
+ }
156
+ export {
157
+ DirRefusal,
158
+ GIT_ENTRY_NAME,
159
+ WORKTREE_FILES_LINK_HOPS,
160
+ WORKTREE_FILES_SCAN_MAX,
161
+ listWorktreeDir
162
+ };
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/files/files.const.ts","../../src/files/files.lib.ts"],"sourcesContent":["// Происхождение — onreel @ b73407be076a5df73931d1340f70910ca13c908d,\n// src/host/worktree/files.const.ts — дословная копия.\n\n// Пределы и исключения списка файлов worktree (#64, D-367).\n\n/** Сколько записей каталога host просматривает за вызов worktree.files (D-367). */\nexport const WORKTREE_FILES_SCAN_MAX = 10_000;\n\n/** Сколько битых симлинков подряд host проходит, выясняя, не ведёт ли путь за worktree: столько же, сколько ядро до ELOOP. */\nexport const WORKTREE_FILES_LINK_HOPS = 40;\n\n/** Служебный каталог git: не показывается ни на каком уровне, без учёта регистра. */\nexport const GIT_ENTRY_NAME = '.git';\n","// Происхождение — onreel @ b73407be076a5df73931d1340f70910ca13c908d,\n// src/host/worktree/files.lib.ts — дословная копия; импорт констант, схем и типов контракта\n// перенацелен на срез src/contract этой библиотеки (часть B выделения).\n\nimport type { OpenDirOptions } from 'node:fs';\nimport { lstat, opendir, readlink, realpath } from 'node:fs/promises';\nimport { dirname, join, relative, resolve, sep } from 'node:path';\nimport { WORKTREE_FILES_MAX } from '../contract/contract.const.ts';\nimport { worktreeEntrySchema } from '../contract/contract.schema.ts';\nimport type { WorktreeEntry, WorktreeEntryKind } from '../contract/contract.types.ts';\nimport { GIT_ENTRY_NAME, WORKTREE_FILES_LINK_HOPS, WORKTREE_FILES_SCAN_MAX } from './files.const.ts';\nimport type { DirListing, DirListLimits, DirRefusalReason } from './files.types.ts';\nimport { checkManagedPath, isInside, PathEscapeError } from '../git/paths.lib.ts';\n\n// Список файлов worktree (#64, D-367): записи одного каталога без `.git` в пределах realpath worktree; симлинки не разыменовываются, глубже одного каталога host не идёт.\n\n/** Отказ чтения каталога; controller переводит причину в ошибку контракта по матрице D-367. */\nexport class DirRefusal extends Error {\n readonly reason: DirRefusalReason;\n\n constructor(reason: DirRefusalReason, message: string) {\n super(message);\n this.name = 'DirRefusal';\n this.reason = reason;\n }\n}\n\n/**\n * Записи каталога `path` worktree (без `path` — корня). Каталог worktree проверяется как перед очисткой\n * (checkManagedPath), realpath каталога обязан лежать в realpath worktree и не внутри `.git`. Записи `.git` без\n * учёта регистра пропускаются; вид берётся из dirent (семантика lstat), поэтому симлинк остаётся листом.\n * Просматривается не больше `scanMax` записей; имя не в UTF-8 или запись, не прошедшая схему, пропускается с\n * `truncated`, как и всё сверх `maxEntries` после сортировки.\n */\nexport async function listWorktreeDir(\n dataDir: string,\n worktreePath: string,\n path: string | undefined,\n limits: DirListLimits = { maxEntries: WORKTREE_FILES_MAX, scanMax: WORKTREE_FILES_SCAN_MAX },\n): Promise<DirListing> {\n const root = await worktreeRoot(dataDir, worktreePath);\n const target = path === undefined ? root : resolve(root, path);\n if (target !== root && !isInside(root, target)) throw new DirRefusal('outside_worktree', `путь '${String(path)}' выходит за worktree`);\n const real = target === root ? root : await realTarget(root, target, path);\n if (real !== root && !isInside(root, real)) throw new DirRefusal('outside_worktree', `realpath пути '${String(path)}' выходит за worktree`);\n if (relative(root, real).split(sep).some(isGitName)) throw new DirRefusal('excluded', `путь '${String(path)}' ведёт в каталог .git`);\n // real уже без симлинков: lstat видит сам каталог, а подменённый между шагами симлинк — не каталог.\n const info = await fsStep(() => lstat(real));\n if (!info.isDirectory()) throw new DirRefusal('not_directory', `путь '${String(path)}' — не каталог`);\n\n let truncated = false;\n let scanned = 0;\n const found: WorktreeEntry[] = [];\n const decoder = new TextDecoder('utf-8', { fatal: true });\n // Имена — байтами: имя не в UTF-8 иначе пришло бы с U+FFFD и вело бы на другой путь. Типы @types/node\n // не знают encoding 'buffer' у opendir, а Node его поддерживает.\n const dir = await fsStep(() => opendir(real, { encoding: 'buffer' } as unknown as OpenDirOptions));\n for await (const dirent of dir) {\n if (scanned >= limits.scanMax) {\n truncated = true;\n break;\n }\n scanned += 1;\n const name = decodeName(dirent.name as string | Buffer, decoder);\n if (name === undefined) {\n truncated = true;\n continue;\n }\n if (isGitName(name)) continue;\n const entry = worktreeEntrySchema.safeParse({ name, path: path === undefined ? name : `${path}/${name}`, kind: kindOf(dirent) });\n if (!entry.success) {\n truncated = true;\n continue;\n }\n found.push(entry.data);\n }\n\n found.sort(byKindAndName);\n if (found.length > limits.maxEntries) truncated = true;\n const entries = await Promise.all(found.slice(0, limits.maxEntries).map((entry) => (entry.kind === 'file' ? withSize(real, entry) : Promise.resolve(entry))));\n return { entries, truncated };\n}\n\n/** realpath каталога worktree после проверки, что он управляется host: нет на диске или подменён — отказ. */\nasync function worktreeRoot(dataDir: string, worktreePath: string): Promise<string> {\n let present: boolean;\n try {\n present = checkManagedPath(dataDir, worktreePath);\n } catch (error) {\n if (error instanceof PathEscapeError) throw new DirRefusal('unsafe_path', error.message);\n throw error;\n }\n if (!present) throw new DirRefusal('missing_on_disk', `каталога worktree ${worktreePath} нет на диске`);\n try {\n return await realpath(worktreePath);\n } catch (error) {\n if (fsCode(error) === 'ENOENT') throw new DirRefusal('missing_on_disk', `каталога worktree ${worktreePath} нет на диске`);\n throw error;\n }\n}\n\n/**\n * realpath каталога запроса. realpath не получился (пути нет, нет прав, битый симлинк), но путь ведёт за\n * worktree — outside_worktree, а не not_found или unreadable: путь за симлинком наружу отказывает одинаково,\n * что бы ни лежало снаружи.\n */\nasync function realTarget(root: string, target: string, path: string | undefined): Promise<string> {\n try {\n return await realpath(target);\n } catch (error) {\n const refusal = fsRefusal(error);\n if (refusal === undefined) throw error;\n if (await leadsOutside(root, target)) throw new DirRefusal('outside_worktree', `путь '${String(path)}' выходит за worktree`);\n throw refusal;\n }\n}\n\n/**\n * Ведёт ли путь без realpath за worktree. От пути к корню: ближайший предок с realpath решает сам; битый\n * симлинк по дороге заменяется своей целью (`dangling -> <снаружи>/нет`), и обход идёт уже от неё.\n */\nasync function leadsOutside(root: string, start: string): Promise<boolean> {\n let current = start;\n for (let hops = 0; current !== root; ) {\n if (!isInside(root, current)) return true;\n const real = await realpath(current).catch(() => undefined);\n if (real !== undefined) return real !== root && !isInside(root, real);\n const link = hops < WORKTREE_FILES_LINK_HOPS ? await readlink(current).catch(() => undefined) : undefined;\n if (link === undefined) {\n current = dirname(current);\n continue;\n }\n hops += 1;\n const base = await realpath(dirname(current)).catch(() => dirname(current));\n current = resolve(base, link);\n }\n return false;\n}\n\n/** Шаг файловой системы над каталогом запроса; отказ — через fsRefusal, прочее — дальше. */\nasync function fsStep<T>(step: () => Promise<T>): Promise<T> {\n try {\n return await step();\n } catch (error) {\n throw fsRefusal(error) ?? error;\n }\n}\n\n/** Ошибка файловой системы как отказ: нет пути — not_found, нет прав — unreadable; прочее — undefined (internal). */\nfunction fsRefusal(error: unknown): DirRefusal | undefined {\n const code = fsCode(error);\n // Петля симлинков и слишком длинная компонента тоже значат, что каталога по этому пути нет.\n if (code === 'ENOENT' || code === 'ENOTDIR' || code === 'ELOOP' || code === 'ENAMETOOLONG') return new DirRefusal('not_found', messageOf(error));\n if (code === 'EACCES' || code === 'EPERM') return new DirRefusal('unreadable', messageOf(error));\n return undefined;\n}\n\nfunction fsCode(error: unknown): unknown {\n return (error as { code?: unknown } | null)?.code;\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction isGitName(name: string): boolean {\n return name.toLowerCase() === GIT_ENTRY_NAME;\n}\n\nfunction decodeName(raw: string | Buffer, decoder: TextDecoder): string | undefined {\n if (typeof raw === 'string') return raw;\n try {\n return decoder.decode(raw);\n } catch {\n return undefined;\n }\n}\n\nfunction kindOf(dirent: { isDirectory(): boolean; isFile(): boolean; isSymbolicLink(): boolean }): WorktreeEntryKind {\n if (dirent.isDirectory()) return 'dir';\n if (dirent.isFile()) return 'file';\n if (dirent.isSymbolicLink()) return 'symlink';\n return 'other';\n}\n\n/** Сначала каталоги, затем остальные; внутри — по имени в порядке кодовых единиц. */\nfunction byKindAndName(a: WorktreeEntry, b: WorktreeEntry): number {\n const rank = Number(a.kind !== 'dir') - Number(b.kind !== 'dir');\n if (rank !== 0) return rank;\n return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n}\n\n/** Размер файла по lstat; не прочитался (файл удалили между шагами) — запись без размера. */\nasync function withSize(dir: string, entry: WorktreeEntry): Promise<WorktreeEntry> {\n try {\n return { ...entry, size: (await lstat(join(dir, entry.name))).size };\n } catch {\n return entry;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,4 @@
1
+ /** Таймаут обычной команды: локальный репозиторий отвечает за секунды, зависание — сбой. */
2
+ export declare const GIT_TIMEOUT_MS = 120000;
3
+ /** `git worktree add` выкладывает всё дерево: на больших репозиториях это минуты. */
4
+ export declare const GIT_CHECKOUT_TIMEOUT_MS = 600000;
@@ -0,0 +1,90 @@
1
+ import type { WorktreeStatusEntry } from '../contract/contract.types.ts';
2
+ import type { GitRunOptions, GitRun, CommitOutcome, RebaseOutcome, ChangedFile, FileSide } from './git.types.ts';
3
+ /**
4
+ * Git-команда не выполнилась: код, вывод и признак таймаута — для ошибки `git_failed` и журнала.
5
+ * Аргументы и вывод маскируются здесь, до сообщения (D-272): stderr git и хуков несёт
6
+ * `https://user:token@host`, а текст ошибки уходит в ответ, receipt, `worktrees.error` и события.
7
+ * Поля `args` и `stderr` хранят уже замаскированные значения; `stdout` (до 16 МиБ, его никто не
8
+ * читает) маскируется при первом чтении, чтобы не держать event loop на каждом сбое git.
9
+ */
10
+ export declare class GitError extends Error {
11
+ #private;
12
+ readonly args: readonly string[];
13
+ readonly exitCode: number | null;
14
+ readonly stderr: string;
15
+ readonly timedOut: boolean;
16
+ constructor(args: readonly string[], exitCode: number | null, stderr: string, stdout?: string, timedOut?: boolean);
17
+ get stdout(): string;
18
+ }
19
+ /** Остановка host: очередь отклоняется, запущенные git убиваются (задания пула отменяемы, §3.1). */
20
+ export declare function closeGitPool(): void;
21
+ /** Пул закрыт остановкой host: сбой git тогда не повод писать failed — доделает старт. */
22
+ export declare function isGitPoolClosed(): boolean;
23
+ /** Только для тестов: снова открыть пул после closeGitPool. */
24
+ export declare function reopenGitPool(): void;
25
+ /** Запуск git в cwd: ненулевой код вне okCodes, таймаут или переполнение вывода — GitError. */
26
+ export declare function runGit(args: readonly string[], cwd: string, options?: GitRunOptions): Promise<GitRun>;
27
+ /** Корень рабочего дерева репозитория, в котором лежит path; не репозиторий или bare — GitError. */
28
+ export declare function repoToplevel(path: string): Promise<string>;
29
+ /** Ref резолвится в коммит: sha; нет такого ref — undefined. Ref идёт после `--end-of-options`. */
30
+ export declare function resolveCommit(cwd: string, ref: string): Promise<string | undefined>;
31
+ /** Текущая ветка репозитория; HEAD отсоединён — undefined. */
32
+ export declare function currentBranch(repoPath: string): Promise<string | undefined>;
33
+ /** Имя проходит `git check-ref-format --branch`. */
34
+ export declare function isValidBranchName(repoPath: string, branch: string): Promise<boolean>;
35
+ /** Sha локальной ветки; ветки нет — undefined. */
36
+ export declare function branchSha(repoPath: string, branch: string): Promise<string | undefined>;
37
+ /** `git worktree add --no-track -b <branch> <path> <sha>` (§6.9): ветка без upstream. */
38
+ export declare function addWorktree(repoPath: string, worktreePath: string, branch: string, baseSha: string): Promise<void>;
39
+ /** Снимает с учёта репозитория worktree, чьих каталогов больше нет (после переноса в trash). */
40
+ export declare function pruneWorktrees(repoPath: string): Promise<void>;
41
+ /** Удаляет локальную ветку. */
42
+ export declare function deleteBranch(repoPath: string, branch: string): Promise<void>;
43
+ /** HEAD worktree: sha (40 или 64 hex). */
44
+ export declare function headOf(worktreePath: string): Promise<string>;
45
+ /**
46
+ * `git status --porcelain=v1 -z` → записи ревью. С -z имена приходят байт в байт, без
47
+ * кавычек и экранирования; у переименования и копии следом идёт запись исходного пути.
48
+ */
49
+ export declare function statusEntries(worktreePath: string, untracked?: 'normal' | 'no'): Promise<WorktreeStatusEntry[]>;
50
+ /** Есть незакоммиченные изменения (включая неотслеживаемые файлы вне .gitignore). */
51
+ export declare function isDirty(worktreePath: string): Promise<boolean>;
52
+ /** `git diff <sha>`: индекс и рабочий каталог против коммита. */
53
+ export declare function diffAgainst(worktreePath: string, sha: string): Promise<string>;
54
+ /**
55
+ * Коммитит изменения: `git add -A` и `git commit`, или только переданные пути — `git add` и
56
+ * `git commit --only` по ним, буквально (pathspec-магия вида `:(top)` от клиента не работает),
57
+ * так что прежде добавленное в индекс в коммит не попадает. Нечего коммитить — committed: false.
58
+ */
59
+ export declare function commitChanges(worktreePath: string, message: string, paths?: readonly string[]): Promise<CommitOutcome>;
60
+ /** Незавершённая операция git в worktree: rebase, merge, cherry-pick или revert; нет — undefined. */
61
+ export declare function operationInProgress(worktreePath: string): Promise<string | undefined>;
62
+ /**
63
+ * Rebase worktree на коммит. Чужой незавершённый rebase или merge и изменения отслеживаемых
64
+ * файлов — отказ без действий: работу агента вызов не трогает. Конфликт — список путей и
65
+ * `git rebase --abort` только для rebase, начатого этим вызовом; HEAD остаётся прежним.
66
+ */
67
+ export declare function rebaseOnto(worktreePath: string, baseSha: string): Promise<RebaseOutcome>;
68
+ /**
69
+ * Файлы отслеживаемого дерева, изменённые против коммита: статус из `--name-status -z -M`,
70
+ * счётчики строк и признак бинарного (`-\t-`) из `--numstat -z -M`. Пути — байт в байт (-z);
71
+ * у переименования и копии в записи два пути (исходный, затем новый). Неотслеживаемые файлы
72
+ * здесь не видны: их отдаёт untrackedFiles.
73
+ */
74
+ export declare function changedFiles(worktreePath: string, sha: string): Promise<ChangedFile[]>;
75
+ /** Неотслеживаемые файлы вне .gitignore: `ls-files --others --exclude-standard -z`. */
76
+ export declare function untrackedFiles(worktreePath: string): Promise<string[]>;
77
+ /**
78
+ * Содержимое файла в коммите (`git cat-file`); файла там нет (или это не блоб) — undefined.
79
+ * Больше maxBytes — усечённая сторона без текста: объект целиком не читается, поэтому бинарность
80
+ * такого файла неизвестна (binary: false).
81
+ */
82
+ export declare function showBlob(worktreePath: string, sha: string, path: string, maxBytes: number): Promise<FileSide | undefined>;
83
+ /**
84
+ * Рабочее состояние файла для ревью. Корень и родитель файла резолвятся realpath и обязаны
85
+ * лежать внутри worktree (иначе PathEscapeError). Симлинк не разыменовывается: показывается
86
+ * текст ссылки, как у git, но ссылка, чья цель выходит за worktree, — PathEscapeError.
87
+ * Нет файла, это каталог или не обычный файл (fifo, сокет) — undefined; бинарный и больший
88
+ * maxBytes файлы текста не несут.
89
+ */
90
+ export declare function readWorktreeFile(worktreePath: string, path: string, maxBytes: number): Promise<FileSide | undefined>;