nc1709 1.15.4__py3-none-any.whl → 1.18.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.
nc1709/cli.py CHANGED
@@ -17,7 +17,8 @@ from .remote_client import RemoteClient, is_remote_mode
17
17
  from .cli_ui import (
18
18
  ActionSpinner, Color, Icons,
19
19
  status, thinking, success, error, warning, info,
20
- action_spinner, print_response, format_response
20
+ action_spinner, print_response, format_response,
21
+ log_action, log_output, get_terminal_width
21
22
  )
22
23
 
23
24
  # Default server URL - users connect to this server by default
@@ -96,6 +97,26 @@ try:
96
97
  except ImportError:
97
98
  HAS_COGNITIVE = False
98
99
 
100
+ # Import requirements tracker
101
+ try:
102
+ from .requirements_tracker import (
103
+ RequirementsTracker, RequirementStatus, RequirementPriority,
104
+ get_tracker, reset_tracker
105
+ )
106
+ HAS_REQUIREMENTS = True
107
+ except ImportError:
108
+ HAS_REQUIREMENTS = False
109
+
110
+ # Import conversation logger
111
+ try:
112
+ from .conversation_logger import (
113
+ ConversationLogger, init_logger, get_logger,
114
+ log_user, log_assistant, log_tool, log_error, log_system
115
+ )
116
+ HAS_CONVERSATION_LOGGER = True
117
+ except ImportError:
118
+ HAS_CONVERSATION_LOGGER = False
119
+
99
120
 
100
121
  class NC1709CLI:
101
122
  """Main CLI application"""
@@ -150,9 +171,18 @@ class NC1709CLI:
150
171
  api_key=self.api_key
151
172
  )
152
173
  # Verify connection
153
- status = self.remote_client.check_status()
154
- print(f"🌐 Connected to remote NC1709 server: {self.remote_url}")
155
- print(f" Server version: {status.get('version', 'unknown')}")
174
+ server_status = self.remote_client.check_status()
175
+
176
+ # Get server host for display
177
+ from urllib.parse import urlparse
178
+ parsed = urlparse(self.remote_url)
179
+ server_host = parsed.netloc or self.remote_url
180
+
181
+ # Store tools count for banner
182
+ self._remote_tools_count = server_status.get('tools_count', 17) # Default to 17
183
+
184
+ # Clean connection display (Claude Code style)
185
+ print(f"\n{Color.GREEN}●{Color.RESET} Connected to {Color.BOLD}{server_host}{Color.RESET}")
156
186
 
157
187
  # Set up minimal local components (no LLM needed)
158
188
  self.file_controller = FileController()
@@ -194,7 +224,12 @@ class NC1709CLI:
194
224
 
195
225
  # Create new ID based on machine info
196
226
  import platform
197
- machine_info = f"{platform.node()}-{platform.machine()}-{os.getlogin() if hasattr(os, 'getlogin') else 'user'}"
227
+ import getpass
228
+ try:
229
+ username = getpass.getuser()
230
+ except Exception:
231
+ username = os.environ.get('USER', os.environ.get('USERNAME', 'user'))
232
+ machine_info = f"{platform.node()}-{platform.machine()}-{username}"
198
233
  user_id = hashlib.sha256(machine_info.encode()).hexdigest()[:16]
199
234
 
200
235
  # Save for future sessions
