moai-adk 0.9.0__py3-none-any.whl → 0.9.1__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.
Potentially problematic release.
This version of moai-adk might be problematic. Click here for more details.
- moai_adk/cli/commands/update.py +214 -56
- moai_adk/core/tags/pre_commit_validator.py +0 -1
- moai_adk/core/tags/reporter.py +1 -2
- moai_adk/templates/.claude/hooks/alfred/.moai/cache/version-check.json +9 -0
- moai_adk/templates/.claude/hooks/alfred/README.md +343 -0
- moai_adk/templates/.claude/hooks/alfred/TROUBLESHOOTING.md +471 -0
- moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +10 -77
- moai_adk/templates/.claude/hooks/alfred/shared/handlers/user.py +19 -0
- moai_adk/templates/.github/workflows/tag-report.yml +261 -0
- moai_adk/templates/.github/workflows/tag-validation.yml +176 -0
- moai_adk/templates/.moai/docs/quick-issue-creation-guide.md +219 -0
- moai_adk/templates/.moai/hooks/install.sh +79 -0
- moai_adk/templates/.moai/hooks/pre-commit.sh +66 -0
- moai_adk/templates/.moai/memory/GITFLOW-PROTECTION-POLICY.md +220 -0
- moai_adk/templates/.moai/memory/gitflow-protection-policy.md +30 -140
- moai_adk/templates/.moai/memory/spec-metadata.md +356 -0
- moai_adk/templates/src/moai_adk/core/__init__.py +5 -0
- moai_adk/templates/src/moai_adk/core/tags/__init__.py +86 -0
- moai_adk/templates/src/moai_adk/core/tags/ci_validator.py +433 -0
- moai_adk/templates/src/moai_adk/core/tags/cli.py +283 -0
- moai_adk/templates/src/moai_adk/core/tags/pre_commit_validator.py +354 -0
- moai_adk/templates/src/moai_adk/core/tags/reporter.py +956 -0
- moai_adk/templates/src/moai_adk/core/tags/validator.py +897 -0
- {moai_adk-0.9.0.dist-info → moai_adk-0.9.1.dist-info}/METADATA +69 -333
- {moai_adk-0.9.0.dist-info → moai_adk-0.9.1.dist-info}/RECORD +28 -13
- moai_adk/templates/.claude/hooks/alfred/core/project.py +0 -750
- moai_adk/templates/README.md +0 -256
- {moai_adk-0.9.0.dist-info → moai_adk-0.9.1.dist-info}/WHEEL +0 -0
- {moai_adk-0.9.0.dist-info → moai_adk-0.9.1.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.9.0.dist-info → moai_adk-0.9.1.dist-info}/licenses/LICENSE +0 -0
moai_adk/cli/commands/update.py
CHANGED
|
@@ -37,9 +37,11 @@ Includes:
|
|
|
37
37
|
- Use backup to restore previous state
|
|
38
38
|
- Debug with `python -m moai_adk doctor --verbose`
|
|
39
39
|
"""
|
|
40
|
+
|
|
40
41
|
from __future__ import annotations
|
|
41
42
|
|
|
42
43
|
import json
|
|
44
|
+
import logging
|
|
43
45
|
import subprocess
|
|
44
46
|
from datetime import datetime
|
|
45
47
|
from pathlib import Path
|
|
@@ -53,6 +55,7 @@ from moai_adk import __version__
|
|
|
53
55
|
from moai_adk.core.template.processor import TemplateProcessor
|
|
54
56
|
|
|
55
57
|
console = Console()
|
|
58
|
+
logger = logging.getLogger(__name__)
|
|
56
59
|
|
|
57
60
|
# Constants for tool detection
|
|
58
61
|
TOOL_DETECTION_TIMEOUT = 5 # seconds
|
|
@@ -65,26 +68,31 @@ PIP_COMMAND = ["pip", "install", "--upgrade", "moai-adk"]
|
|
|
65
68
|
# Custom exceptions for better error handling
|
|
66
69
|
class UpdateError(Exception):
|
|
67
70
|
"""Base exception for update operations."""
|
|
71
|
+
|
|
68
72
|
pass
|
|
69
73
|
|
|
70
74
|
|
|
71
75
|
class InstallerNotFoundError(UpdateError):
|
|
72
76
|
"""Raised when no package installer detected."""
|
|
77
|
+
|
|
73
78
|
pass
|
|
74
79
|
|
|
75
80
|
|
|
76
81
|
class NetworkError(UpdateError):
|
|
77
82
|
"""Raised when network operation fails."""
|
|
83
|
+
|
|
78
84
|
pass
|
|
79
85
|
|
|
80
86
|
|
|
81
87
|
class UpgradeError(UpdateError):
|
|
82
88
|
"""Raised when package upgrade fails."""
|
|
89
|
+
|
|
83
90
|
pass
|
|
84
91
|
|
|
85
92
|
|
|
86
93
|
class TemplateSyncError(UpdateError):
|
|
87
94
|
"""Raised when template sync fails."""
|
|
95
|
+
|
|
88
96
|
pass
|
|
89
97
|
|
|
90
98
|
|
|
@@ -96,11 +104,7 @@ def _is_installed_via_uv_tool() -> bool:
|
|
|
96
104
|
"""
|
|
97
105
|
try:
|
|
98
106
|
result = subprocess.run(
|
|
99
|
-
["uv", "tool", "list"],
|
|
100
|
-
capture_output=True,
|
|
101
|
-
text=True,
|
|
102
|
-
timeout=TOOL_DETECTION_TIMEOUT,
|
|
103
|
-
check=False
|
|
107
|
+
["uv", "tool", "list"], capture_output=True, text=True, timeout=TOOL_DETECTION_TIMEOUT, check=False
|
|
104
108
|
)
|
|
105
109
|
return result.returncode == 0 and "moai-adk" in result.stdout
|
|
106
110
|
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
@@ -115,11 +119,7 @@ def _is_installed_via_pipx() -> bool:
|
|
|
115
119
|
"""
|
|
116
120
|
try:
|
|
117
121
|
result = subprocess.run(
|
|
118
|
-
["pipx", "list"],
|
|
119
|
-
capture_output=True,
|
|
120
|
-
text=True,
|
|
121
|
-
timeout=TOOL_DETECTION_TIMEOUT,
|
|
122
|
-
check=False
|
|
122
|
+
["pipx", "list"], capture_output=True, text=True, timeout=TOOL_DETECTION_TIMEOUT, check=False
|
|
123
123
|
)
|
|
124
124
|
return result.returncode == 0 and "moai-adk" in result.stdout
|
|
125
125
|
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
@@ -134,11 +134,7 @@ def _is_installed_via_pip() -> bool:
|
|
|
134
134
|
"""
|
|
135
135
|
try:
|
|
136
136
|
result = subprocess.run(
|
|
137
|
-
["pip", "show", "moai-adk"],
|
|
138
|
-
capture_output=True,
|
|
139
|
-
text=True,
|
|
140
|
-
timeout=TOOL_DETECTION_TIMEOUT,
|
|
141
|
-
check=False
|
|
137
|
+
["pip", "show", "moai-adk"], capture_output=True, text=True, timeout=TOOL_DETECTION_TIMEOUT, check=False
|
|
142
138
|
)
|
|
143
139
|
return result.returncode == 0
|
|
144
140
|
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
@@ -274,6 +270,7 @@ def _get_project_config_version(project_path: Path) -> str:
|
|
|
274
270
|
Raises:
|
|
275
271
|
ValueError: If config.json exists but cannot be parsed
|
|
276
272
|
"""
|
|
273
|
+
|
|
277
274
|
def _is_placeholder(value: str) -> bool:
|
|
278
275
|
"""Check if value contains unsubstituted template placeholders."""
|
|
279
276
|
return isinstance(value, str) and value.startswith("{{") and value.endswith("}}")
|
|
@@ -302,6 +299,200 @@ def _get_project_config_version(project_path: Path) -> str:
|
|
|
302
299
|
raise ValueError(f"Failed to parse project config.json: {e}") from e
|
|
303
300
|
|
|
304
301
|
|
|
302
|
+
# @CODE:UPDATE-CACHE-FIX-001-001-DETECT-STALE
|
|
303
|
+
def _detect_stale_cache(upgrade_output: str, current_version: str, latest_version: str) -> bool:
|
|
304
|
+
"""
|
|
305
|
+
Detect if uv cache is stale by comparing versions.
|
|
306
|
+
|
|
307
|
+
A stale cache occurs when PyPI metadata is outdated, causing uv to incorrectly
|
|
308
|
+
report "Nothing to upgrade" even though a newer version exists. This function
|
|
309
|
+
detects this condition by:
|
|
310
|
+
1. Checking if upgrade output contains "Nothing to upgrade"
|
|
311
|
+
2. Verifying that latest version is actually newer than current version
|
|
312
|
+
|
|
313
|
+
Uses packaging.version.parse() for robust semantic version comparison that
|
|
314
|
+
handles pre-releases, dev versions, and other PEP 440 version formats correctly.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
upgrade_output: Output from uv tool upgrade command
|
|
318
|
+
current_version: Currently installed version (string, e.g., "0.8.3")
|
|
319
|
+
latest_version: Latest version available on PyPI (string, e.g., "0.9.0")
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
True if cache is stale (output shows "Nothing to upgrade" but current < latest),
|
|
323
|
+
False otherwise
|
|
324
|
+
|
|
325
|
+
Examples:
|
|
326
|
+
>>> _detect_stale_cache("Nothing to upgrade", "0.8.3", "0.9.0")
|
|
327
|
+
True
|
|
328
|
+
>>> _detect_stale_cache("Updated moai-adk", "0.8.3", "0.9.0")
|
|
329
|
+
False
|
|
330
|
+
>>> _detect_stale_cache("Nothing to upgrade", "0.9.0", "0.9.0")
|
|
331
|
+
False
|
|
332
|
+
"""
|
|
333
|
+
# Check if output indicates no upgrade needed
|
|
334
|
+
if not upgrade_output or "Nothing to upgrade" not in upgrade_output:
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
# Compare versions using packaging.version
|
|
338
|
+
try:
|
|
339
|
+
current_v = version.parse(current_version)
|
|
340
|
+
latest_v = version.parse(latest_version)
|
|
341
|
+
return current_v < latest_v
|
|
342
|
+
except (version.InvalidVersion, TypeError) as e:
|
|
343
|
+
# Graceful degradation: if version parsing fails, assume cache is not stale
|
|
344
|
+
logger.debug(f"Version parsing failed: {e}")
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# @CODE:UPDATE-CACHE-FIX-001-002-CLEAR-SUCCESS
|
|
349
|
+
def _clear_uv_package_cache(package_name: str = "moai-adk") -> bool:
|
|
350
|
+
"""
|
|
351
|
+
Clear uv cache for specific package.
|
|
352
|
+
|
|
353
|
+
Executes `uv cache clean <package>` with 10-second timeout to prevent
|
|
354
|
+
hanging on network issues. Provides user-friendly error handling for
|
|
355
|
+
various failure scenarios (timeout, missing uv, etc.).
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
package_name: Package name to clear cache for (default: "moai-adk")
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
True if cache cleared successfully, False otherwise
|
|
362
|
+
|
|
363
|
+
Exceptions:
|
|
364
|
+
- subprocess.TimeoutExpired: Logged as warning, returns False
|
|
365
|
+
- FileNotFoundError: Logged as warning, returns False
|
|
366
|
+
- Exception: Logged as warning, returns False
|
|
367
|
+
|
|
368
|
+
Examples:
|
|
369
|
+
>>> _clear_uv_package_cache("moai-adk")
|
|
370
|
+
True # If uv cache clean succeeds
|
|
371
|
+
"""
|
|
372
|
+
try:
|
|
373
|
+
result = subprocess.run(
|
|
374
|
+
["uv", "cache", "clean", package_name],
|
|
375
|
+
capture_output=True,
|
|
376
|
+
text=True,
|
|
377
|
+
timeout=10, # 10 second timeout
|
|
378
|
+
check=False,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if result.returncode == 0:
|
|
382
|
+
logger.debug(f"UV cache cleared for {package_name}")
|
|
383
|
+
return True
|
|
384
|
+
else:
|
|
385
|
+
logger.warning(f"Failed to clear UV cache: {result.stderr}")
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
except subprocess.TimeoutExpired:
|
|
389
|
+
logger.warning(f"UV cache clean timed out for {package_name}")
|
|
390
|
+
return False
|
|
391
|
+
except FileNotFoundError:
|
|
392
|
+
logger.warning("UV command not found. Is uv installed?")
|
|
393
|
+
return False
|
|
394
|
+
except Exception as e:
|
|
395
|
+
logger.warning(f"Unexpected error clearing cache: {e}")
|
|
396
|
+
return False
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
# @CODE:UPDATE-CACHE-FIX-001-003-RETRY-LOGIC
|
|
400
|
+
def _execute_upgrade_with_retry(installer_cmd: list[str], package_name: str = "moai-adk") -> bool:
|
|
401
|
+
"""
|
|
402
|
+
Execute upgrade with automatic cache retry on stale detection.
|
|
403
|
+
|
|
404
|
+
Implements a robust 7-stage upgrade flow that handles PyPI cache staleness:
|
|
405
|
+
|
|
406
|
+
Stage 1: First upgrade attempt (up to 60 seconds)
|
|
407
|
+
Stage 2: Check success condition (returncode=0 AND no "Nothing to upgrade")
|
|
408
|
+
Stage 3: Detect stale cache using _detect_stale_cache()
|
|
409
|
+
Stage 4: Show user feedback if stale cache detected
|
|
410
|
+
Stage 5: Clear cache using _clear_uv_package_cache()
|
|
411
|
+
Stage 6: Retry upgrade with same command
|
|
412
|
+
Stage 7: Return final result (success or failure)
|
|
413
|
+
|
|
414
|
+
Retry Logic:
|
|
415
|
+
- Only ONE retry is performed to prevent infinite loops
|
|
416
|
+
- Retry only happens if stale cache is detected AND cache clear succeeds
|
|
417
|
+
- Cache clear failures are reported to user with manual workaround
|
|
418
|
+
|
|
419
|
+
User Feedback:
|
|
420
|
+
- Shows emoji-based status messages for each stage
|
|
421
|
+
- Clear guidance on manual workaround if automatic retry fails
|
|
422
|
+
- All errors logged at WARNING level for debugging
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
installer_cmd: Command list from _detect_tool_installer()
|
|
426
|
+
e.g., ["uv", "tool", "upgrade", "moai-adk"]
|
|
427
|
+
package_name: Package name for cache clearing (default: "moai-adk")
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
True if upgrade succeeded (either first attempt or after retry),
|
|
431
|
+
False otherwise
|
|
432
|
+
|
|
433
|
+
Examples:
|
|
434
|
+
>>> # First attempt succeeds
|
|
435
|
+
>>> _execute_upgrade_with_retry(["uv", "tool", "upgrade", "moai-adk"])
|
|
436
|
+
True
|
|
437
|
+
|
|
438
|
+
>>> # First attempt stale, retry succeeds
|
|
439
|
+
>>> _execute_upgrade_with_retry(["uv", "tool", "upgrade", "moai-adk"])
|
|
440
|
+
True # After cache clear and retry
|
|
441
|
+
|
|
442
|
+
Raises:
|
|
443
|
+
subprocess.TimeoutExpired: Re-raised if upgrade command times out
|
|
444
|
+
"""
|
|
445
|
+
# Stage 1: First upgrade attempt
|
|
446
|
+
try:
|
|
447
|
+
result = subprocess.run(installer_cmd, capture_output=True, text=True, timeout=60, check=False)
|
|
448
|
+
except subprocess.TimeoutExpired:
|
|
449
|
+
raise # Re-raise timeout for caller to handle
|
|
450
|
+
except Exception:
|
|
451
|
+
return False
|
|
452
|
+
|
|
453
|
+
# Stage 2: Check if upgrade succeeded without stale cache
|
|
454
|
+
if result.returncode == 0 and "Nothing to upgrade" not in result.stdout:
|
|
455
|
+
return True
|
|
456
|
+
|
|
457
|
+
# Stage 3: Detect stale cache
|
|
458
|
+
try:
|
|
459
|
+
current_version = _get_current_version()
|
|
460
|
+
latest_version = _get_latest_version()
|
|
461
|
+
except RuntimeError:
|
|
462
|
+
# If version check fails, return original result
|
|
463
|
+
return result.returncode == 0
|
|
464
|
+
|
|
465
|
+
if _detect_stale_cache(result.stdout, current_version, latest_version):
|
|
466
|
+
# Stage 4: User feedback
|
|
467
|
+
console.print("[yellow]⚠️ Cache outdated, refreshing...[/yellow]")
|
|
468
|
+
|
|
469
|
+
# Stage 5: Clear cache
|
|
470
|
+
if _clear_uv_package_cache(package_name):
|
|
471
|
+
console.print("[cyan]♻️ Cache cleared, retrying upgrade...[/cyan]")
|
|
472
|
+
|
|
473
|
+
# Stage 6: Retry upgrade
|
|
474
|
+
try:
|
|
475
|
+
result = subprocess.run(installer_cmd, capture_output=True, text=True, timeout=60, check=False)
|
|
476
|
+
|
|
477
|
+
if result.returncode == 0:
|
|
478
|
+
return True
|
|
479
|
+
else:
|
|
480
|
+
console.print("[red]✗ Upgrade failed after retry[/red]")
|
|
481
|
+
return False
|
|
482
|
+
except subprocess.TimeoutExpired:
|
|
483
|
+
raise # Re-raise timeout
|
|
484
|
+
except Exception:
|
|
485
|
+
return False
|
|
486
|
+
else:
|
|
487
|
+
# Cache clear failed
|
|
488
|
+
console.print("[red]✗ Cache clear failed. Manual workaround:[/red]")
|
|
489
|
+
console.print(" [cyan]uv cache clean moai-adk && moai-adk update[/cyan]")
|
|
490
|
+
return False
|
|
491
|
+
|
|
492
|
+
# Stage 7: Cache is not stale, return original result
|
|
493
|
+
return result.returncode == 0
|
|
494
|
+
|
|
495
|
+
|
|
305
496
|
def _execute_upgrade(installer_cmd: list[str]) -> bool:
|
|
306
497
|
"""Execute package upgrade using detected installer.
|
|
307
498
|
|
|
@@ -316,13 +507,7 @@ def _execute_upgrade(installer_cmd: list[str]) -> bool:
|
|
|
316
507
|
subprocess.TimeoutExpired: If upgrade times out
|
|
317
508
|
"""
|
|
318
509
|
try:
|
|
319
|
-
result = subprocess.run(
|
|
320
|
-
installer_cmd,
|
|
321
|
-
capture_output=True,
|
|
322
|
-
text=True,
|
|
323
|
-
timeout=60,
|
|
324
|
-
check=False
|
|
325
|
-
)
|
|
510
|
+
result = subprocess.run(installer_cmd, capture_output=True, text=True, timeout=60, check=False)
|
|
326
511
|
return result.returncode == 0
|
|
327
512
|
except subprocess.TimeoutExpired:
|
|
328
513
|
raise # Re-raise timeout for caller to handle
|
|
@@ -395,10 +580,7 @@ def set_optimized_false(project_path: Path) -> None:
|
|
|
395
580
|
try:
|
|
396
581
|
config_data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
397
582
|
config_data.setdefault("project", {})["optimized"] = False
|
|
398
|
-
config_path.write_text(
|
|
399
|
-
json.dumps(config_data, indent=2, ensure_ascii=False) + "\n",
|
|
400
|
-
encoding="utf-8"
|
|
401
|
-
)
|
|
583
|
+
config_path.write_text(json.dumps(config_data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
402
584
|
except (json.JSONDecodeError, KeyError):
|
|
403
585
|
# Ignore errors if config.json is invalid
|
|
404
586
|
pass
|
|
@@ -536,10 +718,7 @@ def _preserve_project_metadata(
|
|
|
536
718
|
# This allows Stage 2 to compare package vs project template versions
|
|
537
719
|
project_data["template_version"] = version_for_config
|
|
538
720
|
|
|
539
|
-
config_path.write_text(
|
|
540
|
-
json.dumps(config_data, indent=2, ensure_ascii=False) + "\n",
|
|
541
|
-
encoding="utf-8"
|
|
542
|
-
)
|
|
721
|
+
config_path.write_text(json.dumps(config_data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
543
722
|
|
|
544
723
|
|
|
545
724
|
def _apply_context_to_file(processor: TemplateProcessor, target_path: Path) -> None:
|
|
@@ -629,32 +808,11 @@ def _show_timeout_error_help() -> None:
|
|
|
629
808
|
|
|
630
809
|
|
|
631
810
|
@click.command()
|
|
632
|
-
@click.option(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
)
|
|
638
|
-
@click.option(
|
|
639
|
-
"--force",
|
|
640
|
-
is_flag=True,
|
|
641
|
-
help="Skip backup and force the update"
|
|
642
|
-
)
|
|
643
|
-
@click.option(
|
|
644
|
-
"--check",
|
|
645
|
-
is_flag=True,
|
|
646
|
-
help="Only check version (do not update)"
|
|
647
|
-
)
|
|
648
|
-
@click.option(
|
|
649
|
-
"--templates-only",
|
|
650
|
-
is_flag=True,
|
|
651
|
-
help="Skip package upgrade, sync templates only"
|
|
652
|
-
)
|
|
653
|
-
@click.option(
|
|
654
|
-
"--yes",
|
|
655
|
-
is_flag=True,
|
|
656
|
-
help="Auto-confirm all prompts (CI/CD mode)"
|
|
657
|
-
)
|
|
811
|
+
@click.option("--path", type=click.Path(exists=True), default=".", help="Project path (default: current directory)")
|
|
812
|
+
@click.option("--force", is_flag=True, help="Skip backup and force the update")
|
|
813
|
+
@click.option("--check", is_flag=True, help="Only check version (do not update)")
|
|
814
|
+
@click.option("--templates-only", is_flag=True, help="Skip package upgrade, sync templates only")
|
|
815
|
+
@click.option("--yes", is_flag=True, help="Auto-confirm all prompts (CI/CD mode)")
|
|
658
816
|
def update(path: str, force: bool, check: bool, templates_only: bool, yes: bool) -> None:
|
|
659
817
|
"""Update command with 3-stage workflow (v0.6.3+).
|
|
660
818
|
|
|
@@ -206,7 +206,6 @@ class PreCommitValidator:
|
|
|
206
206
|
for line_num, line in enumerate(lines, start=1):
|
|
207
207
|
matches = self.tag_pattern.findall(line)
|
|
208
208
|
for prefix, domain in matches:
|
|
209
|
-
tag = f"@{prefix}:{domain}"
|
|
210
209
|
if domain not in tags_by_type[prefix]:
|
|
211
210
|
tags_by_type[prefix][domain] = []
|
|
212
211
|
tags_by_type[prefix][domain].append((filepath, line_num))
|
moai_adk/core/tags/reporter.py
CHANGED
|
@@ -224,7 +224,6 @@ class InventoryGenerator:
|
|
|
224
224
|
|
|
225
225
|
for tag_type, domain in matches:
|
|
226
226
|
tag_id = domain
|
|
227
|
-
full_tag = f"@{tag_type}:{domain}"
|
|
228
227
|
|
|
229
228
|
# Extract context (±2 lines)
|
|
230
229
|
context_lines = []
|
|
@@ -421,7 +420,7 @@ class MatrixGenerator:
|
|
|
421
420
|
spec = "1" if row["SPEC"] else "0"
|
|
422
421
|
code = "1" if row["CODE"] else "0"
|
|
423
422
|
test = "1" if row["TEST"] else "0"
|
|
424
|
-
|
|
423
|
+
"1" if row["DOC"] else "0"
|
|
425
424
|
completion = f"{matrix.completion_percentages[domain]:.1f}"
|
|
426
425
|
|
|
427
426
|
lines.append(f"{domain},{spec},{code},{test},{completion}")
|