Skip to content

feat: implement semantic conflict detection in workspace merge#860

Merged
Aureliolo merged 7 commits intomainfrom
feat/semantic-conflict-detection
Mar 27, 2026
Merged

feat: implement semantic conflict detection in workspace merge#860
Aureliolo merged 7 commits intomainfrom
feat/semantic-conflict-detection

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add hybrid AST + LLM semantic conflict detection after workspace merges, detecting removed-name references, signature mismatches, duplicate definitions, and import conflicts that git's textual merge cannot catch
  • New SemanticAnalyzer protocol with AstSemanticAnalyzer (deterministic), LlmSemanticAnalyzer (optional deep review), and CompositeSemanticAnalyzer (chains both)
  • Integrate into merge flow via PlannerWorktreeStrategy using merge-base for cross-branch detection, with escalation handling in MergeOrchestrator (HUMAN stops, REVIEW_AGENT continues)

Test plan

  • 37 unit tests for AST checks (4 check functions, positive/negative/edge cases)
  • 10 unit tests for analyzer classes (protocol compliance, deduplication, error isolation)
  • 20 unit tests for LLM analyzer + prompt parsing (tool calls, content fallback, retry)
  • 7 tests for merge orchestrator semantic escalation and MergeResult validator
  • 4 integration tests with real git repos (renamed function detection, clean merge, duplicates, disabled mode)
  • All 10779 existing tests pass, mypy clean, ruff clean
  • Pre-reviewed by 11 agents, 18 findings addressed

Related issues

Closes #611

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Walkthrough

Adds post-merge semantic conflict detection. Introduces a SemanticAnalyzer protocol with AstSemanticAnalyzer, LlmSemanticAnalyzer, and CompositeSemanticAnalyzer; AST-based semantic checks and LLM prompt/response utilities; a frozen SemanticAnalysisConfig model; a new SemanticAnalysisError exception; workspace observability events; secure path validation and base-source retrieval; git_worktree runs semantic analysis after successful textual merges and may attach MergeResult.semantic_conflicts; MergeOrchestrator updates escalation and can stop merging on semantic conflicts; extends MergeConflict/models and adds unit and integration tests.

Suggested labels

autorelease: tagged

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: implementing semantic conflict detection in workspace merges, which is the core objective of this PR.
Description check ✅ Passed The description is directly related to the changeset, providing a comprehensive summary of the semantic conflict detection implementation including the analyzer protocol, AST checks, LLM integration, and test coverage.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #611: hybrid AST+LLM semantic conflict detection, SemanticAnalyzer protocol with three implementations, integration into merge flow with escalation handling, and comprehensive test coverage across unit and integration tests.
Out of Scope Changes check ✅ Passed All changes are tightly scoped to implementing semantic conflict detection and supporting infrastructure (config, models, error types, observability events). No unrelated modifications or scope creep detected.
Docstring Coverage ✅ Passed Docstring coverage is 73.58% which is sufficient. The required threshold is 40.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 21:38 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a semantic conflict detection framework to identify logical inconsistencies after successful textual merges. It implements AST-based checks for Python files—detecting removed references, signature mismatches, duplicate definitions, and import conflicts—and adds an LLM-based analyzer for complex logical reviews. The merge orchestration logic has been updated to integrate these checks and respect defined escalation strategies. Feedback includes a critical correction for a Python 2-style exception handling syntax and a performance recommendation to parallelize sequential git operations using asyncio.

for file_path in files:
try:
contents[file_path] = (root / file_path).read_text(encoding="utf-8")
except FileNotFoundError, PermissionError, OSError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This except block uses Python 2 syntax (a comma to separate exception types). For compatibility with Python 3, the exceptions should be grouped in a tuple.

Suggested change
except FileNotFoundError, PermissionError, OSError:
except (FileNotFoundError, PermissionError, OSError):

Comment on lines +649 to +658
sources: dict[str, str] = {}
for file_path in files:
rc, stdout, _ = await self._run_git(
"show",
f"{base_sha}:{file_path}",
log_event=WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
)
if rc == 0:
sources[file_path] = stdout
return sources
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This loop executes git show sequentially for each file, which can be slow if there are many changed files. This can be parallelized to improve performance.

While I see this is noted as a future improvement in issue #857, addressing it now in this new function would be beneficial. You can use asyncio.gather to run the _run_git calls concurrently.

        tasks = [
            self._run_git(
                "show",
                f"{base_sha}:{file_path}",
                log_event=WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
            )
            for file_path in files
        ]
        results = await asyncio.gather(*tasks)

        sources: dict[str, str] = {}
        for file_path, result in zip(files, results):
            rc, stdout, _ = result
            if rc == 0:
                sources[file_path] = stdout
        return sources

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 3245d5e.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 71 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.16%. Comparing base (d6d8a06) to head (3245d5e).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/engine/workspace/semantic_checks.py 74.85% 27 Missing and 15 partials ⚠️
src/synthorg/engine/workspace/semantic_llm.py 60.00% 16 Missing and 2 partials ⚠️
src/synthorg/engine/workspace/config.py 72.72% 4 Missing and 2 partials ⚠️
src/synthorg/engine/workspace/semantic_analyzer.py 82.14% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #860      +/-   ##
==========================================
- Coverage   92.31%   92.16%   -0.15%     
==========================================
  Files         585      589       +4     
  Lines       30745    31082     +337     
  Branches     2958     3026      +68     
