zco-claude 0.0.8__py3-none-any.whl
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.
- ClaudeSettings/DOT.claudeignore +7 -0
- ClaudeSettings/README.md +100 -0
- ClaudeSettings/commands/generate_changelog.sh +49 -0
- ClaudeSettings/commands/show_env +92 -0
- ClaudeSettings/commands/zco-clean +164 -0
- ClaudeSettings/commands/zco-git-summary +15 -0
- ClaudeSettings/commands/zco-git-tag +42 -0
- ClaudeSettings/hooks/CHANGELOG.md +157 -0
- ClaudeSettings/hooks/README.md +254 -0
- ClaudeSettings/hooks/save_chat_plain.py +148 -0
- ClaudeSettings/hooks/save_chat_spec.py +398 -0
- ClaudeSettings/rules/README.md +270 -0
- ClaudeSettings/rules/go/.golangci.yml.template +170 -0
- ClaudeSettings/rules/go/GoBuildAutoVersion.v250425.md +95 -0
- ClaudeSettings/rules/go/check-standards.sh +128 -0
- ClaudeSettings/rules/go/coding-standards.md +973 -0
- ClaudeSettings/rules/go/example.go +207 -0
- ClaudeSettings/rules/go/go-testing.md +691 -0
- ClaudeSettings/rules/go/list-comments.sh +85 -0
- ClaudeSettings/settings.sample.json +71 -0
- ClaudeSettings/skills/README.md +225 -0
- ClaudeSettings/skills/zco-docs-update/SKILL.md +381 -0
- ClaudeSettings/skills/zco-help/SKILL.md +601 -0
- ClaudeSettings/skills/zco-plan/SKILL.md +661 -0
- ClaudeSettings/skills/zco-plan-new/SKILL.md +585 -0
- ClaudeSettings/zco-scripts/co-docs-update.sh +150 -0
- ClaudeSettings/zco-scripts/test_update_plan_metadata.py +328 -0
- ClaudeSettings/zco-scripts/update-plan-metadata.py +324 -0
- zco_claude-0.0.8.dist-info/METADATA +190 -0
- zco_claude-0.0.8.dist-info/RECORD +34 -0
- zco_claude-0.0.8.dist-info/WHEEL +5 -0
- zco_claude-0.0.8.dist-info/entry_points.txt +3 -0
- zco_claude-0.0.8.dist-info/top_level.txt +1 -0
- zco_claude_init.py +1732 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# co-docs-update.sh - 自动更新 CLAUDE.md 文档元信息
|
|
5
|
+
#
|
|
6
|
+
# 用途:
|
|
7
|
+
# - 更新 Git Commit ID、Branch、最新提交信息
|
|
8
|
+
# - 更新文档修改时间
|
|
9
|
+
# - 保持文档与代码同步
|
|
10
|
+
#
|
|
11
|
+
# 使用方法:
|
|
12
|
+
# bash scripts/co-docs-update.sh
|
|
13
|
+
#
|
|
14
|
+
# 作者:Claude Code
|
|
15
|
+
# 日期:2026-01-07
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
set -e # 遇到错误立即退出
|
|
19
|
+
|
|
20
|
+
# 颜色定义
|
|
21
|
+
RED='\033[0;31m'
|
|
22
|
+
GREEN='\033[0;32m'
|
|
23
|
+
YELLOW='\033[1;33m'
|
|
24
|
+
BLUE='\033[0;34m'
|
|
25
|
+
NC='\033[0m' # No Color
|
|
26
|
+
|
|
27
|
+
# 日志函数
|
|
28
|
+
log_info() {
|
|
29
|
+
echo -e "${BLUE}[INFO]${NC} $1"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
log_success() {
|
|
33
|
+
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log_warn() {
|
|
37
|
+
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
log_error() {
|
|
41
|
+
echo -e "${RED}[ERROR]${NC} $1"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# 获取项目根目录
|
|
45
|
+
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
46
|
+
if [ -z "$PROJECT_ROOT" ]; then
|
|
47
|
+
log_error "未找到 Git 仓库,请在 Git 项目中运行此脚本"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
CLAUDE_MD="$PROJECT_ROOT/CLAUDE.md"
|
|
52
|
+
|
|
53
|
+
log_info "项目根目录: $PROJECT_ROOT"
|
|
54
|
+
log_info "CLAUDE.md 路径: $CLAUDE_MD"
|
|
55
|
+
|
|
56
|
+
# 检查 CLAUDE.md 是否存在
|
|
57
|
+
if [ ! -f "$CLAUDE_MD" ]; then
|
|
58
|
+
log_error "CLAUDE.md 文件不存在: $CLAUDE_MD"
|
|
59
|
+
log_info "请先创建 CLAUDE.md 文件"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
## 创建备份
|
|
64
|
+
#BACKUP_FILE="${CLAUDE_MD}._.$(date +%y%m%d_%H%M).md"
|
|
65
|
+
BACKUP_FILE="$PROJECT_ROOT/CLAUDE._.v$(date +%y%m%d_%H%M).md"
|
|
66
|
+
cp "$CLAUDE_MD" "$BACKUP_FILE"
|
|
67
|
+
log_info "已创建备份: $BACKUP_FILE"
|
|
68
|
+
|
|
69
|
+
# 收集 Git 信息
|
|
70
|
+
log_info "收集 Git 信息..."
|
|
71
|
+
|
|
72
|
+
# 当前时间
|
|
73
|
+
CURRENT_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
|
74
|
+
|
|
75
|
+
# Git Commit 信息
|
|
76
|
+
GIT_COMMIT_FULL=$(git log -1 --pretty=format:"%H" 2>/dev/null || echo "unknown")
|
|
77
|
+
GIT_COMMIT_SHORT=$(git log -1 --pretty=format:"%h" 2>/dev/null || echo "unknown")
|
|
78
|
+
GIT_COMMIT_MSG=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "unknown")
|
|
79
|
+
GIT_AUTHOR=$(git log -1 --pretty=format:"%an" 2>/dev/null || echo "unknown")
|
|
80
|
+
GIT_DATE=$(git log -1 --pretty=format:"%ad" --date=iso 2>/dev/null || echo "unknown")
|
|
81
|
+
|
|
82
|
+
# Git Branch
|
|
83
|
+
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
84
|
+
|
|
85
|
+
# 应用版本(从 main.go 中提取)
|
|
86
|
+
MAIN_GO="$PROJECT_ROOT/main.go"
|
|
87
|
+
APP_VERSION="unknown"
|
|
88
|
+
if [ -f "$MAIN_GO" ]; then
|
|
89
|
+
APP_VERSION=$(grep -E 'const version string = "' "$MAIN_GO" | sed -E 's/.*const version string = "(.*)".*/\1/' || echo "unknown")
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
log_info " - 更新时间: $CURRENT_TIME"
|
|
93
|
+
log_info " - Git Commit: $GIT_COMMIT_SHORT ($GIT_COMMIT_FULL)"
|
|
94
|
+
log_info " - Git Branch: $GIT_BRANCH"
|
|
95
|
+
log_info " - 最新提交: $GIT_COMMIT_MSG"
|
|
96
|
+
log_info " - 提交作者: $GIT_AUTHOR"
|
|
97
|
+
log_info " - 提交时间: $GIT_DATE"
|
|
98
|
+
log_info " - 应用版本: $APP_VERSION"
|
|
99
|
+
|
|
100
|
+
# 使用 sed 更新 CLAUDE.md 中的元信息
|
|
101
|
+
log_info "更新 CLAUDE.md 元信息..."
|
|
102
|
+
|
|
103
|
+
# 临时文件
|
|
104
|
+
TEMP_FILE=$(mktemp)
|
|
105
|
+
|
|
106
|
+
# 更新各个字段
|
|
107
|
+
sed -e "s|^- \*\*更新时间\*\*:.*|- **更新时间**: $CURRENT_TIME|" \
|
|
108
|
+
-e "s|^- \*\*Git Commit\*\*:.*|- **Git Commit**: \`$GIT_COMMIT_SHORT\` ($GIT_COMMIT_FULL)|" \
|
|
109
|
+
-e "s|^- \*\*Git Branch\*\*:.*|- **Git Branch**: \`$GIT_BRANCH\`|" \
|
|
110
|
+
-e "s|^- \*\*最新提交\*\*:.*|- **最新提交**: $GIT_COMMIT_MSG (by $GIT_AUTHOR, $GIT_DATE)|" \
|
|
111
|
+
-e "s|^- \*\*应用版本\*\*:.*|- **应用版本**: $APP_VERSION|" \
|
|
112
|
+
"$CLAUDE_MD" >"$TEMP_FILE"
|
|
113
|
+
|
|
114
|
+
# 更新文档底部的最后更新时间
|
|
115
|
+
sed -i "s|^\*\*最后更新\*\*:.*|\*\*最后更新\*\*: $CURRENT_TIME|" "$TEMP_FILE"
|
|
116
|
+
|
|
117
|
+
# 替换原文件
|
|
118
|
+
mv "$TEMP_FILE" "$CLAUDE_MD"
|
|
119
|
+
|
|
120
|
+
log_success "CLAUDE.md 元信息更新完成!"
|
|
121
|
+
|
|
122
|
+
# 显示差异
|
|
123
|
+
echo ""
|
|
124
|
+
log_info "更新内容对比:"
|
|
125
|
+
echo "----------------------------------------"
|
|
126
|
+
git diff --no-index --color=always "$BACKUP_FILE" "$CLAUDE_MD" | tail -n +5 || true
|
|
127
|
+
echo "----------------------------------------"
|
|
128
|
+
|
|
129
|
+
# 询问是否删除备份
|
|
130
|
+
echo ""
|
|
131
|
+
read -p "$(echo -e ${YELLOW}是否删除备份文件? [y/N]: ${NC})" -n 1 -r
|
|
132
|
+
echo
|
|
133
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
134
|
+
rm -f "$BACKUP_FILE"
|
|
135
|
+
log_success "备份文件已删除"
|
|
136
|
+
else
|
|
137
|
+
log_info "备份文件保留在: $BACKUP_FILE"
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# 显示文档状态
|
|
141
|
+
echo ""
|
|
142
|
+
log_success "✅ 文档更新完成!"
|
|
143
|
+
echo ""
|
|
144
|
+
log_info "下一步操作:"
|
|
145
|
+
log_info " 1. 查看文档: cat CLAUDE.md"
|
|
146
|
+
log_info " 2. 提交更改: git add CLAUDE.md && git commit -m 'docs: update CLAUDE.md metadata'"
|
|
147
|
+
log_info " 3. 验证 Claude Code 加载: claude code"
|
|
148
|
+
echo ""
|
|
149
|
+
|
|
150
|
+
exit 0
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Unit tests for update-plan-metadata.py
|
|
4
|
+
|
|
5
|
+
Tests:
|
|
6
|
+
1. Parse valid YAML front matter
|
|
7
|
+
2. Update status transitions
|
|
8
|
+
3. Set created_at only on first execution
|
|
9
|
+
4. Update updated_at on every execution
|
|
10
|
+
5. Merge tags without overwriting
|
|
11
|
+
6. Handle missing fields gracefully
|
|
12
|
+
7. Handle malformed YAML (error case)
|
|
13
|
+
8. Preserve Markdown body unchanged
|
|
14
|
+
9. Atomic write on success
|
|
15
|
+
10. Backwards compatibility with old format
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import unittest
|
|
19
|
+
import tempfile
|
|
20
|
+
import json
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
import sys
|
|
24
|
+
import subprocess
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
# Add parent dir to path to import the script
|
|
28
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestPlanMetadataUpdater(unittest.TestCase):
|
|
32
|
+
"""Test suite for plan metadata updates"""
|
|
33
|
+
|
|
34
|
+
def setUp(self):
|
|
35
|
+
"""Create temporary test directory"""
|
|
36
|
+
self.test_dir = tempfile.mkdtemp()
|
|
37
|
+
self.test_dir_path = Path(self.test_dir)
|
|
38
|
+
|
|
39
|
+
def tearDown(self):
|
|
40
|
+
"""Clean up temporary files"""
|
|
41
|
+
import shutil
|
|
42
|
+
shutil.rmtree(self.test_dir, ignore_errors=True)
|
|
43
|
+
|
|
44
|
+
def create_test_plan(self, filename: str, content: str) -> Path:
|
|
45
|
+
"""Helper: Create a test plan file"""
|
|
46
|
+
plan_path = self.test_dir_path / filename
|
|
47
|
+
plan_path.write_text(content, encoding='utf-8')
|
|
48
|
+
return plan_path
|
|
49
|
+
|
|
50
|
+
def run_update_script(self, plan_path: Path, action: str, tags: list = None) -> dict:
|
|
51
|
+
"""
|
|
52
|
+
Helper: Run update-plan-metadata.py script
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
plan_path: Path to plan file
|
|
56
|
+
action: Action to perform (start, complete, fail, cancel)
|
|
57
|
+
tags: Optional tags list
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
dict: JSON result from script
|
|
61
|
+
"""
|
|
62
|
+
input_data = {
|
|
63
|
+
'plan_path': str(plan_path),
|
|
64
|
+
'action': action
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if tags is not None:
|
|
68
|
+
input_data['tags'] = tags
|
|
69
|
+
|
|
70
|
+
input_json = json.dumps(input_data)
|
|
71
|
+
|
|
72
|
+
script_path = Path(__file__).parent / 'update-plan-metadata.py'
|
|
73
|
+
|
|
74
|
+
result = subprocess.run(
|
|
75
|
+
['python3', str(script_path)],
|
|
76
|
+
input=input_json,
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if result.returncode == 0:
|
|
82
|
+
return json.loads(result.stdout)
|
|
83
|
+
else:
|
|
84
|
+
return json.loads(result.stderr)
|
|
85
|
+
|
|
86
|
+
def test_01_parse_valid_yaml(self):
|
|
87
|
+
"""Test 1: Parse valid YAML front matter"""
|
|
88
|
+
content = """---
|
|
89
|
+
seq: 001
|
|
90
|
+
title: "Test Plan"
|
|
91
|
+
status: "draft:0"
|
|
92
|
+
priority: "p2:中:可纳入后续迭代计划"
|
|
93
|
+
created_at: ""
|
|
94
|
+
updated_at: ""
|
|
95
|
+
tags: []
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
# Test Plan
|
|
99
|
+
|
|
100
|
+
This is a test.
|
|
101
|
+
"""
|
|
102
|
+
plan_path = self.create_test_plan('plan.001.test.md', content)
|
|
103
|
+
|
|
104
|
+
result = self.run_update_script(plan_path, 'start')
|
|
105
|
+
|
|
106
|
+
self.assertTrue(result['success'])
|
|
107
|
+
self.assertEqual(result['old_status'], 'draft:0')
|
|
108
|
+
self.assertEqual(result['new_status'], 'ongoing:2')
|
|
109
|
+
|
|
110
|
+
def test_02_status_transitions(self):
|
|
111
|
+
"""Test 2: Status transitions work correctly"""
|
|
112
|
+
content = """---
|
|
113
|
+
seq: 002
|
|
114
|
+
title: "Status Test"
|
|
115
|
+
status: "draft:0"
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
# Status Test
|
|
119
|
+
"""
|
|
120
|
+
plan_path = self.create_test_plan('plan.002.test.md', content)
|
|
121
|
+
|
|
122
|
+
# Transition: draft → ongoing
|
|
123
|
+
result = self.run_update_script(plan_path, 'start')
|
|
124
|
+
self.assertEqual(result['new_status'], 'ongoing:2')
|
|
125
|
+
|
|
126
|
+
# Transition: ongoing → completed
|
|
127
|
+
result = self.run_update_script(plan_path, 'complete')
|
|
128
|
+
self.assertEqual(result['old_status'], 'ongoing:2')
|
|
129
|
+
self.assertEqual(result['new_status'], 'completed:3')
|
|
130
|
+
|
|
131
|
+
def test_03_created_at_set_once(self):
|
|
132
|
+
"""Test 3: created_at set only on first execution"""
|
|
133
|
+
content = """---
|
|
134
|
+
seq: 003
|
|
135
|
+
title: "Timestamp Test"
|
|
136
|
+
status: "draft:0"
|
|
137
|
+
created_at: ""
|
|
138
|
+
updated_at: ""
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
# Timestamp Test
|
|
142
|
+
"""
|
|
143
|
+
plan_path = self.create_test_plan('plan.003.test.md', content)
|
|
144
|
+
|
|
145
|
+
# First execution
|
|
146
|
+
result1 = self.run_update_script(plan_path, 'start')
|
|
147
|
+
created_at_1 = result1['created_at']
|
|
148
|
+
updated_at_1 = result1['updated_at']
|
|
149
|
+
|
|
150
|
+
self.assertNotEqual(created_at_1, '')
|
|
151
|
+
self.assertNotEqual(updated_at_1, '')
|
|
152
|
+
|
|
153
|
+
# Wait a moment
|
|
154
|
+
time.sleep(1)
|
|
155
|
+
|
|
156
|
+
# Second execution
|
|
157
|
+
result2 = self.run_update_script(plan_path, 'complete')
|
|
158
|
+
created_at_2 = result2['created_at']
|
|
159
|
+
updated_at_2 = result2['updated_at']
|
|
160
|
+
|
|
161
|
+
# created_at should not change
|
|
162
|
+
self.assertEqual(created_at_1, created_at_2)
|
|
163
|
+
|
|
164
|
+
# updated_at should change
|
|
165
|
+
self.assertNotEqual(updated_at_1, updated_at_2)
|
|
166
|
+
|
|
167
|
+
def test_04_updated_at_always_updates(self):
|
|
168
|
+
"""Test 4: updated_at updates on every execution"""
|
|
169
|
+
content = """---
|
|
170
|
+
seq: 004
|
|
171
|
+
title: "Update Test"
|
|
172
|
+
status: "draft:0"
|
|
173
|
+
created_at: "2026-01-01 10:00:00"
|
|
174
|
+
updated_at: "2026-01-01 10:00:00"
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
# Update Test
|
|
178
|
+
"""
|
|
179
|
+
plan_path = self.create_test_plan('plan.004.test.md', content)
|
|
180
|
+
|
|
181
|
+
time.sleep(1)
|
|
182
|
+
|
|
183
|
+
result = self.run_update_script(plan_path, 'start')
|
|
184
|
+
|
|
185
|
+
# updated_at should be newer than the original
|
|
186
|
+
self.assertNotEqual(result['updated_at'], '2026-01-01 10:00:00')
|
|
187
|
+
|
|
188
|
+
def test_05_merge_tags(self):
|
|
189
|
+
"""Test 5: Merge tags without overwriting other fields"""
|
|
190
|
+
content = """---
|
|
191
|
+
seq: 005
|
|
192
|
+
title: "Tag Test"
|
|
193
|
+
status: "draft:0"
|
|
194
|
+
tags: []
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
# Tag Test
|
|
198
|
+
"""
|
|
199
|
+
plan_path = self.create_test_plan('plan.005.test.md', content)
|
|
200
|
+
|
|
201
|
+
result = self.run_update_script(plan_path, 'start', tags=['feature', 'backend'])
|
|
202
|
+
|
|
203
|
+
self.assertTrue(result['success'])
|
|
204
|
+
self.assertIn('feature', result['tags'])
|
|
205
|
+
self.assertIn('backend', result['tags'])
|
|
206
|
+
|
|
207
|
+
def test_06_handle_missing_fields(self):
|
|
208
|
+
"""Test 6: Handle missing fields gracefully"""
|
|
209
|
+
content = """---
|
|
210
|
+
seq: 006
|
|
211
|
+
title: "Minimal Plan"
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
# Minimal Plan
|
|
215
|
+
"""
|
|
216
|
+
plan_path = self.create_test_plan('plan.006.test.md', content)
|
|
217
|
+
|
|
218
|
+
result = self.run_update_script(plan_path, 'start')
|
|
219
|
+
|
|
220
|
+
self.assertTrue(result['success'])
|
|
221
|
+
self.assertEqual(result['new_status'], 'ongoing:2')
|
|
222
|
+
self.assertNotEqual(result['created_at'], '')
|
|
223
|
+
|
|
224
|
+
def test_07_malformed_yaml_error(self):
|
|
225
|
+
"""Test 7: Handle malformed YAML with clear error"""
|
|
226
|
+
content = """---
|
|
227
|
+
seq: 007
|
|
228
|
+
title: "Missing quote
|
|
229
|
+
status: draft:0
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
# Bad YAML
|
|
233
|
+
"""
|
|
234
|
+
plan_path = self.create_test_plan('plan.007.test.md', content)
|
|
235
|
+
|
|
236
|
+
result = self.run_update_script(plan_path, 'start')
|
|
237
|
+
|
|
238
|
+
self.assertFalse(result['success'])
|
|
239
|
+
self.assertIn('error', result)
|
|
240
|
+
self.assertIn('YAML', result['error'])
|
|
241
|
+
|
|
242
|
+
def test_08_preserve_markdown_body(self):
|
|
243
|
+
"""Test 8: Preserve Markdown body unchanged"""
|
|
244
|
+
original_body = """
|
|
245
|
+
# Test Plan
|
|
246
|
+
|
|
247
|
+
## 🎯 Goal
|
|
248
|
+
Do something important
|
|
249
|
+
|
|
250
|
+
## 📋 Requirements
|
|
251
|
+
- Requirement 1
|
|
252
|
+
- Requirement 2
|
|
253
|
+
|
|
254
|
+
## ✅ Verification
|
|
255
|
+
- [ ] Test 1
|
|
256
|
+
- [ ] Test 2
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
content = f"""---
|
|
260
|
+
seq: 008
|
|
261
|
+
title: "Body Preservation Test"
|
|
262
|
+
status: "draft:0"
|
|
263
|
+
---
|
|
264
|
+
{original_body}"""
|
|
265
|
+
|
|
266
|
+
plan_path = self.create_test_plan('plan.008.test.md', content)
|
|
267
|
+
|
|
268
|
+
result = self.run_update_script(plan_path, 'start')
|
|
269
|
+
|
|
270
|
+
self.assertTrue(result['success'])
|
|
271
|
+
|
|
272
|
+
# Read file and check body
|
|
273
|
+
updated_content = plan_path.read_text(encoding='utf-8')
|
|
274
|
+
self.assertIn('## 🎯 Goal', updated_content)
|
|
275
|
+
self.assertIn('Do something important', updated_content)
|
|
276
|
+
self.assertIn('- Requirement 1', updated_content)
|
|
277
|
+
self.assertIn('- [ ] Test 1', updated_content)
|
|
278
|
+
|
|
279
|
+
def test_09_backwards_compatibility(self):
|
|
280
|
+
"""Test 10: Backwards compatibility with old status format"""
|
|
281
|
+
content = """---
|
|
282
|
+
seq: 010
|
|
283
|
+
title: "Old Format Test"
|
|
284
|
+
status: pending
|
|
285
|
+
priority: medium
|
|
286
|
+
created: 2026-01-01 10:00:00
|
|
287
|
+
updated: 2026-01-01 10:00:00
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
# Old Format Test
|
|
291
|
+
"""
|
|
292
|
+
plan_path = self.create_test_plan('plan.010.test.md', content)
|
|
293
|
+
|
|
294
|
+
result = self.run_update_script(plan_path, 'start')
|
|
295
|
+
|
|
296
|
+
self.assertTrue(result['success'])
|
|
297
|
+
# Old 'pending' should convert to 'draft:0' first, then to 'ongoing:2'
|
|
298
|
+
self.assertEqual(result['new_status'], 'ongoing:2')
|
|
299
|
+
|
|
300
|
+
def test_10_missing_yaml_error(self):
|
|
301
|
+
"""Test: Handle missing YAML front matter"""
|
|
302
|
+
content = """# No YAML Plan
|
|
303
|
+
|
|
304
|
+
This plan has no YAML front matter.
|
|
305
|
+
"""
|
|
306
|
+
plan_path = self.create_test_plan('plan.999.no-yaml.md', content)
|
|
307
|
+
|
|
308
|
+
result = self.run_update_script(plan_path, 'start')
|
|
309
|
+
|
|
310
|
+
self.assertFalse(result['success'])
|
|
311
|
+
self.assertIn('No YAML front matter', result['error'])
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def run_tests():
|
|
315
|
+
"""Run all tests and report results"""
|
|
316
|
+
# Create test suite
|
|
317
|
+
suite = unittest.TestLoader().loadTestsFromTestCase(TestPlanMetadataUpdater)
|
|
318
|
+
|
|
319
|
+
# Run tests with verbose output
|
|
320
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
321
|
+
result = runner.run(suite)
|
|
322
|
+
|
|
323
|
+
# Return exit code
|
|
324
|
+
return 0 if result.wasSuccessful() else 1
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
if __name__ == '__main__':
|
|
328
|
+
sys.exit(run_tests())
|