Migrate all repos into monorepo context folders

Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1,222 @@
# Design Document: Task Name Column
## Overview
This feature adds the `name__c` database column to the Task model and all task-related queries, enabling users to see and look up tasks by their short human-readable name. The changes span four layers:
1. **Model** Add an optional `name` field to the `Task` dataclass.
2. **Database** Modify SQL queries in `TrinoClient` to SELECT `name__c`; add a new `get_task_by_name` method.
3. **Protocol** Extend the `DBClient` protocol with `get_task_by_name`.
4. **MCP Tools** Include the name in list/detail output; resolve name-based lookups in `get_task_details`.
## Architecture
```mermaid
flowchart TD
A[MCP Tool: list_open_tasks] -->|calls| B[TrinoClient.get_open_tasks]
C[MCP Tool: get_task_details] -->|calls| D[TrinoClient.get_task_by_id]
C -->|calls| E[TrinoClient.get_task_by_name]
B --> F[(Trino DB: name__c)]
D --> F
E --> F
B --> G[Task dataclass with name field]
D --> G
E --> G
```
The identifier resolution logic in `get_task_details` will attempt `get_task_by_id` first. If that returns `None`, it falls back to `get_task_by_name`. This keeps backward compatibility for callers passing IDs while enabling name-based lookup without a separate tool.
## Components and Interfaces
### 1. Task Dataclass (`models.py`)
Add a single field:
```python
@dataclass(frozen=True)
class Task:
id: str
name: Optional[str] # NEW maps to name__c
title: str
description: str
start_date: Optional[date]
end_date: Optional[date]
created_date: datetime
skills: list[str] = field(default_factory=list)
```
The field is `Optional[str]` because the database column can be NULL.
### 2. DBClient Protocol (`database/types.py`)
Add one method:
```python
def get_task_by_name(self, name: str) -> Task | None:
"""Fetch one published task by its short name (name__c)."""
raise NotImplementedError
```
### 3. TrinoClient (`database/trino_client.py`)
**SQL changes** Every SELECT that builds a `Task` must include `t.name__c`. Affected methods:
- `get_open_tasks` add `t.name__c` to both the unlimited and CTE-limited queries.
- `get_task_by_id` add `t.name__c` to the SELECT.
**New method** `get_task_by_name`:
```python
def get_task_by_name(self, name: str) -> Optional[Task]:
"""Fetch a single published task by name__c (case-sensitive)."""
query = """
SELECT
t.id,
t.name__c,
t.title__c,
t.description__c,
t.startdate__c,
t.enddate__c,
t.createddate,
s.skillname__c
FROM beschaffungstool_kmp_task_latest t
LEFT JOIN beschaffungstool_kmp_skill_latest s
ON t.id = s.task__c
WHERE t.name__c = ? AND t.status__c = ?
"""
# Same row-assembly logic as get_task_by_id
```
### 4. MCP Server (`mcp_server.py`)
**`list_open_tasks`** Add a "Name" column to the output table (between task_id and Title).
**`get_task_details`** Change the resolution logic:
```python
@mcp.tool()
async def get_task_details(task_id: str) -> str:
# 1. Try by ID
task = db_client.get_task_by_id(task_id)
# 2. Fallback: try by name
if task is None:
task = db_client.get_task_by_name(task_id)
# 3. Not found
if task is None:
return f"Task not found or not published: {task_id}"
# ... rest unchanged, but include task.name in the summary table
```
Add "Name" to the summary table header and row.
## Data Models
### Task Table Schema (relevant columns)
| Column | Type | Nullable | Maps to |
|--------|------|----------|---------|
| `id` | VARCHAR | No | `Task.id` |
| `name__c` | VARCHAR | Yes | `Task.name` |
| `title__c` | VARCHAR | Yes | `Task.title` |
| `description__c` | VARCHAR | Yes | `Task.description` |
| `startdate__c` | DATE | Yes | `Task.start_date` |
| `enddate__c` | DATE | Yes | `Task.end_date` |
| `createddate` | TIMESTAMP | No | `Task.created_date` |
### Task Dataclass (after change)
```python
@dataclass(frozen=True)
class Task:
id: str
name: Optional[str]
title: str
description: str
start_date: Optional[date]
end_date: Optional[date]
created_date: datetime
skills: list[str] = field(default_factory=list)
```
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Task name field preserves database value
*For any* database row with a `name__c` value (including NULL), when the row is mapped to a `Task` object, `Task.name` must equal the original `name__c` value (with NULL mapped to `None`).
**Validates: Requirements 1.1, 1.2, 1.3, 1.4**
### Property 2: Task output contains name
*For any* `Task` with a non-None `name` field, the formatted output string (from both `list_open_tasks` and `get_task_details`) must contain that name value as a substring.
**Validates: Requirements 2.1, 2.2**
### Property 3: Name lookup returns correct task
*For any* task that exists in the database with a given `name__c` value, calling `get_task_by_name` with that exact name must return a `Task` whose `name` field equals the queried name.
**Validates: Requirements 3.2**
### Property 4: Name resolution equivalence
*For any* published task with a non-None name, calling `get_task_details` with the task's name must produce the same task data as calling `get_task_details` with the task's ID.
**Validates: Requirements 3.4**
### Property 5: Unknown identifier returns not-found
*For any* string that is neither a valid published task ID nor a valid published task name, `get_task_details` must return a not-found message.
**Validates: Requirements 3.3, 3.5**
## Error Handling
| Scenario | Behavior |
|----------|----------|
| `name__c` is NULL in DB | `Task.name` is set to `None`; output displays empty or omits the name cell |
| `get_task_by_name` receives empty string | Returns `None` (no match) |
| `get_task_details` identifier matches neither ID nor name | Returns `"Task not found or not published: {identifier}"` |
| Multiple tasks share the same `name__c` (data quality issue) | `get_task_by_name` returns the first match (ORDER BY createddate DESC) |
The resolution order in `get_task_details` (ID first, then name) ensures that if a name happens to look like an ID, the ID lookup takes precedence. This avoids ambiguity.
## Testing Strategy
### Unit Tests
- Verify `Task` dataclass construction with `name=None` and `name="ABC-123"`.
- Verify `list_open_tasks` output table includes a "Name" column.
- Verify `get_task_details` output table includes the name value.
- Verify `get_task_details` falls back to `get_task_by_name` when ID lookup returns None.
- Verify `get_task_by_name` returns None for empty string input.
### Property-Based Tests
Use **Hypothesis** (Python PBT library) with a minimum of 100 iterations per property.
Each test must be tagged with a comment referencing the design property:
```python
# Feature: task-name-column, Property 1: Task name field preserves database value
```
**Property 1** Generate random `(name__c_value | None)` values, construct Task objects via the row-mapping helper, assert `task.name == input_value`.
**Property 2** Generate random Task objects with non-None names (using `st.text(min_size=1)`), format them through the output functions, assert the name string appears in the output.
**Property 3** Generate a random set of tasks with unique names, mock the DB cursor to return those rows, call `get_task_by_name(name)`, assert the returned task's name matches.
**Property 4** Generate a task with a non-None name, mock both `get_task_by_id` and `get_task_by_name` to return the same task, call `get_task_details` with the name and with the ID, assert both outputs are identical.
**Property 5** Generate random strings, filter out any that match known task IDs or names in the mock data, call `get_task_details`, assert the output contains "not found".
### Test Configuration
```python
from hypothesis import given, settings, strategies as st
@settings(max_examples=100)
```