atdd 0.2.1__py3-none-any.whl → 0.2.4__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.
atdd/__main__.py CHANGED
@@ -1,4 +1,4 @@
1
- from .cli import main
1
+ from .cli import cli
2
2
 
3
3
  if __name__ == "__main__":
4
- main()
4
+ raise SystemExit(cli())
atdd/cli.py CHANGED
@@ -10,6 +10,7 @@ The coach orchestrates all ATDD lifecycle operations:
10
10
  - Init: Initialize ATDD structure in consumer repos
11
11
  - Session: Manage session files
12
12
  - Sync: Sync ATDD rules to agent config files
13
+ - Gate: Verify agents loaded ATDD rules
13
14
 
14
15
  Usage:
15
16
  atdd init # Initialize ATDD in consumer repo
@@ -19,6 +20,7 @@ Usage:
19
20
  atdd sync # Sync ATDD rules to agent configs
20
21
  atdd sync --verify # Check if files are in sync
21
22
  atdd sync --agent claude # Sync specific agent only
23
+ atdd gate # Show ATDD gate verification
22
24
  atdd --inventory # Generate inventory
23
25
  atdd --test all # Run all meta-tests
24
26
  atdd --test planner # Run planner phase tests
@@ -41,6 +43,8 @@ from atdd.coach.commands.registry import RegistryUpdater
41
43
  from atdd.coach.commands.initializer import ProjectInitializer
42
44
  from atdd.coach.commands.session import SessionManager
43
45
  from atdd.coach.commands.sync import AgentConfigSync
46
+ from atdd.coach.commands.gate import ATDDGate
47
+ from atdd.version_check import print_update_notice
44
48
 
45
49
 
46
50
  class ATDDCoach:
@@ -156,6 +160,10 @@ Examples:
156
160
  %(prog)s sync --agent claude Sync specific agent only
157
161
  %(prog)s sync --status Show sync status
158
162
 
163
+ # ATDD gate verification
164
+ %(prog)s gate Show gate verification info
165
+ %(prog)s gate --json Output as JSON
166
+
159
167
  # Existing flag-based commands (backwards compatible)
160
168
  %(prog)s --inventory Generate full inventory (YAML)
161
169
  %(prog)s --inventory --format json Generate inventory (JSON)
@@ -268,6 +276,18 @@ Phase descriptions:
268
276
  help="Show sync status for all agents"
269
277
  )
270
278
 
279
+ # ----- atdd gate -----
280
+ gate_parser = subparsers.add_parser(
281
+ "gate",
282
+ help="Show ATDD gate verification info",
283
+ description="Verify agents have loaded ATDD rules before starting work"
284
+ )
285
+ gate_parser.add_argument(
286
+ "--json",
287
+ action="store_true",
288
+ help="Output as JSON for programmatic use"
289
+ )
290
+
271
291
  # ----- Existing flag-based arguments (backwards compatible) -----
272
292
 
273
293
  # Main command groups
@@ -367,6 +387,11 @@ Phase descriptions:
367
387
  return syncer.verify()
368
388
  return syncer.sync(agents=[args.agent] if args.agent else None)
369
389
 
390
+ # atdd gate
391
+ elif args.command == "gate":
392
+ gate = ATDDGate()
393
+ return gate.verify(json=args.json)
394
+
370
395
  # ----- Handle flag-based commands (backwards compatible) -----
371
396
 
372
397
  # Create coach instance
@@ -400,5 +425,14 @@ Phase descriptions:
400
425
  return 0
401
426
 
402
427
 
428
+ def cli() -> int:
429
+ """CLI entry point with version check."""
430
+ try:
431
+ result = main()
432
+ finally:
433
+ print_update_notice()
434
+ return result
435
+
436
+
403
437
  if __name__ == "__main__":
