Skip to content

feat: auto-delete containers whose issue or PR is closed#394

Open
konard wants to merge 6 commits into
ProverCoderAI:mainfrom
konard:issue-117-bc31e273b33e
Open

feat: auto-delete containers whose issue or PR is closed#394
konard wants to merge 6 commits into
ProverCoderAI:mainfrom
konard:issue-117-bc31e273b33e

Conversation

@konard

@konard konard commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

What & why

Closes #117«Сделать возможность автоматического удаления контейнера issues или PR которого уже закрылся» (automatically delete a container whose issue or PR has already been closed).

A closed issue/PR leaves its docker-git container running with no purpose, and these pile up over time. This adds an opt-in background loop that periodically checks each project's originating GitHub issue/PR and removes the container once that source is closed — modeled directly on the existing auto-suspend loop.

How it works

  1. Recover the source identity (packages/lib/src/core/project-source-ref.ts, pure): inverts the clone-time normalization to map a project's repoUrl + repoRef back to {provider, owner, repo, kind: issue|pull, number} (issue-<n>, refs/pull/<n>/head, refs/merge-requests/<n>/head). Returns null for plain-branch projects so they are never touched.
  2. Query the state (packages/lib/src/usecases/project-source-state.ts, shell): calls gh api /repos/{owner}/{repo}/issues/{n} --jq .state inside the gh-auth container and normalizes to open | closed | unknown (GitLab → unknown until first-class support exists).
  3. Decide (packages/api/src/services/project-closed-source-policy.ts, pure): delete only when the source is definitively closed and no agent is active and no live interactive session (ssh/terminal/browser/skiller) is attached. open/unknown always keep the project, so a transient API failure never destroys live work.
  4. Loop (packages/api/src/services/project-auto-delete.ts, shell): disabled by default; forked from program.ts next to the auto-suspend loop.

Shared activity predicates (projectHasActiveAgent, projectHasLiveInteractiveSession) were extracted into project-activity.ts and reused by both the auto-suspend and auto-delete loops to avoid duplicated logic.

Configuration (opt-in)

Env var Default Meaning
DOCKER_GIT_AUTO_DELETE_CLOSED false Enable the loop (1/true/on/yes). Deletion is irreversible, so it is off unless explicitly enabled.
DOCKER_GIT_AUTO_DELETE_SCAN_INTERVAL_SECONDS 300 Scan interval.

Safety invariants

  • Off by default; never deletes on open or unknown state.
  • Never deletes while an agent or a live interactive session is active.
  • API/HTTP failures collapse to unknown → keep.

Tests

  • packages/lib/tests/core/project-source-ref.test.ts — 19 cases covering issue/PR/MR parsing for GitHub & GitLab, branch/empty/non-provider rejection, and state normalization.
  • packages/api/tests/project-closed-source-policy.test.ts — 7 cases covering every Keep/Delete branch and reason precedence.

Full suites green: lib 300/300, api 192/192. Lib (vibecode-linter) and api (eslint) lint clean; lib/api typecheck clean. Changeset added (minor for @prover-coder-ai/docker-git).

Adding .gitkeep for PR creation (default mode).
This file will be removed when the task is complete.

Issue: ProverCoderAI#117
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

Выпуск

  • Новые возможности

    • Добавлена опциональная функция автоматического удаления Docker-контейнеров при закрытии исходного GitHub issue или pull request. Включается через переменную окружения DOCKER_GIT_AUTO_DELETE_CLOSED=1. Интервал сканирования настраивается через DOCKER_GIT_AUTO_DELETE_SCAN_INTERVAL_SECONDS (по умолчанию 300 сек). Удаление не выполняется, если в проекте активна сессия агента или интерактивная сессия пользователя.
  • Тесты

    • Добавлены тесты для логики принятия решений по удалению проектов с закрытыми источниками.

Обзор

PR реализует опциональный механизм фонового автоудаления Docker-контейнеров проектов, исходные GitHub issues/pull requests которых находятся в закрытом состоянии. Удаление блокируется консервативной политикой: если состояние источника открыто/неизвестно или есть активные агенты/интерактивные сессии, проект сохраняется.

Изменения

Автоудаление закрытого источника

Layer / File(s) Описание
Парсинг и нормализация ссылок на исходники
packages/lib/src/core/project-source-ref.ts, packages/lib/src/core/domain.ts, packages/lib/tests/core/project-source-ref.test.ts
Новый модуль парсит GitHub/GitLab issue/PR из repoUrl/repoRef, нормализует состояния провайдеров в единую категоризацию (open/closed/unknown), и определяет критерий удаления (только для closed). Типы и функции реэкспортируются через доменный модуль. Функциональность полностью покрыта unit-тестами.
Получение состояния источника от GitHub
packages/lib/src/usecases/project-source-state.ts
Usecase запрашивает GitHub API для получения текущего состояния issue/PR и нормализует ответ. Ошибки и неподдерживаемые провайдеры коллапсятся в unknown для консервативного пропуска удаления.
Общие предикаты активности проекта
packages/api/src/services/project-activity.ts, packages/api/src/services/project-auto-suspend.ts
Новый модуль определяет переиспользуемые предикаты: projectHasActiveAgent (проверка снэпшота задач на наличие active agents) и projectHasLiveInteractiveSession (проверка SSH-сессий и реестра live сессий). Рефакторизованный project-auto-suspend.ts теперь использует эти общие функции для согласованности с автоудалением.
Политика принятия решения об удалении
packages/api/src/services/project-closed-source-policy.ts, packages/api/tests/project-closed-source-policy.test.ts
Новый модуль определяет типы входных данных и результата решения. Функция decideProjectClosedSourceAction применяет последовательные проверки: сохраняет проект если состояние не закрыто, или если есть активный агент, или если есть live-интерактивная сессия; удаляет только при выполнении всех условий закрытия. Unit-тесты покрывают все комбинации состояний.
Сервис и интеграция автоудаления
packages/api/src/services/project-auto-delete.ts, packages/api/src/program.ts, .changeset/auto-delete-closed-source.md
Новый сервис разрешает конфигурацию из переменных окружения, реализует параллельное сканирование проектов (конкурентность 2) и цикл повторного запуска с настраиваемым интервалом. Функция evaluateProject связывает все компоненты: получает ref источника, запрашивает состояние, проверяет активность, применяет решение и удаляет при необходимости. Интеграция в program.ts запускает фоновый цикл при старте. Changeset документирует функционал как minor-изменение.

