feat: Add impact analysis for component dependency tracking

- New tables: components, component_dependencies, component_files,
  verification_checks, change_impacts, impact_analysis_runs
- 8 new MCP tools: component_register, component_list,
  component_add_dependency, component_add_file, component_add_check,
  impact_analysis, impact_learn, component_graph
- Seed data: 17 components, 9 dependencies, 12 file patterns, 5 checks
- Historical impacts from session 397 issues recorded

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-01-11 07:20:00 +02:00
parent 5015b1416f
commit 4fb557c624
6 changed files with 2517 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
-- Impact Analysis Schema
-- Tracks system components, dependencies, and verification checks
-- Components registry
CREATE TABLE IF NOT EXISTS components (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL CHECK (type IN ('service', 'script', 'config', 'database', 'api', 'ui', 'library')),
path TEXT,
repo TEXT,
description TEXT,
health_check TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Component dependencies (directed graph)
CREATE TABLE IF NOT EXISTS component_dependencies (
id SERIAL PRIMARY KEY,
component_id TEXT NOT NULL REFERENCES components(id) ON DELETE CASCADE,
depends_on TEXT NOT NULL REFERENCES components(id) ON DELETE CASCADE,
dependency_type TEXT NOT NULL CHECK (dependency_type IN ('hard', 'soft', 'config', 'data')),
description TEXT,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(component_id, depends_on)
);
-- File-to-component mapping (for git diff analysis)
CREATE TABLE IF NOT EXISTS component_files (
id SERIAL PRIMARY KEY,
component_id TEXT NOT NULL REFERENCES components(id) ON DELETE CASCADE,
file_pattern TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(component_id, file_pattern)
);
-- Verification checks per component
CREATE TABLE IF NOT EXISTS verification_checks (
id SERIAL PRIMARY KEY,
component_id TEXT NOT NULL REFERENCES components(id) ON DELETE CASCADE,
name TEXT NOT NULL,
check_type TEXT NOT NULL CHECK (check_type IN ('command', 'http', 'tcp', 'file')),
check_command TEXT NOT NULL,
expected_result TEXT,
timeout_seconds INTEGER DEFAULT 30,
created_at TIMESTAMP DEFAULT NOW()
);
-- Historical change impacts (learned from errors)
CREATE TABLE IF NOT EXISTS change_impacts (
id SERIAL PRIMARY KEY,
changed_component TEXT NOT NULL REFERENCES components(id) ON DELETE CASCADE,
affected_component TEXT NOT NULL REFERENCES components(id) ON DELETE CASCADE,
impact_description TEXT NOT NULL,
error_id TEXT,
task_id TEXT,
learned_at TIMESTAMP DEFAULT NOW()
);
-- Impact analysis runs (audit log)
CREATE TABLE IF NOT EXISTS impact_analysis_runs (
id SERIAL PRIMARY KEY,
task_id TEXT,
triggered_by TEXT NOT NULL CHECK (triggered_by IN ('task_close', 'manual', 'git_push')),
components_analyzed INTEGER DEFAULT 0,
issues_found INTEGER DEFAULT 0,
verification_passed BOOLEAN,
details JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_component_deps_component ON component_dependencies(component_id);
CREATE INDEX IF NOT EXISTS idx_component_deps_depends ON component_dependencies(depends_on);
CREATE INDEX IF NOT EXISTS idx_component_files_component ON component_files(component_id);
CREATE INDEX IF NOT EXISTS idx_change_impacts_changed ON change_impacts(changed_component);
CREATE INDEX IF NOT EXISTS idx_change_impacts_affected ON change_impacts(affected_component);
CREATE INDEX IF NOT EXISTS idx_impact_runs_task ON impact_analysis_runs(task_id);

View File

@@ -0,0 +1,103 @@
-- Seed initial components for impact analysis
-- Run after 009_impact_analysis.sql
-- Docker services on docker-host
INSERT INTO components (id, name, type, path, repo, description, health_check) VALUES
('propertymap-scraper', 'PropertyMap Scraper', 'service', '/opt/docker/propertymap', 'christian/propertymap', 'Scrapes Bazaraki/BuySellCyprus listings', 'docker inspect --format="{{.State.Health.Status}}" propertymap-scraper'),
('propertymap-db', 'PropertyMap Database', 'database', 'litellm-pgvector', NULL, 'PostgreSQL with pgvector for property embeddings', 'docker exec litellm-pgvector pg_isready -U litellm'),
('gridbot-conductor', 'Gridbot Conductor', 'service', '/opt/apps/eToroGridbot', 'christian/eToroGridbot', 'Trading signal processor and order executor', 'curl -s http://localhost:8000/health'),
('litellm', 'LiteLLM Proxy', 'service', '/opt/docker/litellm', NULL, 'LLM API proxy with cost tracking', 'curl -s http://localhost:4000/health'),
('n8n', 'n8n Workflows', 'service', '/opt/docker/n8n', NULL, 'Workflow automation', 'curl -s http://localhost:5678/healthz'),
('gitea', 'Gitea', 'service', '/opt/docker/gitea', NULL, 'Git hosting and CI', 'curl -s http://localhost:3000/api/v1/version')
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
type = EXCLUDED.type,
path = EXCLUDED.path,
repo = EXCLUDED.repo,
description = EXCLUDED.description,
health_check = EXCLUDED.health_check,
updated_at = NOW();
-- Local scripts (AgilitonScripts)
INSERT INTO components (id, name, type, path, repo, description) VALUES
('agiliton-scripts', 'AgilitonScripts', 'library', '~/Development/Infrastructure/AgilitonScripts', 'christian/AgilitonScripts', 'CLI tools and automation scripts'),
('task-cli', 'Task CLI', 'script', '~/Development/Infrastructure/AgilitonScripts/bin/task', 'christian/AgilitonScripts', 'Task management CLI wrapper'),
('backup-restic', 'Backup Restic', 'script', '~/Development/Infrastructure/AgilitonScripts/bin/backup-restic', 'christian/AgilitonScripts', 'Restic backup to Hetzner S3'),
('vault-cli', 'Vault CLI', 'script', '~/Development/Infrastructure/AgilitonScripts/bin/vault', 'christian/AgilitonScripts', 'GPG-encrypted credential vault')
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
path = EXCLUDED.path,
repo = EXCLUDED.repo,
description = EXCLUDED.description,
updated_at = NOW();
-- MCP servers
INSERT INTO components (id, name, type, path, repo, description) VALUES
('task-mcp', 'Task MCP', 'service', '~/Development/Infrastructure/mcp-servers/task-mcp', NULL, 'Task management MCP server'),
('gridbot-mcp', 'Gridbot MCP', 'api', 'docker-host:8000', 'christian/eToroGridbot', 'Trading operations MCP proxy'),
('vault-mcp', 'Vault MCP', 'api', '~/bin/vault-mcp', 'christian/AgilitonScripts', 'Credential vault MCP server'),
('gitea-mcp', 'Gitea MCP', 'api', '~/bin/gitea-mcp', NULL, 'Gitea operations MCP server')
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
path = EXCLUDED.path,
repo = EXCLUDED.repo,
description = EXCLUDED.description,
updated_at = NOW();
-- Configuration files
INSERT INTO components (id, name, type, path, description) VALUES
('claude-config', 'Claude Code Config', 'config', '~/.claude.json', 'MCP server and Claude Code configuration'),
('ssh-tunnel-config', 'SSH Tunnel Config', 'config', '~/Library/LaunchAgents/eu.agiliton.ssh-tunnel.plist', 'autossh tunnels to docker-host'),
('zshrc', 'Zsh Config', 'config', '~/.zshrc', 'Shell configuration and PATH')
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
path = EXCLUDED.path,
description = EXCLUDED.description,
updated_at = NOW();
-- Dependencies
INSERT INTO component_dependencies (component_id, depends_on, dependency_type, description) VALUES
('propertymap-scraper', 'propertymap-db', 'hard', 'Stores scraped properties'),
('propertymap-scraper', 'litellm', 'soft', 'Uses LLM for embeddings'),
('gridbot-conductor', 'gridbot-mcp', 'config', 'MCP exposes API'),
('task-mcp', 'propertymap-db', 'hard', 'Uses litellm-pgvector database'),
('task-mcp', 'ssh-tunnel-config', 'hard', 'Needs port 5435 tunnel'),
('task-cli', 'task-mcp', 'soft', 'CLI can use MCP or direct DB'),
('agiliton-scripts', 'zshrc', 'config', 'PATH configuration'),
('vault-mcp', 'vault-cli', 'hard', 'MCP wraps vault CLI'),
('gitea-mcp', 'ssh-tunnel-config', 'hard', 'Needs port 3000 tunnel')
ON CONFLICT (component_id, depends_on) DO UPDATE SET
dependency_type = EXCLUDED.dependency_type,
description = EXCLUDED.description;
-- File patterns for git diff analysis
INSERT INTO component_files (component_id, file_pattern) VALUES
('propertymap-scraper', 'propertymap/services/*.py'),
('propertymap-scraper', 'propertymap/scraper/*.py'),
('propertymap-scraper', 'propertymap/docker-compose.yml'),
('gridbot-conductor', 'eToroGridbot/conductor/*.py'),
('gridbot-conductor', 'eToroGridbot/docker-compose.yml'),
('task-mcp', 'mcp-servers/task-mcp/src/*.ts'),
('task-mcp', 'mcp-servers/task-mcp/migrations/*.sql'),
('task-cli', 'AgilitonScripts/bin/task'),
('agiliton-scripts', 'AgilitonScripts/bin/*'),
('claude-config', '.claude.json'),
('ssh-tunnel-config', 'Library/LaunchAgents/eu.agiliton.ssh-tunnel.plist'),
('zshrc', '.zshrc')
ON CONFLICT (component_id, file_pattern) DO NOTHING;
-- Verification checks
INSERT INTO verification_checks (component_id, name, check_type, check_command, expected_result) VALUES
('propertymap-scraper', 'container-health', 'command', 'ssh docker-host "docker inspect --format={{.State.Health.Status}} propertymap-scraper"', 'healthy'),
('propertymap-db', 'db-ready', 'command', 'ssh docker-host "docker exec litellm-pgvector pg_isready -U litellm"', 'accepting connections'),
('gridbot-conductor', 'api-health', 'http', 'http://localhost:8000/health', '{"status":"ok"}'),
('task-mcp', 'db-connection', 'tcp', 'localhost:5435', 'connected'),
('ssh-tunnel-config', 'tunnel-active', 'command', 'pgrep -f "ssh.*5435" || pgrep -f autossh', 'running')
ON CONFLICT DO NOTHING;
-- Historical impacts (learned from this session)
INSERT INTO change_impacts (changed_component, affected_component, impact_description, task_id) VALUES
('propertymap-db', 'propertymap-scraper', 'pgvector migration required updating processor.py in services/ directory, not scraper/services/', NULL),
('agiliton-scripts', 'backup-restic', 'Path changed from ~/Development/AgilitonScripts to ~/Development/Infrastructure/AgilitonScripts', NULL),
('task-mcp', 'task-cli', 'CLI uses project name in INSERT but database expects project key - foreign key violation', NULL)
ON CONFLICT DO NOTHING;

1860
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,16 @@ import { taskDelegations, taskDelegationQuery } from './tools/delegations.js';
import { projectLock, projectUnlock, projectLockStatus, projectContext } from './tools/locks.js';
import { versionAdd, versionList, versionShow, versionUpdate, versionRelease, versionAssignTask } from './tools/versions.js';
import { taskCommitAdd, taskCommitRemove, taskCommitsList, taskLinkCommits, sessionTasks } from './tools/commits.js';
import {
componentRegister,
componentList,
componentAddDependency,
componentAddFile,
componentAddCheck,
impactAnalysis,
impactLearn,
componentGraph,
} from './tools/impact.js';
// Create MCP server
const server = new Server(
@@ -266,6 +276,50 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
});
break;
// Impact Analysis
case 'component_register':
result = JSON.stringify(await componentRegister(a.id, a.name, a.type, {
path: a.path,
repo: a.repo,
description: a.description,
health_check: a.health_check,
}), null, 2);
break;
case 'component_list':
result = JSON.stringify(await componentList(a.type), null, 2);
break;
case 'component_add_dependency':
result = JSON.stringify(await componentAddDependency(
a.component_id,
a.depends_on,
a.dependency_type,
a.description
), null, 2);
break;
case 'component_add_file':
result = JSON.stringify(await componentAddFile(a.component_id, a.file_pattern), null, 2);
break;
case 'component_add_check':
result = JSON.stringify(await componentAddCheck(a.component_id, a.name, a.check_type, a.check_command, {
expected_result: a.expected_result,
timeout_seconds: a.timeout_seconds,
}), null, 2);
break;
case 'impact_analysis':
result = JSON.stringify(await impactAnalysis(a.changed_files), null, 2);
break;
case 'impact_learn':
result = JSON.stringify(await impactLearn(
a.changed_component,
a.affected_component,
a.impact_description,
{ error_id: a.error_id, task_id: a.task_id }
), null, 2);
break;
case 'component_graph':
result = JSON.stringify(await componentGraph(a.component_id), null, 2);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}