404
- sys.exit(main())
438
+ sys.exit(cli())
@@ -0,0 +1,168 @@
1
+ """
2
+ ATDD Gate verification command.
3
+
4
+ Ensures agents have loaded and confirmed ATDD rules before starting work.
5
+ Outputs the expected hash and key constraints for verification.
6
+
7
+ Usage:
8
+ atdd gate # Show gate verification info
9
+ atdd gate --json # Output as JSON for programmatic use
10
+ """
11
+ import hashlib
12
+ import json as json_module
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional
15
+
16
+ from atdd.coach.commands.sync import AgentConfigSync
17
+
18
+
19
+ class ATDDGate:
20
+ """ATDD Gate verification."""
21
+
22
+ # Key constraints agents must acknowledge
23
+ KEY_CONSTRAINTS = [
24
+ "No ad-hoc tests - follow ATDD conventions",
25
+ "Domain layer NEVER imports from other layers",
26
+ "Phase transitions require quality gates (INIT → PLANNED → RED → GREEN → REFACTOR)",
27
+ ]
28
+
29
+ def __init__(self, target_dir: Optional[Path] = None):
30
+ """
31
+ Initialize the ATDDGate.
32
+
33
+ Args:
34
+ target_dir: Target directory containing agent config files.
35
+ """
36
+ self.target_dir = target_dir or Path.cwd()
37
+ self.syncer = AgentConfigSync(self.target_dir)
38
+
39
+ def _compute_block_hash(self, content: str) -> Optional[str]:
40
+ """
41
+ Compute SHA256 hash of the managed block in content.
42
+
43
+ Args:
44
+ content: File content.
45
+
46
+ Returns:
47
+ SHA256 hash or None if no managed block found.
48
+ """
49
+ block, _, _ = self.syncer._extract_managed_block(content)
50
+ if block is None:
51
+ return None
52
+
53
+ return hashlib.sha256(block.encode()).hexdigest()
54
+
55
+ def _get_synced_files(self) -> Dict[str, Dict]:
56
+ """
57
+ Get info about synced agent config files.
58
+
59
+ Returns:
60
+ Dict mapping agent name to file info.
61
+ """
62
+ agents = self.syncer._get_enabled_agents()
63
+ result = {}
64
+
65
+ for agent in agents:
66
+ target_file = self.syncer.AGENT_FILES.get(agent)
67
+ if not target_file:
68
+ continue
69
+
70
+ target_path = self.target_dir / target_file
71
+ if not target_path.exists():
72
+ result[agent] = {
73
+ "file": target_file,
74
+ "exists": False,
75
+ "hash": None,
76
+ }
77
+ continue
78
+
79
+ content = target_path.read_text()
80
+ block_hash = self._compute_block_hash(content)
81
+
82
+ result[agent] = {
83
+ "file": target_file,
84
+ "exists": True,
85
+ "has_block": block_hash is not None,
86
+ "hash": block_hash[:16] if block_hash else None, # Short hash for display
87
+ "hash_full": block_hash,
88
+ }
89
+
90
+ return result
91
+
92
+ def verify(self, json: bool = False) -> int:
93
+ """
94
+ Output gate verification info.
95
+
96
+ Args:
97
+ json: If True, output as JSON.
98
+
99
+ Returns:
100
+ 0 on success, 1 if no synced files found.
101
+ """
102
+ files = self._get_synced_files()
103
+
104
+ if not files:
105
+ print("No agent config files configured.")
106
+ print("Run 'atdd init' to set up ATDD in this repo.")
107
+ return 1
108
+
109
+ if json:
110
+ output = {
111
+ "files": files,
112
+ "constraints": self.KEY_CONSTRAINTS,
113
+ }
114
+ print(json_module.dumps(output, indent=2))
115
+ return 0
116
+
117
+ # Human-readable output
118
+ print("=" * 60)
119
+ print("ATDD Gate Verification")
120
+ print("=" * 60)
121
+
122
+ print("\nLoaded files:")
123
+ for agent, info in files.items():
124
+ if info["exists"] and info.get("has_block"):
125
+ print(f" - {info['file']} (hash: {info['hash']}...)")
126
+ elif info["exists"]:
127
+ print(f" - {info['file']} (no managed block)")
128
+ else:
129
+ print(f" - {info['file']} (missing)")
130
+
131
+ print("\nKey constraints:")
132
+ for i, constraint in enumerate(self.KEY_CONSTRAINTS, 1):
133
+ print(f" {i}. {constraint}")
134
+
135
+ print("\n" + "-" * 60)
136
+ print("Before starting work, confirm you have loaded these rules.")
137
+ print("-" * 60)
138
+
139
+ return 0
140
+
141
+ def get_confirmation_template(self) -> str:
142
+ """
143
+ Get a template agents can use to confirm gate compliance.
144
+
145
+ Returns:
146
+ Markdown template for gate confirmation.
147
+ """
148
+ files = self._get_synced_files()
149
+
150
+ lines = [
151
+ "## ATDD Gate Confirmation",
152
+ "",
153
+ "**Files loaded:**",
154
+ ]
155
+
156
+ for agent, info in files.items():
157
+ if info["exists"] and info.get("has_block"):
158
+ lines.append(f"- {info['file']} (hash: `{info['hash']}...`)")
159
+
160
+ lines.extend([
161
+ "",
162
+ "**Key constraints acknowledged:**",
163
+ ])
164
+
165
+ for constraint in self.KEY_CONSTRAINTS:
166
+ lines.append(f"- {constraint}")
167
+
168
+ return "\n".join(lines)
@@ -4,6 +4,21 @@ missions:
4
4
  validate_phase_transitions: "Phase transitions and quality gates per conventions and schemas"
