feat: Add project_context tool and CWD auto-detection

New features:
- detectProjectFromCwd(): Maps directory paths to project keys
- project_context: Returns project, tasks, epics, lock status
- Auto-detection for 18 known projects (ST, VPN, OWUI, etc.)
- Falls back to extracting from Apps/X or Infrastructure/X paths

Use project_context at session start to see only relevant tasks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-01-10 09:26:33 +02:00
parent 837fb8060c
commit 99f5828f60
4 changed files with 170 additions and 4 deletions

View File

@@ -272,4 +272,12 @@ export const toolDefinitions = [
},
},
},
{
name: 'project_context',
description: 'Get project context from current directory - returns detected project, open tasks, epics, and lock status. Use at session start.',
inputSchema: {
type: 'object',
properties: {},
},
},
];

View File

@@ -1,6 +1,6 @@
// Project lock operations for session exclusivity
import { query, queryOne, execute, getProjectKey } from '../db.js';
import { query, queryOne, execute, getProjectKey, detectProjectFromCwd } from '../db.js';
interface ProjectLock {
project: string;
@@ -150,3 +150,93 @@ export async function projectLockStatus(args: LockStatusArgs): Promise<string> {
return lines.join('\n');
}
interface Task {
id: string;
title: string;
type: string;
status: string;
priority: string;
}
interface Epic {
id: string;
title: string;
status: string;
}
/**
* Get project context from CWD - returns project key, open tasks, and epics
*/
export async function projectContext(): Promise<string> {
const projectKey = detectProjectFromCwd();
if (!projectKey) {
return 'No project detected from current directory';
}
// Ensure project exists
await execute(
`INSERT INTO projects (key, name) VALUES ($1, $1) ON CONFLICT (key) DO NOTHING`,
[projectKey]
);
// Get open tasks for this project
const tasks = await query<Task>(
`SELECT id, title, type, status, priority FROM tasks
WHERE project = $1 AND status != 'completed'
ORDER BY
CASE priority WHEN 'P0' THEN 0 WHEN 'P1' THEN 1 WHEN 'P2' THEN 2 ELSE 3 END,
created_at DESC
LIMIT 10`,
[projectKey]
);
// Get open epics for this project
const epics = await query<Epic>(
`SELECT id, title, status FROM epics
WHERE project = $1 AND status != 'completed'
ORDER BY created_at DESC
LIMIT 5`,
[projectKey]
);
// Check lock status
const lock = await queryOne<ProjectLock>(
`SELECT * FROM project_locks WHERE project = $1`,
[projectKey]
);
let result = `Project: ${projectKey}\n`;
result += `CWD: ${process.cwd()}\n`;
if (lock) {
const now = new Date();
const expired = lock.expires_at && new Date(lock.expires_at) < now;
result += `Lock: ${expired ? 'EXPIRED' : 'ACTIVE'} (session: ${lock.session_id.substring(0, 8)}...)\n`;
} else {
result += `Lock: None\n`;
}
result += `\n`;
if (epics.length > 0) {
result += `Epics (${epics.length}):\n`;
epics.forEach(e => {
result += ` ${e.id}: ${e.title} [${e.status}]\n`;
});
result += `\n`;
}
if (tasks.length > 0) {
result += `Tasks (${tasks.length}):\n`;
tasks.forEach(t => {
const icon = t.status === 'in_progress' ? '>' : ' ';
result += `${icon} ${t.priority} ${t.id}: ${t.title} [${t.type}]\n`;
});
} else {
result += `No open tasks\n`;
}
return result;
}