311
src/tools/impact.ts Normal file
View File

@@ -0,0 +1,311 @@
// Impact Analysis Tools
// Track components, dependencies, and verify changes
import { query, queryOne, execute } from '../db.js';
interface Component {
id: string;
name: string;
type: string;
path?: string;
repo?: string;
description?: string;
health_check?: string;
}
interface ComponentDependency {
id: number;
component_id: string;
depends_on: string;
dependency_type: string;
description?: string;
}
interface VerificationCheck {
id: number;
component_id: string;
name: string;
check_type: string;
check_command: string;
expected_result?: string;
timeout_seconds: number;
}
/**
* Register a new component
*/
export async function componentRegister(
id: string,
name: string,
type: string,
options: {
path?: string;
repo?: string;
description?: string;
health_check?: string;
} = {}
): Promise<{ success: boolean; component: Component }> {
await execute(
`INSERT INTO components (id, name, type, path, repo, description, health_check)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
type = EXCLUDED.type,
path = EXCLUDED.path,
repo = EXCLUDED.repo,
description = EXCLUDED.description,
health_check = EXCLUDED.health_check,
updated_at = NOW()`,
[id, name, type, options.path || null, options.repo || null, options.description || null, options.health_check || null]
);
const component = await queryOne<Component>(
'SELECT * FROM components WHERE id = $1',
[id]
);
return { success: true, component: component! };
}
/**
* List all components
*/
export async function componentList(
type?: string
): Promise<Component[]> {
if (type) {
return query<Component>(
'SELECT * FROM components WHERE type = $1 ORDER BY name',
[type]
);
}
return query<Component>(
'SELECT * FROM components ORDER BY type, name'
);
}
/**
* Add a dependency between components
*/
export async function componentAddDependency(
component_id: string,
depends_on: string,
dependency_type: string,
description?: string
): Promise<{ success: boolean; message: string }> {
// Verify both components exist
const source = await queryOne<Component>('SELECT id FROM components WHERE id = $1', [component_id]);
const target = await queryOne<Component>('SELECT id FROM components WHERE id = $1', [depends_on]);
if (!source) return { success: false, message: `Component ${component_id} not found` };
if (!target) return { success: false, message: `Component ${depends_on} not found` };
await execute(
`INSERT INTO component_dependencies (component_id, depends_on, dependency_type, description)
VALUES ($1, $2, $3, $4)
ON CONFLICT (component_id, depends_on) DO UPDATE SET
dependency_type = EXCLUDED.dependency_type,
description = EXCLUDED.description`,
[component_id, depends_on, dependency_type, description || null]
);
return { success: true, message: `Added dependency: ${component_id} -> ${depends_on} (${dependency_type})` };
}
/**
* Add file pattern to component mapping
*/
export async function componentAddFile(
component_id: string,
file_pattern: string
): Promise<{ success: boolean; message: string }> {
const component = await queryOne<Component>('SELECT id FROM components WHERE id = $1', [component_id]);
if (!component) return { success: false, message: `Component ${component_id} not found` };
await execute(
`INSERT INTO component_files (component_id, file_pattern)
VALUES ($1, $2)
ON CONFLICT (component_id, file_pattern) DO NOTHING`,
[component_id, file_pattern]
);
return { success: true, message: `Added file pattern ${file_pattern} to ${component_id}` };
}
/**
* Add verification check to component
*/
export async function componentAddCheck(
component_id: string,
name: string,
check_type: string,
check_command: string,
options: { expected_result?: string; timeout_seconds?: number } = {}
): Promise<{ success: boolean; check_id: number }> {
const component = await queryOne<Component>('SELECT id FROM components WHERE id = $1', [component_id]);
if (!component) throw new Error(`Component ${component_id} not found`);
const result = await queryOne<{ id: number }>(
`INSERT INTO verification_checks (component_id, name, check_type, check_command, expected_result, timeout_seconds)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id`,
[component_id, name, check_type, check_command, options.expected_result || null, options.timeout_seconds || 30]
);
return { success: true, check_id: result!.id };
}
/**
* Analyze impact of changes to files
*/
export async function impactAnalysis(
changed_files: string[]
): Promise<{
affected_components: string[];
downstream_components: string[];
verification_checks: VerificationCheck[];
historical_issues: Array<{ component: string; description: string }>;
}> {
const affected = new Set<string>();
const downstream = new Set<string>();
// Find components affected by file changes
for (const file of changed_files) {
const matches = await query<{ component_id: string }>(
`SELECT DISTINCT component_id FROM component_files
WHERE $1 LIKE REPLACE(file_pattern, '*', '%')
OR $1 = file_pattern`,
[file]
);
for (const m of matches) {
affected.add(m.component_id);
}
}
// Find downstream dependencies (what depends on affected components)
for (const comp of affected) {
const deps = await query<{ component_id: string }>(
`WITH RECURSIVE downstream AS (
SELECT component_id FROM component_dependencies WHERE depends_on = $1
UNION
SELECT cd.component_id FROM component_dependencies cd
JOIN downstream d ON cd.depends_on = d.component_id
)
SELECT component_id FROM downstream`,
[comp]
);
for (const d of deps) {
downstream.add(d.component_id);
}
}
// Get verification checks for affected + downstream
const allAffected = [...affected, ...downstream];
const checks: VerificationCheck[] = allAffected.length > 0
? await query<VerificationCheck>(
`SELECT * FROM verification_checks WHERE component_id = ANY($1)`,
[allAffected]
)
: [];
// Get historical issues
const historicalIssues = allAffected.length > 0
? await query<{ component: string; description: string }>(
`SELECT affected_component as component, impact_description as description
FROM change_impacts
WHERE changed_component = ANY($1)
ORDER BY learned_at DESC
LIMIT 10`,
[[...affected]]
)
: [];
return {
affected_components: [...affected],
downstream_components: [...downstream],
verification_checks: checks,
historical_issues: historicalIssues,
};
}
/**
* Record a learned impact (from error discovery)
*/
export async function impactLearn(
changed_component: string,
affected_component: string,
impact_description: string,
options: { error_id?: string; task_id?: string } = {}
): Promise<{ success: boolean; id: number }> {
const result = await queryOne<{ id: number }>(
`INSERT INTO change_impacts (changed_component, affected_component, impact_description, error_id, task_id)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`,
[changed_component, affected_component, impact_description, options.error_id || null, options.task_id || null]
);
return { success: true, id: result!.id };
}
/**
* Get component dependency graph
*/
export async function componentGraph(
component_id?: string
): Promise<{
nodes: Array<{ id: string; name: string; type: string }>;
edges: Array<{ from: string; to: string; type: string }>;
}> {
const nodes = component_id
? await query<{ id: string; name: string; type: string }>(
`WITH RECURSIVE deps AS (
SELECT id, name, type FROM components WHERE id = $1
UNION
SELECT c.id, c.name, c.type FROM components c
JOIN component_dependencies cd ON c.id = cd.depends_on
JOIN deps d ON cd.component_id = d.id
UNION
SELECT c.id, c.name, c.type FROM components c
JOIN component_dependencies cd ON c.id = cd.component_id
JOIN deps d ON cd.depends_on = d.id
)
SELECT DISTINCT id, name, type FROM deps`,
[component_id]
)
: await query<{ id: string; name: string; type: string }>(
'SELECT id, name, type FROM components'
);
const nodeIds = nodes.map(n => n.id);
const edges = nodeIds.length > 0
? await query<{ from: string; to: string; type: string }>(
`SELECT component_id as "from", depends_on as "to", dependency_type as type
FROM component_dependencies
WHERE component_id = ANY($1) AND depends_on = ANY($1)`,
[nodeIds]
)
: [];
return { nodes, edges };
}
/**
* Log an impact analysis run
*/
export async function logImpactRun(
triggered_by: string,
components_analyzed: number,
issues_found: number,
verification_passed: boolean,
details: object,
task_id?: string
): Promise<{ id: number }> {
const result = await queryOne<{ id: number }>(
`INSERT INTO impact_analysis_runs (task_id, triggered_by, components_analyzed, issues_found, verification_passed, details)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id`,
[task_id || null, triggered_by, components_analyzed, issues_found, verification_passed, JSON.stringify(details)]
);
return { id: result!.id };
}