5
5
  required: true
6
6
 
7
+ # ATDD Gate (REQUIRED before any work)
8
+ # =====================================
9
+ # Before starting ANY task, you MUST confirm you have loaded these rules.
10
+ # Provide this confirmation at the start of your first response:
11
+ #
12
+ # ## ATDD Gate Confirmation
13
+ # - **File loaded:** CLAUDE.md (or AGENTS.md, etc.)
14
+ # - **Key constraints:**
15
+ # 1. No ad-hoc tests - follow ATDD conventions
16
+ # 2. Domain layer NEVER imports from other layers
17
+ # 3. Phase transitions require quality gates
18
+ #
19
+ # If you cannot provide this confirmation, STOP and reload the rules.
20
+ # Run `atdd gate` to verify the expected values.
21
+
7
22
  manifest:
8
23
  - trains: "plan/_trains.yaml"
9
24
  - wagons: "plan/_wagons.yaml"
atdd/version_check.py ADDED
@@ -0,0 +1,126 @@
1
+ """
2
+ Version check for ATDD CLI.
3
+
4
+ Checks PyPI for newer versions and notifies users. Uses a cached check
5
+ to avoid adding latency to every command.
6
+
7
+ Cache location: ~/.atdd/version_cache.json
8
+ Disable: Set ATDD_NO_UPDATE_CHECK=1 environment variable
9
+ """
10
+ import json
11
+ import os
12
+ import sys
13
+ import time
14
+ from pathlib import Path
15
+ from typing import Optional, Tuple
16
+ from urllib.request import urlopen
17
+ from urllib.error import URLError
18
+
19
+ from atdd import __version__
20
+
21
+ # Check once per day (86400 seconds)
22
+ CHECK_INTERVAL = 86400
23
+ CACHE_DIR = Path.home() / ".atdd"
24
+ CACHE_FILE = CACHE_DIR / "version_cache.json"
25
+ PYPI_URL = "https://pypi.org/pypi/atdd/json"
26
+
27
+
28
+ def _parse_version(version: str) -> Tuple[int, ...]:
29
+ """Parse version string into tuple for comparison."""
30
+ try:
31
+ return tuple(int(x) for x in version.split(".")[:3])
32
+ except (ValueError, AttributeError):
33
+ return (0, 0, 0)
34
+
35
+
36
+ def _is_newer(latest: str, current: str) -> bool:
37
+ """Check if latest version is newer than current."""
38
+ return _parse_version(latest) > _parse_version(current)
39
+
40
+
41
+ def _load_cache() -> dict:
42
+ """Load version cache from disk."""
43
+ try:
44
+ if CACHE_FILE.exists():
45
+ with open(CACHE_FILE) as f:
46
+ return json.load(f)
47
+ except (json.JSONDecodeError, OSError):
48
+ pass
49
+ return {}
50
+
51
+
52
+ def _save_cache(data: dict) -> None:
53
+ """Save version cache to disk."""
54
+ try:
55
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
56
+ with open(CACHE_FILE, "w") as f:
57
+ json.dump(data, f)
58
+ except OSError:
59
+ pass # Silently fail if we can't write cache
60
+
61
+
62
+ def _fetch_latest_version() -> Optional[str]:
63
+ """Fetch latest version from PyPI."""
64
+ try:
65
+ with urlopen(PYPI_URL, timeout=2) as response:
66
+ data = json.loads(response.read().decode())
67
+ return data.get("info", {}).get("version")
68
+ except (URLError, json.JSONDecodeError, OSError, TimeoutError):
69
+ return None
70
+
71
+
72
+ def check_for_updates() -> Optional[str]:
73
+ """
74
+ Check for updates if cache is stale.
75
+
76
+ Returns:
77
+ Message to display if update available, None otherwise.
78
+ """
79
+ # Respect disable flag
80
+ if os.environ.get("ATDD_NO_UPDATE_CHECK", "").lower() in ("1", "true", "yes"):
81
+ return None
82
+
83
+ # Skip if running in development (version 0.0.0)
84
+ if __version__ == "0.0.0":
85
+ return None
86
+
87
+ cache = _load_cache()
88
+ now = time.time()
89
+ last_check = cache.get("last_check", 0)
90
+ cached_latest = cache.get("latest_version")
91
+
92
+ # Check if cache is fresh
93
+ if now - last_check < CHECK_INTERVAL and cached_latest:
94
+ latest = cached_latest
95
+ else:
96
+ # Fetch from PyPI
97
+ latest = _fetch_latest_version()
98
+ if latest:
99
+ _save_cache({
100
+ "last_check": now,
101
+ "latest_version": latest,
102
+ })
103
+ elif cached_latest:
104
+ # Use cached version if fetch failed
105
+ latest = cached_latest
106
+ else:
107
+ return None
108
+
109
+ # Compare versions
110
+ if latest and _is_newer(latest, __version__):
111
+ return (
112
+ f"\nA new version of atdd is available: {__version__} → {latest}\n"
113
+ f"Run `pip install --upgrade atdd` to update."
114
+ )
115
+
116
+ return None
117
+
118
+
119
+ def print_update_notice() -> None:
120
+ """Print update notice to stderr if available."""
121
+ try:
122
+ notice = check_for_updates()
123
+ if notice:
124
+ print(notice, file=sys.stderr)
125
+ except Exception:
126
+ pass # Never fail the main command due to version check
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atdd
3
- Version: 0.2.1
3
+ Version: 0.2.4
4
4
  Summary: ATDD Platform - Acceptance Test Driven Development toolkit