Оценка сложности рецензирования

🎯 3 (Умеренная) | ⏱️ ~25 минут

Возможно связанные PR

  • ProverCoderAI/docker-git#374: Новая функция использует parseProjectSourceRef и нормализацию состояний для определения закрыто/открыто по repoUrl/repoRef, что напрямую связано с логикой парсинга source-ref в соответствующем PR.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Requirements Alignment ❌ Error Review comments identified critical issues not addressed: projectHasActiveAgent error handling unsafe for auto-delete; GitLab owner field; missing fast-check property tests for invariants. Fix projectHasActiveAgent error handling; correct gitlab.owner; add fast-check property tests for source-ref and policy invariants.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately and concisely summarizes the main feature: auto-deletion of containers when their source issue or PR is closed.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering objectives, implementation details, configuration, safety guarantees, and test results, though it deviates from the template structure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Regression ✅ Passed No high-confidence security regressions found: input parsing protected by anchored regex, API params passed as array, token via environment, path traversal validated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@konard konard changed the title [WIP] Сделать возможность автоматического удаления контейнера issues или PR которого уже закрылся feat: auto-delete containers whose issue or PR is closed Jun 10, 2026
@konard konard marked this pull request as ready for review June 10, 2026 10:39
@konard

konard commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Working session summary

The "Check" workflow is the remaining one. I'll wait for the background monitor (bmzrwz0zf) to notify me when all CI completes rather than polling repeatedly.

Meanwhile, here's a summary of where things stand:

Issue #117 — auto-delete containers for closed issues/PRs — implementation complete:

  • Pure core (packages/lib/src/core/project-source-ref.ts): parseProjectSourceRef, normalizeProjectSourceState, shouldDeleteForSourceState — recovers issue/PR identity and classifies open/closed/unknown.
  • Shell usecase (packages/lib/src/usecases/project-source-state.ts): queries GitHub state via gh api.
  • Policy (packages/api/src/services/project-closed-source-policy.ts): pure Keep/Delete decision — deletes only on closed + no active agent + no live session.
  • Loop (packages/api/src/services/project-auto-delete.ts + program.ts wiring): opt-in via DOCKER_GIT_AUTO_DELETE_CLOSED, off by default.
  • Shared predicates extracted to project-activity.ts (reused by auto-suspend).

Verification: lib 300/300 + api 192/192 tests pass; new tests added (19 + 7 cases); lib vibecode-linter and api eslint clean; typecheck clean; changeset added (minor).

CI so far: Final Build ✅, Checking Dependencies ✅, Snapshot ✅ — only "Check" still running.

PR #394 is updated (title + description) and marked ready for review.


This summary was automatically extracted from the AI working session output.

@konard

konard commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Solution Draft Log

This log file contains the complete execution trace of the AI solution draft process.

💰 Cost: $7.013169

📊 Context and tokens usage:

Claude Opus 4.8: (2 sub-sessions)

  1. 116.4K / 1M (12%) input tokens, 34.0K / 128K (27%) output tokens
  2. 67.1K / 1M (7%) input tokens, 16.0K / 128K (13%) output tokens

Total: (16.1K new + 225.0K cache writes + 8.1M cache reads) input tokens, 58.4K output tokens, $7.013169 cost

🤖 Models used:

  • Tool: Anthropic Claude Code
  • Requested: opus
  • Model: Claude Opus 4.8 (claude-opus-4-8)

📎 Log file uploaded as Gist (3117KB)


Now working session is ended, feel free to review and add any feedback on the solution draft.

@konard

konard commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Cancelled CI/CD Requires Review

Hive Mind detected cancelled or stale CI/CD checks and cannot get them running automatically.

Commit: 2e4**********************************45e

Cancelled checks

  • E2E (Browser command)

Workflow runs inspected

Automatic re-run result
Automatic re-run was attempted, but no workflow run was successfully re-triggered.

Action required

  1. Review the cancelled CI logs to decide whether this was a real timeout/failure or a deliberate manual cancellation.
  2. If the cancelled check is required, re-run the workflow manually from GitHub Actions or push a new commit.
  3. If the cancellation was deliberate and non-blocking, decide whether this PR can be merged outside automation.

If workflow/job timeout-minutes or a runner execution limit caused the cancellation, treat it as a CI failure and fix the timeout, test, or infrastructure before merging.


