speccycle 1.0.0__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 (138) hide show
  1. speccycle/__init__.py +79 -0
  2. speccycle/approvals.py +156 -0
  3. speccycle/auth.py +678 -0
  4. speccycle/cli.py +678 -0
  5. speccycle/config_api.py +325 -0
  6. speccycle/console.py +42 -0
  7. speccycle/context.py +499 -0
  8. speccycle/data/en/agents/behavior.md +37 -0
  9. speccycle/data/en/agents/blueprint.md +42 -0
  10. speccycle/data/en/agents/breakdown.md +30 -0
  11. speccycle/data/en/agents/build.md +36 -0
  12. speccycle/data/en/agents/discovery.md +53 -0
  13. speccycle/data/en/agents/foundation.md +22 -0
  14. speccycle/data/en/agents/intent.md +34 -0
  15. speccycle/data/en/agents/learning.md +28 -0
  16. speccycle/data/en/agents/mentor.md +14 -0
  17. speccycle/data/en/agents/orchestrator.md +12 -0
  18. speccycle/data/en/agents/quality.md +35 -0
  19. speccycle/data/en/checkpoints/ready-for-behavior.md +1 -0
  20. speccycle/data/en/checkpoints/ready-for-blueprint.md +3 -0
  21. speccycle/data/en/checkpoints/ready-for-breakdown.md +2 -0
  22. speccycle/data/en/checkpoints/ready-for-build.md +2 -0
  23. speccycle/data/en/checkpoints/ready-for-intent.md +4 -0
  24. speccycle/data/en/checkpoints/ready-for-quality.md +2 -0
  25. speccycle/data/en/checkpoints/ready-for-release.md +6 -0
  26. speccycle/data/en/commands/behavior.md +17 -0
  27. speccycle/data/en/commands/blueprint.md +17 -0
  28. speccycle/data/en/commands/breakdown.md +17 -0
  29. speccycle/data/en/commands/build.md +17 -0
  30. speccycle/data/en/commands/discovery.md +17 -0
  31. speccycle/data/en/commands/foundation.md +16 -0
  32. speccycle/data/en/commands/intent.md +17 -0
  33. speccycle/data/en/commands/learning.md +16 -0
  34. speccycle/data/en/commands/quality.md +17 -0
  35. speccycle/data/en/commands/scycle.index.md +18 -0
  36. speccycle/data/en/commands/scycle.mentor.md +16 -0
  37. speccycle/data/en/commands/scycle.new.md +14 -0
  38. speccycle/data/en/templates/behaviors-template.md +9 -0
  39. speccycle/data/en/templates/blueprint-template.md +15 -0
  40. speccycle/data/en/templates/breakdown-template.md +9 -0
  41. speccycle/data/en/templates/build-log-template.md +5 -0
  42. speccycle/data/en/templates/discovery-template.md +15 -0
  43. speccycle/data/en/templates/feature-template.feature +7 -0
  44. speccycle/data/en/templates/foundation-template.md +38 -0
  45. speccycle/data/en/templates/increment-template.md +7 -0
  46. speccycle/data/en/templates/intent-template.md +10 -0
  47. speccycle/data/en/templates/learnings-template.md +7 -0
  48. speccycle/data/en/templates/quality-report-template.md +9 -0
  49. speccycle/data/en/workflows/0-foundation.md +9 -0
  50. speccycle/data/en/workflows/1-discovery.md +9 -0
  51. speccycle/data/en/workflows/2-intent.md +8 -0
  52. speccycle/data/en/workflows/3-behavior.md +7 -0
  53. speccycle/data/en/workflows/4-blueprint.md +7 -0
  54. speccycle/data/en/workflows/5-breakdown.md +8 -0
  55. speccycle/data/en/workflows/6-build.md +9 -0
  56. speccycle/data/en/workflows/7-quality.md +10 -0
  57. speccycle/data/en/workflows/8-learning.md +7 -0
  58. speccycle/data/pt/agents/behavior.md +42 -0
  59. speccycle/data/pt/agents/blueprint.md +53 -0
  60. speccycle/data/pt/agents/breakdown.md +42 -0
  61. speccycle/data/pt/agents/build.md +48 -0
  62. speccycle/data/pt/agents/discovery.md +58 -0
  63. speccycle/data/pt/agents/foundation.md +44 -0
  64. speccycle/data/pt/agents/intent.md +45 -0
  65. speccycle/data/pt/agents/learning.md +33 -0
  66. speccycle/data/pt/agents/mentor.md +23 -0
  67. speccycle/data/pt/agents/orchestrator.md +20 -0
  68. speccycle/data/pt/agents/quality.md +42 -0
  69. speccycle/data/pt/checkpoints/ready-for-behavior.md +4 -0
  70. speccycle/data/pt/checkpoints/ready-for-blueprint.md +4 -0
  71. speccycle/data/pt/checkpoints/ready-for-breakdown.md +4 -0
  72. speccycle/data/pt/checkpoints/ready-for-build.md +4 -0
  73. speccycle/data/pt/checkpoints/ready-for-intent.md +4 -0
  74. speccycle/data/pt/checkpoints/ready-for-quality.md +4 -0
  75. speccycle/data/pt/checkpoints/ready-for-release.md +6 -0
  76. speccycle/data/pt/commands/behavior.md +18 -0
  77. speccycle/data/pt/commands/blueprint.md +18 -0
  78. speccycle/data/pt/commands/breakdown.md +18 -0
  79. speccycle/data/pt/commands/build.md +23 -0
  80. speccycle/data/pt/commands/discovery.md +18 -0
  81. speccycle/data/pt/commands/foundation.md +15 -0
  82. speccycle/data/pt/commands/intent.md +18 -0
  83. speccycle/data/pt/commands/learning.md +18 -0
  84. speccycle/data/pt/commands/quality.md +18 -0
  85. speccycle/data/pt/commands/scycle.index.md +51 -0
  86. speccycle/data/pt/commands/scycle.mentor.md +17 -0
  87. speccycle/data/pt/commands/scycle.new.md +14 -0
  88. speccycle/data/pt/templates/behaviors-template.md +9 -0
  89. speccycle/data/pt/templates/blueprint-template.md +20 -0
  90. speccycle/data/pt/templates/breakdown-template.md +6 -0
  91. speccycle/data/pt/templates/build-log-template.md +4 -0
  92. speccycle/data/pt/templates/discovery-template.md +17 -0
  93. speccycle/data/pt/templates/feature-template.feature +7 -0
  94. speccycle/data/pt/templates/foundation-template.md +38 -0
  95. speccycle/data/pt/templates/increment-template.md +9 -0
  96. speccycle/data/pt/templates/intent-template.md +15 -0
  97. speccycle/data/pt/templates/learnings-template.md +9 -0
  98. speccycle/data/pt/templates/quality-report-template.md +12 -0
  99. speccycle/data/pt/workflows/0-foundation.md +9 -0
  100. speccycle/data/pt/workflows/1-discovery.md +8 -0
  101. speccycle/data/pt/workflows/2-intent.md +6 -0
  102. speccycle/data/pt/workflows/3-behavior.md +7 -0
  103. speccycle/data/pt/workflows/4-blueprint.md +8 -0
  104. speccycle/data/pt/workflows/5-breakdown.md +6 -0
  105. speccycle/data/pt/workflows/6-build.md +6 -0
  106. speccycle/data/pt/workflows/7-quality.md +8 -0
  107. speccycle/data/pt/workflows/8-learning.md +6 -0
  108. speccycle/design_cache.py +182 -0
  109. speccycle/env_check.py +73 -0
  110. speccycle/gates.py +755 -0
  111. speccycle/i18n.py +908 -0
  112. speccycle/i18n_web.py +484 -0
  113. speccycle/orchestrator/__init__.py +8 -0
  114. speccycle/orchestrator/antigravity_runtime.py +378 -0
  115. speccycle/orchestrator/claude_runtime.py +222 -0
  116. speccycle/orchestrator/github.py +327 -0
  117. speccycle/orchestrator/runtime.py +191 -0
  118. speccycle/paths.py +17 -0
  119. speccycle/progress.py +203 -0
  120. speccycle/project_config.py +44 -0
  121. speccycle/scaffold.py +2314 -0
  122. speccycle/server.py +1190 -0
  123. speccycle/sessions.py +481 -0
  124. speccycle/telemetry.py +957 -0
  125. speccycle/web/assets/brand/favicon.svg +14 -0
  126. speccycle/web/assets/brand/logo-dark.svg +23 -0
  127. speccycle/web/assets/brand/logo-light.svg +23 -0
  128. speccycle/web/assets/brand/mark-mono.svg +14 -0
  129. speccycle/web/assets/brand/mark.svg +14 -0
  130. speccycle/web/assets/brand/spec-cycle-logo.png +0 -0
  131. speccycle/web/assets/brand/tokens.css +27 -0
  132. speccycle/web/index.html +4954 -0
  133. speccycle-1.0.0.dist-info/METADATA +436 -0
  134. speccycle-1.0.0.dist-info/RECORD +138 -0
  135. speccycle-1.0.0.dist-info/WHEEL +5 -0
  136. speccycle-1.0.0.dist-info/entry_points.txt +2 -0
  137. speccycle-1.0.0.dist-info/licenses/LICENSE +100 -0
  138. speccycle-1.0.0.dist-info/top_level.txt +1 -0