5
5
  License: MIT
6
6
  Requires-Python: >=3.10
@@ -129,6 +129,38 @@ sync:
129
129
  # - qwen # Uncomment to sync QWEN.md
130
130
  ```
131
131
 
132
+ ### ATDD Gate
133
+
134
+ Verify agents have loaded ATDD rules before starting work:
135
+
136
+ ```bash
137
+ atdd gate # Show gate verification info
138
+ atdd gate --json # Output as JSON for programmatic use
139
+ ```
140
+
141
+ Example output:
142
+ ```
143
+ ============================================================
144
+ ATDD Gate Verification
145
+ ============================================================
146
+
147
+ Loaded files:
148
+ - CLAUDE.md (hash: d04f897c6691dc13...)
149
+
150
+ Key constraints:
151
+ 1. No ad-hoc tests - follow ATDD conventions
152
+ 2. Domain layer NEVER imports from other layers
153
+ 3. Phase transitions require quality gates
154
+
155
+ ------------------------------------------------------------
156
+ Before starting work, confirm you have loaded these rules.
157
+ ------------------------------------------------------------
158
+ ```
159
+
160
+ Agents should confirm at the start of each session:
161
+ - Which ATDD file(s) they loaded
162
+ - The key constraints they will follow
163
+
132
164
  ### Validation
133
165
 
134
166
  ```bash
@@ -1,12 +1,14 @@
1
1
  atdd/__init__.py,sha256=-S8i9OahH-t9FJkPn6nprxipnjVum3rLeVsCS74T6eY,156
2
- atdd/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
3
- atdd/cli.py,sha256=POp59pszeoxh5KKJKWxkr0mrWv7-xlg1AWXhONEnipU,13149
2
+ atdd/__main__.py,sha256=B0sXDQLjFN9GowTlXo4NMWwPZPjDsrT8Frq7DnbdOD8,77
3
+ atdd/cli.py,sha256=ONdVyLKHb4PCCmRg2Qi9UCU6CScihKBdPSrzgtdHbg0,14149
4
4
  atdd/conftest.py,sha256=Fj3kIhCETbj2QBCIjySBgdS3stKNRZcZzKTJr7A4LaQ,5300
5
+ atdd/version_check.py,sha256=B9MbbxO_sJrEC3fxFJhTlOIkLLTRQCDO1_8ec1KvuWY,3540
5
6
  atdd/coach/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
7
  atdd/coach/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
8
  atdd/coach/commands/add_persistence_metadata.py,sha256=xAdeO9k53CIGTBhzNQbWenuzeoWKZimDBR9xBWaA3NU,6887
8
9
  atdd/coach/commands/analyze_migrations.py,sha256=2bLfR7OwicBXAZWB4R3Sm4d5jFe87d0O_kO68X96ykU,6083
