Coverage for src / moai_adk / cli / spec_status.py: 0.00%

100 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-20 20:52 +0900

1#!/usr/bin/env python3 

2"""Spec Status Manager CLI Tool 

3 

4Provides command-line interface for managing SPEC status tracking. 

5 

6Usage: 

7 python3 -m moai_adk.cli.spec_status <command> <spec_id> [options] 

8 moai-spec <command> <spec_id> [options] 

9 

10Commands: 

11 - status_update: Update SPEC status 

12 - validate_completion: Validate if SPEC is ready for completion 

13 - batch_update: Update all completed SPECs 

14""" 

15 

16import argparse 

17import json 

18import sys 

19from datetime import datetime 

20from pathlib import Path 

21from typing import Any, Dict 

22 

23try: 

24 from moai_adk.core.spec_status_manager import SpecStatusManager 

25except ImportError: 

26 # Fallback: look in current directory 

27 import sys 

28 from pathlib import Path 

29 sys.path.insert(0, str(Path(__file__).parent.parent)) 

30 try: 

31 from core.spec_status_manager import SpecStatusManager 

32 except ImportError: 

33 raise ImportError("SpecStatusManager not found") 

34 

35 

36def update_spec_status(spec_id: str, new_status: str, reason: str = "") -> Dict[str, Any]: 

37 """Update SPEC status with validation and logging 

38 

39 Args: 

40 spec_id: The SPEC identifier 

41 new_status: New status value 

42 reason: Reason for status change 

43 

44 Returns: 

45 Update result dictionary 

46 """ 

47 try: 

48 # Initialize manager 

49 project_root = Path.cwd() 

50 manager = SpecStatusManager(project_root) 

51 

52 # Validate new status 

53 valid_statuses = ['draft', 'in-progress', 'completed', 'archived'] 

54 if new_status not in valid_statuses: 

55 return { 

56 "success": False, 

57 "error": f"Invalid status: {new_status}. Valid statuses: {valid_statuses}" 

58 } 

59 

60 # Check if SPEC exists 

61 spec_file = project_root / ".moai" / "specs" / spec_id / "spec.md" 

62 if not spec_file.exists(): 

63 return { 

64 "success": False, 

65 "error": f"SPEC file not found: {spec_file}" 

66 } 

67 

68 # Update status 

69 success = manager.update_spec_status(spec_id, new_status) 

70 

71 if success: 

72 # Log the status change 

73 log_entry = { 

74 "timestamp": datetime.now().isoformat(), 

75 "spec_id": spec_id, 

76 "old_status": "unknown", # Could be extracted from file before update 

77 "new_status": new_status, 

78 "reason": reason 

79 } 

80 

81 # Create status change log 

82 log_dir = project_root / ".moai" / "logs" 

83 log_dir.mkdir(parents=True, exist_ok=True) 

84 log_file = log_dir / "spec_status_changes.jsonl" 

85 

86 with open(log_file, 'a', encoding='utf-8') as f: 

87 f.write(json.dumps(log_entry, ensure_ascii=False) + '\n') 

88 

89 return { 

90 "success": True, 

91 "spec_id": spec_id, 

92 "new_status": new_status, 

93 "reason": reason, 

94 "timestamp": log_entry["timestamp"] 

95 } 

96 else: 

97 return { 

98 "success": False, 

99 "error": f"Failed to update SPEC {spec_id} status" 

100 } 

101 

102 except Exception as e: 

103 return { 

104 "success": False, 

105 "error": f"Error updating SPEC status: {str(e)}" 

106 } 

107 

108 

109def validate_spec_completion(spec_id: str) -> Dict[str, Any]: 

110 """Validate if SPEC is ready for completion 

111 

112 Args: 

113 spec_id: The SPEC identifier 

114 

115 Returns: 

116 Validation result dictionary 

117 """ 

118 try: 

119 project_root = Path.cwd() 

120 manager = SpecStatusManager(project_root) 

121 

122 # Run validation 

123 validation = manager.validate_spec_for_completion(spec_id) 

124 

125 return { 

126 "success": True, 

127 "spec_id": spec_id, 

128 "validation": validation 

129 } 

