sdd-flow-kit 1.3.11 → 1.3.12

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
@@ -92,6 +92,14 @@ node ./node_modules/sdd-flow-kit/dist/postinstall.js
92
92
  - Claude Code 安装(`.claude/skills`)时默认用 `claude -p` 执行修复,不限于 Cursor
93
93
  - 可用 `--agent claude-code` 或 `SDD_FLOW_KIT_ENABLE_*_AUTORUN=0` 控制
94
94
 
95
+ **1.3.12** — Confluence 关键字搜索稳定性(dosearchsite 主路径)
96
+
97
+ - `confluence-doc.py`:每个关键词优先打开 `dosearchsite.action?queryString=...` 专用搜索页(不再依赖 `#all-updates` 顶栏搜索)
98
+ - 匹配放宽:**标题命中或 URL 路径命中** 均可得分(`score_link_match` 原已支持,增强前缀匹配与 ADI 加权)
99
+ - `run_search_dosearch`:处理 `ERR_ABORTED`、搜索结果页导航重试;顶栏搜索降为兜底
100
+ - 新增 `CONFLUENCE_DEBUG=1` 输出选择器解析日志
101
+ - 更新 `01-adi-doc-skill-指引` / `doc-skill.SKILL`:关键字搜索为主,pageId 直链为可选兜底
102
+
95
103
  **1.3.9** — PRD 不一致自动修复循环(最多 3 次)
96
104
 
97
105
  - 新增 `prd-remediate`:`prd-diff` 失败 → 按 `09` 生成修复指引 → 调 AI 工具 `/opsx-apply` 修代码 → 再 `prd-diff`(**每项**最多 3 次)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-flow-kit",
3
- "version": "1.3.11",
3
+ "version": "1.3.12",
4
4
  "private": false,
5
5
  "description": "Cross-agent SDD automated development workflow kit (ADI/ADI-like).",
6
6
  "license": "MIT",
@@ -12,14 +12,18 @@ python3 confluence-doc.py {{VERSION_EXAMPLE}}
12
12
 
13
13
  版本号可传:`2.3.2`、`V2.3.2`、`ADI_V2.3.2`、`ADI-V2.3.2`(脚本会归一化)。
14
14
 
15
- 2. 脚本会依次搜索(含用户原样输入):`ADI_V*` → `ADI-V*` → `ADI_*` → `ADI-*` → `ADI_v*` → `ADI-v*` → `ADI V*` → `ADI_V*结算` → `V*` 等。
15
+ 2. 脚本会依次用关键词打开 **Confluence 专用搜索页** `dosearchsite.action?queryString=<关键词>`(非首页顶栏搜索),并依次尝试:`ADI_V*` → `ADI-V*` → `ADI_*` → `ADI-*` → `ADI_v*` → `ADI-v*` → `ADI V*` → `ADI_V*结算` → `V*` 等。
16
16
 
17
- 3. **禁止**在 Confluence 脚本未跑完或未确认失败前,用本地 Word/原型替代 `source/PRD.md`。
17
+ 3. 匹配规则:**标题包含关键词或以关键词为前缀**,或 **URL 路径包含关键词**,任一满足即命中(Confluence 列表标题常被截断)。
18
18
 
19
- 4. 成功后把生成的 `docs/*.md` **主文档**复制/回填到本 run 目录:
19
+ 4. **禁止**在 Confluence 脚本未跑完或未确认失败前,用本地 Word/原型替代 `source/PRD.md`。
20
+
21
+ 5. 成功后把生成的 `docs/*.md` **主文档**复制/回填到本 run 目录:
20
22
  - `source/PRD.md`
21
23
 
22
- ## 直链兜底(已知 pageId 时推荐)
24
+ ## 直链兜底(可选,非默认)
25
+
26
+ 仅在关键字搜索全部失败、且已知 pageId 时使用:
23
27
 
24
28
  ADI V2.3.4 示例(`yyzcpt` 空间 pageId `109609740`):
25
29
 
@@ -44,8 +48,8 @@ python3 confluence-doc.py 2.3.4
44
48
 
45
49
  ## 失败排查
46
50
 
47
- - 输出 `❌ 未找到`:到 Confluence 确认页面标题(ADI 常为 `ADI_V2.3.4结算、开票…`,**下划线**而非 `ADI-V`)
48
- - 页面存在但搜不到:设置 `CONFLUENCE_PAGE_ID=<pageId>` `CONFLUENCE_PAGE_URL=<完整URL>` 直链下载
51
+ - 输出 `❌ 未找到`:到 Confluence 确认页面标题(ADI 常为 `ADI_V2.3.4结算、开票…`,**下划线**而非 `ADI-V`);可尝试输入更长关键词如 `ADI_V2.3.4结算`
52
+ - 页面存在但搜不到:加 `CONFLUENCE_DEBUG=1` 查看选择器解析日志;或(可选)设置 `CONFLUENCE_PAGE_ID` / `CONFLUENCE_PAGE_URL` 直链下载
49
53
  - Playwright 失败且设置 `CONFLUENCE_USE_REST=1` 时才回退 Token;登录最多重试 3 次,并用 `expect_navigation` 避免竞态