==========================================
+ Hits        28381    28647     +266     
- Misses       1870     1921      +51     
- Partials      494      514      +20     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/workspace/git_worktree.py`:
- Around line 714-730: The code calls _get_base_sources for every path in
changed_files while holding PlannerWorktreeStrategy._lock, causing many git show
calls before the analyzer's max_files/file_extensions filter
(AstSemanticAnalyzer._filter_files) runs; change the flow to pre-filter
changed_files by applying the same file_extensions and max_files rules (or
move/replicate _filter_files logic) immediately after _get_changed_files and
before calling _get_base_sources so only up to max_files and matching extensions
are passed into _get_base_sources, or alternatively make _get_base_sources
return lazily-loaded base contents so the analyzer can enforce
max_files/file_extensions without serializing expensive git calls.

In `@src/synthorg/engine/workspace/semantic_analyzer.py`:
- Around line 83-92: Replace the parenthesized exception tuple with the PEP 758
multi-except pipe syntax in the try/except that reads files into the sources
dict: change the except clause handling FileNotFoundError, PermissionError,
OSError (the block that logs WORKSPACE_SEMANTIC_ANALYSIS_FAILED for
workspace_id/file) to use "except FileNotFoundError | PermissionError | OSError
as exc:" so the read_text call and logger.debug remain unchanged.

In `@src/synthorg/engine/workspace/semantic_checks.py`:
- Around line 268-283: _compare_signatures currently only records positional
min/max and removed names, so adding required keyword-only params (e.g., def
f(x) -> def f(x, *, y)) is missed; update the signature diff creation (around
where _function_min_args/_function_max_args/_function_param_names are used for
base_node and merged_node) to also compute and store required keyword-only
parameter names from merged_node (kwonly args with no default) and record
per-parameter kind changes (compare each param's kind between base_node and
merged_node) into the _SigInfo structure; then ensure _SigInfo includes these
new fields so _check_call_compat() can validate new required kw-only params and
parameter-kind changes when checking calls.
- Around line 56-62: _all_name_references currently collects every ast.Name,
causing false positives/negatives; change it to collect only load-context names
(filter where isinstance(node.ctx, ast.Load)) and then perform simple
scope/binding resolution inside the same module before returning (exclude names
that are bound in the module via ast.Assign, ast.AnnAssign, ast.AugAssign,
ast.FunctionDef args, ast.ClassDef names, ast.Import/ImportFrom aliases, and
comprehension targets). Update the related logic used by
check_removed_references (and the duplicate logic around the other occurrence at
the area noted ~196-203) to use this Load-only + binding-exclusion helper so
references from the same module aren’t treated as removed and only free/used
names drive merge escalation.

In `@src/synthorg/engine/workspace/semantic_llm_prompt.py`:
- Around line 173-185: The _parse_conflicts_from_args function currently raises
TypeError when the "conflicts" argument is not a list; change this to raise
ValueError to match the exception contract described for
parse_tool_call_response and keep callers consistent. Update the raise statement
in _parse_conflicts_from_args to raise ValueError(msg) (leave the logger.warning
call and msg construction unchanged) so the function signature and error flows
align with parse_tool_call_response.

In `@src/synthorg/engine/workspace/semantic_llm.py`:
- Around line 218-223: The except clause in semantic_llm.py redundantly lists
FileNotFoundError and PermissionError along with OSError; replace the multi-type
clause with a single except OSError: to catch all I/O-related errors cleanly
(keep the existing handler that calls logger.debug with
WORKSPACE_SEMANTIC_ANALYSIS_FAILED, file=file_path, reason="read_error"). Ensure
you update the except block that currently reads "except FileNotFoundError,
PermissionError, OSError:" to "except OSError:" so only the superclass is
caught.

In `@tests/integration/engine/test_workspace_semantic_integration.py`:
- Around line 253-292: Test is nondeterministic because both branches rewrite
the same hunk in "helpers.py" so merges often produce textual conflicts and skip
semantic checks; change the fixture commits so each branch touches disjoint
hunks that auto-merge and produce duplicate definitions. Concretely, in the test
code that calls _commit_file for ws_a and ws_b, make the two commits modify
different parts of the file (e.g., insert Agent A's process() in a top section
and Agent B's process() in a bottom section or add distinct surrounding
comments/marker lines) so git can perform a clean textual merge; keep the file
name "helpers.py", the workspace setup calls (WorkspaceRequest/use of ws_a and
ws_b), and the later merge_workspace result_b semantic_conflicts assertions
intact so check_duplicate_definitions() is exercised. Ensure the changes still
result in two process definitions after merging so the semantic_conflicts
assertion triggers.

In `@tests/unit/engine/test_semantic_checks.py`:
- Around line 1-6: The module docstring incorrectly states that tests are
"parametrized tests" while the test suite uses individual test functions; update
the module-level docstring in test_semantic_checks.py to remove the reference to
parametrization and instead state that individual test methods (e.g., the
various test_* functions in this module) cover positive detections and negative
(no-conflict) cases, and remove or reword any mention of
`@pytest.mark.parametrize` so the description matches the actual implementation.

In `@tests/unit/engine/test_workspace_models.py`:
- Around line 397-417: Add the missing pytest marker to the test method:
decorate test_total_semantic_conflicts with `@pytest.mark.unit` (ensure pytest is
imported if not) so it matches other tests in the file; locate the
test_total_semantic_conflicts function in the WorkspaceGroupResult tests and add
the `@pytest.mark.unit` decorator immediately above the def line.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 341634c3-9087-4506-a6e3-de11c055c463

📥 Commits

Reviewing files that changed from the base of the PR and between 82d4b01 and acedf11.

📒 Files selected for processing (20)
  • src/synthorg/engine/errors.py
  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/git_worktree.py
  • src/synthorg/engine/workspace/merge.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_checks.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/observability/events/workspace.py
  • tests/integration/engine/test_workspace_semantic_integration.py
  • tests/unit/engine/conftest.py
  • tests/unit/engine/test_semantic_analyzer.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_semantic_llm.py
  • tests/unit/engine/test_semantic_llm_prompt.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • tests/unit/engine/test_workspace_merge.py
  • tests/unit/engine/test_workspace_models.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use PEP 758 except syntax: except A, B: (no parentheses) instead of except (A, B): -- ruff enforces this on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task for structured concurrency
Keep functions under 50 lines and files under 800 lines
Line length must be 88 characters as enforced by ruff
Validate explicitly at system boundaries (user input, external APIs, config files); never silently swallow errors

Files:

  • src/synthorg/engine/errors.py
  • tests/unit/engine/test_workspace_models.py
  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/merge.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • tests/unit/engine/conftest.py
  • tests/unit/engine/test_semantic_llm.py
  • src/synthorg/observability/events/workspace.py
  • src/synthorg/engine/workspace/config.py
  • tests/unit/engine/test_semantic_analyzer.py
  • src/synthorg/engine/workspace/git_worktree.py
  • tests/unit/engine/test_semantic_checks.py
  • src/synthorg/engine/workspace/models.py
  • tests/unit/engine/test_semantic_llm_prompt.py
  • tests/integration/engine/test_workspace_semantic_integration.py
  • tests/unit/engine/test_workspace_merge.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_checks.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import and use logger: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging, logging.getLogger(), or print() in application code; only use the structured logger from synthorg.observability
Always use the logger variable name logger (not _logger, not log)
Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api); import directly and always use structured kwargs: logger.info(EVENT, key=value) never string formatting
All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with model_copy(update=...)) for runtime state that evolves
Use Pydantic v2 with @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields instead of manual validators
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code
Validate at system boundaries: user input, external APIs, config files; handle errors explicitly, never silently swallow

Files:

  • src/synthorg/engine/errors.py
  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/merge.py
  • src/synthorg/observability/events/workspace.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/git_worktree.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_checks.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow to categorize tests
Maintain 80% minimum code coverage as enforced in CI
Use asyncio_mode = 'auto' for async tests; no manual @pytest.mark.asyncio needed
Use @pytest.mark.parametrize for testing similar cases instead of test duplication
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, tests, or config examples; use generic names: example-provider, example-large-001, test-provider, test-small-001, large/medium/small
Use Hypothesis with @given + @settings for property-based testing; avoid flaky timing-based tests by mocking time.monotonic() and asyncio.sleep() or using asyncio.Event().wait() for indefinite blocks

Files:

  • tests/unit/engine/test_workspace_models.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • tests/unit/engine/conftest.py
  • tests/unit/engine/test_semantic_llm.py
  • tests/unit/engine/test_semantic_analyzer.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_semantic_llm_prompt.py
  • tests/integration/engine/test_workspace_semantic_integration.py
  • tests/unit/engine/test_workspace_merge.py
🧠 Learnings (20)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.848Z
Learning: Git merge strategy: squash merge -- PR body becomes the squash commit message on main; preserve existing `Closes `#NNN`` trailers in PR body; add `Release-As: X.Y.Z` trailer as final paragraph for version bumping
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_checks.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and always use structured kwargs: `logger.info(EVENT, key=value)` never string formatting

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.) — import directly

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/engine/test_semantic_analyzer.py
🔇 Additional comments (26)
src/synthorg/engine/errors.py (1)

90-92: LGTM!

The new SemanticAnalysisError exception follows the established workspace error hierarchy pattern and is consistent with sibling exceptions like WorkspaceMergeError and WorkspaceCleanupError.

src/synthorg/engine/workspace/models.py (3)

92-95: LGTM!

The description field with default="" maintains backward compatibility with existing textual conflict creation (which doesn't provide descriptions) while allowing semantic conflicts to include meaningful descriptions.


133-136: LGTM!

The semantic_conflicts field and its validator correctly enforce the design constraint that semantic conflicts can only exist on successful merges (since semantic analysis runs post-merge). The implementation aligns with the control flow in git_worktree.py where semantic analysis only executes when rc == 0.

Also applies to: 150-152


194-201: LGTM!

The total_semantic_conflicts computed field follows the established pattern of total_conflicts and correctly aggregates semantic conflicts across all merge results.

tests/unit/engine/conftest.py (1)

359-359: LGTM!

The semantic_conflicts parameter addition follows the established pattern of the helper function, maintaining backward compatibility with a default empty tuple while enabling tests to construct MergeResult instances with semantic conflicts.

Also applies to: 375-375

src/synthorg/engine/workspace/merge.py (1)

138-149: LGTM!

The semantic conflict handling correctly mirrors the textual conflict pattern:

  • Sets escalation via model_copy to maintain immutability
  • Appends result before checking break condition (ensuring result is recorded)
  • Breaks on HUMAN escalation, allowing REVIEW_AGENT to continue
  • Skips cleanup when breaking, preserving the workspace for human review
tests/unit/engine/test_workspace_models.py (1)

419-456: LGTM!

The TestMergeResultSemanticConflicts class properly tests both validation scenarios:

  • Rejects semantic_conflicts when success=False
  • Accepts semantic_conflicts when success=True

The class-level pytestmark = pytest.mark.unit correctly applies the marker to all methods in the class.

src/synthorg/engine/workspace/__init__.py (1)

9-9: LGTM!

The semantic analysis components are correctly exported with proper alphabetical ordering in __all__. The exclusion of LlmSemanticAnalyzer from the public API suggests it's intended for internal use via CompositeSemanticAnalyzer, which is a reasonable encapsulation choice.

Also applies to: 26-30, 36-37, 43-44

tests/unit/engine/test_workspace_git_worktree.py (3)

260-267: LGTM!

The mock sequence correctly reflects the updated git command flow: checkout → pre-merge rev-parse (for semantic analysis base) → merge → post-merge rev-parse. The comments clearly distinguish between the two rev-parse calls.


289-296: LGTM!

The conflict test correctly includes the pre-merge rev-parse in the mock sequence. Even when merge fails, the pre-merge SHA capture still occurs before the merge attempt.


339-346: LGTM!

Both the abort failure and rev-parse failure tests correctly include the pre-merge rev-parse mock. The test_merge_revparse_failure_raises test specifically validates that the post-merge rev-parse failure (not pre-merge) raises the expected error.

Also applies to: 366-372

src/synthorg/engine/workspace/config.py (2)

9-51: LGTM!

The SemanticAnalysisConfig follows all coding guidelines:

  • Frozen Pydantic model with extra="forbid"
  • Google-style docstring with attributes
  • Proper field validation constraints (ge, le, gt)
  • Uses NotBlankStr | None for optional identifier field
  • Safe defaults (enabled=False)

89-92: LGTM!

The semantic_analysis field correctly uses default_factory=SemanticAnalysisConfig for the nested frozen model, following the established pattern for other nested configs like planner_worktrees in WorkspaceIsolationConfig.

tests/unit/engine/test_semantic_llm.py (1)

1-194: LGTM!

Well-structured test suite covering key scenarios for LlmSemanticAnalyzer: constructor validation, conflict parsing, filtering, provider errors, and retry behavior. The tests use appropriate mocking patterns and comply with test guidelines (module-level pytest.mark.unit, generic model names).

src/synthorg/observability/events/workspace.py (1)

25-31: LGTM!

New semantic analysis event constants follow the established naming convention and are properly typed with Final[str].

tests/unit/engine/test_semantic_analyzer.py (1)

1-260: LGTM!

Comprehensive test coverage for both AstSemanticAnalyzer and CompositeSemanticAnalyzer. Tests verify protocol compliance, edge cases (empty inputs, file read errors, extension filtering), and composite behavior (aggregation, deduplication, fault isolation).

tests/unit/engine/test_semantic_checks.py (1)

7-388: LGTM!

Thorough test coverage for all four semantic check functions. Tests cover positive detections (removed references, signature changes, duplicates, import conflicts), negative cases (clean merges), and edge cases (empty sources, syntax errors in input).

tests/unit/engine/test_workspace_merge.py (1)

391-447: LGTM!

New tests correctly verify semantic conflict escalation behavior: HUMAN escalation stops on first semantic conflict (single result), while REVIEW_AGENT continues merging (two results). The assertions properly check both semantic_conflicts content and escalation field values.

tests/unit/engine/test_semantic_llm_prompt.py (1)

1-188: LGTM!

Comprehensive test coverage for prompt building and response parsing. Tests verify tool schema structure, message roles and content, and parsing behavior including error cases (unparseable JSON, empty responses, invalid conflict items).

src/synthorg/engine/workspace/semantic_llm_prompt.py (3)

1-83: LGTM!

Well-structured module with pure functions for prompt construction. The tool definition schema properly defines required fields and the system message provides clear instructions for semantic conflict detection.


86-142: LGTM!

Message builders correctly construct system and user messages with appropriate roles and structured content including diff summary and file contents.


204-231: LGTM!

Content fallback parsing handles both raw JSON and fenced code blocks with proper error logging before raising exceptions.

src/synthorg/engine/workspace/semantic_llm.py (4)

38-72: LGTM!

Constructor properly validates the model parameter and lazily imports SemanticAnalysisConfig for default config creation.


74-138: LGTM!

The analyze method correctly filters files by extension, respects max_files limit, and builds appropriate messages for LLM review. Logging at method entry follows observability guidelines.


140-206: LGTM!

Retry logic properly handles parse failures with configurable retries, logs at appropriate levels (DEBUG for intermediate failures, WARNING for exhaustion), and returns empty tuple as a safe fallback. Success path logs completion with conflict count.


227-239: LGTM!

Helper function _build_diff_summary correctly identifies new vs modified files by comparing against base sources.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/workspace/semantic_llm_prompt.py`:
- Around line 195-199: The MergeConflict constructor call is passing
description=description but the MergeConflict model (class MergeConflict) does
not define a description field; fix this by making the call and model
consistent: either add a description attribute to the MergeConflict model
definition (class MergeConflict) in models.py and migrate any
validation/serialization accordingly, or remove description=description from the
construction and instead assign the semantic text to the existing field on
MergeConflict (e.g., message/details/metadata—use the exact field name defined
in the class) so the semantic details are preserved in the payload.
- Around line 177-193: The loop over raw_conflicts (and the handling around
conflicts: list[MergeConflict]) currently silently skips malformed items;
instead enforce strict validation by detecting non-dict items or dicts missing
required fields (file_path/description), log a clear error via logger with
WORKSPACE_SEMANTIC_ANALYSIS_FAILED and a reason like "malformed_conflict_item",
and raise a ValueError so the caller/retry loop handles provider output errors;
update the parsing in the block that iterates raw_conflicts (and any use of
MergeConflict) to either fail fast on the first malformed entry or collect
malformed counts and raise if any exist, rather than continuing silently.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 75356b68-cb17-4882-a2cd-14bdb6e71bbc

📥 Commits

Reviewing files that changed from the base of the PR and between acedf11 and b9448b7.

📒 Files selected for processing (1)
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use PEP 758 except syntax: except A, B: (no parentheses) instead of except (A, B): -- ruff enforces this on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task for structured concurrency
Keep functions under 50 lines and files under 800 lines
Line length must be 88 characters as enforced by ruff
Validate explicitly at system boundaries (user input, external APIs, config files); never silently swallow errors

Files:

  • src/synthorg/engine/workspace/semantic_llm_prompt.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import and use logger: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging, logging.getLogger(), or print() in application code; only use the structured logger from synthorg.observability
Always use the logger variable name logger (not _logger, not log)
Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api); import directly and always use structured kwargs: logger.info(EVENT, key=value) never string formatting
All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with model_copy(update=...)) for runtime state that evolves
Use Pydantic v2 with @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields instead of manual validators
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code
Validate at system boundaries: user input, external APIs, config files; handle errors explicitly, never silently swallow

Files:

  • src/synthorg/engine/workspace/semantic_llm_prompt.py
🧠 Learnings (2)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm_prompt.py

Comment on lines +177 to +193
raw_conflicts = arguments.get("conflicts", [])
if not isinstance(raw_conflicts, list):
msg = f"Expected list of conflicts, got {type(raw_conflicts).__name__}"
logger.warning(
WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
reason="invalid_conflicts_type",
error=msg,
)
raise ValueError(msg) # noqa: TRY004 -- must be ValueError for retry loop

conflicts: list[MergeConflict] = []
for item in raw_conflicts:
if not isinstance(item, dict):
continue
file_path = item.get("file_path", "")
description = item.get("description", "")
if file_path and description:
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

Do not silently skip malformed conflict items from provider output.

Lines 189-193 quietly continue on invalid entries and missing fields. This can return partial/empty results for malformed tool output instead of triggering retry/error handling, causing false negatives in semantic conflict detection.

♻️ Suggested strict parsing change
 def _parse_conflicts_from_args(
     arguments: dict[str, Any],
 ) -> tuple[MergeConflict, ...]:
     """Parse conflicts from tool call arguments dict."""
-    raw_conflicts = arguments.get("conflicts", [])
+    if "conflicts" not in arguments:
+        msg = "Missing required key: conflicts"
+        logger.warning(
+            WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
+            reason="missing_conflicts_key",
+            error=msg,
+        )
+        raise ValueError(msg)
+
+    raw_conflicts = arguments["conflicts"]
     if not isinstance(raw_conflicts, list):
         msg = f"Expected list of conflicts, got {type(raw_conflicts).__name__}"
         logger.warning(
             WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
             reason="invalid_conflicts_type",
             error=msg,
         )
         raise ValueError(msg)  # noqa: TRY004 -- must be ValueError for retry loop

     conflicts: list[MergeConflict] = []
-    for item in raw_conflicts:
+    for index, item in enumerate(raw_conflicts):
         if not isinstance(item, dict):
-            continue
-        file_path = item.get("file_path", "")
-        description = item.get("description", "")
-        if file_path and description:
-            conflicts.append(
-                MergeConflict(
-                    file_path=file_path,
-                    conflict_type=ConflictType.SEMANTIC,
-                    description=description,
-                ),
-            )
+            msg = f"Conflict at index {index} is not an object"
+            logger.warning(
+                WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
+                reason="invalid_conflict_item_type",
+                error=msg,
+            )
+            raise ValueError(msg)
+        file_path = item.get("file_path")
+        description = item.get("description")
+        if not isinstance(file_path, str) or not file_path.strip():
+            msg = f"Conflict at index {index} has invalid file_path"
+            logger.warning(
+                WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
+                reason="invalid_conflict_file_path",
+                error=msg,
+            )
+            raise ValueError(msg)
+        if not isinstance(description, str) or not description.strip():
+            msg = f"Conflict at index {index} has invalid description"
+            logger.warning(
+                WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
+                reason="invalid_conflict_description",
+                error=msg,
+            )
+            raise ValueError(msg)
+        conflicts.append(
+            MergeConflict(
+                file_path=file_path,
+                conflict_type=ConflictType.SEMANTIC,
+                description=description,
+            ),
+        )
     return tuple(conflicts)

