xwang 0.0.7 → 0.0.9
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.
|
@@ -13,6 +13,35 @@ red() { echo -e "\033[31m$1\033[0m" >&2; }
|
|
|
13
13
|
green() { echo -e "\033[32m$1\033[0m" >&2; }
|
|
14
14
|
warn() { echo -e "\033[33m$1\033[0m" >&2; }
|
|
15
15
|
|
|
16
|
+
# --- Project root resolution ---
|
|
17
|
+
|
|
18
|
+
find_project_root() {
|
|
19
|
+
if [ -n "${XWANG_PROJECT_ROOT:-}" ]; then
|
|
20
|
+
printf '%s' "$XWANG_PROJECT_ROOT"
|
|
21
|
+
return 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
local git_root
|
|
25
|
+
git_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
26
|
+
if [ -n "$git_root" ]; then
|
|
27
|
+
printf '%s' "$git_root"
|
|
28
|
+
return 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
local dir="$PWD"
|
|
32
|
+
while [ "$dir" != "/" ]; do
|
|
33
|
+
if [ -d "$dir/openspec/changes" ]; then
|
|
34
|
+
printf '%s' "$dir"
|
|
35
|
+
return 0
|
|
36
|
+
fi
|
|
37
|
+
dir=$(dirname "$dir")
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
printf '%s' "$PWD"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
PROJECT_ROOT=$(find_project_root)
|
|
44
|
+
|
|
16
45
|
# --- Input validation ---
|
|
17
46
|
|
|
18
47
|
validate_change_name() {
|
|
@@ -21,15 +50,19 @@ validate_change_name() {
|
|
|
21
50
|
red "ERROR: Change name cannot be empty" >&2
|
|
22
51
|
exit 1
|
|
23
52
|
fi
|
|
24
|
-
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
25
|
-
red "ERROR: Invalid change name: '$name'" >&2
|
|
26
|
-
red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
|
|
27
|
-
exit 1
|
|
28
|
-
fi
|
|
29
53
|
if [[ "$name" =~ \.\. ]]; then
|
|
30
54
|
red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
|
|
31
55
|
exit 1
|
|
32
56
|
fi
|
|
57
|
+
# Reject path separators, whitespace, control chars, and shell metacharacters.
|
|
58
|
+
# CJK characters and other Unicode letters are allowed.
|
|
59
|
+
if printf '%s' "$name" | perl -ne 'exit 1 if m{[/\\\s\0-\x1F\x7F;|&$`()<>{}[\]*'"'"'"!#@%^=+~]}; exit 0'; then
|
|
60
|
+
: # valid
|
|
61
|
+
else
|
|
62
|
+
red "ERROR: Invalid change name: '$name'" >&2
|
|
63
|
+
red "Avoid: / \\ spaces control chars and shell metacharacters" >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
33
66
|
}
|
|
34
67
|
|
|
35
68
|
if [ "${XWANG_GUARD_SOURCE_ONLY:-0}" = "1" ]; then
|
|
@@ -48,9 +81,16 @@ else
|
|
|
48
81
|
if [[ "${3:-}" == "--apply" ]]; then
|
|
49
82
|
APPLY=1
|
|
50
83
|
fi
|
|
51
|
-
CHANGE_DIR="openspec/changes/$CHANGE"
|
|
52
|
-
if [ "$PHASE" = "archive" ] && [ ! -d "$CHANGE_DIR" ]
|
|
53
|
-
|
|
84
|
+
CHANGE_DIR="$PROJECT_ROOT/openspec/changes/$CHANGE"
|
|
85
|
+
if [ "$PHASE" = "archive" ] && [ ! -d "$CHANGE_DIR" ]; then
|
|
86
|
+
if [ -d "$PROJECT_ROOT/openspec/changes/archive/$CHANGE" ]; then
|
|
87
|
+
CHANGE_DIR="$PROJECT_ROOT/openspec/changes/archive/$CHANGE"
|
|
88
|
+
elif [ -d "$PROJECT_ROOT/openspec/changes/archive" ]; then
|
|
89
|
+
archived=$(find "$PROJECT_ROOT/openspec/changes/archive" -maxdepth 1 -type d -name "*-$CHANGE" -print -quit 2>/dev/null || true)
|
|
90
|
+
if [ -n "$archived" ]; then
|
|
91
|
+
CHANGE_DIR="$archived"
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
54
94
|
fi
|
|
55
95
|
fi
|
|
56
96
|
|
|
@@ -306,7 +346,7 @@ branch_status_handled() {
|
|
|
306
346
|
design_doc_recorded() {
|
|
307
347
|
local design_doc
|
|
308
348
|
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
|
|
309
|
-
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then
|
|
349
|
+
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && { [ -f "$PROJECT_ROOT/$design_doc" ] || [ -f "$design_doc" ]; }; then
|
|
310
350
|
return 0
|
|
311
351
|
fi
|
|
312
352
|
echo "design_doc must point to an existing PRD for full workflow before leaving design." >&2
|
|
@@ -410,6 +450,7 @@ guard_design() {
|
|
|
410
450
|
check "design.md exists and non-empty" file_nonempty "$CHANGE_DIR/design.md"
|
|
411
451
|
check "tasks.md exists and non-empty" file_nonempty "$CHANGE_DIR/tasks.md"
|
|
412
452
|
check "tasks.md has at least one task" tasks_has_any
|
|
453
|
+
check "OpenSpec artifacts language consistency" openspec_artifacts_language_consistent
|
|
413
454
|
|
|
414
455
|
if [ "$workflow" = "full" ]; then
|
|
415
456
|
check "design_doc is recorded for full workflow" design_doc_recorded
|
|
@@ -418,7 +459,10 @@ guard_design() {
|
|
|
418
459
|
local design_doc
|
|
419
460
|
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
|
|
420
461
|
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
|
|
421
|
-
|
|
462
|
+
local doc_path="$design_doc"
|
|
463
|
+
[ -f "$PROJECT_ROOT/$design_doc" ] && doc_path="$PROJECT_ROOT/$design_doc"
|
|
464
|
+
check "PRD file ($design_doc) exists" file_nonempty "$doc_path"
|
|
465
|
+
check "PRD language consistency" design_doc_language_consistent
|
|
422
466
|
elif [ "$workflow" != "full" ]; then
|
|
423
467
|
warn " [WARN] No design_doc recorded in .xwang.yaml (optional for quick/tweak)"
|
|
424
468
|
fi
|
|
@@ -454,6 +498,13 @@ guard_archive() {
|
|
|
454
498
|
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
455
499
|
check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
|
|
456
500
|
check "tasks.md all tasks checked" tasks_all_done
|
|
501
|
+
check "OpenSpec artifacts language consistency" openspec_artifacts_language_consistent
|
|
502
|
+
|
|
503
|
+
local design_doc
|
|
504
|
+
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
|
|
505
|
+
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
|
|
506
|
+
check "PRD language consistency" design_doc_language_consistent
|
|
507
|
+
fi
|
|
457
508
|
}
|
|
458
509
|
|
|
459
510
|
locate_state_script() {
|
|
@@ -470,6 +521,66 @@ locate_state_script() {
|
|
|
470
521
|
return 1
|
|
471
522
|
}
|
|
472
523
|
|
|
524
|
+
locate_doc_lang_check() {
|
|
525
|
+
local check_sh="$SCRIPT_DIR/xwang-doc-lang-check.sh"
|
|
526
|
+
if [ -f "$check_sh" ]; then
|
|
527
|
+
echo "$check_sh"
|
|
528
|
+
return 0
|
|
529
|
+
fi
|
|
530
|
+
check_sh="$(find "$SCRIPT_DIR" "$SCRIPT_DIR/.." "$HOME" -path '*/xwang/scripts/xwang-doc-lang-check.sh' -type f -print -quit 2>/dev/null || true)"
|
|
531
|
+
if [ -n "$check_sh" ] && [ -f "$check_sh" ]; then
|
|
532
|
+
echo "$check_sh"
|
|
533
|
+
return 0
|
|
534
|
+
fi
|
|
535
|
+
return 1
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
design_doc_language_consistent() {
|
|
539
|
+
local design_doc
|
|
540
|
+
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
|
|
541
|
+
if [ -z "$design_doc" ] || [ "$design_doc" = "null" ]; then
|
|
542
|
+
return 0
|
|
543
|
+
fi
|
|
544
|
+
|
|
545
|
+
local target=""
|
|
546
|
+
if [ -f "$PROJECT_ROOT/$design_doc" ]; then
|
|
547
|
+
target="$PROJECT_ROOT/$design_doc"
|
|
548
|
+
elif [ -f "$design_doc" ]; then
|
|
549
|
+
target="$design_doc"
|
|
550
|
+
elif [ -f "$CHANGE_DIR/design.md" ]; then
|
|
551
|
+
# design_doc may be stale after archiving; fall back to design.md in the change dir
|
|
552
|
+
target="$CHANGE_DIR/design.md"
|
|
553
|
+
fi
|
|
554
|
+
|
|
555
|
+
if [ -z "$target" ]; then
|
|
556
|
+
echo "design_doc file not found: $design_doc" >&2
|
|
557
|
+
return 1
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
local check_sh
|
|
561
|
+
if ! check_sh=$(locate_doc_lang_check); then
|
|
562
|
+
echo "xwang-doc-lang-check.sh not found; skipping language check" >&2
|
|
563
|
+
return 0
|
|
564
|
+
fi
|
|
565
|
+
"$XWANG_BASH" "$check_sh" "$target" >/dev/null 2>&1
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
openspec_artifacts_language_consistent() {
|
|
569
|
+
local check_sh
|
|
570
|
+
if ! check_sh=$(locate_doc_lang_check); then
|
|
571
|
+
echo "xwang-doc-lang-check.sh not found; skipping language check" >&2
|
|
572
|
+
return 0
|
|
573
|
+
fi
|
|
574
|
+
local files=()
|
|
575
|
+
for f in "$CHANGE_DIR/proposal.md" "$CHANGE_DIR/design.md" "$CHANGE_DIR/tasks.md"; do
|
|
576
|
+
[ -f "$f" ] && files+=("$f")
|
|
577
|
+
done
|
|
578
|
+
if [ ${#files[@]} -eq 0 ]; then
|
|
579
|
+
return 0
|
|
580
|
+
fi
|
|
581
|
+
"$XWANG_BASH" "$check_sh" "${files[@]}" >/dev/null 2>&1
|
|
582
|
+
}
|
|
583
|
+
|
|
473
584
|
apply_state_update() {
|
|
474
585
|
local state_sh
|
|
475
586
|
if ! state_sh=$(locate_state_script); then
|
|
@@ -62,15 +62,19 @@ validate_change_name() {
|
|
|
62
62
|
red "ERROR: Change name cannot be empty" >&2
|
|
63
63
|
exit 1
|
|
64
64
|
fi
|
|
65
|
-
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
66
|
-
red "ERROR: Invalid change name: '$name'" >&2
|
|
67
|
-
red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
|
|
68
|
-
exit 1
|
|
69
|
-
fi
|
|
70
65
|
if [[ "$name" =~ \.\. ]]; then
|
|
71
66
|
red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
|
|
72
67
|
exit 1
|
|
73
68
|
fi
|
|
69
|
+
# Reject path separators, whitespace, control chars, and shell metacharacters.
|
|
70
|
+
# CJK characters and other Unicode letters are allowed.
|
|
71
|
+
if printf '%s' "$name" | perl -ne 'exit 1 if m{[/\\\s\0-\x1F\x7F;|&$`()<>{}[\]*'"'"'"!#@%^=+~]}; exit 0'; then
|
|
72
|
+
: # valid
|
|
73
|
+
else
|
|
74
|
+
red "ERROR: Invalid change name: '$name'" >&2
|
|
75
|
+
red "Avoid: / \\ spaces control chars and shell metacharacters" >&2
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
validate_enum() {
|
|
@@ -77,6 +77,7 @@ fi
|
|
|
77
77
|
- [ ] 调用 `/openspec-archive-change <name>`(或等价的 OpenSpec archive 命令)按 delta 语义合并主 spec 并移动 change 到归档目录。
|
|
78
78
|
- [ ] 如归档命令返回非零退出码,报告错误并停止。
|
|
79
79
|
- [ ] 归档成功后,确认 change 目录已移动到 `openspec/changes/archive/YYYY-MM-DD-<name>/`(或 OpenSpec 默认归档路径)。
|
|
80
|
+
- [ ] **同步 `.xwang.yaml` 中的文件路径**:如果 `design_doc`、`plan`、`verification_report` 等字段指向的是 `openspec/changes/<name>/` 下的旧路径,更新为归档目录 `openspec/changes/archive/YYYY-MM-DD-<name>/` 下的相对路径,避免后续 guard 或读取时路径失效。
|
|
80
81
|
- [ ] 向用户汇报归档完成、新目录路径与生命周期闭环状态。
|
|
81
82
|
|
|
82
83
|
## 退出条件
|
|
@@ -11,10 +11,10 @@ description: 启动 xwang 项目阶段2:深度设计,明确需求并同步
|
|
|
11
11
|
|
|
12
12
|
1. 调用 `/grill-with-docs` 围绕需求目标、范围、用户、约束、验收标准等逐层追问,同时对照现有领域模型和文档(CONTEXT.md、ADRs)挑战术语、澄清冲突,直到达成共识。
|
|
13
13
|
2. **强制进行 PRD 拆分预检(阻塞点)**:`/grill-with-docs` 确认需求完成后,**必须**输出一份 `PRD 拆分预检报告`,基于已澄清信息评估是否需要拆分为多个 change。**无论是否需要拆分,都必须停下来向用户展示报告并等待明确选择**;未确认前不得生成 PRD 或创建 OpenSpec change。
|
|
14
|
-
3. grilling
|
|
14
|
+
3. grilling 结束后,根据需求内容自己总结一个简洁的中文需求名称 `<name>`,用于生成 `docs/mattpocock/<name>-prd.md`。
|
|
15
15
|
4. 创建 `docs/mattpocock/` 目录(如不存在)。
|
|
16
16
|
5. **需求澄清完成确认(阻塞点)**:向用户输出一份结构化的需求摘要(目标、范围、用户、核心行为、边界情况、非功能性需求、依赖与限制),等待用户明确确认或提出修改;未确认前不得生成 PRD 或创建 OpenSpec change。
|
|
17
|
-
6. 用户确认后,调用 `/to-prd`
|
|
17
|
+
6. 用户确认后,调用 `/to-prd` 基于对话上下文合成 PRD。
|
|
18
18
|
7. 将生成的 PRD 内容写入 `docs/mattpocock/<name>-prd.md`。
|
|
19
19
|
8. 直接调用 `/openspec-new-change`:以 `<name>` 作为 change 名称,将 PRD 的中文内容作为 change 文档/上下文创建 change,过程中无需用户确认;若 `/openspec-new-change` 流程中出现任何确认提示(如“是否继续创建 proposal.md?”),直接代为确认并继续,不得将确认问题抛给用户;**要求 `/openspec-new-change` 生成的所有文档/artifact 必须使用中文表述,包括但不限于 `proposal.md`、`design.md`、`tasks.md` 以及 `specs/*/spec.md`**。
|
|
20
20
|
9. **初始化 xwang 状态**:调用 `"$XWANG_STATE" init <name> full`,在 `openspec/changes/<name>/.xwang.yaml` 创建状态文件(起始 phase 为 `design`)。
|
|
@@ -97,7 +97,7 @@ grilling 确认完成后、生成 PRD 前,**必须**执行 PRD 拆分预检并
|
|
|
97
97
|
|
|
98
98
|
### Step 1b:需求澄清完成确认(阻塞点)
|
|
99
99
|
|
|
100
|
-
- [ ] grilling
|
|
100
|
+
- [ ] grilling 结束后,根据需求核心内容自己生成一个简洁的中文名称(如 `会议周视图 UI 增强`)作为 `<name>`,直接使用无需确认。
|
|
101
101
|
- [ ] 使用 `mkdir -p docs/mattpocock` 确保目录存在。
|
|
102
102
|
- [ ] 向用户输出结构化的**需求确认摘要**(建议包含:目标、范围、目标用户、核心行为、边界情况、非功能性需求、依赖与限制)。
|
|
103
103
|
- [ ] **必须使用当前平台可用的用户输入/确认机制暂停并等待用户确认需求澄清完成**。若用户提出修改,根据反馈调整摘要并再次确认,直到达成共识。
|