@deepstorm/cli 0.11.0 → 0.11.1

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 (123) hide show
  1. package/README.md +8 -8
  2. package/dist/build-registry.js +11 -5
  3. package/dist/cli.js +839 -585
  4. package/dist/mcp/code-hosting/github.json +14 -1
  5. package/dist/mcp/docs-reference/context7.json +1 -10
  6. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +8 -5
  7. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +10 -8
  8. package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +21 -20
  9. package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +17 -17
  10. package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +20 -20
  11. package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +8 -8
  12. package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +13 -12
  13. package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +10 -10
  14. package/dist/skills/atoll-ops/SKILL.md +4 -0
  15. package/dist/skills/reef-commit/SKILL.md +9 -5
  16. package/dist/skills/reef-commit/scripts/branch-check.mjs +5 -11
  17. package/dist/skills/reef-commit/scripts/check-openspec-status.mjs +6 -12
  18. package/dist/skills/reef-commit/scripts/collect-git-context.mjs +13 -11
  19. package/dist/skills/reef-gen-backend/variants/java/steps.md +7 -7
  20. package/dist/skills/reef-gen-backend/variants/nodejs/steps.md +7 -7
  21. package/dist/skills/reef-gen-backend/variants/python/steps.md +7 -7
  22. package/dist/skills/reef-gen-frontend/variants/angular/steps.md +5 -5
  23. package/dist/skills/reef-gen-frontend/variants/react/steps.md +6 -6
  24. package/dist/skills/reef-gen-frontend/variants/vue/steps.md +6 -6
  25. package/dist/skills/reef-harden/EXAMPLES.md +12 -10
  26. package/dist/skills/reef-harden/SKILL.md +6 -0
  27. package/dist/skills/reef-harden/scripts/find-change-dir.mjs +13 -13
  28. package/dist/skills/reef-pr/SKILL.md +7 -0
  29. package/dist/skills/reef-pr/scripts/create-pr.mjs +31 -29
  30. package/dist/skills/reef-scope/SKILL.md +6 -6
  31. package/dist/skills/reef-start/references/jira-start-subagent.md +7 -7
  32. package/dist/skills/reef-start/references/risk-routing-card.md +28 -25
  33. package/dist/skills/reef-start/references/stage-4-implementation.md +33 -21
  34. package/dist/skills/reef-start/references/superpowers-gate.md +9 -9
  35. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +2 -2
  36. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +11 -0
  37. package/dist/skills/reef-style-backend/fragments/java/api-spec/jackson-polymorphism.md +36 -40
  38. package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +11 -10
  39. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +7 -7
  40. package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +24 -21
  41. package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +7 -7
  42. package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +10 -8
  43. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +2 -2
  44. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +5 -5
  45. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +1 -0
  46. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +12 -12
  47. package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +13 -12
  48. package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +11 -9
  49. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +7 -7
  50. package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +6 -6
  51. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +8 -8
  52. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +8 -7
  53. package/dist/skills/reef-style-backend/fragments/nodejs/eslint-config.json +1 -4
  54. package/dist/skills/reef-style-backend/fragments/nodejs/nestjs-structure.md +9 -9
  55. package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +2 -1
  56. package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +10 -10
  57. package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +24 -22
  58. package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +10 -9
  59. package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +3 -0
  60. package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +10 -7
  61. package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +2 -0
  62. package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +2 -0
  63. package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +11 -9
  64. package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +3 -0
  65. package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +3 -4
  66. package/dist/skills/reef-style-backend/variants/java/quick-reference.md +31 -29
  67. package/dist/skills/reef-style-backend/variants/nodejs/examples/module-example.md +2 -8
  68. package/dist/skills/reef-style-backend/variants/nodejs/quick-reference.md +35 -36
  69. package/dist/skills/reef-style-backend/variants/python/quick-reference.md +32 -31
  70. package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +9 -7
  71. package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +9 -4
  72. package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +4 -4
  73. package/dist/skills/reef-style-frontend/fragments/test/vitest-react/examples/testing.md +135 -140
  74. package/dist/skills/reef-style-frontend/fragments/test/vitest-react/quick-reference.md +32 -32
  75. package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/examples/testing.md +161 -162
  76. package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/quick-reference.md +42 -42
  77. package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +16 -10
  78. package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/examples/ui-components.md +72 -87
  79. package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/quick-reference.md +14 -10
  80. package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/examples/ui-components.md +93 -111
  81. package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/quick-reference.md +29 -33
  82. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +14 -23
  83. package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +15 -17
  84. package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +22 -5
  85. package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +52 -22
  86. package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +17 -20
  87. package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +8 -11
  88. package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +27 -27
  89. package/dist/skills/reef-style-frontend/variants/react/examples/code-wrapping.md +14 -27
  90. package/dist/skills/reef-style-frontend/variants/react/examples/component-types-pipes.md +45 -52
  91. package/dist/skills/reef-style-frontend/variants/react/examples/entity-types.md +44 -44
  92. package/dist/skills/reef-style-frontend/variants/react/examples/forms-layer.md +56 -85
  93. package/dist/skills/reef-style-frontend/variants/react/examples/service-routing.md +114 -88
  94. package/dist/skills/reef-style-frontend/variants/react/quick-reference.md +48 -42
  95. package/dist/skills/reef-style-frontend/variants/vue/examples/code-wrapping.md +24 -43
  96. package/dist/skills/reef-style-frontend/variants/vue/examples/component-types-pipes.md +48 -49
  97. package/dist/skills/reef-style-frontend/variants/vue/examples/entity-types.md +45 -45
  98. package/dist/skills/reef-style-frontend/variants/vue/examples/forms-layer.md +64 -68
  99. package/dist/skills/reef-style-frontend/variants/vue/examples/service-routing.md +85 -84
  100. package/dist/skills/reef-style-frontend/variants/vue/quick-reference.md +46 -51
  101. package/dist/skills/reef-testcase/SKILL.md +19 -19
  102. package/dist/skills/reef-testcase/references/coverage-dimensions.md +8 -0
  103. package/dist/skills/reef-testcase/references/test-case-template.md +16 -16
  104. package/dist/skills/sweep-explore/SKILL.md +129 -100
  105. package/dist/skills/sweep-explore/references/explore-flow-template.md +9 -9
  106. package/dist/skills/sweep-init/SKILL.md +4 -0
  107. package/dist/skills/sweep-init/scripts/flow-selector.mjs +6 -11
  108. package/dist/skills/sweep-init/scripts/init-project.mjs +68 -51
  109. package/dist/skills/sweep-plan/references/test-flow-template.md +12 -6
  110. package/dist/skills/sweep-record/SKILL.md +38 -15
  111. package/dist/skills/sweep-run/SKILL.md +59 -50
  112. package/dist/skills/sweep-run/scripts/env-manager.mjs +4 -5
  113. package/dist/skills/sweep-run/scripts/flow-parser.mjs +7 -6
  114. package/dist/skills/sweep-run/scripts/flow-selector.mjs +4 -15
  115. package/dist/skills/sweep-run/scripts/generate-report.mjs +1 -2
  116. package/dist/skills/sweep-run/scripts/mcp-manager.mjs +107 -14
  117. package/dist/skills/sweep-run/scripts/spec-compiler.mjs +19 -9
  118. package/dist/skills/tide-discuss/references/checklists.md +32 -32
  119. package/dist/skills/tide-discuss/references/data-format.md +85 -77
  120. package/dist/skills/tide-discuss/references/prd-template.md +21 -19
  121. package/dist/skills/tide-discuss/references/publish-flow.md +30 -11
  122. package/dist/skills/tide-discuss/references/role-prompts.md +5 -5
  123. package/package.json +4 -3
@@ -62,7 +62,10 @@ function parseScenarios(content) {
62
62
  let inBody = false;
63
63
  for (const line of lines) {
64
64
  // Skip header and separator
65
- if (/^\|[-]+\|/.test(line) && line.includes('---')) { inBody = true; continue; }
65
+ if (/^\|[-]+\|/.test(line) && line.includes('---')) {
66
+ inBody = true;
67
+ continue;
68
+ }
66
69
  if (!inBody) continue;
67
70
 
68
71
  const m = line.match(SCENARIO_ROW_RE);
@@ -172,9 +175,7 @@ function parseFlows(content) {
172
175
 
173
176
  // Get content between this flow heading and the next, or end of file
174
177
  const startIdx = heading.index + heading[0].length;
175
- const endIdx = i + 1 < flowHeadings.length
176
- ? flowHeadings[i + 1].index
177
- : content.length;
178
+ const endIdx = i + 1 < flowHeadings.length ? flowHeadings[i + 1].index : content.length;
178
179
  const flowContent = content.slice(startIdx, endIdx);
179
180
 
180
181
  const preconditionsMatch = flowContent.match(PRECONDITIONS_RE);
@@ -186,8 +187,8 @@ function parseFlows(content) {
186
187
  if (preconditionsMatch) {
187
188
  const precondLines = preconditionsMatch[1]
188
189
  .split('\n')
189
- .map(l => l.replace(/^-\s*/, '').trim())
190
- .filter(l => l.length > 0);
190
+ .map((l) => l.replace(/^-\s*/, '').trim())
191
+ .filter((l) => l.length > 0);
191
192
  flow.preconditions = precondLines.join('\n');
192
193
  }
193
194
 
@@ -41,11 +41,7 @@ async function getCheckbox() {
41
41
  // ── TTY detection ───────────────────────────────────────────────────
42
42
 
43
43
  function isTtyAvailable() {
44
- return Boolean(
45
- process.stdout.isTTY &&
46
- process.stdin.isTTY &&
47
- process.stdin.setRawMode
48
- );
44
+ return Boolean(process.stdout.isTTY && process.stdin.isTTY && process.stdin.setRawMode);
49
45
  }
50
46
 
51
47
  // ── YAML 解析(轻量,不依赖 yaml 包) ──────────────────────────
@@ -98,14 +94,10 @@ function resolveFlowFile(modulePath, baseFlowsDir) {
98
94
 
99
95
  const parts = modulePath.split('/');
100
96
  const leafName = parts[parts.length - 1];
101
- const parentDir = parts.length > 1
102
- ? join(baseFlowsDir, parts.slice(0, -1).join('/'))
103
- : baseFlowsDir;
97
+ const parentDir = parts.length > 1 ? join(baseFlowsDir, parts.slice(0, -1).join('/')) : baseFlowsDir;
104
98
  if (existsSync(parentDir)) {
105
99
  const files = readdirSync(parentDir);
106
- const found = files.find(
107
- (f) => f.endsWith('.flow.md') && f.toLowerCase().includes(leafName.toLowerCase())
108
- );
100
+ const found = files.find((f) => f.endsWith('.flow.md') && f.toLowerCase().includes(leafName.toLowerCase()));
109
101
  if (found) return join(parentDir, found);
110
102
  }
111
103
 
@@ -442,10 +434,7 @@ async function main() {
442
434
  }
443
435
  }
444
436
 
445
- choices = [
446
- { name: '📋 全部执行', value: '__all__' },
447
- ...choices,
448
- ];
437
+ choices = [{ name: '📋 全部执行', value: '__all__' }, ...choices];
449
438
 
450
439
  // ── Decide mode: text vs checkbox ──────────────────────
451
440
 
@@ -30,8 +30,7 @@ import { resolve } from 'node:path';
30
30
  */
31
31
  export function generateReport(data) {
32
32
  const total = data.passed + data.failed + data.skipped;
33
- const passRate = total > 0
34
- ? Math.round((data.passed / total) * 100) : 0;
33
+ const passRate = total > 0 ? Math.round((data.passed / total) * 100) : 0;
35
34
 
36
35
  const lines = [];
37
36
  lines.push('# 测试执行报告\n');
@@ -13,6 +13,10 @@
13
13
  * node mcp-manager.mjs --status [--port 54321]
14
14
  * node mcp-manager.mjs --stop [--port 54321]
15
15
  * import { ensureMcp, stopMcp, getStatus } from './mcp-manager.mjs'
16
+ *
17
+ * Cross-platform note:
18
+ * 所有系统调用均兼容 macOS / Linux / Windows。
19
+ * 使用 Node.js 内置 API 替代 lsof / ps / sleep / SIGKILL 等 Unix-only 命令。
16
20
  */
17
21
 
18
22
  import { execSync, spawn } from 'node:child_process';
@@ -20,16 +24,39 @@ import { execSync, spawn } from 'node:child_process';
20
24
  const DEFAULT_PORT = 54321;
21
25
  const MCP_PACKAGE = '@playwright/mcp';
22
26
 
23
- // ── Process helpers ────────────────────────────────────────────────
27
+ // ── Cross-platform helpers ─────────────────────────────────────────
24
28
 
25
29
  /**
26
- * Find PID of process listening on a given TCP port.
27
- * Returns null if nothing is listening.
30
+ * 跨平台同步 sleep 使用 Atomics.wait 实现,不依赖 shell sleep 命令。
31
+ * @param {number} ms
32
+ */
33
+ function sleepSync(ms) {
34
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
35
+ }
36
+
37
+ // ── Cross-platform process helpers ──────────────────────────────────
38
+
39
+ /**
40
+ * 跨平台查找监听指定 TCP 端口的进程 PID。
41
+ *
42
+ * - macOS/Linux: lsof -ti tcp:<port>
43
+ * - Windows: netstat -ano | findstr LISTENING
44
+ *
45
+ * @param {number} port
46
+ * @returns {number|null}
28
47
  */
29
48
  export function findPidByPort(port) {
49
+ if (process.platform === 'win32') {
50
+ return findPidByPortWin(port);
51
+ }
52
+ return findPidByPortUnix(port);
53
+ }
54
+
55
+ function findPidByPortUnix(port) {
30
56
  try {
31
- const stdout = execSync(`lsof -ti tcp:${port} 2>/dev/null`, {
57
+ const stdout = execSync(`lsof -ti tcp:${port}`, {
32
58
  encoding: 'utf-8',
59
+ stdio: ['pipe', 'pipe', 'ignore'],
33
60
  timeout: 3000,
34
61
  }).trim();
35
62
  return stdout ? parseInt(stdout.split('\n')[0], 10) : null;
@@ -38,13 +65,47 @@ export function findPidByPort(port) {
38
65
  }
39
66
  }
40
67
 
68
+ function findPidByPortWin(port) {
69
+ try {
70
+ const stdout = execSync(`netstat -ano | findstr "LISTENING" | findstr ":${port} "`, {
71
+ encoding: 'utf-8',
72
+ stdio: ['pipe', 'pipe', 'ignore'],
73
+ timeout: 5000,
74
+ }).trim();
75
+ // netstat output: TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 12345
76
+ const lines = stdout.split('\n');
77
+ for (const line of lines) {
78
+ const parts = line.trim().split(/\s+/);
79
+ const pid = parseInt(parts[parts.length - 1], 10);
80
+ if (Number.isFinite(pid)) return pid;
81
+ }
82
+ return null;
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
41
88
  /**
42
- * Get the command line of a process by PID.
89
+ * 跨平台获取进程命令行。
90
+ *
91
+ * - macOS/Linux: ps -p <pid> -o args=
92
+ * - Windows: wmic process where processid=<pid> get commandline
93
+ *
94
+ * @param {number} pid
95
+ * @returns {string}
43
96
  */
44
97
  export function getProcessCommand(pid) {
98
+ if (process.platform === 'win32') {
99
+ return getProcessCommandWin(pid);
100
+ }
101
+ return getProcessCommandUnix(pid);
102
+ }
103
+
104
+ function getProcessCommandUnix(pid) {
45
105
  try {
46
- return execSync(`ps -p ${pid} -o args= 2>/dev/null`, {
106
+ return execSync(`ps -p ${pid} -o args=`, {
47
107
  encoding: 'utf-8',
108
+ stdio: ['pipe', 'pipe', 'ignore'],
48
109
  timeout: 3000,
49
110
  }).trim();
50
111
  } catch {
@@ -52,6 +113,20 @@ export function getProcessCommand(pid) {
52
113
  }
53
114
  }
54
115
 
116
+ function getProcessCommandWin(pid) {
117
+ try {
118
+ const stdout = execSync(`wmic process where processid=${pid} get commandline`, {
119
+ encoding: 'utf-8',
120
+ stdio: ['pipe', 'pipe', 'ignore'],
121
+ timeout: 5000,
122
+ }).trim();
123
+ const lines = stdout.split('\n').filter(Boolean);
124
+ return lines.length >= 2 ? lines[1].trim() : '';
125
+ } catch {
126
+ return '';
127
+ }
128
+ }
129
+
55
130
  /**
56
131
  * Check if a process command indicates headless mode.
57
132
  */
@@ -60,12 +135,17 @@ export function isHeadless(cmd) {
60
135
  }
61
136
 
62
137
  /**
63
- * Kill a process by PID with escalation (SIGTERM -> SIGKILL).
138
+ * 跨平台终止进程。
139
+ *
140
+ * - macOS/Linux: SIGTERM → wait → SIGKILL (force)
141
+ * - Windows: process.kill SIGTERM → taskkill /F (force fallback)
142
+ *
143
+ * @param {number} pid
64
144
  */
65
145
  export function killProcess(pid) {
66
146
  try {
67
147
  process.kill(pid, 'SIGTERM');
68
- // Wait briefly for graceful shutdown
148
+ // Wait briefly for graceful shutdown — polling with setTimeout
69
149
  const start = Date.now();
70
150
  while (Date.now() - start < 2000) {
71
151
  try {
@@ -73,10 +153,23 @@ export function killProcess(pid) {
73
153
  } catch {
74
154
  return; // process is gone
75
155
  }
76
- execSync('sleep 0.2', { timeout: 1000 });
156
+ // Busy-wait with micro-pause to avoid CPU spin
157
+ const pollStart = Date.now();
158
+ while (Date.now() - pollStart < 50) {
159
+ /* yield */
160
+ }
77
161
  }
78
162
  // Force kill
79
- process.kill(pid, 'SIGKILL');
163
+ if (process.platform === 'win32') {
164
+ // Windows: SIGKILL unsupported, use taskkill /F
165
+ try {
166
+ execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore', timeout: 5000 });
167
+ } catch {
168
+ // already dead
169
+ }
170
+ } else {
171
+ process.kill(pid, 'SIGKILL');
172
+ }
80
173
  } catch {
81
174
  // already dead
82
175
  }
@@ -142,7 +235,7 @@ export function ensureMcp(options = {}) {
142
235
  }
143
236
  // Running in wrong mode -- restart
144
237
  stopMcp(port);
145
- execSync('sleep 1', { timeout: 2000 });
238
+ sleepSync(1000);
146
239
  }
147
240
 
148
241
  // Start MCP
@@ -161,7 +254,7 @@ export function ensureMcp(options = {}) {
161
254
  const deadline = Date.now() + 10000;
162
255
  let startedPid = null;
163
256
  while (Date.now() < deadline) {
164
- execSync('sleep 0.3', { timeout: 1000 });
257
+ sleep(300);
165
258
  const found = findPidByPort(port);
166
259
  if (found) {
167
260
  startedPid = found;
@@ -181,8 +274,8 @@ export function ensureMcp(options = {}) {
181
274
 
182
275
  if (process.argv[1] === import.meta.filename) {
183
276
  const args = process.argv.slice(2);
184
- const modeFlag = args.find(a => a.startsWith('--mode='));
185
- const portFlag = args.find(a => a.startsWith('--port='));
277
+ const modeFlag = args.find((a) => a.startsWith('--mode='));
278
+ const portFlag = args.find((a) => a.startsWith('--port='));
186
279
  const mode = modeFlag ? modeFlag.split('=')[1] : 'headless';
187
280
  const port = portFlag ? parseInt(portFlag.split('=')[1], 10) : DEFAULT_PORT;
188
281
 
@@ -24,7 +24,7 @@ function inferAction(description, index) {
24
24
  // Navigation
25
25
  if (/导航|打开|goto/i.test(desc)) {
26
26
  const urlMatch = desc.match(/`([^`]+)`/);
27
- const url = urlMatch ? urlMatch[1] : 'process.env.BASE_URL || \'/\'';
27
+ const url = urlMatch ? urlMatch[1] : "process.env.BASE_URL || '/'";
28
28
  return {
29
29
  comment: desc,
30
30
  action: 'page.goto',
@@ -48,7 +48,9 @@ function inferAction(description, index) {
48
48
  const valueMatch = desc.match(/`([^`]+)`/);
49
49
  const value = valueMatch ? valueMatch[1] : '';
50
50
  const targetMatch = desc.match(/[""「」](.+?)[""」』]/);
51
- const selector = targetMatch ? `[placeholder="${targetMatch[1]}" i]` : `input${index > 0 ? `:nth-child(${index})` : ''}`;
51
+ const selector = targetMatch
52
+ ? `[placeholder="${targetMatch[1]}" i]`
53
+ : `input${index > 0 ? `:nth-child(${index})` : ''}`;
52
54
  return {
53
55
  comment: desc,
54
56
  action: 'page.fill',
@@ -155,7 +157,9 @@ export function compile(parsed) {
155
157
  lines.push('');
156
158
 
157
159
  for (const flow of parsed.flows) {
158
- lines.push(`${indent}test('${escapeStr(parsed.featureName)} - ${escapeStr(flow.id + ' - ' + flow.title)}', async ({ page }) => {`);
160
+ lines.push(
161
+ `${indent}test('${escapeStr(parsed.featureName)} - ${escapeStr(flow.id + ' - ' + flow.title)}', async ({ page }) => {`,
162
+ );
159
163
 
160
164
  // Preconditions as comments
161
165
  if (flow.preconditions) {
@@ -166,8 +170,8 @@ export function compile(parsed) {
166
170
  }
167
171
 
168
172
  // AI_REQUIRED marker — skip entire flow if any step needs AI
169
- const hasAiRequired = flow.steps.some(s =>
170
- s.description.includes('AI_REQUIRED') || s.description.includes('<!-- AI_REQUIRED -->')
173
+ const hasAiRequired = flow.steps.some(
174
+ (s) => s.description.includes('AI_REQUIRED') || s.description.includes('<!-- AI_REQUIRED -->'),
171
175
  );
172
176
  if (hasAiRequired) {
173
177
  lines.push(`${indent}${indent}// AI_REQUIRED — cannot execute natively, AI will handle`);
@@ -181,7 +185,7 @@ export function compile(parsed) {
181
185
 
182
186
  const action = inferAction(desc, step.order);
183
187
  if (action.action) {
184
- const args = action.args.map(a => typeof a === 'string' ? `'${escapeStr(a)}'` : a).join(', ');
188
+ const args = action.args.map((a) => (typeof a === 'string' ? `'${escapeStr(a)}'` : a)).join(', ');
185
189
  lines.push(`${indent}${indent}await ${action.action}(${args});`);
186
190
  } else {
187
191
  // Cannot map to Playwright API — keep as TODO comment
@@ -196,11 +200,17 @@ export function compile(parsed) {
196
200
  if (va.assertion === 'toBeVisible') {
197
201
  lines.push(`${indent}${indent}await expect(page.locator('${escapeStr(va.args[0])}')).toBeVisible();`);
198
202
  } else if (va.assertion === 'toHaveText') {
199
- lines.push(`${indent}${indent}await expect(page.locator('${escapeStr(va.args[0])}')).toHaveText('${escapeStr(va.args[1])}');`);
203
+ lines.push(
204
+ `${indent}${indent}await expect(page.locator('${escapeStr(va.args[0])}')).toHaveText('${escapeStr(va.args[1])}');`,
205
+ );
200
206
  } else if (va.assertion === 'toHaveTitle') {
201
- lines.push(`${indent}${indent}await expect(page).toHaveTitle(${typeof va.args[0] === 'string' ? `'${escapeStr(va.args[0])}'` : va.args[0]});`);
207
+ lines.push(
208
+ `${indent}${indent}await expect(page).toHaveTitle(${typeof va.args[0] === 'string' ? `'${escapeStr(va.args[0])}'` : va.args[0]});`,
209
+ );
202
210
  } else if (va.assertion === 'toHaveURL') {
203
- lines.push(`${indent}${indent}await expect(page).toHaveURL(${typeof va.args[0] === 'string' ? `'${escapeStr(va.args[0])}'` : va.args[0]});`);
211
+ lines.push(
212
+ `${indent}${indent}await expect(page).toHaveURL(${typeof va.args[0] === 'string' ? `'${escapeStr(va.args[0])}'` : va.args[0]});`,
213
+ );
204
214
  }
205
215
  }
206
216
 
@@ -24,13 +24,13 @@
24
24
 
25
25
  ## 💼 analyst(必需)— 📊 业务分析师 Mary
26
26
 
27
- | # | 检查项 | 说明 |
28
- | -- | ------ | ---- |
29
- | 1 | 需求背景和业务目标 | 为什么做这个需求?业务层面要达成什么目标? |
30
- | 2 | 目标用户画像 | 谁是目标用户?使用场景是什么?用户规模? |
31
- | 3 | 核心痛点分析 | 当前有什么问题/痛点?用户反馈了什么? |
32
- | 4 | 竞品和市场调研 | 竞品有类似功能吗?市场趋势如何?行业最佳实践? |
33
- | 5 | 关键成功指标 | 怎么衡量需求上线后的效果?核心 KPI 是什么? |
27
+ | # | 检查项 | 说明 |
28
+ | --- | ------------------ | ---------------------------------------------- |
29
+ | 1 | 需求背景和业务目标 | 为什么做这个需求?业务层面要达成什么目标? |
30
+ | 2 | 目标用户画像 | 谁是目标用户?使用场景是什么?用户规模? |
31
+ | 3 | 核心痛点分析 | 当前有什么问题/痛点?用户反馈了什么? |
32
+ | 4 | 竞品和市场调研 | 竞品有类似功能吗?市场趋势如何?行业最佳实践? |
33
+ | 5 | 关键成功指标 | 怎么衡量需求上线后的效果?核心 KPI 是什么? |
34
34
 
35
35
  **完成标准:** 以上 5 项至少都提出了问题并收到用户反馈,不要仅凭 AI 自行补充。
36
36
 
@@ -38,13 +38,13 @@
38
38
 
39
39
  ## 📋 pm(必需)— 产品经理 John
40
40
 
41
- | # | 检查项 | 说明 |
42
- | -- | ------ | ---- |
43
- | 1 | MVP 功能范围 | 第一期做什么?边界在哪里? |
44
- | 2 | 用户故事 | 从用户视角描述核心功能场景(格式:作为XXX,我想XXX,以便XXX) |
45
- | 3 | 优先级排序 | 功能点的优先级怎么排?Must have / Should have / Nice to have? |
46
- | 4 | 验收标准 | 每个功能点的可验证完成标准 |
47
- | 5 | 边界与反例 | 明确不做什么、不做的情况,以及异常场景处理 |
41
+ | # | 检查项 | 说明 |
42
+ | --- | ------------ | -------------------------------------------------------------- |
43
+ | 1 | MVP 功能范围 | 第一期做什么?边界在哪里? |
44
+ | 2 | 用户故事 | 从用户视角描述核心功能场景(格式:作为XXX,我想XXX,以便XXX) |
45
+ | 3 | 优先级排序 | 功能点的优先级怎么排?Must have / Should have / Nice to have? |
46
+ | 4 | 验收标准 | 每个功能点的可验证完成标准 |
47
+ | 5 | 边界与反例 | 明确不做什么、不做的情况,以及异常场景处理 |
48
48
 
49
49
  **完成标准:** 至少产出 3+ 条用户故事、2+ 条验收标准、明确的功能边界。
50
50
 
@@ -52,12 +52,12 @@
52
52
 
53
53
  ## 🏗️ architect(可选)— 架构师 Winston
54
54
 
55
- | # | 检查项 | 说明 |
56
- | -- | ------ | ---- |
57
- | 1 | 技术可行性 | 现有架构能否支撑?需要哪些新依赖或服务? |
58
- | 2 | 系统影响 | 影响哪些已有模块?是否需要数据库变更? |
59
- | 3 | 技术风险 | 性能瓶颈?安全风险?第三方依赖风险? |
60
- | 4 | 架构建议 | 推荐的实现方案、技术选型、模块划分 |
55
+ | # | 检查项 | 说明 |
56
+ | --- | ---------- | ---------------------------------------- |
57
+ | 1 | 技术可行性 | 现有架构能否支撑?需要哪些新依赖或服务? |
58
+ | 2 | 系统影响 | 影响哪些已有模块?是否需要数据库变更? |
59
+ | 3 | 技术风险 | 性能瓶颈?安全风险?第三方依赖风险? |
60
+ | 4 | 架构建议 | 推荐的实现方案、技术选型、模块划分 |
61
61
 
62
62
  **完成标准:** 识别出主要技术风险和影响范围,给出架构决策建议。
63
63
 
@@ -65,12 +65,12 @@
65
65
 
66
66
  ## 🎨 designer(可选)— UX 设计师 Sally
67
67
 
68
- | # | 检查项 | 说明 |
69
- | -- | ------ | ---- |
70
- | 1 | 用户流程 | 用户完成功能的关键步骤和操作路径 |
71
- | 2 | 关键交互场景 | 核心界面交互,含正常流程和异常状态 |
72
- | 3 | UX 建议 | 交互细节、动效、文案、无障碍等建议 |
73
- | 4 | 设计约束 | 需要遵循的设计规范、组件库引用 |
68
+ | # | 检查项 | 说明 |
69
+ | --- | ------------ | ---------------------------------- |
70
+ | 1 | 用户流程 | 用户完成功能的关键步骤和操作路径 |
71
+ | 2 | 关键交互场景 | 核心界面交互,含正常流程和异常状态 |
72
+ | 3 | UX 建议 | 交互细节、动效、文案、无障碍等建议 |
73
+ | 4 | 设计约束 | 需要遵循的设计规范、组件库引用 |
74
74
 
75
75
  **完成标准:** 梳理出关键交互流程,给出至少 2 条 UX 改进建议。
76
76
 
@@ -78,11 +78,11 @@
78
78
 
79
79
  ## 👑 po(可选)— 产品负责人 Chris
80
80
 
81
- | # | 检查项 | 说明 |
82
- | -- | ------ | ---- |
83
- | 1 | 业务价值 | 这个需求对产品整体方向和商业目标的贡献 |
84
- | 2 | 投入产出评估 | 实现成本 vs 预期收益的权衡 |
85
- | 3 | 发布策略 | 发布方式(全量/灰度/内测)、时间窗口、回滚方案 |
86
- | 4 | 风险决策 | 是否值得做、什么时候做、与哪些需求并行 |
81
+ | # | 检查项 | 说明 |
82
+ | --- | ------------ | ---------------------------------------------- |
83
+ | 1 | 业务价值 | 这个需求对产品整体方向和商业目标的贡献 |
84
+ | 2 | 投入产出评估 | 实现成本 vs 预期收益的权衡 |
85
+ | 3 | 发布策略 | 发布方式(全量/灰度/内测)、时间窗口、回滚方案 |
86
+ | 4 | 风险决策 | 是否值得做、什么时候做、与哪些需求并行 |
87
87
 
88
88
  **完成标准:** 明确发布策略和优先级决策,给出明确的 Go/No-Go 建议。