As per coding guidelines, "Validate explicitly at system boundaries (user input, external APIs, config files); never silently swallow errors."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/workspace/semantic_llm_prompt.py` around lines 177 - 193,
The loop over raw_conflicts (and the handling around conflicts:
list[MergeConflict]) currently silently skips malformed items; instead enforce
strict validation by detecting non-dict items or dicts missing required fields
(file_path/description), log a clear error via logger with
WORKSPACE_SEMANTIC_ANALYSIS_FAILED and a reason like "malformed_conflict_item",
and raise a ValueError so the caller/retry loop handles provider output errors;
update the parsing in the block that iterates raw_conflicts (and any use of
MergeConflict) to either fail fast on the first malformed entry or collect
malformed counts and raise if any exist, rather than continuing silently.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/workspace/config.py`:
- Around line 27-30: The file_extensions Field on SemanticAnalysisConfig
currently accepts empty or malformed values (e.g. "" or empty tuple) which
breaks downstream path.endswith checks; add a pydantic validator on
SemanticAnalysisConfig.file_extensions that (1) rejects an empty tuple, and (2)
rejects any extension that is blank or malformed (e.g. empty string or not
starting with '.' and/or contains whitespace/path separators), raising
ValueError with a clear message; update the class-level validator (using
`@validator`("file_extensions") or `@root_validator`) to perform these checks so
invalid configs are rejected at load time.

In `@src/synthorg/engine/workspace/semantic_analyzer.py`:
- Around line 99-107: The except block in the semantic analyzer should be
simplified to catch OSError only and use the PEP 758 syntax without parentheses:
replace the current "except (FileNotFoundError, PermissionError, OSError) as
exc:" with "except OSError as exc:" in the function that reads sources so the
logger.debug call that emits WORKSPACE_SEMANTIC_ANALYSIS_FAILED (including
workspace_id, file=file_path, reason="read_error", error=...) remains unchanged;
this removes redundant exception types and fixes the except syntax per
guidelines.

In `@src/synthorg/engine/workspace/semantic_llm.py`:
- Around line 274-281: Change the except clause to use the PEP 758 form and
catch only OSError (since FileNotFoundError and PermissionError are subclasses):
replace the current "except (FileNotFoundError, PermissionError, OSError) as
exc:" with "except OSError as exc:" in the block around logger.warning (using
WORKSPACE_SEMANTIC_ANALYSIS_FAILED and file_path) so the handler remains the
same but adheres to the required except syntax.

In `@tests/unit/engine/test_semantic_checks.py`:
- Around line 29-388: Collapse repetitive test cases into parametrized tests:
for the removed-reference tests, replace individual functions like
test_function_removed_and_referenced, test_class_removed_and_referenced,
test_constant_removed_and_referenced, test_removed_but_not_referenced,
test_name_still_present_no_conflict, test_empty_sources_no_conflict, and
test_syntax_error_in_source_skipped with a single `@pytest.mark.parametrize` table
(each row contains base_sources, merged_sources, expected_count or predicate,
and an optional highlight string) and one test that calls
check_removed_references and asserts accordingly; do the same for signature
tests (group test_param_removed_callers_break,
test_required_param_added_callers_break, test_default_param_added_no_break,
test_no_callers_no_conflict, test_keyword_arg_removed_detected,
test_varargs_accepts_extra_positional, test_signature_unchanged_no_conflict)
into one parametrized test that calls check_signature_changes; similarly
collapse duplicate-definition tests (test_duplicate_function_same_file,
test_duplicate_class_same_file, test_no_duplicates_no_conflict,
test_nested_same_name_not_flagged, test_empty_sources,
test_syntax_error_skipped) into a parametrize for check_duplicate_definitions,
and import-conflict tests (test_import_of_removed_name, test_import_still_valid,
test_import_star_not_flagged, test_class_removed_import_breaks,
test_empty_sources) into a parametrize for check_import_conflicts; keep
references to the target functions check_removed_references,
check_signature_changes, check_duplicate_definitions, and check_import_conflicts
so reviewers can find where to change assertions and test data.

In `@tests/unit/engine/test_semantic_llm.py`:
- Around line 83-89: The tests patch _read_file_contents with a
mapping-returning value, but semantic_llm._read_file_contents(file_path,
workspace_id) should return a string for a single file; update the patches in
tests (around test_semantic_llm, lines exercising analyzer.analyze and calls
using merged_content) to use a side_effect or per-file return that returns the
file's string content keyed by the file_path argument (i.e., return "def foo():
..." for "utils.py") instead of returning {"utils.py": "..."} so merged_sources
keeps the correct shape and prompt construction is exercised; apply the same
change to the other occurrences noted (lines ~105-111, ~141-147, ~174-180).

In `@tests/unit/engine/test_workspace_models.py`:
- Around line 421-457: Add a negative test that directly exercises the
MergeConflict.description validator: attempt to construct a MergeConflict (or a
MergeResult containing one) with conflict_type=ConflictType.SEMANTIC and an
empty or whitespace description and assert it raises ValidationError (matching
an appropriate message about non-empty description). Reference the MergeConflict
class and its description field and/or the MergeResult.semantic_conflicts tuple
to locate where to add the test.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: de784ee1-4b09-43e3-b77b-10f62856dafa

📥 Commits

Reviewing files that changed from the base of the PR and between b9448b7 and 85be92c.

📒 Files selected for processing (16)
  • CLAUDE.md
  • docs/design/engine.md
  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/git_worktree.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_checks.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • tests/integration/engine/test_workspace_semantic_integration.py
  • tests/unit/engine/test_semantic_analyzer.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_semantic_llm.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • tests/unit/engine/test_workspace_models.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use PEP 758 except syntax: except A, B: (no parentheses) instead of except (A, B): -- ruff enforces this on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task for structured concurrency
Keep functions under 50 lines and files under 800 lines
Line length must be 88 characters as enforced by ruff
Validate explicitly at system boundaries (user input, external APIs, config files); never silently swallow errors

Files:

  • src/synthorg/engine/workspace/__init__.py
  • tests/unit/engine/test_workspace_models.py
  • tests/unit/engine/test_semantic_analyzer.py
  • tests/unit/engine/test_semantic_llm.py
  • tests/unit/engine/test_semantic_checks.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/models.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • tests/integration/engine/test_workspace_semantic_integration.py
  • src/synthorg/engine/workspace/git_worktree.py
  • src/synthorg/engine/workspace/semantic_checks.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import and use logger: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging, logging.getLogger(), or print() in application code; only use the structured logger from synthorg.observability
Always use the logger variable name logger (not _logger, not log)
Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api); import directly and always use structured kwargs: logger.info(EVENT, key=value) never string formatting
All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with model_copy(update=...)) for runtime state that evolves
Use Pydantic v2 with @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields instead of manual validators
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code
Validate at system boundaries: user input, external APIs, config files; handle errors explicitly, never silently swallow

Files:

  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/git_worktree.py
  • src/synthorg/engine/workspace/semantic_checks.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow to categorize tests
Maintain 80% minimum code coverage as enforced in CI
Use asyncio_mode = 'auto' for async tests; no manual @pytest.mark.asyncio needed
Use @pytest.mark.parametrize for testing similar cases instead of test duplication
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, tests, or config examples; use generic names: example-provider, example-large-001, test-provider, test-small-001, large/medium/small
Use Hypothesis with @given + @settings for property-based testing; avoid flaky timing-based tests by mocking time.monotonic() and asyncio.sleep() or using asyncio.Event().wait() for indefinite blocks

Files:

  • tests/unit/engine/test_workspace_models.py
  • tests/unit/engine/test_semantic_analyzer.py
  • tests/unit/engine/test_semantic_llm.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • tests/integration/engine/test_workspace_semantic_integration.py
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS read the relevant design specification page in docs/design/ before implementing any feature or planning any issue; design spec is the starting point for architecture, data models, and behavior; alert user and explain if implementation deviates from spec; update the spec page when deviations are approved

Files:

  • docs/design/engine.md
🧠 Learnings (40)
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_checks.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/engine/test_workspace_models.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values instead of storing + validating redundant fields. Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with `computed_field` for derived values instead of storing redundant fields; use `NotBlankStr` (from `core.types`) for all identifier/name fields instead of manual validators

Applied to files:

  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • tests/unit/engine/test_workspace_git_worktree.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to src/synthorg/**/*.py : Validate at system boundaries: user input, external APIs, config files; handle errors explicitly, never silently swallow

Applied to files:

  • tests/unit/engine/test_workspace_git_worktree.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to **/*.py : Validate explicitly at system boundaries (user input, external APIs, config files); never silently swallow errors

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) instead of `except (A, B):` -- ruff enforces this on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/engine/workspace/git_worktree.py
📚 Learning: 2026-03-17T18:52:05.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T18:52:05.142Z
Learning: Applies to **/*.py : Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Applied to files:

  • src/synthorg/engine/workspace/git_worktree.py
🔇 Additional comments (26)
src/synthorg/engine/workspace/semantic_llm_prompt.py (4)

188-213: Silently skipping malformed conflict items can cause false negatives.

Lines 189-205 log warnings but continue past invalid entries (non-dict items, missing file_path/description). This can return partial or empty results from malformed LLM output instead of triggering retry/error handling, potentially missing real semantic conflicts.

Consider raising ValueError on malformed items to trigger the retry loop in the caller, or at minimum tracking and surfacing malformed item counts so callers can decide whether to trust partial results.


34-83: LGTM! Well-structured tool definition with clear JSON schema.

The build_semantic_review_tool() function properly defines the tool schema with required conflicts (array) and summary fields, matching the expected tool-call interface.


86-142: LGTM! Clear prompt construction for semantic review.

The system and review message builders provide focused instructions for detecting semantic conflicts without style issues, and properly format the code context for LLM review.


216-243: LGTM! Robust JSON fallback parsing.

The _parse_conflicts_from_content() function properly handles:

  • Markdown-fenced JSON extraction
  • Direct JSON parsing fallback
  • Both dict and list structures
  • Proper error logging and ValueError propagation for retry handling
tests/unit/engine/test_semantic_analyzer.py (1)

1-248: LGTM! Well-structured unit tests for semantic analyzers.

