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 && pnpm rebuild sdd-flow-kit`
18
- - 安装后会自动执行一次初始化(postinstall)
19
- - 交互终端下会提示选择项目类型(OMS / ADI / 欢盟 / AD Tools,默认 OMS)
20
- - 非交互环境自动使用默认值(agent=claude-code, project=OMS)
21
- 3. 安装流程(安装即用,默认 `claude-code`):`npx sdd-flow-kit install`
22
- - 会自动安装对应 AI 环境并写入项目级触发 Skill(仅在“开发 <产品> <版本> 需求/版本/PRD”场景触发)
23
- - 会在项目根目录生成 `docs/<项目>-doc-skill/`(用于 PRD 下载脚本与说明),可通过 `--project` 选择:`OMS` / `ADI` / `欢盟` / `AD Tools`(默认 `OMS`)
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,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-flow-kit",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "private": false,
5
5
  "description": "Cross-agent SDD automated development workflow kit (ADI/ADI-like).",
6
6
  "license": "MIT",
@@ -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 or str(v).strip() == "":
38
- raise RuntimeError(f"缺少环境变量: {name}")
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
- return [f"{prefix}-V{version}", f"{prefix}_V{version}"]
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"![](\1)", 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, search_url: str) -> str | None:
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(1500)
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", base_url.rstrip("/") + "/dosearchsite.action?includeArchivedSpaces=false")
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, search_url)
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
- - 脚本会自动尝试读取仓库根目录的 `.env.pre` / `.env` / `.env.local`(若其中存在 `CONFLUENCE_*` 配置可直接复用)
60
+ - 默认 Confluence 地址为 `https://confluence.huan.tv`(可用 `CONFLUENCE_BASE_URL` 覆盖)
61
+ - 脚本会自动尝试读取仓库根目录的 `.env.pre` / `.env` / `.env.local` / `.env.development` / `.env.test`(若其中存在 `CONFLUENCE_*` 配置可直接复用)
56
62