specfact-cli 0.6.3__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 (97) hide show
  1. specfact_cli/__init__.py +14 -0
  2. specfact_cli/agents/__init__.py +24 -0
  3. specfact_cli/agents/analyze_agent.py +391 -0
  4. specfact_cli/agents/base.py +95 -0
  5. specfact_cli/agents/plan_agent.py +202 -0
  6. specfact_cli/agents/registry.py +176 -0
  7. specfact_cli/agents/sync_agent.py +133 -0
  8. specfact_cli/analyzers/__init__.py +12 -0
  9. specfact_cli/analyzers/ambiguity_scanner.py +592 -0
  10. specfact_cli/analyzers/code_analyzer.py +1228 -0
  11. specfact_cli/analyzers/contract_extractor.py +419 -0
  12. specfact_cli/analyzers/control_flow_analyzer.py +281 -0
  13. specfact_cli/analyzers/requirement_extractor.py +337 -0
  14. specfact_cli/analyzers/test_pattern_extractor.py +330 -0
  15. specfact_cli/cli.py +264 -0
  16. specfact_cli/commands/__init__.py +7 -0
  17. specfact_cli/commands/constitution.py +261 -0
  18. specfact_cli/commands/enforce.py +96 -0
  19. specfact_cli/commands/import_cmd.py +694 -0
  20. specfact_cli/commands/init.py +143 -0
  21. specfact_cli/commands/plan.py +2398 -0
  22. specfact_cli/commands/repro.py +214 -0
  23. specfact_cli/commands/sync.py +744 -0
  24. specfact_cli/common/__init__.py +25 -0
  25. specfact_cli/common/logger_setup.py +654 -0
  26. specfact_cli/common/logging_utils.py +41 -0
  27. specfact_cli/common/text_utils.py +52 -0
  28. specfact_cli/common/utils.py +48 -0
  29. specfact_cli/comparators/__init__.py +11 -0
  30. specfact_cli/comparators/plan_comparator.py +391 -0
  31. specfact_cli/enrichers/constitution_enricher.py +765 -0
  32. specfact_cli/enrichers/plan_enricher.py +268 -0
  33. specfact_cli/generators/__init__.py +14 -0
  34. specfact_cli/generators/plan_generator.py +105 -0
  35. specfact_cli/generators/protocol_generator.py +115 -0
  36. specfact_cli/generators/report_generator.py +200 -0
  37. specfact_cli/generators/workflow_generator.py +120 -0
  38. specfact_cli/importers/__init__.py +7 -0
  39. specfact_cli/importers/speckit_converter.py +1051 -0
  40. specfact_cli/importers/speckit_scanner.py +776 -0
  41. specfact_cli/models/__init__.py +33 -0
  42. specfact_cli/models/deviation.py +105 -0
  43. specfact_cli/models/enforcement.py +150 -0
  44. specfact_cli/models/plan.py +139 -0
  45. specfact_cli/models/protocol.py +28 -0
  46. specfact_cli/modes/__init__.py +19 -0
  47. specfact_cli/modes/detector.py +126 -0
  48. specfact_cli/modes/router.py +153 -0
  49. specfact_cli/resources/mappings/node-async.yaml +49 -0
  50. specfact_cli/resources/mappings/python-async.yaml +47 -0
  51. specfact_cli/resources/mappings/speckit-default.yaml +82 -0
  52. specfact_cli/resources/prompts/specfact-enforce.md +185 -0
  53. specfact_cli/resources/prompts/specfact-import-from-code.md +597 -0
  54. specfact_cli/resources/prompts/specfact-plan-add-feature.md +188 -0
  55. specfact_cli/resources/prompts/specfact-plan-add-story.md +212 -0
  56. specfact_cli/resources/prompts/specfact-plan-compare.md +571 -0
  57. specfact_cli/resources/prompts/specfact-plan-init.md +531 -0
  58. specfact_cli/resources/prompts/specfact-plan-promote.md +352 -0
  59. specfact_cli/resources/prompts/specfact-plan-review.md +869 -0
  60. specfact_cli/resources/prompts/specfact-plan-select.md +401 -0
  61. specfact_cli/resources/prompts/specfact-plan-update-feature.md +234 -0
  62. specfact_cli/resources/prompts/specfact-plan-update-idea.md +211 -0
  63. specfact_cli/resources/prompts/specfact-repro.md +268 -0
  64. specfact_cli/resources/prompts/specfact-sync.md +457 -0
  65. specfact_cli/resources/schemas/deviation.schema.json +61 -0
  66. specfact_cli/resources/schemas/plan.schema.json +204 -0
  67. specfact_cli/resources/schemas/protocol.schema.json +53 -0
  68. specfact_cli/resources/semgrep/async.yml +285 -0
  69. specfact_cli/resources/templates/github-action.yml.j2 +140 -0
  70. specfact_cli/resources/templates/plan.bundle.yaml.j2 +141 -0
  71. specfact_cli/resources/templates/pr-template.md.j2 +58 -0
  72. specfact_cli/resources/templates/protocol.yaml.j2 +24 -0
  73. specfact_cli/resources/templates/telemetry.yaml.example +35 -0
  74. specfact_cli/sync/__init__.py +21 -0
  75. specfact_cli/sync/repository_sync.py +279 -0
  76. specfact_cli/sync/speckit_sync.py +388 -0
  77. specfact_cli/sync/watcher.py +268 -0
  78. specfact_cli/telemetry.py +440 -0
  79. specfact_cli/utils/__init__.py +58 -0
  80. specfact_cli/utils/console.py +70 -0
  81. specfact_cli/utils/enrichment_parser.py +445 -0
  82. specfact_cli/utils/feature_keys.py +212 -0
  83. specfact_cli/utils/git.py +241 -0
  84. specfact_cli/utils/github_annotations.py +399 -0
  85. specfact_cli/utils/ide_setup.py +389 -0
  86. specfact_cli/utils/prompts.py +180 -0
  87. specfact_cli/utils/structure.py +674 -0
  88. specfact_cli/utils/yaml_utils.py +200 -0
  89. specfact_cli/validators/__init__.py +20 -0
  90. specfact_cli/validators/fsm.py +262 -0
  91. specfact_cli/validators/repro_checker.py +780 -0
  92. specfact_cli/validators/schema.py +196 -0
  93. specfact_cli-0.6.3.dist-info/METADATA +456 -0
  94. specfact_cli-0.6.3.dist-info/RECORD +97 -0
  95. specfact_cli-0.6.3.dist-info/WHEEL +4 -0
  96. specfact_cli-0.6.3.dist-info/entry_points.txt +2 -0
  97. specfact_cli-0.6.3.dist-info/licenses/LICENSE.md +202 -0