Hive Mind is stopping because continuing to poll the same cancelled/stale check would not change the mergeability result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/api/src/services/project-activity.ts`:
- Around line 27-32: The code masks readContainerTaskSnapshot failures by
catching all errors and returning false (via Effect.catchAll(() =>
Effect.succeed(false))), which makes the caller think "no active agent" and can
cause unsafe auto-deletion; change the behavior in the pipeline that uses
readContainerTaskSnapshot and activeAgents so errors are propagated instead of
swallowed — remove or modify the Effect.catchAll on the
readContainerTaskSnapshot pipeline (or replace it with an explicit
failure/unknown/keep result) so that failures from readContainerTaskSnapshot
surface to the caller (or return an explicit "unknown"/"keep" sentinel) and let
the higher-level evaluator decide rather than forcing false.

In `@packages/api/tests/project-closed-source-policy.test.ts`:
- Around line 1-57: Add a fast-check property test that exhaustively verifies
the decideProjectClosedSourceAction invariant by generating arbitrary
ProjectClosedSourcePolicyInput values (use fast-check) and asserting the policy
rules: when sourceState === "closed" && !hasActiveAgent &&
!hasLiveInteractiveSession result._tag === "Delete"; when sourceState === "open"
|| sourceState === "unknown" result._tag === "Keep" with reason
"source-open-or-unknown"; when hasActiveAgent result._tag === "Keep" with reason
"active-agent" (and this should take precedence over hasLiveInteractiveSession
which yields "live-interactive-session"); import and use
decideProjectClosedSourceAction and the ProjectClosedSourcePolicyInput type in
the test file and keep the existing example tests.

In `@packages/lib/src/core/project-source-ref.ts`:
- Around line 20-21: The ProjectSourceRef contract for GitLab is inconsistent:
the docstring states owner is the namespace (e.g. "group/subgroup") but the code
sets owner to gitlab.projectPath (namespace/repo). Update ProjectSourceRef so
owner consistently carries only the namespace and the repository name goes into
a separate field (e.g., repo or name), or adjust the docstring to match the
current behavior. Locate the ProjectSourceRef type/constructor and the place
assigning gitlab.projectPath (reference gitlab.projectPath and the owner
property) and change the assignment and/or documentation so owner = namespace
only and repository = repoName, ensuring any code consumers are updated to use
the new repo field.

In `@packages/lib/src/usecases/project-source-state.ts`:
- Around line 19-20: Инвариант в строках 19‑20 должен быть уточнён: замените
формулировку "never throws on API/HTTP failure — unknown is returned so deletion
is skipped" на указание, что fetchGithubSourceState может завершаться ошибкой в
error‑channel (PlatformError) и не выполняет локального преобразования ошибок в
"unknown"; свёртка в "unknown" происходит в оркестраторе (например в проекте
AutoDelete через Effect.catchAll(() => Effect.succeed("unknown"))). Ссылаться в
комментарии явно на fetchGithubSourceState и на поведение обработчика в
project-auto-delete (Effect.catchAll), чтобы контракт был корректно понят при
чтении кода.

In `@packages/lib/tests/core/project-source-ref.test.ts`:
- Around line 9-115: Add fast-check property-based tests for
parseProjectSourceRef, normalizeProjectSourceState, and
shouldDeleteForSourceState: write fc.property cases that (1) assert trimming
whitespace around ref does not change the parsed .number for
parseProjectSourceRef and that known GitHub/GitLab ref patterns produce the
expected object shape ({provider, owner, repo, kind, number}) while branch-like
or unknown patterns (including a GitHub repo with refs/merge-requests/... )
return null; (2) assert normalizeProjectSourceState is idempotent, case- and
whitespace-insensitive, maps "open"/"opened"→"open",
"closed"/"merged"/"locked"→"closed", and null/undefined/other→"unknown"; and (3)
assert shouldDeleteForSourceState(state) === (normalizeProjectSourceState(state)
=== "closed") for arbitrary string/null/undefined inputs; use
parseProjectSourceRef, normalizeProjectSourceState, and
shouldDeleteForSourceState identifiers and fc.property with appropriate
generators (fc.string, fc.option, fc.oneof, and custom generators for valid ref
patterns) to implement these invariants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b8711bdb-0097-4739-913f-4a74be709e7f

📥 Commits

Reviewing files that changed from the base of the PR and between 8889c5c and 8054a91.

📒 Files selected for processing (11)
  • .changeset/auto-delete-closed-source.md
  • packages/api/src/program.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/api/src/services/project-auto-suspend.ts
  • packages/api/src/services/project-closed-source-policy.ts
  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/lib/src/core/domain.ts
  • packages/lib/src/core/project-source-ref.ts
  • packages/lib/src/usecases/project-source-state.ts
  • packages/lib/tests/core/project-source-ref.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: E2E (OpenCode)
  • GitHub Check: E2E (Clone cache)
  • GitHub Check: Lint
  • GitHub Check: Test
  • GitHub Check: E2E (Runtime volumes + SSH)
  • GitHub Check: E2E (Login context)
  • GitHub Check: E2E (Clone auto-open SSH)
  • GitHub Check: E2E (Browser command)
  • GitHub Check: Final build (windows-latest)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Implement Functional Core, Imperative Shell (FCIS) pattern: CORE layer contains only pure functions with immutable data and mathematical operations; SHELL layer isolates all effects (IO, network, database). Strict dependency direction: SHELL → CORE (never reverse).
Never use any, unknown, eslint-disable, ts-ignore, or as type assertions (except in rigorously justified cases with documentation). Always use exhaustive union type analysis through .exhaustive() pattern matching.
All external dependencies must be wrapped through typed interfaces and injected via Effect-TS Layer pattern. Never call external services directly from CORE functions.
Use monadic composition with Effect-TS for all effects: Effect<Success, Error, Requirements>. Compose effects through pipe() and Effect.flatMap(). Implement dependency injection via Layer pattern. Handle errors without try/catch blocks.
All functions must be pure in the CORE layer: no side effects (logging, console output, IO operations, mutations). Separate all side effects into the SHELL layer.
Use exhaustive pattern matching with Effect.Match instead of switch statements. Example: Match.value(item).pipe(Match.when(...), Match.exhaustive).
Document all functions with comprehensive TSDoc including: @pure (true/false), @effect (required services), @invariant (mathematical invariants), @precondition, @postcondition, @complexity (time and space), @throws Never (errors must be typed in Effect).
Use functional comment markers for code clarity: CHANGE (brief description), WHY (mathematical/architectural justification), QUOTE(ТЗ) (requirement citation), REF (RTM or message ID), SOURCE (external source with quote), FORMAT THEOREM (∀x ∈ Domain: P(x) → Q(f(x))), PURITY (CORE|SHELL), EFFECT (Effect type signature), INVARIANT (mathematical invariant), COMPLEXITY (time/space).
Define all external service dependencies as Context.Tag classes with fully typed methods returning Effect types. Example: `class Da...

Files:

  • packages/api/src/services/project-closed-source-policy.ts
  • packages/lib/src/core/domain.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/api/src/program.ts
  • packages/lib/src/usecases/project-source-state.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/lib/src/core/project-source-ref.ts
  • packages/lib/tests/core/project-source-ref.test.ts
  • packages/api/src/services/project-auto-suspend.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Forbidden constructs in CORE code: any, eslint-disable, ts-ignore, async/await, raw Promise chains (then/catch), Promise.all, try/catch for logic control, console.*, switch statements (use Match with .exhaustive() instead)