The test module properly covers:

  • Protocol compliance for both AstSemanticAnalyzer and CompositeSemanticAnalyzer
  • Edge cases (empty files, non-Python files, max_files limit)
  • Core functionality (conflict detection, clean merge, deduplication)
  • Error resilience (single analyzer failure doesn't block others)

The use of pytest.mark.unit and mocking patterns align with project conventions.

src/synthorg/engine/workspace/models.py (3)

92-103: LGTM! Well-designed semantic conflict validation.

The description field with the _validate_semantic_description model validator correctly enforces that semantic conflicts must have meaningful descriptions while allowing textual conflicts to have empty descriptions (since their content is captured in ours_content/theirs_content).


141-161: LGTM! Consistent semantic_conflicts validation.

The validation logic correctly ensures:

  • semantic_conflicts can only exist on successful merges (since semantic analysis runs post-merge)
  • Failed merges with textual conflicts cannot have semantic conflicts attached

203-209: LGTM! Consistent computed field for semantic conflicts aggregation.

The total_semantic_conflicts computed property follows the same pattern as total_conflicts, providing a convenient aggregate for merge group results.

src/synthorg/engine/workspace/semantic_llm.py (3)

56-75: LGTM! Proper constructor with validation.

The constructor validates the model parameter and provides a sensible default for SemanticAnalysisConfig when not provided.


77-119: LGTM! Well-structured analyze method.

The analyze() method properly:

  • Logs the analysis start event
  • Delegates context preparation to a separate method
  • Returns early when nothing to review
  • Uses retry logic for LLM calls

172-240: LGTM! Robust retry logic with proper exception handling.

The _call_with_retry() method correctly:

  • Retries on ValueError (parse failures)
  • Re-raises CancelledError for proper cancellation handling
  • Logs appropriate events at different severity levels
  • Returns empty tuple on exhaustion rather than failing the merge
src/synthorg/engine/workspace/git_worktree.py (4)

55-70: LGTM! Conservative path validation for git arguments.

The _validate_file_path() function properly rejects:

  • Empty strings and flag-like arguments (-)
  • Directory traversal (..)
  • Absolute paths
  • Characters outside a conservative allowlist

This prevents command injection in git path arguments.


307-351: LGTM! Clean separation of merge phases.

The refactored merge_workspace() properly:

  1. Executes git merge under lock
  2. Releases lock before semantic analysis (read-only SHA operations)
  3. Attaches semantic conflicts via model_copy() for immutability

This addresses the concurrency concern from past reviews.


744-788: LGTM! Parallel base source fetching with TaskGroup.

The _get_base_sources() method uses asyncio.TaskGroup for structured concurrency when fetching file contents via parallel git show calls, following the coding guideline preference for TaskGroup over bare create_task.


831-888: LGTM! Robust semantic analysis with pre-filtering.

The _do_semantic_analysis() method correctly:

  • Pre-filters files by extension and max_files before expensive git operations
  • Falls back to pre_merge_sha if merge-base fails
  • Catches and logs exceptions without failing the merge
  • Re-raises CancelledError for proper cancellation handling
src/synthorg/engine/workspace/semantic_checks.py (4)

63-69: LGTM! Proper Load-context filtering for name references.

The _all_name_references() function now correctly filters to ast.Load context only (line 67), avoiding false positives from name bindings in other contexts.


153-180: LGTM! Proper handling of deleted files in removed-name collection.

The _collect_removed_names() function now correctly handles the case where merged_src is None (file deleted), treating all top-level names from the base as removed rather than silently skipping.


230-329: LGTM! Comprehensive signature change detection.

The _SigInfo NamedTuple now includes required_kwonly (line 236), and _compare_signatures() properly tracks new required keyword-only parameters (lines 291-309). The _check_call_compat() function validates calls against these constraints (lines 367-380).


493-522: LGTM! Deleted files handled in export collection.

The _collect_removed_exports() function properly handles deleted files (lines 505-512), marking all their exports as removed for import conflict detection.

src/synthorg/engine/workspace/semantic_analyzer.py (3)

35-63: LGTM! Clean protocol definition for semantic analyzers.

The SemanticAnalyzer protocol is properly decorated with @runtime_checkable and defines the expected analyze() signature with clear documentation.


140-225: LGTM! Well-structured AST semantic analyzer.

The AstSemanticAnalyzer class properly:

  • Filters files by extension and limits to max_files
  • Offloads blocking I/O to thread via asyncio.to_thread()
  • Runs all AST checks and aggregates results
  • Logs appropriate observability events

228-297: LGTM! Robust composite analyzer with proper error isolation.

The CompositeSemanticAnalyzer correctly:

  • Runs analyzers sequentially and aggregates results
  • Isolates failures (logs and skips failing analyzers)
  • Re-raises CancelledError for proper cancellation
  • Deduplicates by (file_path, description) to avoid duplicates from multiple analyzers
tests/integration/engine/test_workspace_semantic_integration.py (4)

1-110: LGTM! Well-structured integration test helpers.

The helper functions properly:

  • Initialize git repos with required config (user.email, user.name)
  • Commit files with proper git add/commit flow
  • Create strategy with configurable semantic analysis

120-186: LGTM! Comprehensive renamed-function semantic conflict test.

This test properly exercises the core semantic conflict scenario:

  • Agent A renames a function
  • Agent B adds a caller using the old name
  • After merging both, the semantic conflict is correctly detected

236-326: LGTM! Duplicate definition test with disjoint hunks.

The test now properly uses a spacer block to ensure git can perform a clean textual merge while producing duplicate process() definitions for semantic analysis to catch. This addresses the past review concern about nondeterministic test behavior.


328-362: LGTM! Semantic disabled test ensures graceful degradation.

This test verifies that when semantic analysis is disabled, merges complete successfully without any semantic conflicts being reported, even when potential issues exist.

Comment on lines +83 to +89
with patch(_READ_FN, return_value={"utils.py": merged_content}):
result = await analyzer.analyze(
workspace=_make_workspace(),
changed_files=("utils.py",),
repo_root="/tmp/repo", # noqa: S108
base_sources={"utils.py": "def old_func():\n pass\n"},
)
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

Patch _read_file_contents() with strings, not mapping objects.

semantic_llm._read_file_contents(file_path, workspace_id) returns the contents of a single file as str. Returning {"foo.py": "..."} here changes the shape of merged_sources, so these tests either exercise an impossible code path or miss prompt-construction bugs entirely. Use a side_effect keyed by file_path instead.

🧪 Representative fix
-        with patch(_READ_FN, return_value={"utils.py": merged_content}):
+        with patch(
+            _READ_FN,
+            side_effect=lambda file_path, _workspace_id: {
+                "utils.py": merged_content,
+            }[file_path],
+        ):
             result = await analyzer.analyze(
                 workspace=_make_workspace(),
                 changed_files=("utils.py",),
                 repo_root="/tmp/repo",  # noqa: S108
                 base_sources={"utils.py": "def old_func():\n    pass\n"},

Also applies to: 105-111, 141-147, 174-180

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/engine/test_semantic_llm.py` around lines 83 - 89, The tests patch
_read_file_contents with a mapping-returning value, but
semantic_llm._read_file_contents(file_path, workspace_id) should return a string
for a single file; update the patches in tests (around test_semantic_llm, lines
exercising analyzer.analyze and calls using merged_content) to use a side_effect
or per-file return that returns the file's string content keyed by the file_path
argument (i.e., return "def foo(): ..." for "utils.py") instead of returning
{"utils.py": "..."} so merged_sources keeps the correct shape and prompt
construction is exercised; apply the same change to the other occurrences noted
(lines ~105-111, ~141-147, ~174-180).

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/workspace/config.py`:
- Around line 12-21: The class docstring for the semantic conflict detection
configuration is missing documentation for two fields: add entries for
llm_max_retries and max_file_bytes to the existing docstring near the other
attributes; describe llm_max_retries as the maximum number of retry attempts for
LLM-based semantic analysis (used with llm_model/llm_temperature/llm_max_tokens)
and max_file_bytes as the maximum allowed file size in bytes to analyze per file
(complements max_files and file_extensions), keeping the same docstring style as
the other attribute lines.

In `@src/synthorg/engine/workspace/semantic_analyzer.py`:
- Around line 176-180: The initial WORKSPACE_SEMANTIC_ANALYSIS_START log
currently uses file_count=len(changed_files) which is the unfiltered set; change
it to report the filtered/actual-to-be-analyzed count (e.g. the number of files
that match file_extensions or the length of merged_sources) so the start event
matches the completion metric. Locate the logger.info call that emits
WORKSPACE_SEMANTIC_ANALYSIS_START and replace file_count=len(changed_files) with
the filtered count (compute filtered_files by applying the same file_extensions
filter used later or reuse len(merged_sources) if available at that point) so
both start and completion logs reflect the same set size.

In `@src/synthorg/engine/workspace/semantic_llm.py`:
- Around line 243-281: Add an inline comment above
LlmSemanticAnalyzer._read_file_contents explaining that the byte-size limit
(max_file_bytes, default ~512 KB) is intentional to prevent exceeding LLM token
limits during semantic analysis and that the method intentionally logs read
errors at WARNING (not DEBUG) because LLM ingestion failures are operationally
more significant than AST-only analysis; reference the function name
_read_file_contents, the parameter max_file_bytes, and the differing behavior vs
_read_sources so future maintainers understand this is deliberate, not a bug to
be made consistent.

In `@tests/unit/engine/test_semantic_checks.py`:
- Line 18: The module already sets pytestmark = pytest.mark.unit to apply the
unit marker to all tests; remove the redundant `@pytest.mark.unit` decorators from
each individual test function in this file (i.e., delete the decorator lines
currently applied directly above the test functions) while leaving the
module-level pytestmark unchanged so all tests keep the unit marker.

In `@tests/unit/engine/test_workspace_models.py`:
- Around line 426-457: Combine the two tests
test_failed_merge_with_semantic_conflicts_rejected and
test_successful_merge_with_semantic_conflicts_accepted into a single
parametrized pytest using `@pytest.mark.parametrize` over the success boolean and
the expected outcome (raises ValidationError with "semantic conflicts" vs
accepts and has one semantic_conflict). Create the same MergeConflict
(file_path="a.py", conflict_type=ConflictType.SEMANTIC, description="issue")
inside the test and call MergeResult with workspace_id="ws-1", branch_name="b",
duration_seconds=0.1, semantic_conflicts=(sc,), varying success and
merged_commit_sha when success is True; assert accordingly (use pytest.raises
for the failure case and check len(mr.semantic_conflicts) == 1 for the success
case). Ensure the parametrized test name reflects the scenario.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d3f6e952-f517-47e8-9a04-6c7ee25f33d5

📥 Commits

Reviewing files that changed from the base of the PR and between 85be92c and 7000c46.

📒 Files selected for processing (5)
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_workspace_models.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use PEP 758 except syntax: except A, B: (no parentheses) instead of except (A, B): -- ruff enforces this on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task for structured concurrency
Keep functions under 50 lines and files under 800 lines
Line length must be 88 characters as enforced by ruff
Validate explicitly at system boundaries (user input, external APIs, config files); never silently swallow errors

Files:

  • tests/unit/engine/test_semantic_checks.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • tests/unit/engine/test_workspace_models.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow to categorize tests
Maintain 80% minimum code coverage as enforced in CI
Use asyncio_mode = 'auto' for async tests; no manual @pytest.mark.asyncio needed
Use @pytest.mark.parametrize for testing similar cases instead of test duplication
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, tests, or config examples; use generic names: example-provider, example-large-001, test-provider, test-small-001, large/medium/small
Use Hypothesis with @given + @settings for property-based testing; avoid flaky timing-based tests by mocking time.monotonic() and asyncio.sleep() or using asyncio.Event().wait() for indefinite blocks

Files:

  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_workspace_models.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import and use logger: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging, logging.getLogger(), or print() in application code; only use the structured logger from synthorg.observability
Always use the logger variable name logger (not _logger, not log)
Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api); import directly and always use structured kwargs: logger.info(EVENT, key=value) never string formatting
All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with model_copy(update=...)) for runtime state that evolves
Use Pydantic v2 with @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields instead of manual validators
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code
Validate at system boundaries: user input, external APIs, config files; handle errors explicitly, never silently swallow

Files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
🧠 Learnings (28)
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to tests/**/*.py : Use `pytest.mark.parametrize` for testing similar cases instead of test duplication

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to **/*.py : Validate explicitly at system boundaries (user input, external APIs, config files); never silently swallow errors

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to src/synthorg/**/*.py : Validate at system boundaries: user input, external APIs, config files; handle errors explicitly, never silently swallow

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values instead of storing + validating redundant fields. Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) instead of `except (A, B):` -- ruff enforces this on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/engine/test_workspace_models.py
🔇 Additional comments (6)
tests/unit/engine/test_workspace_models.py (1)

397-418: Good semantic-model coverage additions.

total_semantic_conflicts aggregation and both MergeConflict.description validation paths are now explicitly covered and align with model contracts.

Also applies to: 459-478

tests/unit/engine/test_semantic_checks.py (1)

1-476: LGTM!

The test file is well-structured with comprehensive parametrized test cases covering positive detections, negative cases, empty sources, and syntax error handling for all four AST semantic check functions. The use of @pytest.mark.parametrize addresses the earlier review feedback effectively.

src/synthorg/engine/workspace/config.py (1)

65-78: LGTM on the validation logic.

The file_extensions validator correctly rejects empty tuples, blank strings, extensions missing the leading dot, and extensions containing spaces. This addresses the earlier review feedback.

src/synthorg/engine/workspace/semantic_llm.py (1)

172-240: LGTM on retry and error handling logic.

The _call_with_retry method correctly:

  • Retries on ValueError from parse failures up to max_retries
  • Re-raises asyncio.CancelledError to respect cancellation
  • Logs provider errors with exc_info=True for debugging
  • Returns empty tuple on exhaustion rather than raising
src/synthorg/engine/workspace/semantic_analyzer.py (2)

35-63: LGTM on the SemanticAnalyzer protocol definition.

The protocol is well-defined with @runtime_checkable, comprehensive docstrings, and appropriate type hints including Mapping[str, str] for base_sources (allowing immutable views).


228-297: LGTM on CompositeSemanticAnalyzer.

The implementation correctly:

  • Runs analyzers sequentially and aggregates results
  • Re-raises asyncio.CancelledError to respect structured concurrency
  • Logs and skips failing analyzers with full exception info
  • Deduplicates conflicts by (file_path, description) tuple

Comment on lines +426 to +457
def test_failed_merge_with_semantic_conflicts_rejected(self) -> None:
"""Failed merge cannot have semantic conflicts."""
sc = MergeConflict(
file_path="a.py",
conflict_type=ConflictType.SEMANTIC,
description="issue",
)
with pytest.raises(ValidationError, match="semantic conflicts"):
MergeResult(
workspace_id="ws-1",
branch_name="b",
success=False,
duration_seconds=0.1,
semantic_conflicts=(sc,),
)

def test_successful_merge_with_semantic_conflicts_accepted(self) -> None:
"""Successful merge can have semantic conflicts."""
sc = MergeConflict(
file_path="a.py",
conflict_type=ConflictType.SEMANTIC,
description="issue",
)
mr = MergeResult(
workspace_id="ws-1",
branch_name="b",
success=True,
merged_commit_sha="abc",
duration_seconds=0.1,
semantic_conflicts=(sc,),
)
assert len(mr.semantic_conflicts) == 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Refactor similar semantic-conflict cases into one parametrized test.

These two tests duplicate setup/constructor paths and differ mainly by success and expected outcome. Converting to @pytest.mark.parametrize will reduce drift and keep intent tighter.

♻️ Proposed refactor
 class TestMergeResultSemanticConflicts:
     """Tests for semantic_conflicts field and validator."""

     pytestmark = pytest.mark.unit