9
10
  atdd/coach/commands/consumers.py,sha256=7vTexse8xznXSzNWjPGYuF4iJ8ZWymmOSkpcA6IWyxU,27514
11
+ atdd/coach/commands/gate.py,sha256=Snua6kLXV1AE8xV67O5rmbWotFafdEXaN6OF_vnKrr0,4942
10
12
  atdd/coach/commands/infer_governance_status.py,sha256=91-VnI64mOzijc1Cgkmr7cnNCir2-z2ITA2-SGwk3TU,4473
11
13
  atdd/coach/commands/initializer.py,sha256=Hli3hlL_aHnuDIzK0lHzE6KjG2QGvl2E2TvjmYoPPNE,6069
12
14
  atdd/coach/commands/interface.py,sha256=PPCwICFNN4ddPqucUATIiBrfEkDO66MZbYQkwNu6lm4,40459
@@ -25,7 +27,7 @@ atdd/coach/overlays/__init__.py,sha256=2lMiMSgfLJ3YHLpbzNI5B88AdQxiMEwjIfsWWb8t3
25
27
  atdd/coach/overlays/claude.md,sha256=33mhpqhmsRhCtdWlU7cMXAJDsaVra9uBBK8URV8OtQA,101
26
28
  atdd/coach/schemas/config.schema.json,sha256=xzct7gBoPTIGh3NFPSGtfW0zIiyFdHDZkvjuy1qgAqA,951
27
29
  atdd/coach/schemas/manifest.schema.json,sha256=WO13-YF_FgH1awh96khCtk-112b6XSC24anlY3B7GjY,2885
28
- atdd/coach/templates/ATDD.md,sha256=I3pI2EeAQselGqB_-ZDKM6UyIFLp8OSEGUhckDTYIrk,10803
30
+ atdd/coach/templates/ATDD.md,sha256=oHZi7g-quHSmE4NPhN20AFSPrB2N03lzeuldX3LG8Xk,11410
29
31
  atdd/coach/templates/SESSION-TEMPLATE.md,sha256=p_AwdV9ktKS-FGcg8ocDso74NdR7yeMEQO3dJmeb4VQ,8647
30
32
  atdd/coach/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
33
  atdd/coach/utils/graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -176,9 +178,9 @@ atdd/tester/validators/test_red_supabase_layer_structure.py,sha256=26cnzPZAwSFy0
176
178
  atdd/tester/validators/test_telemetry_structure.py,sha256=hIUnU2WU-8PNIg9EVHe2fnUdIQKIOUm5AWEtCBUXLVk,22467
177
179
  atdd/tester/validators/test_typescript_test_naming.py,sha256=E-TyGv_GVlTfsbyuxrtv9sOWSZS_QcpH6rrJFbWoeeU,11280
178
180
  atdd/tester/validators/test_typescript_test_structure.py,sha256=eV89SD1RaKtchBZupqhnJmaruoROosf3LwB4Fwe4UJI,2612
179
- atdd-0.2.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
180
- atdd-0.2.1.dist-info/METADATA,sha256=ePniZ2i1mFTZaHz4dQrXpsxYxq0o0QWUD3K2HA5qS-o,5215
181
- atdd-0.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
182
- atdd-0.2.1.dist-info/entry_points.txt,sha256=z0s90AA3SVM-A2hXQiOw-V3B_ygwqdgeib4-NbKTMyQ,39
183
- atdd-0.2.1.dist-info/top_level.txt,sha256=VKkf6Uiyrm4RS6ULCGM-v8AzYN8K2yg8SMqwJLoO-xs,5
184
- atdd-0.2.1.dist-info/RECORD,,
181
+ atdd-0.2.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
182
+ atdd-0.2.4.dist-info/METADATA,sha256=vahm6OJ7nJ8Sb__7-2nWWprbq2D3Q92kAHoV5sJSkZ0,6119
183
+ atdd-0.2.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
184
+ atdd-0.2.4.dist-info/entry_points.txt,sha256=-C3yrA1WQQfN3iuGmSzPapA5cKVBEYU5Q1HUffSJTbY,38
185
+ atdd-0.2.4.dist-info/top_level.txt,sha256=VKkf6Uiyrm4RS6ULCGM-v8AzYN8K2yg8SMqwJLoO-xs,5
186
+ atdd-0.2.4.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ atdd = atdd.cli:cli
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- atdd = atdd.cli:main
File without changes