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:
@@ -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: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user