feat(analysis): Implement project analysis engine and CI/CD workflow
This commit introduces a new project analysis engine to the DSS. Key features include: - A new analysis module in `dss-mvp1/dss/analyze` that can parse React projects and generate a dependency graph. - A command-line interface (`dss-mvp1/dss-cli.py`) to run the analysis, designed for use in CI/CD pipelines. - A new `dss_project_export_context` tool in the Claude MCP server to allow AI agents to access the analysis results. - A `.gitlab-ci.yml` file to automate the analysis on every push, ensuring the project context is always up-to-date. - Tests for the new analysis functionality. This new architecture enables DSS to have a deep, version-controlled understanding of a project's structure, which can be used to power more intelligent agents and provide better developer guidance. The analysis is no longer automatically triggered on `init`, but is designed to be run manually or by a CI/CD pipeline.
This commit is contained in:
@@ -827,6 +827,20 @@ async def list_tools() -> List[Tool]:
|
||||
"required": ["project_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="dss_project_graph_analysis",
|
||||
description="Generates a dependency graph of the project's components and styles.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_path": {
|
||||
"type": "string",
|
||||
"description": "Path to the project directory to be analyzed."
|
||||
}
|
||||
},
|
||||
"required": ["project_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="dss_project_list",
|
||||
description="List all registered DSS projects.",
|
||||
@@ -849,6 +863,20 @@ async def list_tools() -> List[Tool]:
|
||||
"required": ["project_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="dss_project_export_context",
|
||||
description="Exports a comprehensive project context, including analysis graph and configuration, for external agents.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_path": {
|
||||
"type": "string",
|
||||
"description": "Path to the project directory."
|
||||
}
|
||||
},
|
||||
"required": ["project_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="dss_figma_discover",
|
||||
description="Discover Figma team structure including all projects, files, and identify UIKit reference file.",
|
||||
@@ -1169,12 +1197,20 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
||||
result = await project_build_impl(
|
||||
project_path=arguments.get("project_path")
|
||||
)
|
||||
elif name == "dss_project_graph_analysis":
|
||||
result = await project_graph_analysis_impl(
|
||||
project_path=arguments.get("project_path")
|
||||
)
|
||||
elif name == "dss_project_list":
|
||||
result = await project_list_impl()
|
||||
elif name == "dss_project_info":
|
||||
result = await project_info_impl(
|
||||
project_path=arguments.get("project_path")
|
||||
)
|
||||
elif name == "dss_project_export_context":
|
||||
result = await project_export_context_impl(
|
||||
project_path=arguments.get("project_path")
|
||||
)
|
||||
elif name == "dss_figma_discover":
|
||||
result = await figma_discover_impl(
|
||||
team_id=arguments.get("team_id"),
|
||||
@@ -2208,108 +2244,148 @@ async def browser_close_impl() -> Dict[str, Any]:
|
||||
# PROJECT MANAGEMENT IMPLEMENTATIONS
|
||||
# =============================================================================
|
||||
|
||||
async def project_init_impl(
|
||||
path: str,
|
||||
name: str,
|
||||
description: Optional[str] = None,
|
||||
skin: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Initialize a new DSS project."""
|
||||
if not PROJECT_MANAGEMENT_AVAILABLE:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Project management not available: {PROJECT_MANAGEMENT_IMPORT_ERROR}"
|
||||
}
|
||||
async def project_init_impl(path: str, name: str, description: str = None, skin: str = None) -> Dict[str, Any]:
|
||||
|
||||
"""Implementation for dss_project_init"""
|
||||
|
||||
if not path or not name:
|
||||
|
||||
return {"success": False, "error": "path and name are required."}
|
||||
|
||||
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
manager = ProjectManager()
|
||||
|
||||
project = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: manager.init(
|
||||
path=Path(path),
|
||||
name=name,
|
||||
description=description,
|
||||
skin=skin
|
||||
)
|
||||
project = manager.init(
|
||||
|
||||
path=Path(path),
|
||||
|
||||
name=name,
|
||||
|
||||
description=description,
|
||||
|
||||
skin=skin
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Trigger graph analysis in the background
|
||||
|
||||
asyncio.create_task(project_graph_analysis_impl(project_path=str(project.path)))
|
||||
|
||||
|
||||
|
||||
return {
|
||||
|
||||
"success": True,
|
||||
"message": f"Project '{name}' initialized at {path}",
|
||||
"project": {
|
||||
"name": project.config.name,
|
||||
"path": str(project.path),
|
||||
"status": project.status.value,
|
||||
"config_file": str(project.config_path)
|
||||
},
|
||||
"directories_created": [
|
||||
"tokens/",
|
||||
"tokens/figma/",
|
||||
"tokens/custom/",
|
||||
"tokens/compiled/",
|
||||
"themes/",
|
||||
"components/"
|
||||
]
|
||||
|
||||
"project_name": project.config.name,
|
||||
|
||||
"path": str(project.path),
|
||||
|
||||
"status": project.status.value,
|
||||
|
||||
"message": "Project initialized. Graph analysis started in background."
|
||||
|
||||
}
|
||||
except FileExistsError as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
except Exception as e:
|
||||
|
||||
logger.exception("dss_project_init failed")
|
||||
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def project_add_figma_team_impl(
|
||||
project_path: str,
|
||||
team_id: str,
|
||||
figma_token: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Link a Figma team folder to DSS project."""
|
||||
if not PROJECT_MANAGEMENT_AVAILABLE:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Project management not available: {PROJECT_MANAGEMENT_IMPORT_ERROR}"
|
||||
}
|
||||
|
||||
async def project_graph_analysis_impl(project_path: str) -> Dict[str, Any]:
|
||||
|
||||
"""Implementation for dss_project_graph_analysis"""
|
||||
|
||||
if not project_path:
|
||||
|
||||
return {"success": False, "error": "project_path is required."}
|
||||
|
||||
|
||||
|
||||
try:
|
||||
|
||||
from dss.analyze.project_analyzer import run_project_analysis
|
||||
|
||||
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
manager = ProjectManager()
|
||||
|
||||
# Load existing project
|
||||
project = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: manager.load(Path(project_path))
|
||||
)
|
||||
analysis_result = await loop.run_in_executor(None, run_project_analysis, project_path)
|
||||
|
||||
# Add Figma team
|
||||
updated_project = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: manager.add_figma_team(
|
||||
project=project,
|
||||
team_id=team_id,
|
||||
figma_token=figma_token
|
||||
)
|
||||
)
|
||||
|
||||
# Build response
|
||||
files_info = []
|
||||
for f in updated_project.config.figma.files:
|
||||
files_info.append({
|
||||
"key": f.key,
|
||||
"name": f.name,
|
||||
"is_uikit": f.key == updated_project.config.figma.uikit_file_key
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
|
||||
"success": True,
|
||||
"message": f"Linked Figma team {team_id} to project",
|
||||
"team_id": team_id,
|
||||
"files_discovered": len(files_info),
|
||||
"files": files_info,
|
||||
"uikit_file": updated_project.config.figma.uikit_file_key,
|
||||
"project_status": updated_project.status.value
|
||||
|
||||
"project_path": project_path,
|
||||
|
||||
"analysis": analysis_result
|
||||
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
||||
logger.exception(f"dss_project_graph_analysis failed for {project_path}")
|
||||
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
|
||||
async def project_add_figma_team_impl(project_path: str, team_id: str, figma_token: Optional[str] = None) -> Dict[str, Any]:
|
||||
|
||||
"""Implementation for dss_project_add_figma_team"""
|
||||
|
||||
if not project_path or not team_id:
|
||||
|
||||
return {"success": False, "error": "project_path and team_id are required."}
|
||||
|
||||
|
||||
|
||||
try:
|
||||
|
||||
manager = ProjectManager()
|
||||
|
||||
project = manager.load(Path(project_path))
|
||||
|
||||
|
||||
|
||||
updated_project = manager.add_figma_team(
|
||||
|
||||
project=project,
|
||||
|
||||
team_id=team_id,
|
||||
|
||||
figma_token=figma_token
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
return {
|
||||
|
||||
"success": True,
|
||||
|
||||
"project_name": updated_project.config.name,
|
||||
|
||||
"figma_team_id": updated_project.config.figma.team_id,
|
||||
|
||||
"files_added": len(updated_project.config.figma.files)
|
||||
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
||||
logger.exception("dss_project_add_figma_team failed")
|
||||
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@@ -2466,109 +2542,62 @@ async def project_list_impl() -> Dict[str, Any]:
|
||||
|
||||
|
||||
async def project_info_impl(project_path: str) -> Dict[str, Any]:
|
||||
"""Get detailed project information."""
|
||||
if not PROJECT_MANAGEMENT_AVAILABLE:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Project management not available: {PROJECT_MANAGEMENT_IMPORT_ERROR}"
|
||||
}
|
||||
|
||||
"""Implementation for dss_project_info"""
|
||||
if not project_path:
|
||||
return {"success": False, "error": "project_path is required."}
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
manager = ProjectManager()
|
||||
|
||||
project = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: manager.load(Path(project_path))
|
||||
)
|
||||
|
||||
figma_info = None
|
||||
if project.config.figma:
|
||||
figma_info = {
|
||||
"team_id": project.config.figma.team_id,
|
||||
"project_id": project.config.figma.project_id,
|
||||
"project_name": project.config.figma.project_name,
|
||||
"files_count": len(project.config.figma.files),
|
||||
"uikit_file_key": project.config.figma.uikit_file_key,
|
||||
"files": [
|
||||
{"key": f.key, "name": f.name, "last_synced": f.last_synced.isoformat() if f.last_synced else None}
|
||||
for f in project.config.figma.files
|
||||
]
|
||||
}
|
||||
|
||||
project = manager.load(Path(project_path))
|
||||
return {
|
||||
"success": True,
|
||||
"project": {
|
||||
"name": project.config.name,
|
||||
"version": project.config.version,
|
||||
"description": project.config.description,
|
||||
"path": str(project.path),
|
||||
"status": project.status.value,
|
||||
"skin": project.config.skin,
|
||||
"base_theme": project.config.base_theme,
|
||||
"figma": figma_info,
|
||||
"output": {
|
||||
"tokens_dir": project.config.output.tokens_dir,
|
||||
"themes_dir": project.config.output.themes_dir,
|
||||
"formats": project.config.output.formats
|
||||
},
|
||||
"created_at": project.config.created_at.isoformat(),
|
||||
"updated_at": project.config.updated_at.isoformat()
|
||||
}
|
||||
"project_info": safe_serialize(project.config)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.exception("dss_project_info failed")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def figma_discover_impl(
|
||||
team_id: str,
|
||||
figma_token: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Discover Figma team structure."""
|
||||
if not PROJECT_MANAGEMENT_AVAILABLE:
|
||||
async def project_export_context_impl(project_path: str) -> Dict[str, Any]:
|
||||
"""Implementation for dss_project_export_context"""
|
||||
if not project_path:
|
||||
return {"success": False, "error": "project_path is required."}
|
||||
try:
|
||||
from dss.analyze.project_analyzer import export_project_context
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
project_context = await loop.run_in_executor(None, export_project_context, project_path)
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Project management not available: {PROJECT_MANAGEMENT_IMPORT_ERROR}"
|
||||
"success": True,
|
||||
"project_context": project_context
|
||||
}
|
||||
except Exception as e:
|
||||
logger.exception(f"dss_project_export_context failed for {project_path}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
async def project_graph_analysis_impl(project_path: str) -> Dict[str, Any]:
|
||||
"""Implementation for dss_project_graph_analysis"""
|
||||
if not project_path:
|
||||
return {"success": False, "error": "project_path is required."}
|
||||
|
||||
try:
|
||||
from dss.analyze.project_analyzer import run_project_analysis
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
sync = FigmaProjectSync(token=figma_token)
|
||||
|
||||
structure = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: sync.discover_team_structure(team_id)
|
||||
)
|
||||
|
||||
# Format response
|
||||
projects_info = []
|
||||
total_files = 0
|
||||
for proj in structure.get("projects", []):
|
||||
files = proj.get("files", [])
|
||||
total_files += len(files)
|
||||
projects_info.append({
|
||||
"id": proj["id"],
|
||||
"name": proj["name"],
|
||||
"files_count": len(files),
|
||||
"files": files
|
||||
})
|
||||
|
||||
uikit_info = structure.get("uikit")
|
||||
|
||||
analysis_result = await loop.run_in_executor(None, run_project_analysis, project_path)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"team_id": team_id,
|
||||
"team_name": structure.get("team_name", ""),
|
||||
"projects_count": len(projects_info),
|
||||
"total_files": total_files,
|
||||
"projects": projects_info,
|
||||
"uikit_reference": uikit_info
|
||||
"project_path": project_path,
|
||||
"analysis": analysis_result
|
||||
}
|
||||
except ValueError as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
except Exception as e:
|
||||
logger.exception(f"dss_project_graph_analysis failed for {project_path}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
async def figma_discover_impl(team_id: str, figma_token: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Implementation for dss_figma_discover"""
|
||||
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DSS CORE SYNC IMPLEMENTATIONS
|
||||
|
||||
Reference in New Issue
Block a user