-    def test_failed_merge_with_semantic_conflicts_rejected(self) -> None:
-        """Failed merge cannot have semantic conflicts."""
-        sc = MergeConflict(
-            file_path="a.py",
-            conflict_type=ConflictType.SEMANTIC,
-            description="issue",
-        )
-        with pytest.raises(ValidationError, match="semantic conflicts"):
-            MergeResult(
-                workspace_id="ws-1",
-                branch_name="b",
-                success=False,
-                duration_seconds=0.1,
-                semantic_conflicts=(sc,),
-            )
-
-    def test_successful_merge_with_semantic_conflicts_accepted(self) -> None:
-        """Successful merge can have semantic conflicts."""
+    `@pytest.mark.parametrize`(
+        ("success", "expect_error"),
+        [
+            (False, True),
+            (True, False),
+        ],
+    )
+    def test_semantic_conflicts_success_consistency(
+        self,
+        success: bool,
+        expect_error: bool,
+    ) -> None:
+        """semantic_conflicts are rejected on failed merges and allowed on success."""
         sc = MergeConflict(
             file_path="a.py",
             conflict_type=ConflictType.SEMANTIC,
             description="issue",
         )
-        mr = MergeResult(
-            workspace_id="ws-1",
-            branch_name="b",
-            success=True,
-            merged_commit_sha="abc",
-            duration_seconds=0.1,
-            semantic_conflicts=(sc,),
-        )
-        assert len(mr.semantic_conflicts) == 1
+        if expect_error:
+            with pytest.raises(ValidationError, match="semantic conflicts"):
+                MergeResult(
+                    workspace_id="ws-1",
+                    branch_name="b",
+                    success=success,
+                    duration_seconds=0.1,
+                    semantic_conflicts=(sc,),
+                )
+            return
+        mr = MergeResult(
+            workspace_id="ws-1",
+            branch_name="b",
+            success=success,
+            merged_commit_sha="abc",
+            duration_seconds=0.1,
+            semantic_conflicts=(sc,),
+        )
+        assert len(mr.semantic_conflicts) == 1

As per coding guidelines: "Use @pytest.mark.parametrize for testing similar cases instead of test duplication".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/engine/test_workspace_models.py` around lines 426 - 457, Combine
the two tests test_failed_merge_with_semantic_conflicts_rejected and
test_successful_merge_with_semantic_conflicts_accepted into a single
parametrized pytest using `@pytest.mark.parametrize` over the success boolean and
the expected outcome (raises ValidationError with "semantic conflicts" vs
accepts and has one semantic_conflict). Create the same MergeConflict
(file_path="a.py", conflict_type=ConflictType.SEMANTIC, description="issue")
inside the test and call MergeResult with workspace_id="ws-1", branch_name="b",
duration_seconds=0.1, semantic_conflicts=(sc,), varying success and
merged_commit_sha when success is True; assert accordingly (use pytest.raises
for the failure case and check len(mr.semantic_conflicts) == 1 for the success
case). Ensure the parametrized test name reflects the scenario.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/workspace/config.py`:
- Around line 73-79: The validator loop over self.file_extensions currently
allows the single-character extension "." which would match all filenames;
update the check in the loop that iterates over ext so it rejects "." (e.g.,
require len(ext) >= 2 or explicitly check ext != ".") in the same place where
you validate emptiness/leading dot/space, and keep raising the ValueError(msg)
with the same message when the new condition fails; this ensures the ext
variable (in the for ext in self.file_extensions loop) cannot be just ".".
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 600bfc69-a23c-4e01-a2e8-9dec65a23c17

📥 Commits

Reviewing files that changed from the base of the PR and between 7000c46 and feda149.

📒 Files selected for processing (4)
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • tests/unit/engine/test_semantic_checks.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations — Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for multiple exception types — PEP 758 style enforced by ruff on Python 3.14
Type hints required on all public functions — must pass mypy strict mode
Google-style docstrings required on all public classes and functions — enforced by ruff D rules
Create new objects instead of mutating existing ones — use copy.deepcopy() at construction for non-Pydantic internal collections; use MappingProxyType wrapping for read-only enforcement
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves — never mix static config fields with mutable runtime fields
Use @computed_field for derived values instead of storing + validating redundant fields in Pydantic models
Use NotBlankStr (from core.types) for all identifier/name fields in Pydantic models — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations (e.g. multiple tool invocations, parallel agent calls) over bare create_task
Line length: 88 characters (ruff-enforced)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files) in Python code

Files:

  • tests/unit/engine/test_semantic_checks.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
{src,tests,cli,web}/**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples — use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases. Vendor names allowed only in: (1) Operations design page, (2) .claude/ files, (3) third-party import paths

Files:

  • tests/unit/engine/test_semantic_checks.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow test markers
Use asyncio_mode = 'auto' for async tests — no manual @pytest.mark.asyncio needed
Global test timeout: 30 seconds per test (in pyproject.toml) — non-default overrides like @pytest.mark.timeout(60) are allowed
Prefer @pytest.mark.parametrize for testing similar cases in Python tests
Use test-provider, test-small-001, etc. for vendor-agnostic test data in Python and React tests
Use Hypothesis for property-based testing in Python (@given + @settings); control with HYPOTHESIS_PROFILE env var (ci: 50 examples default, dev: 1000 examples)
Never skip, dismiss, or ignore flaky tests — always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Files:

  • tests/unit/engine/test_semantic_checks.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging, logging.getLogger(), or print() in application code — exception: observability/setup.py and observability/sinks.py may use stdlib logging and stderr print for bootstrap
Always name logger variable logger (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool) — import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Always use structured logging with kwargs: logger.info(EVENT, key=value) — never use string formatting like logger.info('msg %s', val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO level
DEBUG logging for object creation, internal flow, entry/exit of key functions
Maintain 80% minimum code coverage (enforced in CI)

Files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
🧠 Learnings (30)
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to tests/**/*.py : Prefer pytest.mark.parametrize for testing similar cases in Python tests

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to tests/**/*.py : Use pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow test markers

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to src/synthorg/config/**/*.py : YAML company config loading and validation in src/synthorg/config/ — never hardcode configuration

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves — never mix static config fields with mutable runtime fields

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files) in Python code

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values instead of storing + validating redundant fields. Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for multiple exception types — PEP 758 style enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_llm.py
🔇 Additional comments (4)
tests/unit/engine/test_semantic_checks.py (1)

1-467: LGTM! Well-structured parametrized tests.

The test module properly uses:

  • Module-level pytestmark = pytest.mark.unit (no redundant decorators on individual methods)
  • @pytest.mark.parametrize for similar test cases
  • Edge case coverage for empty sources and syntax error handling

Minor observation: TestCheckImportConflicts lacks a test_syntax_error_skipped case that other classes have, but this is a minor coverage gap since the underlying behavior is consistent across all check functions.

src/synthorg/engine/workspace/config.py (1)

118-121: LGTM!

Correctly uses default_factory=SemanticAnalysisConfig for the nested configuration, following Pydantic best practices for mutable default values.

src/synthorg/engine/workspace/semantic_llm.py (1)

1-302: LGTM! Well-implemented LLM semantic analyzer.

The implementation correctly:

  • Validates model parameter on construction
  • Filters and limits files per configuration
  • Implements retry logic with proper attempt counting
  • Protects against path traversal attacks
  • Enforces file size limits for LLM token constraints
  • Documents intentional divergence from AST analyzer's file reading
  • Uses structured logging throughout
src/synthorg/engine/workspace/semantic_analyzer.py (1)

1-297: LGTM! Clean implementation of semantic analyzer hierarchy.

The module provides:

  • Well-defined SemanticAnalyzer protocol with @runtime_checkable
  • AstSemanticAnalyzer with proper file filtering, path traversal protection, and structured logging
  • CompositeSemanticAnalyzer with correct exception handling (re-raises CancelledError, logs and continues on other exceptions) and sensible deduplication

The deduplication by (file_path, description) works correctly since the MergeConflict model validates that SEMANTIC conflicts must have non-empty descriptions.

Aureliolo and others added 7 commits March 27, 2026 00:28
Add hybrid AST + LLM semantic conflict detection after workspace
merges. Detects removed-name references, signature mismatches,
duplicate definitions, and import conflicts that git's textual
merge cannot catch.

New modules:
- semantic_checks.py: Pure AST-based check functions
- semantic_analyzer.py: SemanticAnalyzer protocol + AstSemanticAnalyzer
- semantic_llm.py: LlmSemanticAnalyzer for deeper review
- semantic_llm_prompt.py: Prompt building and response parsing

Key changes:
- MergeConflict: add description field
- MergeResult: add semantic_conflicts field (separate from textual)
- PlannerWorktreesConfig: add SemanticAnalysisConfig
- PlannerWorktreeStrategy: accept optional SemanticAnalyzer, run
  post-merge analysis using merge-base for cross-branch detection
- MergeOrchestrator: handle semantic conflict escalation

Closes #611

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-reviewed by 11 agents, 18 findings addressed:
- Replace bare string event with WORKSPACE_SEMANTIC_PARSE_SKIP constant
- Add exc_info=True to all except Exception warning logs
- Fix _function_min_args/_function_max_args to include posonlyargs
- Fix module stem derivation to strip src/ prefix for import checks
- Replace _SigInfo tuple alias with NamedTuple + frozenset
- Replace contextlib.suppress with explicit logging in _read_file_contents
- Add warning logs before all ValueError/TypeError raises in prompt parser
- Use WORKSPACE_SEMANTIC_ANALYSIS_FAILED for semantic git helper log events
- Split long functions (analyze, _run_semantic_analysis) into helpers
- Add MergeResult.semantic_conflicts to docstring
- Add tests: merge orchestrator semantic escalation (HUMAN/REVIEW_AGENT)
- Add tests: MergeResult validator, total_semantic_conflicts, keyword args

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TypeError bypassed the retry loop in _call_with_retry which only
catches ValueError. This caused malformed LLM output to be treated
as a fatal provider error instead of a retryable parse error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements all findings from 14 local review agents, CodeRabbit, and Gemini:

- Security: path traversal validation, file path sanitization in git show args
- Concurrency: move semantic analysis outside merge lock, use TaskGroup for
  parallel git show, add CancelledError re-raise before broad except handlers
- Config: enforce enabled flag, add max_file_bytes and llm_max_retries fields,
  pre-filter changed files by extension/max_files before git show calls
- AST checks: Load-only name references, handle deleted files in collectors,
  track required keyword-only params, fix variadic sentinel false positives,
  fix __init__.py module stem, allow same-file reference detection
- Types: Mapping[str, str] in Protocol/check functions, MergeConflict semantic
  description validator, replace assert with SemanticAnalysisError guard
- Logging: add structured logging to git helpers, capture exc details in error
  handlers, log malformed LLM conflict items
- Functions: refactor to stay under 50-line limit, extract _prepare_review_context
  and _merge_under_lock helpers
- Docs: update design spec protocol signature, add semantic analysis section
- Tests: 26 new unit tests for git helpers, fix integration test determinism
- Export LlmSemanticAnalyzer from workspace __init__.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- config: add file_extensions validator (reject empty/malformed extensions)
- semantic_analyzer: simplify except to OSError (subclasses are redundant)
- semantic_llm: same OSError simplification
- test_semantic_checks: collapse into @pytest.mark.parametrize per CLAUDE.md
- test_workspace_models: add negative test for semantic description validator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- config: add llm_max_retries and max_file_bytes to docstring Attributes
- semantic_analyzer: log filtered file_count (not unfiltered) at start
- semantic_llm: add docstring explaining _read_file_contents vs _read_sources
- test_semantic_checks: remove redundant @pytest.mark.unit (pytestmark covers)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevent "." from matching all filenames by requiring extensions to have
at least one character after the dot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo force-pushed the feat/semantic-conflict-detection branch from feda149 to 3245d5e Compare March 26, 2026 23:31
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 23:32 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/synthorg/engine/workspace/semantic_llm_prompt.py (1)

188-205: ⚠️ Potential issue | 🟠 Major

Partial results returned when LLM produces malformed conflict items.

Lines 188-205 log warnings but continue on invalid entries, potentially returning an incomplete conflict list. This masks malformed provider output and can cause false negatives in semantic conflict detection, as the retry loop only triggers on ValueError.

Given the previous review flagged this as a major issue, consider strict validation that raises ValueError on any malformed item to trigger retry logic:

♻️ Suggested strict validation
     for idx, item in enumerate(raw_conflicts):
         if not isinstance(item, dict):
             logger.warning(
                 WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
                 reason="unexpected_conflict_item_type",
                 item_type=type(item).__name__,
+                index=idx,
             )
-            continue
+            msg = f"Conflict at index {idx} is not a dict"
+            raise ValueError(msg)
         file_path = item.get("file_path", "")
         description = item.get("description", "")
         if not file_path or not description:
             logger.warning(
                 WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
                 reason="incomplete_conflict_item",
                 file_path=file_path or "(empty)",
                 has_description=bool(description),
+                index=idx,
             )
