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" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
53
- CHANGE_DIR="openspec/changes/archive/$CHANGE"
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
- check "PRD file ($design_doc) exists" file_nonempty "$design_doc"
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 结束后,根据需求内容自己总结一个简洁的 kebab-case 需求名称 `<name>`,用于生成 `docs/mattpocock/<name>-prd.md`。
14
+ 3. grilling 结束后,根据需求内容自己总结一个简洁的中文需求名称 `<name>`,用于生成 `docs/mattpocock/<name>-prd.md`。
15
15
  4. 创建 `docs/mattpocock/` 目录(如不存在)。
16
16
  5. **需求澄清完成确认(阻塞点)**:向用户输出一份结构化的需求摘要(目标、范围、用户、核心行为、边界情况、非功能性需求、依赖与限制),等待用户明确确认或提出修改;未确认前不得生成 PRD 或创建 OpenSpec change。
17
- 6. 用户确认后,调用 `/to-prd` 基于对话上下文合成**中文** 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 结束后,根据需求核心内容自己生成一个简洁的 kebab-case 名称(如 `Auth Flow` → `auth-flow`)作为 `<name>`,直接使用无需确认。
100
+ - [ ] grilling 结束后,根据需求核心内容自己生成一个简洁的中文名称(如 `会议周视图 UI 增强`)作为 `<name>`,直接使用无需确认。
101
101
  - [ ] 使用 `mkdir -p docs/mattpocock` 确保目录存在。
102
102
  - [ ] 向用户输出结构化的**需求确认摘要**(建议包含:目标、范围、目标用户、核心行为、边界情况、非功能性需求、依赖与限制)。
103
103
  - [ ] **必须使用当前平台可用的用户输入/确认机制暂停并等待用户确认需求澄清完成**。若用户提出修改,根据反馈调整摘要并再次确认,直到达成共识。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xwang",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "xwang CLI",
5
5
  "keywords": [
6
6
  "xwang",