@@ -0,0 +1,261 @@
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(help="Manage project constitutions")
23
+ console = Console()
24
+
25
+
26
+ @app.command("bootstrap")
27
+ @beartype
28
+ @require(lambda repo: repo.exists(), "Repository path must exist")
29
+ @require(lambda repo: repo.is_dir(), "Repository path must be a directory")
30
+ @ensure(lambda result: result is None, "Must return None")
31
+ def bootstrap(
32
+ repo: Path = typer.Option(
33
+ Path("."),
34
+ "--repo",
35
+ help="Repository path (default: current directory)",
36
+ exists=True,
37
+ file_okay=False,
38
+ dir_okay=True,
39
+ ),
40
+ output: Path | None = typer.Option(
41
+ None,
42
+ "--output",
43
+ help="Output path for constitution (default: .specify/memory/constitution.md)",
44
+ ),
45
+ overwrite: bool = typer.Option(
46
+ False,
47
+ "--overwrite",
48
+ help="Overwrite existing constitution if it exists",
49
+ ),
50
+ ) -> None:
51
+ """
52
+ Generate bootstrap constitution from repository analysis.
53
+
54
+ Analyzes the repository (README, pyproject.toml, .cursor/rules/, docs/rules/)
55
+ to extract project metadata, development principles, and quality standards,
56
+ then generates a bootstrap constitution template ready for review and adjustment.
57
+
58
+ Example:
59
+ specfact constitution bootstrap --repo .
60
+ specfact constitution bootstrap --repo . --output custom-constitution.md
61
+ """
62
+ from specfact_cli.telemetry import telemetry
63
+
64
+ with telemetry.track_command("constitution.bootstrap", {"repo": str(repo)}):
65
+ console.print(f"[bold cyan]Generating bootstrap constitution for:[/bold cyan] {repo}")
66
+
67
+ # Determine output path
68
+ if output is None:
69
+ # Use Spec-Kit convention: .specify/memory/constitution.md
70
+ specify_dir = repo / ".specify" / "memory"
71
+ specify_dir.mkdir(parents=True, exist_ok=True)
72
+ output = specify_dir / "constitution.md"
73
+ else:
74
+ output.parent.mkdir(parents=True, exist_ok=True)
75
+
76
+ # Check if constitution already exists
77
+ if output.exists() and not overwrite:
78
+ console.print(f"[yellow]⚠[/yellow] Constitution already exists: {output}")
79
+ console.print("[dim]Use --overwrite to replace it[/dim]")
80
+ raise typer.Exit(1)
81
+
82
+ # Generate bootstrap constitution
83
+ print_info("Analyzing repository...")
84
+ enricher = ConstitutionEnricher()
85
+ enriched_content = enricher.bootstrap(repo, output)
86
+
87
+ # Write constitution
88
+ output.write_text(enriched_content, encoding="utf-8")
89
+ print_success(f"✓ Bootstrap constitution generated: {output}")
90
+
91
+ console.print("\n[bold]Next Steps:[/bold]")
92
+ console.print("1. Review the generated constitution")
93
+ console.print("2. Adjust principles and sections as needed")
94
+ console.print("3. Run 'specfact constitution validate' to check completeness")
95
+ console.print("4. Run 'specfact sync spec-kit' to sync with Spec-Kit artifacts")
96
+
97
+
98
+ @app.command("enrich")
99
+ @beartype
100
+ @require(lambda repo: repo.exists(), "Repository path must exist")
101
+ @require(lambda repo: repo.is_dir(), "Repository path must be a directory")
102
+ @ensure(lambda result: result is None, "Must return None")
103
+ def enrich(
104
+ repo: Path = typer.Option(
105
+ Path("."),
106
+ "--repo",
107
+ help="Repository path (default: current directory)",
108
+ exists=True,
109
+ file_okay=False,
110
+ dir_okay=True,
111
+ ),
112
+ constitution: Path | None = typer.Option(
113
+ None,
114
+ "--constitution",
115
+ help="Path to constitution file (default: .specify/memory/constitution.md)",
116
+ ),
117
+ ) -> None:
118
+ """
119
+ Auto-enrich existing constitution with repository context.
120
+
121
+ Analyzes the repository and enriches the existing constitution with
122
+ additional principles and details extracted from repository context.
123
+
124
+ Example:
125
+ specfact constitution enrich --repo .
126
+ """
127
+ from specfact_cli.telemetry import telemetry
128
+
129
+ with telemetry.track_command("constitution.enrich", {"repo": str(repo)}):
130
+ # Determine constitution path
131
+ if constitution is None:
132
+ constitution = repo / ".specify" / "memory" / "constitution.md"
133
+
134
+ if not constitution.exists():
135
+ console.print(f"[bold red]✗[/bold red] Constitution not found: {constitution}")
136
+ console.print("[dim]Run 'specfact constitution bootstrap' first[/dim]")
137
+ raise typer.Exit(1)
138
+
139
+ console.print(f"[bold cyan]Enriching constitution:[/bold cyan] {constitution}")
140
+
141
+ # Analyze repository
142
+ print_info("Analyzing repository...")
143
+ enricher = ConstitutionEnricher()
144
+ analysis = enricher.analyze_repository(repo)
145
+
146
+ # Suggest additional principles
147
+ principles = enricher.suggest_principles(analysis)
148
+
149
+ console.print(f"[dim]Found {len(principles)} suggested principles[/dim]")
150
+
151
+ # Read existing constitution
152
+ existing_content = constitution.read_text(encoding="utf-8")
153
+
154
+ # Check if enrichment is needed (has placeholders)
155
+ import re
156
+
157
+ placeholder_pattern = r"\[[A-Z_0-9]+\]"
158
+ placeholders = re.findall(placeholder_pattern, existing_content)
159
+
160
+ if not placeholders:
161
+ console.print("[yellow]⚠[/yellow] Constitution appears complete (no placeholders found)")
162
+ console.print("[dim]No enrichment needed[/dim]")
163
+ return
164
+
165
+ console.print(f"[dim]Found {len(placeholders)} placeholders to enrich[/dim]")
166
+
167
+ # Enrich template
168
+ suggestions: dict[str, Any] = {
169
+ "project_name": analysis.get("project_name", "Project"),
170
+ "principles": principles,
171
+ "section2_name": "Development Workflow",
172
+ "section2_content": enricher._generate_workflow_section(analysis),
173
+ "section3_name": "Quality Standards",
174
+ "section3_content": enricher._generate_quality_standards_section(analysis),
175
+ "governance_rules": "Constitution supersedes all other practices. Amendments require documentation, team approval, and migration plan for breaking changes.",
176
+ }
177
+
178
+ enriched_content = enricher.enrich_template(constitution, suggestions)
179
+
180
+ # Write enriched constitution
181
+ constitution.write_text(enriched_content, encoding="utf-8")
182
+ print_success(f"✓ Constitution enriched: {constitution}")
183
+
184
+ console.print("\n[bold]Next Steps:[/bold]")
185
+ console.print("1. Review the enriched constitution")
186
+ console.print("2. Adjust as needed")
187
+ console.print("3. Run 'specfact constitution validate' to check completeness")
188
+
189
+
190
+ @app.command("validate")
191
+ @beartype
192
+ @require(lambda constitution: constitution.exists(), "Constitution path must exist")
193
+ @ensure(lambda result: result is None, "Must return None")
194
+ def validate(
195
+ constitution: Path = typer.Option(
196
+ Path(".specify/memory/constitution.md"),
197
+ "--constitution",
198
+ help="Path to constitution file",
199
+ exists=True,
200
+ ),
201
+ ) -> None:
202
+ """
203
+ Validate constitution completeness.
204
+
205
+ Checks if the constitution is complete (no placeholders, has principles,
206
+ has governance section, etc.).
207
+
208
+ Example:
209
+ specfact constitution validate
210
+ specfact constitution validate --constitution custom-constitution.md
211
+ """
212
+ from specfact_cli.telemetry import telemetry
213
+
214
+ with telemetry.track_command("constitution.validate", {"constitution": str(constitution)}):
215
+ console.print(f"[bold cyan]Validating constitution:[/bold cyan] {constitution}")
216
+
217
+ enricher = ConstitutionEnricher()
218
+ is_valid, issues = enricher.validate(constitution)
219
+
220
+ if is_valid:
221
+ print_success("✓ Constitution is valid and complete")
222
+ else:
223
+ print_error("✗ Constitution validation failed")
224
+ console.print("\n[bold]Issues found:[/bold]")
225
+ for issue in issues:
226
+ console.print(f" - {issue}")
227
+
228
+ console.print("\n[bold]Next Steps:[/bold]")
229
+ console.print("1. Run 'specfact constitution bootstrap' to generate a complete constitution")
230
+ console.print("2. Or run 'specfact constitution enrich' to enrich existing constitution")
231
+ raise typer.Exit(1)
232
+
233
+
234
+ def is_constitution_minimal(constitution_path: Path) -> bool:
235
+ """
236
+ Check if constitution is minimal (essentially empty).
237
+
238
+ Args:
239
+ constitution_path: Path to constitution file
240
+
241
+ Returns:
242
+ True if constitution is minimal, False otherwise
243
+ """
244
+ if not constitution_path.exists():
245
+ return True
246
+
247
+ try:
248
+ content = constitution_path.read_text(encoding="utf-8").strip()
249
+ # Check if it's just a header or very minimal
250
+ if not content or content == "# Constitution" or len(content) < 100:
251
+ return True
252
+
253
+ # Check if it has mostly placeholders
254
+ import re
255
+
256
+ placeholder_pattern = r"\[[A-Z_0-9]+\]"
257
+ placeholders = re.findall(placeholder_pattern, content)
258
+ lines = [line.strip() for line in content.split("\n") if line.strip()]
259
+ return bool(lines and len(placeholders) > len(lines) * 0.5)
260
+ except Exception:
261
+ return True
@@ -0,0 +1,96 @@
1
+ """
2
+ Enforce command - Configure contract validation quality gates.
3
+
4
+ This module provides commands for configuring enforcement modes
5
+ and validation policies.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import typer
11
+ from beartype import beartype
12
+ from rich.console import Console
13
+ from rich.table import Table
14
+
15
+ from specfact_cli.models.enforcement import EnforcementConfig, EnforcementPreset
16
+ from specfact_cli.telemetry import telemetry
17
+ from specfact_cli.utils.structure import SpecFactStructure
18
+ from specfact_cli.utils.yaml_utils import dump_yaml
19
+
20
+
21
+ app = typer.Typer(help="Configure quality gates and enforcement modes")
22
+ console = Console()
23
+
24
+
25
+ @app.command("stage")
26
+ @beartype
27
+ def stage(
28
+ preset: str = typer.Option(
29
+ "balanced",
30
+ "--preset",
31
+ help="Enforcement preset (minimal, balanced, strict)",
32
+ ),
33
+ ) -> None:
34
+ """
35
+ Set enforcement mode for contract validation.
36
+
37
+ Modes:
38
+ - minimal: Log violations, never block
39
+ - balanced: Block HIGH severity, warn MEDIUM
40
+ - strict: Block all MEDIUM+ violations
41
+
42
+ Example:
43
+ specfact enforce stage --preset balanced
44
+ """
45
+ telemetry_metadata = {
46
+ "preset": preset.lower(),
47
+ }
48
+
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)
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)
59
+
60
+ console.print(f"[bold cyan]Setting enforcement mode:[/bold cyan] {preset}")
61
+
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
69
+
70
+ # Create enforcement configuration
71
+ config = EnforcementConfig.from_preset(preset_enum)
72
+
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")
77
+
78
+ for severity, action in config.to_summary_dict().items():
79
+ table.add_row(severity, action)
80
+
81
+ console.print(table)
82
+
83
+ # Ensure .specfact structure exists
84
+ SpecFactStructure.ensure_structure()
85
+
86
+ # Write configuration to file
87
+ config_path = SpecFactStructure.get_enforcement_config_path()
88
+ config_path.parent.mkdir(parents=True, exist_ok=True)
89
+
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]")