-            continue
+            msg = f"Conflict at index {idx} missing required fields"
+            raise ValueError(msg)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/workspace/semantic_llm_prompt.py` around lines 188 - 205,
The current loop over raw_conflicts silently skips malformed items (in the for
loop handling raw_conflicts) by logging WORKSPACE_SEMANTIC_ANALYSIS_FAILED and
using continue, which leads to partial results and prevents the retry logic that
depends on ValueError; change the validation inside that loop (the
isinstance(item, dict) check and the file_path/description presence checks) to
raise a ValueError with a clear message referencing the problematic item
(include item_type or file_path/description state) instead of continuing so that
the caller’s retry logic is triggered and malformed provider output is surfaced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/workspace/semantic_checks.py`:
- Around line 475-490: The module-stem conversion in _file_path_to_module_stem
only removes "src/" and "lib/" prefixes which misses other common layouts;
update this function to handle additional common prefixes (e.g., "packages/",
"app/", "src/main/python/") by replacing the hard-coded ("src/", "lib/") tuple
with a configurable or extended tuple (e.g., MODULE_ROOT_PREFIXES) and iterate
over that to strip any matching prefix before converting separators and trimming
"__init__"; ensure you keep the existing behavior for Windows separators and the
__init__ handling in _file_path_to_module_stem.

In `@src/synthorg/engine/workspace/semantic_llm_prompt.py`:
- Around line 136-137: The loop in build_review_message currently hardcodes a
"python" code fence for every changed_files entry which can mislabel non-.py
files; update the loop that iterates over changed_files (the for path, content
in changed_files.items()) to derive a fence language from the file extension
(e.g., ext = path.rsplit(".", 1)[-1] if "." in path else ""), map common
extensions to languages (e.g., {"py":"python","pyi":"python","pyx":"cython"}),
and use that mapped lang when building parts.append(f"\n###
{path}\n```{lang}\n{content}\n```\n") falling back to an empty fence if unknown.

In `@src/synthorg/engine/workspace/semantic_llm.py`:
- Around line 172-240: The retry loop in _call_with_retry currently resends
identical messages/tool_def on ValueError; change it to append a short
system/user hint on retry attempts to tell the model the previous response was
malformed (e.g., add a ChatMessage like "Previous response could not be parsed;
please return valid JSON using the specified tool schema." when attempt>0)
before calling self._provider.complete, or alternatively document in the
function docstring that successful retries depend on non-zero llm_temperature in
comp_config; update the call site that constructs messages (used by
_call_with_retry) so the hint is injected only for retry attempts and reference
_provider.complete, parse_tool_call_response, and comp_config in the change.

---

Duplicate comments:
In `@src/synthorg/engine/workspace/semantic_llm_prompt.py`:
- Around line 188-205: The current loop over raw_conflicts silently skips
malformed items (in the for loop handling raw_conflicts) by logging
WORKSPACE_SEMANTIC_ANALYSIS_FAILED and using continue, which leads to partial
results and prevents the retry logic that depends on ValueError; change the
validation inside that loop (the isinstance(item, dict) check and the
file_path/description presence checks) to raise a ValueError with a clear
message referencing the problematic item (include item_type or
file_path/description state) instead of continuing so that the caller’s retry
logic is triggered and malformed provider output is surfaced.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a44f7b32-c2d6-4511-8881-6a17d0389f02

📥 Commits

Reviewing files that changed from the base of the PR and between feda149 and 3245d5e.

📒 Files selected for processing (22)
  • CLAUDE.md
  • docs/design/engine.md
  • src/synthorg/engine/errors.py
  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/git_worktree.py
  • src/synthorg/engine/workspace/merge.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
  • src/synthorg/engine/workspace/semantic_checks.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/observability/events/workspace.py
  • tests/integration/engine/test_workspace_semantic_integration.py
  • tests/unit/engine/conftest.py
  • tests/unit/engine/test_semantic_analyzer.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_semantic_llm.py
  • tests/unit/engine/test_semantic_llm_prompt.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • tests/unit/engine/test_workspace_merge.py
  • tests/unit/engine/test_workspace_models.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Deploy Preview
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations — Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for multiple exception types — PEP 758 style enforced by ruff on Python 3.14
Type hints required on all public functions — must pass mypy strict mode
Google-style docstrings required on all public classes and functions — enforced by ruff D rules
Create new objects instead of mutating existing ones — use copy.deepcopy() at construction for non-Pydantic internal collections; use MappingProxyType wrapping for read-only enforcement
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves — never mix static config fields with mutable runtime fields
Use @computed_field for derived values instead of storing + validating redundant fields in Pydantic models
Use NotBlankStr (from core.types) for all identifier/name fields in Pydantic models — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations (e.g. multiple tool invocations, parallel agent calls) over bare create_task
Line length: 88 characters (ruff-enforced)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files) in Python code

Files:

  • tests/unit/engine/conftest.py
  • src/synthorg/engine/errors.py
  • tests/unit/engine/test_workspace_models.py
  • src/synthorg/engine/workspace/__init__.py
  • tests/unit/engine/test_semantic_llm_prompt.py
  • tests/unit/engine/test_semantic_llm.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • tests/unit/engine/test_workspace_merge.py
  • tests/unit/engine/test_semantic_analyzer.py
  • src/synthorg/engine/workspace/merge.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_checks.py
  • src/synthorg/observability/events/workspace.py
  • src/synthorg/engine/workspace/git_worktree.py
  • tests/integration/engine/test_workspace_semantic_integration.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
{src,tests,cli,web}/**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples — use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases. Vendor names allowed only in: (1) Operations design page, (2) .claude/ files, (3) third-party import paths

Files:

  • tests/unit/engine/conftest.py
  • src/synthorg/engine/errors.py
  • tests/unit/engine/test_workspace_models.py
  • src/synthorg/engine/workspace/__init__.py
  • tests/unit/engine/test_semantic_llm_prompt.py
  • tests/unit/engine/test_semantic_llm.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • tests/unit/engine/test_workspace_merge.py
  • tests/unit/engine/test_semantic_analyzer.py
  • src/synthorg/engine/workspace/merge.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_checks.py
  • src/synthorg/observability/events/workspace.py
  • src/synthorg/engine/workspace/git_worktree.py
  • tests/integration/engine/test_workspace_semantic_integration.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow test markers
Use asyncio_mode = 'auto' for async tests — no manual @pytest.mark.asyncio needed
Global test timeout: 30 seconds per test (in pyproject.toml) — non-default overrides like @pytest.mark.timeout(60) are allowed
Prefer @pytest.mark.parametrize for testing similar cases in Python tests
Use test-provider, test-small-001, etc. for vendor-agnostic test data in Python and React tests
Use Hypothesis for property-based testing in Python (@given + @settings); control with HYPOTHESIS_PROFILE env var (ci: 50 examples default, dev: 1000 examples)
Never skip, dismiss, or ignore flaky tests — always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Files:

  • tests/unit/engine/conftest.py
  • tests/unit/engine/test_workspace_models.py
  • tests/unit/engine/test_semantic_llm_prompt.py
  • tests/unit/engine/test_semantic_llm.py
  • tests/unit/engine/test_semantic_checks.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • tests/unit/engine/test_workspace_merge.py
  • tests/unit/engine/test_semantic_analyzer.py
  • tests/integration/engine/test_workspace_semantic_integration.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging, logging.getLogger(), or print() in application code — exception: observability/setup.py and observability/sinks.py may use stdlib logging and stderr print for bootstrap
Always name logger variable logger (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool) — import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Always use structured logging with kwargs: logger.info(EVENT, key=value) — never use string formatting like logger.info('msg %s', val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO level
DEBUG logging for object creation, internal flow, entry/exit of key functions
Maintain 80% minimum code coverage (enforced in CI)

Files:

  • src/synthorg/engine/errors.py
  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/merge.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_checks.py
  • src/synthorg/observability/events/workspace.py
  • src/synthorg/engine/workspace/git_worktree.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Docs: Markdown in docs/ (built with Zensical, config: mkdocs.yml) — design spec: docs/design/ (11 pages), Architecture: docs/architecture/, Roadmap: docs/roadmap/, Security: docs/security.md, Licensing: docs/licensing.md, Reference: docs/reference/

Files:

  • docs/design/engine.md
🧠 Learnings (48)
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/errors.py
  • src/synthorg/engine/workspace/config.py
  • tests/unit/engine/test_workspace_git_worktree.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
  • src/synthorg/engine/workspace/semantic_checks.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/engine/test_workspace_models.py
  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/engine/test_workspace_models.py
  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to tests/**/*.py : Prefer pytest.mark.parametrize for testing similar cases in Python tests

Applied to files:

  • tests/unit/engine/test_workspace_models.py
  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/engine/test_workspace_models.py
  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to src/synthorg/config/**/*.py : YAML company config loading and validation in src/synthorg/config/ — never hardcode configuration

Applied to files:

  • src/synthorg/engine/workspace/__init__.py
  • src/synthorg/engine/workspace/models.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values instead of storing + validating redundant fields. Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/engine/workspace/models.py
  • src/synthorg/engine/workspace/config.py
  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files) in Python code

Applied to files:

  • src/synthorg/engine/workspace/config.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to tests/**/*.py : Use pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow test markers

Applied to files:

  • tests/unit/engine/test_semantic_checks.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm_prompt.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for multiple exception types — PEP 758 style enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/engine/workspace/semantic_llm.py
  • src/synthorg/engine/workspace/semantic_analyzer.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool) — import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/workspace.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/engine/workspace/git_worktree.py
📚 Learning: 2026-03-17T18:52:05.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T18:52:05.142Z
Learning: Applies to **/*.py : Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Applied to files:

  • src/synthorg/engine/workspace/git_worktree.py
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to **/*.py : Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations (e.g. multiple tool invocations, parallel agent calls) over bare create_task

Applied to files:

  • src/synthorg/engine/workspace/git_worktree.py
🔇 Additional comments (54)
CLAUDE.md (1)

113-113: LGTM!

The documentation update accurately captures the new workspace capabilities: git worktree isolation, merge orchestration, and semantic conflict detection. This aligns with the production code changes introduced in the PR.

src/synthorg/engine/errors.py (1)

90-92: LGTM!

The SemanticAnalysisError follows the established error hierarchy pattern, correctly subclassing WorkspaceError alongside its siblings (WorkspaceSetupError, WorkspaceMergeError, etc.). The docstring is appropriately concise.

src/synthorg/engine/workspace/merge.py (1)

138-149: LGTM!

The semantic conflict handling correctly mirrors the existing textual conflict pattern:

  1. Updates escalation via model_copy() (immutable pattern)
  2. Appends result before the break check
  3. Breaks only on HUMAN escalation, allowing REVIEW_AGENT to continue

The placement after line 143 ensures the semantic-conflict result is captured before early termination.

tests/unit/engine/conftest.py (1)

359-376: LGTM!

The make_merge_result helper is correctly extended with semantic_conflicts defaulting to an empty tuple, maintaining backward compatibility with existing tests while supporting new semantic conflict test scenarios.

src/synthorg/engine/workspace/__init__.py (1)

26-48: LGTM!

The module's public API is correctly expanded to expose the new semantic analysis components. The __all__ list maintains alphabetical ordering, and the imports align with the new semantic analyzer modules introduced in this PR.

tests/unit/engine/test_workspace_models.py (1)

396-478: LGTM!

Comprehensive test coverage for the new semantic conflict modeling:

  • test_total_semantic_conflicts: Validates WorkspaceGroupResult aggregation
  • TestMergeResultSemanticConflicts: Covers the validation rules (rejected on failure, accepted on success) and the MergeConflict.description constraint for ConflictType.SEMANTIC

All tests have proper @pytest.mark.unit markers (class-level pytestmark for the new test class).

docs/design/engine.md (1)

924-981: LGTM!

The design documentation comprehensively covers the new semantic conflict detection feature:

  • Configuration block aligns with SemanticAnalysisConfig fields
  • SemanticAnalyzer protocol and composite pattern are clearly documented
  • Four AST-based checks match the implementation in semantic_checks.py
  • Optional LLM-based analysis configuration is accurately described
  • The merge flow through MergeResult.semantic_conflicts is correctly documented

This provides essential reference material for developers working with the workspace merge pipeline.

tests/unit/engine/test_workspace_git_worktree.py (8)

10-23: LGTM!

The imports and _make_strategy helper are correctly updated to support semantic analyzer injection for testing. The semantic_analyzer parameter with object | None typing and type ignore comment allows flexible test doubles.


263-289: LGTM!

The merge success test correctly updated to include the pre-merge rev-parse HEAD call that captures the SHA before the merge operation. This aligns with the production code flow where pre_merge_sha is captured during _merge_under_lock and later passed to semantic analysis.


740-777: LGTM!

Comprehensive coverage of _validate_file_path security validation:

  • Empty strings, dash-prefixed (flag injection), directory traversal (..), absolute paths
  • Valid relative paths, paths with spaces (allowed), disallowed special characters

These tests ensure the path allowlist protects against command injection vectors.


784-826: LGTM!

Good coverage of _get_merge_base including success and failure paths. The test correctly verifies the log_event parameter is passed through to _run_git.


833-904: LGTM!

Thorough testing of _get_changed_files including the critical security test at lines 888-904 that verifies unsafe paths (flag-like --malicious, traversal ../escape.py) are filtered from git diff output.


912-998: LGTM!

Good coverage of _get_base_sources:

  • Successful file content retrieval
  • Graceful handling of failed git show (files omitted, not errored)
  • Unsafe path filtering before git operations

1005-1141: LGTM!

