@smwb/onreel-git-mcp 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -204,7 +204,13 @@ await withWorktreeLock('wt-9', async () => {
204
204
  все переходы (`markWorktreeReady`, `markWorktreeFailed`, `requestWorktreeCleanup`, `completeWorktreeCleanup`);
205
205
  вложенность и асинхронные обратные вызовы не поддерживаются. **Эталон реализации — `test/helpers/memory-store.lib.ts`
206
206
  в тестовом дереве пакета** (D-003): он же образец семантики для потребителей, включая копию-на-коммит в
207
- `inTransaction` и отказ броска как откат.
207
+ `inTransaction` и отказ броска как откат. Свои переходы библиотека пишет с `actor: 'host'` — процесс, который её
208
+ держит (D-012); реализация порта отображает это значение в лексику потребителя.
209
+
210
+ Замки worktree — одна таблица на процесс. Субпуть, которому нужны замки, импортирует модуль жизненного цикла
211
+ напрямую: модуль двух и более точек входа tsup выносит в общий chunk, поэтому определение таблицы в собранном
212
+ пакете остаётся одно (D-013). Инвариант держит `test/worktree/locks-bundle.test.ts` — он собирает пакет
213
+ конфигурацией проекта во временный каталог и требует ровно одного определения.
208
214
 
209
215
  ## tracker
210
216
 
@@ -255,7 +261,13 @@ try {
255
261
  `GITHUB_PRIORITY_LABELS`, `GITLAB_PRIORITY_LABELS`, …), чтобы потребитель видел, во что именно преводится его
256
262
  статус. Свои трекеры добавляют в реестр явным `registerTracker(definition)`; повторный `kind` — исключение, а не
257
263
  молчаливая замена. Типы порта `LocalIssueStore` (и его строк) реэкспортированы из `…/tracker` — реализацию, как и
258
- для `WorktreeStore`, пишет потребитель; эталон в памяти — `test/helpers/tracker-fixtures.lib.ts`.
264
+ для `WorktreeStore`, пишет потребитель; эталон в памяти — `test/helpers/tracker-fixtures.lib.ts`. Порт требует:
265
+ комментарий — только к существующей задаче, иначе отказ с кодом `unknown_issue` (сиротских комментариев хранилище
266
+ не держит, D-011); `limit` страницы — целое от 1 до 500, по умолчанию 100, значение вне диапазона прижимается
267
+ к границам; снятый исполнитель (`assignee: null`) убирает ключ из строки задачи, а не оставляет его со значением
268
+ `undefined`. Нецелый `limit` до хранилища не доходит: адаптер отказывает `invalid_request` ещё в проверке запроса,
269
+ а чем на него отвечает сама реализация порта, не предписано — эталон в памяти отвечает `invalid_request`, ledger
270
+ падает ошибкой SQLite.
259
271
 
260
272
  Таймаут запроса к трекеру — `TRACKER_TIMEOUT_MS` (15 с), тело ответа ошибки обрезают до
261
273
  `TRACKER_ERROR_BODY_MAX` (300 символов) и маскируют; 404 с `missingIsIssue` — `unknown_issue`, а не «неверный
@@ -811,8 +823,10 @@ console.log(manifest.name, manifest.version);
811
823
  их не нужно править в потребителе.
812
824
  - `sideEffects: false`, один рантайм-зависимый пакет — `zod`.
813
825
  - Реестр трекеров, пул git, замки worktree и redactor — модульная глобальна на процесс: несколько независимых
814
- «хостов» в одном процессе делят их. Запросы к внешним API идут `fetch` из контекста; узлы без TLS-цепей
815
- внешнего трекера — задача вызывающего.
826
+ «хостов» в одном процессе делят их. Субпути пакета делят такое состояние, потому что модуль двух и более точек
827
+ входа tsup выносит в общий chunk; свою копию дал бы только бандл без splitting — инвариант одного определения
828
+ в собранном пакете держит проверка (D-013). Две независимо установленные копии пакета состояние не делят.
829
+ Запросы к внешним API идут `fetch` из контекста; узлы без TLS-цепей внешнего трекера — задача вызывающего.
816
830
  - Имена `ONREEL_*` и `HOST_CONTRACT_VERSION` (`1.14`) заморожены: они держат interop с onreel.
817
831
 
818
832
  ## Как внести изменение
package/dist/index.js CHANGED
@@ -59,7 +59,7 @@ import {
59
59
  // package.json
60
60
  var package_default = {
61
61
  name: "@smwb/onreel-git-mcp",
62
- version: "0.10.0",
62
+ version: "0.10.1",
63
63
  description: "\u0411\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0430 \u043C\u043E\u0434\u0443\u043B\u0435\u0439, \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u043D\u044B\u0445 \u0438\u0437 onreel: git-\u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0435 \u043F\u0443\u0442\u0438, \u0441\u043F\u0438\u0441\u043E\u043A \u0444\u0430\u0439\u043B\u043E\u0432 worktree, \u0436\u0438\u0437\u043D\u0435\u043D\u043D\u044B\u0439 \u0446\u0438\u043A\u043B worktree \u043F\u0440\u043E\u0442\u0438\u0432 \u043F\u043E\u0440\u0442\u0430 WorktreeStore, \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u044B \u0442\u0440\u0435\u043A\u0435\u0440\u043E\u0432 GitHub/GitLab/Jira/SourceCraft/\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0430 stdio",
64
64
  keywords: [
65
65
  "git",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@smwb/onreel-git-mcp\",\n \"version\": \"0.10.0\",\n \"description\": \"Библиотека модулей, выделенных из onreel: git-операции и безопасные пути, список файлов worktree, жизненный цикл worktree против порта WorktreeStore, адаптеры трекеров GitHub/GitLab/Jira/SourceCraft/локального и MCP-сервер на stdio\",\n \"keywords\": [\n \"git\",\n \"worktree\",\n \"mcp\",\n \"issue-tracker\",\n \"github\",\n \"gitlab\",\n \"jira\",\n \"sourcecraft\",\n \"onreel\",\n \"typescript\"\n ],\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=24\"\n },\n \"files\": [\n \"dist\"\n ],\n \"sideEffects\": false,\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://git.sourcecraft.dev/elfenmail/onreel-git-mcp.git\"\n },\n \"homepage\": \"https://git.sourcecraft.dev/elfenmail/onreel-git-mcp\",\n \"license\": \"MIT\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"default\": \"./dist/index.js\"\n },\n \"./git\": {\n \"types\": \"./dist/git/index.d.ts\",\n \"import\": \"./dist/git/index.js\",\n \"default\": \"./dist/git/index.js\"\n },\n \"./files\": {\n \"types\": \"./dist/files/index.d.ts\",\n \"import\": \"./dist/files/index.js\",\n \"default\": \"./dist/files/index.js\"\n },\n \"./worktree\": {\n \"types\": \"./dist/worktree/index.d.ts\",\n \"import\": \"./dist/worktree/index.js\",\n \"default\": \"./dist/worktree/index.js\"\n },\n \"./tracker\": {\n \"types\": \"./dist/tracker/index.d.ts\",\n \"import\": \"./dist/tracker/index.js\",\n \"default\": \"./dist/tracker/index.js\"\n },\n \"./mcp\": {\n \"types\": \"./dist/mcp/index.d.ts\",\n \"import\": \"./dist/mcp/index.js\",\n \"default\": \"./dist/mcp/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"build\": \"tsup && tsc -p tsconfig.build.json\",\n \"typecheck\": \"tsc -p tsconfig.json && tsc -p tsconfig.examples.json\",\n \"lint\": \"oxlint --type-aware\",\n \"test\": \"node --test \\\"test/**/*.test.ts\\\"\",\n \"changeset\": \"changeset\",\n \"version\": \"changeset version\",\n \"release\": \"npm run build && changeset publish\"\n },\n \"dependencies\": {\n \"zod\": \"^4.6.5\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"2.29.2\",\n \"@types/node\": \"24.19.1\",\n \"oxlint\": \"1.87.0\",\n \"oxlint-tsgolint\": \"7.0.2003\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"7.0.2\"\n }\n}\n","// Корневой подпуть пакета @smwb/onreel-git-mcp: срез контракта (константы, схемы, типы, чистые\n// функции) и версия пакета. Всё без побочных эффектов при импорте (R-001). Швы ядра\n// (SecretSink, WorktreeStore, LocalIssueStore) и модули git/files/worktree/tracker/mcp\n// экспортируют свои подпути — см. exports в package.json и README.\nimport pkg from '../package.json' with { type: 'json' };\n\nexport * from './contract/contract.const.ts';\nexport * from './contract/contract.schema.ts';\nexport * from './contract/contract.types.ts';\nexport * from './contract/contract.lib.ts';\n\n// Версия пакета — из version в package.json: статический импорт JSON, его значение tsup подставляет\n// в бандл на сборке, поэтому VERSION не расходится с релизом changesets (тот правит только version).\nexport const VERSION: string = pkg.version;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,MAAQ;AAAA,EACR,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,OAAS;AAAA,IACP;AAAA,EACF;AAAA,EACA,aAAe;AAAA,EACf,eAAiB;AAAA,IACf,QAAU;AAAA,IACV,UAAY;AAAA,EACd;AAAA,EACA,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,UAAY;AAAA,EACZ,SAAW;AAAA,EACX,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA,MACT,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,cAAc;AAAA,MACZ,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,aAAa;AAAA,MACX,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,kBAAkB;AAAA,EACpB;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,WAAa;AAAA,IACb,SAAW;AAAA,IACX,SAAW;AAAA,EACb;AAAA,EACA,cAAgB;AAAA,IACd,KAAO;AAAA,EACT;AAAA,EACA,iBAAmB;AAAA,IACjB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,QAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,MAAQ;AAAA,IACR,YAAc;AAAA,EAChB;AACF;;;AC1EO,IAAM,UAAkB,gBAAI;","names":[]}
1
+ {"version":3,"sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@smwb/onreel-git-mcp\",\n \"version\": \"0.10.1\",\n \"description\": \"Библиотека модулей, выделенных из onreel: git-операции и безопасные пути, список файлов worktree, жизненный цикл worktree против порта WorktreeStore, адаптеры трекеров GitHub/GitLab/Jira/SourceCraft/локального и MCP-сервер на stdio\",\n \"keywords\": [\n \"git\",\n \"worktree\",\n \"mcp\",\n \"issue-tracker\",\n \"github\",\n \"gitlab\",\n \"jira\",\n \"sourcecraft\",\n \"onreel\",\n \"typescript\"\n ],\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=24\"\n },\n \"files\": [\n \"dist\"\n ],\n \"sideEffects\": false,\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://git.sourcecraft.dev/elfenmail/onreel-git-mcp.git\"\n },\n \"homepage\": \"https://git.sourcecraft.dev/elfenmail/onreel-git-mcp\",\n \"license\": \"MIT\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"default\": \"./dist/index.js\"\n },\n \"./git\": {\n \"types\": \"./dist/git/index.d.ts\",\n \"import\": \"./dist/git/index.js\",\n \"default\": \"./dist/git/index.js\"\n },\n \"./files\": {\n \"types\": \"./dist/files/index.d.ts\",\n \"import\": \"./dist/files/index.js\",\n \"default\": \"./dist/files/index.js\"\n },\n \"./worktree\": {\n \"types\": \"./dist/worktree/index.d.ts\",\n \"import\": \"./dist/worktree/index.js\",\n \"default\": \"./dist/worktree/index.js\"\n },\n \"./tracker\": {\n \"types\": \"./dist/tracker/index.d.ts\",\n \"import\": \"./dist/tracker/index.js\",\n \"default\": \"./dist/tracker/index.js\"\n },\n \"./mcp\": {\n \"types\": \"./dist/mcp/index.d.ts\",\n \"import\": \"./dist/mcp/index.js\",\n \"default\": \"./dist/mcp/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"build\": \"tsup && tsc -p tsconfig.build.json\",\n \"typecheck\": \"tsc -p tsconfig.json && tsc -p tsconfig.examples.json\",\n \"lint\": \"oxlint --type-aware\",\n \"test\": \"node --test \\\"test/**/*.test.ts\\\"\",\n \"changeset\": \"changeset\",\n \"version\": \"changeset version\",\n \"release\": \"npm run build && changeset publish\"\n },\n \"dependencies\": {\n \"zod\": \"^4.6.5\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"2.29.2\",\n \"@types/node\": \"24.19.1\",\n \"oxlint\": \"1.87.0\",\n \"oxlint-tsgolint\": \"7.0.2003\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"7.0.2\"\n }\n}\n","// Корневой подпуть пакета @smwb/onreel-git-mcp: срез контракта (константы, схемы, типы, чистые\n// функции) и версия пакета. Всё без побочных эффектов при импорте (R-001). Швы ядра\n// (SecretSink, WorktreeStore, LocalIssueStore) и модули git/files/worktree/tracker/mcp\n// экспортируют свои подпути — см. exports в package.json и README.\nimport pkg from '../package.json' with { type: 'json' };\n\nexport * from './contract/contract.const.ts';\nexport * from './contract/contract.schema.ts';\nexport * from './contract/contract.types.ts';\nexport * from './contract/contract.lib.ts';\n\n// Версия пакета — из version в package.json: статический импорт JSON, его значение tsup подставляет\n// в бандл на сборке, поэтому VERSION не расходится с релизом changesets (тот правит только version).\nexport const VERSION: string = pkg.version;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,MAAQ;AAAA,EACR,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,OAAS;AAAA,IACP;AAAA,EACF;AAAA,EACA,aAAe;AAAA,EACf,eAAiB;AAAA,IACf,QAAU;AAAA,IACV,UAAY;AAAA,EACd;AAAA,EACA,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,UAAY;AAAA,EACZ,SAAW;AAAA,EACX,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA,MACT,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,cAAc;AAAA,MACZ,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,aAAa;AAAA,MACX,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,kBAAkB;AAAA,EACpB;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,WAAa;AAAA,IACb,SAAW;AAAA,IACX,SAAW;AAAA,EACb;AAAA,EACA,cAAgB;AAAA,IACd,KAAO;AAAA,EACT;AAAA,EACA,iBAAmB;AAAA,IACjB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,QAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,MAAQ;AAAA,IACR,YAAc;AAAA,EAChB;AACF;;;AC1EO,IAAM,UAAkB,gBAAI;","names":[]}
@@ -1,6 +1,10 @@
1
1
  import type { IssuePriority, IssueStatusCategory, Worktree, WorktreeStatus } from '../contract/contract.types.ts';
2
2
  /** Контекст записи: кто и когда; без них реализация берёт текущее время и не пишет actor. */
3
3
  export type WorktreeWriteContext = {
4
+ /**
5
+ * Кто сделал переход. Собственные переходы библиотека пишет с actor `'host'` — процесс, который
6
+ * её держит (D-012); реализация потребителя отображает это значение в свою лексику.
7
+ */
4
8
  actor?: string | undefined;
5
9
  ts?: number | undefined;
6
10
  };
@@ -48,7 +52,13 @@ export type WorktreeStore = {
48
52
  /** Ready worktree терминальных задач без живых сессий — их чистит reconciler. */
49
53
  readyWorktreesOfSettledTasks(): Worktree[];
50
54
  };
51
- /** Пагинация списков: всё скоупится и листается страницами, без неограниченных выборок. */
55
+ /**
56
+ * Пагинация списков: всё скоупится и листается страницами, без неограниченных выборок.
57
+ * `limit` — целое от 1 до 500 (по умолчанию 100): значение вне диапазона реализация прижимает
58
+ * к границам. Нецелое значение до хранилища не доходит — адаптер отказывает `invalid_request`
59
+ * ещё в проверке запроса, поэтому код отказа реализации порта не предписан: эталон в памяти
60
+ * отвечает `invalid_request`, ledger падает ошибкой SQLite.
61
+ */
52
62
  export type PageInput = {
53
63
  limit?: number | undefined;
54
64
  cursor?: string | undefined;
@@ -102,8 +112,12 @@ export type LocalIssueStore = {
102
112
  createLocalIssue(projectId: string, input: NewLocalIssue, ts?: number): LocalIssueRow;
103
113
  getLocalIssue(projectId: string, number: number): LocalIssueRow | undefined;
104
114
  listLocalIssues(projectId: string, input?: ListLocalIssuesInput): Page<LocalIssueRow>;
105
- /** Нет задачи — undefined. */
115
+ /** Нет задачи — undefined. `assignee: null` снимает исполнителя и убирает ключ из строки. */
106
116
  updateLocalIssue(projectId: string, number: number, patch: LocalIssuePatch, ts?: number): LocalIssueRow | undefined;
117
+ /**
118
+ * Комментарий к существующей задаче: задачи нет — отказ с кодом `unknown_issue`, сиротных
119
+ * комментариев порт не хранит (D-011).
120
+ */
107
121
  addLocalComment(projectId: string, number: number, body: string, author?: string, ts?: number): LocalCommentRow;
108
122
  listLocalComments(projectId: string, number: number, input?: PageInput): Page<LocalCommentRow>;
109
123
  };
@@ -127,7 +127,7 @@ async function releaseGitState(services, worktree, ownsBranch) {
127
127
  function reconcileWorktreesAtStart(services) {
128
128
  for (const worktree of services.store.worktreesInStatus(["preparing"])) {
129
129
  services.store.inTransaction((tx) => {
130
- tx.markWorktreeFailed(worktree.id, "host \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0449\u0435\u043D \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F worktree", { actor: HOST_ACTOR });
130
+ tx.markWorktreeFailed(worktree.id, "\u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044C \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0449\u0435\u043D \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F worktree", { actor: HOST_ACTOR });
131
131
  tx.requestWorktreeCleanup(worktree.id, { actor: HOST_ACTOR, discardChanges: true, evidence: "\u043E\u0447\u0438\u0441\u0442\u043A\u0430 \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0435\u0440\u0432\u0430\u043D\u043D\u043E\u0433\u043E \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F" });
132
132
  });
133
133
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/worktree/lifecycle.const.ts","../../src/worktree/lifecycle.lib.ts"],"sourcesContent":["// Происхождение — onreel @ b73407be076a5df73931d1340f70910ca13c908d,\n// src/host/worktree/lifecycle.const.ts — дословная копия.\n\n/** Период reconciler: подбирает worktree завершённых Task и прерванные очистки (§6.8). */\nexport const WORKTREE_RECONCILE_INTERVAL_MS = 30_000;\n","// Жизненный цикл worktree библиотеки @smwb/onreel-git-mcp (часть D выделения).\n// Происхождение — onreel @ b73407be076a5df73931d1340f70910ca13c908d,\n// src/host/worktree/lifecycle.lib.ts — 9 прямых вызовов функций ledger заменены на порт\n// WorktreeStore ядра (kernel/store.types.ts, D-003): чтения — методы порта, записи — только\n// внутри единицы работы store.inTransaction (соответствие 1:1, в том числе группировка\n// вызовов в одну транзакцию). Хранилище пишет потребитель, эталонная реализация в памяти —\n// в тестах библиотеки (test/helpers/memory-store.lib.ts).\nimport { randomUUID } from 'node:crypto';\nimport { mkdirSync, readdirSync, renameSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Worktree } from '../contract/contract.types.ts';\nimport * as GitOps from '../git/git.lib.ts';\nimport { checkManagedPath, prepareWorktreeParent, trashRoot } from '../git/paths.lib.ts';\nimport { WORKTREE_RECONCILE_INTERVAL_MS } from './lifecycle.const.ts';\nimport type { WorktreeLifecycleServices, WorktreeReconciler } from './lifecycle.types.ts';\n\n// Вторая фаза создания и очистки worktree (D-050) и reconciler (§6.8, D-080). Receipt-транзакция\n// уже записала preparing или cleaning; здесь git и диск работают вне транзакции, а итог —\n// ready, cleaned или failed с причиной — пишется отдельной транзакцией. Работа над одним\n// worktree идёт под его замком: создание, очистка, коммит и rebase не перекрываются.\n\ntype Services = WorktreeLifecycleServices;\n\nconst HOST_ACTOR = 'host';\n\n// --- Замок на worktree ---\n\nconst locks = new Map<string, Promise<void>>();\n\n/** Берёт замок worktree: возвращает функцию освобождения. Ожидающие идут по очереди. */\nexport async function acquireWorktreeLock(id: string): Promise<() => void> {\n const previous = locks.get(id) ?? Promise.resolve();\n let release!: () => void;\n const current = new Promise<void>((resolve) => (release = resolve));\n const chained = previous.then(() => current);\n locks.set(id, chained);\n await previous;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n release();\n if (locks.get(id) === chained) locks.delete(id);\n };\n}\n\nexport async function withWorktreeLock<T>(id: string, fn: () => Promise<T>): Promise<T> {\n const release = await acquireWorktreeLock(id);\n try {\n return await fn();\n } finally {\n release();\n }\n}\n\n/** Над worktree сейчас идёт работа этого процесса. */\nexport function isWorktreeBusy(id: string): boolean {\n return locks.has(id);\n}\n\n// --- Создание ---\n\n/**\n * Доводит preparing до ready: каталог под корнем workspaces, `git worktree add --no-track`,\n * HEAD. Сбой — failed с причиной и сразу очистка остатков (каталог и пустая ветка).\n */\nexport async function finishPreparing(services: Services, id: string): Promise<void> {\n await withWorktreeLock(id, async () => {\n const worktree = services.store.getWorktree(id);\n if (worktree?.status !== 'preparing') return;\n let headSha: string;\n let foreignBranch = false;\n try {\n prepareWorktreeParent(services.dataDir, worktree.path);\n // Ветка появилась после проверки create: она чужая, git -b откажет, очистка её не тронет.\n foreignBranch = (await GitOps.branchSha(worktree.repoPath, worktree.branch)) !== undefined;\n await GitOps.addWorktree(worktree.repoPath, worktree.path, worktree.branch, worktree.baseSha);\n headSha = await GitOps.headOf(worktree.path);\n } catch (error) {\n // Остановка потребителя: preparing доведёт сверка при следующем старте.\n if (GitOps.isGitPoolClosed()) return;\n services.store.inTransaction((tx) => {\n tx.markWorktreeFailed(id, `создание не удалось: ${messageOf(error)}`, { actor: HOST_ACTOR });\n // Работы в незавершённом worktree нет: остатки убираются без проверки изменений.\n tx.requestWorktreeCleanup(id, { actor: HOST_ACTOR, discardChanges: true, foreignBranch, evidence: 'очистка после неудачного создания' });\n });\n await cleanUnderLock(services, id);\n return;\n }\n services.store.inTransaction((tx) => tx.markWorktreeReady(id, headSha, { actor: HOST_ACTOR }));\n });\n}\n\n// --- Очистка ---\n\n/** Доводит cleaning до cleaned (или failed с причиной) под замком worktree. */\nexport async function finishCleaning(services: Services, id: string): Promise<void> {\n await withWorktreeLock(id, () => cleanUnderLock(services, id));\n}\n\n/**\n * Очистка (D-079, D-088): незакоммиченные изменения без решения вызывающего — failed, каталог\n * остаётся; иначе каталог переносится в trash, git снимает worktree с учёта, ветка без\n * коммитов сверх базы удаляется, trash удаляется. Перенос не удался — failed с причиной.\n */\nasync function cleanUnderLock(services: Services, id: string): Promise<void> {\n const worktree = services.store.getWorktree(id);\n if (worktree?.status !== 'cleaning') return;\n let trashed: string | undefined;\n let flags: { discardChanges: boolean; ownsBranch: boolean };\n try {\n const present = checkManagedPath(services.dataDir, worktree.path);\n flags = services.store.worktreeCleanupFlags(id);\n if (present && !flags.discardChanges && (await GitOps.isDirty(worktree.path))) {\n services.store.inTransaction((tx) =>\n tx.markWorktreeFailed(id, 'в worktree незакоммиченные изменения: очистка ждёт решения (worktree.remove с force: true выбросит их)', {\n actor: HOST_ACTOR,\n }),\n );\n return;\n }\n if (present) {\n const trash = trashRoot(services.dataDir);\n mkdirSync(trash, { recursive: true, mode: 0o700 });\n trashed = join(trash, `${worktree.id}-${randomUUID()}`);\n renameSync(worktree.path, trashed);\n }\n } catch (error) {\n // Остановка потребителя: cleaning доведёт reconciler при следующем старте.\n if (GitOps.isGitPoolClosed()) return;\n services.store.inTransaction((tx) => tx.markWorktreeFailed(id, `очистка не удалась: ${messageOf(error)}`, { actor: HOST_ACTOR }));\n return;\n }\n // Каталог ушёл с места: дальнейшие сбои не возвращают worktree — они в журнал, а trash\n // доочищается при старте (§6.9: прерванные удаления завершаются при старте).\n await releaseGitState(services, worktree, flags.ownsBranch);\n if (GitOps.isGitPoolClosed()) return;\n if (trashed !== undefined) {\n await rm(trashed, { recursive: true, force: true }).catch((error: unknown) => {\n services.log(`worktree ${worktree.id}: trash ${trashed ?? ''} не удалён: ${messageOf(error)}`);\n });\n }\n services.store.inTransaction((tx) => tx.completeWorktreeCleanup(id, { actor: HOST_ACTOR }));\n}\n\n/** Снимает worktree с учёта репозитория и удаляет ветку host, если на ней нет работы (tip = база). */\nasync function releaseGitState(services: Services, worktree: Worktree, ownsBranch: boolean): Promise<void> {\n try {\n await GitOps.pruneWorktrees(worktree.repoPath);\n if (!ownsBranch) return;\n const tip = await GitOps.branchSha(worktree.repoPath, worktree.branch);\n if (tip !== undefined && tip === worktree.baseSha) await GitOps.deleteBranch(worktree.repoPath, worktree.branch);\n } catch (error) {\n services.log(`worktree ${worktree.id}: репозиторий ${worktree.repoPath} не обновлён после очистки: ${messageOf(error)}`);\n }\n}\n\n// --- Reconciler ---\n\n/**\n * Сверка при старте, синхронно до приёма запросов: preparing без процесса, который его вёл,\n * — failed (потребитель упал посреди создания) и сразу в очистку. Дальше работает sweep.\n */\nexport function reconcileWorktreesAtStart(services: Services): void {\n for (const worktree of services.store.worktreesInStatus(['preparing'])) {\n services.store.inTransaction((tx) => {\n tx.markWorktreeFailed(worktree.id, 'host перезапущен во время создания worktree', { actor: HOST_ACTOR });\n tx.requestWorktreeCleanup(worktree.id, { actor: HOST_ACTOR, discardChanges: true, evidence: 'очистка после прерванного создания' });\n });\n }\n}\n\n/**\n * Один проход reconciler: ready worktree завершённых Task — в cleaning, все cleaning без\n * работы в этом процессе — до cleaned или failed. failed остаётся до решения (worktree.remove).\n */\nexport async function sweepWorktrees(services: Services): Promise<void> {\n for (const worktree of services.store.readyWorktreesOfSettledTasks()) {\n if (isWorktreeBusy(worktree.id)) continue;\n try {\n services.store.inTransaction((tx) => tx.requestWorktreeCleanup(worktree.id, { actor: HOST_ACTOR, evidence: 'Task завершена' }));\n } catch (error) {\n services.log(`worktree ${worktree.id}: очистка после завершения Task не началась: ${messageOf(error)}`);\n }\n }\n for (const worktree of services.store.worktreesInStatus(['cleaning'])) {\n if (isWorktreeBusy(worktree.id)) continue;\n await finishCleaning(services, worktree.id);\n }\n}\n\n/** Доочищает trash, брошенный прерванными удалениями. */\nexport async function emptyTrash(services: Services): Promise<void> {\n const trash = trashRoot(services.dataDir);\n let entries: string[];\n try {\n entries = readdirSync(trash);\n } catch {\n return;\n }\n for (const entry of entries) {\n await rm(join(trash, entry), { recursive: true, force: true }).catch((error: unknown) => {\n services.log(`trash ${entry} не удалён: ${messageOf(error)}`);\n });\n }\n}\n\n/** Запускает reconciler: сверка при старте, затем проход сразу и по таймеру. */\nexport function startWorktreeReconciler(services: Services, intervalMs = WORKTREE_RECONCILE_INTERVAL_MS): WorktreeReconciler {\n reconcileWorktreesAtStart(services);\n let current: Promise<void> = Promise.resolve();\n let stopped = false;\n const kick = (): Promise<void> => {\n if (stopped) return current;\n current = current.then(() => sweepWorktrees(services)).catch((error: unknown) => {\n services.log(`reconciler worktree: проход не удался: ${messageOf(error)}`);\n });\n return current;\n };\n current = emptyTrash(services);\n void kick();\n const timer = setInterval(() => void kick(), intervalMs);\n timer.unref();\n return {\n kick,\n stop: async () => {\n stopped = true;\n clearInterval(timer);\n await current;\n },\n };\n}\n\nexport function messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAIO,IAAM,iCAAiC;;;ACG9C,SAAS,kBAAkB;AAC3B,SAAS,WAAW,aAAa,kBAAkB;AACnD,SAAS,UAAU;AACnB,SAAS,YAAY;AAcrB,IAAM,aAAa;AAInB,IAAM,QAAQ,oBAAI,IAA2B;AAG7C,eAAsB,oBAAoB,IAAiC;AACzE,QAAM,WAAW,MAAM,IAAI,EAAE,KAAK,QAAQ,QAAQ;AAClD,MAAI;AACJ,QAAM,UAAU,IAAI,QAAc,CAAC,YAAa,UAAU,OAAQ;AAClE,QAAM,UAAU,SAAS,KAAK,MAAM,OAAO;AAC3C,QAAM,IAAI,IAAI,OAAO;AACrB,QAAM;AACN,MAAI,WAAW;AACf,SAAO,MAAM;AACX,QAAI,SAAU;AACd,eAAW;AACX,YAAQ;AACR,QAAI,MAAM,IAAI,EAAE,MAAM,QAAS,OAAM,OAAO,EAAE;AAAA,EAChD;AACF;AAEA,eAAsB,iBAAoB,IAAY,IAAkC;AACtF,QAAM,UAAU,MAAM,oBAAoB,EAAE;AAC5C,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,UAAE;AACA,YAAQ;AAAA,EACV;AACF;AAGO,SAAS,eAAe,IAAqB;AAClD,SAAO,MAAM,IAAI,EAAE;AACrB;AAQA,eAAsB,gBAAgB,UAAoB,IAA2B;AACnF,QAAM,iBAAiB,IAAI,YAAY;AACrC,UAAM,WAAW,SAAS,MAAM,YAAY,EAAE;AAC9C,QAAI,UAAU,WAAW,YAAa;AACtC,QAAI;AACJ,QAAI,gBAAgB;AACpB,QAAI;AACF,4BAAsB,SAAS,SAAS,SAAS,IAAI;AAErD,sBAAiB,MAAa,UAAU,SAAS,UAAU,SAAS,MAAM,MAAO;AACjF,YAAa,YAAY,SAAS,UAAU,SAAS,MAAM,SAAS,QAAQ,SAAS,OAAO;AAC5F,gBAAU,MAAa,OAAO,SAAS,IAAI;AAAA,IAC7C,SAAS,OAAO;AAEd,UAAW,gBAAgB,EAAG;AAC9B,eAAS,MAAM,cAAc,CAAC,OAAO;AACnC,WAAG,mBAAmB,IAAI,6GAAwB,UAAU,KAAK,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC;AAE3F,WAAG,uBAAuB,IAAI,EAAE,OAAO,YAAY,gBAAgB,MAAM,eAAe,UAAU,0LAAoC,CAAC;AAAA,MACzI,CAAC;AACD,YAAM,eAAe,UAAU,EAAE;AACjC;AAAA,IACF;AACA,aAAS,MAAM,cAAc,CAAC,OAAO,GAAG,kBAAkB,IAAI,SAAS,EAAE,OAAO,WAAW,CAAC,CAAC;AAAA,EAC/F,CAAC;AACH;AAKA,eAAsB,eAAe,UAAoB,IAA2B;AAClF,QAAM,iBAAiB,IAAI,MAAM,eAAe,UAAU,EAAE,CAAC;AAC/D;AAOA,eAAe,eAAe,UAAoB,IAA2B;AAC3E,QAAM,WAAW,SAAS,MAAM,YAAY,EAAE;AAC9C,MAAI,UAAU,WAAW,WAAY;AACrC,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,iBAAiB,SAAS,SAAS,SAAS,IAAI;AAChE,YAAQ,SAAS,MAAM,qBAAqB,EAAE;AAC9C,QAAI,WAAW,CAAC,MAAM,kBAAmB,MAAa,QAAQ,SAAS,IAAI,GAAI;AAC7E,eAAS,MAAM;AAAA,QAAc,CAAC,OAC5B,GAAG,mBAAmB,IAAI,wXAA0G;AAAA,UAClI,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AACA;AAAA,IACF;AACA,QAAI,SAAS;AACX,YAAM,QAAQ,UAAU,SAAS,OAAO;AACxC,gBAAU,OAAO,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACjD,gBAAU,KAAK,OAAO,GAAG,SAAS,EAAE,IAAI,WAAW,CAAC,EAAE;AACtD,iBAAW,SAAS,MAAM,OAAO;AAAA,IACnC;AAAA,EACF,SAAS,OAAO;AAEd,QAAW,gBAAgB,EAAG;AAC9B,aAAS,MAAM,cAAc,CAAC,OAAO,GAAG,mBAAmB,IAAI,uGAAuB,UAAU,KAAK,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC,CAAC;AAChI;AAAA,EACF;AAGA,QAAM,gBAAgB,UAAU,UAAU,MAAM,UAAU;AAC1D,MAAW,gBAAgB,EAAG;AAC9B,MAAI,YAAY,QAAW;AACzB,UAAM,GAAG,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,CAAC,UAAmB;AAC5E,eAAS,IAAI,YAAY,SAAS,EAAE,WAAW,WAAW,EAAE,uDAAe,UAAU,KAAK,CAAC,EAAE;AAAA,IAC/F,CAAC;AAAA,EACH;AACA,WAAS,MAAM,cAAc,CAAC,OAAO,GAAG,wBAAwB,IAAI,EAAE,OAAO,WAAW,CAAC,CAAC;AAC5F;AAGA,eAAe,gBAAgB,UAAoB,UAAoB,YAAoC;AACzG,MAAI;AACF,UAAa,eAAe,SAAS,QAAQ;AAC7C,QAAI,CAAC,WAAY;AACjB,UAAM,MAAM,MAAa,UAAU,SAAS,UAAU,SAAS,MAAM;AACrE,QAAI,QAAQ,UAAa,QAAQ,SAAS,QAAS,OAAa,aAAa,SAAS,UAAU,SAAS,MAAM;AAAA,EACjH,SAAS,OAAO;AACd,aAAS,IAAI,YAAY,SAAS,EAAE,wEAAiB,SAAS,QAAQ,6IAA+B,UAAU,KAAK,CAAC,EAAE;AAAA,EACzH;AACF;AAQO,SAAS,0BAA0B,UAA0B;AAClE,aAAW,YAAY,SAAS,MAAM,kBAAkB,CAAC,WAAW,CAAC,GAAG;AACtE,aAAS,MAAM,cAAc,CAAC,OAAO;AACnC,SAAG,mBAAmB,SAAS,IAAI,iLAA+C,EAAE,OAAO,WAAW,CAAC;AACvG,SAAG,uBAAuB,SAAS,IAAI,EAAE,OAAO,YAAY,gBAAgB,MAAM,UAAU,gMAAqC,CAAC;AAAA,IACpI,CAAC;AAAA,EACH;AACF;AAMA,eAAsB,eAAe,UAAmC;AACtE,aAAW,YAAY,SAAS,MAAM,6BAA6B,GAAG;AACpE,QAAI,eAAe,SAAS,EAAE,EAAG;AACjC,QAAI;AACF,eAAS,MAAM,cAAc,CAAC,OAAO,GAAG,uBAAuB,SAAS,IAAI,EAAE,OAAO,YAAY,UAAU,8DAAiB,CAAC,CAAC;AAAA,IAChI,SAAS,OAAO;AACd,eAAS,IAAI,YAAY,SAAS,EAAE,gNAAgD,UAAU,KAAK,CAAC,EAAE;AAAA,IACxG;AAAA,EACF;AACA,aAAW,YAAY,SAAS,MAAM,kBAAkB,CAAC,UAAU,CAAC,GAAG;AACrE,QAAI,eAAe,SAAS,EAAE,EAAG;AACjC,UAAM,eAAe,UAAU,SAAS,EAAE;AAAA,EAC5C;AACF;AAGA,eAAsB,WAAW,UAAmC;AAClE,QAAM,QAAQ,UAAU,SAAS,OAAO;AACxC,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,KAAK;AAAA,EAC7B,QAAQ;AACN;AAAA,EACF;AACA,aAAW,SAAS,SAAS;AAC3B,UAAM,GAAG,KAAK,OAAO,KAAK,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,CAAC,UAAmB;AACvF,eAAS,IAAI,SAAS,KAAK,uDAAe,UAAU,KAAK,CAAC,EAAE;AAAA,IAC9D,CAAC;AAAA,EACH;AACF;AAGO,SAAS,wBAAwB,UAAoB,aAAa,gCAAoD;AAC3H,4BAA0B,QAAQ;AAClC,MAAI,UAAyB,QAAQ,QAAQ;AAC7C,MAAI,UAAU;AACd,QAAM,OAAO,MAAqB;AAChC,QAAI,QAAS,QAAO;AACpB,cAAU,QAAQ,KAAK,MAAM,eAAe,QAAQ,CAAC,EAAE,MAAM,CAAC,UAAmB;AAC/E,eAAS,IAAI,gHAA0C,UAAU,KAAK,CAAC,EAAE;AAAA,IAC3E,CAAC;AACD,WAAO;AAAA,EACT;AACA,YAAU,WAAW,QAAQ;AAC7B,OAAK,KAAK;AACV,QAAM,QAAQ,YAAY,MAAM,KAAK,KAAK,GAAG,UAAU;AACvD,QAAM,MAAM;AACZ,SAAO;AAAA,IACL;AAAA,IACA,MAAM,YAAY;AAChB,gBAAU;AACV,oBAAc,KAAK;AACnB,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,UAAU,OAAwB;AAChD,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;","names":[]}
1
+ {"version":3,"sources":["../../src/worktree/lifecycle.const.ts","../../src/worktree/lifecycle.lib.ts"],"sourcesContent":["// Происхождение — onreel @ b73407be076a5df73931d1340f70910ca13c908d,\n// src/host/worktree/lifecycle.const.ts — дословная копия.\n\n/** Период reconciler: подбирает worktree завершённых Task и прерванные очистки (§6.8). */\nexport const WORKTREE_RECONCILE_INTERVAL_MS = 30_000;\n","// Жизненный цикл worktree библиотеки @smwb/onreel-git-mcp (часть D выделения).\n// Происхождение — onreel @ b73407be076a5df73931d1340f70910ca13c908d,\n// src/host/worktree/lifecycle.lib.ts — 9 прямых вызовов функций ledger заменены на порт\n// WorktreeStore ядра (kernel/store.types.ts, D-003): чтения — методы порта, записи — только\n// внутри единицы работы store.inTransaction (соответствие 1:1, в том числе группировка\n// вызовов в одну транзакцию). Хранилище пишет потребитель, эталонная реализация в памяти —\n// в тестах библиотеки (test/helpers/memory-store.lib.ts).\nimport { randomUUID } from 'node:crypto';\nimport { mkdirSync, readdirSync, renameSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Worktree } from '../contract/contract.types.ts';\nimport * as GitOps from '../git/git.lib.ts';\nimport { checkManagedPath, prepareWorktreeParent, trashRoot } from '../git/paths.lib.ts';\nimport { WORKTREE_RECONCILE_INTERVAL_MS } from './lifecycle.const.ts';\nimport type { WorktreeLifecycleServices, WorktreeReconciler } from './lifecycle.types.ts';\n\n// Вторая фаза создания и очистки worktree (D-050) и reconciler (§6.8, D-080). Receipt-транзакция\n// уже записала preparing или cleaning; здесь git и диск работают вне транзакции, а итог —\n// ready, cleaned или failed с причиной — пишется отдельной транзакцией. Работа над одним\n// worktree идёт под его замком: создание, очистка, коммит и rebase не перекрываются.\n\ntype Services = WorktreeLifecycleServices;\n\n// Актер собственных переходов библиотеки — процесс, который её держит (D-012). Значение уходит\n// в хранилище потребителя, то есть это данные, а не формулировка: потребитель отображает его\n// в свою лексику уже в реализации WorktreeStore.\nconst HOST_ACTOR = 'host';\n\n// --- Замок на worktree ---\n\n// Таблица замков одна на процесс и принадлежит этому модулю. Импортировать жизненный цикл из\n// нескольких точек входа можно: модуль двух и более точек входа tsup выносит в общий chunk, и\n// определение таблицы в бандле остаётся одно (D-013). Свою копию таблицы заводит только сборка\n// без splitting — тогда isWorktreeBusy перестаёт видеть работу, идущую через другой субпуть.\n// Инвариант держит test/worktree/locks-bundle.test.ts: он считает определения в собранном пакете.\nconst locks = new Map<string, Promise<void>>();\n\n/** Берёт замок worktree: возвращает функцию освобождения. Ожидающие идут по очереди. */\nexport async function acquireWorktreeLock(id: string): Promise<() => void> {\n const previous = locks.get(id) ?? Promise.resolve();\n let release!: () => void;\n const current = new Promise<void>((resolve) => (release = resolve));\n const chained = previous.then(() => current);\n locks.set(id, chained);\n await previous;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n release();\n if (locks.get(id) === chained) locks.delete(id);\n };\n}\n\nexport async function withWorktreeLock<T>(id: string, fn: () => Promise<T>): Promise<T> {\n const release = await acquireWorktreeLock(id);\n try {\n return await fn();\n } finally {\n release();\n }\n}\n\n/** Над worktree сейчас идёт работа этого процесса. */\nexport function isWorktreeBusy(id: string): boolean {\n return locks.has(id);\n}\n\n// --- Создание ---\n\n/**\n * Доводит preparing до ready: каталог под корнем workspaces, `git worktree add --no-track`,\n * HEAD. Сбой — failed с причиной и сразу очистка остатков (каталог и пустая ветка).\n */\nexport async function finishPreparing(services: Services, id: string): Promise<void> {\n await withWorktreeLock(id, async () => {\n const worktree = services.store.getWorktree(id);\n if (worktree?.status !== 'preparing') return;\n let headSha: string;\n let foreignBranch = false;\n try {\n prepareWorktreeParent(services.dataDir, worktree.path);\n // Ветка появилась после проверки create: она чужая, git -b откажет, очистка её не тронет.\n foreignBranch = (await GitOps.branchSha(worktree.repoPath, worktree.branch)) !== undefined;\n await GitOps.addWorktree(worktree.repoPath, worktree.path, worktree.branch, worktree.baseSha);\n headSha = await GitOps.headOf(worktree.path);\n } catch (error) {\n // Остановка потребителя: preparing доведёт сверка при следующем старте.\n if (GitOps.isGitPoolClosed()) return;\n services.store.inTransaction((tx) => {\n tx.markWorktreeFailed(id, `создание не удалось: ${messageOf(error)}`, { actor: HOST_ACTOR });\n // Работы в незавершённом worktree нет: остатки убираются без проверки изменений.\n tx.requestWorktreeCleanup(id, { actor: HOST_ACTOR, discardChanges: true, foreignBranch, evidence: 'очистка после неудачного создания' });\n });\n await cleanUnderLock(services, id);\n return;\n }\n services.store.inTransaction((tx) => tx.markWorktreeReady(id, headSha, { actor: HOST_ACTOR }));\n });\n}\n\n// --- Очистка ---\n\n/** Доводит cleaning до cleaned (или failed с причиной) под замком worktree. */\nexport async function finishCleaning(services: Services, id: string): Promise<void> {\n await withWorktreeLock(id, () => cleanUnderLock(services, id));\n}\n\n/**\n * Очистка (D-079, D-088): незакоммиченные изменения без решения вызывающего — failed, каталог\n * остаётся; иначе каталог переносится в trash, git снимает worktree с учёта, ветка без\n * коммитов сверх базы удаляется, trash удаляется. Перенос не удался — failed с причиной.\n */\nasync function cleanUnderLock(services: Services, id: string): Promise<void> {\n const worktree = services.store.getWorktree(id);\n if (worktree?.status !== 'cleaning') return;\n let trashed: string | undefined;\n let flags: { discardChanges: boolean; ownsBranch: boolean };\n try {\n const present = checkManagedPath(services.dataDir, worktree.path);\n flags = services.store.worktreeCleanupFlags(id);\n if (present && !flags.discardChanges && (await GitOps.isDirty(worktree.path))) {\n services.store.inTransaction((tx) =>\n tx.markWorktreeFailed(id, 'в worktree незакоммиченные изменения: очистка ждёт решения (worktree.remove с force: true выбросит их)', {\n actor: HOST_ACTOR,\n }),\n );\n return;\n }\n if (present) {\n const trash = trashRoot(services.dataDir);\n mkdirSync(trash, { recursive: true, mode: 0o700 });\n trashed = join(trash, `${worktree.id}-${randomUUID()}`);\n renameSync(worktree.path, trashed);\n }\n } catch (error) {\n // Остановка потребителя: cleaning доведёт reconciler при следующем старте.\n if (GitOps.isGitPoolClosed()) return;\n services.store.inTransaction((tx) => tx.markWorktreeFailed(id, `очистка не удалась: ${messageOf(error)}`, { actor: HOST_ACTOR }));\n return;\n }\n // Каталог ушёл с места: дальнейшие сбои не возвращают worktree — они в журнал, а trash\n // доочищается при старте (§6.9: прерванные удаления завершаются при старте).\n await releaseGitState(services, worktree, flags.ownsBranch);\n if (GitOps.isGitPoolClosed()) return;\n if (trashed !== undefined) {\n await rm(trashed, { recursive: true, force: true }).catch((error: unknown) => {\n services.log(`worktree ${worktree.id}: trash ${trashed ?? ''} не удалён: ${messageOf(error)}`);\n });\n }\n services.store.inTransaction((tx) => tx.completeWorktreeCleanup(id, { actor: HOST_ACTOR }));\n}\n\n/** Снимает worktree с учёта репозитория и удаляет ветку host, если на ней нет работы (tip = база). */\nasync function releaseGitState(services: Services, worktree: Worktree, ownsBranch: boolean): Promise<void> {\n try {\n await GitOps.pruneWorktrees(worktree.repoPath);\n if (!ownsBranch) return;\n const tip = await GitOps.branchSha(worktree.repoPath, worktree.branch);\n if (tip !== undefined && tip === worktree.baseSha) await GitOps.deleteBranch(worktree.repoPath, worktree.branch);\n } catch (error) {\n services.log(`worktree ${worktree.id}: репозиторий ${worktree.repoPath} не обновлён после очистки: ${messageOf(error)}`);\n }\n}\n\n// --- Reconciler ---\n\n/**\n * Сверка при старте, синхронно до приёма запросов: preparing без процесса, который его вёл,\n * — failed (потребитель упал посреди создания) и сразу в очистку. Дальше работает sweep.\n */\nexport function reconcileWorktreesAtStart(services: Services): void {\n for (const worktree of services.store.worktreesInStatus(['preparing'])) {\n services.store.inTransaction((tx) => {\n tx.markWorktreeFailed(worktree.id, 'потребитель перезапущен во время создания worktree', { actor: HOST_ACTOR });\n tx.requestWorktreeCleanup(worktree.id, { actor: HOST_ACTOR, discardChanges: true, evidence: 'очистка после прерванного создания' });\n });\n }\n}\n\n/**\n * Один проход reconciler: ready worktree завершённых Task — в cleaning, все cleaning без\n * работы в этом процессе — до cleaned или failed. failed остаётся до решения (worktree.remove).\n */\nexport async function sweepWorktrees(services: Services): Promise<void> {\n for (const worktree of services.store.readyWorktreesOfSettledTasks()) {\n if (isWorktreeBusy(worktree.id)) continue;\n try {\n services.store.inTransaction((tx) => tx.requestWorktreeCleanup(worktree.id, { actor: HOST_ACTOR, evidence: 'Task завершена' }));\n } catch (error) {\n services.log(`worktree ${worktree.id}: очистка после завершения Task не началась: ${messageOf(error)}`);\n }\n }\n for (const worktree of services.store.worktreesInStatus(['cleaning'])) {\n if (isWorktreeBusy(worktree.id)) continue;\n await finishCleaning(services, worktree.id);\n }\n}\n\n/** Доочищает trash, брошенный прерванными удалениями. */\nexport async function emptyTrash(services: Services): Promise<void> {\n const trash = trashRoot(services.dataDir);\n let entries: string[];\n try {\n entries = readdirSync(trash);\n } catch {\n return;\n }\n for (const entry of entries) {\n await rm(join(trash, entry), { recursive: true, force: true }).catch((error: unknown) => {\n services.log(`trash ${entry} не удалён: ${messageOf(error)}`);\n });\n }\n}\n\n/** Запускает reconciler: сверка при старте, затем проход сразу и по таймеру. */\nexport function startWorktreeReconciler(services: Services, intervalMs = WORKTREE_RECONCILE_INTERVAL_MS): WorktreeReconciler {\n reconcileWorktreesAtStart(services);\n let current: Promise<void> = Promise.resolve();\n let stopped = false;\n const kick = (): Promise<void> => {\n if (stopped) return current;\n current = current.then(() => sweepWorktrees(services)).catch((error: unknown) => {\n services.log(`reconciler worktree: проход не удался: ${messageOf(error)}`);\n });\n return current;\n };\n current = emptyTrash(services);\n void kick();\n const timer = setInterval(() => void kick(), intervalMs);\n timer.unref();\n return {\n kick,\n stop: async () => {\n stopped = true;\n clearInterval(timer);\n await current;\n },\n };\n}\n\nexport function messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smwb/onreel-git-mcp",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Библиотека модулей, выделенных из onreel: git-операции и безопасные пути, список файлов worktree, жизненный цикл worktree против порта WorktreeStore, адаптеры трекеров GitHub/GitLab/Jira/SourceCraft/локального и MCP-сервер на stdio",
5
5
  "keywords": [
6
6
  "git",