feat: multi-repo workspace — org settings, project repos, deliverable types

- sd_projects: add workspace_dir field
- sd_project_repos: new table for multi-repo per project
- sd_org_settings: org-level workspace_root + skills_dir
- pipeline_deliverables: add repo_name, target_path, deliverable_type enum
- cockpit_chat.dspy: load repos+org settings, pass to LLM prompt
This commit is contained in:
yumoqing 2026-08-03 17:13:01 +08:00
parent 95bd140393
commit f86cce02f4
8 changed files with 166 additions and 8 deletions

View File

@ -0,0 +1,17 @@
{
"tblname": "sd_org_settings",
"title": "组织SDLC设置",
"params": {
"sortby": ["created_at desc"],
"logined_userorgid": "org_id",
"browserfields": {
"exclouded": []
},
"editexclouded": ["id", "created_at", "updated_at", "org_id"],
"editable": {
"new_data_url": "{{entire_url('../api/sd_org_setting_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_org_setting_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_org_setting_delete.dspy')}}"
}
}
}

View File

@ -68,6 +68,12 @@
"title": "迭代列表",
"url": "{{entire_url('../sd_iteration_list?project_id=${id}')}}",
"subtable": "sd_iterations"
},
{
"field": "project_id",
"title": "代码仓库",
"url": "{{entire_url('../sd_project_repo_list?project_id=${id}')}}",
"subtable": "sd_project_repos"
}
]
}

View File

@ -0,0 +1,17 @@
{
"tblname": "sd_project_repos",
"title": "代码仓库",
"params": {
"sortby": ["created_at desc"],
"logined_userorgid": "org_id",
"browserfields": {
"exclouded": ["org_id"]
},
"editexclouded": ["id", "created_at", "org_id"],
"editable": {
"new_data_url": "{{entire_url('../api/sd_project_repo_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_project_repo_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_project_repo_delete.dspy')}}"
}
}
}

View File

@ -0,0 +1,36 @@
{
"summary": [
{
"name": "pipeline_deliverables",
"title": "任务交付件",
"primary": ["id"],
"catelog": "entity"
}
],
"fields": [
{"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "project_id", "title": "项目ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "task_id", "title": "任务ID", "type": "str", "length": 32},
{"name": "title", "title": "交付件标题", "type": "str", "length": 200, "nullable": "no"},
{"name": "deliverable_type", "title": "交付件类型", "type": "str", "length": 32, "nullable": "no", "default": "'code'"},
{"name": "repo_name", "title": "目标仓库别名", "type": "str", "length": 100},
{"name": "target_path", "title": "仓库内相对路径", "type": "str", "length": 500},
{"name": "file_path", "title": "本地存储路径", "type": "str", "length": 500},
{"name": "content", "title": "交付件内容", "type": "longtext"},
{"name": "quality_score", "title": "质量评分(0-100)", "type": "int", "nullable": "no", "default": "80"},
{"name": "review_status", "title": "评审状态", "type": "str", "length": 20, "nullable": "no", "default": "'pending'"},
{"name": "review_comment", "title": "评审意见", "type": "text"},
{"name": "reviewed_by", "title": "评审人", "type": "str", "length": 32},
{"name": "created_by", "title": "创建人", "type": "str", "length": 32, "nullable": "no"},
{"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"}
],
"indexes": [
{"name": "idx_pd_project", "idxtype": "index", "idxfields": ["project_id"]},
{"name": "idx_pd_task", "idxtype": "index", "idxfields": ["task_id"]},
{"name": "idx_pd_review", "idxtype": "index", "idxfields": ["review_status"]}
],
"codes": [
{"field": "deliverable_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='deliverable_type'"},
{"field": "review_status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='review_status'"}
]
}

View File

@ -0,0 +1,21 @@
{
"summary": [
{
"name": "sd_org_settings",
"title": "组织SDLC设置",
"primary": ["id"],
"catelog": "entity"
}
],
"fields": [
{"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "org_id", "title": "组织ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "workspace_root", "title": "工作空间根路径", "type": "str", "length": 500, "nullable": "no"},
{"name": "skills_dir", "title": "企业Skills目录", "type": "str", "length": 500},
{"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"},
{"name": "updated_at", "title": "更新时间", "type": "timestamp"}
],
"indexes": [
{"name": "idx_sos_org", "idxtype": "unique", "idxfields": ["org_id"]}
]
}

View File

@ -0,0 +1,24 @@
{
"summary": [
{
"name": "sd_project_repos",
"title": "项目代码仓库",
"primary": ["id"],
"catelog": "entity"
}
],
"fields": [
{"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "project_id", "title": "项目ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "repo_name", "title": "仓库别名", "type": "str", "length": 100, "nullable": "no"},
{"name": "repo_url", "title": "Git地址", "type": "str", "length": 500, "nullable": "no"},
{"name": "default_branch", "title": "默认分支", "type": "str", "length": 100, "nullable": "no", "default": "'main'"},
{"name": "local_path", "title": "本地相对路径", "type": "str", "length": 200},
{"name": "org_id", "title": "组织ID", "type": "str", "length": 32, "nullable": "no", "default": "'0'"},
{"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"}
],
"indexes": [
{"name": "idx_spr_project", "idxtype": "index", "idxfields": ["project_id"]},
{"name": "idx_spr_org", "idxtype": "index", "idxfields": ["org_id"]}
]
}

View File