All functions must use Effect-TS for composing effects: Effect<Success, Error, Requirements>. No direct async/await, Promise chains, or try/catch in product logic.
Functional comments must include: CHANGE, WHY, QUOTE(ТЗ) or n/a, REF, SOURCE or n/a, FORMAT THEOREM, PURITY (CORE|SHELL), EFFECT signature for SHELL functions, INVARIANT, and COMPLEXITY.
All data mutations must use immutable patterns (ReadonlyArray, readonly properties, Object.freeze); mutation in SHELL only when absolutely necessary and documented.

Files:

  • packages/api/src/services/project-closed-source-policy.ts
  • packages/lib/src/core/domain.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/api/src/program.ts
  • packages/lib/src/usecases/project-source-state.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/lib/src/core/project-source-ref.ts
  • packages/lib/tests/core/project-source-ref.test.ts
  • packages/api/src/services/project-auto-suspend.ts
**/*.{sh,bash,py,js,ts,jsx,tsx,go,java,rb,php}

📄 CodeRabbit inference engine (Custom checks)

Fail if changed files introduce command injection or unsafe shell/process execution with user-controlled input

Files:

  • packages/api/src/services/project-closed-source-policy.ts
  • packages/lib/src/core/domain.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/api/src/program.ts
  • packages/lib/src/usecases/project-source-state.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/lib/src/core/project-source-ref.ts
  • packages/lib/tests/core/project-source-ref.test.ts
  • packages/api/src/services/project-auto-suspend.ts
**/*.{py,js,ts,jsx,tsx,go,java,rb,php,sh,bash,c,cpp}

📄 CodeRabbit inference engine (Custom checks)

Fail if changed files introduce path traversal or writes outside intended project/container state directories

Files:

  • packages/api/src/services/project-closed-source-policy.ts
  • packages/lib/src/core/domain.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/api/src/program.ts
  • packages/lib/src/usecases/project-source-state.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/lib/src/core/project-source-ref.ts
  • packages/lib/tests/core/project-source-ref.test.ts
  • packages/api/src/services/project-auto-suspend.ts
