forked from Gitlink/gitlink-cli
fix(workflow): AI JSON 解析支持数字类型参数,补充 health-report wiki 发布逻辑
- parseAIResponse 将 args 中的数字值统一转为字符串,避免类型不匹配 - health-report 步骤 AI 模式下补充规则引擎的 wiki 发布动作 - 简化 init 工作流参数,去掉冗余选项 - 新增 mapKeys 辅助函数
This commit is contained in:
parent
43eb36cb33
commit
c6ebe659bc
|
|
@ -123,19 +123,63 @@ func (c *AIClient) Analyze(req *AIRequest) (*AIResponse, error) {
|
|||
}
|
||||
|
||||
text := result.Choices[0].Message.Content
|
||||
var aiResp AIResponse
|
||||
if err := json.Unmarshal([]byte(text), &aiResp); err != nil {
|
||||
aiResp, err := parseAIResponse(text)
|
||||
if err != nil {
|
||||
// Fallback: try to extract JSON from markdown code fences.
|
||||
if extracted := extractJSONFromMarkdown(text); extracted != "" {
|
||||
if err2 := json.Unmarshal([]byte(extracted), &aiResp); err2 != nil {
|
||||
aiResp2, err2 := parseAIResponse(extracted)
|
||||
if err2 != nil {
|
||||
return nil, fmt.Errorf("parse AI JSON output: %w\nraw: %s", err, text)
|
||||
}
|
||||
aiResp = aiResp2
|
||||
} else {
|
||||
return nil, fmt.Errorf("parse AI JSON output: %w\nraw: %s", err, text)
|
||||
}
|
||||
}
|
||||
|
||||
return &aiResp, nil
|
||||
return aiResp, nil
|
||||
}
|
||||
|
||||
// parseAIResponse unmarshals the AI's JSON output, coercing numeric arg values to strings.
|
||||
func parseAIResponse(text string) (*AIResponse, error) {
|
||||
// First pass: unmarshal into a flexible structure that accepts numbers in args.
|
||||
raw := struct {
|
||||
Analysis json.RawMessage `json:"analysis"`
|
||||
Actions []struct {
|
||||
Type string `json:"type"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Body map[string]interface{} `json:"body,omitempty"`
|
||||
Module string `json:"module,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
Args map[string]interface{} `json:"args,omitempty"`
|
||||
} `json:"actions"`
|
||||
}{}
|
||||
if err := json.Unmarshal([]byte(text), &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &AIResponse{}
|
||||
if err := json.Unmarshal(raw.Analysis, &resp.Analysis); err != nil {
|
||||
// If it's not valid JSON, treat it as a plain string.
|
||||
resp.Analysis = string(raw.Analysis)
|
||||
}
|
||||
for _, a := range raw.Actions {
|
||||
args := make(map[string]string, len(a.Args))
|
||||
for k, v := range a.Args {
|
||||
args[k] = fmt.Sprint(v)
|
||||
}
|
||||
resp.Actions = append(resp.Actions, AIAction{
|
||||
Type: a.Type,
|
||||
Method: a.Method,
|
||||
Path: a.Path,
|
||||
Body: a.Body,
|
||||
Module: a.Module,
|
||||
Command: a.Command,
|
||||
Args: args,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// HasKey reports whether the AI client is configured.
|
||||
|
|
|
|||
|
|
@ -221,7 +221,34 @@ func executeSkillStep(ctx *common.RuntimeContext, step StepDef, sr *StepResult,
|
|||
fmt.Fprintf(os.Stderr, "[workflow] triage rule action fallback failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
// In AI mode, the rule engine's wiki actions were not generated.
|
||||
// In AI mode, supplement health-report with deterministic wiki action.
|
||||
// AI provides richer semantic analysis; rule engine ensures wiki publishing.
|
||||
if usedAI && step.Name == "health-report" {
|
||||
fmt.Fprintf(os.Stderr, "[workflow] health-report AI supplement: upstream_keys=%v\n", mapKeys(upstream))
|
||||
if ruleResp, err := runRuleEngine(step, upstream); err == nil && len(ruleResp.Actions) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "[workflow] health-report rule engine returned %d actions\n", len(ruleResp.Actions))
|
||||
content := ""
|
||||
if s, ok := aiResp.Analysis.(string); ok && s != "" {
|
||||
content = s
|
||||
}
|
||||
if content == "" {
|
||||
if b, err := json.MarshalIndent(aiResp.Analysis, "", " "); err == nil {
|
||||
content = "# 项目健康度报告\n\n" + string(b) + "\n"
|
||||
}
|
||||
}
|
||||
// Rewrite the rule engine's wiki +create with AI-enhanced content.
|
||||
for _, a := range ruleResp.Actions {
|
||||
if a.Module == "wiki" && a.Command == "+create" {
|
||||
if content != "" {
|
||||
a.Args["content"] = content
|
||||
}
|
||||
aiResp.Actions = append(aiResp.Actions, a)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "[workflow] health-report rule engine failed or empty: err=%v actions=%d\n", err, len(ruleResp.Actions))
|
||||
}
|
||||
}
|
||||
// Supplement AI responses with wiki publishing so the full pipeline runs.
|
||||
if usedAI && step.Name == "contributor-ranking" {
|
||||
content := ""
|
||||
|
|
@ -448,6 +475,14 @@ func isActionAllowed(action AIAction) bool {
|
|||
|
||||
// parseCommandTarget splits a CLI command string into tokens,
|
||||
// respecting quoted arguments.
|
||||
func mapKeys(m map[string]interface{}) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func parseCommandTarget(target string) []string {
|
||||
var parts []string
|
||||
var current strings.Builder
|
||||
|
|
|
|||
|
|
@ -528,10 +528,7 @@ const WORKFLOWS = [
|
|||
features: ["params", "execute"],
|
||||
desc:"输入仓库名 → 检查现状 → 补齐缺失(README/LICENSE/标签/里程碑/Issue)",
|
||||
params: [
|
||||
{k:"description", l:"项目描述", t:"textarea", v:"一个基于 Go 的 GitLink CLI 工具,提供命令行方式管理仓库、Issue、PR 等"},
|
||||
{k:"template", l:"模板类型", t:"select", opts:["通用项目","前端项目","后端项目","Go 项目","Python 项目","Java 项目"]},
|
||||
{k:"license", l:"许可证", t:"select", opts:["MIT","Apache-2.0","GPL-3.0","BSD-3-Clause","无"]},
|
||||
{k:"private", l:"仓库可见性", t:"select", opts:["公开","私有"]},
|
||||
{k:"desc", l:"项目描述", t:"textarea", v:"一个基于 Go 的 GitLink CLI 工具,提供命令行方式管理仓库、Issue、PR 等"},
|
||||
],
|
||||
steps:["获取仓库基本信息","检查文件结构","获取已有标签和里程碑","检查分支保护状态","[AI] 对比初始化清单,识别缺失项","[AI] 生成建议的初始化内容","[AI] 自动创建缺失的标签/里程碑/Issue","[AI] 生成项目初始化报告"] },
|
||||
{ name:"multi-repo", category:"协同", triggerType:"cron", triggerOn:"0 9 * * 1",
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue