@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.
- package/README.md +8 -8
- package/dist/build-registry.js +11 -5
- package/dist/cli.js +839 -585
- package/dist/mcp/code-hosting/github.json +14 -1
- package/dist/mcp/docs-reference/context7.json +1 -10
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +8 -5
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +10 -8
- package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +21 -20
- package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +17 -17
- package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +20 -20
- package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +8 -8
- package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +13 -12
- package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +10 -10
- package/dist/skills/atoll-ops/SKILL.md +4 -0
- package/dist/skills/reef-commit/SKILL.md +9 -5
- package/dist/skills/reef-commit/scripts/branch-check.mjs +5 -11
- package/dist/skills/reef-commit/scripts/check-openspec-status.mjs +6 -12
- package/dist/skills/reef-commit/scripts/collect-git-context.mjs +13 -11
- package/dist/skills/reef-gen-backend/variants/java/steps.md +7 -7
- package/dist/skills/reef-gen-backend/variants/nodejs/steps.md +7 -7
- package/dist/skills/reef-gen-backend/variants/python/steps.md +7 -7
- package/dist/skills/reef-gen-frontend/variants/angular/steps.md +5 -5
- package/dist/skills/reef-gen-frontend/variants/react/steps.md +6 -6
- package/dist/skills/reef-gen-frontend/variants/vue/steps.md +6 -6
- package/dist/skills/reef-harden/EXAMPLES.md +12 -10
- package/dist/skills/reef-harden/SKILL.md +6 -0
- package/dist/skills/reef-harden/scripts/find-change-dir.mjs +13 -13
- package/dist/skills/reef-pr/SKILL.md +7 -0
- package/dist/skills/reef-pr/scripts/create-pr.mjs +31 -29
- package/dist/skills/reef-scope/SKILL.md +6 -6
- package/dist/skills/reef-start/references/jira-start-subagent.md +7 -7
- package/dist/skills/reef-start/references/risk-routing-card.md +28 -25
- package/dist/skills/reef-start/references/stage-4-implementation.md +33 -21
- package/dist/skills/reef-start/references/superpowers-gate.md +9 -9
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +2 -2
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +11 -0
- package/dist/skills/reef-style-backend/fragments/java/api-spec/jackson-polymorphism.md +36 -40
- package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +11 -10
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +24 -21
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +10 -8
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +2 -2
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +5 -5
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +1 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +12 -12
- package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +13 -12
- package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +11 -9
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +6 -6
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +8 -8
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +8 -7
- package/dist/skills/reef-style-backend/fragments/nodejs/eslint-config.json +1 -4
- package/dist/skills/reef-style-backend/fragments/nodejs/nestjs-structure.md +9 -9
- package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +2 -1
- package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +10 -10
- package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +24 -22
- package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +10 -9
- package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +3 -0
- package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +10 -7
- package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +2 -0
- package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +2 -0
- package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +11 -9
- package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +3 -0
- package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +3 -4
- package/dist/skills/reef-style-backend/variants/java/quick-reference.md +31 -29
- package/dist/skills/reef-style-backend/variants/nodejs/examples/module-example.md +2 -8
- package/dist/skills/reef-style-backend/variants/nodejs/quick-reference.md +35 -36
- package/dist/skills/reef-style-backend/variants/python/quick-reference.md +32 -31
- package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +9 -7
- package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +9 -4
- package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +4 -4
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/examples/testing.md +135 -140
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/quick-reference.md +32 -32
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/examples/testing.md +161 -162
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/quick-reference.md +42 -42
- package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +16 -10
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/examples/ui-components.md +72 -87
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/quick-reference.md +14 -10
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/examples/ui-components.md +93 -111
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/quick-reference.md +29 -33
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +14 -23
- package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +15 -17
- package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +22 -5
- package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +52 -22
- package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +17 -20
- package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +8 -11
- package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +27 -27
- package/dist/skills/reef-style-frontend/variants/react/examples/code-wrapping.md +14 -27
- package/dist/skills/reef-style-frontend/variants/react/examples/component-types-pipes.md +45 -52
- package/dist/skills/reef-style-frontend/variants/react/examples/entity-types.md +44 -44
- package/dist/skills/reef-style-frontend/variants/react/examples/forms-layer.md +56 -85
- package/dist/skills/reef-style-frontend/variants/react/examples/service-routing.md +114 -88
- package/dist/skills/reef-style-frontend/variants/react/quick-reference.md +48 -42
- package/dist/skills/reef-style-frontend/variants/vue/examples/code-wrapping.md +24 -43
- package/dist/skills/reef-style-frontend/variants/vue/examples/component-types-pipes.md +48 -49
- package/dist/skills/reef-style-frontend/variants/vue/examples/entity-types.md +45 -45
- package/dist/skills/reef-style-frontend/variants/vue/examples/forms-layer.md +64 -68
- package/dist/skills/reef-style-frontend/variants/vue/examples/service-routing.md +85 -84
- package/dist/skills/reef-style-frontend/variants/vue/quick-reference.md +46 -51
- package/dist/skills/reef-testcase/SKILL.md +19 -19
- package/dist/skills/reef-testcase/references/coverage-dimensions.md +8 -0
- package/dist/skills/reef-testcase/references/test-case-template.md +16 -16
- package/dist/skills/sweep-explore/SKILL.md +129 -100
- package/dist/skills/sweep-explore/references/explore-flow-template.md +9 -9
- package/dist/skills/sweep-init/SKILL.md +4 -0
- package/dist/skills/sweep-init/scripts/flow-selector.mjs +6 -11
- package/dist/skills/sweep-init/scripts/init-project.mjs +68 -51
- package/dist/skills/sweep-plan/references/test-flow-template.md +12 -6
- package/dist/skills/sweep-record/SKILL.md +38 -15
- package/dist/skills/sweep-run/SKILL.md +59 -50
- package/dist/skills/sweep-run/scripts/env-manager.mjs +4 -5
- package/dist/skills/sweep-run/scripts/flow-parser.mjs +7 -6
- package/dist/skills/sweep-run/scripts/flow-selector.mjs +4 -15
- package/dist/skills/sweep-run/scripts/generate-report.mjs +1 -2
- package/dist/skills/sweep-run/scripts/mcp-manager.mjs +107 -14
- package/dist/skills/sweep-run/scripts/spec-compiler.mjs +19 -9
- package/dist/skills/tide-discuss/references/checklists.md +32 -32
- package/dist/skills/tide-discuss/references/data-format.md +85 -77
- package/dist/skills/tide-discuss/references/prd-template.md +21 -19
- package/dist/skills/tide-discuss/references/publish-flow.md +30 -11
- package/dist/skills/tide-discuss/references/role-prompts.md +5 -5
- 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('---')) {
|
|
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
|
-
// ──
|
|
27
|
+
// ── Cross-platform helpers ─────────────────────────────────────────
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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}
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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] :
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
44
|
-
| 2
|
|
45
|
-
| 3
|
|
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
|
|
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
|
|
85
|
-
| 3
|
|
86
|
-
| 4
|
|
81
|
+
| # | 检查项 | 说明 |
|
|
82
|
+
| --- | ------------ | ---------------------------------------------- |
|
|
83
|
+
| 1 | 业务价值 | 这个需求对产品整体方向和商业目标的贡献 |
|
|
84
|
+
| 2 | 投入产出评估 | 实现成本 vs 预期收益的权衡 |
|
|
85
|
+
| 3 | 发布策略 | 发布方式(全量/灰度/内测)、时间窗口、回滚方案 |
|
|
86
|
+
| 4 | 风险决策 | 是否值得做、什么时候做、与哪些需求并行 |
|
|
87
87
|
|
|
88
88
|
**完成标准:** 明确发布策略和优先级决策,给出明确的 Go/No-Go 建议。
|