@xihe-lab/tapd-cli 2.0.0-rc.5 → 2.0.0-rc.7

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.
Files changed (71) hide show
  1. package/LICENSE +16 -4
  2. package/README.md +13 -1
  3. package/package.json +7 -4
  4. package/dist/advisor/domain-axis.test.d.ts +0 -2
  5. package/dist/advisor/domain-axis.test.d.ts.map +0 -1
  6. package/dist/advisor/domain-axis.test.js +0 -198
  7. package/dist/advisor/domain-axis.test.js.map +0 -1
  8. package/dist/flags.test.d.ts +0 -2
  9. package/dist/flags.test.d.ts.map +0 -1
  10. package/dist/flags.test.js +0 -56
  11. package/dist/flags.test.js.map +0 -1
  12. package/dist/output/formatter.test.d.ts +0 -2
  13. package/dist/output/formatter.test.d.ts.map +0 -1
  14. package/dist/output/formatter.test.js +0 -170
  15. package/dist/output/formatter.test.js.map +0 -1
  16. package/dist/pipeline/engine.test.d.ts +0 -2
  17. package/dist/pipeline/engine.test.d.ts.map +0 -1
  18. package/dist/pipeline/engine.test.js +0 -419
  19. package/dist/pipeline/engine.test.js.map +0 -1
  20. package/dist/pipeline/expression.test.d.ts +0 -2
  21. package/dist/pipeline/expression.test.d.ts.map +0 -1
  22. package/dist/pipeline/expression.test.js +0 -111
  23. package/dist/pipeline/expression.test.js.map +0 -1
  24. package/dist/pipeline/record.test.d.ts +0 -2
  25. package/dist/pipeline/record.test.d.ts.map +0 -1
  26. package/dist/pipeline/record.test.js +0 -109
  27. package/dist/pipeline/record.test.js.map +0 -1
  28. package/dist/pipeline/template.test.d.ts +0 -2
  29. package/dist/pipeline/template.test.d.ts.map +0 -1
  30. package/dist/pipeline/template.test.js +0 -94
  31. package/dist/pipeline/template.test.js.map +0 -1
  32. package/dist/pipeline/unwrap.test.d.ts +0 -2
  33. package/dist/pipeline/unwrap.test.d.ts.map +0 -1
  34. package/dist/pipeline/unwrap.test.js +0 -36
  35. package/dist/pipeline/unwrap.test.js.map +0 -1
  36. package/dist/pipeline/validate.test.d.ts +0 -2
  37. package/dist/pipeline/validate.test.d.ts.map +0 -1
  38. package/dist/pipeline/validate.test.js +0 -166
  39. package/dist/pipeline/validate.test.js.map +0 -1
  40. package/dist/pm/discovery.test.d.ts +0 -2
  41. package/dist/pm/discovery.test.d.ts.map +0 -1
  42. package/dist/pm/discovery.test.js +0 -147
  43. package/dist/pm/discovery.test.js.map +0 -1
  44. package/dist/pm/domains.test.d.ts +0 -2
  45. package/dist/pm/domains.test.d.ts.map +0 -1
  46. package/dist/pm/domains.test.js +0 -141
  47. package/dist/pm/domains.test.js.map +0 -1
  48. package/dist/pm/iteration.test.d.ts +0 -2
  49. package/dist/pm/iteration.test.d.ts.map +0 -1
  50. package/dist/pm/iteration.test.js +0 -125
  51. package/dist/pm/iteration.test.js.map +0 -1
  52. package/dist/pm/pm-command.test.d.ts +0 -2
  53. package/dist/pm/pm-command.test.d.ts.map +0 -1
  54. package/dist/pm/pm-command.test.js +0 -321
  55. package/dist/pm/pm-command.test.js.map +0 -1
  56. package/dist/pm/tpl-communication-stakeholder.test.d.ts +0 -2
  57. package/dist/pm/tpl-communication-stakeholder.test.d.ts.map +0 -1
  58. package/dist/pm/tpl-communication-stakeholder.test.js +0 -392
  59. package/dist/pm/tpl-communication-stakeholder.test.js.map +0 -1
  60. package/dist/pm/tpl-integration-scope.test.d.ts +0 -2
  61. package/dist/pm/tpl-integration-scope.test.d.ts.map +0 -1
  62. package/dist/pm/tpl-integration-scope.test.js +0 -387
  63. package/dist/pm/tpl-integration-scope.test.js.map +0 -1
  64. package/dist/pm/tpl-quality-risk.test.d.ts +0 -2
  65. package/dist/pm/tpl-quality-risk.test.d.ts.map +0 -1
  66. package/dist/pm/tpl-quality-risk.test.js +0 -414
  67. package/dist/pm/tpl-quality-risk.test.js.map +0 -1
  68. package/dist/pm/tpl-schedule-cost.test.d.ts +0 -2
  69. package/dist/pm/tpl-schedule-cost.test.d.ts.map +0 -1
  70. package/dist/pm/tpl-schedule-cost.test.js +0 -376
  71. package/dist/pm/tpl-schedule-cost.test.js.map +0 -1
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=domains.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"domains.test.d.ts","sourceRoot":"","sources":["../../src/pm/domains.test.ts"],"names":[],"mappings":""}
@@ -1,141 +0,0 @@
1
- // pm 域元数据表与分组渲染测试(S2)
2
- import assert from 'node:assert/strict';
3
- import { PM_DOMAINS, PM_OTHER_DOMAIN_LABEL, domainLabel, findDomain } from './domains.js';
4
- import { groupByDomain, renderDomainHelp, renderPmHelp, renderPmList } from './render.js';
5
- import { pmAliasKey, splitAlias } from './discovery.js';
6
- function entry(partial) {
7
- return {
8
- domain: 'risk',
9
- scenario: 'scan',
10
- name: 'risk-scan',
11
- title: '',
12
- write: false,
13
- file: '/tmp/risk-scan.yaml',
14
- source: 'builtin',
15
- domainKnown: true,
16
- ...partial,
17
- };
18
- }
19
- const emptyCatalog = { entries: [], warnings: [] };
20
- const checks = [
21
- ['域表:8 域、键唯一、章节号格式、与设计 §2 顺序一致', () => {
22
- assert.equal(PM_DOMAINS.length, 8);
23
- assert.deepEqual(PM_DOMAINS.map(d => d.key), ['integration', 'scope', 'schedule', 'cost', 'quality', 'communication', 'risk', 'stakeholder']);
24
- assert.equal(new Set(PM_DOMAINS.map(d => d.key)).size, 8);
25
- for (const d of PM_DOMAINS) {
26
- assert.match(d.chapter, /^第\d+章$/);
27
- assert.ok(d.zh.length > 0);
28
- }
29
- assert.deepEqual(PM_DOMAINS.map(d => d.chapter), ['第8章', '第9章', '第10章', '第11章', '第12章', '第14章', '第15章', '第17章']);
30
- }],
31
- ['domainLabel:命中域表出中文名+章节号,未知归「其他」', () => {
32
- assert.equal(domainLabel('risk'), '风险管理(第15章)');
33
- assert.equal(domainLabel('communication'), '沟通管理(第14章)');
34
- assert.equal(domainLabel('procurement'), PM_OTHER_DOMAIN_LABEL);
35
- assert.equal(findDomain('nope'), undefined);
36
- }],
37
- ['groupByDomain:域表序在前,「其他」永远最后', () => {
38
- const groups = groupByDomain([
39
- entry({ domain: 'risk', scenario: 'scan' }),
40
- entry({ domain: 'procurement', scenario: 'audit', domainKnown: false }),
41
- entry({ domain: 'stakeholder', scenario: 'map' }),
42
- entry({ domain: '', scenario: 'orphan', domainKnown: false }),
43
- ]);
44
- const keys = groups.map(g => g.key);
45
- assert.deepEqual(keys, ['integration', 'scope', 'schedule', 'cost', 'quality', 'communication', 'risk', 'stakeholder', '']);
46
- assert.deepEqual(groups.at(-1).label, PM_OTHER_DOMAIN_LABEL);
47
- // 「其他」收编未知域与无域模板,known 域各归其位
48
- assert.deepEqual(groups.at(-1).entries.map(e => e.scenario), ['audit', 'orphan']);
49
- assert.equal(groups.find(g => g.key === 'risk').entries.length, 1);
50
- assert.equal(groups.find(g => g.key === 'integration').entries.length, 0);
51
- }],
52
- ['renderPmHelp:空目录也有八域骨架与收录提示(零代码注册入口)', () => {
53
- const help = renderPmHelp(emptyCatalog);
54
- for (const key of ['integration', 'scope', 'schedule', 'cost', 'quality', 'communication', 'risk', 'stakeholder']) {
55
- assert.ok(help.includes(key), `help 缺域键 ${key}`);
56
- }
57
- assert.ok(help.includes('整合管理(第8章)'));
58
- assert.ok(help.includes('干系人管理(第17章)'));
59
- assert.ok(help.includes('自动收录'));
60
- assert.ok(help.includes('--iteration'));
61
- assert.ok(help.includes('--dry-run'));
62
- assert.ok(help.includes('--yes'));
63
- assert.ok(help.includes('zzz-delete-me'));
64
- }],
65
- ['renderPmHelp:场景行带标题与 read/write 标记;未知域归「其他」且带域前缀', () => {
66
- const help = renderPmHelp({
67
- entries: [
68
- entry({ domain: 'risk', scenario: 'scan', title: '风险扫描(软考第15章)' }),
69
- entry({ domain: 'scope', scenario: 'baseline', write: true, title: '范围基线冻结' }),
70
- entry({ domain: 'procurement', scenario: 'audit', domainKnown: false }),
71
- ],
72
- warnings: [],
73
- });
74
- assert.ok(help.includes('scan 风险扫描(软考第15章) [read]'));
75
- assert.ok(help.includes('baseline 范围基线冻结 [write]'));
76
- assert.ok(help.includes('procurement/audit'));
77
- const otherIdx = help.indexOf(PM_OTHER_DOMAIN_LABEL);
78
- const stakeholderIdx = help.indexOf('stakeholder');
79
- assert.ok(otherIdx > stakeholderIdx, '「其他」组必须排在域表之后');
80
- }],
81
- ['renderDomainHelp:域标题带中文与章节号,场景对齐+写标记', () => {
82
- const help = renderDomainHelp('risk', {
83
- entries: [
84
- entry({ domain: 'risk', scenario: 'register', write: true, title: '登记册维护' }),
85
- entry({ domain: 'risk', scenario: 'scan', title: '风险扫描' }),
86
- ],
87
- warnings: [],
88
- });
89
- assert.ok(help.startsWith('risk 风险管理(第15章) 场景:'));
90
- assert.ok(help.includes('register 登记册维护 [write]'));
91
- assert.ok(help.includes('scan 风险扫描 [read]'));
92
- assert.ok(help.includes('用法: tapd pm risk <scenario>'));
93
- // 自定义域无域表元数据
94
- const custom = renderDomainHelp('procurement', { entries: [], warnings: [] });
95
- assert.ok(custom.includes('procurement(自定义域)'));
96
- assert.ok(custom.includes('暂无场景模板'));
97
- }],
98
- ['renderPmList:列头与域/场景/读写/来源/标题', () => {
99
- const list = renderPmList([
100
- entry({ domain: 'risk', scenario: 'scan', title: '风险扫描', source: 'builtin' }),
101
- entry({ domain: 'risk', scenario: 'register', title: '登记册', write: true, source: 'user' }),
102
- ]);
103
- const lines = list.split('\n');
104
- assert.equal(lines.length, 3);
105
- assert.match(lines[0], /^domain\s+scenario\s+mode\s+source\s+title$/);
106
- assert.match(lines[1], /^risk\s+scan\s+read\s+builtin\s+风险扫描$/);
107
- assert.match(lines[2], /^risk\s+register\s+write\s+user\s+登记册/);
108
- assert.ok(list.includes('用户覆盖内置'));
109
- }],
110
- ['renderPmList:空清单给收录指引', () => {
111
- const list = renderPmList([]);
112
- assert.ok(list.includes('暂无 pm 场景模板'));
113
- assert.ok(list.includes('自动收录'));
114
- }],
115
- ['splitAlias / pmAliasKey:文件名首个 - 拆域;frontmatter domain 优先', () => {
116
- assert.deepEqual(splitAlias('risk-scan'), { domain: 'risk', scenario: 'scan' });
117
- assert.deepEqual(splitAlias('scope-drift-check'), { domain: 'scope', scenario: 'drift-check' });
118
- assert.deepEqual(splitAlias('standup', 'schedule'), { domain: 'schedule', scenario: 'standup' });
119
- assert.deepEqual(splitAlias('drift-check', 'scope'), { domain: 'scope', scenario: 'drift-check' });
120
- assert.deepEqual(splitAlias('orphan'), { domain: '', scenario: 'orphan' });
121
- assert.equal(pmAliasKey({ domain: 'risk', scenario: 'scan' }), 'risk::scan');
122
- }],
123
- ];
124
- let failed = 0;
125
- for (const [name, fn] of checks) {
126
- try {
127
- await fn();
128
- console.log(`ok - ${name}`);
129
- }
130
- catch (error) {
131
- failed++;
132
- console.error(`FAIL - ${name}`);
133
- console.error(error);
134
- }
135
- }
136
- if (failed > 0) {
137
- console.error(`\n${failed}/${checks.length} failed`);
138
- process.exit(1);
139
- }
140
- console.log(`\n${checks.length}/${checks.length} passed`);
141
- //# sourceMappingURL=domains.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"domains.test.js","sourceRoot":"","sources":["../../src/pm/domains.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=iteration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"iteration.test.d.ts","sourceRoot":"","sources":["../../src/pm/iteration.test.ts"],"names":[],"mappings":""}
@@ -1,125 +0,0 @@
1
- // 迭代缺省解析测试(S2):mock registry,不触真实 API
2
- import assert from 'node:assert/strict';
3
- import { z } from 'zod';
4
- import { ToolRegistry } from '@xihe-lab/tapd-core';
5
- import { pickLatestOpenIteration, probeDefaultIteration } from './iteration.js';
6
- const OPEN_ITERATIONS = {
7
- data: [
8
- { id: '3001', name: 'Sprint A', startdate: '2026-09-01', status: 'open' },
9
- { id: '2999', name: 'Old', startdate: '2026-08-01', status: 'done' },
10
- { id: '3003', name: 'zzz-delete-me 老迭代', startdate: '2026-12-31', status: 'open' },
11
- { id: '3002', name: 'Sprint B', startdate: '2026-09-10', status: 'open' },
12
- ],
13
- };
14
- function makeRegistry(mode = 'ok') {
15
- const record = { calls: 0, args: [], mode };
16
- const tools = [
17
- {
18
- name: 'tapd_get_iterations',
19
- description: 'mock: 迭代查询',
20
- inputSchema: z.object({
21
- workspace_id: z.number().optional(),
22
- id: z.string().optional(),
23
- status: z.string().optional(),
24
- fields: z.string().optional(),
25
- order: z.string().optional(),
26
- }),
27
- handler: (_client, params) => {
28
- record.calls++;
29
- record.args.push(params);
30
- if (record.mode === 'auth-error') {
31
- return Promise.reject(new Error('missing TAPD_ACCESS_TOKEN'));
32
- }
33
- if (record.mode === 'api-error') {
34
- return Promise.reject(new Error('TAPD API error: 403'));
35
- }
36
- return Promise.resolve(OPEN_ITERATIONS);
37
- },
38
- },
39
- ];
40
- const registry = new ToolRegistry();
41
- registry.register(tools);
42
- return { registry, record };
43
- }
44
- const clientFactory = () => ({});
45
- const checks = [
46
- ['pickLatestOpenIteration:过滤 zzz-delete-me* 与非 open 由调用侧保证,startdate 最新优先', () => {
47
- const picked = pickLatestOpenIteration(OPEN_ITERATIONS.data);
48
- assert.ok(picked);
49
- assert.equal(picked.id, '3002', 'zzz-delete-me(2026-12-31) 必须被过滤,取 startdate 最新的 3002');
50
- assert.equal(picked.name, 'Sprint B');
51
- }],
52
- ['pickLatestOpenIteration:兼容 { data: [...] } / 数组 / 空值形态', () => {
53
- assert.equal(pickLatestOpenIteration(OPEN_ITERATIONS).id, '3002');
54
- assert.equal(pickLatestOpenIteration([]), undefined);
55
- assert.equal(pickLatestOpenIteration({ data: [] }), undefined);
56
- assert.equal(pickLatestOpenIteration({ data: [{ id: '1', name: 'zzz-delete-me', startdate: '2030-01-01' }] }), undefined);
57
- assert.equal(pickLatestOpenIteration(null), undefined);
58
- assert.equal(pickLatestOpenIteration('nope'), undefined);
59
- assert.equal(pickLatestOpenIteration([{ id: '9', name: '唯一', startdate: '' }]).id, '9');
60
- }],
61
- ['probeDefaultIteration:status=open + fields 只读探测,defaultArgs 兜底合并', async () => {
62
- const { registry, record } = makeRegistry();
63
- const probe = await probeDefaultIteration(registry, clientFactory, {
64
- defaultArgs: { workspace_id: 39814312 },
65
- timeoutMs: 5_000,
66
- });
67
- assert.equal(probe.status, 'resolved');
68
- assert.equal(probe.status === 'resolved' ? probe.iteration.id : '', '3002');
69
- assert.equal(record.calls, 1);
70
- assert.equal(record.args[0].status, 'open');
71
- assert.equal(record.args[0].fields, 'id,name,startdate,status');
72
- assert.equal(record.args[0].workspace_id, 39814312);
73
- }],
74
- ['probeDefaultIteration:无可用迭代 → none(不抛错)', async () => {
75
- // 全 zzz-delete-me 的迭代列表同样归 none
76
- const empty = new ToolRegistry();
77
- empty.register([
78
- {
79
- name: 'tapd_get_iterations',
80
- description: 'mock empty',
81
- inputSchema: z.object({ status: z.string().optional(), fields: z.string().optional() }),
82
- handler: () => Promise.resolve({ data: [] }),
83
- },
84
- ]);
85
- const probeEmpty = await probeDefaultIteration(empty, clientFactory);
86
- assert.equal(probeEmpty.status, 'none');
87
- const allZzz = new ToolRegistry();
88
- allZzz.register([
89
- {
90
- name: 'tapd_get_iterations',
91
- description: 'mock all filtered',
92
- inputSchema: z.object({ status: z.string().optional(), fields: z.string().optional() }),
93
- handler: () => Promise.resolve([{ id: '1', name: 'zzz-delete-me', startdate: '2030-01-01' }]),
94
- },
95
- ]);
96
- const probeZzz = await probeDefaultIteration(allZzz, clientFactory);
97
- assert.equal(probeZzz.status, 'none');
98
- }],
99
- ['probeDefaultIteration:探测失败(auth/API)→ failed + 消息,不抛错不中断场景', async () => {
100
- for (const mode of ['auth-error', 'api-error']) {
101
- const { registry } = makeRegistry(mode);
102
- const probe = await probeDefaultIteration(registry, clientFactory);
103
- assert.equal(probe.status, 'failed');
104
- assert.ok(probe.status === 'failed' && probe.message.length > 0, `${mode} 应带错误消息`);
105
- }
106
- }],
107
- ];
108
- let failed = 0;
109
- for (const [name, fn] of checks) {
110
- try {
111
- await fn();
112
- console.log(`ok - ${name}`);
113
- }
114
- catch (error) {
115
- failed++;
116
- console.error(`FAIL - ${name}`);
117
- console.error(error);
118
- }
119
- }
120
- if (failed > 0) {
121
- console.error(`\n${failed}/${checks.length} failed`);
122
- process.exit(1);
123
- }
124
- console.log(`\n${checks.length}/${checks.length} passed`);
125
- //# sourceMappingURL=iteration.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"iteration.test.js","sourceRoot":"","sources":["../../src/pm/iteration.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=pm-command.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pm-command.test.d.ts","sourceRoot":"","sources":["../../src/pm/pm-command.test.ts"],"names":[],"mappings":""}
@@ -1,321 +0,0 @@
1
- // tapd pm 命令组集成测试(S2):别名路由到 pipeline 引擎 / help 分组 / list / 写闸门 / 迭代注入
2
- // fixture 模板写测试临时目录(TAPD_USER_TEMPLATES_DIR),registry 为 mock,不触真实 API。
3
- import assert from 'node:assert/strict';
4
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
5
- import { tmpdir } from 'node:os';
6
- import path from 'node:path';
7
- import { z } from 'zod';
8
- import { ToolRegistry } from '@xihe-lab/tapd-core';
9
- import { buildProgram } from '../program.js';
10
- import { CliError } from '../errors.js';
11
- // ---- fixture 模板(用户目录,覆盖 env 注入) ----
12
- const RISK_SCAN = `name: risk-scan
13
- domain: risk
14
- title: 风险扫描(fixture)
15
- write: false
16
- vars:
17
- iteration_id: ""
18
- steps:
19
- - id: iterations
20
- uses: tapd_get_iterations
21
- args: { status: open, fields: "id,name,startdate" }
22
- filter: "!item.name.startsWith('zzz-delete-me')"
23
- - id: stories
24
- uses: tapd_get_stories
25
- args: { iteration_id: "${'$'}{vars.iteration_id}", fields: "id,name" }
26
- filter: "!item.name.startsWith('zzz-delete-me')"
27
- `;
28
- const SCOPE_CLOSE = `name: scope-close
29
- domain: scope
30
- title: 迭代关闭(fixture 写场景)
31
- write: true
32
- vars:
33
- iteration_id: ""
34
- steps:
35
- - id: close
36
- uses: tapd_update_story
37
- args: { id: "${'$'}{vars.iteration_id}", status: closed }
38
- `;
39
- const PROCUREMENT_AUDIT = `name: procurement-audit
40
- domain: procurement
41
- title: 外部依赖台账(自定义域 fixture)
42
- steps:
43
- - id: stories
44
- uses: tapd_get_stories
45
- `;
46
- // ---- mock registry ----
47
- const OPEN_ITERATIONS = {
48
- data: [
49
- { id: '3001', name: 'Sprint A', startdate: '2026-09-01', status: 'open' },
50
- { id: '3003', name: 'zzz-delete-me 老迭代', startdate: '2026-12-31', status: 'open' },
51
- { id: '3002', name: 'Sprint B', startdate: '2026-09-10', status: 'open' },
52
- ],
53
- };
54
- function makeRegistry() {
55
- const tracker = { probes: [], storyArgs: [], updateArgs: [] };
56
- const tools = [
57
- {
58
- name: 'tapd_get_iterations',
59
- description: 'mock: 迭代查询(pm 缺省迭代探测数据源)',
60
- inputSchema: z.object({
61
- workspace_id: z.number().optional(),
62
- status: z.string().optional(),
63
- fields: z.string().optional(),
64
- }),
65
- handler: (_client, params) => {
66
- tracker.probes.push(params);
67
- return Promise.resolve(OPEN_ITERATIONS);
68
- },
69
- },
70
- {
71
- name: 'tapd_get_stories',
72
- description: 'mock: 需求查询(记录入参,含 1 条 zzz 脏数据)',
73
- inputSchema: z.object({
74
- workspace_id: z.number().optional(),
75
- iteration_id: z.string().optional(),
76
- fields: z.string().optional(),
77
- }),
78
- handler: (_client, params) => {
79
- tracker.storyArgs.push(params);
80
- return Promise.resolve([
81
- { id: 's1', name: 'story-1' },
82
- { id: 's2', name: 'zzz-delete-me 脏数据' },
83
- ]);
84
- },
85
- },
86
- {
87
- name: 'tapd_update_story',
88
- description: 'mock: 写操作',
89
- write: true,
90
- inputSchema: z.object({
91
- workspace_id: z.number().optional(),
92
- id: z.string(),
93
- status: z.string().optional(),
94
- }),
95
- handler: (_client, params) => {
96
- tracker.updateArgs.push(params);
97
- return Promise.resolve({ id: params.id });
98
- },
99
- },
100
- ];
101
- const registry = new ToolRegistry();
102
- registry.register(tools);
103
- return { registry, tracker };
104
- }
105
- function captureIo(run) {
106
- const out = [];
107
- const err = [];
108
- const origOut = process.stdout.write.bind(process.stdout);
109
- const origErr = process.stderr.write.bind(process.stderr);
110
- process.stdout.write = (chunk) => {
111
- out.push(String(chunk));
112
- return true;
113
- };
114
- process.stderr.write = (chunk) => {
115
- err.push(String(chunk));
116
- return true;
117
- };
118
- return run().then(() => {
119
- process.stdout.write = origOut;
120
- process.stderr.write = origErr;
121
- return { out: out.join(''), err: err.join('') };
122
- }, error => {
123
- process.stdout.write = origOut;
124
- process.stderr.write = origErr;
125
- throw error;
126
- });
127
- }
128
- async function withExitCode(fn) {
129
- const prev = process.exitCode;
130
- try {
131
- return await fn();
132
- }
133
- finally {
134
- process.exitCode = prev;
135
- }
136
- }
137
- async function runCli(registry, args, config = {}) {
138
- const program = buildProgram(registry, config);
139
- // commander parse 约定 argv[0]=node argv[1]=script,真实参数从下标 2 开始
140
- return captureIo(async () => {
141
- await program.parseAsync(['node', 'tapd', ...args]);
142
- });
143
- }
144
- /**
145
- * help 路径(--help):exitOverride 下 commander 打印后抛 helpDisplayed,
146
- * 输出已经落 stdout,吞掉该错误只取输出。
147
- * 注意 commander v13 的 addHelpText 走事件流,helpInformation() 不含 after 文本,
148
- * 必须经 parseAsync 才能拿到完整 help。
149
- */
150
- async function runHelp(registry, args) {
151
- const program = buildProgram(registry, {});
152
- const out = [];
153
- const origOut = process.stdout.write.bind(process.stdout);
154
- process.stdout.write = (chunk) => {
155
- out.push(String(chunk));
156
- return true;
157
- };
158
- try {
159
- await program.parseAsync(['node', 'tapd', ...args]);
160
- }
161
- catch {
162
- // helpDisplayed / help 事件后的 CommanderError 属预期
163
- }
164
- finally {
165
- process.stdout.write = origOut;
166
- }
167
- return { out: out.join(''), err: '' };
168
- }
169
- // ---- fixture 目录 ----
170
- const root = mkdtempSync(path.join(tmpdir(), 'tapd-pm-command-'));
171
- mkdirSync(path.join(root, 'pm'), { recursive: true });
172
- writeFileSync(path.join(root, 'pm', 'risk-scan.yaml'), RISK_SCAN);
173
- writeFileSync(path.join(root, 'pm', 'scope-close.yaml'), SCOPE_CLOSE);
174
- writeFileSync(path.join(root, 'pm', 'procurement-audit.yaml'), PROCUREMENT_AUDIT);
175
- const prevEnv = process.env.TAPD_USER_TEMPLATES_DIR;
176
- process.env.TAPD_USER_TEMPLATES_DIR = root;
177
- // mock registry 不触 HTTP,但 registry.exec 会先构建 client(fromEnv 无凭证即抛 AUTH_MISSING)
178
- const prevToken = process.env.TAPD_ACCESS_TOKEN;
179
- process.env.TAPD_ACCESS_TOKEN = 'pm-test-token';
180
- const checks = [
181
- ['tapd pm --help:八域分组(中文+章节号)、场景清单、自定义域归「其他」', async () => {
182
- const { registry } = makeRegistry();
183
- const program = buildProgram(registry, {});
184
- const pmCmd = program.commands.find(c => c.name() === 'pm');
185
- assert.ok(pmCmd, 'pm 命令已注册');
186
- // 域子命令逐一注册(含自定义域 procurement)
187
- for (const sub of ['list', 'integration', 'scope', 'schedule', 'cost', 'quality', 'communication', 'risk', 'stakeholder', 'procurement']) {
188
- assert.ok(pmCmd.commands.some(c => c.name() === sub), `pm 缺子命令 ${sub}`);
189
- }
190
- const io = await runHelp(registry, ['pm', '--help']);
191
- const help = io.out;
192
- for (const fragment of [
193
- 'integration', 'scope', 'schedule', 'cost', 'quality', 'communication', 'risk', 'stakeholder',
194
- '整合管理(第8章)', '进度管理(第10章)', '沟通管理(第14章)', '干系人管理(第17章)',
195
- 'scan 风险扫描(fixture) [read]',
196
- 'close 迭代关闭(fixture 写场景) [write]',
197
- '其他', 'procurement/audit',
198
- '通用 flags:', '--iteration <id>', '--dry-run', '--yes', 'zzz-delete-me',
199
- ]) {
200
- assert.ok(help.includes(fragment), `help 缺片段: ${fragment}`);
201
- }
202
- const otherIdx = help.indexOf('其他');
203
- assert.ok(otherIdx > help.indexOf('stakeholder'), '「其他」组排在域表之后');
204
- }],
205
- ['tapd pm <domain> --help:该域场景清单', async () => {
206
- const { registry } = makeRegistry();
207
- const io = await runHelp(registry, ['pm', 'risk', '--help']);
208
- assert.ok(io.out.includes('risk 风险管理(第15章) 场景:'), io.out);
209
- assert.ok(io.out.includes('scan 风险扫描(fixture) [read]'));
210
- assert.ok(io.out.includes('用法: tapd pm risk <scenario>'));
211
- }],
212
- ['tapd pm:裸命令输出分组 help,退出码 0', async () => {
213
- const { registry } = makeRegistry();
214
- const io = await withExitCode(() => runCli(registry, ['pm']));
215
- assert.ok(io.out.includes('域分组:'), io.err);
216
- }],
217
- ['tapd pm list:域/场景/读写/来源列;--domain 过滤;--output json', async () => {
218
- const { registry } = makeRegistry();
219
- const io = await withExitCode(() => runCli(registry, ['pm', 'list']));
220
- assert.ok(io.out.includes('domain') && io.out.includes('scenario'));
221
- assert.match(io.out, /^risk\s+scan\s+read\s+user\s+风险扫描(fixture)/m);
222
- assert.match(io.out, /^scope\s+close\s+write\s+user\s+迭代关闭(fixture 写场景)/m);
223
- const filtered = await withExitCode(() => runCli(registry, ['pm', 'list', '--domain', 'risk']));
224
- assert.ok(filtered.out.includes('scan') && !/\bclose\b/.test(filtered.out), '--domain 过滤');
225
- await assert.rejects(withExitCode(() => runCli(registry, ['pm', 'list', '--domain', 'nope'])), (error) => error instanceof CliError && error.message.includes('未知域 "nope"'));
226
- const json = await withExitCode(() => runCli(registry, ['pm', 'list', '--output', 'json']));
227
- const entries = JSON.parse(json.out);
228
- assert.ok(entries.length >= 3);
229
- for (const e of entries) {
230
- for (const key of ['domain', 'scenario', 'name', 'write', 'file', 'source']) {
231
- assert.ok(key in e, `json 条目缺 ${key}`);
232
- }
233
- }
234
- assert.ok(entries.some(e => e.source === 'user' && e.domain === 'procurement'));
235
- }],
236
- ['别名执行 --dry-run:缺省迭代自动解析(3002)并注入 vars 预览,探测只读', async () => {
237
- const { registry, tracker } = makeRegistry();
238
- const io = await withExitCode(() => runCli(registry, ['pm', 'risk', 'scan', '--dry-run']));
239
- assert.ok(io.out.includes('dry-run'), io.out);
240
- assert.ok(io.out.includes('"iteration_id": "3002"'), `预览应注入探测到的迭代: ${io.out}`);
241
- assert.ok(io.out.includes('[read] stories (tapd_get_stories)'));
242
- assert.equal(tracker.probes.length, 1, '探测恰一次');
243
- assert.equal(tracker.probes[0].status, 'open');
244
- assert.equal(tracker.storyArgs.length, 0, 'dry-run 不执行业务步骤');
245
- assert.ok(io.err.includes('iteration: 自动选择 open 迭代 3002(Sprint B)'), io.err);
246
- assert.ok(tracker.probes[0].fields === 'id,name,startdate,status');
247
- }],
248
- ['--iteration 显式注入:跳过缺省探测', async () => {
249
- const { registry, tracker } = makeRegistry();
250
- const io = await withExitCode(() => runCli(registry, ['pm', 'risk', 'scan', '--dry-run', '--iteration', '1001']));
251
- assert.equal(tracker.probes.length, 0, '显式 --iteration 不应探测');
252
- assert.ok(io.out.includes('"iteration_id": "1001"'), io.out);
253
- assert.ok(!io.err.includes('自动选择'));
254
- }],
255
- ['别名路由到 pipeline 引擎:真实执行 + zzz 过滤 + workspace_id 兜底注入', async () => {
256
- const { registry, tracker } = makeRegistry();
257
- const io = await withExitCode(() => runCli(registry, ['pm', 'risk', 'scan', '--workspace-id', '39814312']));
258
- assert.equal(tracker.storyArgs.length, 1);
259
- assert.deepEqual(tracker.storyArgs[0], { iteration_id: '3002', fields: 'id,name', workspace_id: 39814312 });
260
- // 默认 report:无模板 report 块时输出运行摘要(zzz 过滤后 stories 1 条;iterations 返回对象 → ok)
261
- assert.ok(io.out.includes('stories (tapd_get_stories): 1 条'), io.out);
262
- assert.ok(io.out.includes('iterations (tapd_get_iterations): ok'), io.out);
263
- }],
264
- ['写闸门:含写场景未 --yes 拒绝落库(退出码 2)+ 预览;--yes 后才执行', async () => {
265
- const { registry, tracker } = makeRegistry();
266
- const blocked = await withExitCode(() => runCli(registry, ['pm', 'scope', 'close']));
267
- assert.ok(blocked.err.includes('WRITE_GATE'), blocked.err);
268
- assert.ok(blocked.out.includes('write gate'));
269
- assert.equal(tracker.updateArgs.length, 0, '未 --yes 不得执行写步骤');
270
- const allowed = await withExitCode(() => runCli(registry, ['pm', 'scope', 'close', '--yes']));
271
- assert.equal(tracker.updateArgs.length, 1);
272
- assert.deepEqual(tracker.updateArgs[0], { id: '3002', status: 'closed' }, '写步骤拿到探测迭代注入的 vars');
273
- assert.ok(!allowed.err.includes('WRITE_GATE'));
274
- }],
275
- ['未知场景:CliError 列出该域可用场景', async () => {
276
- const { registry } = makeRegistry();
277
- await assert.rejects(withExitCode(() => runCli(registry, ['pm', 'risk', 'nope'])), (error) => error instanceof CliError &&
278
- error.code === 'INVALID_ARGS' &&
279
- error.message.includes('域 risk 下没有场景 "nope"') &&
280
- error.message.includes('可用场景') &&
281
- error.message.includes('scan'));
282
- }],
283
- ['自定义域可执行:pm procurement audit 走同一条别名链路', async () => {
284
- const { registry, tracker } = makeRegistry();
285
- const io = await withExitCode(() => runCli(registry, ['pm', 'procurement', 'audit', '--dry-run']));
286
- assert.ok(io.out.includes('procurement-audit'), io.out);
287
- assert.ok(io.out.includes('dry-run'));
288
- assert.ok(tracker.storyArgs.length === 0);
289
- }],
290
- ];
291
- let failed = 0;
292
- try {
293
- for (const [name, fn] of checks) {
294
- try {
295
- await fn();
296
- console.log(`ok - ${name}`);
297
- }
298
- catch (error) {
299
- failed++;
300
- console.error(`FAIL - ${name}`);
301
- console.error(error);
302
- }
303
- }
304
- }
305
- finally {
306
- if (prevEnv === undefined)
307
- delete process.env.TAPD_USER_TEMPLATES_DIR;
308
- else
309
- process.env.TAPD_USER_TEMPLATES_DIR = prevEnv;
310
- if (prevToken === undefined)
311
- delete process.env.TAPD_ACCESS_TOKEN;
312
- else
313
- process.env.TAPD_ACCESS_TOKEN = prevToken;
314
- rmSync(root, { recursive: true, force: true });
315
- }
316
- if (failed > 0) {
317
- console.error(`\n${failed}/${checks.length} failed`);
318
- process.exit(1);
319
- }
320
- console.log(`\n${checks.length}/${checks.length} passed`);
321
- //# sourceMappingURL=pm-command.test.js.map