@@ -676,6 +711,12 @@ class NC1709CLI:
676
711
  """
677
712
  self._print_banner()
678
713
 
714
+ # Initialize conversation logger for this session
715
+ if HAS_CONVERSATION_LOGGER:
716
+ mode = "remote" if self.remote_client else "local"
717
+ self._conversation_logger = init_logger(mode=mode)
718
+ log_system("Session started", {"mode": mode, "cwd": str(Path.cwd())})
719
+
679
720
  # Initialize or resume session
680
721
  if self.session_manager:
681
722
  if resume_session:
@@ -688,9 +729,28 @@ class NC1709CLI:
688
729
  self.session_manager.start_session(project_path=str(Path.cwd()))
689
730
  else:
690
731
  self.session_manager.start_session(project_path=str(Path.cwd()))
691
- print(f"📝 Started new session: {self.session_manager.current_session.id}")
692
732
 
693
- print("\nType /help for commands, or just describe what you want.\n")
733
+ # Show tools count and session ID (Claude Code style)
734
+ if self.remote_client:
735
+ tools_count = getattr(self, '_remote_tools_count', 17)
736
+ print(f"{Color.GREEN}●{Color.RESET} {tools_count} tools available")
737
+ else:
738
+ # Count local tools
739
+ tools_count = 0
740
+ if hasattr(self, '_local_registry') and self._local_registry:
741
+ tools_count = len(self._local_registry.list_names())
742
+ print(f"{Color.GREEN}●{Color.RESET} {tools_count} tools available")
743
+
744
+ # Show session ID
745
+ if HAS_CONVERSATION_LOGGER and hasattr(self, '_conversation_logger') and self._conversation_logger:
746
+ session_id = self._conversation_logger.session_id[:16] # Shortened
747
+ print(f"{Color.GREEN}●{Color.RESET} Session: {Color.DIM}{session_id}{Color.RESET}")
748
+
749
+ # Initialize file tracking for this session
750
+ self._session_files_created = []
751
+ self._session_files_modified = []
752
+
753
+ print()
694
754
 
695
755
  # Set up prompt_toolkit with slash command completion
696
756
  prompt_session = self._create_prompt_session()
@@ -750,6 +810,16 @@ class NC1709CLI:
750
810
  self._list_sessions()
751
811
  continue
752
812
 
813
+ # Conversation logs commands
814
+ if cmd_lower == "logs":
815
+ self._show_conversation_logs()
816
+ continue
817
+
818
+ if cmd_lower.startswith("logs "):
819
+ session_id = cmd[5:].strip()
820
+ self._show_conversation_log(session_id)
821
+ continue
822
+
753
823
  if cmd_lower == "save":
754
824
  if self.session_manager and self.session_manager.current_session:
755
825
  self.session_manager.save_session(self.session_manager.current_session)
@@ -941,6 +1011,47 @@ class NC1709CLI:
941
1011
  self._exit_plan_mode()
942
1012
  continue
943
1013
 
1014
+ # Requirements tracking commands
1015
+ if cmd_lower in ["requirements", "reqs"]:
1016
+ self._show_requirements()
1017
+ continue
1018
+
1019
+ if cmd_lower.startswith("requirements init ") or cmd_lower.startswith("reqs init "):
1020
+ parts = cmd.split(" ", 2)
1021
+ name = parts[2] if len(parts) > 2 else ""
1022
+ self._init_requirements(name)
1023
+ continue
1024
+
1025
+ if cmd_lower.startswith("requirements add ") or cmd_lower.startswith("reqs add "):
1026
+ parts = cmd.split(" ", 2)
1027
+ title = parts[2] if len(parts) > 2 else ""
1028
+ self._add_requirement(title)
1029
+ continue
1030
+
1031
+ if cmd_lower.startswith("requirements done ") or cmd_lower.startswith("reqs done "):
1032
+ parts = cmd.split(" ", 2)
1033
+ req_id = parts[2] if len(parts) > 2 else ""
1034
+ self._complete_requirement(req_id)
1035
+ continue
1036
+
1037
+ if cmd_lower.startswith("requirements start ") or cmd_lower.startswith("reqs start "):
1038
+ parts = cmd.split(" ", 2)
1039
+ req_id = parts[2] if len(parts) > 2 else ""
1040
+ self._start_requirement(req_id)
1041
+ continue
1042
+
1043
+ if cmd_lower.startswith("requirements note "):
1044
+ parts = cmd.split(" ", 3)
1045
+ if len(parts) >= 4:
1046
+ req_id = parts[2]
1047
+ note = parts[3]
1048
+ self._add_requirement_note(req_id, note)
1049
+ continue
1050
+
1051
+ if cmd_lower in ["requirements all", "reqs all"]:
1052
+ self._show_requirements(include_completed=True)
1053
+ continue
1054
+
944
1055
  # GitHub/PR commands
945
1056
  if cmd_lower == "pr":
946
1057
  self._create_pr_interactive()
@@ -1130,6 +1241,10 @@ class NC1709CLI:
1130
1241
  Args:
1131
1242
  prompt: User's prompt
1132
1243
  """
1244
+ # Log user message
1245
+ if HAS_CONVERSATION_LOGGER:
1246
+ log_user(prompt)
1247
+
1133
1248
  if self.remote_client:
1134
1249
  # Remote mode with LOCAL tool execution
1135
1250
  # Server only provides LLM thinking, tools run on user's machine
@@ -1257,11 +1372,19 @@ class NC1709CLI:
1257
1372
  tool_history = []
1258
1373
  final_response = ""
1259
1374
 
1375
+ # Track files created/modified in this request
1376
+ files_created = []
1377
+ files_modified = []
1378
+
1260
1379
  print() # Add spacing
1261
1380
 
1381
+ # Show analyzing status (Claude Code style)
1382
+ print(f"{Color.YELLOW}{Icons.BULLET}{Color.RESET} Analyzing request...")
1383
+
1262
1384
  while iteration < max_iterations:
1263
1385
  iteration += 1
1264
- thinking(f"Thinking... (iteration {iteration})")
1386
+ if iteration > 1:
1387
+ thinking(f"Thinking... (iteration {iteration})")
1265
1388
 
1266
1389
  try:
1267
1390
  # Call remote server for LLM response (NO tool execution on server)
@@ -1282,16 +1405,23 @@ class NC1709CLI:
1282
1405
  clean_response = self._clean_response_text(response)
1283
1406
  print_response(clean_response)
1284
1407
 
1408
+ # Log assistant response
1409
+ if HAS_CONVERSATION_LOGGER:
1410
+ log_assistant(clean_response)
1411
+
1285
1412
  # Save assistant response to session for memory
1286
1413
  if self.session_manager:
1287
1414
  self.session_manager.add_message("assistant", clean_response, auto_save=True)
1288
1415
 
1289
- # Show tool execution summary
1290
- if tool_history:
1291
- print(f"\n{Color.DIM}Tools executed: {len(tool_history)}{Color.RESET}")
1292
- for entry in tool_history[-5:]:
1293
- icon = Icons.SUCCESS if entry['success'] else Icons.FAILURE
1294
- print(f" {icon} {entry['tool']}({entry['target']})")
1416
+ # Show completion summary with file tree (Claude Code style)
1417
+ if files_created or files_modified:
1418
+ self._show_file_summary(files_created, files_modified)
1419
+
1420
+ # Update session-level file tracking
1421
+ if hasattr(self, '_session_files_created'):
1422
+ self._session_files_created.extend(files_created)
1423
+ if hasattr(self, '_session_files_modified'):
1424
+ self._session_files_modified.extend(files_modified)
1295
1425
 
1296
1426
  # Flush any remaining files to index
1297
1427
  self._flush_index_queue()
@@ -1330,16 +1460,41 @@ class NC1709CLI:
1330
1460
  tool_history.append({"tool": tool_name, "target": target, "success": False})
1331
1461
  continue
1332
1462
 
1333
- # Execute tool locally
1334
- info(f"Executing: {tool_name}({target})")
1463
+ # Execute tool locally with Claude Code-style UI
1464
+ log_action(tool_name, target)
1335
1465
 
1336
1466
  try:
1467
+ import time
1468
+ start_time = time.time()
1337
1469
  tool_result = tool.run(**tool_params)
1470
+ duration_ms = int((time.time() - start_time) * 1000)
1471
+
1338
1472
  if tool_result.success:
1339
1473
  result_text = tool_result.output
1340
- success(f"{tool_name} completed")
1474
+
1475
+ # Track file operations
1476
+ file_path = tool_params.get("file_path") or tool_params.get("path") or target
1477
+ if tool_name == "Write":
1478
+ # Track new file creation
1479
+ files_created.append(file_path)
1480
+ # Enhanced output for Write - show line count
1481
+ content = tool_params.get("content", "")
1482
+ line_count = content.count('\n') + 1 if content else 0
1483
+ log_output(f"Written {line_count} lines", is_error=False)
1484
+ elif tool_name == "Edit":
1485
+ # Track file modification
1486
+ files_modified.append(file_path)
1487
+ log_output(f"File modified", is_error=False)
1488
+ else:
1489
+ # Show output in Claude Code style (truncated for readability)
1490
+ log_output(result_text, is_error=False)
1491
+
1341
1492
  tool_history.append({"tool": tool_name, "target": target, "success": True})
1342
1493
 
1494
+ # Log tool call
1495
+ if HAS_CONVERSATION_LOGGER:
1496
+ log_tool(tool_name, tool_params, result_text[:1000], success=True, duration_ms=duration_ms)
1497
+
1343
1498
  # Auto-index files when Read tool is used
1344
1499
  if tool_name == "Read" and hasattr(self, '_user_id'):
1345
1500
  self._queue_file_for_indexing(
@@ -1348,17 +1503,25 @@ class NC1709CLI:
1348
1503
  )
1349
1504
  else:
1350
1505
  result_text = f"Error: {tool_result.error}"
1351
- warning(f"{tool_name} failed: {tool_result.error}")
1506
+ log_output(result_text, is_error=True)
1352
1507
  tool_history.append({"tool": tool_name, "target": target, "success": False})
1353
1508
 
1509
+ # Log failed tool call
1510
+ if HAS_CONVERSATION_LOGGER:
1511
+ log_tool(tool_name, tool_params, result_text, success=False, duration_ms=duration_ms)
1512
+
1354
1513
  all_results.append(f"[{tool_name}({target})] {result_text}")
1355
1514
 
1356
1515
  except Exception as e:
1357
1516
  result_text = f"Exception: {str(e)}"
1358
- error(f"{tool_name} error: {e}")
1517
+ log_output(result_text, is_error=True)
1359
1518
  all_results.append(f"[{tool_name}] {result_text}")
1360
1519
  tool_history.append({"tool": tool_name, "target": target, "success": False})
1361
1520
 
1521
+ # Log exception
1522
+ if HAS_CONVERSATION_LOGGER:
1523
+ log_error(result_text, {"tool": tool_name, "params": tool_params})
1524
+
1362
1525
  # Add assistant response and tool results to conversation
1363
1526
  messages.append({"role": "assistant", "content": response})
1364
1527
  messages.append({
@@ -1786,6 +1949,14 @@ class NC1709CLI:
1786
1949
  {B}{M}📧 Need a key? support@lafzusa.com{R}
1787
1950
  {C}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{R}
1788
1951
  ''')
1952
+ # Check for updates and show notification even on auth screen
1953
+ try:
1954
+ from .version_check import check_and_notify
1955
+ update_msg = check_and_notify()
1956
+ if update_msg:
1957
+ print(f"{Y}{update_msg}{R}")
1958
+ except Exception:
1959
+ pass
1789
1960
 
1790
1961
  def _print_startup_banner(self) -> None:
1791
1962
  """Print startup banner (kept for compatibility)"""
@@ -1900,6 +2071,13 @@ class NC1709CLI:
1900
2071
  /brain status Show cognitive system status
1901
2072
  /brain suggest Get proactive suggestions
1902
2073
  /brain index Index project for context awareness
2074
+
2075
+ \033[36mRequirements:\033[0m
2076
+ /requirements Show project requirements (/reqs shortcut)
2077
+ /reqs add <title> Add a new requirement
2078
+ /reqs start <id> Mark requirement as in-progress
2079
+ /reqs done <id> Mark requirement as complete
2080
+ /reqs all Show all requirements including completed
1903
2081
  """
1904
2082
 
1905
2083
  def _show_history(self) -> None:
@@ -3024,6 +3202,244 @@ class NC1709CLI:
3024
3202
  print(f"❌ Error connecting: {e}")
3025
3203
  return 1
3026
3204
 
3205
+ # =========================================================================
3206
+ # File Summary Display
3207
+ # =========================================================================
3208
+
3209
+ def _show_file_summary(self, files_created: list, files_modified: list) -> None:
3210
+ """Show a summary of files created/modified with file tree format.
3211
+
3212
+ Args:
3213
+ files_created: List of file paths that were created
3214
+ files_modified: List of file paths that were modified
3215
+ """
3216
+ if not files_created and not files_modified:
3217
+ return
3218
+
3219
+ # Deduplicate paths
3220
+ created_set = set(files_created)
3221
+ modified_set = set(files_modified) - created_set # Don't show as modified if created
3222
+
3223
+ total_files = len(created_set) + len(modified_set)
3224
+
3225
+ # Show summary header
3226
+ print(f"\n{Color.GREEN}{Icons.SUCCESS}{Color.RESET} Done!")
3227
+
3228
+ if created_set:
3229
+ print(f"\n{Color.BOLD}Files created:{Color.RESET}")
3230
+ self._print_file_tree(list(created_set), "created")
3231
+
3232
+ if modified_set:
3233
+ print(f"\n{Color.BOLD}Files modified:{Color.RESET}")
3234
+ self._print_file_tree(list(modified_set), "modified")
3235
+
3236
+ print()
3237
+
3238
+ def _print_file_tree(self, file_paths: list, action: str) -> None:
3239
+ """Print files in a tree format.
3240
+
3241
+ Args:
3242
+ file_paths: List of file paths
3243
+ action: Either 'created' or 'modified'
3244
+ """
3245
+ # Get current working directory for relative paths
3246
+ cwd = Path.cwd()
3247
+
3248
+ # Sort and convert to relative paths where possible
3249
+ relative_paths = []
3250
+ for fp in file_paths:
3251
+ try:
3252
+ path = Path(fp)
3253
+ if path.is_absolute():
3254
+ try:
3255
+ rel = path.relative_to(cwd)
3256
+ relative_paths.append(str(rel))
3257
+ except ValueError:
3258
+ relative_paths.append(fp)
3259
+ else:
3260
+ relative_paths.append(fp)
3261
+ except Exception:
3262
+ relative_paths.append(fp)
3263
+
3264
+ relative_paths.sort()
3265
+
3266
+ # Print with tree structure
3267
+ for i, path in enumerate(relative_paths):
3268
+ is_last = (i == len(relative_paths) - 1)
3269
+ prefix = Icons.TREE_BRANCH if is_last else Icons.TREE_TEE
3270
+ color = Color.GREEN if action == "created" else Color.YELLOW
3271
+ print(f"{prefix} {color}{path}{Color.RESET}")
3272
+
3273
+ # =========================================================================
3274
+ # Conversation Logs Methods
3275
+ # =========================================================================
3276
+
3277
+ def _show_conversation_logs(self) -> None:
3278
+ """Show recent conversation logs"""
3279
+ if not HAS_CONVERSATION_LOGGER:
3280
+ warning("Conversation logger not available")
3281
+ return
3282
+
3283
+ sessions = ConversationLogger.list_sessions(limit=20)
3284
+
3285
+ if not sessions:
3286
+ print("No conversation logs found.")
3287
+ print(f"Logs are stored in: ~/.nc1709/logs/")
3288
+ return
3289
+
3290
+ print(f"\n{Color.BOLD}Recent Conversation Logs:{Color.RESET}\n")
3291
+ for session in sessions:
3292
+ session_id = session.get("session_id", "unknown")[:20]
3293
+ started = session.get("started_at", "")[:16]
3294
+ ip = session.get("ip_address") or "local"
3295
+ mode = session.get("mode", "remote")
3296
+ count = session.get("entry_count", 0)
3297
+
3298
+ print(f" {Color.CYAN}{session_id}{Color.RESET}")
3299
+ print(f" Started: {started} | IP: {ip} | Mode: {mode}")
3300
+ print(f" Entries: {count}")
3301
+ print()
3302
+
3303
+ print(f"{Color.DIM}Use /logs <session_id> to view a specific log{Color.RESET}")
3304
+
3305
+ def _show_conversation_log(self, session_id: str) -> None:
3306
+ """Show a specific conversation log"""
3307
+ if not HAS_CONVERSATION_LOGGER:
3308
+ warning("Conversation logger not available")
3309
+ return
3310
+
3311
+ session_data = ConversationLogger.load_session(session_id)
3312
+
3313
+ if not session_data:
3314
+ error(f"Session log not found: {session_id}")
3315
+ return
3316
+
3317
+ session = session_data.get("session", {})
3318
+ entries = session_data.get("entries", [])
3319
+
3320
+ print(f"\n{Color.BOLD}Session: {session.get('session_id')}{Color.RESET}")
3321
+ print(f"Started: {session.get('started_at', '')[:16]}")
3322
+ print(f"IP: {session.get('ip_address') or 'local'}")
3323
+ print(f"Mode: {session.get('mode', 'remote')}")
3324
+ print(f"Working Dir: {session.get('working_directory', 'unknown')}")
3325
+ print(f"\n{'─' * 60}\n")
3326
+
3327
+ for entry in entries:
3328
+ role = entry.get("role", "unknown")
3329
+ timestamp = entry.get("timestamp", "")[:19]
3330
+ content = entry.get("content", "")
3331
+ metadata = entry.get("metadata", {})
3332
+
3333
+ if role == "user":
3334
+ print(f"{Color.GREEN}[{timestamp}] User:{Color.RESET}")
3335
+ print(f" {content[:500]}{'...' if len(content) > 500 else ''}")
3336
+ elif role == "assistant":
3337
+ print(f"{Color.BLUE}[{timestamp}] Assistant:{Color.RESET}")
3338
+ print(f" {content[:500]}{'...' if len(content) > 500 else ''}")
3339
+ elif role == "tool":
3340
+ tool_name = metadata.get("tool_name", "unknown")
3341
+ success_str = "✓" if metadata.get("success") else "✗"
3342
+ duration = metadata.get("duration_ms", 0)
3343
+ print(f"{Color.YELLOW}[{timestamp}] Tool: {tool_name} {success_str} ({duration}ms){Color.RESET}")
3344
+ elif role == "error":
3345
+ print(f"{Color.RED}[{timestamp}] Error: {content}{Color.RESET}")
3346
+ elif role == "system":
3347
+ print(f"{Color.DIM}[{timestamp}] System: {content}{Color.RESET}")
3348
+
3349
+ print()
3350
+
3351
+ print(f"\n{Color.DIM}Total entries: {len(entries)}{Color.RESET}")
3352
+
3353
+ # =========================================================================
3354
+ # Requirements Tracking Methods
3355
+ # =========================================================================
3356
+
3357
+ def _show_requirements(self, include_completed: bool = False) -> None:
3358
+ """Show project requirements"""
3359
+ if not HAS_REQUIREMENTS:
3360
+ warning("Requirements tracking module not available")
3361
+ return
3362
+
3363
+ tracker = get_tracker()
3364
+ print(tracker.format_all(verbose=True, include_completed=include_completed))
3365
+
3366
+ def _init_requirements(self, name: str) -> None:
3367
+ """Initialize a new project for requirements tracking"""
3368
+ if not HAS_REQUIREMENTS:
3369
+ warning("Requirements tracking module not available")
3370
+ return
3371
+
3372
+ if not name:
3373
+ # Use current directory name
3374
+ name = Path.cwd().name
3375
+
3376
+ tracker = get_tracker()
3377
+ project = tracker.init_project(name)
3378
+ success(f"Initialized requirements for project: {project.name}")
3379
+ info(f"Storage: {tracker.storage_path}")
3380
+
3381
+ def _add_requirement(self, title: str) -> None:
3382
+ """Add a new requirement"""
3383
+ if not HAS_REQUIREMENTS:
3384
+ warning("Requirements tracking module not available")
3385
+ return
3386
+
3387
+ if not title:
3388
+ warning("Please provide a requirement title")
3389
+ return
3390
+
3391
+ tracker = get_tracker()
3392
+ req = tracker.add_requirement(title)
3393
+ success(f"Added requirement: {req.id}")
3394
+ print(tracker.format_requirement(req, verbose=True))
3395
+
3396
+ def _complete_requirement(self, req_id: str) -> None:
3397
+ """Mark a requirement as complete"""
3398
+ if not HAS_REQUIREMENTS:
3399
+ warning("Requirements tracking module not available")
3400
+ return
3401
+
3402
+ if not req_id:
3403
+ warning("Please provide a requirement ID")
3404
+ return
3405
+
3406
+ tracker = get_tracker()
3407
+ req = tracker.set_status(req_id.upper(), RequirementStatus.COMPLETED)
3408
+ if req:
3409
+ success(f"Completed: {req.title}")
3410
+ else:
3411
+ error(f"Requirement not found: {req_id}")
3412
+
3413
+ def _start_requirement(self, req_id: str) -> None:
3414
+ """Mark a requirement as in progress"""
3415
+ if not HAS_REQUIREMENTS:
3416
+ warning("Requirements tracking module not available")
3417
+ return
3418
+
3419
+ if not req_id:
3420
+ warning("Please provide a requirement ID")
3421
+ return
3422
+
3423
+ tracker = get_tracker()
3424
+ req = tracker.set_status(req_id.upper(), RequirementStatus.IN_PROGRESS)
3425
+ if req:
3426
+ success(f"Started: {req.title}")
3427
+ else:
3428
+ error(f"Requirement not found: {req_id}")
3429
+
3430
+ def _add_requirement_note(self, req_id: str, note: str) -> None:
3431
+ """Add a note to a requirement"""
3432
+ if not HAS_REQUIREMENTS:
3433
+ warning("Requirements tracking module not available")
3434
+ return
3435
+
3436
+ tracker = get_tracker()
3437
+ req = tracker.add_note(req_id.upper(), note)
3438
+ if req:
3439
+ success(f"Added note to {req_id}")
3440
+ else:
3441
+ error(f"Requirement not found: {req_id}")
3442
+
3027
3443
  # =========================================================================
3028
3444
  # Cognitive System Methods (Brain)
3029
3445
  # =========================================================================