sdd-flow-kit 1.0.6 → 1.0.8
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
CHANGED
|
@@ -14,14 +14,13 @@
|
|
|
14
14
|
#### 安装教程
|
|
15
15
|
|
|
16
16
|
1. 进入你要开发的项目目录
|
|
17
|
-
2. 安装:`pnpm add -D sdd-flow-kit
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
3.
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
- 会自动注入 `.opsx/config.json`(按所选工具写入 editor/skillsDir/commandsDir)
|
|
17
|
+
2. 安装:`pnpm add -D sdd-flow-kit `
|
|
18
|
+
检查是否安装 - `ls .cursor/skills/sdd-flow-kit/SKILL.md`
|
|
19
|
+
- `ls .claude/skills/sdd-flow-kit/SKILL.md`
|
|
20
|
+
- `ls docs/*-doc-skill/SKILL.md`
|
|
21
|
+
3. 若仍未生成,执行手动兜底(100%触发初始化):
|
|
22
|
+
- `node ./node_modules/sdd-flow-kit/dist/postinstall.js`
|
|
23
|
+
|
|
25
24
|
4. (可选)预演安装:`npx sdd-flow-kit install --dry-run --plan-json`
|
|
26
25
|
5. (可选)环境健检:`npx sdd-flow-kit doctor --agent claude-code`
|
|
27
26
|
6. 完成后,直接在 Cursor / Claude Code / Codex / OpenClaw 中用自然语言发起需求,例如:`开发 ADI 2.3.2 需求`
|
package/package.json
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Confluence 需求文档获取脚本(Playwright)
|
|
4
|
-
|
|
5
|
-
用法:
|
|
6
|
-
python3 confluence-doc.py <版本号>
|
|
7
|
-
|
|
8
|
-
环境变量:
|
|
9
|
-
CONFLUENCE_BASE_URL 例如 https://confluence.huan.tv
|
|
10
|
-
CONFLUENCE_SEARCH_URL 例如 https://confluence.huan.tv/dosearchsite.action?includeArchivedSpaces=false
|
|
11
|
-
CONFLUENCE_USERNAME
|
|
12
|
-
CONFLUENCE_PASSWORD
|
|
13
|
-
DOC_PRODUCT_PREFIX 例如 ADI / OMS / HUAN / ADTOOLS
|
|
14
|
-
DOC_DOWNLOAD_DIR 输出目录(默认当前脚本所在目录的上级 docs/)
|
|
15
|
-
|
|
16
|
-
注意:
|
|
17
|
-
- 为安全起见,本模板不包含任何默认账号密码
|
|
18
|
-
- 你需要先安装 playwright:
|
|
19
|
-
pip install playwright && playwright install chromium
|
|
20
|
-
"""
|
|
2
|
+
"""Confluence 需求文档获取脚本(Playwright - 通用版)"""
|
|
21
3
|
|
|
22
4
|
import sys
|
|
23
5
|
import os
|
|
@@ -34,9 +16,9 @@ except ImportError:
|
|
|
34
16
|
|
|
35
17
|
def env(name: str, default: str | None = None) -> str:
|
|
36
18
|
v = os.environ.get(name, default)
|
|
37
|
-
if v is None
|
|
38
|
-
|
|
39
|
-
return str(v)
|
|
19
|
+
if v is None:
|
|
20
|
+
return ""
|
|
21
|
+
return str(v).strip()
|
|
40
22
|
|
|
41
23
|
|
|
42
24
|
def load_env_file(file_path: Path) -> None:
|
|
@@ -72,7 +54,11 @@ def version_slug(prefix: str, version: str) -> str:
|
|
|
72
54
|
|
|
73
55
|
|
|
74
56
|
def search_keywords(prefix: str, version: str) -> list[str]:
|
|
75
|
-
|
|
57
|
+
# 搜索时强制带 V,并增加不带前缀的 V 兜底项:
|
|
58
|
+
# 1) ADI-V2.3.3
|
|
59
|
+
# 2) ADI_V2.3.3
|
|
60
|
+
# 3) V2.3.3
|
|
61
|
+
return [f"{prefix}-V{version}", f"{prefix}_V{version}", f"V{version}"]
|
|
76
62
|
|
|
77
63
|
|
|
78
64
|
def sanitize_filename(name: str) -> str:
|
|
@@ -89,6 +75,11 @@ def parse_html_to_markdown(html_content: str) -> str:
|
|
|
89
75
|
md = re.sub(r"<li[^>]*>(.*?)</li>", r"- \1\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
90
76
|
md = re.sub(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', r"[\2](\1)", md, flags=re.DOTALL | re.IGNORECASE)
|
|
91
77
|
md = re.sub(r'<img[^>]*src="([^"]+)"[^>]*>', r"", md, flags=re.DOTALL | re.IGNORECASE)
|
|
78
|
+
md = re.sub(r"<table[^>]*>", r"\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
79
|
+
md = re.sub(r"</table>", r"\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
80
|
+
md = re.sub(r"<tr[^>]*>", r"|", md, flags=re.DOTALL | re.IGNORECASE)
|
|
81
|
+
md = re.sub(r"</tr>", r"\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
82
|
+
md = re.sub(r"<t[dh][^>]*>(.*?)</t[dh]>", r"|\1", md, flags=re.DOTALL | re.IGNORECASE)
|
|
92
83
|
md = re.sub(r"<p[^>]*>(.*?)</p>", r"\1\n\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
93
84
|
md = re.sub(r"<br[^>]*>", r"\n", md, flags=re.DOTALL | re.IGNORECASE)
|
|
94
85
|
md = re.sub(r"<strong[^>]*>(.*?)</strong>", r"**\1**", md, flags=re.DOTALL | re.IGNORECASE)
|
|
@@ -167,20 +158,20 @@ def find_search_result(page, base_url: str, keyword: str):
|
|
|
167
158
|
return best
|
|
168
159
|
|
|
169
160
|
|
|
170
|
-
def run_search(page, base_url: str, keyword: str
|
|
171
|
-
page.goto(search_url, wait_until="networkidle", timeout=60000)
|
|
172
|
-
page.wait_for_timeout(800)
|
|
161
|
+
def run_search(page, base_url: str, keyword: str) -> str | None:
|
|
173
162
|
search_selector = None
|
|
174
163
|
for selector in ["#search-query-input", "input[name='search-query']", "input.search-query", "input[id*='search']"]:
|
|
175
164
|
if page.locator(selector).count() > 0:
|
|
176
165
|
search_selector = selector
|
|
177
166
|
break
|
|
178
167
|
if not search_selector:
|
|
168
|
+
if page.locator("input[type='text']").count() == 0:
|
|
169
|
+
return None
|
|
179
170
|
search_selector = "input[type='text']"
|
|
180
|
-
page.fill(search_selector, keyword)
|
|
171
|
+
page.fill(search_selector, keyword, timeout=8000)
|
|
181
172
|
page.keyboard.press("Enter")
|
|
182
173
|
page.wait_for_load_state("networkidle", timeout=30000)
|
|
183
|
-
page.wait_for_timeout(
|
|
174
|
+
page.wait_for_timeout(2000)
|
|
184
175
|
return find_search_result(page, base_url, keyword)
|
|
185
176
|
|
|
186
177
|
|
|
@@ -195,10 +186,10 @@ def main():
|
|
|
195
186
|
for env_name in [".env.pre", ".env", ".env.local", ".env.development", ".env.test"]:
|
|
196
187
|
load_env_file(repo_root / env_name)
|
|
197
188
|
|
|
198
|
-
base_url = env("CONFLUENCE_BASE_URL")
|
|
199
|
-
search_url = env("CONFLUENCE_SEARCH_URL"
|
|
200
|
-
username = env("CONFLUENCE_USERNAME")
|
|
201
|
-
password = env("CONFLUENCE_PASSWORD")
|
|
189
|
+
base_url = env("CONFLUENCE_BASE_URL", "https://confluence.huan.tv") or "https://confluence.huan.tv"
|
|
190
|
+
search_url = env("CONFLUENCE_SEARCH_URL") or f"{base_url.rstrip('/')}/dosearchsite.action?includeArchivedSpaces=false"
|
|
191
|
+
username = env("CONFLUENCE_USERNAME") or env("CONFLUENCE_USER")
|
|
192
|
+
password = env("CONFLUENCE_PASSWORD") or env("CONFLUENCE_PASS")
|
|
202
193
|
prefix = os.environ.get("DOC_PRODUCT_PREFIX", "{{DOC_PRODUCT_PREFIX}}").strip() or "{{DOC_PRODUCT_PREFIX}}"
|
|
203
194
|
version = normalize_version(sys.argv[1])
|
|
204
195
|
keywords = search_keywords(prefix, version)
|
|
@@ -221,23 +212,34 @@ def main():
|
|
|
221
212
|
|
|
222
213
|
with sync_playwright() as p:
|
|
223
214
|
browser = p.chromium.launch(headless=True)
|
|
224
|
-
context = browser.new_context(
|
|
215
|
+
context = browser.new_context(
|
|
216
|
+
user_agent=(
|
|
217
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
|
218
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
219
|
+
)
|
|
220
|
+
)
|
|
225
221
|
page = context.new_page()
|
|
226
222
|
try:
|
|
227
|
-
|
|
228
|
-
page.goto(search_url, wait_until="networkidle", timeout=60000)
|
|
223
|
+
print("\n📄 访问搜索页面...")
|
|
224
|
+
response = page.goto(search_url, wait_until="networkidle", timeout=60000)
|
|
225
|
+
if response:
|
|
226
|
+
print(f" 状态码: {response.status}")
|
|
229
227
|
if "login" in page.url.lower():
|
|
228
|
+
if not username or not password:
|
|
229
|
+
raise RuntimeError("检测到登录页但未提供 CONFLUENCE_USERNAME / CONFLUENCE_PASSWORD")
|
|
230
|
+
print("🔐 检测到需要登录,开始自动登录...")
|
|
230
231
|
page.wait_for_selector("#username-field", timeout=10000)
|
|
231
232
|
page.fill("#username-field", username)
|
|
232
233
|
page.fill("#password-field", password)
|
|
233
234
|
page.click("#login-button")
|
|
234
235
|
page.wait_for_load_state("networkidle", timeout=30000)
|
|
236
|
+
print("✅ 登录完成")
|
|
235
237
|
|
|
236
238
|
target_url = None
|
|
237
239
|
matched_keyword = None
|
|
238
240
|
for kw in keywords:
|
|
239
241
|
print(f"\n🔍 搜索关键词: {kw}")
|
|
240
|
-
found = run_search(page, base_url, kw
|
|
242
|
+
found = run_search(page, base_url, kw)
|
|
241
243
|
if found:
|
|
242
244
|
target_url = found
|
|
243
245
|
matched_keyword = kw
|
|
@@ -260,6 +262,8 @@ def main():
|
|
|
260
262
|
content_elem = page.locator("#main-content")
|
|
261
263
|
if not content_elem.count():
|
|
262
264
|
content_elem = page.locator(".wiki-content")
|
|
265
|
+
if not content_elem.count():
|
|
266
|
+
content_elem = page.locator(".content-body")
|
|
263
267
|
content_html = content_elem.inner_html() if content_elem.count() else ""
|
|
264
268
|
|
|
265
269
|
img_urls = re.findall(r'<img[^>]*src="([^"]+)"', content_html)
|
|
@@ -304,6 +308,9 @@ def main():
|
|
|
304
308
|
print("\n✅ 获取完成!")
|
|
305
309
|
print(f"📄 主文档: {md_path}")
|
|
306
310
|
print(f"🖼️ 图片目录: {version_dir}")
|
|
311
|
+
except Exception as e:
|
|
312
|
+
print(f"\n❌ 获取失败: {e}")
|
|
313
|
+
raise
|
|
307
314
|
finally:
|
|
308
315
|
browser.close()
|
|
309
316
|
|
|
@@ -21,7 +21,6 @@ description: |
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
cd docs/{{SKILL_DIR}}
|
|
24
|
-
export CONFLUENCE_BASE_URL="https://confluence.huan.tv"
|
|
25
24
|
export CONFLUENCE_USERNAME="<你的账号>"
|
|
26
25
|
export CONFLUENCE_PASSWORD="<你的密码>"
|
|
27
26
|
export DOC_PRODUCT_PREFIX="{{PRODUCT_PREFIX}}"
|
|
@@ -37,6 +36,12 @@ python3 confluence-doc.py {{PRODUCT_PREFIX}}-V1.2.3
|
|
|
37
36
|
python3 confluence-doc.py {{PRODUCT_PREFIX}}_V1.2.3
|
|
38
37
|
```
|
|
39
38
|
|
|
39
|
+
脚本搜索顺序(强制带 `V`):
|
|
40
|
+
|
|
41
|
+
1. `{{PRODUCT_PREFIX}}-V<版本>`
|
|
42
|
+
2. `{{PRODUCT_PREFIX}}_V<版本>`
|
|
43
|
+
3. `V<版本>`(兜底)
|
|
44
|
+
|
|
40
45
|
## 输出结构
|
|
41
46
|
|
|
42
47
|
```
|
|
@@ -52,5 +57,6 @@ python3 confluence-doc.py {{PRODUCT_PREFIX}}_V1.2.3
|
|
|
52
57
|
- 依赖 Playwright:`pip install playwright && playwright install chromium`
|
|
53
58
|
- 脚本:`docs/{{SKILL_DIR}}/confluence-doc.py`
|
|
54
59
|
- 为安全起见,脚本不包含默认账号密码,请用环境变量注入
|
|
55
|
-
-
|
|
60
|
+
- 默认 Confluence 地址为 `https://confluence.huan.tv`(可用 `CONFLUENCE_BASE_URL` 覆盖)
|
|
61
|
+
- 脚本会自动尝试读取仓库根目录的 `.env.pre` / `.env` / `.env.local` / `.env.development` / `.env.test`(若其中存在 `CONFLUENCE_*` 配置可直接复用)
|
|
56
62
|
|