50
54
  - 登录偶发失败:用 `CONFLUENCE_HEADED=1 CONFLUENCE_SLOW_MO=500` 有头观察;失败截图 `confluence-login-fail-*.png`
51
55
  - `playwright` 未安装:`pip install playwright && playwright install chromium`
@@ -177,8 +177,10 @@ def href_path_matches_keyword(href: str, keyword: str) -> bool:
177
177
 
178
178
 
179
179
  def text_matches_keyword(text: str, keyword: str) -> bool:
180
- """标题/链接文案匹配:忽略 -/_ 差异。"""
181
- return normalize_match_token(keyword) in normalize_match_token(text)
180
+ """标题/链接文案匹配:忽略 -/_ 差异;支持前缀命中(如 ADI_V2.3.4结算…)。"""
181
+ t_norm = normalize_match_token(text or "")
182
+ kw_norm = normalize_match_token(keyword)
183
+ return kw_norm in t_norm or t_norm.startswith(kw_norm)
182
184
 
183
185
 
184
186
  def score_link_match(text: str, href: str, prefix: str, version: str, keyword: str) -> int:
@@ -212,22 +214,34 @@ def score_link_match(text: str, href: str, prefix: str, version: str, keyword: s
212
214
  space = env("CONFLUENCE_SPACE", "").lower()
213
215
  if space and f"/spaces/{space}/" in href.lower():
214
216
  score += 5
217
+ # ADI/产品 PRD 标题或 URL 含版本号时加权,降低误命中其他空间页面
218
+ if re.search(r"adi[-_]?v", text, re.IGNORECASE) or re.search(
219
+ r"adi[-_]?v", unquote(href), re.IGNORECASE
220
+ ):
221
+ score += 2
215
222
  return score
216
223
 
217
224
 
218
225
  def collect_search_candidates(
219
226
  page, base_url: str, prefix: str, version: str, keyword: str
220
227
  ) -> list[tuple[str, str, int]]:
221
- """返回 [(url, title, score), ...]"""
228
+ """返回 [(url, title, score), ...];标题命中或 URL 路径命中均可得分(>=2)。"""
222
229
  seen: set[str] = set()
223
230
  scored: list[tuple[int, str, str]] = []
231
+ debug = env_bool("CONFLUENCE_DEBUG")
224
232
 
225
233
  selectors = [
226
234
  ".search-result .title a",
227
235
  ".search-result-title a",
228
236
  "#search-results .gs-title a",
237
+ "ol.search-results-list a",
238
+ ".search-results a",
239
+ ".search-result a",
240
+ "#search-results a",
229
241
  ".search-results-panel a",
230
242
  "a[data-linked-resource-id]",
243
+ f"a[href*='{keyword}']",
244
+ f"a:has-text('{keyword}')",
231
245
  "a[href*='/pages/']",
232
246
  "a[href*='/display/']",
233
247
  ]
@@ -235,6 +249,8 @@ def collect_search_candidates(
235
249
  for selector in selectors:
236
250
  links = page.locator(selector)
237
251
  count = min(links.count(), 200)
252
+ if debug and count:
253
+ print(f" [debug] selector={selector!r} count={count}")
238
254
  for i in range(count):
239
255
  href = links.nth(i).get_attribute("href") or ""
240
256
  raw_text = (links.nth(i).inner_text() or "").strip()
@@ -252,6 +268,9 @@ def collect_search_candidates(
252
268
  if s >= 2:
253
269
  scored.append((s, full_url, text[:120]))
254
270
 
271
+ if debug and not scored:
272
+ print(" [debug] 未解析到 score>=2 的 wiki 页面链接")
273
+
255
274
  scored.sort(key=lambda x: x[0], reverse=True)
256
275
  return [(u, t, s) for s, u, t in scored]
257
276
 
@@ -625,19 +644,36 @@ def try_rest_download(
625
644
 
626
645
 
627
646
  def run_search_dosearch(page, base_url: str, prefix: str, version: str, keyword: str) -> list[tuple[str, str, int]]:
647
+ """打开 Confluence 全站专用搜索页(dosearchsite),比首页 #all-updates 顶栏搜索更稳定。"""
628
648
  url = f"{base_url.rstrip('/')}/dosearchsite.action?queryString={quote(keyword)}"
629
- print(f" → 全站搜索: {url}")
630
- try:
631
- page.goto(url, wait_until="domcontentloaded", timeout=60000)
632
- except Exception:
633
- page.goto(url, wait_until="load", timeout=60000)
634
- page.wait_for_timeout(2500)
635
- if is_login_page(page):
636
- return []
649
+ print(f" → 专用搜索页: .../dosearchsite.action?queryString={quote(keyword)}")
650
+ for attempt in range(2):
651
+ try:
652
+ page.goto(url, wait_until="domcontentloaded", timeout=60000)
653
+ except Exception as exc:
654
+ # 搜索页常有重定向/长连接,networkidle 易误报 ERR_ABORTED
655
+ if "ERR_ABORTED" not in str(exc) and "Timeout" not in str(exc):
656
+ raise
657
+ if attempt == 0:
658
+ print(" 导航被中断,等待搜索结果页稳定...")
659
+ try:
660
+ page.goto(url, wait_until="load", timeout=60000)
661
+ except Exception:
662
+ pass
663
+ page.wait_for_load_state("domcontentloaded", timeout=30000)
664
+ page.wait_for_timeout(2500)
665
+ if is_login_page(page):
666
+ return []
667
+ current = page.url.lower()
668
+ on_search = "dosearchsite" in current or "search" in current
669
+ if on_search or attempt == 1:
670
+ break
671
+ print(" 未停留在搜索结果页,重试一次...")
637
672
  return collect_search_candidates(page, base_url, prefix, version, keyword)
638
673
 
639
674
 
640
675
  def run_search_quick(page, base_url: str, prefix: str, version: str, keyword: str) -> list[tuple[str, str, int]]:
676
+ """顶栏快速搜索(dosearchsite 无结果时的兜底,非主路径)。"""
641
677
  search_selector = None
642
678
  for selector in [
643
679
  "#quick-search-query",
@@ -653,10 +689,10 @@ def run_search_quick(page, base_url: str, prefix: str, version: str, keyword: st
653
689
  break
654
690
  if not search_selector:
655
691
  return []
656
- print(f" → 顶栏搜索框: {search_selector} 关键词: {keyword}")
692
+ print(f" → 顶栏搜索框(兜底): {search_selector} 关键词: {keyword}")
657
693
  page.fill(search_selector, keyword, timeout=8000)
658
694
  page.keyboard.press("Enter")
659
- page.wait_for_load_state("networkidle", timeout=30000)
695
+ page.wait_for_load_state("domcontentloaded", timeout=30000)
660
696
  page.wait_for_timeout(2500)
661
697
  return collect_search_candidates(page, base_url, prefix, version, keyword)
662
698
 
@@ -60,9 +60,15 @@ python3 confluence-doc.py {{PRODUCT_PREFIX}}_V1.2.3
60
60
  4. `{{PRODUCT_PREFIX}}-<版本>`(如 ADI-2.3.2)
61
61
  5. `ADI_V<版本>结算`(ADI 长标题页,如 V2.3.4 结算对接欢聚)
62
62
 
63
- 每个关键词都会走 **dosearchsite 全站搜索** + 顶栏搜索;匹配时忽略 `-`/`_` 差异;ADI 页按 `ADI_V*` 标题规则识别(面包屑可无 PRD 字样)。
63
+ 每个关键词优先走 **`dosearchsite.action?queryString=...` 专用搜索页**(比首页 `#all-updates` 顶栏搜索稳定);无结果时再兜底顶栏搜索。匹配时 **标题或 URL 路径** 任一命中即可,并忽略 `-`/`_` 差异;ADI 页按 `ADI_V*` 标题规则识别(面包屑可无 PRD 字样)。
64
64
 
65
- ## 直链(跳过搜索)
65
+ 调试搜索解析:
66
+
67
+ ```bash
68
+ CONFLUENCE_DEBUG=1 python3 confluence-doc.py ADI_V2.3.4
69
+ ```
70
+
71
+ ## 直链(可选,跳过搜索)
66
72
 
67
73
  ```bash
68
74
  CONFLUENCE_PAGE_ID=109609740 python3 confluence-doc.py 2.3.4
@@ -88,6 +94,6 @@ CONFLUENCE_PAGE_URL="https://confluence.huan.tv/spaces/yyzcpt/pages/109609740/..
88
94
  - 脚本:`docs/{{SKILL_DIR}}/confluence-doc.py`
89
95
  - 脚本已内置默认账号密码;如需使用其他账号可用环境变量覆盖
90
96
  - 默认 Confluence 地址为 `https://confluence.huan.tv`(可用 `CONFLUENCE_BASE_URL` 覆盖)
91
- - 默认搜索入口为 `https://confluence.huan.tv/#all-updates`(可用 `CONFLUENCE_SEARCH_URL` 覆盖)
97
+ - 搜索主路径为 `dosearchsite.action?queryString=<关键词>`(全站专用搜索页)
92
98
  - 脚本会自动尝试读取仓库根目录的 `.env.pre` / `.env` / `.env.local` / `.env.development` / `.env.test`(若其中存在 `CONFLUENCE_*` 配置可直接复用)
93
99