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:
@@ -0,0 +1 @@
|
||||
{"specId": "1a31d9bb-f189-4374-95b4-1bd117d009b6", "workflowType": "requirements-first", "specType": "feature"}
|
||||
@@ -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)
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
# Requirements Document
|
||||
|
||||
## Introduction
|
||||
|
||||
The Teamlandkarte MCP server currently fetches tasks from the database without including the `name` column (a short identifier). Users need to reference tasks by this short name in addition to the full ID or title. This feature adds the `name` column to all task queries and enables task lookup by name.
|
||||
|
||||
## Glossary
|
||||
|
||||
- **MCP_Server**: The Teamlandkarte MCP server that exposes tools for querying tasks and capacities.
|
||||
- **Task**: A published task record stored in the `beschaffungstool_kmp_task_latest` database table.
|
||||
- **Task_Name**: The `name__c` column in the task table, a short human-readable identifier for a task.
|
||||
- **DBClient**: The database client protocol that defines methods for fetching tasks and capacities.
|
||||
- **TrinoClient**: The concrete database client implementation that queries the Trino/Presto backend.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement 1: Include Task Name in Task Model
|
||||
|
||||
**User Story:** As a user, I want the task name (short ID) to be part of the task data, so that I can see and use it when browsing tasks.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. THE Task model SHALL include a `name` field of type optional string.
|
||||
2. WHEN the TrinoClient fetches open tasks, THE TrinoClient SHALL select the `name__c` column from the task table and populate the Task `name` field.
|
||||
3. WHEN the TrinoClient fetches a task by ID, THE TrinoClient SHALL select the `name__c` column from the task table and populate the Task `name` field.
|
||||
4. WHEN the task name column value is NULL in the database, THE Task `name` field SHALL be set to None.
|
||||
|
||||
### Requirement 2: Display Task Name in Output
|
||||
|
||||
**User Story:** As a user, I want to see the task name in task listings and detail views, so that I can reference tasks by their short name.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN the MCP_Server returns a list of open tasks, THE MCP_Server SHALL include the task name in the output for each task.
|
||||
2. WHEN the MCP_Server returns task details, THE MCP_Server SHALL include the task name in the summary table.
|
||||
|
||||
### Requirement 3: Look Up Task by Name
|
||||
|
||||
**User Story:** As a user, I want to retrieve task details by providing only the task name, so that I do not need to remember the full ID.
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. THE DBClient protocol SHALL define a `get_task_by_name` method that accepts a task name string and returns a Task or None.
|
||||
2. WHEN a valid published task name is provided, THE TrinoClient SHALL return the matching Task with all fields populated.
|
||||
3. WHEN a task name that does not match any published task is provided, THE TrinoClient SHALL return None.
|
||||
4. WHEN the user provides a task name to the get_task_details tool, THE MCP_Server SHALL resolve the task using the name and return the full task details.
|
||||
5. IF the provided identifier matches neither a task ID nor a task name, THEN THE MCP_Server SHALL return a "not found" message.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Implementation Plan: Task Name Column
|
||||
|
||||
## Overview
|
||||
|
||||
Add the `name__c` database column to the Task model and all task queries, enabling users to see and look up tasks by their short human-readable name. Changes span model, database layer, protocol, and MCP tool output.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] 1. Add name field to Task model and update TrinoClient queries
|
||||
- [x] 1.1 Add `name: Optional[str]` field to the Task dataclass in `models.py`
|
||||
- Insert `name: Optional[str]` after `id` field
|
||||
- Field must be Optional since `name__c` can be NULL in the database
|
||||
- _Requirements: 1.1, 1.4_
|
||||
|
||||
- [x] 1.2 Update `get_open_tasks` SQL queries in `trino_client.py` to SELECT `t.name__c`
|
||||
- Add `t.name__c` to the unlimited query SELECT clause
|
||||
- Add `t.name__c` to the CTE inner SELECT and outer SELECT in the limited query
|
||||
- Update row unpacking to extract the name value
|
||||
- Populate `name` field in Task construction (map NULL to None)
|
||||
- _Requirements: 1.2, 1.4_
|
||||
|
||||
- [x] 1.3 Update `get_task_by_id` SQL query in `trino_client.py` to SELECT `t.name__c`
|
||||
- Add `t.name__c` to the SELECT clause
|
||||
- Update row unpacking to extract the name value
|
||||
- Populate `name` field in Task construction (map NULL to None)
|
||||
- _Requirements: 1.3, 1.4_
|
||||
|
||||
- [ ]* 1.4 Write property test: Task name field preserves database value
|
||||
- **Property 1: Task name field preserves database value**
|
||||
- Generate random `(name__c_value | None)` values using Hypothesis
|
||||
- Construct Task objects via the row-mapping pattern, assert `task.name == input_value`
|
||||
- **Validates: Requirements 1.1, 1.2, 1.3, 1.4**
|
||||
|
||||
- [x] 2. Add `get_task_by_name` to protocol and TrinoClient
|
||||
- [x] 2.1 Add `get_task_by_name` method to `DBClient` protocol in `database/types.py`
|
||||
- Method signature: `def get_task_by_name(self, name: str) -> Task | None`
|
||||
- Include docstring: "Fetch one published task by its short name (name__c)."
|
||||
- _Requirements: 3.1_
|
||||
|
||||
- [x] 2.2 Implement `get_task_by_name` in `TrinoClient`
|
||||
- SQL query selects same columns as `get_task_by_id` plus `t.name__c`
|
||||
- WHERE clause filters on `t.name__c = ?` and `t.status__c = ?`
|
||||
- ORDER BY `t.createddate DESC` to handle duplicates (return first match)
|
||||
- Reuse same row-assembly logic as `get_task_by_id`
|
||||
- Return None for empty string input or no matching rows
|
||||
- _Requirements: 3.1, 3.2, 3.3_
|
||||
|
||||
- [ ]* 2.3 Write property test: Name lookup returns correct task
|
||||
- **Property 3: Name lookup returns correct task**
|
||||
- Generate random tasks with unique names, mock DB cursor
|
||||
- Call `get_task_by_name(name)`, assert returned task's name matches
|
||||
- **Validates: Requirements 3.2**
|
||||
|
||||
- [x] 3. Checkpoint
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 4. Update MCP tool output formatting
|
||||
- [x] 4.1 Add "Name" column to `list_open_tasks` output table
|
||||
- Add "Name" to the header list (between task_id and Title)
|
||||
- Add `str(task.name or "")` to each row
|
||||
- Update the empty-row fallback to include the extra column
|
||||
- _Requirements: 2.1_
|
||||
|
||||
- [x] 4.2 Add "Name" to `get_task_details` summary table
|
||||
- Add "Name" to the header list
|
||||
- Add `str(task.name or "")` to the row data
|
||||
- _Requirements: 2.2_
|
||||
|
||||
- [ ]* 4.3 Write property test: Task output contains name
|
||||
- **Property 2: Task output contains name**
|
||||
- Generate random Task objects with non-None names using `st.text(min_size=1)`
|
||||
- Format through output functions, assert name appears as substring
|
||||
- **Validates: Requirements 2.1, 2.2**
|
||||
|
||||
- [x] 5. Update `get_task_details` resolution logic
|
||||
- [x] 5.1 Add name-based fallback resolution in `get_task_details` tool
|
||||
- After `get_task_by_id` returns None, call `db_client.get_task_by_name(task_id)`
|
||||
- If both return None, return "Task not found or not published: {task_id}"
|
||||
- _Requirements: 3.4, 3.5_
|
||||
|
||||
- [ ]* 5.2 Write property test: Name resolution equivalence
|
||||
- **Property 4: Name resolution equivalence**
|
||||
- Generate a task with non-None name, mock both lookup methods
|
||||
- Call `get_task_details` with name and with ID, assert outputs identical
|
||||
- **Validates: Requirements 3.4**
|
||||
|
||||
- [ ]* 5.3 Write property test: Unknown identifier returns not-found
|
||||
- **Property 5: Unknown identifier returns not-found**
|
||||
- Generate random strings not matching any known task ID or name
|
||||
- Call `get_task_details`, assert output contains "not found"
|
||||
- **Validates: Requirements 3.3, 3.5**
|
||||
|
||||
- [x] 6. Update FakeDBClient in tests and add `get_task_by_name` stub
|
||||
- Add `name` field to `FakeTask` in existing test files
|
||||
- Add `get_task_by_name` method to `FakeDBClient`
|
||||
- Update existing test assertions that check table headers (add "Name" column)
|
||||
- _Requirements: 3.1_
|
||||
|
||||
- [x] 7. Final checkpoint
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
## Notes
|
||||
|
||||
- Tasks marked with `*` are optional and can be skipped for faster MVP
|
||||
- Each task references specific requirements for traceability
|
||||
- Property tests use Hypothesis with `@settings(max_examples=100)`
|
||||
- The implementation language is Python (matching the existing codebase)
|
||||
Reference in New Issue
Block a user