Comprehensive control flow testing for _run_semantic_analysis:

  • No analyzer configured → returns empty
  • Empty pre_merge_sha → returns empty
  • Disabled config → returns empty
  • Enabled → delegates to _do_semantic_analysis
  • Conflicts found → returns conflicts (warning logged)

1148-1297: LGTM!

Excellent coverage of _do_semantic_analysis:

  • SemanticAnalysisError raised when analyzer is None (defensive check)
  • Non-CancelledError exceptions caught and swallowed (returns empty)
  • CancelledError properly re-raised (not swallowed)
  • Extension filtering ensures only configured file types are analyzed

The cancellation behavior is critical for cooperative shutdown - good to see it explicitly tested.

src/synthorg/observability/events/workspace.py (1)

25-31: Well-structured event constants for the semantic analysis pipeline.

The new constants follow the established naming convention and value pattern used by existing workspace events. Good consistency with the domain-specific event organization.

tests/unit/engine/test_semantic_llm.py (3)

57-63: Good validation test for blank model rejection.

The test correctly verifies that LlmSemanticAnalyzer raises ValueError with a "non-blank" message when given an empty model string. This ensures proper input validation at construction time.


151-183: Retry behavior test is well-structured.

The test clearly validates the retry-on-parse-failure mechanism by providing an unparseable first response followed by a valid second response, then asserting both the result and that the provider was called twice.


83-89: Remove comment—the mock return type is correct.

_read_file_contents(root: Path, files: list[str], max_file_bytes: int) returns dict[str, str], not a string. The mock correctly returns a dictionary mapping file paths to content strings, matching the production implementation.

			> Likely an incorrect or invalid review comment.
src/synthorg/engine/workspace/models.py (3)

92-103: Well-designed validation for semantic conflict descriptions.

The description field defaults to empty string (allowing textual conflicts to omit it), while the validator correctly enforces non-empty descriptions for SEMANTIC conflicts. This aligns with the relevant code snippets showing all semantic conflict instantiations provide meaningful descriptions.


141-160: Semantic conflicts validation logic is correct.

The validation properly enforces that:

  1. Semantic conflicts can only exist on successful merges (since they run post-merge)
  2. Failed merges must have an empty semantic_conflicts tuple

This maintains model consistency with the merge workflow design.


203-209: Good use of @computed_field for aggregated metrics.

The total_semantic_conflicts property follows the same pattern as total_conflicts, maintaining consistency in the model's API. As per coding guidelines, using computed_field for derived values is the correct approach.

src/synthorg/engine/workspace/config.py (2)

67-80: File extension validation is comprehensive.

The validator correctly rejects:

  • Empty tuple (not self.file_extensions)
  • Empty strings (not ext)
  • Single dot (ext == ".")
  • Non-dot-prefixed extensions
  • Extensions containing spaces

This addresses all the edge cases from past reviews.


11-65: Well-structured semantic analysis configuration.

The config model follows project conventions: frozen Pydantic model with explicit bounds on numeric fields. The defaults are sensible (disabled by default, reasonable limits for max_files, llm_max_tokens, etc.).

tests/unit/engine/test_semantic_analyzer.py (3)

60-65: Good protocol compliance tests.

Testing isinstance(analyzer, SemanticAnalyzer) ensures the implementations satisfy the protocol contract, which is important for the composite pattern to work correctly.


106-129: Effective end-to-end semantic conflict detection test.

This test validates the core use case: a function renamed in one file while another file still references the old name. The assertion len(result) >= 1 allows for potential multiple conflict types to be detected.


226-244: Resilience test validates fault isolation.

This correctly tests that one analyzer's exception doesn't prevent other analyzers from contributing their results, matching the production behavior shown in the relevant code snippet.

tests/unit/engine/test_workspace_merge.py (2)

383-388: Semantic conflict helper correctly includes required description.

The helper provides a non-empty description field, satisfying the MergeConflict validator that requires descriptions for SEMANTIC conflict types.


391-447: Comprehensive escalation tests for semantic conflicts.

Both tests clearly validate the expected orchestrator behavior:

  • HUMAN escalation stops processing after the first workspace with semantic conflicts
  • REVIEW_AGENT escalation continues to subsequent workspaces

The assertions verify result counts, escalation values, and semantic conflict presence.

tests/unit/engine/test_semantic_llm_prompt.py (3)

22-36: Tool schema tests validate contract with LLM provider.

These tests ensure the tool definition structure matches what's expected by the semantic review workflow, including the required file_path and description fields for conflict items.


133-144: Good coverage of JSON fallback parsing.

Testing the json code fence fallback ensures the parser handles cases where the LLM returns JSON in content rather than through tool calls, improving robustness.


156-172: Partial parsing validation is important for resilience.

This test verifies that invalid conflict items (empty file_path, non-dict entries) are skipped while valid items are still parsed, preventing a single malformed item from breaking the entire response.

tests/unit/engine/test_semantic_checks.py (4)

29-126: Excellent use of parametrized tests for removed references.

The test matrix covers key scenarios: function/class/constant removal with references, removal without references, and name still present. The description_contains parameter enables flexible assertion on conflict details.


136-149: Syntax error resilience tests are important.

These tests verify that unparseable source files don't raise exceptions, ensuring the semantic checks degrade gracefully when encountering invalid Python syntax.


228-251: Good coverage of varargs and signature compatibility.

The varargs-accepts-extra-positional and signature-unchanged-no-conflict cases ensure the signature checker doesn't produce false positives for compatible changes.


324-338: Nested same-name case prevents false positives.

Testing that a method named helper inside a class doesn't conflict with a top-level function helper is important for avoiding spurious duplicate definition warnings.

src/synthorg/engine/workspace/semantic_llm_prompt.py (1)

216-243: LGTM!

The _parse_conflicts_from_content function correctly handles JSON fallback parsing with proper error handling - all failure paths log and raise ValueError to trigger the retry loop.

src/synthorg/engine/workspace/semantic_llm.py (2)

56-75: LGTM!

The __init__ method properly validates the model string is non-blank and uses lazy import for SemanticAnalysisConfig to avoid circular dependencies. The default config instantiation when None is passed is clean.


243-287: LGTM!

The _read_file_contents function correctly:

  • Validates path traversal using is_relative_to() after resolving both paths
  • Checks file size before reading to prevent memory exhaustion
  • Uses a single except OSError clause per PEP 758 guidelines
  • Documents the intentional divergence from _read_sources (byte limit and log level)
src/synthorg/engine/workspace/semantic_checks.py (4)

63-69: LGTM!

The _all_name_references function now correctly filters to Load context only (isinstance(node.ctx, ast.Load)), addressing the previous review concern about over-reporting due to collecting all ast.Name nodes.


153-180: LGTM!

The _collect_removed_names function now correctly handles deleted files (lines 165-171) by treating all top-level names from the base file as removed when merged_src is None. This addresses the previous review concern about whole-file removals being invisible.


230-237: LGTM!

The _SigInfo NamedTuple now includes required_kwonly: frozenset[str], and _compare_signatures correctly computes added required keyword-only parameters. The _check_call_compat function validates these are provided at call sites. This addresses the previous review concern about signature changes like def f(x)def f(x, *, y) being missed.

Also applies to: 290-309


213-227: LGTM!

The check_removed_references function correctly excludes names that are redefined locally in the merged file (line 216: name not in local_defs), preventing false positives from local rebindings like foo = 1; print(foo).

src/synthorg/engine/workspace/git_worktree.py (4)

58-70: LGTM!

The _validate_file_path function provides robust input validation:

  • Rejects empty/flag-like strings
  • Blocks directory traversal (.. in path components)
  • Rejects absolute paths
  • Uses a conservative character allowlist

This properly validates git path arguments before use.


857-866: LGTM!

The pre-filtering logic correctly applies file_extensions and max_files limits before calling _get_base_sources, addressing the previous review concern about expensive git show calls not being capped by the config limits.


785-788: LGTM!

The _get_base_sources method correctly uses asyncio.TaskGroup for parallel git show operations, following the coding guideline to prefer structured concurrency over bare create_task.


879-887: LGTM!

The exception handling correctly:

  • Re-raises asyncio.CancelledError to support proper cancellation
  • Catches all other exceptions, logs with context and exc_info=True
  • Returns empty tuple to avoid blocking the merge flow

This graceful degradation is appropriate since semantic analysis is informational and shouldn't prevent successful textual merges.

tests/integration/engine/test_workspace_semantic_integration.py (3)

29-63: LGTM!

The _init_test_repo helper properly initializes a test git repository with required git config and initial commit. The # noqa: S607 comments acknowledge Bandit's subprocess security warnings, which are acceptable in tests using controlled inputs.


236-326: LGTM!

The test_duplicate_definition_detected test correctly uses disjoint hunks (Agent A adds at TOP, Agent B adds at BOTTOM) separated by a spacer block, ensuring git cleanly merges both branches and produces two process() definitions. This addresses the previous review concern about nondeterministic test behavior.


120-185: LGTM!

The test_renamed_function_detected test provides good end-to-end coverage of the removed-reference detection scenario. The assertions correctly verify that:

  1. First merge succeeds without semantic conflicts
  2. Second merge succeeds textually but detects semantic conflict
  3. The conflict description references the removed function name
src/synthorg/engine/workspace/semantic_analyzer.py (4)

35-63: LGTM!

The SemanticAnalyzer protocol is well-designed with:

  • @runtime_checkable decorator enabling isinstance() checks
  • Keyword-only parameters for clarity
  • Clear docstrings documenting arguments and return type
  • Appropriate use of Mapping for read-only base_sources

79-107: LGTM!

