specfact-cli 0.4.2__py3-none-any.whl → 0.6.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- specfact_cli/__init__.py +1 -1
- specfact_cli/agents/analyze_agent.py +2 -3
- specfact_cli/analyzers/__init__.py +2 -1
- specfact_cli/analyzers/ambiguity_scanner.py +601 -0
- specfact_cli/analyzers/code_analyzer.py +462 -30
- specfact_cli/analyzers/constitution_evidence_extractor.py +491 -0
- specfact_cli/analyzers/contract_extractor.py +419 -0
- specfact_cli/analyzers/control_flow_analyzer.py +281 -0
- specfact_cli/analyzers/requirement_extractor.py +337 -0
- specfact_cli/analyzers/test_pattern_extractor.py +330 -0
- specfact_cli/cli.py +151 -206
- specfact_cli/commands/constitution.py +281 -0
- specfact_cli/commands/enforce.py +42 -34
- specfact_cli/commands/import_cmd.py +481 -152
- specfact_cli/commands/init.py +224 -55
- specfact_cli/commands/plan.py +2133 -547
- specfact_cli/commands/repro.py +100 -78
- specfact_cli/commands/sync.py +701 -186
- specfact_cli/enrichers/constitution_enricher.py +765 -0
- specfact_cli/enrichers/plan_enricher.py +294 -0
- specfact_cli/importers/speckit_converter.py +364 -48
- specfact_cli/importers/speckit_scanner.py +65 -0
- specfact_cli/models/plan.py +42 -0
- specfact_cli/resources/mappings/node-async.yaml +49 -0
- specfact_cli/resources/mappings/python-async.yaml +47 -0
- specfact_cli/resources/mappings/speckit-default.yaml +82 -0
- specfact_cli/resources/prompts/specfact-enforce.md +185 -0
- specfact_cli/resources/prompts/specfact-import-from-code.md +626 -0
- specfact_cli/resources/prompts/specfact-plan-add-feature.md +188 -0
- specfact_cli/resources/prompts/specfact-plan-add-story.md +212 -0
- specfact_cli/resources/prompts/specfact-plan-compare.md +571 -0
- specfact_cli/resources/prompts/specfact-plan-init.md +531 -0
- specfact_cli/resources/prompts/specfact-plan-promote.md +352 -0
- specfact_cli/resources/prompts/specfact-plan-review.md +1276 -0
- specfact_cli/resources/prompts/specfact-plan-select.md +401 -0
- specfact_cli/resources/prompts/specfact-plan-update-feature.md +242 -0
- specfact_cli/resources/prompts/specfact-plan-update-idea.md +211 -0
- specfact_cli/resources/prompts/specfact-repro.md +268 -0
- specfact_cli/resources/prompts/specfact-sync.md +497 -0
- specfact_cli/resources/schemas/deviation.schema.json +61 -0
- specfact_cli/resources/schemas/plan.schema.json +204 -0
- specfact_cli/resources/schemas/protocol.schema.json +53 -0
- specfact_cli/resources/templates/github-action.yml.j2 +140 -0
- specfact_cli/resources/templates/plan.bundle.yaml.j2 +141 -0
- specfact_cli/resources/templates/pr-template.md.j2 +58 -0
- specfact_cli/resources/templates/protocol.yaml.j2 +24 -0
- specfact_cli/resources/templates/telemetry.yaml.example +35 -0
- specfact_cli/sync/__init__.py +10 -1
- specfact_cli/sync/watcher.py +268 -0
- specfact_cli/telemetry.py +440 -0
- specfact_cli/utils/acceptance_criteria.py +127 -0
- specfact_cli/utils/enrichment_parser.py +445 -0
- specfact_cli/utils/feature_keys.py +12 -3
- specfact_cli/utils/ide_setup.py +170 -0
- specfact_cli/utils/structure.py +179 -2
- specfact_cli/utils/yaml_utils.py +33 -0
- specfact_cli/validators/repro_checker.py +22 -1
- specfact_cli/validators/schema.py +15 -4
- specfact_cli-0.6.8.dist-info/METADATA +456 -0
- specfact_cli-0.6.8.dist-info/RECORD +99 -0
- {specfact_cli-0.4.2.dist-info → specfact_cli-0.6.8.dist-info}/entry_points.txt +1 -0
- specfact_cli-0.6.8.dist-info/licenses/LICENSE.md +202 -0
- specfact_cli-0.4.2.dist-info/METADATA +0 -370
- specfact_cli-0.4.2.dist-info/RECORD +0 -62
- specfact_cli-0.4.2.dist-info/licenses/LICENSE.md +0 -61
- {specfact_cli-0.4.2.dist-info → specfact_cli-0.6.8.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constitution command - Manage project constitutions.
|
|
3
|
+
|
|
4
|
+
This module provides commands for bootstrapping, enriching, and validating
|
|
5
|
+
project constitutions based on repository context analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
from beartype import beartype
|
|
15
|
+
from icontract import ensure, require
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
from specfact_cli.enrichers.constitution_enricher import ConstitutionEnricher
|
|
19
|
+
from specfact_cli.utils import print_error, print_info, print_success
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
app = typer.Typer(
|
|
23
|
+
help="Manage project constitutions (Spec-Kit compatibility layer). Generates and validates constitutions at .specify/memory/constitution.md for Spec-Kit format compatibility."
|
|
24
|
+
)
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.command("bootstrap")
|
|
29
|
+
@beartype
|
|
30
|
+
@require(lambda repo: repo.exists(), "Repository path must exist")
|
|
31
|
+
@require(lambda repo: repo.is_dir(), "Repository path must be a directory")
|
|
32
|
+
@ensure(lambda result: result is None, "Must return None")
|
|
33
|
+
def bootstrap(
|
|
34
|
+
repo: Path = typer.Option(
|
|
35
|
+
Path("."),
|
|
36
|
+
"--repo",
|
|
37
|
+
help="Repository path (default: current directory)",
|
|
38
|
+
exists=True,
|
|
39
|
+
file_okay=False,
|
|
40
|
+
dir_okay=True,
|
|
41
|
+
),
|
|
42
|
+
output: Path | None = typer.Option(
|
|
43
|
+
None,
|
|
44
|
+
"--output",
|
|
45
|
+
help="Output path for constitution (default: .specify/memory/constitution.md)",
|
|
46
|
+
),
|
|
47
|
+
overwrite: bool = typer.Option(
|
|
48
|
+
False,
|
|
49
|
+
"--overwrite",
|
|
50
|
+
help="Overwrite existing constitution if it exists",
|
|
51
|
+
),
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Generate bootstrap constitution from repository analysis (Spec-Kit compatibility).
|
|
55
|
+
|
|
56
|
+
This command generates a constitution in Spec-Kit format (`.specify/memory/constitution.md`)
|
|
57
|
+
for compatibility with Spec-Kit artifacts and sync operations.
|
|
58
|
+
|
|
59
|
+
**Note**: SpecFact itself uses plan bundles (`.specfact/plans/*.bundle.yaml`) for internal
|
|
60
|
+
operations. Constitutions are only needed when syncing with Spec-Kit or working in Spec-Kit format.
|
|
61
|
+
|
|
62
|
+
Analyzes the repository (README, pyproject.toml, .cursor/rules/, docs/rules/)
|
|
63
|
+
to extract project metadata, development principles, and quality standards,
|
|
64
|
+
then generates a bootstrap constitution template ready for review and adjustment.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
specfact constitution bootstrap --repo .
|
|
68
|
+
specfact constitution bootstrap --repo . --output custom-constitution.md
|
|
69
|
+
"""
|
|
70
|
+
from specfact_cli.telemetry import telemetry
|
|
71
|
+
|
|
72
|
+
with telemetry.track_command("constitution.bootstrap", {"repo": str(repo)}):
|
|
73
|
+
console.print(f"[bold cyan]Generating bootstrap constitution for:[/bold cyan] {repo}")
|
|
74
|
+
|
|
75
|
+
# Determine output path
|
|
76
|
+
if output is None:
|
|
77
|
+
# Use Spec-Kit convention: .specify/memory/constitution.md
|
|
78
|
+
specify_dir = repo / ".specify" / "memory"
|
|
79
|
+
specify_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
output = specify_dir / "constitution.md"
|
|
81
|
+
else:
|
|
82
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
|
|
84
|
+
# Check if constitution already exists
|
|
85
|
+
if output.exists() and not overwrite:
|
|
86
|
+
console.print(f"[yellow]⚠[/yellow] Constitution already exists: {output}")
|
|
87
|
+
console.print("[dim]Use --overwrite to replace it[/dim]")
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
|
|
90
|
+
# Generate bootstrap constitution
|
|
91
|
+
print_info("Analyzing repository...")
|
|
92
|
+
enricher = ConstitutionEnricher()
|
|
93
|
+
enriched_content = enricher.bootstrap(repo, output)
|
|
94
|
+
|
|
95
|
+
# Write constitution
|
|
96
|
+
output.write_text(enriched_content, encoding="utf-8")
|
|
97
|
+
print_success(f"✓ Bootstrap constitution generated: {output}")
|
|
98
|
+
|
|
99
|
+
console.print("\n[bold]Next Steps:[/bold]")
|
|
100
|
+
console.print("1. Review the generated constitution")
|
|
101
|
+
console.print("2. Adjust principles and sections as needed")
|
|
102
|
+
console.print("3. Run 'specfact constitution validate' to check completeness")
|
|
103
|
+
console.print("4. Run 'specfact sync spec-kit' to sync with Spec-Kit artifacts")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.command("enrich")
|
|
107
|
+
@beartype
|
|
108
|
+
@require(lambda repo: repo.exists(), "Repository path must exist")
|
|
109
|
+
@require(lambda repo: repo.is_dir(), "Repository path must be a directory")
|
|
110
|
+
@ensure(lambda result: result is None, "Must return None")
|
|
111
|
+
def enrich(
|
|
112
|
+
repo: Path = typer.Option(
|
|
113
|
+
Path("."),
|
|
114
|
+
"--repo",
|
|
115
|
+
help="Repository path (default: current directory)",
|
|
116
|
+
exists=True,
|
|
117
|
+
file_okay=False,
|
|
118
|
+
dir_okay=True,
|
|
119
|
+
),
|
|
120
|
+
constitution: Path | None = typer.Option(
|
|
121
|
+
None,
|
|
122
|
+
"--constitution",
|
|
123
|
+
help="Path to constitution file (default: .specify/memory/constitution.md)",
|
|
124
|
+
),
|
|
125
|
+
) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Auto-enrich existing constitution with repository context (Spec-Kit compatibility).
|
|
128
|
+
|
|
129
|
+
This command enriches a constitution in Spec-Kit format (`.specify/memory/constitution.md`)
|
|
130
|
+
for compatibility with Spec-Kit artifacts and sync operations.
|
|
131
|
+
|
|
132
|
+
**Note**: SpecFact itself uses plan bundles (`.specfact/plans/*.bundle.yaml`) for internal
|
|
133
|
+
operations. Constitutions are only needed when syncing with Spec-Kit or working in Spec-Kit format.
|
|
134
|
+
|
|
135
|
+
Analyzes the repository and enriches the existing constitution with
|
|
136
|
+
additional principles and details extracted from repository context.
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
specfact constitution enrich --repo .
|
|
140
|
+
"""
|
|
141
|
+
from specfact_cli.telemetry import telemetry
|
|
142
|
+
|
|
143
|
+
with telemetry.track_command("constitution.enrich", {"repo": str(repo)}):
|
|
144
|
+
# Determine constitution path
|
|
145
|
+
if constitution is None:
|
|
146
|
+
constitution = repo / ".specify" / "memory" / "constitution.md"
|
|
147
|
+
|
|
148
|
+
if not constitution.exists():
|
|
149
|
+
console.print(f"[bold red]✗[/bold red] Constitution not found: {constitution}")
|
|
150
|
+
console.print("[dim]Run 'specfact constitution bootstrap' first[/dim]")
|
|
151
|
+
raise typer.Exit(1)
|
|
152
|
+
|
|
153
|
+
console.print(f"[bold cyan]Enriching constitution:[/bold cyan] {constitution}")
|
|
154
|
+
|
|
155
|
+
# Analyze repository
|
|
156
|
+
print_info("Analyzing repository...")
|
|
157
|
+
enricher = ConstitutionEnricher()
|
|
158
|
+
analysis = enricher.analyze_repository(repo)
|
|
159
|
+
|
|
160
|
+
# Suggest additional principles
|
|
161
|
+
principles = enricher.suggest_principles(analysis)
|
|
162
|
+
|
|
163
|
+
console.print(f"[dim]Found {len(principles)} suggested principles[/dim]")
|
|
164
|
+
|
|
165
|
+
# Read existing constitution
|
|
166
|
+
existing_content = constitution.read_text(encoding="utf-8")
|
|
167
|
+
|
|
168
|
+
# Check if enrichment is needed (has placeholders)
|
|
169
|
+
import re
|
|
170
|
+
|
|
171
|
+
placeholder_pattern = r"\[[A-Z_0-9]+\]"
|
|
172
|
+
placeholders = re.findall(placeholder_pattern, existing_content)
|
|
173
|
+
|
|
174
|
+
if not placeholders:
|
|
175
|
+
console.print("[yellow]⚠[/yellow] Constitution appears complete (no placeholders found)")
|
|
176
|
+
console.print("[dim]No enrichment needed[/dim]")
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
console.print(f"[dim]Found {len(placeholders)} placeholders to enrich[/dim]")
|
|
180
|
+
|
|
181
|
+
# Enrich template
|
|
182
|
+
suggestions: dict[str, Any] = {
|
|
183
|
+
"project_name": analysis.get("project_name", "Project"),
|
|
184
|
+
"principles": principles,
|
|
185
|
+
"section2_name": "Development Workflow",
|
|
186
|
+
"section2_content": enricher._generate_workflow_section(analysis),
|
|
187
|
+
"section3_name": "Quality Standards",
|
|
188
|
+
"section3_content": enricher._generate_quality_standards_section(analysis),
|
|
189
|
+
"governance_rules": "Constitution supersedes all other practices. Amendments require documentation, team approval, and migration plan for breaking changes.",
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
enriched_content = enricher.enrich_template(constitution, suggestions)
|
|
193
|
+
|
|
194
|
+
# Write enriched constitution
|
|
195
|
+
constitution.write_text(enriched_content, encoding="utf-8")
|
|
196
|
+
print_success(f"✓ Constitution enriched: {constitution}")
|
|
197
|
+
|
|
198
|
+
console.print("\n[bold]Next Steps:[/bold]")
|
|
199
|
+
console.print("1. Review the enriched constitution")
|
|
200
|
+
console.print("2. Adjust as needed")
|
|
201
|
+
console.print("3. Run 'specfact constitution validate' to check completeness")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@app.command("validate")
|
|
205
|
+
@beartype
|
|
206
|
+
@require(lambda constitution: constitution.exists(), "Constitution path must exist")
|
|
207
|
+
@ensure(lambda result: result is None, "Must return None")
|
|
208
|
+
def validate(
|
|
209
|
+
constitution: Path = typer.Option(
|
|
210
|
+
Path(".specify/memory/constitution.md"),
|
|
211
|
+
"--constitution",
|
|
212
|
+
help="Path to constitution file",
|
|
213
|
+
exists=True,
|
|
214
|
+
),
|
|
215
|
+
) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Validate constitution completeness (Spec-Kit compatibility).
|
|
218
|
+
|
|
219
|
+
This command validates a constitution in Spec-Kit format (`.specify/memory/constitution.md`)
|
|
220
|
+
for compatibility with Spec-Kit artifacts and sync operations.
|
|
221
|
+
|
|
222
|
+
**Note**: SpecFact itself uses plan bundles (`.specfact/plans/*.bundle.yaml`) for internal
|
|
223
|
+
operations. Constitutions are only needed when syncing with Spec-Kit or working in Spec-Kit format.
|
|
224
|
+
|
|
225
|
+
Checks if the constitution is complete (no placeholders, has principles,
|
|
226
|
+
has governance section, etc.).
|
|
227
|
+
|
|
228
|
+
Example:
|
|
229
|
+
specfact constitution validate
|
|
230
|
+
specfact constitution validate --constitution custom-constitution.md
|
|
231
|
+
"""
|
|
232
|
+
from specfact_cli.telemetry import telemetry
|
|
233
|
+
|
|
234
|
+
with telemetry.track_command("constitution.validate", {"constitution": str(constitution)}):
|
|
235
|
+
console.print(f"[bold cyan]Validating constitution:[/bold cyan] {constitution}")
|
|
236
|
+
|
|
237
|
+
enricher = ConstitutionEnricher()
|
|
238
|
+
is_valid, issues = enricher.validate(constitution)
|
|
239
|
+
|
|
240
|
+
if is_valid:
|
|
241
|
+
print_success("✓ Constitution is valid and complete")
|
|
242
|
+
else:
|
|
243
|
+
print_error("✗ Constitution validation failed")
|
|
244
|
+
console.print("\n[bold]Issues found:[/bold]")
|
|
245
|
+
for issue in issues:
|
|
246
|
+
console.print(f" - {issue}")
|
|
247
|
+
|
|
248
|
+
console.print("\n[bold]Next Steps:[/bold]")
|
|
249
|
+
console.print("1. Run 'specfact constitution bootstrap' to generate a complete constitution")
|
|
250
|
+
console.print("2. Or run 'specfact constitution enrich' to enrich existing constitution")
|
|
251
|
+
raise typer.Exit(1)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def is_constitution_minimal(constitution_path: Path) -> bool:
|
|
255
|
+
"""
|
|
256
|
+
Check if constitution is minimal (essentially empty).
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
constitution_path: Path to constitution file
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
True if constitution is minimal, False otherwise
|
|
263
|
+
"""
|
|
264
|
+
if not constitution_path.exists():
|
|
265
|
+
return True
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
content = constitution_path.read_text(encoding="utf-8").strip()
|
|
269
|
+
# Check if it's just a header or very minimal
|
|
270
|
+
if not content or content == "# Constitution" or len(content) < 100:
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
# Check if it has mostly placeholders
|
|
274
|
+
import re
|
|
275
|
+
|
|
276
|
+
placeholder_pattern = r"\[[A-Z_0-9]+\]"
|
|
277
|
+
placeholders = re.findall(placeholder_pattern, content)
|
|
278
|
+
lines = [line.strip() for line in content.split("\n") if line.strip()]
|
|
279
|
+
return bool(lines and len(placeholders) > len(lines) * 0.5)
|
|
280
|
+
except Exception:
|
|
281
|
+
return True
|
specfact_cli/commands/enforce.py
CHANGED
|
@@ -13,6 +13,7 @@ from rich.console import Console
|
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
15
|
from specfact_cli.models.enforcement import EnforcementConfig, EnforcementPreset
|
|
16
|
+
from specfact_cli.telemetry import telemetry
|
|
16
17
|
from specfact_cli.utils.structure import SpecFactStructure
|
|
17
18
|
from specfact_cli.utils.yaml_utils import dump_yaml
|
|
18
19
|
|
|
@@ -41,48 +42,55 @@ def stage(
|
|
|
41
42
|
Example:
|
|
42
43
|
specfact enforce stage --preset balanced
|
|
43
44
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
raise typer.Exit(1)
|
|
45
|
+
telemetry_metadata = {
|
|
46
|
+
"preset": preset.lower(),
|
|
47
|
+
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
with telemetry.track_command("enforce.stage", telemetry_metadata) as record:
|
|
50
|
+
# Validate preset (contract-style validation)
|
|
51
|
+
if not isinstance(preset, str) or len(preset) == 0:
|
|
52
|
+
console.print("[bold red]✗[/bold red] Preset must be non-empty string")
|
|
53
|
+
raise typer.Exit(1)
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
if preset.lower() not in ("minimal", "balanced", "strict"):
|
|
56
|
+
console.print(f"[bold red]✗[/bold red] Unknown preset: {preset}")
|
|
57
|
+
console.print("Valid presets: minimal, balanced, strict")
|
|
58
|
+
raise typer.Exit(1)
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
preset_enum = EnforcementPreset(preset)
|
|
59
|
-
except ValueError as err:
|
|
60
|
-
console.print(f"[bold red]✗[/bold red] Unknown preset: {preset}")
|
|
61
|
-
console.print("Valid presets: minimal, balanced, strict")
|
|
62
|
-
raise typer.Exit(1) from err
|
|
60
|
+
console.print(f"[bold cyan]Setting enforcement mode:[/bold cyan] {preset}")
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
# Validate preset enum
|
|
63
|
+
try:
|
|
64
|
+
preset_enum = EnforcementPreset(preset)
|
|
65
|
+
except ValueError as err:
|
|
66
|
+
console.print(f"[bold red]✗[/bold red] Unknown preset: {preset}")
|
|
67
|
+
console.print("Valid presets: minimal, balanced, strict")
|
|
68
|
+
raise typer.Exit(1) from err
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
table.add_column("Severity", style="cyan")
|
|
70
|
-
table.add_column("Action", style="yellow")
|
|
70
|
+
# Create enforcement configuration
|
|
71
|
+
config = EnforcementConfig.from_preset(preset_enum)
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
table.
|
|
73
|
+
# Display configuration as table
|
|
74
|
+
table = Table(title=f"Enforcement Mode: {preset.upper()}")
|
|
75
|
+
table.add_column("Severity", style="cyan")
|
|
76
|
+
table.add_column("Action", style="yellow")
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
for severity, action in config.to_summary_dict().items():
|
|
79
|
+
table.add_row(severity, action)
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
SpecFactStructure.ensure_structure()
|
|
81
|
+
console.print(table)
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
# Ensure .specfact structure exists
|
|
84
|
+
SpecFactStructure.ensure_structure()
|
|
83
85
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
# Write configuration to file
|
|
87
|
+
config_path = SpecFactStructure.get_enforcement_config_path()
|
|
88
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
# Use mode='json' to convert enums to their string values
|
|
91
|
+
dump_yaml(config.model_dump(mode="json"), config_path)
|
|
92
|
+
|
|
93
|
+
record({"config_saved": True, "enabled": config.enabled})
|
|
94
|
+
|
|
95
|
+
console.print(f"\n[bold green]✓[/bold green] Enforcement mode set to {preset}")
|
|
96
|
+
console.print(f"[dim]Configuration saved to: {config_path}[/dim]")
|