@ -13,7 +13,8 @@
{"name": "description", "title": "项目描述", "type": "text"},
{"name": "project_type", "title": "项目类型", "type": "str", "length": 50, "nullable": "no", "default": "'web_app'"},
{"name": "tech_stack", "title": "技术栈配置(JSON)", "type": "text"},
{"name": "repo_url", "title": "代码仓库地址", "type": "str", "length": 500},
{"name": "repo_url", "title": "主仓库地址", "type": "str", "length": 500},
{"name": "workspace_dir", "title": "项目工作目录", "type": "str", "length": 500},
{"name": "pipeline_id", "title": "关联Pipeline定义ID", "type": "str", "length": 32},
{"name": "status", "title": "项目状态", "type": "str", "length": 20, "nullable": "no", "default": "'draft'"},
{"name": "org_id", "title": "组织ID", "type": "str", "length": 32, "nullable": "no", "default": "'0'"},

View File

@ -53,18 +53,34 @@ async def _load_context(sor, uid):
"SELECT current_project_id, current_iteration_id FROM pipeline_agent_settings WHERE user_id=${uid}$",
{"uid": uid}
)
ctx = {'project_id': '', 'iteration_id': '', 'project_name': '', 'iteration_name': ''}
ctx = {'project_id': '', 'iteration_id': '', 'project_name': '', 'iteration_name': '',
'workspace_dir': '', 'workspace_root': '', 'skills_dir': '', 'repos': []}
if recs:
r = recs[0]
ctx['project_id'] = getattr(r, 'current_project_id', '') or ''
ctx['iteration_id'] = getattr(r, 'current_iteration_id', '') or ''
if ctx['project_id']:
projs = await sor.sqlExe(
"SELECT name, workspace_dir FROM sd_projects WHERE id=${pid}$", {"pid": ctx['project_id']}
"SELECT name, workspace_dir, org_id FROM sd_projects WHERE id=${pid}$", {"pid": ctx['project_id']}
)
if projs:
ctx['project_name'] = getattr(projs[0], 'name', '')
ctx['workspace_dir'] = getattr(projs[0], 'workspace_dir', '') or ''
org_id = getattr(projs[0], 'org_id', '') or '0'
# Load org settings
orgs = await sor.sqlExe(
"SELECT workspace_root, skills_dir FROM sd_org_settings WHERE org_id=${oid}$", {"oid": org_id}
)
if orgs:
ctx['workspace_root'] = getattr(orgs[0], 'workspace_root', '') or ''
ctx['skills_dir'] = getattr(orgs[0], 'skills_dir', '') or ''
# Load repos
repos = await sor.sqlExe(
"SELECT repo_name, repo_url, default_branch, local_path FROM sd_project_repos WHERE project_id=${pid}$",
{"pid": ctx['project_id']}
)
ctx['repos'] = [{'name': r.repo_name, 'url': r.repo_url,
'branch': r.default_branch, 'path': r.local_path or ''} for r in (repos or [])]
if ctx['iteration_id']:
iters = await sor.sqlExe(
"SELECT iteration_name FROM sd_iterations WHERE id=${iid}$", {"iid": ctx['iteration_id']}
@ -407,20 +423,40 @@ if action == 'send_message':
if not tasks:
agent_reply = f"项目「{ctx['project_name']}」暂无待执行任务。\n\n请先提交开发任务例如设计用户表结构"
else:
# Execute each pending task
results = []
ws = ctx.get('workspace_dir', '')
results = [f"📁 工作目录:{ws}" if ws else "📁 工作目录:未配置"]
ws_root = ctx.get('workspace_root', '')
repos = ctx.get('repos', [])
# Build repo info for prompt
repo_lines = [f"工作空间根路径:{ws_root}" if ws_root else "工作空间根路径:未配置"]
repo_lines.append(f"项目本地路径:{ws}" if ws else "项目本地路径:未配置")
skills_dir = ctx.get('skills_dir', '')
if skills_dir:
repo_lines.append(f"企业Skills目录{skills_dir}")
if repos:
repo_lines.append("关联代码仓库:")
for rp in repos:
repo_lines.append(f" - {rp['name']}: {rp['url']} (分支:{rp['branch']}, 本地:{rp['path']})")
results.append('\\n'.join(repo_lines))
for t in tasks:
try:
task_msgs = [{"role": "system", "content": settings['system_prompt']}]
task_msgs.append({"role": "user", "content": f"请完成:{t.title}\n工作目录{ws}"})
prompt_parts = [f"请完成:{t.title}"]
prompt_parts.append('\\n'.join(repo_lines))
prompt_parts.append("请在关联仓库中直接修改代码文件,完成后提供变更摘要。")
task_msgs.append({"role": "user", "content": '\\n'.join(prompt_parts)})
result = await _call_llm(model_info, task_msgs, settings['temperature'])
did = getID()
role_dir = f"{ws}/deliverables/agent"
role_dir = f\"{ws}/deliverables/agent\" if ws else \"deliverables/agent\"
# Detect primary repo for deliverable
primary_repo = repos[0]['name'] if repos else ''
await sor.C('pipeline_deliverables', {
'id': did, 'project_id': pid, 'task_id': t.id,
'deliverable_type': 'code', 'title': t.title, 'content': result,
'file_path': f"{role_dir}/{t.id}.md",
'repo_name': primary_repo, 'target_path': '',
'file_path': f\"{role_dir}/{t.id}.md\",
'quality_score': 80, 'review_status': 'pending', 'created_by': 'agent'
})
await sor.sqlExe("UPDATE pipeline_tasks SET state='completed' WHERE id=${tid}$", {"tid": t.id})