**/*.{js,ts,jsx,tsx,py,java,go,rb,php,sh,bash,yml,yaml,json,env*,toml,cfg,config,dockerfile,dockerignore}

📄 CodeRabbit inference engine (Custom checks)

Fail if changed files expose credentials, tokens, private-keys, or PII in source, generated config, logs, or CI output

Files:

  • packages/api/src/services/project-closed-source-policy.ts
  • packages/lib/src/core/domain.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/api/src/program.ts
  • packages/lib/src/usecases/project-source-state.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/lib/src/core/project-source-ref.ts
  • packages/lib/tests/core/project-source-ref.test.ts
  • packages/api/src/services/project-auto-suspend.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Ты строгий ревьюер SPEC DRIVEN DEVELOPMENT.

Перед выводами изучи README.md, другие *.md файлы, linked issues,
PR description, PR comments/discussion и релевантную кодовую базу.

Сверь изменения с исходным ТЗ/спекой и обсуждением. Флагай любой уход
от спеки, недокументированное изменение поведения, отсутствие тестов
для заявленного поведения и security-риск. Если спека не видна,
попроси автора добавить ее в issue или PR description.

Проверь решение с точки зрения формальной верификации: какие инварианты,
предусловия и постусловия можно доказать математически, а где доказуемость
слабая. Оцени решение с точки зрения теории игр: устойчивы ли стимулы,
нет ли выгодного обхода правил, и какое решение было бы сильнее.

Files:

  • packages/api/src/services/project-closed-source-policy.ts
  • packages/lib/src/core/domain.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/api/src/program.ts
  • packages/lib/src/usecases/project-source-state.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/lib/src/core/project-source-ref.ts
  • packages/lib/tests/core/project-source-ref.test.ts
  • packages/api/src/services/project-auto-suspend.ts
**

⚙️ CodeRabbit configuration file

**: РОЛЬ: Математик-программист, специализирующийся на формально верифицируемой функциональной архитектуре.

ЦЕЛЬ: Создавать математически доказуемые решения через функциональную парадигму с полным разделением чистых вычислений и контролируемых эффектов.

МОДЕЛЬ РАССУЖДЕНИЯ:

  • Не выдавать “личные мнения”. Формировать вывод как результат симуляции профессионального обсуждения релевантных ролей
    (архитектор Effect/FP, ревьюер типов, страж CORE↔SHELL, тест-инженер).
  • Если запрос сформулирован как “что думаешь”, отвечать в терминах аргументов ролей и выбирать решение
    по критериям инвариантов, типовой безопасности и тестируемости (если пользователь явно просит выбор — выбрать и обосновать).

ПРАВИЛО ПРОЦЕССА (НЕ ФОРМАТ ОТВЕТА):
В начале работы (внутренне) формулировать Deep Research вопрос:
"I am looking for code that does , is there existing code that can do this?"
Далее:

  • если доступен проект/код — сперва искать и переиспользовать существующие паттерны (минимальный корректный diff),
  • если проект недоступен — опираться на предоставленный контекст и явно фиксировать допущения,
  • код писать только после формального понимания задачи (типы/инварианты → архитектура → код → тесты),
  • источники указывать только если реально использован внешний материал; иначе SOURCE: n/a.

ИНСТРУМЕНТАЛЬНОЕ ПОВЕДЕНИЕ (ОБЯЗАТЕЛЬНО, НЕ ФОРМАТ ОТВЕТА):

  • Агент всегда использует доступные инструменты среды (терминал, поиск по проекту, запуск тестов/скриптов, анализ сборки, web-ресёрч при необходимости)
    для ресёрча, проверки гипотез и выполнения действий. Приоритет: проверяемость, воспроизводимость, минимальный риск.
  • Агент не предлагает “гайд” как замену действия. Если действие возможно выполнить инструментами — агент выполняет его сам,
    затем сообщает, что было сделано и как повторить.
  • Любые инструкции (команды/процедуры) агент даёт только после собственной проверки на доступной среде.
    Если проверить невозможно — явно фиксирует ограничение и перечисляе...

Files:

  • packages/api/src/services/project-closed-source-policy.ts
  • packages/lib/src/core/domain.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/api/src/program.ts
  • packages/lib/src/usecases/project-source-state.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/lib/src/core/project-source-ref.ts
  • packages/lib/tests/core/project-source-ref.test.ts
  • packages/api/src/services/project-auto-suspend.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx}: Implement property-based testing using fast-check for mathematical properties and invariants. Example: fc.property(fc.array(messageArbitrary), (messages) => isChronologicallySorted(sortMessagesByTimestamp(messages))).
Mock external dependencies in unit tests using Effect's testing utilities. Run tests without Effect runtime for speed. Example: Effect.provide(MockService), Effect.runPromise.

Files:

  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/lib/tests/core/project-source-ref.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Property-based tests (fast-check) must verify mathematical invariants; unit tests must use Effect test utilities without async/await.

Files:

  • packages/api/tests/project-closed-source-policy.test.ts
  • packages/lib/tests/core/project-source-ref.test.ts
🧠 Learnings (1)
📚 Learning: 2026-05-18T07:43:38.131Z
Learnt from: skulidropek
Repo: ProverCoderAI/docker-git PR: 313
File: packages/api/src/services/panel-cloudflare-tunnel.ts:123-135
Timestamp: 2026-05-18T07:43:38.131Z
Learning: In this repo’s ProverCoderAI/docker-git architecture, it is intentional for service modules under packages/api/src/services/ to call Node.js APIs directly (e.g., fs, child_process, fetch). Do not treat direct Node API usage in these service modules as a dependency-injection (DI) violation. This codebase provides the NodeContext at the program boundary (entry point) rather than injecting Context.Tag/Layer services into individual service modules; reviewers should only flag DI/context issues if the expected boundary setup is missing.

Applied to files:

  • packages/api/src/services/project-closed-source-policy.ts
  • packages/api/src/services/project-activity.ts
  • packages/api/src/services/project-auto-delete.ts
  • packages/api/src/services/project-auto-suspend.ts
🪛 markdownlint-cli2 (0.22.1)
.changeset/auto-delete-closed-source.md

[warning] 5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🪛 OpenGrep (1.22.0)
packages/lib/src/core/project-source-ref.ts

[ERROR] 43-43: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 47-47: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 55-55: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 59-59: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🔇 Additional comments (2)
packages/lib/src/core/domain.ts (1)

29-39: LGTM!

.changeset/auto-delete-closed-source.md (1)

1-5: LGTM!

Comment on lines +27 to +32
readContainerTaskSnapshot(project.projectDir, false).pipe(
Effect.map((snapshot) =>
activeAgents(snapshot.agents).length > 0 || snapshot.tasks.some((task) => task.kind === "agent")
),
Effect.catchAll(() => Effect.succeed(false))
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Не маскируйте сбой чтения снапшота как false для признака активного агента.

На Line 31 ошибка чтения состояния контейнера принудительно превращается в false. В контуре авто-удаления это даёт ложный вывод «агента нет» и может привести к удалению проекта при недоступности проверки. Для безопасного поведения ошибку нужно пробрасывать вверх (или явно трактовать как keep/unknown), чтобы оценка проекта завершалась без удаления.

💡 Proposed fix
 export const projectHasActiveAgent = (
   project: ProjectItem
 ) =>
   readContainerTaskSnapshot(project.projectDir, false).pipe(
-    Effect.map((snapshot) =>
-      activeAgents(snapshot.agents).length > 0 || snapshot.tasks.some((task) => task.kind === "agent")
-    ),
-    Effect.catchAll(() => Effect.succeed(false))
+    Effect.map(
+      (snapshot) =>
+        activeAgents(snapshot.agents).length > 0 || snapshot.tasks.some((task) => task.kind === "agent")
+    )
   )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/src/services/project-activity.ts` around lines 27 - 32, The code
masks readContainerTaskSnapshot failures by catching all errors and returning
false (via Effect.catchAll(() => Effect.succeed(false))), which makes the caller
think "no active agent" and can cause unsafe auto-deletion; change the behavior
in the pipeline that uses readContainerTaskSnapshot and activeAgents so errors
are propagated instead of swallowed — remove or modify the Effect.catchAll on
the readContainerTaskSnapshot pipeline (or replace it with an explicit
failure/unknown/keep result) so that failures from readContainerTaskSnapshot
surface to the caller (or return an explicit "unknown"/"keep" sentinel) and let
the higher-level evaluator decide rather than forcing false.

Comment on lines +1 to +57
import { describe, expect, it } from "vitest"

import {
decideProjectClosedSourceAction,
type ProjectClosedSourcePolicyInput
} from "../src/services/project-closed-source-policy.js"

const makeInput = (overrides: Partial<ProjectClosedSourcePolicyInput> = {}): ProjectClosedSourcePolicyInput => ({
sourceState: "closed",
hasActiveAgent: false,
hasLiveInteractiveSession: false,
...overrides
})

describe("project closed-source policy", () => {
it("deletes a closed-source project with no active work", () => {
expect(decideProjectClosedSourceAction(makeInput())).toEqual({ _tag: "Delete" })
})

it("keeps an open-source project", () => {
expect(decideProjectClosedSourceAction(makeInput({ sourceState: "open" }))).toEqual({
_tag: "Keep",
reason: "source-open-or-unknown"
})
})

it("keeps a project whose source state is unknown", () => {
expect(decideProjectClosedSourceAction(makeInput({ sourceState: "unknown" }))).toEqual({
_tag: "Keep",
reason: "source-open-or-unknown"
})
})

it("keeps a closed-source project while an agent is active", () => {
expect(decideProjectClosedSourceAction(makeInput({ hasActiveAgent: true }))).toEqual({
_tag: "Keep",
reason: "active-agent"
})
})

it("keeps a closed-source project while an interactive session is live", () => {
expect(decideProjectClosedSourceAction(makeInput({ hasLiveInteractiveSession: true }))).toEqual({
_tag: "Keep",
reason: "live-interactive-session"
})
})

it("prefers the active-agent reason over a live interactive session", () => {
expect(
decideProjectClosedSourceAction(makeInput({ hasActiveAgent: true, hasLiveInteractiveSession: true }))
).toEqual({ _tag: "Keep", reason: "active-agent" })
})

it("never deletes an open project even with no active work", () => {
expect(decideProjectClosedSourceAction(makeInput({ sourceState: "open" }))._tag).toBe("Keep")
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Добавьте property-based тест инварианта для политики удаления.

На Line 15-57 есть только примерные сценарии. Для .test.ts здесь нужен тест инварианта через fast-check, чтобы проверить правило удаления на всём пространстве входов, а не только на фиксированных кейсах.

💡 Proposed fix
 import { describe, expect, it } from "vitest"
+import fc from "fast-check"
@@
 describe("project closed-source policy", () => {
@@
   it("never deletes an open project even with no active work", () => {
     expect(decideProjectClosedSourceAction(makeInput({ sourceState: "open" }))._tag).toBe("Keep")
   })
+
+  it("preserves deletion invariant for all input combinations", () => {
+    fc.assert(
+      fc.property(
+        fc.constantFrom<"open" | "closed" | "unknown">("open", "closed", "unknown"),
+        fc.boolean(),
+        fc.boolean(),
+        (sourceState, hasActiveAgent, hasLiveInteractiveSession) => {
+          const result = decideProjectClosedSourceAction({
+            sourceState,
+            hasActiveAgent,
+            hasLiveInteractiveSession
+          })
+
+          return result._tag === "Delete"
+            ? sourceState === "closed" && !hasActiveAgent && !hasLiveInteractiveSession
+            : true
+        }
+      )
+    )
+  })
 })

As per coding guidelines **/*.test.{ts,tsx} requires fast-check property tests for invariants, and **/*.{test,spec}.{ts,tsx} requires invariant verification via fast-check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/tests/project-closed-source-policy.test.ts` around lines 1 - 57,
Add a fast-check property test that exhaustively verifies the
decideProjectClosedSourceAction invariant by generating arbitrary
ProjectClosedSourcePolicyInput values (use fast-check) and asserting the policy
rules: when sourceState === "closed" && !hasActiveAgent &&
!hasLiveInteractiveSession result._tag === "Delete"; when sourceState === "open"
|| sourceState === "unknown" result._tag === "Keep" with reason
"source-open-or-unknown"; when hasActiveAgent result._tag === "Keep" with reason
"active-agent" (and this should take precedence over hasLiveInteractiveSession
which yields "live-interactive-session"); import and use
decideProjectClosedSourceAction and the ProjectClosedSourcePolicyInput type in
the test file and keep the existing example tests.

Source: Coding guidelines

Comment on lines +20 to +21
* For GitLab projects `owner` carries the full namespace path (e.g. `group/subgroup`).
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Синхронизируйте контракт поля owner для GitLab.

На Line 20-21 задокументирован owner как namespace (пример group/subgroup), но на Line 105 возвращается gitlab.projectPath (namespace/repo). Это создаёт неоднозначный публичный контракт ProjectSourceRef для GitLab.

Предлагаемое исправление
-        owner: gitlab.projectPath,
+        owner: gitlab.namespace,

Also applies to: 104-106

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/src/core/project-source-ref.ts` around lines 20 - 21, The
ProjectSourceRef contract for GitLab is inconsistent: the docstring states owner
is the namespace (e.g. "group/subgroup") but the code sets owner to
gitlab.projectPath (namespace/repo). Update ProjectSourceRef so owner
consistently carries only the namespace and the repository name goes into a
separate field (e.g., repo or name), or adjust the docstring to match the
current behavior. Locate the ProjectSourceRef type/constructor and the place
assigning gitlab.projectPath (reference gitlab.projectPath and the owner
property) and change the assignment and/or documentation so owner = namespace
only and repository = repoName, ensuring any code consumers are updated to use
the new repo field.

Comment on lines +19 to +20
// INVARIANT: never throws on API/HTTP failure — unknown is returned so deletion is skipped
// COMPLEXITY: O(1) API call

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Проверка локальной реализации usecase:"
rg -n --type=ts 'fetchGithubSourceState|fetchProjectSourceState|PlatformError|catchAll|normalizeProjectSourceState' \
  packages/lib/src/usecases/project-source-state.ts

echo
echo "Проверка места, где делается collapse в unknown:"
rg -n --type=ts 'fetchProjectSourceState|catchAll\\(\\) => Effect\\.succeed\\("unknown"' \
  packages/api/src/services/project-auto-delete.ts

Repository: ProverCoderAI/docker-git

Length of output: 914


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== project-source-state.ts (lines 1-120) ==="
nl -ba packages/lib/src/usecases/project-source-state.ts | sed -n '1,120p'

echo
echo "=== project-auto-delete.ts: occurrences of unknown and catchAll ==="
rg -n --type=ts 'unknown|catchAll' packages/api/src/services/project-auto-delete.ts

echo
echo "=== project-auto-delete.ts (show around first unknown occurrence) ==="
UNKNOWN_LINE=$(rg -n --type=ts 'unknown' packages/api/src/services/project-auto-delete.ts | head -n 1 | cut -d: -f1)
if [ -n "$UNKNOWN_LINE" ]; then
  nl -ba packages/api/src/services/project-auto-delete.ts | sed -n "$((UNKNOWN_LINE-20)),$((UNKNOWN_LINE+40))p"
else
  echo "No 'unknown' occurrence found."
fi

echo
echo "=== project-auto-delete.ts: show catchAll blocks context ==="
# Print a small window around each catchAll
rg -n --type=ts 'catchAll\(' packages/api/src/services/project-auto-delete.ts | cut -d: -f1 | sort -u | while read -r ln; do
  echo "--- catchAll near line $ln ---"
  nl -ba packages/api/src/services/project-auto-delete.ts | sed -n "$((ln-20)),$((ln+40))p" | sed 's/^/ /'
done

Repository: ProverCoderAI/docker-git

Length of output: 175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== project-source-state.ts (lines 1-120) ==="
nl -ba packages/lib/src/usecases/project-source-state.ts | sed -n '1,120p'

echo
echo "=== project-auto-delete.ts: occurrences of unknown and catchAll ==="
rg -n --type=ts 'unknown|catchAll' packages/api/src/services/project-auto-delete.ts

echo
echo "=== project-auto-delete.ts (show around first unknown occurrence) ==="
UNKNOWN_LINE=$(rg -n --type=ts 'unknown' packages/api/src/services/project-auto-delete.ts | head -n 1 | cut -d: -f1)
if [ -n "${UNKNOWN_LINE:-}" ]; then
  nl -ba packages/api/src/services/project-auto-delete.ts | sed -n "$((UNKNOWN_LINE-20)),$((UNKNOWN_LINE+40))p"
else
  echo "No 'unknown' occurrence found."
fi

echo
echo "=== project-auto-delete.ts: show catchAll blocks context ==="
rg -n --type=ts 'catchAll\(' packages/api/src/services/project-auto-delete.ts | cut -d: -f1 | sort -u | while read -r ln; do
  echo "--- catchAll near line $ln ---"
  nl -ba packages/api/src/services/project-auto-delete.ts | sed -n "$((ln-20)),$((ln+40))p"
done

Repository: ProverCoderAI/docker-git

Length of output: 175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== project-source-state.ts: show around line ~15-80 ==="
sed -n '1,120p' packages/lib/src/usecases/project-source-state.ts | cat -n

echo
echo "=== project-auto-delete.ts: find unknown/catchAll ==="
rg -n --type=ts 'unknown|catchAll' packages/api/src/services/project-auto-delete.ts

echo
echo "=== project-auto-delete.ts: show around first unknown occurrence ==="
UNKNOWN_LINE=$(rg -n --type=ts 'unknown' packages/api/src/services/project-auto-delete.ts | head -n 1 | cut -d: -f1 || true)
if [ -n "${UNKNOWN_LINE:-}" ]; then
  START=$((UNKNOWN_LINE-30)); END=$((UNKNOWN_LINE+60))
  sed -n "${START},${END}p" packages/api/src/services/project-auto-delete.ts | cat -n
else
  echo "No 'unknown' occurrence found."
fi

echo
echo "=== project-auto-delete.ts: show around each catchAll occurrence ==="
while read -r ln; do
  START=$((ln-30)); END=$((ln+60))
  echo "--- catchAll window near line $ln ---"
  sed -n "${START},${END}p" packages/api/src/services/project-auto-delete.ts | cat -n
done < <(rg -n --type=ts 'catchAll\(' packages/api/src/services/project-auto-delete.ts | cut -d: -f1 | sort -u)

Repository: ProverCoderAI/docker-git

Length of output: 7061


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== project-auto-delete.ts: show around catchAll line 70-120 ==="
sed -n '60,120p' packages/api/src/services/project-auto-delete.ts | cat -n

echo
echo "=== project-auto-delete.ts: show around catchAll line 130-190 ==="
sed -n '130,190p' packages/api/src/services/project-auto-delete.ts | cat -n

echo
echo "=== Find runGhApiNullable implementation ==="
rg -n --type=ts 'function runGhApiNullable|const runGhApiNullable|runGhApiNullable\s*=' packages/lib/src/usecases/github-api-helpers.ts packages/lib/src/usecases/github-api-helpers.* 2>/dev/null || true
# Try likely path
if [ -f packages/lib/src/usecases/github-api-helpers.ts ]; then
  sed -n '1,220p' packages/lib/src/usecases/github-api-helpers.ts | cat -n
fi

echo
echo "=== Find normalizeProjectSourceState implementation ==="
rg -n --type=ts 'function normalizeProjectSourceState|const normalizeProjectSourceState|normalizeProjectSourceState\s*=' packages/lib/src/core/project-source-ref.ts packages/lib/src/core/project-source-ref.* 2>/dev/null || true
# Try likely path
if [ -f packages/lib/src/core/project-source-ref.ts ]; then
  sed -n '1,260p' packages/lib/src/core/project-source-ref.ts | cat -n
fi

Repository: ProverCoderAI/docker-git

Length of output: 12917


Уточнить инвариант в fetchGithubSourceState, чтобы он соответствовал контракту PlatformError

Инвариант never throws … unknown is returned в packages/lib/src/usecases/project-source-state.ts (стр. 19-20) шире реального поведения: fetchGithubSourceState всё ещё может завершиться ошибкой в error-channel (PlatformError), т.к. внутри нет Effect.catchAll (тип сигнатуры на это указывает). Коллапс к "unknown" происходит в packages/api/src/services/project-auto-delete.ts через Effect.catchAll(() => Effect.succeed("unknown" as const)), поэтому инвариант стоит переформулировать с учётом того, что "unknown" — это поведение оркестратора, а не самой usecase-функции.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/src/usecases/project-source-state.ts` around lines 19 - 20,
Инвариант в строках 19‑20 должен быть уточнён: замените формулировку "never
throws on API/HTTP failure — unknown is returned so deletion is skipped" на
указание, что fetchGithubSourceState может завершаться ошибкой в error‑channel
(PlatformError) и не выполняет локального преобразования ошибок в "unknown";
свёртка в "unknown" происходит в оркестраторе (например в проекте AutoDelete
через Effect.catchAll(() => Effect.succeed("unknown"))). Ссылаться в комментарии
явно на fetchGithubSourceState и на поведение обработчика в project-auto-delete
(Effect.catchAll), чтобы контракт был корректно понят при чтении кода.

Comment on lines +9 to +115
describe("parseProjectSourceRef", () => {
it("recovers a GitHub issue identity from issue-<n>", () => {
expect(parseProjectSourceRef("https://github.com/owner/repo.git", "issue-42")).toEqual({
provider: "github",
owner: "owner",
repo: "repo",
kind: "issue",
number: "42"
})
})

it("recovers a GitHub pull-request identity from refs/pull/<n>/head", () => {
expect(parseProjectSourceRef("https://github.com/owner/repo.git", "refs/pull/7/head")).toEqual({
provider: "github",
owner: "owner",
repo: "repo",
kind: "pull",
number: "7"
})
})

it("recovers a GitLab merge-request identity from refs/merge-requests/<n>/head", () => {
expect(parseProjectSourceRef("https://gitlab.com/group/repo.git", "refs/merge-requests/9/head")).toEqual({
provider: "gitlab",
owner: "group/repo",
repo: "repo",
kind: "pull",
number: "9"
})
})

it("recovers a GitLab issue identity from issue-<n>", () => {
expect(parseProjectSourceRef("https://gitlab.com/group/sub/repo.git", "issue-3")).toEqual({
provider: "gitlab",
owner: "group/sub/repo",
repo: "repo",
kind: "issue",
number: "3"
})
})

it("trims surrounding whitespace from the ref", () => {
expect(parseProjectSourceRef("https://github.com/owner/repo.git", " issue-1 ")?.number).toBe("1")
})

it("returns null for a plain branch ref", () => {
expect(parseProjectSourceRef("https://github.com/owner/repo.git", "main")).toBeNull()
})

it("returns null for an empty ref", () => {
expect(parseProjectSourceRef("https://github.com/owner/repo.git", " ")).toBeNull()
})

it("returns null when the repo URL is not a known provider", () => {
expect(parseProjectSourceRef("https://example.com/owner/repo.git", "issue-1")).toBeNull()
})

it("does not treat a GitHub merge-request style ref as a pull request", () => {
expect(parseProjectSourceRef("https://github.com/owner/repo.git", "refs/merge-requests/9/head")).toBeNull()
})
})

describe("normalizeProjectSourceState", () => {
it("maps GitHub open to open", () => {
expect(normalizeProjectSourceState("open")).toBe("open")
})

it("maps GitLab opened to open", () => {
expect(normalizeProjectSourceState("opened")).toBe("open")
})

it("maps closed to closed", () => {
expect(normalizeProjectSourceState("closed")).toBe("closed")
})

it("maps GitLab merged to closed", () => {
expect(normalizeProjectSourceState("merged")).toBe("closed")
})

it("maps GitLab locked to closed", () => {
expect(normalizeProjectSourceState("locked")).toBe("closed")
})

it("is case- and whitespace-insensitive", () => {
expect(normalizeProjectSourceState(" CLOSED ")).toBe("closed")
})

it("maps unrecognized values to unknown", () => {
expect(normalizeProjectSourceState("draft")).toBe("unknown")
})

it("maps null and undefined to unknown", () => {
expect(normalizeProjectSourceState(null)).toBe("unknown")
expect(normalizeProjectSourceState(undefined)).toBe("unknown")
})
})

describe("shouldDeleteForSourceState", () => {
it("deletes only when the source is closed", () => {
expect(shouldDeleteForSourceState("closed")).toBe(true)
})

it("never deletes for open or unknown", () => {
expect(shouldDeleteForSourceState("open")).toBe(false)
expect(shouldDeleteForSourceState("unknown")).toBe(false)
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Проверка использования fast-check в тестах:"
rg -n --type=ts 'from "fast-check"|fc\.property|fc\.assert'

echo
echo "2) Проверка данного файла на property-based проверки:"
rg -n --type=ts 'fc\.property|fc\.assert' packages/lib/tests/core/project-source-ref.test.ts

echo
echo "3) Проверка, что зависимость fast-check объявлена:"
fd -i 'package.json' -x rg -n '"fast-check"'

Repository: ProverCoderAI/docker-git

Length of output: 17894


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/lib/tests/core/project-source-ref.test.ts"

echo "1) fast-check usage in target file:"
rg -n --type=ts 'from "fast-check"|fc\.property|fc\.assert' "$FILE" || true

echo
echo "2) Show file header imports (first 40 lines):"
head -n 50 "$FILE"

echo
echo "3) fast-check declared in any package.json:"
fd -i 'package.json' . -x sh -c 'echo "---- $1"; rg -n "\"fast-check\"" "$1" || true' sh {} \
  | head -n 200

Repository: ProverCoderAI/docker-git

Length of output: 2010


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/lib/tests/core/project-source-ref.test.ts"

echo "1) fast-check usage in target file:"
rg -n --type=ts 'from "fast-check"|fc\.property|fc\.assert' "$FILE" || true

echo
echo "2) Show file header imports (first 40 lines):"
head -n 50 "$FILE"

echo
echo "3) fast-check declared in any package.json:"
fd -i 'package.json' . -x sh -c 'echo "---- $1"; rg -n "\"fast-check\"" "$1" || true' sh {} \
  | head -n 200

Repository: ProverCoderAI/docker-git

Length of output: 2010


Добавить fast-check property-based инварианты для project-source-ref
В packages/lib/tests/core/project-source-ref.test.ts используются только примерные vitest-тесты (нет fast-check/fc.property), при этом fast-check уже подключён в packages/lib. Добавить property-based проверки инвариантов для:

  • parseProjectSourceRef: пробелы вокруг ref не меняют number; валидные паттерны GitHub/GitLab дают соответствующий { provider, owner, repo, kind, number }; ветки/неизвестные паттерны (в т.ч. GitHub merge-request style для GitHub) дают null.
  • normalizeProjectSourceState: идемпотентность; нечувствительность к регистру/whitespace; маппинг {open, opened}→open, {closed, merged, locked}→closed, {null, undefined, прочее}→unknown.
  • shouldDeleteForSourceState: true тогда и только тогда, когда normalizeProjectSourceState(state) === "closed".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/tests/core/project-source-ref.test.ts` around lines 9 - 115, Add
fast-check property-based tests for parseProjectSourceRef,
normalizeProjectSourceState, and shouldDeleteForSourceState: write fc.property
cases that (1) assert trimming whitespace around ref does not change the parsed
.number for parseProjectSourceRef and that known GitHub/GitLab ref patterns
produce the expected object shape ({provider, owner, repo, kind, number}) while
branch-like or unknown patterns (including a GitHub repo with
refs/merge-requests/... ) return null; (2) assert normalizeProjectSourceState is
idempotent, case- and whitespace-insensitive, maps "open"/"opened"→"open",
"closed"/"merged"/"locked"→"closed", and null/undefined/other→"unknown"; and (3)
assert shouldDeleteForSourceState(state) === (normalizeProjectSourceState(state)
=== "closed") for arbitrary string/null/undefined inputs; use
parseProjectSourceRef, normalizeProjectSourceState, and
shouldDeleteForSourceState identifiers and fc.property with appropriate
generators (fc.string, fc.option, fc.oneof, and custom generators for valid ref
patterns) to implement these invariants.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant