elspais 0.11.0__py3-none-any.whl → 0.11.2__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.
Files changed (53) hide show
  1. elspais/__init__.py +1 -1
  2. elspais/cli.py +75 -23
  3. elspais/commands/analyze.py +5 -6
  4. elspais/commands/changed.py +2 -6
  5. elspais/commands/config_cmd.py +4 -4
  6. elspais/commands/edit.py +32 -36
  7. elspais/commands/hash_cmd.py +24 -18
  8. elspais/commands/index.py +8 -7
  9. elspais/commands/init.py +4 -4
  10. elspais/commands/reformat_cmd.py +32 -43
  11. elspais/commands/rules_cmd.py +6 -2
  12. elspais/commands/trace.py +23 -19
  13. elspais/commands/validate.py +8 -10
  14. elspais/config/defaults.py +7 -1
  15. elspais/core/content_rules.py +0 -1
  16. elspais/core/git.py +4 -10
  17. elspais/core/parser.py +55 -56
  18. elspais/core/patterns.py +2 -6
  19. elspais/core/rules.py +10 -15
  20. elspais/mcp/__init__.py +2 -0
  21. elspais/mcp/context.py +1 -0
  22. elspais/mcp/serializers.py +1 -1
  23. elspais/mcp/server.py +54 -39
  24. elspais/reformat/__init__.py +13 -13
  25. elspais/reformat/detector.py +9 -16
  26. elspais/reformat/hierarchy.py +8 -7
  27. elspais/reformat/line_breaks.py +36 -38
  28. elspais/reformat/prompts.py +22 -12
  29. elspais/reformat/transformer.py +43 -41
  30. elspais/sponsors/__init__.py +0 -2
  31. elspais/testing/__init__.py +1 -1
  32. elspais/testing/result_parser.py +25 -21
  33. elspais/trace_view/__init__.py +4 -3
  34. elspais/trace_view/coverage.py +5 -5
  35. elspais/trace_view/generators/__init__.py +1 -1
  36. elspais/trace_view/generators/base.py +17 -12
  37. elspais/trace_view/generators/csv.py +2 -6
  38. elspais/trace_view/generators/markdown.py +3 -8
  39. elspais/trace_view/html/__init__.py +4 -2
  40. elspais/trace_view/html/generator.py +423 -289
  41. elspais/trace_view/models.py +25 -0
  42. elspais/trace_view/review/__init__.py +21 -18
  43. elspais/trace_view/review/branches.py +114 -121
  44. elspais/trace_view/review/models.py +232 -237
  45. elspais/trace_view/review/position.py +53 -71
  46. elspais/trace_view/review/server.py +264 -288
  47. elspais/trace_view/review/status.py +43 -58
  48. elspais/trace_view/review/storage.py +48 -72
  49. {elspais-0.11.0.dist-info → elspais-0.11.2.dist-info}/METADATA +12 -9
  50. {elspais-0.11.0.dist-info → elspais-0.11.2.dist-info}/RECORD +53 -53
  51. {elspais-0.11.0.dist-info → elspais-0.11.2.dist-info}/WHEEL +0 -0
  52. {elspais-0.11.0.dist-info → elspais-0.11.2.dist-info}/entry_points.txt +0 -0
  53. {elspais-0.11.0.dist-info → elspais-0.11.2.dist-info}/licenses/LICENSE +0 -0
@@ -14,73 +14,58 @@ IMPLEMENTS REQUIREMENTS:
14
14
  """
15
15
 
16
16
  from pathlib import Path
17
- from typing import Optional, Tuple
17
+ from typing import Optional
18
18
 
19
- from flask import Flask, request, jsonify, send_from_directory
19
+ from flask import Flask, jsonify, request, send_from_directory
20
20
  from flask_cors import CORS
21
21
 
22
+ from .branches import (
23
+ commit_and_push_reviews,
24
+ fetch_package_branches,
25
+ get_current_package_context,
26
+ # REQ-d00098: Git audit trail
27
+ get_git_context,
28
+ has_reviews_changes,
29
+ )
22
30
  from .models import (
23
- Thread,
24
- Comment,
25
- CommentPosition,
26
- ThreadsFile,
27
- StatusFile,
28
- StatusRequest,
31
+ Approval,
29
32
  ReviewFlag,
30
- ReviewConfig,
31
33
  ReviewPackage,
32
- PackagesFile,
33
- Approval,
34
- now_iso,
34
+ StatusRequest,
35
+ Thread,
35
36
  )
37
+ from .status import change_req_status, get_req_status
36
38
  from .storage import (
37
- load_threads,
38
- save_threads,
39
- add_thread,
40
- add_comment_to_thread,
41
- resolve_thread,
42
- unresolve_thread,
43
- load_status_requests,
44
- save_status_requests,
45
- create_status_request,
46
39
  add_approval,
47
- mark_request_applied,
48
- load_review_flag,
49
- save_review_flag,
50
- load_packages,
51
- save_packages,
52
- create_package,
53
- update_package,
54
- delete_package,
40
+ add_comment_to_thread,
55
41
  add_req_to_package,
56
- remove_req_from_package,
57
- load_config,
58
- normalize_req_id,
42
+ add_thread,
59
43
  # REQ-d00097: Archive operations
60
44
  archive_package,
61
- list_archived_packages,
45
+ check_auto_archive,
46
+ create_package,
47
+ create_status_request,
62
48
  get_archived_package,
49
+ list_archived_packages,
63
50
  load_archived_threads,
64
- check_auto_archive,
65
- )
66
- from .branches import (
67
- commit_and_push_reviews,
68
- fetch_package_branches,
69
- get_current_package_context,
70
- list_package_branches,
71
- has_reviews_changes,
72
- # REQ-d00098: Git audit trail
73
- get_git_context,
74
- get_head_commit_hash,
51
+ load_packages,
52
+ load_review_flag,
53
+ load_status_requests,
54
+ normalize_req_id,
55
+ remove_req_from_package,
56
+ resolve_thread,
57
+ save_packages,
58
+ save_review_flag,
59
+ unresolve_thread,
60
+ update_package,
75
61
  )
76
- from .status import get_req_status, change_req_status
77
62
 
78
63
 
79
64
  def create_app(
80
65
  repo_root: Path,
81
66
  static_dir: Optional[Path] = None,
82
67
  auto_sync: bool = True,
83
- register_static_routes: bool = True
68
+ register_static_routes: bool = True,
84
69
  ) -> Flask:
85
70
  """
86
71
  Create Flask app with review API endpoints.
@@ -103,11 +88,11 @@ def create_app(
103
88
  CORS(app)
104
89
 
105
90
  # Store configuration in app config
106
- app.config['REPO_ROOT'] = repo_root
107
- app.config['STATIC_DIR'] = static_dir or repo_root
108
- app.config['AUTO_SYNC'] = auto_sync
91
+ app.config["REPO_ROOT"] = repo_root
92
+ app.config["STATIC_DIR"] = static_dir or repo_root
93
+ app.config["AUTO_SYNC"] = auto_sync
109
94
 
110
- def trigger_auto_sync(message: str, user: str = 'system') -> Optional[dict]:
95
+ def trigger_auto_sync(message: str, user: str = "system") -> Optional[dict]:
111
96
  """
112
97
  Trigger auto-sync if enabled.
113
98
 
@@ -121,12 +106,12 @@ def create_app(
121
106
  Returns:
122
107
  dict with sync result, or None if auto-sync disabled
123
108
  """
124
- if not app.config.get('AUTO_SYNC'):
109
+ if not app.config.get("AUTO_SYNC"):
125
110
  return None
126
111
 
127
- repo = app.config['REPO_ROOT']
112
+ repo = app.config["REPO_ROOT"]
128
113
  success, msg = commit_and_push_reviews(repo, message, user)
129
- return {'success': success, 'message': msg}
114
+ return {"success": success, "message": msg}
130
115
 
131
116
  # ==========================================================================
132
117
  # Static File Serving
@@ -134,38 +119,38 @@ def create_app(
134
119
  # ==========================================================================
135
120
 
136
121
  if register_static_routes:
137
- @app.route('/')
122
+
123
+ @app.route("/")
138
124
  def index():
139
125
  """Serve index from static directory"""
140
- return send_from_directory(
141
- app.config['STATIC_DIR'],
142
- 'index.html'
143
- )
126
+ return send_from_directory(app.config["STATIC_DIR"], "index.html")
144
127
 
145
- @app.route('/<path:path>')
128
+ @app.route("/<path:path>")
146
129
  def serve_static(path):
147
130
  """Serve static files from configured static directory"""
148
- return send_from_directory(app.config['STATIC_DIR'], path)
131
+ return send_from_directory(app.config["STATIC_DIR"], path)
149
132
 
150
133
  # ==========================================================================
151
134
  # Health Check
152
135
  # REQ-tv-d00014-J: Provide /api/health endpoint for health checks
153
136
  # ==========================================================================
154
137
 
155
- @app.route('/api/health', methods=['GET'])
138
+ @app.route("/api/health", methods=["GET"])
156
139
  def health_check():
157
140
  """Health check endpoint"""
158
- return jsonify({
159
- 'status': 'ok',
160
- 'repo_root': str(app.config['REPO_ROOT']),
161
- 'reviews_dir': str(app.config['REPO_ROOT'] / '.reviews')
162
- })
141
+ return jsonify(
142
+ {
143
+ "status": "ok",
144
+ "repo_root": str(app.config["REPO_ROOT"]),
145
+ "reviews_dir": str(app.config["REPO_ROOT"] / ".reviews"),
146
+ }
147
+ )
163
148
 
164
149
  # ==========================================================================
165
150
  # File Content API (for external repo files)
166
151
  # ==========================================================================
167
152
 
168
- @app.route('/api/files', methods=['GET'])
153
+ @app.route("/api/files", methods=["GET"])
169
154
  def get_file_content():
170
155
  """
171
156
  Fetch file content for viewing in the browser.
@@ -176,103 +161,103 @@ def create_app(
176
161
  Returns file content as text. Only allows reading files in spec/
177
162
  directories for security.
178
163
  """
179
- file_path = request.args.get('path')
164
+ file_path = request.args.get("path")
180
165
  if not file_path:
181
- return jsonify({'error': 'Missing path parameter'}), 400
166
+ return jsonify({"error": "Missing path parameter"}), 400
182
167
 
183
168
  # Security: only allow reading spec files
184
169
  path = Path(file_path)
185
170
  if not path.is_absolute():
186
- return jsonify({'error': 'Path must be absolute'}), 400
171
+ return jsonify({"error": "Path must be absolute"}), 400
187
172
 
188
173
  # Check that path contains /spec/ for security
189
- if '/spec/' not in str(path):
190
- return jsonify({'error': 'Only spec files can be read'}), 403
174
+ if "/spec/" not in str(path):
175
+ return jsonify({"error": "Only spec files can be read"}), 403
191
176
 
192
177
  if not path.exists():
193
- return jsonify({'error': f'File not found: {path}'}), 404
178
+ return jsonify({"error": f"File not found: {path}"}), 404
194
179
 
195
180
  if not path.is_file():
196
- return jsonify({'error': 'Path is not a file'}), 400
181
+ return jsonify({"error": "Path is not a file"}), 400
197
182
 
198
183
  try:
199
- content = path.read_text(encoding='utf-8')
200
- return content, 200, {'Content-Type': 'text/plain; charset=utf-8'}
184
+ content = path.read_text(encoding="utf-8")
185
+ return content, 200, {"Content-Type": "text/plain; charset=utf-8"}
201
186
  except Exception as e:
202
- return jsonify({'error': f'Failed to read file: {e}'}), 500
187
+ return jsonify({"error": f"Failed to read file: {e}"}), 500
203
188
 
204
189
  # ==========================================================================
205
190
  # Thread API
206
191
  # REQ-tv-d00014-B: Thread endpoints for create, comment, resolve, unresolve
207
192
  # ==========================================================================
208
193
 
209
- @app.route('/api/reviews/reqs/<req_id>/threads', methods=['POST'])
194
+ @app.route("/api/reviews/reqs/<req_id>/threads", methods=["POST"])
210
195
  def create_thread_endpoint(req_id):
211
196
  """
212
197
  Create a new comment thread.
213
198
 
214
199
  REQ-tv-d00014-B: POST create thread endpoint.
215
200
  """
216
- repo = app.config['REPO_ROOT']
201
+ repo = app.config["REPO_ROOT"]
217
202
  normalized_id = normalize_req_id(req_id)
218
203
  data = request.get_json(silent=True)
219
204
 
220
205
  if not data:
221
- return jsonify({'error': 'No data provided'}), 400
206
+ return jsonify({"error": "No data provided"}), 400
222
207
 
223
208
  try:
224
209
  thread = Thread.from_dict(data)
225
210
  add_thread(repo, normalized_id, thread)
226
211
 
227
212
  # REQ-tv-d00014-H: Auto-sync after write operation
228
- user = thread.createdBy or 'system'
213
+ user = thread.createdBy or "system"
229
214
  sync_result = trigger_auto_sync(f"New thread on REQ-{normalized_id}", user)
230
215
 
231
- response = {'success': True, 'thread': thread.to_dict()}
216
+ response = {"success": True, "thread": thread.to_dict()}
232
217
  if sync_result:
233
- response['sync'] = sync_result
218
+ response["sync"] = sync_result
234
219
 
235
220
  return jsonify(response), 201
236
221
  except Exception as e:
237
- return jsonify({'error': str(e)}), 400
222
+ return jsonify({"error": str(e)}), 400
238
223
 
239
- @app.route('/api/reviews/reqs/<req_id>/threads/<thread_id>/comments', methods=['POST'])
224
+ @app.route("/api/reviews/reqs/<req_id>/threads/<thread_id>/comments", methods=["POST"])
240
225
  def add_comment_endpoint(req_id, thread_id):
241
226
  """
242
227
  Add a comment to an existing thread.
243
228
 
244
229
  REQ-tv-d00014-B: POST add comment endpoint.
245
230
  """
246
- repo = app.config['REPO_ROOT']
231
+ repo = app.config["REPO_ROOT"]
247
232
  normalized_id = normalize_req_id(req_id)
248
233
  data = request.get_json(silent=True)
249
234
 
250
235
  if not data:
251
- return jsonify({'error': 'No data provided'}), 400
236
+ return jsonify({"error": "No data provided"}), 400
252
237
 
253
238
  try:
254
- author = data.get('author')
255
- body = data.get('body')
239
+ author = data.get("author")
240
+ body = data.get("body")
256
241
 
257
242
  if not author:
258
- return jsonify({'error': 'Comment author is required'}), 400
243
+ return jsonify({"error": "Comment author is required"}), 400
259
244
  if not body:
260
- return jsonify({'error': 'Comment body is required'}), 400
245
+ return jsonify({"error": "Comment body is required"}), 400
261
246
 
262
247
  comment = add_comment_to_thread(repo, normalized_id, thread_id, author, body)
263
248
 
264
249
  # REQ-tv-d00014-H: Auto-sync after write operation
265
250
  sync_result = trigger_auto_sync(f"Comment on REQ-{normalized_id}", author)
266
251
 
267
- response = {'success': True, 'comment': comment.to_dict()}
252
+ response = {"success": True, "comment": comment.to_dict()}
268
253
  if sync_result:
269
- response['sync'] = sync_result
254
+ response["sync"] = sync_result
270
255
 
271
256
  return jsonify(response), 201
272
257
  except Exception as e:
273
- return jsonify({'error': str(e)}), 400
258
+ return jsonify({"error": str(e)}), 400
274
259
 
275
- @app.route('/api/reviews/reqs/<req_id>/threads/<thread_id>/resolve', methods=['POST'])
260
+ @app.route("/api/reviews/reqs/<req_id>/threads/<thread_id>/resolve", methods=["POST"])
276
261
  def resolve_thread_endpoint(req_id, thread_id):
277
262
  """
278
263
  Resolve a thread.
@@ -280,11 +265,11 @@ def create_app(
280
265
  REQ-tv-d00014-B: POST resolve endpoint.
281
266
  REQ-d00097-D: Resolving all threads in a package SHALL trigger auto-archive.
282
267
  """
283
- repo = app.config['REPO_ROOT']
268
+ repo = app.config["REPO_ROOT"]
284
269
  normalized_id = normalize_req_id(req_id)
285
270
  data = request.get_json(silent=True) or {}
286
- user = data.get('user', 'anonymous')
287
- package_id = data.get('packageId') # Optional: for auto-archive check
271
+ user = data.get("user", "anonymous")
272
+ package_id = data.get("packageId") # Optional: for auto-archive check
288
273
 
289
274
  try:
290
275
  resolve_thread(repo, normalized_id, thread_id, user)
@@ -292,32 +277,32 @@ def create_app(
292
277
  # REQ-tv-d00014-H: Auto-sync after write operation
293
278
  sync_result = trigger_auto_sync(f"Resolved thread on REQ-{normalized_id}", user)
294
279
 
295
- response = {'success': True}
280
+ response = {"success": True}
296
281
  if sync_result:
297
- response['sync'] = sync_result
282
+ response["sync"] = sync_result
298
283
 
299
284
  # REQ-d00097-D: Check for auto-archive if packageId provided
300
285
  if package_id:
301
286
  was_archived = check_auto_archive(repo, package_id, user)
302
287
  if was_archived:
303
- response['packageArchived'] = True
304
- response['archiveReason'] = 'resolved'
288
+ response["packageArchived"] = True
289
+ response["archiveReason"] = "resolved"
305
290
 
306
291
  return jsonify(response), 200
307
292
  except Exception as e:
308
- return jsonify({'error': str(e)}), 400
293
+ return jsonify({"error": str(e)}), 400
309
294
 
310
- @app.route('/api/reviews/reqs/<req_id>/threads/<thread_id>/unresolve', methods=['POST'])
295
+ @app.route("/api/reviews/reqs/<req_id>/threads/<thread_id>/unresolve", methods=["POST"])
311
296
  def unresolve_thread_endpoint(req_id, thread_id):
312
297
  """
313
298
  Unresolve a thread.
314
299
 
315
300
  REQ-tv-d00014-B: POST unresolve endpoint.
316
301
  """
317
- repo = app.config['REPO_ROOT']
302
+ repo = app.config["REPO_ROOT"]
318
303
  normalized_id = normalize_req_id(req_id)
319
304
  data = request.get_json(silent=True) or {}
320
- user = data.get('user', 'anonymous')
305
+ user = data.get("user", "anonymous")
321
306
 
322
307
  try:
323
308
  unresolve_thread(repo, normalized_id, thread_id)
@@ -325,59 +310,59 @@ def create_app(
325
310
  # REQ-tv-d00014-H: Auto-sync after write operation
326
311
  sync_result = trigger_auto_sync(f"Unresolved thread on REQ-{normalized_id}", user)
327
312
 
328
- response = {'success': True}
313
+ response = {"success": True}
329
314
  if sync_result:
330
- response['sync'] = sync_result
315
+ response["sync"] = sync_result
331
316
 
332
317
  return jsonify(response), 200
333
318
  except Exception as e:
334
- return jsonify({'error': str(e)}), 400
319
+ return jsonify({"error": str(e)}), 400
335
320
 
336
321
  # ==========================================================================
337
322
  # Review Flag API
338
323
  # ==========================================================================
339
324
 
340
- @app.route('/api/reviews/reqs/<req_id>/flag', methods=['GET'])
325
+ @app.route("/api/reviews/reqs/<req_id>/flag", methods=["GET"])
341
326
  def get_flag(req_id):
342
327
  """Get review flag for a requirement"""
343
- repo = app.config['REPO_ROOT']
328
+ repo = app.config["REPO_ROOT"]
344
329
  normalized_id = normalize_req_id(req_id)
345
330
  flag = load_review_flag(repo, normalized_id)
346
331
  return jsonify(flag.to_dict())
347
332
 
348
- @app.route('/api/reviews/reqs/<req_id>/flag', methods=['POST'])
333
+ @app.route("/api/reviews/reqs/<req_id>/flag", methods=["POST"])
349
334
  def set_flag(req_id):
350
335
  """Set review flag for a requirement"""
351
- repo = app.config['REPO_ROOT']
336
+ repo = app.config["REPO_ROOT"]
352
337
  normalized_id = normalize_req_id(req_id)
353
338
  data = request.get_json(silent=True)
354
339
 
355
340
  if not data:
356
- return jsonify({'error': 'No data provided'}), 400
341
+ return jsonify({"error": "No data provided"}), 400
357
342
 
358
343
  try:
359
344
  flag = ReviewFlag.from_dict(data)
360
345
  save_review_flag(repo, normalized_id, flag)
361
346
 
362
347
  # REQ-tv-d00014-H: Auto-sync after write operation
363
- user = flag.flaggedBy or 'system'
348
+ user = flag.flaggedBy or "system"
364
349
  sync_result = trigger_auto_sync(f"Flagged REQ-{normalized_id} for review", user)
365
350
 
366
- response = {'success': True, 'flag': flag.to_dict()}
351
+ response = {"success": True, "flag": flag.to_dict()}
367
352
  if sync_result:
368
- response['sync'] = sync_result
353
+ response["sync"] = sync_result
369
354
 
370
355
  return jsonify(response), 200
371
356
  except Exception as e:
372
- return jsonify({'error': str(e)}), 400
357
+ return jsonify({"error": str(e)}), 400
373
358
 
374
- @app.route('/api/reviews/reqs/<req_id>/flag', methods=['DELETE'])
359
+ @app.route("/api/reviews/reqs/<req_id>/flag", methods=["DELETE"])
375
360
  def clear_flag(req_id):
376
361
  """Clear review flag for a requirement"""
377
- repo = app.config['REPO_ROOT']
362
+ repo = app.config["REPO_ROOT"]
378
363
  normalized_id = normalize_req_id(req_id)
379
364
  data = request.get_json(silent=True) or {}
380
- user = data.get('user', 'anonymous')
365
+ user = data.get("user", "anonymous")
381
366
 
382
367
  flag = ReviewFlag.cleared()
383
368
  save_review_flag(repo, normalized_id, flag)
@@ -385,9 +370,9 @@ def create_app(
385
370
  # REQ-tv-d00014-H: Auto-sync after write operation
386
371
  sync_result = trigger_auto_sync(f"Cleared flag on REQ-{normalized_id}", user)
387
372
 
388
- response = {'success': True}
373
+ response = {"success": True}
389
374
  if sync_result:
390
- response['sync'] = sync_result
375
+ response["sync"] = sync_result
391
376
 
392
377
  return jsonify(response), 200
393
378
 
@@ -396,158 +381,157 @@ def create_app(
396
381
  # REQ-tv-d00014-C: Status endpoints for GET/POST requests and approvals
397
382
  # ==========================================================================
398
383
 
399
- @app.route('/api/reviews/reqs/<req_id>/status', methods=['GET'])
384
+ @app.route("/api/reviews/reqs/<req_id>/status", methods=["GET"])
400
385
  def get_status(req_id):
401
386
  """
402
387
  Get the current status of a requirement from the spec file.
403
388
 
404
389
  REQ-tv-d00014-C: GET status endpoint.
405
390
  """
406
- repo = app.config['REPO_ROOT']
391
+ repo = app.config["REPO_ROOT"]
407
392
  normalized_id = normalize_req_id(req_id)
408
393
 
409
394
  status = get_req_status(repo, normalized_id)
410
395
  if status is None:
411
- return jsonify({'error': f'REQ-{normalized_id} not found'}), 404
396
+ return jsonify({"error": f"REQ-{normalized_id} not found"}), 404
412
397
 
413
- return jsonify({'reqId': normalized_id, 'status': status})
398
+ return jsonify({"reqId": normalized_id, "status": status})
414
399
 
415
- @app.route('/api/reviews/reqs/<req_id>/status', methods=['POST'])
400
+ @app.route("/api/reviews/reqs/<req_id>/status", methods=["POST"])
416
401
  def set_status(req_id):
417
402
  """
418
403
  Change the status of a requirement in its spec file.
419
404
 
420
405
  REQ-tv-d00014-C: POST change status endpoint.
421
406
  """
422
- repo = app.config['REPO_ROOT']
407
+ repo = app.config["REPO_ROOT"]
423
408
  normalized_id = normalize_req_id(req_id)
424
409
  data = request.get_json(silent=True)
425
410
 
426
411
  if not data:
427
- return jsonify({'error': 'No data provided'}), 400
412
+ return jsonify({"error": "No data provided"}), 400
428
413
 
429
- new_status = data.get('newStatus')
414
+ new_status = data.get("newStatus")
430
415
  if not new_status:
431
- return jsonify({'error': 'newStatus is required'}), 400
416
+ return jsonify({"error": "newStatus is required"}), 400
432
417
 
433
- user = data.get('user', 'api')
418
+ user = data.get("user", "api")
434
419
 
435
420
  success, message = change_req_status(repo, normalized_id, new_status, user)
436
421
 
437
422
  if success:
438
423
  # REQ-tv-d00014-H: Auto-sync after write operation
439
424
  sync_result = trigger_auto_sync(
440
- f"Changed REQ-{normalized_id} status to {new_status}",
441
- user
425
+ f"Changed REQ-{normalized_id} status to {new_status}", user
442
426
  )
443
427
 
444
- response = {'success': True, 'message': message}
428
+ response = {"success": True, "message": message}
445
429
  if sync_result:
446
- response['sync'] = sync_result
430
+ response["sync"] = sync_result
447
431
 
448
432
  return jsonify(response), 200
449
433
  else:
450
- return jsonify({'success': False, 'error': message}), 400
434
+ return jsonify({"success": False, "error": message}), 400
451
435
 
452
- @app.route('/api/reviews/reqs/<req_id>/requests', methods=['GET'])
436
+ @app.route("/api/reviews/reqs/<req_id>/requests", methods=["GET"])
453
437
  def get_status_requests(req_id):
454
438
  """
455
439
  Get status change requests for a requirement.
456
440
 
457
441
  REQ-tv-d00014-C: GET requests endpoint.
458
442
  """
459
- repo = app.config['REPO_ROOT']
443
+ repo = app.config["REPO_ROOT"]
460
444
  normalized_id = normalize_req_id(req_id)
461
445
  status_file = load_status_requests(repo, normalized_id)
462
446
  return jsonify([r.to_dict() for r in status_file.requests])
463
447
 
464
- @app.route('/api/reviews/reqs/<req_id>/requests', methods=['POST'])
448
+ @app.route("/api/reviews/reqs/<req_id>/requests", methods=["POST"])
465
449
  def create_status_request_endpoint(req_id):
466
450
  """
467
451
  Create a status change request.
468
452
 
469
453
  REQ-tv-d00014-C: POST requests endpoint.
470
454
  """
471
- repo = app.config['REPO_ROOT']
455
+ repo = app.config["REPO_ROOT"]
472
456
  normalized_id = normalize_req_id(req_id)
473
457
  data = request.get_json(silent=True)
474
458
 
475
459
  if not data:
476
- return jsonify({'error': 'No data provided'}), 400
460
+ return jsonify({"error": "No data provided"}), 400
477
461
 
478
462
  try:
479
463
  status_request = StatusRequest.from_dict(data)
480
464
  create_status_request(repo, normalized_id, status_request)
481
465
 
482
466
  # REQ-tv-d00014-H: Auto-sync after write operation
483
- user = status_request.requestedBy or 'system'
467
+ user = status_request.requestedBy or "system"
484
468
  sync_result = trigger_auto_sync(
485
469
  f"Status change request for REQ-{normalized_id}: "
486
470
  f"{status_request.fromStatus} -> {status_request.toStatus}",
487
- user
471
+ user,
488
472
  )
489
473
 
490
- response = {'success': True, 'request': status_request.to_dict()}
474
+ response = {"success": True, "request": status_request.to_dict()}
491
475
  if sync_result:
492
- response['sync'] = sync_result
476
+ response["sync"] = sync_result
493
477
 
494
478
  return jsonify(response), 201
495
479
  except Exception as e:
496
- return jsonify({'error': str(e)}), 400
480
+ return jsonify({"error": str(e)}), 400
497
481
 
498
- @app.route('/api/reviews/reqs/<req_id>/requests/<request_id>/approvals', methods=['POST'])
482
+ @app.route("/api/reviews/reqs/<req_id>/requests/<request_id>/approvals", methods=["POST"])
499
483
  def add_approval_endpoint(req_id, request_id):
500
484
  """
501
485
  Add an approval to a status change request.
502
486
 
503
487
  REQ-tv-d00014-C: POST approvals endpoint.
504
488
  """
505
- repo = app.config['REPO_ROOT']
489
+ repo = app.config["REPO_ROOT"]
506
490
  normalized_id = normalize_req_id(req_id)
507
491
  data = request.get_json(silent=True)
508
492
 
509
493
  if not data:
510
- return jsonify({'error': 'No data provided'}), 400
494
+ return jsonify({"error": "No data provided"}), 400
511
495
 
512
496
  try:
513
497
  approval = Approval.from_dict(data)
514
- add_approval(repo, normalized_id, request_id, approval.user, approval.decision, approval.comment)
498
+ add_approval(
499
+ repo, normalized_id, request_id, approval.user, approval.decision, approval.comment
500
+ )
515
501
 
516
502
  # REQ-tv-d00014-H: Auto-sync after write operation
517
- user = approval.user or 'system'
503
+ user = approval.user or "system"
518
504
  sync_result = trigger_auto_sync(
519
- f"Approval on REQ-{normalized_id} status request: {approval.decision}",
520
- user
505
+ f"Approval on REQ-{normalized_id} status request: {approval.decision}", user
521
506
  )
522
507
 
523
- response = {'success': True, 'approval': approval.to_dict()}
508
+ response = {"success": True, "approval": approval.to_dict()}
524
509
  if sync_result:
525
- response['sync'] = sync_result
510
+ response["sync"] = sync_result
526
511
 
527
512
  return jsonify(response), 201
528
513
  except Exception as e:
529
- return jsonify({'error': str(e)}), 400
514
+ return jsonify({"error": str(e)}), 400
530
515
 
531
516
  # ==========================================================================
532
517
  # Review Packages API
533
518
  # REQ-tv-d00014-D: Package endpoints for CRUD and membership
534
519
  # ==========================================================================
535
520
 
536
- @app.route('/api/reviews/packages', methods=['GET'])
521
+ @app.route("/api/reviews/packages", methods=["GET"])
537
522
  def get_packages():
538
523
  """
539
524
  Get all review packages.
540
525
 
541
526
  REQ-tv-d00014-D: GET packages endpoint.
542
527
  """
543
- repo = app.config['REPO_ROOT']
528
+ repo = app.config["REPO_ROOT"]
544
529
  pf = load_packages(repo)
545
- return jsonify({
546
- 'packages': [p.to_dict() for p in pf.packages],
547
- 'activePackageId': pf.activePackageId
548
- })
530
+ return jsonify(
531
+ {"packages": [p.to_dict() for p in pf.packages], "activePackageId": pf.activePackageId}
532
+ )
549
533
 
550
- @app.route('/api/reviews/packages', methods=['POST'])
534
+ @app.route("/api/reviews/packages", methods=["POST"])
551
535
  def create_package_endpoint():
552
536
  """
553
537
  Create a new review package.
@@ -556,81 +540,81 @@ def create_app(
556
540
  REQ-d00098-A: Package SHALL record branchName when created.
557
541
  REQ-d00098-B: Package SHALL record creationCommitHash when created.
558
542
  """
559
- repo = app.config['REPO_ROOT']
543
+ repo = app.config["REPO_ROOT"]
560
544
  data = request.get_json(silent=True)
561
545
 
562
546
  if not data:
563
- return jsonify({'error': 'No data provided'}), 400
547
+ return jsonify({"error": "No data provided"}), 400
564
548
 
565
- name = data.get('name')
549
+ name = data.get("name")
566
550
  if not name:
567
- return jsonify({'error': 'name is required'}), 400
551
+ return jsonify({"error": "name is required"}), 400
568
552
 
569
- description = data.get('description', '')
570
- user = data.get('user', 'api')
553
+ description = data.get("description", "")
554
+ user = data.get("user", "api")
571
555
 
572
556
  pkg = ReviewPackage.create(name, description, user)
573
557
 
574
558
  # REQ-d00098: Add git context for audit trail
575
559
  git_context = get_git_context(repo)
576
- pkg.branchName = git_context.get('branchName')
577
- pkg.creationCommitHash = git_context.get('commitHash')
578
- pkg.lastReviewedCommitHash = git_context.get('commitHash')
560
+ pkg.branchName = git_context.get("branchName")
561
+ pkg.creationCommitHash = git_context.get("commitHash")
562
+ pkg.lastReviewedCommitHash = git_context.get("commitHash")
579
563
 
580
564
  create_package(repo, pkg)
581
565
 
582
566
  # REQ-tv-d00014-H: Auto-sync after write operation
583
567
  sync_result = trigger_auto_sync(f"Created package: {name}", user)
584
568
 
585
- response = {'success': True, 'package': pkg.to_dict()}
569
+ response = {"success": True, "package": pkg.to_dict()}
586
570
  if sync_result:
587
- response['sync'] = sync_result
571
+ response["sync"] = sync_result
588
572
 
589
573
  return jsonify(response), 201
590
574
 
591
- @app.route('/api/reviews/packages/<package_id>', methods=['GET'])
575
+ @app.route("/api/reviews/packages/<package_id>", methods=["GET"])
592
576
  def get_package_endpoint(package_id):
593
577
  """
594
578
  Get a specific package.
595
579
 
596
580
  REQ-tv-d00014-D: GET package by ID endpoint.
597
581
  """
598
- repo = app.config['REPO_ROOT']
582
+ repo = app.config["REPO_ROOT"]
599
583
  pf = load_packages(repo)
600
584
  pkg = pf.get_by_id(package_id)
601
585
 
602
586
  if not pkg:
603
- return jsonify({'error': 'Package not found'}), 404
587
+ return jsonify({"error": "Package not found"}), 404
604
588
 
605
589
  return jsonify(pkg.to_dict())
606
590
 
607
- @app.route('/api/reviews/packages/<package_id>', methods=['PUT'])
591
+ @app.route("/api/reviews/packages/<package_id>", methods=["PUT"])
608
592
  def update_package_endpoint(package_id):
609
593
  """
610
594
  Update a package.
611
595
 
612
596
  REQ-tv-d00014-D: PUT package endpoint.
613
597
  """
614
- repo = app.config['REPO_ROOT']
598
+ repo = app.config["REPO_ROOT"]
615
599
  data = request.get_json(silent=True)
616
600
 
617
601
  if not data:
618
- return jsonify({'error': 'No data provided'}), 400
602
+ return jsonify({"error": "No data provided"}), 400
619
603
 
620
- user = data.get('user', 'api')
604
+ user = data.get("user", "api")
621
605
 
622
606
  # Load existing package
623
607
  pf = load_packages(repo)
624
608
  pkg = pf.get_by_id(package_id)
625
609
 
626
610
  if not pkg:
627
- return jsonify({'error': 'Package not found'}), 404
611
+ return jsonify({"error": "Package not found"}), 404
628
612
 
629
613
  # Update fields
630
- if 'name' in data:
631
- pkg.name = data['name']
632
- if 'description' in data:
633
- pkg.description = data['description']
614
+ if "name" in data:
615
+ pkg.name = data["name"]
616
+ if "description" in data:
617
+ pkg.description = data["description"]
634
618
 
635
619
  success = update_package(repo, pkg)
636
620
 
@@ -638,15 +622,15 @@ def create_app(
638
622
  # REQ-tv-d00014-H: Auto-sync after write operation
639
623
  sync_result = trigger_auto_sync(f"Updated package: {pkg.name}", user)
640
624
 
641
- response = {'success': True, 'package': pkg.to_dict()}
625
+ response = {"success": True, "package": pkg.to_dict()}
642
626
  if sync_result:
643
- response['sync'] = sync_result
627
+ response["sync"] = sync_result
644
628
 
645
629
  return jsonify(response)
646
630
  else:
647
- return jsonify({'error': 'Package not found'}), 404
631
+ return jsonify({"error": "Package not found"}), 404
648
632
 
649
- @app.route('/api/reviews/packages/<package_id>', methods=['DELETE'])
633
+ @app.route("/api/reviews/packages/<package_id>", methods=["DELETE"])
650
634
  def delete_package_endpoint(package_id):
651
635
  """
652
636
  Delete a package (archives it instead of destroying).
@@ -656,20 +640,20 @@ def create_app(
656
640
  """
657
641
  from .models import ARCHIVE_REASON_DELETED
658
642
 
659
- repo = app.config['REPO_ROOT']
643
+ repo = app.config["REPO_ROOT"]
660
644
  data = request.get_json(silent=True) or {}
661
- user = data.get('user', 'api')
645
+ user = data.get("user", "api")
662
646
 
663
647
  # Get package name before archiving
664
648
  pf = load_packages(repo)
665
649
  pkg = pf.get_by_id(package_id)
666
650
 
667
651
  if not pkg:
668
- return jsonify({'error': 'Package not found'}), 404
652
+ return jsonify({"error": "Package not found"}), 404
669
653
 
670
654
  # Don't allow deleting default package
671
655
  if pkg.isDefault:
672
- return jsonify({'error': 'Cannot delete default package'}), 400
656
+ return jsonify({"error": "Cannot delete default package"}), 400
673
657
 
674
658
  pkg_name = pkg.name
675
659
 
@@ -681,26 +665,26 @@ def create_app(
681
665
  # REQ-tv-d00014-H: Auto-sync after write operation
682
666
  sync_result = trigger_auto_sync(f"Archived (deleted) package: {pkg_name}", user)
683
667
 
684
- response = {'success': True, 'archived': True}
668
+ response = {"success": True, "archived": True}
685
669
  if sync_result:
686
- response['sync'] = sync_result
670
+ response["sync"] = sync_result
687
671
 
688
672
  return jsonify(response)
689
673
  else:
690
- return jsonify({'error': 'Failed to archive package'}), 400
674
+ return jsonify({"error": "Failed to archive package"}), 400
691
675
  except ValueError as e:
692
- return jsonify({'error': str(e)}), 400
676
+ return jsonify({"error": str(e)}), 400
693
677
 
694
- @app.route('/api/reviews/packages/<package_id>/reqs/<req_id>', methods=['POST'])
678
+ @app.route("/api/reviews/packages/<package_id>/reqs/<req_id>", methods=["POST"])
695
679
  def add_req_to_package_endpoint(package_id, req_id):
696
680
  """
697
681
  Add a REQ to a package.
698
682
 
699
683
  REQ-tv-d00014-D: POST membership endpoint.
700
684
  """
701
- repo = app.config['REPO_ROOT']
685
+ repo = app.config["REPO_ROOT"]
702
686
  data = request.get_json(silent=True) or {}
703
- user = data.get('user', 'api')
687
+ user = data.get("user", "api")
704
688
 
705
689
  normalized_id = normalize_req_id(req_id)
706
690
  success = add_req_to_package(repo, package_id, normalized_id)
@@ -709,24 +693,24 @@ def create_app(
709
693
  # REQ-tv-d00014-H: Auto-sync after write operation
710
694
  sync_result = trigger_auto_sync(f"Added REQ-{normalized_id} to package", user)
711
695
 
712
- response = {'success': True}
696
+ response = {"success": True}
713
697
  if sync_result:
714
- response['sync'] = sync_result
698
+ response["sync"] = sync_result
715
699
 
716
700
  return jsonify(response)
717
701
  else:
718
- return jsonify({'error': 'Package not found'}), 404
702
+ return jsonify({"error": "Package not found"}), 404
719
703
 
720
- @app.route('/api/reviews/packages/<package_id>/reqs/<req_id>', methods=['DELETE'])
704
+ @app.route("/api/reviews/packages/<package_id>/reqs/<req_id>", methods=["DELETE"])
721
705
  def remove_req_from_package_endpoint(package_id, req_id):
722
706
  """
723
707
  Remove a REQ from a package.
724
708
 
725
709
  REQ-tv-d00014-D: DELETE membership endpoint.
726
710
  """
727
- repo = app.config['REPO_ROOT']
711
+ repo = app.config["REPO_ROOT"]
728
712
  data = request.get_json(silent=True) or {}
729
- user = data.get('user', 'api')
713
+ user = data.get("user", "api")
730
714
 
731
715
  normalized_id = normalize_req_id(req_id)
732
716
  success = remove_req_from_package(repo, package_id, normalized_id)
@@ -735,22 +719,22 @@ def create_app(
735
719
  # REQ-tv-d00014-H: Auto-sync after write operation
736
720
  sync_result = trigger_auto_sync(f"Removed REQ-{normalized_id} from package", user)
737
721
 
738
- response = {'success': True}
722
+ response = {"success": True}
739
723
  if sync_result:
740
- response['sync'] = sync_result
724
+ response["sync"] = sync_result
741
725
 
742
726
  return jsonify(response)
743
727
  else:
744
- return jsonify({'error': 'Package not found'}), 404
728
+ return jsonify({"error": "Package not found"}), 404
745
729
 
746
- @app.route('/api/reviews/packages/active', methods=['GET'])
730
+ @app.route("/api/reviews/packages/active", methods=["GET"])
747
731
  def get_active_package_endpoint():
748
732
  """
749
733
  Get the currently active package.
750
734
 
751
735
  REQ-tv-d00014-D: GET active endpoint.
752
736
  """
753
- repo = app.config['REPO_ROOT']
737
+ repo = app.config["REPO_ROOT"]
754
738
  pf = load_packages(repo)
755
739
  pkg = pf.get_active()
756
740
 
@@ -759,25 +743,25 @@ def create_app(
759
743
  else:
760
744
  return jsonify(None)
761
745
 
762
- @app.route('/api/reviews/packages/active', methods=['PUT'])
746
+ @app.route("/api/reviews/packages/active", methods=["PUT"])
763
747
  def set_active_package_endpoint():
764
748
  """
765
749
  Set the active package.
766
750
 
767
751
  REQ-tv-d00014-D: PUT active endpoint.
768
752
  """
769
- repo = app.config['REPO_ROOT']
753
+ repo = app.config["REPO_ROOT"]
770
754
  data = request.get_json(silent=True) or {}
771
- user = data.get('user', 'api')
755
+ user = data.get("user", "api")
772
756
 
773
- package_id = data.get('packageId')
757
+ package_id = data.get("packageId")
774
758
 
775
759
  # Load packages
776
760
  pf = load_packages(repo)
777
761
 
778
762
  # Validate package exists if setting active
779
763
  if package_id and not pf.get_by_id(package_id):
780
- return jsonify({'error': 'Package not found'}), 404
764
+ return jsonify({"error": "Package not found"}), 404
781
765
 
782
766
  # Set active package
783
767
  pf.activePackageId = package_id
@@ -787,9 +771,9 @@ def create_app(
787
771
  msg = f"Set active package: {package_id}" if package_id else "Cleared active package"
788
772
  sync_result = trigger_auto_sync(msg, user)
789
773
 
790
- response = {'success': True, 'activePackageId': package_id}
774
+ response = {"success": True, "activePackageId": package_id}
791
775
  if sync_result:
792
- response['sync'] = sync_result
776
+ response["sync"] = sync_result
793
777
 
794
778
  return jsonify(response)
795
779
 
@@ -799,7 +783,7 @@ def create_app(
799
783
  # REQ-d00099: Review Archive Viewer
800
784
  # ==========================================================================
801
785
 
802
- @app.route('/api/reviews/packages/<package_id>/archive', methods=['POST'])
786
+ @app.route("/api/reviews/packages/<package_id>/archive", methods=["POST"])
803
787
  def archive_package_endpoint(package_id):
804
788
  """
805
789
  Manually archive a package.
@@ -808,20 +792,20 @@ def create_app(
808
792
  """
809
793
  from .models import ARCHIVE_REASON_MANUAL
810
794
 
811
- repo = app.config['REPO_ROOT']
795
+ repo = app.config["REPO_ROOT"]
812
796
  data = request.get_json(silent=True) or {}
813
- user = data.get('user', 'api')
797
+ user = data.get("user", "api")
814
798
 
815
799
  # Get package name for response
816
800
  pf = load_packages(repo)
817
801
  pkg = pf.get_by_id(package_id)
818
802
 
819
803
  if not pkg:
820
- return jsonify({'error': 'Package not found'}), 404
804
+ return jsonify({"error": "Package not found"}), 404
821
805
 
822
806
  # Don't allow archiving default package
823
807
  if pkg.isDefault:
824
- return jsonify({'error': 'Cannot archive default package'}), 400
808
+ return jsonify({"error": "Cannot archive default package"}), 400
825
809
 
826
810
  pkg_name = pkg.name
827
811
 
@@ -832,45 +816,43 @@ def create_app(
832
816
  # REQ-tv-d00014-H: Auto-sync after write operation
833
817
  sync_result = trigger_auto_sync(f"Archived package: {pkg_name}", user)
834
818
 
835
- response = {'success': True, 'archived': True, 'packageId': package_id}
819
+ response = {"success": True, "archived": True, "packageId": package_id}
836
820
  if sync_result:
837
- response['sync'] = sync_result
821
+ response["sync"] = sync_result
838
822
 
839
823
  return jsonify(response)
840
824
  else:
841
- return jsonify({'error': 'Failed to archive package'}), 400
825
+ return jsonify({"error": "Failed to archive package"}), 400
842
826
  except ValueError as e:
843
- return jsonify({'error': str(e)}), 400
827
+ return jsonify({"error": str(e)}), 400
844
828
 
845
- @app.route('/api/reviews/archive', methods=['GET'])
829
+ @app.route("/api/reviews/archive", methods=["GET"])
846
830
  def list_archived_packages_endpoint():
847
831
  """
848
832
  List all archived packages.
849
833
 
850
834
  REQ-d00099-A: The UI SHALL display a list of archived packages.
851
835
  """
852
- repo = app.config['REPO_ROOT']
836
+ repo = app.config["REPO_ROOT"]
853
837
  packages = list_archived_packages(repo)
854
- return jsonify({
855
- 'packages': [p.to_dict() for p in packages]
856
- })
838
+ return jsonify({"packages": [p.to_dict() for p in packages]})
857
839
 
858
- @app.route('/api/reviews/archive/<package_id>', methods=['GET'])
840
+ @app.route("/api/reviews/archive/<package_id>", methods=["GET"])
859
841
  def get_archived_package_endpoint(package_id):
860
842
  """
861
843
  Get a specific archived package.
862
844
 
863
845
  REQ-d00099-B: Archived packages SHALL open in read-only mode.
864
846
  """
865
- repo = app.config['REPO_ROOT']
847
+ repo = app.config["REPO_ROOT"]
866
848
  pkg = get_archived_package(repo, package_id)
867
849
 
868
850
  if not pkg:
869
- return jsonify({'error': 'Archived package not found'}), 404
851
+ return jsonify({"error": "Archived package not found"}), 404
870
852
 
871
853
  return jsonify(pkg.to_dict())
872
854
 
873
- @app.route('/api/reviews/archive/<package_id>/reqs/<req_id>/threads', methods=['GET'])
855
+ @app.route("/api/reviews/archive/<package_id>/reqs/<req_id>/threads", methods=["GET"])
874
856
  def get_archived_threads_endpoint(package_id, req_id):
875
857
  """
876
858
  Get threads for a requirement from an archived package.
@@ -878,13 +860,13 @@ def create_app(
878
860
  REQ-d00099-B: Archived packages SHALL open in read-only mode.
879
861
  REQ-d00097-F: Archived data SHALL be read-only.
880
862
  """
881
- repo = app.config['REPO_ROOT']
863
+ repo = app.config["REPO_ROOT"]
882
864
  normalized_id = normalize_req_id(req_id)
883
865
 
884
866
  threads_file = load_archived_threads(repo, package_id, normalized_id)
885
867
 
886
868
  if not threads_file:
887
- return jsonify({'error': 'Threads not found in archived package'}), 404
869
+ return jsonify({"error": "Threads not found in archived package"}), 404
888
870
 
889
871
  return jsonify(threads_file.to_dict())
890
872
 
@@ -893,87 +875,80 @@ def create_app(
893
875
  # REQ-tv-d00014-E: Sync endpoints for status, push, fetch, fetch-all-package
894
876
  # ==========================================================================
895
877
 
896
- @app.route('/api/reviews/sync/status', methods=['GET'])
878
+ @app.route("/api/reviews/sync/status", methods=["GET"])
897
879
  def get_sync_status_endpoint():
898
880
  """
899
881
  Get the current sync status.
900
882
 
901
883
  REQ-tv-d00014-E: GET status endpoint.
902
884
  """
903
- repo = app.config['REPO_ROOT']
885
+ repo = app.config["REPO_ROOT"]
904
886
 
905
887
  # Get basic status info
906
888
  has_changes = has_reviews_changes(repo)
907
889
  context = get_current_package_context(repo)
908
890
 
909
891
  status = {
910
- 'has_changes': has_changes,
911
- 'package_id': context[0] if context else None,
912
- 'user': context[1] if context else None,
913
- 'auto_sync_enabled': app.config.get('AUTO_SYNC', True)
892
+ "has_changes": has_changes,
893
+ "package_id": context[0] if context else None,
894
+ "user": context[1] if context else None,
895
+ "auto_sync_enabled": app.config.get("AUTO_SYNC", True),
914
896
  }
915
897
 
916
898
  return jsonify(status)
917
899
 
918
- @app.route('/api/reviews/sync/push', methods=['POST'])
900
+ @app.route("/api/reviews/sync/push", methods=["POST"])
919
901
  def sync_push():
920
902
  """
921
903
  Manually trigger a sync (commit and push).
922
904
 
923
905
  REQ-tv-d00014-E: POST push endpoint.
924
906
  """
925
- repo = app.config['REPO_ROOT']
907
+ repo = app.config["REPO_ROOT"]
926
908
  data = request.get_json(silent=True) or {}
927
- user = data.get('user', 'manual')
928
- message = data.get('message', 'Manual sync')
909
+ user = data.get("user", "manual")
910
+ message = data.get("message", "Manual sync")
929
911
 
930
912
  success, msg = commit_and_push_reviews(repo, message, user)
931
- return jsonify({'success': success, 'message': msg})
913
+ return jsonify({"success": success, "message": msg})
932
914
 
933
- @app.route('/api/reviews/sync/fetch', methods=['POST'])
915
+ @app.route("/api/reviews/sync/fetch", methods=["POST"])
934
916
  def sync_fetch():
935
917
  """
936
918
  Fetch latest review data from remote.
937
919
 
938
920
  REQ-tv-d00014-E: POST fetch endpoint.
939
921
  """
940
- repo = app.config['REPO_ROOT']
922
+ repo = app.config["REPO_ROOT"]
941
923
 
942
924
  # Get current package context
943
925
  context = get_current_package_context(repo)
944
926
  if context[0]:
945
927
  branches = fetch_package_branches(repo, context[0])
946
- return jsonify({
947
- 'success': True,
948
- 'package_id': context[0],
949
- 'branches_fetched': branches
950
- })
928
+ return jsonify(
929
+ {"success": True, "package_id": context[0], "branches_fetched": branches}
930
+ )
951
931
  else:
952
- return jsonify({
953
- 'success': True,
954
- 'message': 'Not on a review branch',
955
- 'branches_fetched': []
956
- })
932
+ return jsonify(
933
+ {"success": True, "message": "Not on a review branch", "branches_fetched": []}
934
+ )
957
935
 
958
- @app.route('/api/reviews/sync/fetch-all-package', methods=['POST'])
936
+ @app.route("/api/reviews/sync/fetch-all-package", methods=["POST"])
959
937
  def sync_fetch_all_package():
960
938
  """
961
939
  Fetch and merge review data from all users' branches for the current package.
962
940
 
963
941
  REQ-tv-d00014-E: POST fetch-all-package endpoint.
964
942
  """
965
- repo = app.config['REPO_ROOT']
943
+ repo = app.config["REPO_ROOT"]
966
944
 
967
945
  # Get current package context from branch name
968
946
  context = get_current_package_context(repo)
969
947
  if not context[0]:
970
948
  # Not on a review branch - return empty data
971
- return jsonify({
972
- 'threads': {},
973
- 'flags': {},
974
- 'contributors': [],
975
- 'error': 'Not on a review branch'
976
- })
949
+ return jsonify(
950
+ {"threads": {}, "flags": {}, "contributors": [], "error": "Not on a review branch"}
951
+ )
977
952
 
978
953
  package_id = context[0]
979
954
 
@@ -981,11 +956,7 @@ def create_app(
981
956
  branches = fetch_package_branches(repo, package_id)
982
957
 
983
958
  # Return information about fetched branches
984
- return jsonify({
985
- 'success': True,
986
- 'package_id': package_id,
987
- 'branches': branches
988
- })
959
+ return jsonify({"success": True, "package_id": package_id, "branches": branches})
989
960
 
990
961
  return app
991
962
 
@@ -994,14 +965,17 @@ def main():
994
965
  """Run the review server"""
995
966
  import argparse
996
967
 
997
- parser = argparse.ArgumentParser(description='Review API Server')
998
- parser.add_argument('--port', type=int, default=8080, help='Port to run on')
999
- parser.add_argument('--host', default='0.0.0.0', help='Host to bind to')
1000
- parser.add_argument('--repo', type=Path, default=Path.cwd(), help='Repository root')
1001
- parser.add_argument('--static', type=Path, default=None, help='Static files directory')
1002
- parser.add_argument('--debug', action='store_true', help='Enable debug mode')
1003
- parser.add_argument('--no-auto-sync', action='store_true',
1004
- help='Disable automatic git commit/push after changes')
968
+ parser = argparse.ArgumentParser(description="Review API Server")
969
+ parser.add_argument("--port", type=int, default=8080, help="Port to run on")
970
+ parser.add_argument("--host", default="0.0.0.0", help="Host to bind to")
971
+ parser.add_argument("--repo", type=Path, default=Path.cwd(), help="Repository root")
972
+ parser.add_argument("--static", type=Path, default=None, help="Static files directory")
973
+ parser.add_argument("--debug", action="store_true", help="Enable debug mode")
974
+ parser.add_argument(
975
+ "--no-auto-sync",
976
+ action="store_true",
977
+ help="Disable automatic git commit/push after changes",
978
+ )
1005
979
 
1006
980
  args = parser.parse_args()
1007
981
 
@@ -1010,7 +984,8 @@ def main():
1010
984
  app = create_app(args.repo.resolve(), static_dir=static_dir, auto_sync=auto_sync)
1011
985
 
1012
986
  sync_status = "ENABLED" if auto_sync else "DISABLED"
1013
- print(f"""
987
+ print(
988
+ f"""
1014
989
  ======================================
1015
990
  Review API Server
1016
991
  ======================================
@@ -1047,10 +1022,11 @@ API Endpoints:
1047
1022
  POST /api/reviews/sync/fetch-all-package - Fetch all package branches
1048
1023
 
1049
1024
  Press Ctrl+C to stop
1050
- """)
1025
+ """
1026
+ )
1051
1027
 
1052
1028
  app.run(host=args.host, port=args.port, debug=args.debug)
1053
1029
 
1054
1030
 
1055
- if __name__ == '__main__':
1031
+ if __name__ == "__main__":
1056
1032
  main()