View File

@@ -435,4 +435,115 @@ export const toolDefinitions = [
properties: {},
},
},
// Impact Analysis Tools
{
name: 'component_register',
description: 'Register a system component for impact analysis tracking',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique component ID (e.g., propertymap-scraper, gridbot-conductor)' },
name: { type: 'string', description: 'Human-readable name' },
type: { type: 'string', enum: ['service', 'script', 'config', 'database', 'api', 'ui', 'library'], description: 'Component type' },
path: { type: 'string', description: 'File system path or Docker container name' },
repo: { type: 'string', description: 'Git repository (e.g., christian/propertymap)' },
description: { type: 'string', description: 'What this component does' },
health_check: { type: 'string', description: 'Command or URL to check health' },
},
required: ['id', 'name', 'type'],
},
},
{
name: 'component_list',
description: 'List registered components, optionally filtered by type',
inputSchema: {
type: 'object',
properties: {
type: { type: 'string', enum: ['service', 'script', 'config', 'database', 'api', 'ui', 'library'], description: 'Filter by component type' },
},
},
},
{
name: 'component_add_dependency',
description: 'Add a dependency between two components',
inputSchema: {
type: 'object',
properties: {
component_id: { type: 'string', description: 'Source component ID' },
depends_on: { type: 'string', description: 'Target component ID (what source depends on)' },
dependency_type: { type: 'string', enum: ['hard', 'soft', 'config', 'data'], description: 'Type of dependency' },
description: { type: 'string', description: 'Description of the dependency' },
},
required: ['component_id', 'depends_on', 'dependency_type'],
},
},
{
name: 'component_add_file',
description: 'Map a file pattern to a component (for git diff analysis)',
inputSchema: {
type: 'object',
properties: {
component_id: { type: 'string', description: 'Component ID' },
file_pattern: { type: 'string', description: 'File pattern (e.g., src/services/*.py, docker-compose.yml)' },
},
required: ['component_id', 'file_pattern'],
},
},
{
name: 'component_add_check',
description: 'Add a verification check to a component',
inputSchema: {
type: 'object',
properties: {
component_id: { type: 'string', description: 'Component ID' },
name: { type: 'string', description: 'Check name (e.g., health-endpoint, container-running)' },
check_type: { type: 'string', enum: ['command', 'http', 'tcp', 'file'], description: 'Type of check' },
check_command: { type: 'string', description: 'Command/URL to execute' },
expected_result: { type: 'string', description: 'Expected output or status' },
timeout_seconds: { type: 'number', description: 'Timeout in seconds (default: 30)' },
},
required: ['component_id', 'name', 'check_type', 'check_command'],
},
},
{
name: 'impact_analysis',
description: 'Analyze which components are affected by file changes',
inputSchema: {
type: 'object',
properties: {
changed_files: {
type: 'array',
items: { type: 'string' },
description: 'List of changed file paths',
},
},
required: ['changed_files'],
},
},
{
name: 'impact_learn',
description: 'Record a learned impact relationship (when we discover a missed dependency)',
inputSchema: {
type: 'object',
properties: {
changed_component: { type: 'string', description: 'Component that was changed' },
affected_component: { type: 'string', description: 'Component that was unexpectedly affected' },
impact_description: { type: 'string', description: 'What went wrong' },
error_id: { type: 'string', description: 'Related error ID from error memory' },
task_id: { type: 'string', description: 'Related task ID' },
},
required: ['changed_component', 'affected_component', 'impact_description'],
},
},
{
name: 'component_graph',
description: 'Get component dependency graph (for visualization)',
inputSchema: {
type: 'object',
properties: {
component_id: { type: 'string', description: 'Center component (optional, shows all if omitted)' },
},
},
},
];