@xihe-lab/tapd-cli 2.0.0-rc.1 → 2.0.0-rc.3
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/README.md +22 -0
- package/dist/advisor/domain-axis.d.ts +16 -0
- package/dist/advisor/domain-axis.d.ts.map +1 -0
- package/dist/advisor/domain-axis.js +43 -0
- package/dist/advisor/domain-axis.js.map +1 -0
- package/dist/advisor/domain-axis.test.d.ts +2 -0
- package/dist/advisor/domain-axis.test.d.ts.map +1 -0
- package/dist/advisor/domain-axis.test.js +198 -0
- package/dist/advisor/domain-axis.test.js.map +1 -0
- package/dist/advisor/index.d.ts +8 -0
- package/dist/advisor/index.d.ts.map +1 -0
- package/dist/advisor/index.js +6 -0
- package/dist/advisor/index.js.map +1 -0
- package/dist/advisor/persona.d.ts +12 -0
- package/dist/advisor/persona.d.ts.map +1 -0
- package/dist/advisor/persona.js +34 -0
- package/dist/advisor/persona.js.map +1 -0
- package/dist/advisor/recall.d.ts +38 -0
- package/dist/advisor/recall.d.ts.map +1 -0
- package/dist/advisor/recall.js +96 -0
- package/dist/advisor/recall.js.map +1 -0
- package/dist/advisor/render.d.ts +8 -0
- package/dist/advisor/render.d.ts.map +1 -0
- package/dist/advisor/render.js +42 -0
- package/dist/advisor/render.js.map +1 -0
- package/dist/bin/tapd.js +0 -0
- package/dist/commands/advisor.d.ts.map +1 -1
- package/dist/commands/advisor.js +31 -13
- package/dist/commands/advisor.js.map +1 -1
- package/dist/commands/pipeline.d.ts +4 -0
- package/dist/commands/pipeline.d.ts.map +1 -0
- package/dist/commands/pipeline.js +178 -0
- package/dist/commands/pipeline.js.map +1 -0
- package/dist/commands/pm.d.ts +4 -0
- package/dist/commands/pm.d.ts.map +1 -0
- package/dist/commands/pm.js +175 -0
- package/dist/commands/pm.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/engine.d.ts +38 -0
- package/dist/pipeline/engine.d.ts.map +1 -0
- package/dist/pipeline/engine.js +326 -0
- package/dist/pipeline/engine.js.map +1 -0
- package/dist/pipeline/engine.test.d.ts +2 -0
- package/dist/pipeline/engine.test.d.ts.map +1 -0
- package/dist/pipeline/engine.test.js +419 -0
- package/dist/pipeline/engine.test.js.map +1 -0
- package/dist/pipeline/expression.d.ts +31 -0
- package/dist/pipeline/expression.d.ts.map +1 -0
- package/dist/pipeline/expression.js +249 -0
- package/dist/pipeline/expression.js.map +1 -0
- package/dist/pipeline/expression.test.d.ts +2 -0
- package/dist/pipeline/expression.test.d.ts.map +1 -0
- package/dist/pipeline/expression.test.js +111 -0
- package/dist/pipeline/expression.test.js.map +1 -0
- package/dist/pipeline/history.d.ts +13 -0
- package/dist/pipeline/history.d.ts.map +1 -0
- package/dist/pipeline/history.js +66 -0
- package/dist/pipeline/history.js.map +1 -0
- package/dist/pipeline/index.d.ts +13 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +9 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/record.d.ts +13 -0
- package/dist/pipeline/record.d.ts.map +1 -0
- package/dist/pipeline/record.js +30 -0
- package/dist/pipeline/record.js.map +1 -0
- package/dist/pipeline/record.test.d.ts +2 -0
- package/dist/pipeline/record.test.d.ts.map +1 -0
- package/dist/pipeline/record.test.js +109 -0
- package/dist/pipeline/record.test.js.map +1 -0
- package/dist/pipeline/template.d.ts +24 -0
- package/dist/pipeline/template.d.ts.map +1 -0
- package/dist/pipeline/template.js +134 -0
- package/dist/pipeline/template.js.map +1 -0
- package/dist/pipeline/template.test.d.ts +2 -0
- package/dist/pipeline/template.test.d.ts.map +1 -0
- package/dist/pipeline/template.test.js +94 -0
- package/dist/pipeline/template.test.js.map +1 -0
- package/dist/pipeline/types.d.ts +99 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +31 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/pipeline/unwrap.d.ts +3 -0
- package/dist/pipeline/unwrap.d.ts.map +1 -0
- package/dist/pipeline/unwrap.js +26 -0
- package/dist/pipeline/unwrap.js.map +1 -0
- package/dist/pipeline/unwrap.test.d.ts +2 -0
- package/dist/pipeline/unwrap.test.d.ts.map +1 -0
- package/dist/pipeline/unwrap.test.js +36 -0
- package/dist/pipeline/unwrap.test.js.map +1 -0
- package/dist/pipeline/validate.d.ts +18 -0
- package/dist/pipeline/validate.d.ts.map +1 -0
- package/dist/pipeline/validate.js +134 -0
- package/dist/pipeline/validate.js.map +1 -0
- package/dist/pipeline/validate.test.d.ts +2 -0
- package/dist/pipeline/validate.test.d.ts.map +1 -0
- package/dist/pipeline/validate.test.js +166 -0
- package/dist/pipeline/validate.test.js.map +1 -0
- package/dist/pm/discovery.d.ts +54 -0
- package/dist/pm/discovery.d.ts.map +1 -0
- package/dist/pm/discovery.js +198 -0
- package/dist/pm/discovery.js.map +1 -0
- package/dist/pm/discovery.test.d.ts +2 -0
- package/dist/pm/discovery.test.d.ts.map +1 -0
- package/dist/pm/discovery.test.js +147 -0
- package/dist/pm/discovery.test.js.map +1 -0
- package/dist/pm/domains.d.ts +15 -0
- package/dist/pm/domains.d.ts.map +1 -0
- package/dist/pm/domains.js +27 -0
- package/dist/pm/domains.js.map +1 -0
- package/dist/pm/domains.test.d.ts +2 -0
- package/dist/pm/domains.test.d.ts.map +1 -0
- package/dist/pm/domains.test.js +141 -0
- package/dist/pm/domains.test.js.map +1 -0
- package/dist/pm/index.d.ts +9 -0
- package/dist/pm/index.d.ts.map +1 -0
- package/dist/pm/index.js +6 -0
- package/dist/pm/index.js.map +1 -0
- package/dist/pm/iteration.d.ts +26 -0
- package/dist/pm/iteration.d.ts.map +1 -0
- package/dist/pm/iteration.js +50 -0
- package/dist/pm/iteration.js.map +1 -0
- package/dist/pm/iteration.test.d.ts +2 -0
- package/dist/pm/iteration.test.d.ts.map +1 -0
- package/dist/pm/iteration.test.js +125 -0
- package/dist/pm/iteration.test.js.map +1 -0
- package/dist/pm/pm-command.test.d.ts +2 -0
- package/dist/pm/pm-command.test.d.ts.map +1 -0
- package/dist/pm/pm-command.test.js +321 -0
- package/dist/pm/pm-command.test.js.map +1 -0
- package/dist/pm/render.d.ts +17 -0
- package/dist/pm/render.d.ts.map +1 -0
- package/dist/pm/render.js +82 -0
- package/dist/pm/render.js.map +1 -0
- package/dist/pm/tpl-communication-stakeholder.test.d.ts +2 -0
- package/dist/pm/tpl-communication-stakeholder.test.d.ts.map +1 -0
- package/dist/pm/tpl-communication-stakeholder.test.js +392 -0
- package/dist/pm/tpl-communication-stakeholder.test.js.map +1 -0
- package/dist/pm/tpl-integration-scope.test.d.ts +2 -0
- package/dist/pm/tpl-integration-scope.test.d.ts.map +1 -0
- package/dist/pm/tpl-integration-scope.test.js +387 -0
- package/dist/pm/tpl-integration-scope.test.js.map +1 -0
- package/dist/pm/tpl-quality-risk.test.d.ts +2 -0
- package/dist/pm/tpl-quality-risk.test.d.ts.map +1 -0
- package/dist/pm/tpl-quality-risk.test.js +414 -0
- package/dist/pm/tpl-quality-risk.test.js.map +1 -0
- package/dist/pm/tpl-schedule-cost.test.d.ts +2 -0
- package/dist/pm/tpl-schedule-cost.test.d.ts.map +1 -0
- package/dist/pm/tpl-schedule-cost.test.js +376 -0
- package/dist/pm/tpl-schedule-cost.test.js.map +1 -0
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +6 -0
- package/dist/program.js.map +1 -1
- package/package.json +17 -11
- package/templates/deliver-story.yaml +38 -0
- package/templates/iteration-review.yaml +52 -0
- package/templates/pm/README.md +57 -0
- package/templates/pm/communication-notify.yaml +65 -0
- package/templates/pm/communication-weekly.yaml +160 -0
- package/templates/pm/cost-report.yaml +74 -0
- package/templates/pm/integration-close.yaml +118 -0
- package/templates/pm/integration-health.yaml +155 -0
- package/templates/pm/integration-kickoff.yaml +94 -0
- package/templates/pm/quality-dashboard.yaml +88 -0
- package/templates/pm/quality-triage.yaml +42 -0
- package/templates/pm/risk-register.yaml +55 -0
- package/templates/pm/risk-scan.yaml +87 -0
- package/templates/pm/schedule-burndown.yaml +87 -0
- package/templates/pm/schedule-standup.yaml +109 -0
- package/templates/pm/scope-baseline.yaml +93 -0
- package/templates/pm/scope-drift-check.yaml +129 -0
- package/templates/pm/stakeholder-map.yaml +121 -0
- package/templates/triage-bugs.yaml +49 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 范围基线冻结(写场景:需求快照写入 TAPD 项目基线)—— 范围管理(软考高项第4版 第9章)
|
|
2
|
+
# 场景: tapd pm scope baseline
|
|
3
|
+
# [--input '{"iteration_name":"自定义名(缺省取迭代名)"}'] --yes
|
|
4
|
+
# 编排: 拉当前迭代需求快照(id/name/status/priority/label) → expr 摘要(含剔除风险项口径)
|
|
5
|
+
# → tapd_add_baseline(name=<迭代名>-基线-<今天>, baseline_date=今天(经 expr 计算), description=快照 JSON)
|
|
6
|
+
# 口径: 快照过滤 zzz-delete-me*;度量分母剔除风险项(label 含 risk 或名称以【风险】开头),
|
|
7
|
+
# 但快照本体全量入基线(冻结的是完整范围,风险项也是范围)
|
|
8
|
+
name: scope-baseline
|
|
9
|
+
domain: scope
|
|
10
|
+
title: 范围基线冻结(当前迭代需求快照 → 项目基线,软考第9章)
|
|
11
|
+
write: true
|
|
12
|
+
unwrap: true
|
|
13
|
+
vars:
|
|
14
|
+
iteration_id: ""
|
|
15
|
+
iteration_name: ""
|
|
16
|
+
steps:
|
|
17
|
+
- id: iteration
|
|
18
|
+
uses: tapd_get_iterations
|
|
19
|
+
args:
|
|
20
|
+
id: "${vars.iteration_id}"
|
|
21
|
+
fields: "id,name,startdate,enddate,status"
|
|
22
|
+
filter: "!item.name.startsWith('zzz-delete-me')"
|
|
23
|
+
|
|
24
|
+
# 快照(只读,波次 1 与 iteration 并发)
|
|
25
|
+
- id: snapshot
|
|
26
|
+
uses: tapd_get_stories
|
|
27
|
+
args:
|
|
28
|
+
iteration_id: "${vars.iteration_id}"
|
|
29
|
+
limit: 200
|
|
30
|
+
fields: "id,name,status,priority,label"
|
|
31
|
+
filter: "!item.name.startsWith('zzz-delete-me')"
|
|
32
|
+
|
|
33
|
+
# 摘要 + 快照单行 JSON(供基线 description 落库)
|
|
34
|
+
- id: summary
|
|
35
|
+
expr: >-
|
|
36
|
+
(() => {
|
|
37
|
+
const rows = steps.snapshot.data || [];
|
|
38
|
+
const isRisk = x => String(x.label || '').indexOf('risk') >= 0 || String(x.name || '').indexOf('【风险】') === 0;
|
|
39
|
+
const scope = rows.filter(x => !isRisk(x));
|
|
40
|
+
const byStatus = {};
|
|
41
|
+
rows.forEach(x => {
|
|
42
|
+
const k = String(x.status || 'unknown');
|
|
43
|
+
byStatus[k] = (byStatus[k] || 0) + 1;
|
|
44
|
+
});
|
|
45
|
+
const statusRows = Object.keys(byStatus).sort().map(k => ({ status: k, count: byStatus[k] }));
|
|
46
|
+
return {
|
|
47
|
+
total: rows.length,
|
|
48
|
+
scopeTotal: scope.length,
|
|
49
|
+
riskExcluded: rows.length - scope.length,
|
|
50
|
+
statusLine: statusRows.map(r => r.status + '=' + r.count).join(',') || '(空)',
|
|
51
|
+
statusRows: statusRows,
|
|
52
|
+
snapshotJson: JSON.stringify(rows),
|
|
53
|
+
};
|
|
54
|
+
})()
|
|
55
|
+
depends_on: [snapshot]
|
|
56
|
+
|
|
57
|
+
# 基线名与基线日期(日期 = 今天,经 expr 计算生成)
|
|
58
|
+
- id: freeze_meta
|
|
59
|
+
expr: >-
|
|
60
|
+
(() => {
|
|
61
|
+
const it = (steps.iteration.data && steps.iteration.data[0]) || {};
|
|
62
|
+
const itName = it.name || vars.iteration_name || '未命名迭代';
|
|
63
|
+
return { itName: itName, name: itName + '-基线-' + today, date: today };
|
|
64
|
+
})()
|
|
65
|
+
depends_on: [iteration]
|
|
66
|
+
|
|
67
|
+
# 写步骤:冻结基线
|
|
68
|
+
- id: freeze
|
|
69
|
+
uses: tapd_add_baseline
|
|
70
|
+
args:
|
|
71
|
+
name: "${steps.freeze_meta.data.name}"
|
|
72
|
+
baseline_date: "${steps.freeze_meta.data.date}"
|
|
73
|
+
description: "范围基线快照(迭代 ${steps.freeze_meta.data.itName},共 ${steps.summary.data.total} 项,剔除风险项后 ${steps.summary.data.scopeTotal} 项)|快照JSON:${steps.summary.data.snapshotJson}"
|
|
74
|
+
depends_on: [snapshot, summary, freeze_meta]
|
|
75
|
+
|
|
76
|
+
report:
|
|
77
|
+
format: markdown
|
|
78
|
+
template: |
|
|
79
|
+
# 范围基线已冻结 — ${steps.freeze_meta.data.name}
|
|
80
|
+
|
|
81
|
+
- 基线名称: ${steps.freeze_meta.data.name}
|
|
82
|
+
- 基线日期: ${steps.freeze_meta.data.date}(快照时点,expr 计算)
|
|
83
|
+
- 覆盖迭代: ${steps.freeze_meta.data.itName}
|
|
84
|
+
- 快照规模: ${steps.summary.data.total} 项需求(剔除风险项 ${steps.summary.data.riskExcluded} 后,范围口径 ${steps.summary.data.scopeTotal} 项)
|
|
85
|
+
- 状态分布: ${steps.summary.data.statusLine}
|
|
86
|
+
|
|
87
|
+
> 后续用 `tapd pm scope drift-check --input '{"baseline_date":"${steps.freeze_meta.data.date}"}'` 检测范围蔓延。
|
|
88
|
+
|
|
89
|
+
## 需求快照(${steps.summary.data.total} 项)
|
|
90
|
+
|
|
91
|
+
| id | 名称 | 状态 | 优先级 |
|
|
92
|
+
| ---- | ---- | ---- | ---- |
|
|
93
|
+
${steps.snapshot.data | rows id,name,status,priority}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# 范围蔓延检测(纯只读,L0 直接执行)—— 范围管理(软考高项第4版 第9章)
|
|
2
|
+
# 场景: tapd pm scope drift-check
|
|
3
|
+
# --input '{"baseline_date":"2026-09-01"}' [--iteration <id>]
|
|
4
|
+
# 编排三路只读拉取(并发):
|
|
5
|
+
# tapd_get_story_changes(limit=200, created>=基线) —— 注意: 该工具 schema 不含 created
|
|
6
|
+
# 参数(传了会在静态校验报错且运行期被丢弃),故拉最近 200 条按 created desc 排序后
|
|
7
|
+
# 在 filter 里做 created>=baseline_date 的本地过滤(字符串日期前缀比较成立)
|
|
8
|
+
# tapd_get_stories(当前迭代) —— 新增清单 = created>=基线
|
|
9
|
+
# tapd_get_removed_stories(limit=200, deleted>=基线) —— 同理本地过滤 deleted
|
|
10
|
+
# 产出: 新增/字段变更/移除三清单 + 范围口径结论(剔除风险项:label 含 risk 或名称以【风险】开头)
|
|
11
|
+
# 口径: 需求/回收站列表过滤 zzz-delete-me*;比率与清单计数保留分母
|
|
12
|
+
name: scope-drift-check
|
|
13
|
+
domain: scope
|
|
14
|
+
title: 范围蔓延检测(对比基线:新增/字段变更/移除三清单,软考第9章)
|
|
15
|
+
write: false
|
|
16
|
+
unwrap: true
|
|
17
|
+
vars:
|
|
18
|
+
iteration_id: ""
|
|
19
|
+
baseline_date: ""
|
|
20
|
+
steps:
|
|
21
|
+
- id: changes
|
|
22
|
+
uses: tapd_get_story_changes
|
|
23
|
+
args:
|
|
24
|
+
limit: 200
|
|
25
|
+
order: "created desc"
|
|
26
|
+
fields: "id,story_id,field,value_before,value_after,author,created"
|
|
27
|
+
filter: "!vars.baseline_date || String(item.created || '').slice(0, 10) >= vars.baseline_date"
|
|
28
|
+
|
|
29
|
+
- id: current
|
|
30
|
+
uses: tapd_get_stories
|
|
31
|
+
args:
|
|
32
|
+
iteration_id: "${vars.iteration_id}"
|
|
33
|
+
limit: 200
|
|
34
|
+
fields: "id,name,owner,status,label,created"
|
|
35
|
+
filter: "!item.name.startsWith('zzz-delete-me')"
|
|
36
|
+
|
|
37
|
+
- id: removed
|
|
38
|
+
uses: tapd_get_removed_stories
|
|
39
|
+
args:
|
|
40
|
+
limit: 200
|
|
41
|
+
fields: "id,name,owner,creator,deleted"
|
|
42
|
+
filter: "String(item.name || '').startsWith('zzz-delete-me') === false && (!vars.baseline_date || String(item.deleted || '').slice(0, 10) >= vars.baseline_date)"
|
|
43
|
+
|
|
44
|
+
- id: drift
|
|
45
|
+
expr: >-
|
|
46
|
+
(() => {
|
|
47
|
+
const cur = steps.current.data || [];
|
|
48
|
+
const chs = steps.changes.data || [];
|
|
49
|
+
const rem = steps.removed.data || [];
|
|
50
|
+
const bd = String(vars.baseline_date || '');
|
|
51
|
+
const byId = {};
|
|
52
|
+
cur.forEach(s => { byId[String(s.id)] = s; });
|
|
53
|
+
const isRisk = s => String(s.label || '').indexOf('risk') >= 0 || String(s.name || '').indexOf('【风险】') === 0;
|
|
54
|
+
const riskCount = cur.filter(isRisk).length;
|
|
55
|
+
const addedAll = cur.filter(s => !bd || String(s.created || '').slice(0, 10) >= bd);
|
|
56
|
+
const addedScope = addedAll.filter(s => !isRisk(s));
|
|
57
|
+
const addedRows = addedScope.map(s => ({ id: s.id, name: s.name, owner: s.owner, status: s.status, created: String(s.created || '').slice(0, 10) }));
|
|
58
|
+
if (addedAll.length > addedScope.length) {
|
|
59
|
+
addedRows.push({ id: '-', name: '(另有 ' + (addedAll.length - addedScope.length) + ' 项风险项新增,未计入范围口径)', owner: '-', status: '-', created: '-' });
|
|
60
|
+
}
|
|
61
|
+
if (addedRows.length === 0) {
|
|
62
|
+
addedRows.push({ id: '-', name: '(基线以来无新增需求)', owner: '-', status: '-', created: '-' });
|
|
63
|
+
}
|
|
64
|
+
const changeRows = chs.map(c => ({
|
|
65
|
+
story_id: c.story_id,
|
|
66
|
+
story: (byId[String(c.story_id)] || {}).name || '(迭代外/已移除需求)',
|
|
67
|
+
field: c.field,
|
|
68
|
+
from: c.value_before,
|
|
69
|
+
to: c.value_after,
|
|
70
|
+
author: c.author,
|
|
71
|
+
created: c.created,
|
|
72
|
+
}));
|
|
73
|
+
if (changeRows.length === 0) {
|
|
74
|
+
changeRows.push({ story_id: '-', story: '(基线以来无字段变更记录)', field: '-', from: '-', to: '-', author: '-', created: '-' });
|
|
75
|
+
}
|
|
76
|
+
const changedStories = {};
|
|
77
|
+
changeRows.forEach(r => { if (r.story_id !== '-') changedStories[String(r.story_id)] = true; });
|
|
78
|
+
const removedRows = rem.map(r => ({ id: r.id, name: r.name, owner: r.owner, deleted: String(r.deleted || '').slice(0, 10) }));
|
|
79
|
+
if (removedRows.length === 0) {
|
|
80
|
+
removedRows.push({ id: '-', name: '(基线以来无移除需求)', owner: '-', deleted: '-' });
|
|
81
|
+
}
|
|
82
|
+
const driftTotal = addedScope.length + changeRows.filter(r => r.story_id !== '-').length + rem.length;
|
|
83
|
+
return {
|
|
84
|
+
baselineDisplay: bd || '(未提供 baseline_date,按可拉取的全量记录展示)',
|
|
85
|
+
currentLine: '当前范围 ' + cur.length + ' 项(剔除风险项 ' + riskCount + ' 后,范围口径 ' + (cur.length - riskCount) + ' 项)',
|
|
86
|
+
addedCount: addedAll.length,
|
|
87
|
+
addedScopeCount: addedScope.length,
|
|
88
|
+
changeCount: changeRows.filter(r => r.story_id !== '-').length,
|
|
89
|
+
changedStoryCount: Object.keys(changedStories).length,
|
|
90
|
+
removedCount: rem.length,
|
|
91
|
+
driftTotal: driftTotal,
|
|
92
|
+
verdict: driftTotal === 0 ? '基线以来无范围变动,范围受控' : '检测到范围活动 ' + driftTotal + ' 处(新增 ' + addedScope.length + ' / 字段变更 ' + changeRows.filter(r => r.story_id !== '-').length + ' / 移除 ' + rem.length + '),请评审是否纳入基线变更',
|
|
93
|
+
addedRows: addedRows,
|
|
94
|
+
changeRows: changeRows,
|
|
95
|
+
removedRows: removedRows,
|
|
96
|
+
};
|
|
97
|
+
})()
|
|
98
|
+
depends_on: [changes, current, removed]
|
|
99
|
+
|
|
100
|
+
report:
|
|
101
|
+
format: markdown
|
|
102
|
+
template: |
|
|
103
|
+
# 范围蔓延检测报告
|
|
104
|
+
|
|
105
|
+
- 对比基线: ${steps.drift.data.baselineDisplay}
|
|
106
|
+
- 迭代: id=${vars.iteration_id}|${steps.drift.data.currentLine}
|
|
107
|
+
- 结论: ${steps.drift.data.verdict}
|
|
108
|
+
|
|
109
|
+
## 一、基线后新增(范围口径 ${steps.drift.data.addedScopeCount}/${steps.drift.data.addedCount})
|
|
110
|
+
|
|
111
|
+
| id | 名称 | 处理人 | 状态 | 创建日期 |
|
|
112
|
+
| ---- | ---- | ---- | ---- | ---- |
|
|
113
|
+
${steps.drift.data.addedRows | rows id,name,owner,status,created}
|
|
114
|
+
|
|
115
|
+
## 二、字段变更(${steps.drift.data.changeCount} 条记录,涉及 ${steps.drift.data.changedStoryCount} 个需求)
|
|
116
|
+
|
|
117
|
+
| 需求ID | 需求名 | 字段 | 原值 | 新值 | 变更人 | 时间 |
|
|
118
|
+
| ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
|
119
|
+
${steps.drift.data.changeRows | rows story_id,story,field,from,to,author,created}
|
|
120
|
+
|
|
121
|
+
## 三、移除(${steps.drift.data.removedCount} 项)
|
|
122
|
+
|
|
123
|
+
| id | 名称 | 处理人 | 删除日期 |
|
|
124
|
+
| ---- | ---- | ---- | ---- |
|
|
125
|
+
${steps.drift.data.removedRows | rows id,name,owner,deleted}
|
|
126
|
+
|
|
127
|
+
> 口径:新增=当前迭代内 created>=基线 的需求(风险项单列不计入口径);变更记录取自
|
|
128
|
+
> story_changes(工具 schema 无 created 参数,按 created desc 拉 200 条后本地过滤);
|
|
129
|
+
> 移除=回收站 deleted>=基线;全列表已过滤 zzz-delete-me*。
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# 干系人管理 · 参与矩阵(纯只读,L0 直接执行)
|
|
2
|
+
# 用法: tapd pm stakeholder map [--iteration <id>] [--input '{"skip_users":true}']
|
|
3
|
+
# 等价: tapd pipeline run templates/pm/stakeholder-map.yaml --iteration <id>
|
|
4
|
+
# 数据源: tapd_get_workspace_users + 迭代 stories/tasks(owner/cc/participator 字段)
|
|
5
|
+
# 权限降级: users 接口可能 403(需要 users::_get 权限)——此时执行会在 users 步骤报
|
|
6
|
+
# STEP_FAILED 并给出 API 错误;带 --input '{"skip_users":true}' 重跑即降级为仅从
|
|
7
|
+
# 存量 owner/cc/participator 字段反推矩阵(无名单零参与分析)。
|
|
8
|
+
# 实现注记: 聚合步骤用 steps['users'] 方括号取数、刻意不建硬依赖——引擎对
|
|
9
|
+
# 「上游非 ok 即连锁跳过」,users 步骤被 where 跳过时若建了依赖会连带跳过聚合,
|
|
10
|
+
# 降级路径就断了。
|
|
11
|
+
# 沉默口径(简化版): 人员名下存在「未完成且 progress=0 且 due 已过」的任务 → 标沉默;
|
|
12
|
+
# users 名单可用时另列「零参与名单成员」(在册但未出现在任何参与字段)。
|
|
13
|
+
name: stakeholder-map
|
|
14
|
+
domain: stakeholder
|
|
15
|
+
title: 干系人参与矩阵(人×参与对象数×角色视角;users 接口可能 403(权限),此时报告降级为仅从存量 owner 字段反推)
|
|
16
|
+
write: false
|
|
17
|
+
unwrap: true
|
|
18
|
+
vars:
|
|
19
|
+
iteration_id: ""
|
|
20
|
+
skip_users: false
|
|
21
|
+
steps:
|
|
22
|
+
# ---- 项目成员名单(403 风险步骤;skip_users=true 时跳过走降级)----
|
|
23
|
+
- id: users
|
|
24
|
+
uses: tapd_get_workspace_users
|
|
25
|
+
args: { fields: "user,role_id,email" }
|
|
26
|
+
where: "!vars.skip_users"
|
|
27
|
+
|
|
28
|
+
# ---- 迭代需求(保留风险项:风险 owner 属于必须沟通的干系人)----
|
|
29
|
+
- id: stories
|
|
30
|
+
uses: tapd_get_stories
|
|
31
|
+
args: { iteration_id: "${vars.iteration_id}", fields: "id,name,owner,cc,participator,status", limit: 200 }
|
|
32
|
+
filter: "!item.name.startsWith('zzz-delete-me')"
|
|
33
|
+
|
|
34
|
+
# ---- 迭代任务(沉默判定数据源)----
|
|
35
|
+
- id: tasks
|
|
36
|
+
uses: tapd_get_tasks
|
|
37
|
+
args: { iteration_id: "${vars.iteration_id}", fields: "id,name,owner,status,progress,due", limit: 200 }
|
|
38
|
+
filter: "!item.name.startsWith('zzz-delete-me')"
|
|
39
|
+
|
|
40
|
+
# ---- 聚合:人 × 参与对象数 × 角色视角(本地计算)----
|
|
41
|
+
- id: matrix
|
|
42
|
+
expr: |-
|
|
43
|
+
(() => {
|
|
44
|
+
const names = v => String(v || '').split(/[;;,,|]/).map(s => s.trim()).filter(Boolean);
|
|
45
|
+
const doneStatus = s => ['done', 'closed', 'complete', 'completed'].includes(String(s || ''));
|
|
46
|
+
const people = new Map();
|
|
47
|
+
const touch = (who, kind) => {
|
|
48
|
+
if (!who) return;
|
|
49
|
+
const p = people.get(who) || { name: who, story_owner: 0, story_cc: 0, story_partner: 0, task_owner: 0, blocked_tasks: 0, blocked_names: [] };
|
|
50
|
+
p[kind] += 1;
|
|
51
|
+
people.set(who, p);
|
|
52
|
+
};
|
|
53
|
+
for (const s of steps.stories.data) {
|
|
54
|
+
for (const w of names(s.owner)) touch(w, 'story_owner');
|
|
55
|
+
for (const w of names(s.cc)) touch(w, 'story_cc');
|
|
56
|
+
for (const w of names(s.participator)) touch(w, 'story_partner');
|
|
57
|
+
}
|
|
58
|
+
for (const t of steps.tasks.data) {
|
|
59
|
+
for (const w of names(t.owner)) {
|
|
60
|
+
touch(w, 'task_owner');
|
|
61
|
+
const stale = !doneStatus(t.status) && Number(t.progress || 0) === 0 && Boolean(t.due) && String(t.due).slice(0, 10) < today;
|
|
62
|
+
if (stale) {
|
|
63
|
+
const p = people.get(w);
|
|
64
|
+
p.blocked_tasks += 1;
|
|
65
|
+
p.blocked_names.push(String(t.name || t.id || '(未命名)'));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const rows = [...people.values()].map(p => ({
|
|
70
|
+
name: p.name,
|
|
71
|
+
story_owner: p.story_owner,
|
|
72
|
+
story_cc: p.story_cc,
|
|
73
|
+
story_partner: p.story_partner,
|
|
74
|
+
task_owner: p.task_owner,
|
|
75
|
+
blocked_tasks: p.blocked_tasks,
|
|
76
|
+
blocked_names: p.blocked_names,
|
|
77
|
+
objects: p.story_owner + p.story_cc + p.story_partner + p.task_owner,
|
|
78
|
+
silent_label: p.blocked_tasks > 0 ? '是(' + p.blocked_names.join('、') + ')' : ''
|
|
79
|
+
}));
|
|
80
|
+
rows.sort((a, b) => b.objects - a.objects || a.name.localeCompare(b.name));
|
|
81
|
+
|
|
82
|
+
const rosterRaw = steps['users'] && Array.isArray(steps['users'].data) ? steps['users'].data : [];
|
|
83
|
+
const roster = [...new Set(rosterRaw.map(u => String((u && (u.user || u.name)) || '').trim()).filter(Boolean))];
|
|
84
|
+
const involved = new Set(rows.map(r => r.name));
|
|
85
|
+
const idleMembers = roster.filter(n => !involved.has(n));
|
|
86
|
+
const silentRows = rows.filter(r => r.blocked_tasks > 0);
|
|
87
|
+
return {
|
|
88
|
+
users_loaded: rosterRaw.length > 0,
|
|
89
|
+
roster_size: rosterRaw.length,
|
|
90
|
+
people: rows,
|
|
91
|
+
silent_count: silentRows.length,
|
|
92
|
+
silent_lines: silentRows.length
|
|
93
|
+
? silentRows.map(r => '- ' + r.name + '(停滞任务: ' + r.blocked_names.join('、') + ')').join('\n')
|
|
94
|
+
: '-(无:迭代内没有「任务 progress=0 且 due 已过」的负责人)',
|
|
95
|
+
idle_lines: idleMembers.length
|
|
96
|
+
? idleMembers.map(n => '- ' + n).join('\n')
|
|
97
|
+
: '-(无:名单成员均出现在 owner/cc/participator 字段中)',
|
|
98
|
+
users_note: rosterRaw.length > 0
|
|
99
|
+
? '名单接口可用,共 ' + rosterRaw.length + ' 人'
|
|
100
|
+
: '降级模式:users 未加载(接口可能 403 或已声明 skip_users),本矩阵仅从存量 owner/cc/participator 字段反推,无名单零参与分析'
|
|
101
|
+
};
|
|
102
|
+
})()
|
|
103
|
+
depends_on: [stories, tasks]
|
|
104
|
+
|
|
105
|
+
report:
|
|
106
|
+
format: markdown
|
|
107
|
+
template: |
|
|
108
|
+
# 干系人参与矩阵
|
|
109
|
+
- 迭代: ${vars.iteration_id}
|
|
110
|
+
- 名单: ${steps.matrix.data.users_note}
|
|
111
|
+
- 计入口径: 出现在迭代需求/任务的 owner、cc、participator 字段即计入;「参与对象数」为四类视角去重前合计
|
|
112
|
+
|
|
113
|
+
| 人员 | 需求负责 | 需求关注(cc) | 需求参与 | 任务负责 | 停滞任务 | 参与对象数 | 沉默标记 |
|
|
114
|
+
|------|---------|-------------|---------|---------|---------|-----------|---------|
|
|
115
|
+
${steps.matrix.data.people | rows name,story_owner,story_cc,story_partner,task_owner,blocked_tasks,objects,silent_label}
|
|
116
|
+
|
|
117
|
+
## 沉默干系人(有 owner 任务但 progress=0 且 due 已过)
|
|
118
|
+
${steps.matrix.data.silent_lines}
|
|
119
|
+
|
|
120
|
+
## 零参与名单成员(名单在册但未出现在任何参与字段)
|
|
121
|
+
${steps.matrix.data.idle_lines}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 缺陷分诊(含写步骤,默认 dry-run 闸门,--yes 确认落库)
|
|
2
|
+
# 规则分配:按 vars.owners 轮询(round-robin)把未分派缺陷分给候选处理人
|
|
3
|
+
# 用法: tapd pipeline run triage-bugs --input '{"severity":"1","owners":["alice","bob"]}' --yes
|
|
4
|
+
name: triage-bugs
|
|
5
|
+
title: 缺陷分诊(批量查缺陷 → 规则分配 → 评论分诊摘要)
|
|
6
|
+
write: true
|
|
7
|
+
unwrap: true
|
|
8
|
+
vars:
|
|
9
|
+
severity: ""
|
|
10
|
+
owners: []
|
|
11
|
+
steps:
|
|
12
|
+
- id: bugs
|
|
13
|
+
uses: tapd_get_bugs
|
|
14
|
+
args:
|
|
15
|
+
status: new
|
|
16
|
+
fields: "id,title,severity,current_owner,status"
|
|
17
|
+
|
|
18
|
+
- id: triaged
|
|
19
|
+
expr: "steps.bugs.data.filter(b => (!vars.severity || b.severity === vars.severity) && !b.current_owner)"
|
|
20
|
+
depends_on: [bugs]
|
|
21
|
+
|
|
22
|
+
- id: assignment
|
|
23
|
+
expr: "(() => { const pick = i => vars.owners[i % Math.max(vars.owners.length, 1)]; return { count: steps.triaged.data.length, plan: steps.triaged.data.map((b, i) => ({ id: b.id, current_owner: pick(i) })), display: steps.triaged.data.map((b, i) => ({ id: b.id, title: b.title, assignee: pick(i) })) }; })()"
|
|
24
|
+
depends_on: [triaged]
|
|
25
|
+
|
|
26
|
+
- id: assign
|
|
27
|
+
uses: tapd_batch_update_bugs
|
|
28
|
+
args:
|
|
29
|
+
bugs: "${steps.assignment.data.plan | json}"
|
|
30
|
+
where: "steps.assignment.data.count > 0 && vars.owners.length > 0"
|
|
31
|
+
depends_on: [assignment]
|
|
32
|
+
|
|
33
|
+
- id: triage-comment
|
|
34
|
+
uses: tapd_create_comment
|
|
35
|
+
args:
|
|
36
|
+
entry_type: bug
|
|
37
|
+
entry_id: "${steps.triaged.data.0.id}"
|
|
38
|
+
description: "【分诊摘要】本轮共分派 ${steps.assignment.data.count} 个缺陷(round-robin → ${vars.owners | json})"
|
|
39
|
+
where: "steps.assignment.data.count > 0"
|
|
40
|
+
depends_on: [assign]
|
|
41
|
+
|
|
42
|
+
report:
|
|
43
|
+
format: markdown
|
|
44
|
+
template: |
|
|
45
|
+
# 缺陷分诊报告
|
|
46
|
+
- 候选处理人: ${vars.owners | json}
|
|
47
|
+
- 查询缺陷: ${steps.bugs.data.length} 个,其中待分诊 ${steps.assignment.data.count} 个
|
|
48
|
+
- 分派计划:
|
|
49
|
+
${steps.assignment.data.display | rows id,title,assignee}
|