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,79 @@
|
|
|
1
|
+
meta:
|
|
2
|
+
id: execution
|
|
3
|
+
name: "SDLC 执行"
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
domain: ai_native_sdlc
|
|
6
|
+
tracker:
|
|
7
|
+
label: "变更流水"
|
|
8
|
+
# 留空 = 搭建(setup)阶段新建 Base 与表并自动回填;与 intake 同库同批创建。
|
|
9
|
+
base_id: ""
|
|
10
|
+
table_id: ""
|
|
11
|
+
states:
|
|
12
|
+
{
|
|
13
|
+
initial: active,
|
|
14
|
+
active: [ active ],
|
|
15
|
+
review: blocked,
|
|
16
|
+
terminal: [ shipped, rejected, archived ],
|
|
17
|
+
blocked: blocked
|
|
18
|
+
}
|
|
19
|
+
fields: { state: "状态", title: "变更编号", current_step: "当前步骤", event_log: "事件日志" }
|
|
20
|
+
field_definitions:
|
|
21
|
+
- { name: "标题", type: text }
|
|
22
|
+
- { name: "需求", type: text }
|
|
23
|
+
- { name: "需求详情", type: richText }
|
|
24
|
+
- { name: "模块", type: text }
|
|
25
|
+
- {
|
|
26
|
+
name: "当前阶段",
|
|
27
|
+
type: singleSelect,
|
|
28
|
+
options: [ "PRD", "PRD 评审", "设计稿", "技术方案", "技术方案评审", "编码", "评审", "已发布" ]
|
|
29
|
+
}
|
|
30
|
+
- { name: "当前角色", type: singleSelect, options: [ "PM", "Dev", "人" ] }
|
|
31
|
+
- { name: "阻塞原因", type: text }
|
|
32
|
+
- { name: "解除条件", type: text }
|
|
33
|
+
- { name: "评审进展", type: text }
|
|
34
|
+
- { name: "AI问题", type: text }
|
|
35
|
+
- { name: "人工答复", type: text }
|
|
36
|
+
- { name: "Agent会话ID", type: text }
|
|
37
|
+
- { name: "当前CR链接", type: text }
|
|
38
|
+
- { name: "产出物", type: text }
|
|
39
|
+
- { name: "审计日志", type: text }
|
|
40
|
+
- { name: "审查轮次", type: number }
|
|
41
|
+
views:
|
|
42
|
+
- id: default_grid
|
|
43
|
+
type: Grid
|
|
44
|
+
name: "表格视图"
|
|
45
|
+
field_order:
|
|
46
|
+
[
|
|
47
|
+
"变更编号",
|
|
48
|
+
"标题",
|
|
49
|
+
"需求",
|
|
50
|
+
"需求详情",
|
|
51
|
+
"模块",
|
|
52
|
+
"当前阶段",
|
|
53
|
+
"当前角色",
|
|
54
|
+
"状态",
|
|
55
|
+
"AI问题",
|
|
56
|
+
"人工答复",
|
|
57
|
+
"当前CR链接",
|
|
58
|
+
"产出物",
|
|
59
|
+
"阻塞原因",
|
|
60
|
+
"解除条件",
|
|
61
|
+
"评审进展",
|
|
62
|
+
"当前步骤",
|
|
63
|
+
"事件日志"
|
|
64
|
+
]
|
|
65
|
+
steps:
|
|
66
|
+
- { id: prd, name: "PRD", recipe: prd, auto: true }
|
|
67
|
+
- { id: prd_review, name: "PRD 评审", recipe: prd-review, auto: true }
|
|
68
|
+
- { id: spec, name: "技术方案", recipe: spec, auto: true }
|
|
69
|
+
- { id: spec_review, name: "技术方案评审", recipe: spec-review, auto: true }
|
|
70
|
+
- { id: coding, name: "编码", recipe: coding, auto: true }
|
|
71
|
+
- { id: review, name: "评审", recipe: review, auto: true }
|
|
72
|
+
edges:
|
|
73
|
+
- { from: __start__, to: prd }
|
|
74
|
+
- { from: prd, to: prd_review }
|
|
75
|
+
- { from: prd_review, to: spec }
|
|
76
|
+
- { from: spec, to: spec_review }
|
|
77
|
+
- { from: spec_review, to: coding }
|
|
78
|
+
- { from: coding, to: review }
|
|
79
|
+
- { from: review, to: __end__ }
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "workflow-dsl/v1",
|
|
3
|
+
"name": "需求受理门禁",
|
|
4
|
+
"description": "按模块配置表查询 PM负责人,由其对待评估需求作出接受或驳回决定。",
|
|
5
|
+
"trigger": "start",
|
|
6
|
+
"_tables": {
|
|
7
|
+
"module_config": {
|
|
8
|
+
"name": "模块配置表",
|
|
9
|
+
"fields": [
|
|
10
|
+
{ "name": "模块名称", "type": "text" },
|
|
11
|
+
{ "name": "模块说明", "type": "text" },
|
|
12
|
+
{ "name": "典型例子", "type": "text" },
|
|
13
|
+
{ "name": "PM负责人", "type": "user" },
|
|
14
|
+
{ "name": "Dev负责人", "type": "user" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"steps": {
|
|
19
|
+
"start": {
|
|
20
|
+
"type": "WhenRecordUpdated",
|
|
21
|
+
"next": "find_module",
|
|
22
|
+
"data": {
|
|
23
|
+
"sheetId": "<sheetId>",
|
|
24
|
+
"updatedFields": ["<模块_fieldId>"],
|
|
25
|
+
"where": {
|
|
26
|
+
"and": [
|
|
27
|
+
{
|
|
28
|
+
"left": "<需求状态_fieldId>",
|
|
29
|
+
"op": "EQ",
|
|
30
|
+
"right": { "option": "<需求状态_待评估_optionId>" }
|
|
31
|
+
},
|
|
32
|
+
{ "left": "<模块_fieldId>", "op": "EXIST" }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"find_module": {
|
|
38
|
+
"type": "FindRecords",
|
|
39
|
+
"next": "notify_each_pm_owner",
|
|
40
|
+
"data": {
|
|
41
|
+
"sheetId": "<module_config_sheetId>",
|
|
42
|
+
"where": {
|
|
43
|
+
"left": "<module_config_模块名称_fieldId>",
|
|
44
|
+
"op": "EQ",
|
|
45
|
+
"right": { "ref": "$.start.<模块_fieldId>" }
|
|
46
|
+
},
|
|
47
|
+
"outputFields": ["<module_config_PM负责人_fieldId>"],
|
|
48
|
+
"terminateIfNotFound": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"notify_each_pm_owner": {
|
|
52
|
+
"type": "Loop",
|
|
53
|
+
"loopEntry": "send",
|
|
54
|
+
"next": null,
|
|
55
|
+
"data": {
|
|
56
|
+
"mode": "array",
|
|
57
|
+
"items": { "ref": "$.find_module.records[*]" },
|
|
58
|
+
"maxRuns": 20
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"send": {
|
|
62
|
+
"type": "SendMessage",
|
|
63
|
+
"next": null,
|
|
64
|
+
"data": {
|
|
65
|
+
"title": "需求受理",
|
|
66
|
+
"content": [
|
|
67
|
+
{ "text": "【需求受理】『" },
|
|
68
|
+
{ "ref": "$.start.<需求标题_fieldId>", "title": "需求标题" },
|
|
69
|
+
{ "text": "』(模块:" },
|
|
70
|
+
{ "ref": "$.start.<模块_fieldId>", "title": "模块" },
|
|
71
|
+
{ "text": ")待确认是否接入。" }
|
|
72
|
+
],
|
|
73
|
+
"to": { "users": [{ "ref": "$.notify_each_pm_owner.item.<module_config_PM负责人_fieldId>" }] },
|
|
74
|
+
"buttons": [
|
|
75
|
+
{
|
|
76
|
+
"name": "接受",
|
|
77
|
+
"color": "blue",
|
|
78
|
+
"action": {
|
|
79
|
+
"type": "updateRecord",
|
|
80
|
+
"sheetId": "<sheetId>",
|
|
81
|
+
"values": {
|
|
82
|
+
"<需求状态_fieldId>": { "option": "<需求状态_已接受_optionId>" }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "驳回",
|
|
88
|
+
"color": "red",
|
|
89
|
+
"action": {
|
|
90
|
+
"type": "updateRecord",
|
|
91
|
+
"sheetId": "<sheetId>",
|
|
92
|
+
"values": {
|
|
93
|
+
"<需求状态_fieldId>": { "option": "<需求状态_已驳回_optionId>" }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
tableRefConfigSchema,
|
|
3
|
+
type RecipeDefinition,
|
|
4
|
+
} from "aitable-workflow-core";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import {
|
|
7
|
+
failed,
|
|
8
|
+
recordValue,
|
|
9
|
+
selectName,
|
|
10
|
+
success,
|
|
11
|
+
} from "../../../lib/recipe-helpers.js";
|
|
12
|
+
|
|
13
|
+
// 模块配置表只由该分类 recipe 建表与读取,配置契约不应成为跨 recipe 的共享模块。
|
|
14
|
+
const moduleConfigSchema = tableRefConfigSchema({
|
|
15
|
+
label: "模块配置表",
|
|
16
|
+
description: "模块分类与 PM、Dev 负责人的公共只读配置表。",
|
|
17
|
+
access: "read",
|
|
18
|
+
ownership: "owned",
|
|
19
|
+
autoCreate: true,
|
|
20
|
+
defaultBase: "workflow.tracker.base_id",
|
|
21
|
+
setupSchema: {
|
|
22
|
+
fields: [
|
|
23
|
+
{ name: "模块名称", type: "text" },
|
|
24
|
+
{ name: "模块说明", type: "text" },
|
|
25
|
+
{ name: "典型例子", type: "text" },
|
|
26
|
+
{ name: "PM负责人", type: "user" },
|
|
27
|
+
{ name: "Dev负责人", type: "user" },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const recipe: RecipeDefinition = {
|
|
33
|
+
meta: {
|
|
34
|
+
id: "classify-module",
|
|
35
|
+
name: "模块判断",
|
|
36
|
+
role: "pm",
|
|
37
|
+
description: "读取模块配置表并由 PM Agent 判断模块",
|
|
38
|
+
version: "1.0.0",
|
|
39
|
+
requiresAgent: true,
|
|
40
|
+
config: z.object({
|
|
41
|
+
module_config: moduleConfigSchema,
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
async execute(ctx) {
|
|
45
|
+
const row = await ctx.toolkit.table.readCurrentRow();
|
|
46
|
+
const demandState = selectName(row["需求状态"]);
|
|
47
|
+
if (demandState === "已接受") {
|
|
48
|
+
return success({}, "PM负责人已接受需求,继续创建变更");
|
|
49
|
+
}
|
|
50
|
+
if (demandState === "已驳回") {
|
|
51
|
+
return success({}, "PM负责人已驳回需求,交由创建变更步骤结束流程");
|
|
52
|
+
}
|
|
53
|
+
if (row["模块"]) {
|
|
54
|
+
// 等待受理用 needs_human 而非 blocked(同 create-change):行保持 active,
|
|
55
|
+
// PM 把 需求状态 改为已接受/已驳回后下一轮自动续跑,人不需要碰 执行状态。
|
|
56
|
+
return {
|
|
57
|
+
status: "needs_human",
|
|
58
|
+
blockReason: `模块已判定为「${recordValue(row, "模块") ?? ""}」;请在需求池将 需求状态 改为「已接受」或「已驳回」以继续`,
|
|
59
|
+
output: {},
|
|
60
|
+
logs: ["等待 PM 受理门禁"],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const moduleTable = ctx.config.module_config as
|
|
64
|
+
| { baseId?: string; tableId?: string }
|
|
65
|
+
| undefined;
|
|
66
|
+
if (!moduleTable?.baseId || !moduleTable.tableId) {
|
|
67
|
+
return failed("模块配置表尚未完成 setup");
|
|
68
|
+
}
|
|
69
|
+
// 表内容即白名单:配置表由用户自由维护(setup 自动建表不预置行),
|
|
70
|
+
// 不在代码里硬编码候选模块名——那会让"表可自由配置"的承诺失效,
|
|
71
|
+
// 且表内填了名单外模块时查询为空,表现为无限重复"模块配置表为空"。
|
|
72
|
+
const allRows = await ctx.toolkit.table.listRecordsIn({
|
|
73
|
+
baseId: moduleTable.baseId,
|
|
74
|
+
tableId: moduleTable.tableId,
|
|
75
|
+
});
|
|
76
|
+
const modules = allRows.filter((mod) => String(mod.fields["模块名称"] ?? "").trim() !== "");
|
|
77
|
+
if (modules.length === 0) {
|
|
78
|
+
return {
|
|
79
|
+
status: "needs_human",
|
|
80
|
+
blockReason:
|
|
81
|
+
"模块配置表为空,无法判定模块;请先在「模块配置表」补充 模块名称/模块说明,或直接在需求行填写 模块 列",
|
|
82
|
+
output: {},
|
|
83
|
+
logs: ["模块配置表为空"],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const r = await ctx.toolkit.coding.run(
|
|
87
|
+
`## 本轮上下文
|
|
88
|
+
|
|
89
|
+
### 需求
|
|
90
|
+
需求标题:${recordValue(row, "需求标题")}
|
|
91
|
+
需求详情:${recordValue(row, "需求详情")}
|
|
92
|
+
来源类型:${selectName(row["来源类型"])}
|
|
93
|
+
|
|
94
|
+
### 模块配置
|
|
95
|
+
\`\`\`json
|
|
96
|
+
${JSON.stringify(modules, null, 2)}
|
|
97
|
+
\`\`\`
|
|
98
|
+
|
|
99
|
+
## 本轮任务
|
|
100
|
+
依据需求和模块配置判定唯一模块。
|
|
101
|
+
|
|
102
|
+
## 输出格式
|
|
103
|
+
首行只输出模块名称,第二行说明依据。`.trim(),
|
|
104
|
+
);
|
|
105
|
+
if (!r.success) return failed("模块判断 Agent 未完成");
|
|
106
|
+
// 只按首行匹配:第二行是判定依据,可能提及其他模块名导致误判。
|
|
107
|
+
const firstLine = r.response.split("\n")[0]?.trim() ?? "";
|
|
108
|
+
const selected = modules.find((mod) =>
|
|
109
|
+
firstLine.includes(String(mod.fields["模块名称"])),
|
|
110
|
+
);
|
|
111
|
+
if (!selected) {
|
|
112
|
+
// AI 的判定不丢弃:写进模块列作候选,下一轮走「模块已判定」的受理等待分支,
|
|
113
|
+
// 人只需改 需求状态(认可)或直接改 模块 列(不认可)。
|
|
114
|
+
if (firstLine) {
|
|
115
|
+
await ctx.toolkit.table.updateCurrentRow({ 模块: firstLine });
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
status: "needs_human",
|
|
119
|
+
blockReason: firstLine
|
|
120
|
+
? `AI 判定「${firstLine}」不在模块配置表中,已暂存为候选;认可请将 需求状态 改为「已接受」,不认可请直接修改 模块 列`
|
|
121
|
+
: "AI 未给出可辨识的模块判定;请直接填写 模块 列,或将 需求状态 改为「已接受/已驳回」以继续",
|
|
122
|
+
output: {},
|
|
123
|
+
logs: ["模块判定未匹配配置表,等待人工确认"],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
await ctx.toolkit.table.updateCurrentRow({
|
|
127
|
+
模块: selected.fields["模块名称"],
|
|
128
|
+
需求状态: "待评估",
|
|
129
|
+
});
|
|
130
|
+
return success({}, "模块已判定,等待 PM负责人受理");
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
export default recipe;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { RecipeDefinition } from "aitable-workflow-core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { failed, selectName, success } from "../../../lib/recipe-helpers.js";
|
|
4
|
+
|
|
5
|
+
// 只有创建变更需要跨 workflow 写入目标 tracker,契约随 recipe 就近声明。
|
|
6
|
+
// profile 用 workflow:execution.tracker.* 引用提供实际表,不允许写死目标 tableId。
|
|
7
|
+
const createChangeConfig = z.object({
|
|
8
|
+
execution_tracker: z.object({
|
|
9
|
+
base_id: z.string().min(1),
|
|
10
|
+
table_id: z.string().min(1),
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/** 需求标题的确定性短哈希:保证同一标题重试得到同一编号,不同标题编号不碰撞。 */
|
|
15
|
+
function titleHash(title: string): string {
|
|
16
|
+
let hash = 5381;
|
|
17
|
+
for (const char of title) {
|
|
18
|
+
hash = ((hash << 5) + hash + char.charCodeAt(0)) >>> 0;
|
|
19
|
+
}
|
|
20
|
+
return hash.toString(36).padStart(7, "0").slice(-6);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const recipe: RecipeDefinition = {
|
|
24
|
+
meta: {
|
|
25
|
+
id: "create-change",
|
|
26
|
+
name: "创建变更",
|
|
27
|
+
role: "pm",
|
|
28
|
+
description: "仅在需求已接受后创建变更流水",
|
|
29
|
+
version: "1.0.0",
|
|
30
|
+
requiresAgent: false,
|
|
31
|
+
config: createChangeConfig,
|
|
32
|
+
},
|
|
33
|
+
async execute(ctx) {
|
|
34
|
+
const row = await ctx.toolkit.table.readCurrentRow();
|
|
35
|
+
const demandState = selectName(row["需求状态"]);
|
|
36
|
+
if (demandState === "已驳回") {
|
|
37
|
+
return success({}, "需求已驳回,不创建变更流水");
|
|
38
|
+
}
|
|
39
|
+
// 受理等待用 needs_human 而非 blocked:记录保持 执行状态=active 留在轮询集合里,
|
|
40
|
+
// PM 点接受后下一轮自读到「已接受」即建变更流水,不依赖按钮把状态从 blocked 推回。
|
|
41
|
+
// 不复用 waitForExternalProgress:它写 状态/阻塞原因/解除条件(执行 tracker 字段),
|
|
42
|
+
// 而 intake 需求池的状态字段叫 执行状态、也没有那两列。
|
|
43
|
+
if (demandState !== "已接受")
|
|
44
|
+
return {
|
|
45
|
+
status: "needs_human",
|
|
46
|
+
blockReason: "等待 PM负责人将需求状态写为已接受",
|
|
47
|
+
output: {},
|
|
48
|
+
logs: ["等待受理门禁"],
|
|
49
|
+
};
|
|
50
|
+
const target = ctx.config.execution_tracker as
|
|
51
|
+
| { base_id?: string; table_id?: string }
|
|
52
|
+
| undefined;
|
|
53
|
+
if (!target?.base_id || !target.table_id)
|
|
54
|
+
return failed("缺少 execution tracker 引用配置");
|
|
55
|
+
const title = String(row["需求标题"] ?? "").trim();
|
|
56
|
+
const slug =
|
|
57
|
+
title
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
60
|
+
.replace(/^-|-$/g, "")
|
|
61
|
+
.slice(0, 32) || "change";
|
|
62
|
+
const id = `CHG-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${slug}-${titleHash(title)}`;
|
|
63
|
+
const existing = await ctx.toolkit.table.findRecordIn(
|
|
64
|
+
{ baseId: target.base_id, tableId: target.table_id },
|
|
65
|
+
"变更编号",
|
|
66
|
+
id,
|
|
67
|
+
);
|
|
68
|
+
const recordId =
|
|
69
|
+
existing?.recordId ??
|
|
70
|
+
(
|
|
71
|
+
await ctx.toolkit.table.createRecordIn(
|
|
72
|
+
{ baseId: target.base_id, tableId: target.table_id },
|
|
73
|
+
{
|
|
74
|
+
变更编号: id,
|
|
75
|
+
标题: row["需求标题"],
|
|
76
|
+
需求: row["需求标题"],
|
|
77
|
+
需求详情: row["需求详情"],
|
|
78
|
+
模块: row["模块"],
|
|
79
|
+
当前阶段: "PRD",
|
|
80
|
+
当前角色: "PM",
|
|
81
|
+
状态: "active",
|
|
82
|
+
// 人工通知在发送时按模块和阶段查询负责人,不能依赖可能为空的临时指派字段。
|
|
83
|
+
审计日志: `${id}+PRD`,
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
).recordId;
|
|
87
|
+
await ctx.toolkit.table.updateCurrentRow({
|
|
88
|
+
产出物: `变更流水记录:${recordId}`,
|
|
89
|
+
需求状态: "开发中",
|
|
90
|
+
});
|
|
91
|
+
return success({}, "已创建或复用变更流水记录,需求进入开发中");
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
export default recipe;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RecipeDefinition } from "aitable-workflow-core";
|
|
2
|
+
import { blocked, success } from "../../../lib/recipe-helpers.js";
|
|
3
|
+
|
|
4
|
+
const recipe: RecipeDefinition = {
|
|
5
|
+
meta: {
|
|
6
|
+
id: "mechanical-deduplicate",
|
|
7
|
+
name: "机械去重",
|
|
8
|
+
role: "pm",
|
|
9
|
+
description: "按需求标题阻止重复需求",
|
|
10
|
+
version: "1.1.0",
|
|
11
|
+
requiresAgent: false,
|
|
12
|
+
},
|
|
13
|
+
async execute(ctx) {
|
|
14
|
+
const current = await ctx.toolkit.table.readCurrentRow();
|
|
15
|
+
if (current["需求状态"] === "待澄清") {
|
|
16
|
+
return blocked("疑似重复,等待人工确认");
|
|
17
|
+
}
|
|
18
|
+
const title = String(current["需求标题"] ?? "").trim();
|
|
19
|
+
if (!title) {
|
|
20
|
+
return blocked("缺少需求标题,无法执行机械去重");
|
|
21
|
+
}
|
|
22
|
+
const sameTitle = await ctx.toolkit.table.queryRows({ 需求标题: title }, 3);
|
|
23
|
+
if (sameTitle.length > 1) {
|
|
24
|
+
await ctx.toolkit.table.updateCurrentRow({
|
|
25
|
+
需求状态: "待澄清",
|
|
26
|
+
产出物: "机械去重:发现相同标题的已有需求",
|
|
27
|
+
});
|
|
28
|
+
return blocked("机械去重命中,等待人工确认");
|
|
29
|
+
}
|
|
30
|
+
return success({}, "同标题机械去重未命中");
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export default recipe;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
meta:
|
|
2
|
+
id: intake
|
|
3
|
+
name: "需求受理 / 分诊"
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
domain: ai_native_sdlc
|
|
6
|
+
tracker:
|
|
7
|
+
label: "需求池"
|
|
8
|
+
# 留空 = 搭建(setup)阶段新建 Base 与表并自动回填;填了值 = 绑定已有表。
|
|
9
|
+
# 模板不预绑任何人的库;绑定作者库跑通的包不能直接当模板分发。
|
|
10
|
+
base_id: ""
|
|
11
|
+
table_id: ""
|
|
12
|
+
states:
|
|
13
|
+
{
|
|
14
|
+
initial: active,
|
|
15
|
+
active: [ active ],
|
|
16
|
+
review: blocked,
|
|
17
|
+
# 需求池走完 intake 三步 = 已建出变更流水,「已建单」是本行引擎列的终点。
|
|
18
|
+
# 需求是否上线看业务列 需求状态(发布时 review.recipe 回写「已完成」);
|
|
19
|
+
# shipped 只属于变更流水表的终态语义,不复用到这里(避免与业务词撞车)。
|
|
20
|
+
terminal: [ 已建单, archived ],
|
|
21
|
+
blocked: blocked
|
|
22
|
+
}
|
|
23
|
+
fields: { state: "执行状态", title: "需求标题", current_step: "当前步骤", event_log: "事件日志" }
|
|
24
|
+
field_definitions:
|
|
25
|
+
- { name: "需求详情", type: richText }
|
|
26
|
+
- {
|
|
27
|
+
name: "来源类型",
|
|
28
|
+
type: singleSelect,
|
|
29
|
+
options: [ "群反馈", "Aone", "用户访谈", "竞品", "内部提议", "巡检", "线上事故" ]
|
|
30
|
+
}
|
|
31
|
+
- name: "需求状态"
|
|
32
|
+
type: singleSelect
|
|
33
|
+
options: [ "待评估", "已接受", "开发中", "已完成", "已驳回", "待澄清" ]
|
|
34
|
+
- { name: "模块", type: text }
|
|
35
|
+
- { name: "产出物", type: text }
|
|
36
|
+
steps:
|
|
37
|
+
- {
|
|
38
|
+
id: mechanical_deduplicate,
|
|
39
|
+
name: "机械去重",
|
|
40
|
+
recipe: mechanical-deduplicate,
|
|
41
|
+
auto: true,
|
|
42
|
+
description: "按需求标题去重,高相似需求直接关单或阻塞。",
|
|
43
|
+
}
|
|
44
|
+
- {
|
|
45
|
+
id: classify_module,
|
|
46
|
+
name: "模块判断",
|
|
47
|
+
recipe: classify-module,
|
|
48
|
+
auto: true,
|
|
49
|
+
description: "读取模块配置表,由 PM Agent 判定唯一模块。",
|
|
50
|
+
}
|
|
51
|
+
- {
|
|
52
|
+
id: create_change,
|
|
53
|
+
name: "创建变更",
|
|
54
|
+
recipe: create-change,
|
|
55
|
+
auto: true,
|
|
56
|
+
description: "需求被接受后在 execution tracker 创建变更流水记录。",
|
|
57
|
+
}
|
|
58
|
+
edges:
|
|
59
|
+
- { from: __start__, to: mechanical_deduplicate }
|
|
60
|
+
- { from: mechanical_deduplicate, to: classify_module }
|
|
61
|
+
- { from: classify_module, to: create_change }
|
|
62
|
+
- { from: create_change, to: __end__ }
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Dev 工作规程
|
|
2
|
+
|
|
3
|
+
只操作每轮任务明确给出的候选仓库 worktree(绝对路径);当前目录不是工作对象,所有文件读写、测试和 Git 命令都必须发生在给定 worktree 内。先依据已批准 PRD 与工作流提供的技术方案工件判断实际涉及的仓库;禁止为了覆盖率修改无关仓库。
|
|
4
|
+
|
|
5
|
+
本文件只是角色通用规程;进入某个仓库 worktree 后,必须先读取该 worktree 根目录下的 `AGENTS.md`(若存在)并遵循其仓库级约定,仓库约定优先于本通用规程。
|
|
6
|
+
|
|
7
|
+
## 外部需求链接解析
|
|
8
|
+
|
|
9
|
+
技术方案或需求上下文中的外部链接必须先按链接类型只读解析:
|
|
10
|
+
|
|
11
|
+
- Aone 工作项链接(`project.aone.alibaba.com/.../(bug|req|task)/<id>`):执行 `a1 project workitem get <id> --format json`,读取 `title`、`description`、`status` 和必要字段。
|
|
12
|
+
- 钉钉文档链接(`alidocs.dingtalk.com/i/nodes/<nodeId>`):执行 `dws doc info --node '<原始URL>'` 获取元信息,再执行 `dws doc read --node '<原始URL>' --format raw`,读取返回 JSON 的 `markdown` 字段。
|
|
13
|
+
|
|
14
|
+
多个链接逐个解析并保留来源 URL。CLI 未安装、未登录、无权限或读取失败时,必须记录具体原因并提问,不得猜测正文;所有调用均为只读,禁止修改外部工作项或文档。
|
|
15
|
+
|
|
16
|
+
工件路径和内容以工作流提供的 `ARTIFACT_FILES` 及阶段 prompt 为准,本文件不重复维护文件名或章节。各阶段都必须按选中仓库分别记录真实产出和测试证据;不得伪造测试或 CI 通过证据。创建 CR 后持续处理新评论,只有全部 CR 实际合并后才能进入发布门禁。
|
|
17
|
+
|
|
18
|
+
## 测试需求模式
|
|
19
|
+
|
|
20
|
+
当需求标题以 `【测试】` 开头时,仍须真实执行计划、修改、测试、Git 提交与 CR 评审;这不是跳过 Agent 的开关。
|
|
21
|
+
|
|
22
|
+
- recipe 只会提供一个候选 worktree。不得遍历、索引或调研代码库;只读取该 worktree 内本阶段所需的 `changes/<变更编号>/` 工件和评论。
|
|
23
|
+
- 不修改业务功能文件。技术方案阶段只写简短技术方案工件;编码阶段在同一目录创建 `smoke.test.mjs`,使用 Node 内置 `node:test` 写一个通过用例,并运行工作流指定的真实测试命令;评审阶段更新评审工件。
|
|
24
|
+
- 以最短步骤完成,不得伪造测试或 CI 通过证据;没有实际 CI 时在工件中如实写“未配置 CI”。
|
|
25
|
+
|
|
26
|
+
## 提问协议(所有阶段通用)
|
|
27
|
+
|
|
28
|
+
需要真人确认时不得猜测或编造,必须提问:
|
|
29
|
+
|
|
30
|
+
- 每个工件末尾固定保留 `## 待确认问题`,逐项列出尚未被人工答复的问题;没有问题时该节写“无”。
|
|
31
|
+
- 最终回复的末尾单独输出一行机器可读清单:`SDLC_QUESTIONS=["问题1","问题2"]`;没有问题输出 `SDLC_QUESTIONS=[]`。系统会按阶段自动分配稳定编号,无需自行编号。
|
|
32
|
+
- 提问后等待人工答复,答复会在下一轮以「已消费的人工答复」给到,继续同一会话。
|
|
33
|
+
## Git 提交边界
|
|
34
|
+
|
|
35
|
+
不得执行 `git commit` 或 `git push`。Agent 只写入指定 worktree 并运行必要测试;工作流会在 Agent 成功后统一提交、推送和创建 CR。
|
|
36
|
+
# 构建工件
|
|
37
|
+
|
|
38
|
+
每个实际改动的业务仓库都必须写入 `changes/<变更编号>/03-coding.md`,并且包含:
|
|
39
|
+
|
|
40
|
+
- `## 改动内容`:面向 CR 评审者的实际行为、关键文件、兼容性与风险。
|
|
41
|
+
- `## 测试命令与结果`:真实执行的命令与输出摘要。
|
|
42
|
+
- `## CI 证据`:已知的流水线/检查通过证据。
|
|
43
|
+
|
|
44
|
+
## 表达规范
|
|
45
|
+
|
|
46
|
+
- 禁止使用"应该"、"大概"、"可能"、"或许"等模糊表述
|
|
47
|
+
- 每个结论必须有代码级证据(文件路径 + 行号 + 实际输出)
|
|
48
|
+
- 不确定时通过 SDLC_QUESTIONS 向人工确认,不猜测
|
|
49
|
+
|
|
50
|
+
## ⚖️ 证据铁律
|
|
51
|
+
|
|
52
|
+
**没有当场跑过验证命令并看到输出 → 不能声称通过。**
|
|
53
|
+
|
|
54
|
+
### 操作规程
|
|
55
|
+
1. 识别验证命令(测试/类型检查/构建)
|
|
56
|
+
2. 运行完整命令(不是部分)
|
|
57
|
+
3. 阅读完整输出(不是尾部)
|
|
58
|
+
4. 确认输出支持你的声明
|
|
59
|
+
5. 只有此时才能声称"通过"
|
|
60
|
+
|
|
61
|
+
### 🚩 红旗信号
|
|
62
|
+
- 使用"应该"、"大概"、"可能" → 还没验证,继续跑命令
|
|
63
|
+
- 验证前说"太好了"、"搞定了" → 停下来,先跑验证
|
|
64
|
+
- 准备提交但没跑测试 → 不允许
|
|
65
|
+
|
|
66
|
+
### 工件必须包含的证据
|
|
67
|
+
- ✅ 测试命令的**实际运行输出**
|
|
68
|
+
- ✅ 类型检查命令的**实际运行输出**
|
|
69
|
+
- ✅ `git diff HEAD~1` 的**实际输出**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# PM 工作规程
|
|
2
|
+
|
|
3
|
+
负责需求分诊、意图、PRD 与前序工件审阅。所有结论必须写入当前变更的 Git 工件或 AI 表格字段,不能只留在对话中。
|
|
4
|
+
|
|
5
|
+
每轮任务会给出本次变更的 worktree 绝对路径;当前目录不是工作对象,所有文件读写和命令都必须发生在给定的 worktree 路径内。将意图与 PRD 写入 PM worktree 的 `changes/<变更编号>/`。
|
|
6
|
+
|
|
7
|
+
本文件只是角色通用规程;进入某个仓库 worktree 后,必须先读取该 worktree 根目录下的 `AGENTS.md`(若存在)并遵循其仓库级约定,仓库约定优先于本通用规程。
|
|
8
|
+
|
|
9
|
+
## 业务代码上下文
|
|
10
|
+
|
|
11
|
+
当 PRD prompt 提供业务代码仓库 URL 时,先按可用的只读能力阅读其 `master` 当前逻辑,再撰写 PRD。
|
|
12
|
+
业务代码仓库只用于理解现状,不是 PM 工件的写入目标:不得为它们创建变更 worktree、分支、工件、commit 或 push。
|
|
13
|
+
|
|
14
|
+
## 外部需求链接解析
|
|
15
|
+
|
|
16
|
+
需求标题或详情中的外部链接必须先按链接类型只读解析,再用于 PRD:
|
|
17
|
+
|
|
18
|
+
- Aone 工作项链接(`project.aone.alibaba.com/.../(bug|req|task)/<id>`):执行 `a1 project workitem get <id> --format json`。读取返回的 `title`、`description`、`status` 和需要的字段;URL 中的 project id 仅用于核对归属,不替代 workitem id。
|
|
19
|
+
- 钉钉文档链接(`alidocs.dingtalk.com/i/nodes/<nodeId>`):先执行 `dws doc info --node '<原始URL>'`,再执行 `dws doc read --node '<原始URL>' --format raw`,从返回 JSON 的 `markdown` 字段读取正文。
|
|
20
|
+
|
|
21
|
+
多个链接逐个解析并保留来源 URL。CLI 未安装、未登录、无权限或读取失败时,必须把具体失败原因写入「待确认问题」,不得根据链接标题猜测正文;所有调用均为只读,禁止修改 Aone 工作项或钉钉文档。
|
|
22
|
+
|
|
23
|
+
## 测试需求模式
|
|
24
|
+
|
|
25
|
+
当需求标题以 `【测试】` 开头时,仍须真实生成本阶段工件并走 Git/CR 流程;这不是跳过 Agent 的开关。
|
|
26
|
+
|
|
27
|
+
- 不读取、遍历、索引或调研任何业务代码仓库;只读取当前 PM worktree 与本轮明确给出的工件、评论。
|
|
28
|
+
- 立即在 `changes/<变更编号>/01-prd.md` 写入简短 PRD:目标是验证此 SDLC 流程;验收标准是工件、测试、commit 和 CR 均真实完成;依赖写“无”;`## 待确认问题` 写“无”。不要为业务功能补充背景、设计或调研。
|
|
29
|
+
- 以最短步骤完成,不能因测试内容不重要而跳过文件写入、提交或评论处理。
|
|
30
|
+
|
|
31
|
+
## 提问协议(所有阶段通用)
|
|
32
|
+
|
|
33
|
+
需要真人确认时不得猜测或编造,必须提问:
|
|
34
|
+
|
|
35
|
+
- 每个工件末尾固定保留 `## 待确认问题`,逐项列出尚未被人工答复的问题;没有问题时该节写“无”。
|
|
36
|
+
- 最终回复的末尾单独输出一行机器可读清单:`SDLC_QUESTIONS=["问题1","问题2"]`;没有问题输出 `SDLC_QUESTIONS=[]`。系统会按阶段自动分配稳定编号,无需自行编号。
|
|
37
|
+
- 提问后等待人工答复,答复会在下一轮以「已消费的人工答复」给到,继续同一会话。
|
|
38
|
+
|
|
39
|
+
PRD 需形成可评审工件,并在 CR 评论出现后逐项修复、更新工件和验证结果。只以实际 CR 合并作为进入实现计划的事实。
|
|
40
|
+
## Git 提交边界
|
|
41
|
+
|
|
42
|
+
不得执行 `git commit` 或 `git push`。Agent 只写入指定 worktree 并运行必要测试;工作流会在 Agent 成功后统一提交、推送和创建 CR。
|
|
43
|
+
|
|
44
|
+
## 表达规范
|
|
45
|
+
|
|
46
|
+
- 禁止使用"应该"、"大概"、"可能"、"或许"等模糊表述
|
|
47
|
+
- 每个结论必须有代码级证据(文件路径 + 行号 + 实际输出)
|
|
48
|
+
- 不确定时通过 SDLC_QUESTIONS 向人工确认,不猜测
|