130 

131 except Exception as e: 

132 return { 

133 "success": False, 

134 "error": f"Error validating SPEC completion: {str(e)}" 

135 } 

136 

137 

138def batch_update_completed_specs() -> Dict[str, Any]: 

139 """Batch update all draft SPECs that have completed implementations 

140 

141 Returns: 

142 Batch update result dictionary 

143 """ 

144 try: 

145 project_root = Path.cwd() 

146 manager = SpecStatusManager(project_root) 

147 

148 # Run batch update 

149 results = manager.batch_update_completed_specs() 

150 

151 # Log the batch update 

152 log_entry = { 

153 "timestamp": datetime.now().isoformat(), 

154 "operation": "batch_update_completed", 

155 "results": results 

156 } 

157 

158 log_dir = project_root / ".moai" / "logs" 

159 log_dir.mkdir(parents=True, exist_ok=True) 

160 log_file = log_dir / "spec_status_changes.jsonl" 

161 

162 with open(log_file, 'a', encoding='utf-8') as f: 

163 f.write(json.dumps(log_entry, ensure_ascii=False) + '\n') 

164 

165 return { 

166 "success": True, 

167 "results": results, 

168 "timestamp": log_entry["timestamp"] 

169 } 

170 

171 except Exception as e: 

172 return { 

173 "success": False, 

174 "error": f"Error in batch update: {str(e)}" 

175 } 

176 

177 

178def detect_draft_specs() -> Dict[str, Any]: 

179 """Detect all draft SPECs in the project 

180 

181 Returns: 

182 List of draft SPEC IDs 

183 """ 

184 try: 

185 project_root = Path.cwd() 

186 manager = SpecStatusManager(project_root) 

187 

188 draft_specs = manager.detect_draft_specs() 

189 

190 return { 

191 "success": True, 

192 "draft_specs": list(draft_specs), 

193 "count": len(draft_specs) 

194 } 

195 

196 except Exception as e: 

197 return { 

198 "success": False, 

199 "error": f"Error detecting draft SPECs: {str(e)}" 

200 } 

201 

202 

203def main(): 

204 """Main function""" 

205 parser = argparse.ArgumentParser(description='Spec Status Manager Hooks') 

206 parser.add_argument('command', choices=[ 

207 'status_update', 'validate_completion', 'batch_update', 'detect_drafts' 

208 ], help='Command to execute') 

209 parser.add_argument('spec_id', nargs='?', help='SPEC ID (for specific commands)') 

210 parser.add_argument('--status', help='New status value') 

211 parser.add_argument('--reason', default='', help='Reason for status change') 

212 

213 args = parser.parse_args() 

214 

215 try: 

216 result = {} 

217 

218 if args.command == 'status_update': 

219 if not args.spec_id or not args.status: 

220 print(json.dumps({ 

221 "success": False, 

222 "error": "status_update requires spec_id and --status" 

223 })) 

224 sys.exit(1) 

225 

226 result = update_spec_status(args.spec_id, args.status, args.reason) 

227 

228 elif args.command == 'validate_completion': 

229 if not args.spec_id: 

230 print(json.dumps({ 

231 "success": False, 

232 "error": "validate_completion requires spec_id" 

233 })) 

234 sys.exit(1) 

235 

236 result = validate_spec_completion(args.spec_id) 

237 

238 elif args.command == 'batch_update': 

239 result = batch_update_completed_specs() 

240 

241 elif args.command == 'detect_drafts': 

242 result = detect_draft_specs() 

243 

244 # Add command info 

245 result["command"] = args.command 

246 result["execution_time"] = datetime.now().isoformat() 

247 

248 print(json.dumps(result, ensure_ascii=False, indent=2)) 

249 

250 except Exception as e: 

251 error_result = { 

252 "success": False, 

253 "command": args.command if 'args' in locals() else 'unknown', 

254 "error": str(e), 

255 "execution_time": datetime.now().isoformat() 

256 } 

257 

258 print(json.dumps(error_result, ensure_ascii=False, indent=2)) 

259 sys.exit(1) 

260 

261 

262if __name__ == "__main__": 

263 main()