aitable-workflow-cli 0.1.28 → 0.1.29-beta.1790736949347
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant/assets/index-BA6il19d.css +1 -0
- package/dist/assistant/assets/{index-B3LvYfRW.js → index-BMzAOp70.js} +31 -31
- package/dist/assistant/index.html +2 -2
- package/dist/chunk-BBP5RVFU.js +15 -0
- package/dist/cli.js +180 -73
- package/dist/{config-ui-server-4SVD42Z3.js → config-ui-server-UWELHUH4.js} +1 -1
- package/dist/web/assets/index-Bnmnydwk.css +1 -0
- package/dist/web/assets/index-Dbs1xWB9.js +331 -0
- package/dist/web/index.html +2 -2
- package/dist/web/install.ps1 +173 -0
- package/dist/web/install.sh +188 -0
- package/package.json +3 -3
- package/templates/ai-native-sdlc/.env.example +11 -0
- package/templates/ai-native-sdlc/aitable-workflow.config.yml +47 -0
- package/templates/ai-native-sdlc/coding-agents.config.json +17 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-15-ai-native-sdlc-dual-staff-implementation-plan.md +150 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-15-ai-native-sdlc-dual-staff.md +213 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-16-domain-role-notification-routing.md +36 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-16-execution-resume-and-cr-loop.md +29 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-16-project-handoff.md +148 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-17-prd-code-context-and-readable-prompts.md +37 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-21-plan-merge-sync-and-agent-source.md +19 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-21-sdlc-base-ref-and-review-notification.md +18 -0
- package/templates/ai-native-sdlc/docs/plans/2026-09-29-borrow-from-joe-design.md +943 -0
- package/templates/ai-native-sdlc/lib/artifacts.ts +126 -0
- package/templates/ai-native-sdlc/lib/recipe-config.ts +34 -0
- package/templates/ai-native-sdlc/lib/recipe-helpers.ts +595 -0
- package/templates/ai-native-sdlc/npmrc +1 -0
- package/templates/ai-native-sdlc/package.json +19 -0
- package/templates/ai-native-sdlc/profile/profile.yml +115 -0
- package/templates/ai-native-sdlc/setup/module-config.seed.json +37 -0
- package/templates/ai-native-sdlc/test/artifacts.test.ts +72 -0
- package/templates/ai-native-sdlc/test/recipe-helpers.test.ts +931 -0
- package/templates/ai-native-sdlc/tsconfig.json +17 -0
- package/templates/ai-native-sdlc/workflows/.gitkeep +3 -0
- package/templates/ai-native-sdlc/workflows/execution/automations/blocked-questions-dev.json +91 -0
- package/templates/ai-native-sdlc/workflows/execution/automations/blocked-questions.json +91 -0
- package/templates/ai-native-sdlc/workflows/execution/automations/cr-review-dev.json +80 -0
- package/templates/ai-native-sdlc/workflows/execution/automations/cr-review.json +81 -0
- package/templates/ai-native-sdlc/workflows/execution/automations/resume-answers.json +42 -0
- package/templates/ai-native-sdlc/workflows/execution/automations/spec-review-dev.json +138 -0
- package/templates/ai-native-sdlc/workflows/execution/recipes/coding.recipe.ts +161 -0
- package/templates/ai-native-sdlc/workflows/execution/recipes/prd-review.recipe.ts +200 -0
- package/templates/ai-native-sdlc/workflows/execution/recipes/prd.recipe.ts +163 -0
- package/templates/ai-native-sdlc/workflows/execution/recipes/review.recipe.ts +257 -0
- package/templates/ai-native-sdlc/workflows/execution/recipes/spec-review.recipe.ts +242 -0
- package/templates/ai-native-sdlc/workflows/execution/recipes/spec.recipe.ts +173 -0
- package/templates/ai-native-sdlc/workflows/execution/workflow.yml +79 -0
- package/templates/ai-native-sdlc/workflows/intake/automations/accept-intake.json +101 -0
- package/templates/ai-native-sdlc/workflows/intake/recipes/classify-module.recipe.ts +133 -0
- package/templates/ai-native-sdlc/workflows/intake/recipes/create-change.recipe.ts +94 -0
- package/templates/ai-native-sdlc/workflows/intake/recipes/mechanical-deduplicate.recipe.ts +33 -0
- package/templates/ai-native-sdlc/workflows/intake/workflow.yml +62 -0
- package/templates/ai-native-sdlc/workspaces/dev/AGENTS.md +69 -0
- package/templates/ai-native-sdlc/workspaces/pm/AGENTS.md +48 -0
- package/templates/ai-native-sdlc/workspaces/reviewer/AGENTS.md +79 -0
- package/dist/assistant/assets/index-CCEHOkkJ.css +0 -1
- package/dist/chunk-YDJTCQW4.js +0 -15
- package/dist/web/assets/index-DPc2WOdp.css +0 -1
- package/dist/web/assets/index-HTw8K38o.js +0 -332
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { RecipeDefinition } from "aitable-workflow-core";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { devStageConfig } from "../../../lib/recipe-config.js";
|
|
4
|
+
import { artifactAbsPath, artifactName, artifactPath, resolveWorktreeDir } from "../../../lib/artifacts.js";
|
|
5
|
+
import {
|
|
6
|
+
agentQuestions,
|
|
7
|
+
appendArtifact,
|
|
8
|
+
appendStageTransition,
|
|
9
|
+
blockExecution,
|
|
10
|
+
blocked,
|
|
11
|
+
changeId,
|
|
12
|
+
changeTitle,
|
|
13
|
+
commitOptions,
|
|
14
|
+
consumeAnswersOrBlock,
|
|
15
|
+
failed,
|
|
16
|
+
handleCodingFailure,
|
|
17
|
+
hasStageTransition,
|
|
18
|
+
isTestDemand,
|
|
19
|
+
isCurrentRecipe,
|
|
20
|
+
persistCodingSession,
|
|
21
|
+
questionsText,
|
|
22
|
+
recordValue,
|
|
23
|
+
repositoryUrl,
|
|
24
|
+
repoPathFromUrl,
|
|
25
|
+
formatCrEntries,
|
|
26
|
+
requireRepositories,
|
|
27
|
+
sessionMap,
|
|
28
|
+
success,
|
|
29
|
+
testDemandInstruction,
|
|
30
|
+
worktreeOptions,
|
|
31
|
+
buildChangeDetails,
|
|
32
|
+
} from "../../../lib/recipe-helpers.js";
|
|
33
|
+
|
|
34
|
+
const recipe: RecipeDefinition = {
|
|
35
|
+
meta: {
|
|
36
|
+
id: "coding",
|
|
37
|
+
name: "编码",
|
|
38
|
+
role: "dev",
|
|
39
|
+
description: "Dev 实施、运行真实测试并提交编码工件",
|
|
40
|
+
version: "1.0.0",
|
|
41
|
+
requiresAgent: true,
|
|
42
|
+
config: devStageConfig,
|
|
43
|
+
},
|
|
44
|
+
async execute(ctx) {
|
|
45
|
+
const row = await ctx.toolkit.table.readCurrentRow();
|
|
46
|
+
const id = changeId(row);
|
|
47
|
+
const configuredRepos = requireRepositories(ctx, "dev_repositories");
|
|
48
|
+
const repos = isTestDemand(row) ? configuredRepos.slice(0, 1) : configuredRepos;
|
|
49
|
+
if (!id || !repos.length)
|
|
50
|
+
return failed("编码阶段缺少变更编号或 Dev 候选仓库配置");
|
|
51
|
+
if (!isCurrentRecipe(row, ctx.stepId)) {
|
|
52
|
+
return blocked("当前步骤与执行 recipe 不一致,拒绝重复执行编码");
|
|
53
|
+
}
|
|
54
|
+
if (hasStageTransition(row["审计日志"], id, "评审")) {
|
|
55
|
+
return success({}, "恢复:编码已完成但步骤指针尚未推进");
|
|
56
|
+
}
|
|
57
|
+
const resumed = await consumeAnswersOrBlock(ctx, row);
|
|
58
|
+
if (resumed.blocked) return resumed.blocked;
|
|
59
|
+
const worktrees = await Promise.all(
|
|
60
|
+
repos.map((repo) =>
|
|
61
|
+
ctx.toolkit.git.ensureWorktree(repo, id, id, worktreeOptions(repo)),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
const candidateWorktrees = worktrees
|
|
65
|
+
.map((worktree, index) => `- ${repositoryUrl(repos[index]!)}: ${resolveWorktreeDir(ctx.configDir, worktree.dir)}`)
|
|
66
|
+
.join("\n");
|
|
67
|
+
const result = await ctx.toolkit.coding.run(
|
|
68
|
+
`## 候选业务 worktree
|
|
69
|
+
${candidateWorktrees}
|
|
70
|
+
公共父目录不是 Git 仓库;所有文件只能写入具体仓库 worktree。
|
|
71
|
+
进入某仓库前先读取其根目录 AGENTS.md(若存在),并遵循其约定。
|
|
72
|
+
|
|
73
|
+
${testDemandInstruction(row)}
|
|
74
|
+
|
|
75
|
+
## 已消费的人工答复
|
|
76
|
+
${resumed.answers}
|
|
77
|
+
|
|
78
|
+
## 需求
|
|
79
|
+
标题:${recordValue(row, "需求")}
|
|
80
|
+
详情:${recordValue(row, "需求详情")}
|
|
81
|
+
|
|
82
|
+
## 本轮任务
|
|
83
|
+
先在候选 worktree 中读取 ${artifactPath("spec", id)}。
|
|
84
|
+
仅实现技术方案选中的仓库并运行真实测试。
|
|
85
|
+
在每个选中仓库的 ${artifactPath("coding", id)} 写入以下章节。具体行为、关键文件与风险说明写在「改动内容」章节正文中:
|
|
86
|
+
## 改动内容
|
|
87
|
+
## 测试命令与结果
|
|
88
|
+
## CI 证据`.trim(),
|
|
89
|
+
{
|
|
90
|
+
sessionId: sessionMap(row["Agent会话ID"])["coding:dev"],
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
return handleCodingFailure(
|
|
95
|
+
ctx,
|
|
96
|
+
row,
|
|
97
|
+
"coding",
|
|
98
|
+
"coding:dev",
|
|
99
|
+
result,
|
|
100
|
+
"编码 Agent 未完成",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const agentSession = await persistCodingSession(ctx, row, "coding:dev", result);
|
|
104
|
+
const commits = await Promise.all(
|
|
105
|
+
worktrees.map((worktree) =>
|
|
106
|
+
ctx.toolkit.git.commit(worktree.dir, `${id}: coding`, commitOptions(row)),
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
const questions = agentQuestions(result.response, "coding");
|
|
110
|
+
const persisted = {
|
|
111
|
+
产出物: appendArtifact(
|
|
112
|
+
row["产出物"],
|
|
113
|
+
`${artifactName("coding")};commit=${commits.some((commit) => commit.committed) ? `${id}: coding` : "已存在"}`,
|
|
114
|
+
),
|
|
115
|
+
Agent会话ID: agentSession,
|
|
116
|
+
};
|
|
117
|
+
if (questions.length) {
|
|
118
|
+
return blockExecution(
|
|
119
|
+
ctx,
|
|
120
|
+
"编码存在待确认问题",
|
|
121
|
+
"在人工答复中按问题编号逐项回答后自动续跑",
|
|
122
|
+
questionsText(questions),
|
|
123
|
+
persisted,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const diffs = await Promise.all(worktrees.map((worktree) => ctx.toolkit.git.diff(worktree.dir, "origin/master")));
|
|
127
|
+
const changed = worktrees.filter((_, index) => diffs[index]!.files.length > 0);
|
|
128
|
+
if (!changed.length) return blocked("编码完成后没有相对 master 的已提交改动,无法创建 CR");
|
|
129
|
+
const entries = await Promise.all(changed.map(async (worktree) => {
|
|
130
|
+
const repository = repos[worktrees.indexOf(worktree)]!;
|
|
131
|
+
const repoUrl = repositoryUrl(repository);
|
|
132
|
+
let buildMarkdown: string;
|
|
133
|
+
try {
|
|
134
|
+
buildMarkdown = await readFile(artifactAbsPath("coding", resolveWorktreeDir(ctx.configDir, worktree.dir), id), "utf8");
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error(`编码工件缺失:${artifactPath("coding", id)}`);
|
|
137
|
+
}
|
|
138
|
+
const details = buildChangeDetails(buildMarkdown);
|
|
139
|
+
if (!details) throw new Error(`编码工件缺少非空「## 改动内容」:${artifactPath("coding", id)}`);
|
|
140
|
+
await ctx.toolkit.git.push(worktree.dir, id);
|
|
141
|
+
const mr = await ctx.toolkit.codeReview.createMR(
|
|
142
|
+
repoPathFromUrl(repoUrl), id, "master", `${changeTitle(row, id)} · 编码`,
|
|
143
|
+
`请评审 ${artifactPath("coding", id)} 及编码改动。\n\n## 改动详情\n${details}`,
|
|
144
|
+
);
|
|
145
|
+
return { repoUrl, mrId: mr.mrId, mrUrl: mr.mrUrl };
|
|
146
|
+
}));
|
|
147
|
+
await ctx.toolkit.table.updateCurrentRow({
|
|
148
|
+
当前阶段: "评审",
|
|
149
|
+
当前角色: "Dev",
|
|
150
|
+
状态: "active",
|
|
151
|
+
审计日志: appendStageTransition(row["审计日志"], id, "评审"),
|
|
152
|
+
AI问题: "",
|
|
153
|
+
阻塞原因: "",
|
|
154
|
+
解除条件: "",
|
|
155
|
+
当前CR链接: formatCrEntries(entries),
|
|
156
|
+
...persisted,
|
|
157
|
+
});
|
|
158
|
+
return success({}, "编码工件已提交并创建代码 CR");
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
export default recipe;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { RecipeDefinition } from "aitable-workflow-core";
|
|
2
|
+
import { pmStageConfig } from "../../../lib/recipe-config.js";
|
|
3
|
+
import {
|
|
4
|
+
artifactAbsPath,
|
|
5
|
+
artifactName,
|
|
6
|
+
resolveWorktreeDir,
|
|
7
|
+
} from "../../../lib/artifacts.js";
|
|
8
|
+
import {
|
|
9
|
+
agentQuestions,
|
|
10
|
+
appendArtifact,
|
|
11
|
+
appendHandledComments,
|
|
12
|
+
appendStageTransition,
|
|
13
|
+
blockExecution,
|
|
14
|
+
blocked,
|
|
15
|
+
changeId,
|
|
16
|
+
commentRepairNotice,
|
|
17
|
+
commitOptions,
|
|
18
|
+
consumeAnswersOrBlock,
|
|
19
|
+
crId,
|
|
20
|
+
crEntries,
|
|
21
|
+
failed,
|
|
22
|
+
handleCodingFailure,
|
|
23
|
+
hasStageTransition,
|
|
24
|
+
isCurrentRecipe,
|
|
25
|
+
persistCodingSession,
|
|
26
|
+
questionsText,
|
|
27
|
+
repoPathFromUrl,
|
|
28
|
+
repositoryUrl,
|
|
29
|
+
requireRepository,
|
|
30
|
+
sessionMap,
|
|
31
|
+
success,
|
|
32
|
+
testDemandInstruction,
|
|
33
|
+
unhandledComments,
|
|
34
|
+
waitForExternalProgress,
|
|
35
|
+
worktreeOptions,
|
|
36
|
+
MAX_REVIEW_ROUNDS,
|
|
37
|
+
parseReviewRound,
|
|
38
|
+
} from "../../../lib/recipe-helpers.js";
|
|
39
|
+
|
|
40
|
+
const recipe: RecipeDefinition = {
|
|
41
|
+
meta: {
|
|
42
|
+
id: "prd-review",
|
|
43
|
+
name: "PRD 评审",
|
|
44
|
+
role: "reviewer",
|
|
45
|
+
description: "PM 处理 PRD CR 评论并确认实际合并",
|
|
46
|
+
version: "1.0.0",
|
|
47
|
+
requiresAgent: true,
|
|
48
|
+
config: pmStageConfig,
|
|
49
|
+
},
|
|
50
|
+
async execute(ctx) {
|
|
51
|
+
const row = await ctx.toolkit.table.readCurrentRow();
|
|
52
|
+
const id = changeId(row);
|
|
53
|
+
const pmRepository = requireRepository(ctx, "pm_repository");
|
|
54
|
+
if (!id || !pmRepository) return failed("PRD 评审缺少变更编号或 PM 仓库配置");
|
|
55
|
+
const repoUrl = repositoryUrl(pmRepository);
|
|
56
|
+
if (!isCurrentRecipe(row, ctx.stepId)) {
|
|
57
|
+
return blocked("当前步骤与执行 recipe 不一致,拒绝执行 PRD 评审");
|
|
58
|
+
}
|
|
59
|
+
if (hasStageTransition(row["审计日志"], id, "技术方案")) {
|
|
60
|
+
return success({}, "恢复:PRD 已合并但步骤指针尚未推进");
|
|
61
|
+
}
|
|
62
|
+
// 有界重试:超过最大轮次则升级人工(放在恢复检查之后,避免误阻塞已完成阶段)
|
|
63
|
+
const currentRound = parseReviewRound(row);
|
|
64
|
+
if (currentRound > MAX_REVIEW_ROUNDS) {
|
|
65
|
+
return blocked(`PRD 评审修复已超过 ${MAX_REVIEW_ROUNDS} 轮,请人工介入`);
|
|
66
|
+
}
|
|
67
|
+
const mrId = crId(row["当前CR链接"]);
|
|
68
|
+
if (!mrId) return failed("PRD 评审缺少当前 CR 链接");
|
|
69
|
+
const resumed = await consumeAnswersOrBlock(ctx, row);
|
|
70
|
+
if (resumed.blocked) return resumed.blocked;
|
|
71
|
+
|
|
72
|
+
const repoPath = repoPathFromUrl(repoUrl);
|
|
73
|
+
const comments = await ctx.toolkit.codeReview.listUnresolvedComments(
|
|
74
|
+
mrId,
|
|
75
|
+
repoPath,
|
|
76
|
+
);
|
|
77
|
+
const pendingComments = unhandledComments(row["产出物"], "prd", mrId, comments);
|
|
78
|
+
if (pendingComments.length) {
|
|
79
|
+
const worktree = await ctx.toolkit.git.ensureWorktree(
|
|
80
|
+
pmRepository,
|
|
81
|
+
id,
|
|
82
|
+
id,
|
|
83
|
+
worktreeOptions(pmRepository),
|
|
84
|
+
);
|
|
85
|
+
const repair = await ctx.toolkit.coding.run(
|
|
86
|
+
`## 工作目录
|
|
87
|
+
所有文件读写和命令只在此变更 worktree 内进行:${resolveWorktreeDir(ctx.configDir, worktree.dir)}
|
|
88
|
+
先读取该 worktree 根目录下的 AGENTS.md(若存在),并遵循其仓库约定。
|
|
89
|
+
|
|
90
|
+
${testDemandInstruction(row)}
|
|
91
|
+
|
|
92
|
+
## 未解决评论
|
|
93
|
+
\`\`\`json
|
|
94
|
+
${JSON.stringify(pendingComments, null, 2)}
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
## 已消费的人工答复
|
|
98
|
+
${resumed.answers}
|
|
99
|
+
|
|
100
|
+
## 本轮任务
|
|
101
|
+
逐项修复评论。
|
|
102
|
+
更新 ${artifactAbsPath("prd", resolveWorktreeDir(ctx.configDir, worktree.dir), id)}。
|
|
103
|
+
运行适用校验。`.trim(),
|
|
104
|
+
{
|
|
105
|
+
sessionId: sessionMap(row["Agent会话ID"])["prd:pm"],
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
if (!repair.success) {
|
|
109
|
+
return handleCodingFailure(
|
|
110
|
+
ctx,
|
|
111
|
+
row,
|
|
112
|
+
"prd",
|
|
113
|
+
"prd:pm",
|
|
114
|
+
repair,
|
|
115
|
+
"PRD 评论修复 Agent 未完成",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const agentSession = await persistCodingSession(ctx, row, "prd:pm", repair);
|
|
119
|
+
const commit = await ctx.toolkit.git.commit(
|
|
120
|
+
worktree.dir,
|
|
121
|
+
`${id}: prd review fixes`,
|
|
122
|
+
commitOptions(row),
|
|
123
|
+
);
|
|
124
|
+
const persisted = {
|
|
125
|
+
Agent会话ID: agentSession,
|
|
126
|
+
审查轮次: currentRound + 1,
|
|
127
|
+
产出物: appendHandledComments(
|
|
128
|
+
appendArtifact(
|
|
129
|
+
row["产出物"],
|
|
130
|
+
`${artifactName("prd")};评论修复commit=${commit.committed ? `${id}: prd review fixes` : "已存在"}`,
|
|
131
|
+
),
|
|
132
|
+
"prd",
|
|
133
|
+
mrId,
|
|
134
|
+
pendingComments,
|
|
135
|
+
),
|
|
136
|
+
};
|
|
137
|
+
const questions = agentQuestions(repair.response, "prd");
|
|
138
|
+
if (questions.length) {
|
|
139
|
+
return blockExecution(
|
|
140
|
+
ctx,
|
|
141
|
+
"PRD 评论修复仍有待确认问题",
|
|
142
|
+
"在人工答复中按问题编号逐项回答后自动续跑",
|
|
143
|
+
questionsText(questions),
|
|
144
|
+
persisted,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
await ctx.toolkit.git.push(worktree.dir, id);
|
|
148
|
+
const crEntry = crEntries(row["当前CR链接"]).find((entry) => entry.mrId === mrId);
|
|
149
|
+
return waitForExternalProgress(
|
|
150
|
+
ctx,
|
|
151
|
+
row,
|
|
152
|
+
"PRD 评论已修复,等待复审",
|
|
153
|
+
"等待评审者关闭评论或提出新评论",
|
|
154
|
+
{
|
|
155
|
+
评审进展: commentRepairNotice([{
|
|
156
|
+
repoUrl,
|
|
157
|
+
mrUrl: crEntry?.mrUrl ?? String(row["当前CR链接"] ?? ""),
|
|
158
|
+
commentCount: pendingComments.length,
|
|
159
|
+
}]),
|
|
160
|
+
...persisted,
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const [ready, merged] = await Promise.all([
|
|
165
|
+
ctx.toolkit.codeReview.isReadyToMerge(mrId, repoPath),
|
|
166
|
+
ctx.toolkit.codeReview.isMerged(mrId, repoPath),
|
|
167
|
+
]);
|
|
168
|
+
if (merged) {
|
|
169
|
+
await ctx.toolkit.table.updateCurrentRow({
|
|
170
|
+
当前阶段: "技术方案",
|
|
171
|
+
当前角色: "Dev",
|
|
172
|
+
状态: "active",
|
|
173
|
+
当前CR链接: "",
|
|
174
|
+
评审进展: "",
|
|
175
|
+
审计日志: appendStageTransition(row["审计日志"], id, "技术方案"),
|
|
176
|
+
AI问题: "",
|
|
177
|
+
阻塞原因: "",
|
|
178
|
+
解除条件: "",
|
|
179
|
+
});
|
|
180
|
+
return success({}, "PRD CR 已合并,进入技术方案");
|
|
181
|
+
}
|
|
182
|
+
if (!ready || comments.length) {
|
|
183
|
+
return waitForExternalProgress(
|
|
184
|
+
ctx,
|
|
185
|
+
row,
|
|
186
|
+
"PRD CR 尚未通过或仍有未解决评论",
|
|
187
|
+
"等待审核通过,或等待新评论被自动修复",
|
|
188
|
+
{ 当前角色: "PM" },
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return waitForExternalProgress(
|
|
192
|
+
ctx,
|
|
193
|
+
row,
|
|
194
|
+
"PRD CR 已可合并,等待其真正合并",
|
|
195
|
+
"请在 GitLab 合并 PRD CR;合并事实将自动进入技术方案",
|
|
196
|
+
{ 当前角色: "PM" },
|
|
197
|
+
);
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
export default recipe;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { RecipeDefinition } from "aitable-workflow-core";
|
|
2
|
+
import { prdStageConfig } from "../../../lib/recipe-config.js";
|
|
3
|
+
import {
|
|
4
|
+
artifactAbsPath,
|
|
5
|
+
artifactName,
|
|
6
|
+
artifactPath,
|
|
7
|
+
ensureArtifactManifest,
|
|
8
|
+
resolveWorktreeDir,
|
|
9
|
+
} from "../../../lib/artifacts.js";
|
|
10
|
+
import {
|
|
11
|
+
agentQuestions,
|
|
12
|
+
appendArtifact,
|
|
13
|
+
appendStageTransition,
|
|
14
|
+
blockExecution,
|
|
15
|
+
blocked,
|
|
16
|
+
changeId,
|
|
17
|
+
changeTitle,
|
|
18
|
+
consumeAnswersOrBlock,
|
|
19
|
+
failed,
|
|
20
|
+
handleCodingFailure,
|
|
21
|
+
hasStageTransition,
|
|
22
|
+
isTestDemand,
|
|
23
|
+
isCurrentRecipe,
|
|
24
|
+
persistCodingSession,
|
|
25
|
+
questionsText,
|
|
26
|
+
recordValue,
|
|
27
|
+
repositoryUrl,
|
|
28
|
+
repoPathFromUrl,
|
|
29
|
+
requireRepositories,
|
|
30
|
+
requireRepository,
|
|
31
|
+
sessionMap,
|
|
32
|
+
success,
|
|
33
|
+
testDemandInstruction,
|
|
34
|
+
worktreeOptions,
|
|
35
|
+
} from "../../../lib/recipe-helpers.js";
|
|
36
|
+
|
|
37
|
+
const recipe: RecipeDefinition = {
|
|
38
|
+
meta: {
|
|
39
|
+
id: "prd",
|
|
40
|
+
name: "PRD",
|
|
41
|
+
role: "pm",
|
|
42
|
+
description: "PM 产出 PRD 工件,无未解问题后创建待评审 CR",
|
|
43
|
+
version: "1.1.0",
|
|
44
|
+
requiresAgent: true,
|
|
45
|
+
config: prdStageConfig,
|
|
46
|
+
},
|
|
47
|
+
async execute(ctx) {
|
|
48
|
+
const row = await ctx.toolkit.table.readCurrentRow();
|
|
49
|
+
const id = changeId(row);
|
|
50
|
+
const pmRepository = requireRepository(ctx, "pm_repository");
|
|
51
|
+
const businessRepositories = requireRepositories(ctx, "business_repositories");
|
|
52
|
+
const testing = isTestDemand(row);
|
|
53
|
+
if (!id || !pmRepository || (!testing && !businessRepositories.length)) {
|
|
54
|
+
return failed("PRD 阶段缺少变更编号、PM 仓库或业务代码仓库配置");
|
|
55
|
+
}
|
|
56
|
+
const repoUrl = repositoryUrl(pmRepository);
|
|
57
|
+
if (!isCurrentRecipe(row, ctx.stepId)) {
|
|
58
|
+
return blocked("当前步骤与执行 recipe 不一致,拒绝生成 PRD");
|
|
59
|
+
}
|
|
60
|
+
if (hasStageTransition(row["审计日志"], id, "PRD 评审")) {
|
|
61
|
+
return success({}, "恢复:PRD CR 已创建但步骤指针尚未推进");
|
|
62
|
+
}
|
|
63
|
+
const resumed = await consumeAnswersOrBlock(ctx, row);
|
|
64
|
+
if (resumed.blocked) return resumed.blocked;
|
|
65
|
+
const worktree = await ctx.toolkit.git.ensureWorktree(
|
|
66
|
+
repositoryUrl(pmRepository),
|
|
67
|
+
id,
|
|
68
|
+
id,
|
|
69
|
+
worktreeOptions(pmRepository),
|
|
70
|
+
);
|
|
71
|
+
const businessContext = testing
|
|
72
|
+
? "## 测试需求\n不读取业务代码仓库;直接按工作目录 AGENTS.md 的测试模式生成最小 PRD。"
|
|
73
|
+
: `## 业务代码仓库(只读上下文)
|
|
74
|
+
先按可用的只读能力阅读以下仓库 master 的当前逻辑,再撰写 PRD:
|
|
75
|
+
${businessRepositories.map((repository) => `- ${repositoryUrl(repository)}`).join("\n")}
|
|
76
|
+
进入某仓库前先读取其根目录 AGENTS.md(若存在),并遵循其约定,不要为这些业务仓库创建 worktree、分支、工件、commit 或 push。`;
|
|
77
|
+
const result = await ctx.toolkit.coding.run(
|
|
78
|
+
`## 本轮上下文
|
|
79
|
+
|
|
80
|
+
### PM 工件工作目录
|
|
81
|
+
${resolveWorktreeDir(ctx.configDir, worktree.dir)}
|
|
82
|
+
|
|
83
|
+
${businessContext}
|
|
84
|
+
|
|
85
|
+
### 需求
|
|
86
|
+
需求:${recordValue(row, "需求")}
|
|
87
|
+
需求详情:${recordValue(row, "需求详情")}
|
|
88
|
+
模块:${recordValue(row, "模块")}
|
|
89
|
+
|
|
90
|
+
${testDemandInstruction(row)}
|
|
91
|
+
|
|
92
|
+
## 已消费的人工答复
|
|
93
|
+
${resumed.answers}
|
|
94
|
+
|
|
95
|
+
## 本轮任务
|
|
96
|
+
创建或更新 ${artifactAbsPath("prd", resolveWorktreeDir(ctx.configDir, worktree.dir), id)}。
|
|
97
|
+
PRD 必须写明需求、验收标准和依赖。
|
|
98
|
+
|
|
99
|
+
`.trim(),
|
|
100
|
+
{
|
|
101
|
+
sessionId: sessionMap(row["Agent会话ID"])["prd:pm"],
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
if (!result.success) {
|
|
105
|
+
return handleCodingFailure(
|
|
106
|
+
ctx,
|
|
107
|
+
row,
|
|
108
|
+
"prd",
|
|
109
|
+
"prd:pm",
|
|
110
|
+
result,
|
|
111
|
+
"PRD Agent 未完成",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const agentSession = await persistCodingSession(ctx, row, "prd:pm", result);
|
|
115
|
+
// 登记必须先于 commit:manifest 与工件同一次提交,下游 push 后即可读。
|
|
116
|
+
ensureArtifactManifest(resolveWorktreeDir(ctx.configDir, worktree.dir), id, "prd");
|
|
117
|
+
const commit = await ctx.toolkit.git.commit(worktree.dir, `${id}: prd`);
|
|
118
|
+
const questions = agentQuestions(result.response, "prd");
|
|
119
|
+
const persisted = {
|
|
120
|
+
当前阶段: "PRD",
|
|
121
|
+
当前角色: "PM",
|
|
122
|
+
产出物: appendArtifact(
|
|
123
|
+
row["产出物"],
|
|
124
|
+
`${artifactName("prd")};commit=${commit.committed ? `${id}: prd` : "已存在"}`,
|
|
125
|
+
),
|
|
126
|
+
Agent会话ID: agentSession,
|
|
127
|
+
};
|
|
128
|
+
if (questions.length) {
|
|
129
|
+
return blockExecution(
|
|
130
|
+
ctx,
|
|
131
|
+
"PRD 存在待确认问题",
|
|
132
|
+
"在人工答复中按问题编号逐项回答后自动续跑",
|
|
133
|
+
questionsText(questions),
|
|
134
|
+
persisted,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
await ctx.toolkit.git.push(worktree.dir, id);
|
|
138
|
+
const mr = await ctx.toolkit.codeReview.createMR(
|
|
139
|
+
repoPathFromUrl(repoUrl),
|
|
140
|
+
id,
|
|
141
|
+
"master",
|
|
142
|
+
`${changeTitle(row, id)} · PRD`,
|
|
143
|
+
`请评审 ${artifactPath("prd", id)}。`,
|
|
144
|
+
);
|
|
145
|
+
await ctx.toolkit.table.updateCurrentRow({
|
|
146
|
+
当前阶段: "PRD 评审",
|
|
147
|
+
当前角色: "PM",
|
|
148
|
+
状态: "active",
|
|
149
|
+
当前CR链接: mr.mrUrl,
|
|
150
|
+
审计日志: appendStageTransition(row["审计日志"], id, "PRD 评审"),
|
|
151
|
+
AI问题: "",
|
|
152
|
+
阻塞原因: "",
|
|
153
|
+
解除条件: "",
|
|
154
|
+
产出物: appendArtifact(
|
|
155
|
+
persisted.产出物,
|
|
156
|
+
`${artifactName("prd")};CR(!${mr.mrId})=${mr.mrUrl}`,
|
|
157
|
+
),
|
|
158
|
+
Agent会话ID: persisted.Agent会话ID,
|
|
159
|
+
});
|
|
160
|
+
return success({}, "PRD 工件已提交,进入 PRD 评审");
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
export default recipe;
|