The _read_sources function correctly:

  • Resolves the root path once for comparison
  • Validates each file path is relative to the resolved root
  • Logs traversal attempts at WARNING level
  • Catches OSError per PEP 758 guidelines
  • Logs read errors at DEBUG level (consistent with AST analyzer's lower operational significance vs LLM analyzer)

288-296: LGTM!

The deduplication logic correctly uses (file_path, description) as the key, which is appropriate since the same conflict might be detected by both AST and LLM analyzers. The implementation preserves order while efficiently tracking seen conflicts.


156-207: LGTM!

The AstSemanticAnalyzer.analyze method is well-structured:

  • Logs the filtered file count at start (addressing previous review comment)
  • Uses asyncio.to_thread for blocking file I/O
  • Runs synchronous AST checks via _run_ast_checks
  • Properly logs conflicts at WARNING and completion at INFO

Comment on lines +475 to +490
def _file_path_to_module_stem(file_path: str) -> str:
"""Convert a Python file path to a dotted module stem.

Strips ``.py`` suffix, converts ``__init__.py`` to the parent
package name, removes common source root prefixes (``src/``,
``lib/``), and replaces path separators with dots.
"""
stem = file_path.removesuffix(".py")
if stem.endswith(("/__init__", "\\__init__")):
sep = "/" if "/" in stem else "\\"
stem = stem.rsplit(sep, 1)[0]
for prefix in ("src/", "lib/"):
if stem.startswith(prefix):
stem = stem[len(prefix) :]
break
return stem.replace("/", ".").replace("\\", ".")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Module stem conversion has limited prefix handling.

The _file_path_to_module_stem function only strips src/ and lib/ prefixes. Projects with different layouts (e.g., packages/, app/) will have module stems that don't match import statements.

This is acceptable for initial implementation but may cause false negatives in non-standard project layouts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/workspace/semantic_checks.py` around lines 475 - 490, The
module-stem conversion in _file_path_to_module_stem only removes "src/" and
"lib/" prefixes which misses other common layouts; update this function to
handle additional common prefixes (e.g., "packages/", "app/",
"src/main/python/") by replacing the hard-coded ("src/", "lib/") tuple with a
configurable or extended tuple (e.g., MODULE_ROOT_PREFIXES) and iterate over
that to strip any matching prefix before converting separators and trimming
"__init__"; ensure you keep the existing behavior for Windows separators and the
__init__ handling in _file_path_to_module_stem.

Comment on lines +136 to +137
for path, content in changed_files.items():
parts.append(f"\n### {path}\n```python\n{content}\n```\n")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Hardcoded python fence may misrepresent non-Python files.

The build_review_message function wraps all file contents in ```python fences regardless of actual file type. While file_extensions in config defaults to .py, if extended to other languages (e.g., .pyi, .pyx), the fence language hint could confuse the LLM or syntax highlighting.

Consider deriving the fence language from the file extension:

♻️ Suggested improvement
     for path, content in changed_files.items():
-        parts.append(f"\n### {path}\n```python\n{content}\n```\n")
+        ext = path.rsplit(".", 1)[-1] if "." in path else ""
+        lang = {"py": "python", "pyi": "python", "pyx": "cython"}.get(ext, "")
+        parts.append(f"\n### {path}\n```{lang}\n{content}\n```\n")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/workspace/semantic_llm_prompt.py` around lines 136 - 137,
The loop in build_review_message currently hardcodes a "python" code fence for
every changed_files entry which can mislabel non-.py files; update the loop that
iterates over changed_files (the for path, content in changed_files.items()) to
derive a fence language from the file extension (e.g., ext = path.rsplit(".",
1)[-1] if "." in path else ""), map common extensions to languages (e.g.,
{"py":"python","pyi":"python","pyx":"cython"}), and use that mapped lang when
building parts.append(f"\n### {path}\n```{lang}\n{content}\n```\n") falling back
to an empty fence if unknown.

Comment on lines +172 to +240
async def _call_with_retry(
self,
*,
workspace: Workspace,
messages: list[ChatMessage],
tool_def: ToolDefinition,
comp_config: CompletionConfig,
max_retries: int = 2,
) -> tuple[MergeConflict, ...]:
"""Call LLM with retry on parse failure.

Args:
workspace: Workspace for logging context.
messages: Chat messages to send.
tool_def: Tool definition for structured output.
comp_config: Completion configuration.
max_retries: Maximum retry attempts on parse failure.

Returns:
Parsed conflicts, or empty tuple on exhaustion/error.
"""
for attempt in range(1 + max_retries):
try:
response = await self._provider.complete(
messages,
self._model,
tools=[tool_def],
config=comp_config,
)
conflicts = parse_tool_call_response(response)
except ValueError:
if attempt < max_retries:
logger.debug(
WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
workspace_id=workspace.workspace_id,
analyzer="llm",
attempt=attempt,
reason="parse_error",
)
continue
logger.warning(
WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
workspace_id=workspace.workspace_id,
analyzer="llm",
reason="parse_exhausted",
)
return ()
except asyncio.CancelledError:
raise
except Exception:
logger.warning(
WORKSPACE_SEMANTIC_ANALYSIS_FAILED,
workspace_id=workspace.workspace_id,
analyzer="llm",
reason="provider_error",
exc_info=True,
)
return ()
else:
logger.info(
WORKSPACE_SEMANTIC_ANALYSIS_COMPLETE,
workspace_id=workspace.workspace_id,
analyzer="llm",
conflicts=len(conflicts),
attempt=attempt,
)
return conflicts

return () # pragma: no cover
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Retry logic sends identical prompts on parse failure.

The _call_with_retry method retries with the exact same messages and tool_def on ValueError. If the LLM produces deterministic output (e.g., temperature=0 or provider-level caching), retries will fail identically. Consider either:

  1. Documenting this relies on LLM stochasticity, or
  2. Adding a hint to the retry prompt indicating the previous response was malformed.

This is acceptable if llm_temperature defaults to a non-zero value, but worth documenting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/workspace/semantic_llm.py` around lines 172 - 240, The
retry loop in _call_with_retry currently resends identical messages/tool_def on
ValueError; change it to append a short system/user hint on retry attempts to
tell the model the previous response was malformed (e.g., add a ChatMessage like
"Previous response could not be parsed; please return valid JSON using the
specified tool schema." when attempt>0) before calling self._provider.complete,
or alternatively document in the function docstring that successful retries
depend on non-zero llm_temperature in comp_config; update the call site that
constructs messages (used by _call_with_retry) so the hint is injected only for
retry attempts and reference _provider.complete, parse_tool_call_response, and
comp_config in the change.

@Aureliolo Aureliolo merged commit d97283b into main Mar 27, 2026
32 of 34 checks passed
@Aureliolo Aureliolo deleted the feat/semantic-conflict-detection branch March 27, 2026 07:36
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 07:36 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---
#MAJOR CHANGES; We got a somewhat working webui :)

##
[0.5.0](v0.4.9...v0.5.0)
(2026-03-30)


### Features

* add analytics trends and budget forecast API endpoints
([#798](#798))
([16b61f5](16b61f5))
* add department policies to default templates
([#852](#852))
([7a41548](7a41548))
* add remaining activity event types (task_started, tool_used,
delegation, cost_incurred)
([#832](#832))
([4252fac](4252fac))
* agent performance, activity, and history API endpoints
([#811](#811))
([9b75c1d](9b75c1d))
* Agent Profiles and Detail pages (biography, career, performance)
([#874](#874))
([62d7880](62d7880))
* app shell, Storybook, and CI/CD pipeline
([#819](#819))
([d4dde90](d4dde90))
* Approvals page with risk grouping, urgency indicators, batch actions
([#889](#889))
([4e9673d](4e9673d))
* Budget Panel page (P&L dashboard, breakdown charts, forecast)
([#890](#890))
([b63b0f1](b63b0f1))
* build infrastructure layer (API client, auth, WebSocket)
([#815](#815))
([9f01d3e](9f01d3e))
* CLI global options infrastructure, UI modes, exit codes, env vars
([#891](#891))
([fef4fc5](fef4fc5))
* CodeMirror editor and theme preferences toggle
([#905](#905),
[#807](#807))
([#909](#909))
([41fbedc](41fbedc))
* Company page (department/agent management)
([#888](#888))
([cfb88b0](cfb88b0))
* comprehensive hint coverage across all CLI commands
([#900](#900))
([937974e](937974e))
* config system extensions, per-command flags for
init/start/stop/status/logs
([#895](#895))
([32f83fe](32f83fe))
* configurable currency system replacing hardcoded USD
([#854](#854))
([b372551](b372551))
* Dashboard page (metric cards, activity feed, budget burn)
([#861](#861))
([7d519d5](7d519d5))
* department health, provider status, and activity feed endpoints
([#818](#818))
([6d5f196](6d5f196))
* design tokens and core UI components
([#833](#833))
([ed887f2](ed887f2))
* extend approval, meeting, and budget API responses
([#834](#834))
([31472bf](31472bf))
* frontend polish -- real-time UX, accessibility, responsive,
performance ([#790](#790),
[#792](#792),
[#791](#791),
[#793](#793))
([#917](#917))
([f04a537](f04a537))
* implement human roles and access control levels
([#856](#856))
([d6d8a06](d6d8a06))
* implement semantic conflict detection in workspace merge
([#860](#860))
([d97283b](d97283b))
* interaction components and animation patterns
([#853](#853))
([82d4b01](82d4b01))
* Login page + first-run bootstrap + Company page
([#789](#789),
[#888](#888))
([#896](#896))
([8758e8d](8758e8d))
* Meetings page with timeline viz, token bars, contribution formatting
([#788](#788))
([#904](#904))
([b207f46](b207f46))
* Messages page with threading, channel badges, sender indicators
([#787](#787))
([#903](#903))
([28293ad](28293ad))
* Org Chart force-directed view and drag-drop reassignment
([#872](#872),
[#873](#873))
([#912](#912))
([a68a938](a68a938))
* Org Chart page (living nodes, status, CRUD, department health)
([#870](#870))
([0acbdae](0acbdae))
* per-command flags for remaining commands, auto-behavior wiring,
help/discoverability
([#897](#897))
([3f7afa2](3f7afa2))
* Providers page with backend rework -- health, CRUD, subscription auth
([#893](#893))
([9f8dd98](9f8dd98))
* scaffold React + Vite + TypeScript + Tailwind project
([#799](#799))
([bd151aa](bd151aa))
* Settings page with search, dependency indicators, grouped rendering
([#784](#784))
([#902](#902))
([a7b9870](a7b9870))
* Setup Wizard rebuild with template comparison, cost estimator, theme
customization ([#879](#879))
([ae8b50b](ae8b50b))
* setup wizard UX -- template filters, card metadata, provider form
reuse ([#910](#910))
([7f04676](7f04676))
* setup wizard UX overhaul -- mode choice, step reorder, provider fixes
([#907](#907))
([ee964c4](ee964c4))
* structured ModelRequirement in template agent configs
([#795](#795))
([7433548](7433548))
* Task Board page (rich Kanban, filtering, dependency viz)
([#871](#871))
([04a19b0](04a19b0))


### Bug Fixes

* align frontend types with backend and debounce WS refetches
([#916](#916))
([134c11b](134c11b))
* auto-cleanup targets newly pulled images instead of old ones
([#884](#884))
([50e6591](50e6591))
* correct wipe backup-skip flow and harden error handling
([#808](#808))
([c05860f](c05860f))
* improve provider setup in wizard, subscription auth, dashboard bugs
([#914](#914))
([87bf8e6](87bf8e6))
* improve update channel detection and add config get command
([#814](#814))
([6b137f0](6b137f0))
* resolve all ESLint warnings, add zero-warnings enforcement
([#899](#899))
([079b46a](079b46a))
* subscription auth uses api_key, base URL optional for cloud providers
([#915](#915))
([f0098dd](f0098dd))


### Refactoring

* semantic analyzer cleanup -- shared filtering, concurrency, extraction
([#908](#908))
([81372bf](81372bf))


### Documentation

* brand identity and UX design system from
[#765](#765) exploration
([#804](#804))
([389a9f4](389a9f4))
* page structure and information architecture for v0.5.0 dashboard
([#809](#809))
([f8d6d4a](f8d6d4a))
* write UX design guidelines with WCAG-verified color system
([#816](#816))
([4a4594e](4a4594e))


### Tests

* add unit tests for agent hooks and page components
([#875](#875))
([#901](#901))
([1d81546](1d81546))


### CI/CD

* bump actions/deploy-pages from 4.0.5 to 5.0.0 in the major group
([#831](#831))
([01c19de](01c19de))
* bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in
/.github/actions/setup-python-uv in the all group
([#920](#920))
([5f6ba54](5f6ba54))
* bump codecov/codecov-action from 5.5.3 to 6.0.0 in the major group
([#868](#868))
([f22a181](f22a181))
* bump github/codeql-action from 4.34.1 to 4.35.0 in the all group
([#883](#883))
([87a4890](87a4890))
* bump sigstore/cosign-installer from 4.1.0 to 4.1.1 in the
minor-and-patch group
([#830](#830))
([7a69050](7a69050))
* bump the all group with 3 updates
([#923](#923))
([ff27c8e](ff27c8e))
* bump wrangler from 4.76.0 to 4.77.0 in /.github in the minor-and-patch
group ([#822](#822))
([07d43eb](07d43eb))
* bump wrangler from 4.77.0 to 4.78.0 in /.github in the all group
([#882](#882))
([f84118d](f84118d))


### Maintenance

* add design system enforcement hook and component inventory
([#846](#846))
([15abc43](15abc43))
* add dev-only auth bypass for frontend testing
([#885](#885))
([6cdcd8a](6cdcd8a))
* add pre-push rebase check hook
([#855](#855))
([b637a04](b637a04))
* backend hardening -- eviction/size-caps and model validation
([#911](#911))
([81253d9](81253d9))
* bump axios from 1.13.6 to 1.14.0 in /web in the all group across 1
directory ([#922](#922))
([b1b0232](b1b0232))
* bump brace-expansion from 5.0.4 to 5.0.5 in /web
([#862](#862))
([ba4a565](ba4a565))
* bump eslint-plugin-react-refresh from 0.4.26 to 0.5.2 in /web
([#801](#801))
([7574bb5](7574bb5))
* bump faker from 40.11.0 to 40.11.1 in the minor-and-patch group
([#803](#803))
([14d322e](14d322e))
* bump https://github.com/astral-sh/ruff-pre-commit from v0.15.7 to
0.15.8 ([#864](#864))
([f52901e](f52901e))
* bump nginxinc/nginx-unprivileged from `6582a34` to `f99cc61` in
/docker/web in the all group
([#919](#919))
([df85e4f](df85e4f))
* bump nginxinc/nginx-unprivileged from `ccbac1a` to `6582a34` in
/docker/web ([#800](#800))
([f4e9450](f4e9450))
* bump node from `44bcbf4` to `71be405` in /docker/sandbox
([#827](#827))
([91bec67](91bec67))
* bump node from `5209bca` to `cf38e1f` in /docker/web
([#863](#863))
([66d6043](66d6043))
* bump picomatch in /site
([#842](#842))
([5f20bcc](5f20bcc))
* bump recharts 2-&gt;3 and @types/node 22-&gt;25 in /web
([#802](#802))
([a908800](a908800))
* Bump requests from 2.32.5 to 2.33.0
([#843](#843))
([41daf69](41daf69))
* bump smol-toml from 1.6.0 to 1.6.1 in /site
([#826](#826))
([3e5dbe4](3e5dbe4))
* bump the all group with 3 updates
([#921](#921))
([7bace0b](7bace0b))
* bump the minor-and-patch group across 1 directory with 2 updates
([#829](#829))
([93e611f](93e611f))
* bump the minor-and-patch group across 1 directory with 3 updates
([#841](#841))
([7010c8e](7010c8e))
* bump the minor-and-patch group across 1 directory with 3 updates
([#869](#869))
([548cee5](548cee5))
* bump the minor-and-patch group in /site with 2 updates
([#865](#865))
([9558101](9558101))
* bump the minor-and-patch group with 2 updates
([#867](#867))
([4830706](4830706))
* consolidate Dependabot groups to 1 PR per ecosystem
([06d2556](06d2556))
* consolidate Dependabot groups to 1 PR per ecosystem
([#881](#881))
([06d2556](06d2556))
* improve worktree skill with full dep sync and status enhancements
([#906](#906))
([772c625](772c625))
* remove Vue remnants and document framework decision
([#851](#851))
([bf2adf6](bf2adf6))
* update web dependencies and fix brace-expansion CVE
([#880](#880))
([a7a0ed6](a7a0ed6))
* upgrade to Storybook 10 and TypeScript 6
([#845](#845))
([52d95f2](52d95f2))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: implement semantic conflict detection in workspace merge

1 participant