specfact-cli 0.8.0__tar.gz → 0.9.0__tar.gz
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-0.8.0 → specfact_cli-0.9.0}/PKG-INFO +1 -1
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/pyproject.toml +1 -1
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/__init__.py +1 -1
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/cli.py +1 -1
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/enforce.py +39 -37
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/import_cmd.py +222 -127
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/plan.py +491 -383
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/sync.py +277 -168
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/models/__init__.py +36 -0
- specfact_cli-0.9.0/src/specfact_cli/models/bridge.py +203 -0
- specfact_cli-0.9.0/src/specfact_cli/models/project.py +417 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/sync/__init__.py +9 -0
- specfact_cli-0.9.0/src/specfact_cli/sync/bridge_probe.py +365 -0
- specfact_cli-0.9.0/src/specfact_cli/sync/bridge_sync.py +508 -0
- specfact_cli-0.9.0/src/specfact_cli/sync/bridge_watch.py +449 -0
- specfact_cli-0.9.0/src/specfact_cli/templates/__init__.py +14 -0
- specfact_cli-0.9.0/src/specfact_cli/templates/bridge_templates.py +244 -0
- specfact_cli-0.9.0/src/specfact_cli/utils/bundle_loader.py +339 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/structure.py +122 -1
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/.gitignore +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/LICENSE.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/README.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-enforce.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-import-from-code.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-add-feature.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-add-story.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-compare.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-init.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-promote.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-review.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-select.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-update-feature.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-plan-update-idea.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-repro.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/prompts/specfact-sync.md +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/constitution.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/sync/speckit_sync.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.8.0 → specfact_cli-0.9.0}/src/specfact_cli/validators/schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specfact-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions.
|
|
5
5
|
Project-URL: Homepage, https://github.com/nold-ai/specfact-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/nold-ai/specfact-cli.git
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.9.0"
|
|
8
8
|
description = "Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -291,7 +291,7 @@ app.add_typer(
|
|
|
291
291
|
name="constitution",
|
|
292
292
|
help="Manage project constitutions (Spec-Kit compatibility layer)",
|
|
293
293
|
)
|
|
294
|
-
app.add_typer(import_cmd.app, name="import", help="Import codebases and Spec-Kit
|
|
294
|
+
app.add_typer(import_cmd.app, name="import", help="Import codebases and external tool projects (e.g., Spec-Kit, Linear, Jira)")
|
|
295
295
|
app.add_typer(plan.app, name="plan", help="Manage development plans")
|
|
296
296
|
app.add_typer(generate.app, name="generate", help="Generate artifacts from SDD and plans")
|
|
297
297
|
app.add_typer(enforce.app, name="enforce", help="Configure quality gates")
|
|
@@ -104,23 +104,19 @@ def stage(
|
|
|
104
104
|
|
|
105
105
|
@app.command("sdd")
|
|
106
106
|
@beartype
|
|
107
|
+
@require(lambda bundle: isinstance(bundle, str) and len(bundle) > 0, "Bundle name must be non-empty string")
|
|
107
108
|
@require(lambda sdd: sdd is None or isinstance(sdd, Path), "SDD must be None or Path")
|
|
108
|
-
@require(lambda plan: plan is None or isinstance(plan, Path), "Plan must be None or Path")
|
|
109
109
|
@require(
|
|
110
110
|
lambda format: isinstance(format, str) and format.lower() in ("yaml", "json", "markdown"),
|
|
111
111
|
"Format must be yaml, json, or markdown",
|
|
112
112
|
)
|
|
113
113
|
@require(lambda out: out is None or isinstance(out, Path), "Out must be None or Path")
|
|
114
114
|
def enforce_sdd(
|
|
115
|
+
bundle: str = typer.Argument(..., help="Project bundle name (e.g., legacy-api, auth-module)"),
|
|
115
116
|
sdd: Path | None = typer.Option(
|
|
116
117
|
None,
|
|
117
118
|
"--sdd",
|
|
118
|
-
help="Path to SDD manifest (default: .specfact/sdd
|
|
119
|
-
),
|
|
120
|
-
plan: Path | None = typer.Option(
|
|
121
|
-
None,
|
|
122
|
-
"--plan",
|
|
123
|
-
help="Path to plan bundle (default: active plan)",
|
|
119
|
+
help="Path to SDD manifest (default: .specfact/sdd/<bundle-name>.<format>)",
|
|
124
120
|
),
|
|
125
121
|
format: str = typer.Option(
|
|
126
122
|
"yaml",
|
|
@@ -139,21 +135,21 @@ def enforce_sdd(
|
|
|
139
135
|
),
|
|
140
136
|
) -> None:
|
|
141
137
|
"""
|
|
142
|
-
Validate SDD manifest against
|
|
138
|
+
Validate SDD manifest against project bundle and contracts.
|
|
143
139
|
|
|
144
140
|
Checks:
|
|
145
|
-
- SDD ↔
|
|
141
|
+
- SDD ↔ bundle hash match
|
|
146
142
|
- Coverage thresholds (contracts/story, invariants/feature, architecture facets)
|
|
147
143
|
- Frozen sections (hash mismatch detection)
|
|
148
144
|
- Contract density metrics
|
|
149
145
|
|
|
150
146
|
Example:
|
|
151
|
-
specfact enforce sdd
|
|
152
|
-
specfact enforce sdd --
|
|
153
|
-
specfact enforce sdd --format json --out validation-report.json
|
|
147
|
+
specfact enforce sdd legacy-api
|
|
148
|
+
specfact enforce sdd auth-module --format json --out validation-report.json
|
|
154
149
|
"""
|
|
155
|
-
from specfact_cli.migrations.plan_migrator import load_plan_bundle
|
|
156
150
|
from specfact_cli.models.sdd import SDDManifest
|
|
151
|
+
from specfact_cli.utils.bundle_loader import load_project_bundle
|
|
152
|
+
from specfact_cli.utils.structure import SpecFactStructure
|
|
157
153
|
from specfact_cli.utils.structured_io import (
|
|
158
154
|
StructuredFormat,
|
|
159
155
|
dump_structured_file,
|
|
@@ -169,12 +165,19 @@ def enforce_sdd(
|
|
|
169
165
|
console.print("\n[bold cyan]SpecFact CLI - SDD Validation[/bold cyan]")
|
|
170
166
|
console.print("=" * 60)
|
|
171
167
|
|
|
172
|
-
# Find
|
|
168
|
+
# Find bundle directory
|
|
169
|
+
bundle_dir = SpecFactStructure.project_dir(bundle_name=bundle)
|
|
170
|
+
if not bundle_dir.exists():
|
|
171
|
+
console.print(f"[bold red]✗[/bold red] Project bundle not found: {bundle_dir}")
|
|
172
|
+
console.print(f"[dim]Create one with: specfact plan init {bundle}[/dim]")
|
|
173
|
+
raise typer.Exit(1)
|
|
174
|
+
|
|
175
|
+
# Find SDD manifest path (one per bundle: .specfact/sdd/<bundle-name>.yaml)
|
|
173
176
|
if sdd is None:
|
|
174
177
|
base_path = Path(".")
|
|
175
178
|
# Try YAML first, then JSON
|
|
176
|
-
sdd_yaml = base_path / SpecFactStructure.
|
|
177
|
-
sdd_json = base_path / SpecFactStructure.
|
|
179
|
+
sdd_yaml = base_path / SpecFactStructure.SDD / f"{bundle}.yaml"
|
|
180
|
+
sdd_json = base_path / SpecFactStructure.SDD / f"{bundle}.json"
|
|
178
181
|
if sdd_yaml.exists():
|
|
179
182
|
sdd = sdd_yaml
|
|
180
183
|
elif sdd_json.exists():
|
|
@@ -182,47 +185,46 @@ def enforce_sdd(
|
|
|
182
185
|
else:
|
|
183
186
|
console.print("[bold red]✗[/bold red] SDD manifest not found")
|
|
184
187
|
console.print(f"[dim]Expected: {sdd_yaml} or {sdd_json}[/dim]")
|
|
185
|
-
console.print("[dim]Create one with: specfact plan harden[/dim]")
|
|
188
|
+
console.print(f"[dim]Create one with: specfact plan harden {bundle}[/dim]")
|
|
186
189
|
raise typer.Exit(1)
|
|
187
190
|
|
|
188
191
|
if not sdd.exists():
|
|
189
192
|
console.print(f"[bold red]✗[/bold red] SDD manifest not found: {sdd}")
|
|
190
193
|
raise typer.Exit(1)
|
|
191
194
|
|
|
192
|
-
# Find plan path (reuse logic from plan.py)
|
|
193
|
-
plan_path = _find_plan_path(plan)
|
|
194
|
-
if plan_path is None or not plan_path.exists():
|
|
195
|
-
console.print("[bold red]✗[/bold red] Plan bundle not found")
|
|
196
|
-
raise typer.Exit(1)
|
|
197
|
-
|
|
198
195
|
try:
|
|
199
196
|
# Load SDD manifest
|
|
200
197
|
console.print(f"[dim]Loading SDD manifest: {sdd}[/dim]")
|
|
201
198
|
sdd_data = load_structured_file(sdd)
|
|
202
199
|
sdd_manifest = SDDManifest.model_validate(sdd_data)
|
|
203
200
|
|
|
204
|
-
# Load
|
|
205
|
-
console.print(f"[dim]Loading
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
# Load project bundle
|
|
202
|
+
console.print(f"[dim]Loading project bundle: {bundle_dir}[/dim]")
|
|
203
|
+
project_bundle = load_project_bundle(bundle_dir, validate_hashes=False)
|
|
204
|
+
summary = project_bundle.compute_summary(include_hash=True)
|
|
205
|
+
project_hash = summary.content_hash
|
|
209
206
|
|
|
210
|
-
if not
|
|
211
|
-
console.print("[bold red]✗[/bold red] Failed to compute
|
|
207
|
+
if not project_hash:
|
|
208
|
+
console.print("[bold red]✗[/bold red] Failed to compute project bundle hash")
|
|
212
209
|
raise typer.Exit(1)
|
|
213
210
|
|
|
211
|
+
# Convert to PlanBundle for compatibility with validation functions
|
|
212
|
+
from specfact_cli.commands.plan import _convert_project_bundle_to_plan_bundle
|
|
213
|
+
|
|
214
|
+
plan_bundle = _convert_project_bundle_to_plan_bundle(project_bundle)
|
|
215
|
+
|
|
214
216
|
# Create validation report
|
|
215
217
|
report = ValidationReport()
|
|
216
218
|
|
|
217
219
|
# 1. Validate hash match
|
|
218
220
|
console.print("\n[cyan]Validating hash match...[/cyan]")
|
|
219
|
-
if sdd_manifest.plan_bundle_hash !=
|
|
221
|
+
if sdd_manifest.plan_bundle_hash != project_hash:
|
|
220
222
|
deviation = Deviation(
|
|
221
223
|
type=DeviationType.HASH_MISMATCH,
|
|
222
224
|
severity=DeviationSeverity.HIGH,
|
|
223
|
-
description=f"SDD
|
|
224
|
-
location=
|
|
225
|
-
fix_hint="Run 'specfact plan harden' to update SDD manifest with current
|
|
225
|
+
description=f"SDD bundle hash mismatch: expected {project_hash[:16]}..., got {sdd_manifest.plan_bundle_hash[:16]}...",
|
|
226
|
+
location=str(sdd),
|
|
227
|
+
fix_hint=f"Run 'specfact plan harden {bundle}' to update SDD manifest with current bundle hash",
|
|
226
228
|
)
|
|
227
229
|
report.add_deviation(deviation)
|
|
228
230
|
console.print("[bold red]✗[/bold red] Hash mismatch detected")
|
|
@@ -235,10 +237,10 @@ def enforce_sdd(
|
|
|
235
237
|
from specfact_cli.validators.contract_validator import calculate_contract_density, validate_contract_density
|
|
236
238
|
|
|
237
239
|
# Calculate contract density metrics
|
|
238
|
-
metrics = calculate_contract_density(sdd_manifest,
|
|
240
|
+
metrics = calculate_contract_density(sdd_manifest, plan_bundle)
|
|
239
241
|
|
|
240
242
|
# Validate against thresholds
|
|
241
|
-
density_deviations = validate_contract_density(sdd_manifest,
|
|
243
|
+
density_deviations = validate_contract_density(sdd_manifest, plan_bundle, metrics)
|
|
242
244
|
|
|
243
245
|
# Add deviations to report
|
|
244
246
|
for deviation in density_deviations:
|
|
@@ -295,7 +297,7 @@ def enforce_sdd(
|
|
|
295
297
|
|
|
296
298
|
# Save report
|
|
297
299
|
if output_format == "markdown":
|
|
298
|
-
_save_markdown_report(out, report, sdd_manifest, bundle,
|
|
300
|
+
_save_markdown_report(out, report, sdd_manifest, bundle, project_hash)
|
|
299
301
|
elif output_format == "json":
|
|
300
302
|
dump_structured_file(report.model_dump(mode="json"), out, StructuredFormat.JSON)
|
|
301
303
|
else: # yaml
|