From f86cce02f4b972ffed64015b956d6a74bd98b97c Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 3 Aug 2026 17:13:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20multi-repo=20workspace=20=E2=80=94=20or?= =?UTF-8?q?g=20settings,=20project=20repos,=20deliverable=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- json/sd_org_setting_list.json | 17 +++++++++++ json/sd_project_list.json | 6 ++++ json/sd_project_repo_list.json | 17 +++++++++++ models/pipeline_deliverables.json | 36 ++++++++++++++++++++++ models/sd_org_settings.json | 21 +++++++++++++ models/sd_project_repos.json | 24 +++++++++++++++ models/sd_projects.json | 3 +- wwwroot/api/cockpit_chat.dspy | 50 ++++++++++++++++++++++++++----- 8 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 json/sd_org_setting_list.json create mode 100644 json/sd_project_repo_list.json create mode 100644 models/pipeline_deliverables.json create mode 100644 models/sd_org_settings.json create mode 100644 models/sd_project_repos.json diff --git a/json/sd_org_setting_list.json b/json/sd_org_setting_list.json new file mode 100644 index 0000000..c3dc9d8 --- /dev/null +++ b/json/sd_org_setting_list.json @@ -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')}}" + } + } +} diff --git a/json/sd_project_list.json b/json/sd_project_list.json index d29acee..96362d3 100644 --- a/json/sd_project_list.json +++ b/json/sd_project_list.json @@ -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" } ] } diff --git a/json/sd_project_repo_list.json b/json/sd_project_repo_list.json new file mode 100644 index 0000000..2317cb0 --- /dev/null +++ b/json/sd_project_repo_list.json @@ -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')}}" + } + } +} diff --git a/models/pipeline_deliverables.json b/models/pipeline_deliverables.json new file mode 100644 index 0000000..119569e --- /dev/null +++ b/models/pipeline_deliverables.json @@ -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'"} + ] +} diff --git a/models/sd_org_settings.json b/models/sd_org_settings.json new file mode 100644 index 0000000..38b57c6 --- /dev/null +++ b/models/sd_org_settings.json @@ -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"]} + ] +} diff --git a/models/sd_project_repos.json b/models/sd_project_repos.json new file mode 100644 index 0000000..267f1e7 --- /dev/null +++ b/models/sd_project_repos.json @@ -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"]} + ] +} diff --git a/models/sd_projects.json b/models/sd_projects.json index a6fbcd3..aba1219 100644 --- a/models/sd_projects.json +++ b/models/sd_projects.json @@ -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'"}, diff --git a/wwwroot/api/cockpit_chat.dspy b/wwwroot/api/cockpit_chat.dspy index dc029b4..aab7aca 100644 --- a/wwwroot/api/cockpit_chat.dspy +++ b/wwwroot/api/cockpit_chat.dspy @@ -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})