speccycle/__init__.py ADDED
@@ -0,0 +1,79 @@
1
+ __version__ = "1.0.0"
2
+
3
+ from speccycle.i18n import _
4
+
5
+ PHASES = [
6
+ {
7
+ "id": "discovery",
8
+ "n": 1,
9
+ "name": "Discovery",
10
+ "agent": "Discovery Agent",
11
+ "artifact": "discovery.md",
12
+ "command": "/scycle:discovery",
13
+ "desc": _("phase_discovery_desc")
14
+ },
15
+ {
16
+ "id": "intent",
17
+ "n": 2,
18
+ "name": "Intent",
19
+ "agent": "Intent Agent",
20
+ "artifact": "intent.md",
21
+ "command": "/scycle:intent",
22
+ "desc": _("phase_intent_desc")
23
+ },
24
+ {
25
+ "id": "behavior",
26
+ "n": 3,
27
+ "name": "Behavior",
28
+ "agent": "Behavior Agent",
29
+ "artifact": "behaviors.md",
30
+ "command": "/scycle:behavior",
31
+ "desc": _("phase_behavior_desc")
32
+ },
33
+ {
34
+ "id": "blueprint",
35
+ "n": 4,
36
+ "name": "Blueprint",
37
+ "agent": "Blueprint Agent",
38
+ "artifact": "blueprint.md",
39
+ "command": "/scycle:blueprint",
40
+ "desc": _("phase_blueprint_desc")
41
+ },
42
+ {
43
+ "id": "breakdown",
44
+ "n": 5,
45
+ "name": "Breakdown",
46
+ "agent": "Breakdown Agent",
47
+ "artifact": "breakdown.md",
48
+ "command": "/scycle:breakdown",
49
+ "desc": _("phase_breakdown_desc")
50
+ },
51
+ {
52
+ "id": "build",
53
+ "n": 6,
54
+ "name": "Build",
55
+ "agent": "Build Agent",
56
+ "artifact": "build-log.md",
57
+ "command": "/scycle:build",
58
+ "desc": _("phase_build_desc")
59
+ },
60
+ {
61
+ "id": "quality",
62
+ "n": 7,
63
+ "name": "Quality",
64
+ "agent": "Quality Agent",
65
+ "artifact": "quality-report.md",
66
+ "command": "/scycle:quality",
67
+ "desc": _("phase_quality_desc")
68
+ },
69
+ {
70
+ "id": "learning",
71
+ "n": 8,
72
+ "name": "Learning",
73
+ "agent": "Learning Agent",
74
+ "artifact": "learnings.md",
75
+ "command": "/scycle:learn",
76
+ "desc": _("phase_learning_desc")
77
+ }
78
+ ]
79
+
speccycle/approvals.py ADDED
@@ -0,0 +1,156 @@
1
+ """Persistência do ``approvals.json`` da volta, segura entre processos.
2
+
3
+ Quem escreve nesse arquivo: o ``approve`` da CLI, o dashboard (aprovar gate,
4
+ iniciar fase), o ``prompt-gate`` ao reiniciar o Build e a sessão de Quality
5
+ reprovada. São processos distintos, às vezes no mesmo segundo. Toda escrita
6
+ passa por :func:`update_approvals`, que:
7
+
8
+ 1. toma um lock do sistema operacional (``msvcrt``/``fcntl``), liberado
9
+ sozinho se o processo morrer — sem arquivo de lock órfão;
10
+ 2. relê o arquivo **dentro** do lock, para nunca regravar uma leitura antiga;
11
+ 3. grava num temporário e troca com ``os.replace``, para nenhum leitor ver um
12
+ arquivo pela metade;
13
+ 4. recusa-se a sobrescrever um arquivo que não parseia — melhor falhar com
14
+ mensagem do que apagar aprovações.
15
+ """
16
+
17
+ import contextlib
18
+ import json
19
+ import os
20
+ import sys
21
+ import time
22
+
23
+ APPROVALS_FILE = "approvals.json"
24
+ LOCK_FILE = "approvals.lock"
25
+ LOCK_TIMEOUT = 15.0
26
+
27
+
28
+ class ApprovalsLocked(TimeoutError):
29
+ """Outro processo segurou o approvals.json por mais tempo que o limite."""
30
+
31
+
32
+ class ApprovalsCorrupt(ValueError):
33
+ """O approvals.json existe mas não é JSON válido; não vai ser sobrescrito."""
34
+
35
+
36
+ def approvals_path(cycle_path):
37
+ return os.path.join(cycle_path, APPROVALS_FILE)
38
+
39
+
40
+ def _lock_path(cycle_path):
41
+ # cycles/NNN-slug -> <raiz>/.speccycle/approvals.lock
42
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(cycle_path)))
43
+ speccycle_dir = os.path.join(project_root, ".speccycle")
44
+ if not os.path.isdir(speccycle_dir):
45
+ return os.path.join(os.path.abspath(cycle_path), "." + LOCK_FILE)
46
+ return os.path.join(speccycle_dir, LOCK_FILE)
47
+
48
+
49
+ if sys.platform == "win32":
50
+ import msvcrt
51
+
52
+ def _try_lock(fd):
53
+ try:
54
+ msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
55
+ return True
56
+ except OSError:
57
+ return False
58
+
59
+ def _unlock(fd):
60
+ try:
61
+ msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
62
+ except OSError:
63
+ pass
64
+ else:
65
+ import fcntl
66
+
67
+ def _try_lock(fd):
68
+ try:
69
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
70
+ return True
71
+ except OSError:
72
+ return False
73
+
74
+ def _unlock(fd):
75
+ try:
76
+ fcntl.flock(fd, fcntl.LOCK_UN)
77
+ except OSError:
78
+ pass
79
+
80
+
81
+ @contextlib.contextmanager
82
+ def locked(cycle_path, timeout=LOCK_TIMEOUT):
83
+ """Lock exclusivo entre processos para o approvals.json desta volta."""
84
+ lock_path = _lock_path(cycle_path)
85
+ os.makedirs(os.path.dirname(lock_path), exist_ok=True)
86
+ fd = os.open(lock_path, os.O_RDWR | os.O_CREAT)
87
+ try:
88
+ deadline = time.monotonic() + timeout
89
+ while not _try_lock(fd):
90
+ if time.monotonic() > deadline:
91
+ raise ApprovalsLocked(
92
+ f"approvals.json ocupado por outro processo há mais de {timeout:.0f}s ({lock_path})"
93
+ )
94
+ time.sleep(0.02)
95
+ try:
96
+ yield
97
+ finally:
98
+ _unlock(fd)
99
+ finally:
100
+ os.close(fd)
101
+
102
+
103
+ def _load(path, strict):
104
+ if not os.path.isfile(path):
105
+ return {}
106
+ try:
107
+ with open(path, "r", encoding="utf-8") as f:
108
+ data = json.load(f)
109
+ except ValueError as e:
110
+ if strict:
111
+ raise ApprovalsCorrupt(
112
+ f"{path} não é JSON válido ({e}); corrija ou remova o arquivo"
113
+ ) from e
114
+ return {}
115
+ except OSError:
116
+ return {}
117
+ return data if isinstance(data, dict) else {}
118
+
119
+
120
+ def read_approvals(cycle_path):
121
+ """Leitura sem lock: um arquivo inexistente ou ilegível vale ``{}``."""
122
+ return _load(approvals_path(cycle_path), strict=False)
123
+
124
+
125
+ def _atomic_write(path, data):
126
+ tmp = f"{path}.{os.getpid()}.tmp"
127
+ with open(tmp, "w", encoding="utf-8") as f:
128
+ json.dump(data, f, ensure_ascii=False, indent=2)
129
+ f.flush()
130
+ os.fsync(f.fileno())
131
+ # No Windows, os.replace falha enquanto um leitor tiver o arquivo aberto;
132
+ # leituras são curtas, então basta insistir por alguns instantes.
133
+ for attempt in range(50):
134
+ try:
135
+ os.replace(tmp, path)
136
+ return
137
+ except PermissionError:
138
+ if attempt == 49:
139
+ raise
140
+ time.sleep(0.02)
141
+
142
+
143
+ def update_approvals(cycle_path, mutate, timeout=LOCK_TIMEOUT):
144
+ """Aplica ``mutate(approvals)`` ao arquivo atual, sob lock e de forma atômica.
145
+
146
+ ``mutate`` recebe o dict recém-lido e o altera no lugar (ou devolve um
147
+ novo dict). Devolve o conteúdo gravado.
148
+ """
149
+ path = approvals_path(cycle_path)
150
+ with locked(cycle_path, timeout=timeout):
151
+ current = _load(path, strict=True)
152
+ result = mutate(current)
153
+ if not isinstance(result, dict):
154
+ result = current
155
+ _atomic_write(path, result)
156
+ return result