xwang 0.0.6 → 0.0.7
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/assets/manifest.json
CHANGED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# xwang-doc-lang-check — validate language consistency across xwang-generated documents
|
|
3
|
+
# Usage: xwang-doc-lang-check.sh [file-or-dir ...]
|
|
4
|
+
# Exits 0 if all documents are consistent, 1 if any are mixed.
|
|
5
|
+
# On mixed output, stdout contains a correction prompt in the primary language.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# --- Language detection helpers ---
|
|
10
|
+
|
|
11
|
+
# Count CJK unified ideographs in text
|
|
12
|
+
count_cjk() {
|
|
13
|
+
local text="$1"
|
|
14
|
+
printf '%s' "$text" | perl -CS -ne 'BEGIN { $n=0 } while (/[\x{4e00}-\x{9fff}]/g) { $n++ } END { print $n }'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Count alphabetic ASCII words
|
|
18
|
+
count_words() {
|
|
19
|
+
local text="$1"
|
|
20
|
+
printf '%s' "$text" | grep -oE '[a-zA-Z]+' | wc -l | tr -d ' '
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Detect dominant language: "zh" or "en"
|
|
24
|
+
detect_language() {
|
|
25
|
+
local text="$1"
|
|
26
|
+
local cjk words
|
|
27
|
+
cjk=$(count_cjk "$text")
|
|
28
|
+
words=$(count_words "$text")
|
|
29
|
+
if [ "$cjk" -gt "$words" ]; then
|
|
30
|
+
echo "zh"
|
|
31
|
+
else
|
|
32
|
+
echo "en"
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# --- Markdown helpers ---
|
|
37
|
+
|
|
38
|
+
is_markdown() {
|
|
39
|
+
local file="$1"
|
|
40
|
+
case "$file" in
|
|
41
|
+
*.md|*.mdx) return 0 ;;
|
|
42
|
+
*) return 1 ;;
|
|
43
|
+
esac
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Check a single markdown file for language consistency.
|
|
47
|
+
# Outputs issues to stderr and a correction prompt to stdout when inconsistent.
|
|
48
|
+
check_file() {
|
|
49
|
+
local file="$1"
|
|
50
|
+
local title=""
|
|
51
|
+
local lead=""
|
|
52
|
+
local primary=""
|
|
53
|
+
local section_name=""
|
|
54
|
+
local section_body=""
|
|
55
|
+
local issues=()
|
|
56
|
+
|
|
57
|
+
flush_section() {
|
|
58
|
+
if [ -z "$section_name" ]; then
|
|
59
|
+
return
|
|
60
|
+
fi
|
|
61
|
+
local full_section="${section_name}"$'\n'"${section_body}"
|
|
62
|
+
if [ -z "$primary" ]; then
|
|
63
|
+
lead="${lead}${full_section}"
|
|
64
|
+
primary=$(detect_language "$lead")
|
|
65
|
+
else
|
|
66
|
+
local section_lang
|
|
67
|
+
section_lang=$(detect_language "$full_section")
|
|
68
|
+
if [ "$section_lang" != "$primary" ]; then
|
|
69
|
+
issues+=("section \"$section_name\": $section_lang, expected $primary")
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
section_name=""
|
|
73
|
+
section_body=""
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
77
|
+
if [[ "$line" =~ ^#[[:space:]] ]]; then
|
|
78
|
+
# Level-1 heading: treat as title / lead context
|
|
79
|
+
title="${line#*# }"
|
|
80
|
+
lead="${lead}${line}"$'\n'
|
|
81
|
+
continue
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [[ "$line" =~ ^##[[:space:]] ]]; then
|
|
85
|
+
flush_section
|
|
86
|
+
section_name="${line##*## }"
|
|
87
|
+
section_body=""
|
|
88
|
+
continue
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
if [ -n "$section_name" ]; then
|
|
92
|
+
section_body="${section_body}${line}"$'\n'
|
|
93
|
+
else
|
|
94
|
+
# Content before the first section contributes to the lead
|
|
95
|
+
lead="${lead}${line}"$'\n'
|
|
96
|
+
fi
|
|
97
|
+
done < "$file"
|
|
98
|
+
|
|
99
|
+
flush_section
|
|
100
|
+
|
|
101
|
+
# If no sections at all, treat the whole file as one document
|
|
102
|
+
if [ -z "$primary" ]; then
|
|
103
|
+
primary=$(detect_language "$lead")
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ ${#issues[@]} -eq 0 ]; then
|
|
107
|
+
echo "OK: $file language is consistent ($primary)"
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
echo "MIXED-LANGUAGE: $file (${#issues[@]} issue(s))" >&2
|
|
112
|
+
for issue in "${issues[@]}"; do
|
|
113
|
+
echo " - $issue" >&2
|
|
114
|
+
done
|
|
115
|
+
|
|
116
|
+
if [ "$primary" = "zh" ]; then
|
|
117
|
+
cat <<'EOF'
|
|
118
|
+
上述文档存在中英文混用问题。项目上下文为中文,请将整份文档统一改写为中文,保持所有章节(包括但不限于标题、Problem Statement、Solution、User Stories、Implementation Decisions、Testing Decisions、Out of Scope、Further Notes、Why、What Changes、Capabilities、Impact 等)语言一致。
|
|
119
|
+
EOF
|
|
120
|
+
else
|
|
121
|
+
cat <<'EOF'
|
|
122
|
+
The document above has mixed languages. The project context is English. Please rewrite the entire document in English, keeping all sections (including but not limited to title, Problem Statement, Solution, User Stories, Implementation Decisions, Testing Decisions, Out of Scope, Further Notes, Why, What Changes, Capabilities, Impact, etc.) consistent in language.
|
|
123
|
+
EOF
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
return 1
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# --- Argument collection ---
|
|
130
|
+
|
|
131
|
+
declare -a FILES=()
|
|
132
|
+
|
|
133
|
+
if [ $# -eq 0 ]; then
|
|
134
|
+
echo "Usage: $0 [file-or-dir ...]" >&2
|
|
135
|
+
exit 2
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
for arg in "$@"; do
|
|
139
|
+
if [ -f "$arg" ]; then
|
|
140
|
+
FILES+=("$arg")
|
|
141
|
+
elif [ -d "$arg" ]; then
|
|
142
|
+
while IFS= read -r f; do
|
|
143
|
+
if is_markdown "$f"; then
|
|
144
|
+
FILES+=("$f")
|
|
145
|
+
fi
|
|
146
|
+
done < <(find "$arg" -type f \( -name '*.md' -o -name '*.mdx' \) \
|
|
147
|
+
! -path '*/node_modules/*' \
|
|
148
|
+
! -path '*/.git/*' \
|
|
149
|
+
! -path '*/dist/*' \
|
|
150
|
+
! -path '*/.claude/worktrees/*' \
|
|
151
|
+
2>/dev/null || true)
|
|
152
|
+
else
|
|
153
|
+
echo "WARNING: not found: $arg" >&2
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
|
|
157
|
+
if [ ${#FILES[@]} -eq 0 ]; then
|
|
158
|
+
echo "No markdown documents found to check." >&2
|
|
159
|
+
exit 0
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
mixed=0
|
|
163
|
+
for file in "${FILES[@]}"; do
|
|
164
|
+
if ! check_file "$file"; then
|
|
165
|
+
mixed=$((mixed + 1))
|
|
166
|
+
fi
|
|
167
|
+
done
|
|
168
|
+
|
|
169
|
+
if [ "$mixed" -eq 0 ]; then
|
|
170
|
+
echo "OK: all ${#FILES[@]} document(s) have consistent language"
|
|
171
|
+
exit 0
|
|
172
|
+
else
|
|
173
|
+
echo "" >&2
|
|
174
|
+
echo "Total: $mixed / ${#FILES[@]} document(s) have mixed languages." >&2
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
@@ -65,6 +65,17 @@ if [ -z "${XWANG_GUARD:-}" ]; then
|
|
|
65
65
|
fi
|
|
66
66
|
export XWANG_GUARD
|
|
67
67
|
|
|
68
|
+
# Locate xwang-doc-lang-check.sh using the same approach.
|
|
69
|
+
if [ -z "${XWANG_DOC_LANG_CHECK:-}" ]; then
|
|
70
|
+
XWANG_DOC_LANG_CHECK_SIBLING="${XWANG_SCRIPT_DIR}/xwang-doc-lang-check.sh"
|
|
71
|
+
if [ -f "$XWANG_DOC_LANG_CHECK_SIBLING" ]; then
|
|
72
|
+
XWANG_DOC_LANG_CHECK="$(cd "$(dirname "$XWANG_DOC_LANG_CHECK_SIBLING")" && pwd)/$(basename "$XWANG_DOC_LANG_CHECK_SIBLING")"
|
|
73
|
+
else
|
|
74
|
+
XWANG_DOC_LANG_CHECK="$(locate_by_glob '*/xwang/scripts/xwang-doc-lang-check.sh' "${SEARCH_DIRS[@]}" 2>/dev/null || true)"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
export XWANG_DOC_LANG_CHECK
|
|
78
|
+
|
|
68
79
|
if [ -z "$XWANG_OPEN_CHECK" ] || [ ! -f "$XWANG_OPEN_CHECK" ]; then
|
|
69
80
|
echo "WARNING: xwang-open-check.sh not found" >&2
|
|
70
81
|
fi
|
|
@@ -72,3 +83,7 @@ fi
|
|
|
72
83
|
if [ -z "$XWANG_GUARD" ] || [ ! -f "$XWANG_GUARD" ]; then
|
|
73
84
|
echo "WARNING: xwang-guard.sh not found" >&2
|
|
74
85
|
fi
|
|
86
|
+
|
|
87
|
+
if [ -z "$XWANG_DOC_LANG_CHECK" ] || [ ! -f "$XWANG_DOC_LANG_CHECK" ]; then
|
|
88
|
+
echo "WARNING: xwang-doc-lang-check.sh not found" >&2
|
|
89
|
+
fi
|
|
@@ -58,10 +58,18 @@ fi
|
|
|
58
58
|
- **暂不归档**:不执行归档,保留当前 `phase: archive` 状态,等待用户稍后再次调用 `/xwang-archive`
|
|
59
59
|
- [ ] 只有用户选择「确认归档」后才继续 Step 3。
|
|
60
60
|
|
|
61
|
-
### Step 3
|
|
61
|
+
### Step 3:标记归档状态、校验完整性与 PRD 语言一致性
|
|
62
62
|
|
|
63
63
|
- [ ] 调用 `"$XWANG_STATE" set <name> archived true`,在 change 仍在 `openspec/changes/<name>/` 时把 `.xwang.yaml` 标记为已归档。
|
|
64
64
|
- [ ] **归档完整性守护**:调用 `"$XWANG_GUARD" <name> archive` 校验归档退出条件(`archived: true`、必要文档存在且 tasks 已勾选)。
|
|
65
|
+
- [ ] **PRD 语言一致性守护**:定位当前 change 对应的 PRD 文件(常见路径如 `docs/mattpocock/<name>-prd.md`、`docs/<name>-prd.md` 或 `<name>-prd.md`)。如存在,运行:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
"$XWANG_DOC_LANG_CHECK" <prd-file>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
若脚本报告 `MIXED-LANGUAGE`,将其 stdout 中的修正提示词展示给用户,**停止归档**,等待用户修正 PRD 后重新触发 `/xwang-archive`。只有脚本退出 `0` 才继续下一步。
|
|
72
|
+
|
|
65
73
|
- [ ] 如需,在 PRD 和 plan 文件前置元数据中标注 `archived-with: <name>` 和 `status: archived`。
|
|
66
74
|
|
|
67
75
|
### Step 4:执行归档
|
|
@@ -105,8 +105,18 @@ grilling 确认完成后、生成 PRD 前,**必须**执行 PRD 拆分预检并
|
|
|
105
105
|
|
|
106
106
|
### Step 2:生成 PRD
|
|
107
107
|
|
|
108
|
-
- [ ] 用户确认后,调用 `/to-prd`
|
|
108
|
+
- [ ] 用户确认后,调用 `/to-prd` 生成 PRD(遵循 to-prd 的 PRD 模板),无需额外用户确认。
|
|
109
109
|
- [ ] 将 PRD 写入 `docs/mattpocock/<name>-prd.md`;如果 grilling 过程中名称有调整,使用最终生成的名称。
|
|
110
|
+
- [ ] **PRD 语言一致性守护**:写入后运行语言检测脚本:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
XWANG_ENV="${XWANG_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" -path '*/xwang/scripts/xwang-env.sh' -type f -print -quit 2>/dev/null)}"
|
|
114
|
+
. "$XWANG_ENV"
|
|
115
|
+
"$XWANG_DOC_LANG_CHECK" docs/mattpocock/<name>-prd.md
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
若脚本报告 `MIXED-LANGUAGE`,将其 stdout 中的修正提示词追加到下一次 `/to-prd` 请求中,重新生成并写入 PRD,重复检测直到脚本退出 `0`。
|
|
119
|
+
|
|
110
120
|
- [ ] **如果 Step 1a 结论为「保持为一个 change」,在 PRD 中显式写入“不拆分原因”章节。**
|
|
111
121
|
|
|
112
122
|
### Step 3:创建 OpenSpec change
|