diff --git a/docs/bundles/code-review/run.md b/docs/bundles/code-review/run.md index d1d7ab4..bc14db0 100644 --- a/docs/bundles/code-review/run.md +++ b/docs/bundles/code-review/run.md @@ -101,10 +101,12 @@ Use **`--focus`** with **`source`**, **`tests`**, **`docs`**, and/or **`simplify specfact code review run --scope changed --focus tests specfact code review run --scope full --path packages/specfact-code-review --focus source specfact code review run --scope full --focus docs -specfact code review run --scope changed --focus simplify --preview-fixes --json --out .specfact/code-review-simplify.json -specfact code review run --scope changed --focus simplify --with-mutation --json --out .specfact/code-review-simplify.json +specfact code review run --scope changed --focus simplify --preview-fixes --json --out .specfact/code-review.json +specfact code review run --scope changed --focus simplify --with-mutation --json --out .specfact/code-review.json ``` +Use the canonical `.specfact/code-review.json` path unless every consumer in your workflow has been updated to read a custom simplify report path. + ### AI instructions fallback When an IDE does not support bundled prompts or skills, print the same guided simplify workflow for an AI assistant: @@ -143,7 +145,7 @@ The built-in `specfact/ai-bloat-patterns` policy pack is parallel to `specfact/c Use `--focus simplify` when producing the IDE simplification queue: ```bash -specfact code review run --scope changed --focus simplify --preview-fixes --json --out .specfact/code-review-simplify.json +specfact code review run --scope changed --focus simplify --preview-fixes --json --out .specfact/code-review.json ``` Simplify-focused reports keep advisory `ai_bloat` findings plus high-confidence `dry` and `kiss` findings that include deterministic simplification metadata. Metadata fields such as `rewrite_hint`, `canonical_pattern`, `intent_key`, `estimated_deletion_lines`, `related_locations`, `signal_trace`, `preserve_reasons`, and `remediation_packet` are additive; legacy consumers can keep reading the original finding fields. The report-level `cleanup_forecast` summarizes reviewed LOC, estimated deletion ranges, guidance-kind totals, normalized AI-bloat density, weighted bloat points, and cleanup-yield LOC per KLOC. Simplification findings remain score-neutral; enforce mode blocks only unresolved safe-mechanical cleanup candidates. diff --git a/openspec/changes/code-review-13-cleanup-forecast-agent-handoff/proposal.md b/openspec/changes/code-review-13-cleanup-forecast-agent-handoff/proposal.md index 33f6f7f..49533cc 100644 --- a/openspec/changes/code-review-13-cleanup-forecast-agent-handoff/proposal.md +++ b/openspec/changes/code-review-13-cleanup-forecast-agent-handoff/proposal.md @@ -32,7 +32,7 @@ This change turns `specfact code review run --focus simplify` into a cleanup for - **Affected bundle:** `packages/specfact-code-review`. - **Affected docs:** Code Review bundle/module pages and AI bloat quickstart. -- **Affected JSON:** `.specfact/code-review-simplify.json` receives additive optional fields; existing required fields remain compatible. +- **Affected JSON:** `.specfact/code-review.json` receives additive optional fields; existing required fields remain compatible. Custom simplify report paths are allowed only when downstream consumers have been updated to read them. - **Affected command surface:** `specfact code review run` gains `--preview-fixes` and `--with-mutation`. - **Release impact:** `specfact-code-review` version, registry entry, and signatures must be refreshed if packaged assets or manifests change. diff --git a/packages/specfact-code-review/module-package.yaml b/packages/specfact-code-review/module-package.yaml index 62eda58..550b75f 100644 --- a/packages/specfact-code-review/module-package.yaml +++ b/packages/specfact-code-review/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-code-review -version: 0.47.30 +version: 0.47.32 commands: - code tier: official @@ -23,5 +23,5 @@ description: Official SpecFact code review bundle package. category: codebase bundle_group_command: code integrity: - checksum: sha256:3c7c6b62c2c3a7aea2f3bff95628861fa6a3612b4b3de8064cb9380dba843561 - signature: +9rmzN2qzb53LMUFO+LPJMLaP7pA4QMokYvqwYEq7HNMvbogGFgQupeauqMdYxgd3xtyxUkgrELzAKyABhu3CA== + checksum: sha256:5db6e4c0c38085d678b26c14d41cb1f889d73bc698084b99c2a34ea478474322 + signature: fOoHhntdIRp17kVPHMdHhv3NIVY8835TG0xJkYjJEh8sJiwD42WbHwx34xd5BDSz3uRtlF44GbPUiriboVeOBA== diff --git a/packages/specfact-code-review/src/specfact_code_review/review/commands.py b/packages/specfact-code-review/src/specfact_code_review/review/commands.py index d3604d6..8440ba9 100644 --- a/packages/specfact-code-review/src/specfact_code_review/review/commands.py +++ b/packages/specfact-code-review/src/specfact_code_review/review/commands.py @@ -31,7 +31,9 @@ Use this when the user asks to remove AI bloat, simplify code, apply clean-code patterns, reduce boilerplate, or act on SpecFact review findings. 1. Generate evidence first: - specfact code review run --scope changed --focus simplify --preview-fixes --json --out .specfact/code-review-simplify.json + specfact code review run --scope changed --focus simplify --preview-fixes --json --out .specfact/code-review.json + + Keep the canonical .specfact/code-review.json path unless every downstream consumer has been updated to read a custom simplify report path. If the worktree is clean on a PR branch and --scope changed finds no files, review the branch-delta Python files as explicit positional files and omit --scope. Find them with the PR base ref, for example: git diff --name-only ...HEAD -- '*.py' '*.pyi' @@ -51,7 +53,7 @@ 5. For design_judgment findings, check API, callback, framework hook, adapter, public symbol, CLI boundary, compatibility shim, and readability intent. If intent is unclear, default to keep or skip. 6. Apply one file at a time. After each accepted file or very small batch, run targeted tests or rerun: - specfact code review run --scope changed --focus simplify --json --out .specfact/code-review-simplify.json + specfact code review run --scope changed --focus simplify --json --out .specfact/code-review.json 7. Log every action as recommended, applied, kept, skipped, or failed with evidence. Never batch-apply design_judgment findings just because the patch is shorter. Never treat ai_bloat findings as proof of AI authorship; they are cleanup signals only, not proof of AI authorship. """ diff --git a/packages/specfact-code-review/src/specfact_code_review/run/cleanup_evidence.py b/packages/specfact-code-review/src/specfact_code_review/run/cleanup_evidence.py index b57c2c1..7b3b8fd 100644 --- a/packages/specfact-code-review/src/specfact_code_review/run/cleanup_evidence.py +++ b/packages/specfact-code-review/src/specfact_code_review/run/cleanup_evidence.py @@ -51,10 +51,12 @@ def with_mutation_evidence(report: ReviewReport, files: list[Path]) -> ReviewRep @require(lambda files: isinstance(files, list), "files must be a list") @ensure(lambda result: isinstance(result, ReviewReport), "result must be a review report") def with_refreshed_cleanup_forecast(report: ReviewReport, files: list[Path]) -> ReviewReport: - data = report.model_dump() - data["cleanup_forecast"] = build_cleanup_forecast(report.findings, files) - data["simplification_summary"] = None - return ReviewReport(**data) + return report.model_copy( + update={ + "cleanup_forecast": build_cleanup_forecast(report.findings, files), + "schema_version": "1.3", + } + ) def _preview_simplification_fixes( diff --git a/packages/specfact-code-review/src/specfact_code_review/run/findings.py b/packages/specfact-code-review/src/specfact_code_review/run/findings.py index 500be6d..3bad107 100644 --- a/packages/specfact-code-review/src/specfact_code_review/run/findings.py +++ b/packages/specfact-code-review/src/specfact_code_review/run/findings.py @@ -187,6 +187,7 @@ class GuidanceKindForecast(BaseModel): count: int = Field(..., ge=0) estimated_deletion_lines: int = Field(..., ge=0) + weight: float = Field(default=0.0, ge=0.0) class CleanupForecast(BaseModel): diff --git a/packages/specfact-code-review/src/specfact_code_review/run/forecast.py b/packages/specfact-code-review/src/specfact_code_review/run/forecast.py index 60386ed..6c0d7da 100644 --- a/packages/specfact-code-review/src/specfact_code_review/run/forecast.py +++ b/packages/specfact-code-review/src/specfact_code_review/run/forecast.py @@ -79,7 +79,7 @@ def _reviewed_loc_for_files(files: list[Path]) -> ReviewedLoc: loc = _count_python_loc(file_path) except (OSError, UnicodeDecodeError): continue - if "tests" in file_path.parts: + if any("test" in part.lower() for part in file_path.parts): tests += loc else: production += loc @@ -104,10 +104,15 @@ def _cleanup_forecast_totals(guided: list[ReviewFinding]) -> _CleanupForecastTot def _add_cleanup_forecast_finding(totals: _CleanupForecastTotals, finding: ReviewFinding) -> None: guidance_kind = finding.guidance_kind or "design_judgment" deletion_lines = finding.estimated_deletion_lines or 0 - current = totals.by_guidance_kind.get(guidance_kind, GuidanceKindForecast(count=0, estimated_deletion_lines=0)) + weight = _CLEANUP_FORECAST_WEIGHTS.get(guidance_kind, 0.0) + current = totals.by_guidance_kind.get( + guidance_kind, + GuidanceKindForecast(count=0, estimated_deletion_lines=0, weight=weight), + ) totals.by_guidance_kind[guidance_kind] = GuidanceKindForecast( count=current.count + 1, estimated_deletion_lines=current.estimated_deletion_lines + deletion_lines, + weight=weight, ) if finding.action_status is not None: totals.by_action_status[finding.action_status] = totals.by_action_status.get(finding.action_status, 0) + 1 @@ -115,6 +120,5 @@ def _add_cleanup_forecast_finding(totals: _CleanupForecastTotals, finding: Revie totals.low += deletion_lines if guidance_kind != "preserve": totals.high += deletion_lines - weight = _CLEANUP_FORECAST_WEIGHTS.get(guidance_kind, 0.0) totals.expected += deletion_lines * weight totals.weighted_points += weight diff --git a/packages/specfact-code-review/src/specfact_code_review/run/runner.py b/packages/specfact-code-review/src/specfact_code_review/run/runner.py index 95ba06d..1ebfdec 100644 --- a/packages/specfact-code-review/src/specfact_code_review/run/runner.py +++ b/packages/specfact-code-review/src/specfact_code_review/run/runner.py @@ -12,7 +12,7 @@ from collections.abc import Callable, Iterable from contextlib import suppress from dataclasses import dataclass -from functools import partial +from functools import lru_cache, partial from pathlib import Path from typing import Literal, cast from uuid import uuid4 @@ -427,12 +427,10 @@ def _preserve_reasons_for_finding(finding: ReviewFinding, *, load_bearing: bool) explanation="Mutation proof indicates this code is load-bearing.", ) ) - try: - source = Path(finding.file).read_text(encoding="utf-8") - tree = ast.parse(source, filename=finding.file) - except (OSError, SyntaxError, UnicodeDecodeError): + parsed = _get_parsed_source(finding.file) + if parsed is None: return reasons - lines = source.splitlines() + tree, lines = parsed function_node = _function_containing_line(tree, finding.line) class_node = _class_containing_line(tree, finding.line) public_names = _module_all_names(tree) @@ -488,6 +486,23 @@ def _preserve_reasons_for_finding(finding: ReviewFinding, *, load_bearing: bool) return _dedupe_preserve_reasons(reasons) +def _get_parsed_source(file_path: str) -> tuple[ast.Module, list[str]] | None: + try: + source = Path(file_path).read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError): + return None + return _parse_source(file_path, source) + + +@lru_cache(maxsize=256) +def _parse_source(file_path: str, source: str) -> tuple[ast.Module, list[str]] | None: + try: + tree = ast.parse(source, filename=file_path) + except SyntaxError: + return None + return tree, source.splitlines() + + def _dedupe_preserve_reasons(reasons: list[PreserveReasonEvidence]) -> list[PreserveReasonEvidence]: deduped: list[PreserveReasonEvidence] = [] seen: set[str] = set() diff --git a/registry/index.json b/registry/index.json index c559e8e..a1036d8 100644 --- a/registry/index.json +++ b/registry/index.json @@ -78,9 +78,9 @@ }, { "id": "nold-ai/specfact-code-review", - "latest_version": "0.47.30", - "download_url": "modules/specfact-code-review-0.47.30.tar.gz", - "checksum_sha256": "8e9db9d4659f6bd71d572e213a186f188e20ddfa1108838d18cfb4decca5761e", + "latest_version": "0.47.32", + "download_url": "modules/specfact-code-review-0.47.32.tar.gz", + "checksum_sha256": "ba6746be977a29e9ae29f85dc6f56e84cd68e0af8c69746fc4793cc1c544675e", "core_compatibility": ">=0.44.0,<1.0.0", "tier": "official", "publisher": { diff --git a/registry/modules/specfact-backlog-0.39.0.tar.gz.sha256 b/registry/modules/specfact-backlog-0.39.0.tar.gz.sha256 new file mode 100644 index 0000000..812d403 --- /dev/null +++ b/registry/modules/specfact-backlog-0.39.0.tar.gz.sha256 @@ -0,0 +1 @@ +a59c07672ba1fdf02d8dc03d0171d03429e521ca98d383169404127fd1d9ea13 diff --git a/registry/modules/specfact-backlog-0.40.0.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.0.tar.gz.sha256 new file mode 100644 index 0000000..4859412 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.0.tar.gz.sha256 @@ -0,0 +1 @@ +c180966b5488bd7185e3d58201916e9cd8e626d3eace96c542c4caaf827e72a3 diff --git a/registry/modules/specfact-backlog-0.40.1.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.1.tar.gz.sha256 new file mode 100644 index 0000000..5e6723e --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.1.tar.gz.sha256 @@ -0,0 +1 @@ +5303882e03bebd7796c56d0e32c5357f74db77853f9f101fa6e7c568ea113f9f diff --git a/registry/modules/specfact-backlog-0.40.10.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.10.tar.gz.sha256 new file mode 100644 index 0000000..e6d9329 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.10.tar.gz.sha256 @@ -0,0 +1 @@ +2a2fc2dd4ec89faa02336b7e7510d33b8cb96766054fd3f6eae4304c6c5d6460 diff --git a/registry/modules/specfact-backlog-0.40.11.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.11.tar.gz.sha256 new file mode 100644 index 0000000..8460765 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.11.tar.gz.sha256 @@ -0,0 +1 @@ +ec95c9041af8a9217ec3317b1bb190c518cfd4d16d264ea8726248f3fe592463 diff --git a/registry/modules/specfact-backlog-0.40.13.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.13.tar.gz.sha256 new file mode 100644 index 0000000..94bcb08 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.13.tar.gz.sha256 @@ -0,0 +1 @@ +ad41442be4bdd883b0d2c1797692c8a8e0de82682e5675a42c57e92526960352 diff --git a/registry/modules/specfact-backlog-0.40.14.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.14.tar.gz.sha256 new file mode 100644 index 0000000..ca8d073 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.14.tar.gz.sha256 @@ -0,0 +1 @@ +4e50523ad1118ba0d18cd1134ccfe1e2824856517de773edf1f91f27677e4677 diff --git a/registry/modules/specfact-backlog-0.40.15.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.15.tar.gz.sha256 new file mode 100644 index 0000000..bb93007 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.15.tar.gz.sha256 @@ -0,0 +1 @@ +bdf9f0ccf3e18c05e26821ecc4cef94d9a76ecfe97554a2a0387a6c9eadc2ffa diff --git a/registry/modules/specfact-backlog-0.40.2.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.2.tar.gz.sha256 new file mode 100644 index 0000000..ada23f7 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.2.tar.gz.sha256 @@ -0,0 +1 @@ +07e2ed09eb7a780350513415f1ada910101592304a2440cbcceddc09b0797a14 diff --git a/registry/modules/specfact-backlog-0.40.3.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.3.tar.gz.sha256 new file mode 100644 index 0000000..aada58f --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.3.tar.gz.sha256 @@ -0,0 +1 @@ +dab751f2993d992a2bc92cd9b9ba4c70128c5b7072098162efce3910da330ab8 diff --git a/registry/modules/specfact-backlog-0.40.4.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.4.tar.gz.sha256 new file mode 100644 index 0000000..1988e7e --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.4.tar.gz.sha256 @@ -0,0 +1 @@ +71729cca43246ecf0ca2ecd597ac91c934ea7d3af84bccfee90207b448efc830 diff --git a/registry/modules/specfact-backlog-0.40.5.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.5.tar.gz.sha256 new file mode 100644 index 0000000..aa5768d --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.5.tar.gz.sha256 @@ -0,0 +1 @@ +2e63cf5a0385a384543da384f5cda35f76349a38b82a0f044986fe50747f7c0f diff --git a/registry/modules/specfact-backlog-0.40.6.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.6.tar.gz.sha256 new file mode 100644 index 0000000..edc9816 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.6.tar.gz.sha256 @@ -0,0 +1 @@ +55e18cc73e540d335a79436842908fb0d6f32be664443a8b47fff76238b4f011 diff --git a/registry/modules/specfact-backlog-0.40.7.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.7.tar.gz.sha256 new file mode 100644 index 0000000..b3b510e --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.7.tar.gz.sha256 @@ -0,0 +1 @@ +d91040de02cf04e4392a077bcb7117a195994e5ffd45dcb94ef7e8a378a6f02c diff --git a/registry/modules/specfact-backlog-0.40.8.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.8.tar.gz.sha256 new file mode 100644 index 0000000..a471407 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.8.tar.gz.sha256 @@ -0,0 +1 @@ +6640bd7c94d9193d70053fd6b390eedb12c63f8e0107987ab95b95fb36825289 diff --git a/registry/modules/specfact-backlog-0.40.9.tar.gz.sha256 b/registry/modules/specfact-backlog-0.40.9.tar.gz.sha256 new file mode 100644 index 0000000..6bfbfb1 --- /dev/null +++ b/registry/modules/specfact-backlog-0.40.9.tar.gz.sha256 @@ -0,0 +1 @@ +9893e81390c8531d2b7c74ca711d652c6d9688cb88adae3467fc7f22a9a9d70a diff --git a/registry/modules/specfact-code-review-0.47.31.tar.gz b/registry/modules/specfact-code-review-0.47.31.tar.gz new file mode 100644 index 0000000..6c0444d Binary files /dev/null and b/registry/modules/specfact-code-review-0.47.31.tar.gz differ diff --git a/registry/modules/specfact-code-review-0.47.31.tar.gz.sha256 b/registry/modules/specfact-code-review-0.47.31.tar.gz.sha256 new file mode 100644 index 0000000..5d4c31b --- /dev/null +++ b/registry/modules/specfact-code-review-0.47.31.tar.gz.sha256 @@ -0,0 +1 @@ +407f28ae9bc776eb914a0907f64a3724ba080ca9cc781d49379cefb7f3ff1d3f diff --git a/registry/modules/specfact-code-review-0.47.32.tar.gz b/registry/modules/specfact-code-review-0.47.32.tar.gz new file mode 100644 index 0000000..46a6645 Binary files /dev/null and b/registry/modules/specfact-code-review-0.47.32.tar.gz differ diff --git a/registry/modules/specfact-code-review-0.47.32.tar.gz.sha256 b/registry/modules/specfact-code-review-0.47.32.tar.gz.sha256 new file mode 100644 index 0000000..1666528 --- /dev/null +++ b/registry/modules/specfact-code-review-0.47.32.tar.gz.sha256 @@ -0,0 +1 @@ +ba6746be977a29e9ae29f85dc6f56e84cd68e0af8c69746fc4793cc1c544675e diff --git a/registry/modules/specfact-codebase-0.39.0.tar.gz.sha256 b/registry/modules/specfact-codebase-0.39.0.tar.gz.sha256 new file mode 100644 index 0000000..30fd938 --- /dev/null +++ b/registry/modules/specfact-codebase-0.39.0.tar.gz.sha256 @@ -0,0 +1 @@ +1f70e1470af9889c955ae848231878ad7d495a2d9c8d27719acc866bd7ffb33d diff --git a/registry/modules/specfact-codebase-0.40.0.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.0.tar.gz.sha256 new file mode 100644 index 0000000..6be22e7 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.0.tar.gz.sha256 @@ -0,0 +1 @@ +a25a664a9c637014ab8b47d50741228cee411a8bb73e77e7ffb70500e7a99d34 diff --git a/registry/modules/specfact-codebase-0.40.1.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.1.tar.gz.sha256 new file mode 100644 index 0000000..234af91 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.1.tar.gz.sha256 @@ -0,0 +1 @@ +a262025d4098b4913ca694ca1ab17b25500cc3098899785209647a41002433fb diff --git a/registry/modules/specfact-codebase-0.40.10.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.10.tar.gz.sha256 new file mode 100644 index 0000000..d51a8b6 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.10.tar.gz.sha256 @@ -0,0 +1 @@ +247787bf00522865a640945e3d7154b8722ad46b778770864601051d0700bc36 diff --git a/registry/modules/specfact-codebase-0.40.11.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.11.tar.gz.sha256 new file mode 100644 index 0000000..03188bf --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.11.tar.gz.sha256 @@ -0,0 +1 @@ +926d1bc91e715e450342bd0eb001a6c26a3b5359091c8404580146bdcb766652 diff --git a/registry/modules/specfact-codebase-0.40.12.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.12.tar.gz.sha256 new file mode 100644 index 0000000..17f8172 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.12.tar.gz.sha256 @@ -0,0 +1 @@ +093bd3f80d5abe7f969e6a130d45fe62560188c0ec7419773555a93ba13fb531 diff --git a/registry/modules/specfact-codebase-0.40.13.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.13.tar.gz.sha256 new file mode 100644 index 0000000..f203d7f --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.13.tar.gz.sha256 @@ -0,0 +1 @@ +075f6ac03febab05c10a2a153255fc85cecaedce74b748660fd6c5db6e78214f diff --git a/registry/modules/specfact-codebase-0.40.14.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.14.tar.gz.sha256 new file mode 100644 index 0000000..f97be0d --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.14.tar.gz.sha256 @@ -0,0 +1 @@ +1f383286507730a546ae7fd629253e7c7477fa42d84d0284c8bb72b468a212c4 diff --git a/registry/modules/specfact-codebase-0.40.2.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.2.tar.gz.sha256 new file mode 100644 index 0000000..293ac33 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.2.tar.gz.sha256 @@ -0,0 +1 @@ +8f0e63d795737cb95bc9b9bb94393ad510a568fe5051a3389527cb20630d1a2b diff --git a/registry/modules/specfact-codebase-0.40.3.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.3.tar.gz.sha256 new file mode 100644 index 0000000..8f8dd3c --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.3.tar.gz.sha256 @@ -0,0 +1 @@ +90d6627b9cca28f528d75044e3c1110ade8b7a3bc96a2ca95bc6ee9716d8353c diff --git a/registry/modules/specfact-codebase-0.40.4.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.4.tar.gz.sha256 new file mode 100644 index 0000000..7e0614b --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.4.tar.gz.sha256 @@ -0,0 +1 @@ +5742effa6fc0f8f23b61e762d351af5839176174c154c5f3849b34199faad71f diff --git a/registry/modules/specfact-codebase-0.40.5.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.5.tar.gz.sha256 new file mode 100644 index 0000000..0951317 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.5.tar.gz.sha256 @@ -0,0 +1 @@ +0777050f23e0ee2f750a767b6b4ade142391279412b64351b708fe891f065fba diff --git a/registry/modules/specfact-codebase-0.40.6.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.6.tar.gz.sha256 new file mode 100644 index 0000000..d9fba0d --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.6.tar.gz.sha256 @@ -0,0 +1 @@ +145ec268bebf6a560bd40b2098591205b09903d83805b740baed36f2e5a53fc9 diff --git a/registry/modules/specfact-codebase-0.40.7.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.7.tar.gz.sha256 new file mode 100644 index 0000000..d2323a8 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.7.tar.gz.sha256 @@ -0,0 +1 @@ +5a5cb5965c1c2271bb8ce44a183e9470955c5be1daf45deead8c29a2298136c7 diff --git a/registry/modules/specfact-codebase-0.40.8.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.8.tar.gz.sha256 new file mode 100644 index 0000000..d599519 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.8.tar.gz.sha256 @@ -0,0 +1 @@ +36fc309889c7794bba8328324a84a777965b661d933a31472bdcb07e494d2ce8 diff --git a/registry/modules/specfact-codebase-0.40.9.tar.gz.sha256 b/registry/modules/specfact-codebase-0.40.9.tar.gz.sha256 new file mode 100644 index 0000000..922eed5 --- /dev/null +++ b/registry/modules/specfact-codebase-0.40.9.tar.gz.sha256 @@ -0,0 +1 @@ +2efcfba76d5368c325995e5bbcbaf0a1d73f7b253d051697d19d460e31d0a81a diff --git a/registry/modules/specfact-codebase-0.41.0.tar.gz.sha256 b/registry/modules/specfact-codebase-0.41.0.tar.gz.sha256 new file mode 100644 index 0000000..1a71397 --- /dev/null +++ b/registry/modules/specfact-codebase-0.41.0.tar.gz.sha256 @@ -0,0 +1 @@ +af22a5607ce46c1dfe1dc9aaba5b2ceafabdec8f924e4dad5be103600f837a02 diff --git a/registry/modules/specfact-govern-0.39.0.tar.gz.sha256 b/registry/modules/specfact-govern-0.39.0.tar.gz.sha256 new file mode 100644 index 0000000..e7e8ed7 --- /dev/null +++ b/registry/modules/specfact-govern-0.39.0.tar.gz.sha256 @@ -0,0 +1 @@ +00d1fb60364b5545fd44a318ca13b556e8345582e0ff9c2185df6a4a5db2a2bc diff --git a/registry/modules/specfact-govern-0.40.0.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.0.tar.gz.sha256 new file mode 100644 index 0000000..d90b718 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.0.tar.gz.sha256 @@ -0,0 +1 @@ +15fa85a158d2e7b0fd7bda2a626bb30c3e1523bc364af1d6d0506d10704be012 diff --git a/registry/modules/specfact-govern-0.40.1.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.1.tar.gz.sha256 new file mode 100644 index 0000000..98e97c2 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.1.tar.gz.sha256 @@ -0,0 +1 @@ +a12af83810a7336572f2b95afcfa44c37467be42463d071ce49a84a14cea55cd diff --git a/registry/modules/specfact-govern-0.40.10.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.10.tar.gz.sha256 new file mode 100644 index 0000000..4b1ecf9 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.10.tar.gz.sha256 @@ -0,0 +1 @@ +8594ce61bddda858b0d4c98c4837fbde3786f97f7c43d3ff39708206f97492e0 diff --git a/registry/modules/specfact-govern-0.40.11.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.11.tar.gz.sha256 new file mode 100644 index 0000000..4f9d48b --- /dev/null +++ b/registry/modules/specfact-govern-0.40.11.tar.gz.sha256 @@ -0,0 +1 @@ +2b384a2d6008945441532b6c96b8c2e5ea1265bfea6630ebc16bf5a1bae6edba diff --git a/registry/modules/specfact-govern-0.40.12.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.12.tar.gz.sha256 new file mode 100644 index 0000000..5d4043d --- /dev/null +++ b/registry/modules/specfact-govern-0.40.12.tar.gz.sha256 @@ -0,0 +1 @@ +50530b16edc7ea7f6a1b818c3c126bb2fbbdeca92e857c315bbf311314247daf diff --git a/registry/modules/specfact-govern-0.40.13.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.13.tar.gz.sha256 new file mode 100644 index 0000000..3f149ea --- /dev/null +++ b/registry/modules/specfact-govern-0.40.13.tar.gz.sha256 @@ -0,0 +1 @@ +efbc86967cd8796c51c839f2611279ada54216c026bf7879e090926b8665d72f diff --git a/registry/modules/specfact-govern-0.40.2.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.2.tar.gz.sha256 new file mode 100644 index 0000000..aa4d64d --- /dev/null +++ b/registry/modules/specfact-govern-0.40.2.tar.gz.sha256 @@ -0,0 +1 @@ +26052f0d95fcd29a4614f15d818ad28ab678bed16919a4ca3cc8d5c712906048 diff --git a/registry/modules/specfact-govern-0.40.3.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.3.tar.gz.sha256 new file mode 100644 index 0000000..fe973d3 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.3.tar.gz.sha256 @@ -0,0 +1 @@ +fb09d2bf687d905cc3bf53f4c85de77a7d14728c842539e0e004e9bc8b89161c diff --git a/registry/modules/specfact-govern-0.40.4.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.4.tar.gz.sha256 new file mode 100644 index 0000000..4a1b054 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.4.tar.gz.sha256 @@ -0,0 +1 @@ +4aaee86092079224f9ff9a1a12fb162b95ec265464a1bc8250458da92634ff0e diff --git a/registry/modules/specfact-govern-0.40.5.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.5.tar.gz.sha256 new file mode 100644 index 0000000..c13a964 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.5.tar.gz.sha256 @@ -0,0 +1 @@ +c23b31234c350a5e4747beea13649041ea8946b34b537f955ba78f919b371e51 diff --git a/registry/modules/specfact-govern-0.40.6.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.6.tar.gz.sha256 new file mode 100644 index 0000000..3cb6e64 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.6.tar.gz.sha256 @@ -0,0 +1 @@ +064153f07c3de334614d5b90212c9d39643cd9bb84e155b19a05cebb0639ea8d diff --git a/registry/modules/specfact-govern-0.40.7.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.7.tar.gz.sha256 new file mode 100644 index 0000000..dba9892 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.7.tar.gz.sha256 @@ -0,0 +1 @@ +a155843e4b96073516c39fb0e72f64c8dac81977f95c2a1f91786198d1f4520a diff --git a/registry/modules/specfact-govern-0.40.8.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.8.tar.gz.sha256 new file mode 100644 index 0000000..f8346da --- /dev/null +++ b/registry/modules/specfact-govern-0.40.8.tar.gz.sha256 @@ -0,0 +1 @@ +814de7dff193c67bca4159ff9ada1f2900a27457943c00e4a9fcc41236d35dc9 diff --git a/registry/modules/specfact-govern-0.40.9.tar.gz.sha256 b/registry/modules/specfact-govern-0.40.9.tar.gz.sha256 new file mode 100644 index 0000000..a46f252 --- /dev/null +++ b/registry/modules/specfact-govern-0.40.9.tar.gz.sha256 @@ -0,0 +1 @@ +24f5014cb8c99c5eb1127a0766e6254a9f3509205fe0c625886e43aaaf500774 diff --git a/registry/modules/specfact-project-0.39.0.tar.gz.sha256 b/registry/modules/specfact-project-0.39.0.tar.gz.sha256 new file mode 100644 index 0000000..d4f97fd --- /dev/null +++ b/registry/modules/specfact-project-0.39.0.tar.gz.sha256 @@ -0,0 +1 @@ +7c7daea2401ca9618676a3f1369fd14ad65e008c692ed19b442fa9fc1ea93365 diff --git a/registry/modules/specfact-project-0.40.0.tar.gz.sha256 b/registry/modules/specfact-project-0.40.0.tar.gz.sha256 new file mode 100644 index 0000000..2de5576 --- /dev/null +++ b/registry/modules/specfact-project-0.40.0.tar.gz.sha256 @@ -0,0 +1 @@ +fc1dc72db983d0350d257c710f315dc1d89b5955bc1a409bb65ecb2ad4e72b66 diff --git a/registry/modules/specfact-project-0.40.1.tar.gz.sha256 b/registry/modules/specfact-project-0.40.1.tar.gz.sha256 new file mode 100644 index 0000000..86fff47 --- /dev/null +++ b/registry/modules/specfact-project-0.40.1.tar.gz.sha256 @@ -0,0 +1 @@ +6ce5de74b117b2e241980eaf42c27aea50ac7ed17cf98009f3c165095a7be304 diff --git a/registry/modules/specfact-project-0.40.11.tar.gz.sha256 b/registry/modules/specfact-project-0.40.11.tar.gz.sha256 new file mode 100644 index 0000000..52c9c48 --- /dev/null +++ b/registry/modules/specfact-project-0.40.11.tar.gz.sha256 @@ -0,0 +1 @@ +6ad021243359379ef49e45736ac6a012e94afc61d9dccbd55572bd376b55e1c2 diff --git a/registry/modules/specfact-project-0.40.12.tar.gz.sha256 b/registry/modules/specfact-project-0.40.12.tar.gz.sha256 new file mode 100644 index 0000000..1216910 --- /dev/null +++ b/registry/modules/specfact-project-0.40.12.tar.gz.sha256 @@ -0,0 +1 @@ +d8d7d68cbc09b6822d2a61a6c4fe650076029b725a364bb5d394d449623eba07 diff --git a/registry/modules/specfact-project-0.40.13.tar.gz.sha256 b/registry/modules/specfact-project-0.40.13.tar.gz.sha256 new file mode 100644 index 0000000..e882a2d --- /dev/null +++ b/registry/modules/specfact-project-0.40.13.tar.gz.sha256 @@ -0,0 +1 @@ +7c7d8b7acd577670542f7cfaded1dad9c7b18badf1aec7ef8da8bcead678df31 diff --git a/registry/modules/specfact-project-0.40.14.tar.gz.sha256 b/registry/modules/specfact-project-0.40.14.tar.gz.sha256 new file mode 100644 index 0000000..2899a34 --- /dev/null +++ b/registry/modules/specfact-project-0.40.14.tar.gz.sha256 @@ -0,0 +1 @@ +35f04dd9d804312c148d1743b2e64ed3c2d6f3a9572f94b503a9505397574d9b diff --git a/registry/modules/specfact-project-0.40.15.tar.gz.sha256 b/registry/modules/specfact-project-0.40.15.tar.gz.sha256 new file mode 100644 index 0000000..c8410fe --- /dev/null +++ b/registry/modules/specfact-project-0.40.15.tar.gz.sha256 @@ -0,0 +1 @@ +caff2eaef50ab09cc783f06043930c925c769a36ea947e713c10af587d6b1927 diff --git a/registry/modules/specfact-project-0.40.2.tar.gz.sha256 b/registry/modules/specfact-project-0.40.2.tar.gz.sha256 new file mode 100644 index 0000000..e97d975 --- /dev/null +++ b/registry/modules/specfact-project-0.40.2.tar.gz.sha256 @@ -0,0 +1 @@ +123bb72ac14015e6b585c1f7095365a88e3580e16b41b238c559e4e1182c25ff diff --git a/registry/modules/specfact-project-0.40.20.tar.gz.sha256 b/registry/modules/specfact-project-0.40.20.tar.gz.sha256 new file mode 100644 index 0000000..78a0f21 --- /dev/null +++ b/registry/modules/specfact-project-0.40.20.tar.gz.sha256 @@ -0,0 +1 @@ +080d1133ce61ff7b7945124b9cfd7e58ddd9df76a663ea48c2bd1a2f6ea43dab diff --git a/registry/modules/specfact-project-0.40.3.tar.gz.sha256 b/registry/modules/specfact-project-0.40.3.tar.gz.sha256 new file mode 100644 index 0000000..7d74054 --- /dev/null +++ b/registry/modules/specfact-project-0.40.3.tar.gz.sha256 @@ -0,0 +1 @@ +3e19b2b0b1cedc3005265239cf30b1b42309dfdc53c213f14e0c8297a3f6c0fc diff --git a/registry/modules/specfact-project-0.40.4.tar.gz.sha256 b/registry/modules/specfact-project-0.40.4.tar.gz.sha256 new file mode 100644 index 0000000..67739aa --- /dev/null +++ b/registry/modules/specfact-project-0.40.4.tar.gz.sha256 @@ -0,0 +1 @@ +e90bdf20c3053488f92409330f80875286db237a2c25a6a5f00f1044b8ae67cd diff --git a/registry/modules/specfact-project-0.40.5.tar.gz.sha256 b/registry/modules/specfact-project-0.40.5.tar.gz.sha256 new file mode 100644 index 0000000..397c325 --- /dev/null +++ b/registry/modules/specfact-project-0.40.5.tar.gz.sha256 @@ -0,0 +1 @@ +b76d6aaf7128dde79b22a22bfe7f07fe86947c9199f6ed9f7d0e8ec61d833a14 diff --git a/registry/modules/specfact-project-0.40.6.tar.gz.sha256 b/registry/modules/specfact-project-0.40.6.tar.gz.sha256 new file mode 100644 index 0000000..167ddcc --- /dev/null +++ b/registry/modules/specfact-project-0.40.6.tar.gz.sha256 @@ -0,0 +1 @@ +352c60a09916ce36c4a6d32daad79acd3809c00081ae00ad345e48440adb4092 diff --git a/registry/modules/specfact-project-0.40.7.tar.gz.sha256 b/registry/modules/specfact-project-0.40.7.tar.gz.sha256 new file mode 100644 index 0000000..0933c54 --- /dev/null +++ b/registry/modules/specfact-project-0.40.7.tar.gz.sha256 @@ -0,0 +1 @@ +daca73a59dfbd674da3dedddbdca01a4138321f26ad53066e7cb6c73afc9bfa8 diff --git a/registry/modules/specfact-project-0.40.8.tar.gz.sha256 b/registry/modules/specfact-project-0.40.8.tar.gz.sha256 new file mode 100644 index 0000000..e9e6fba --- /dev/null +++ b/registry/modules/specfact-project-0.40.8.tar.gz.sha256 @@ -0,0 +1 @@ +8a6a2a67cf8d26cfe0fcb3c83f91f36f800c7c480db56732057a55bc81644a72 diff --git a/registry/modules/specfact-project-0.40.9.tar.gz.sha256 b/registry/modules/specfact-project-0.40.9.tar.gz.sha256 new file mode 100644 index 0000000..362ce7d --- /dev/null +++ b/registry/modules/specfact-project-0.40.9.tar.gz.sha256 @@ -0,0 +1 @@ +0e69a8ce6b0ff665b34dff18b77894684202ae2f60e23c85f998a8a4dbb81b84 diff --git a/registry/modules/specfact-spec-0.39.0.tar.gz.sha256 b/registry/modules/specfact-spec-0.39.0.tar.gz.sha256 new file mode 100644 index 0000000..41588c2 --- /dev/null +++ b/registry/modules/specfact-spec-0.39.0.tar.gz.sha256 @@ -0,0 +1 @@ +6be5b1ac7b3cd60b51dcb8f45ffa41be3d7f5bcd0429ab70d81b2eb3bbf8367d diff --git a/registry/modules/specfact-spec-0.40.0.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.0.tar.gz.sha256 new file mode 100644 index 0000000..9ab07c6 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.0.tar.gz.sha256 @@ -0,0 +1 @@ +c0d9536577529ed54f09c687c2edfc5c2a078e7aeb7a4cb1e0379077e8dc7f16 diff --git a/registry/modules/specfact-spec-0.40.1.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.1.tar.gz.sha256 new file mode 100644 index 0000000..1932021 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.1.tar.gz.sha256 @@ -0,0 +1 @@ +6afc7770e8c7adbcc0e5ad5747092139d12d38967cde72320d00200647af803b diff --git a/registry/modules/specfact-spec-0.40.10.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.10.tar.gz.sha256 new file mode 100644 index 0000000..2d0204a --- /dev/null +++ b/registry/modules/specfact-spec-0.40.10.tar.gz.sha256 @@ -0,0 +1 @@ +487873085d7bca3221e96f88bdbc5564cc5529086098e422203b21bf96336ce1 diff --git a/registry/modules/specfact-spec-0.40.11.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.11.tar.gz.sha256 new file mode 100644 index 0000000..d60fb39 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.11.tar.gz.sha256 @@ -0,0 +1 @@ +a0729d64dc8a3b3bd883d49dc69d4452b6a8fc4fc68665359b2b9b978b9befd1 diff --git a/registry/modules/specfact-spec-0.40.12.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.12.tar.gz.sha256 new file mode 100644 index 0000000..be2c631 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.12.tar.gz.sha256 @@ -0,0 +1 @@ +ee5e7bebe3815516c00ba8d5c8015c3512186782cd7a9a87642111aad8a1e140 diff --git a/registry/modules/specfact-spec-0.40.13.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.13.tar.gz.sha256 new file mode 100644 index 0000000..cd3a27f --- /dev/null +++ b/registry/modules/specfact-spec-0.40.13.tar.gz.sha256 @@ -0,0 +1 @@ +8c8004144bba37a49a7099f74f903b75819929fb0846d5e1d8dab8ffe5c97dd7 diff --git a/registry/modules/specfact-spec-0.40.2.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.2.tar.gz.sha256 new file mode 100644 index 0000000..5ea31ca --- /dev/null +++ b/registry/modules/specfact-spec-0.40.2.tar.gz.sha256 @@ -0,0 +1 @@ +734baa9da1f2ea3eb92093aaaa46068b079b040101ed97f3514b95995f5e59a1 diff --git a/registry/modules/specfact-spec-0.40.3.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.3.tar.gz.sha256 new file mode 100644 index 0000000..eb72f82 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.3.tar.gz.sha256 @@ -0,0 +1 @@ +5fba7afc3e553289f4afcb8851d460de5fc35a240d125af412b1b46f13bdcd7f diff --git a/registry/modules/specfact-spec-0.40.4.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.4.tar.gz.sha256 new file mode 100644 index 0000000..d7d6726 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.4.tar.gz.sha256 @@ -0,0 +1 @@ +50c15e4b6ef9e38e41cf6a398721791e52d7769146813c8986219a39c88ef171 diff --git a/registry/modules/specfact-spec-0.40.5.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.5.tar.gz.sha256 new file mode 100644 index 0000000..ce6d27c --- /dev/null +++ b/registry/modules/specfact-spec-0.40.5.tar.gz.sha256 @@ -0,0 +1 @@ +cd88073eb64152d4071af925c91d84f137a69669da4022c97b5dcd59160c5d05 diff --git a/registry/modules/specfact-spec-0.40.6.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.6.tar.gz.sha256 new file mode 100644 index 0000000..4391a89 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.6.tar.gz.sha256 @@ -0,0 +1 @@ +050382abf1cb7ddc172c33261f0cc6a70261c791e5af5488692af4e374f9c787 diff --git a/registry/modules/specfact-spec-0.40.7.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.7.tar.gz.sha256 new file mode 100644 index 0000000..cc862c8 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.7.tar.gz.sha256 @@ -0,0 +1 @@ +de0236d638d5ea5b638c3f3cdbfb87a06af0ac91299981a8f81aff9d9b972660 diff --git a/registry/modules/specfact-spec-0.40.8.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.8.tar.gz.sha256 new file mode 100644 index 0000000..bd33437 --- /dev/null +++ b/registry/modules/specfact-spec-0.40.8.tar.gz.sha256 @@ -0,0 +1 @@ +930f27a4f82a27828397343ab2f93091f1561fc31684dd3bf45331f416b62d78 diff --git a/registry/modules/specfact-spec-0.40.9.tar.gz.sha256 b/registry/modules/specfact-spec-0.40.9.tar.gz.sha256 new file mode 100644 index 0000000..167586c --- /dev/null +++ b/registry/modules/specfact-spec-0.40.9.tar.gz.sha256 @@ -0,0 +1 @@ +2df922f15f812b6e8022e9019e88a5bbde3625af85564454af0d58653a7bf30a diff --git a/tests/unit/specfact_code_review/run/test_cleanup_evidence.py b/tests/unit/specfact_code_review/run/test_cleanup_evidence.py index 5a89fb6..14f80a8 100644 --- a/tests/unit/specfact_code_review/run/test_cleanup_evidence.py +++ b/tests/unit/specfact_code_review/run/test_cleanup_evidence.py @@ -4,8 +4,12 @@ import pytest -from specfact_code_review.run.cleanup_evidence import with_mutation_evidence, with_previewed_simplification_findings -from specfact_code_review.run.findings import ReviewFinding, ReviewReport +from specfact_code_review.run.cleanup_evidence import ( + with_mutation_evidence, + with_previewed_simplification_findings, + with_refreshed_cleanup_forecast, +) +from specfact_code_review.run.findings import RemediationPacket, ReviewFinding, ReviewReport def _finding(file_path: Path) -> ReviewFinding: @@ -35,6 +39,44 @@ def _report(finding: ReviewFinding) -> ReviewReport: return ReviewReport(run_id="review", score=90, findings=[finding], summary="Simplify") +def test_with_previewed_simplification_findings_refreshes_forecast_without_fixable_findings(tmp_path: Path) -> None: + source = tmp_path / "sample.py" + source.write_text("def total(values: list[int]) -> int:\n return sum(values)\n", encoding="utf-8") + finding = _finding(source).model_copy(update={"guidance_kind": "preserve", "preserve_reason": "public_api"}) + + refreshed = with_previewed_simplification_findings(_report(finding), [source], lambda report: []) + + assert refreshed.cleanup_forecast is not None + assert refreshed.findings[0].guidance_kind == "preserve" + + +def test_with_refreshed_cleanup_forecast_preserves_shadow_ci_exit(tmp_path: Path) -> None: + source = tmp_path / "sample.py" + source.write_text("def total(values: list[int]) -> int:\n return sum(values)\n", encoding="utf-8") + report = ReviewReport( + run_id="review", + score=90, + findings=[ + ReviewFinding( + category="tool_error", + severity="error", + tool="ast", + rule="tool_error", + file=str(source), + line=1, + message="Unable to parse Python source.", + fixable=False, + ) + ], + summary="Shadow mode.", + ).model_copy(update={"ci_exit_code": 0}) + + refreshed = with_refreshed_cleanup_forecast(report, [source]) + + assert refreshed.ci_exit_code == 0 + assert refreshed.cleanup_forecast is not None + + def test_with_previewed_simplification_findings_records_patch_ref(tmp_path: Path) -> None: source = tmp_path / "sample.py" source.write_text( @@ -54,6 +96,45 @@ def _apply(report: ReviewReport) -> list[ReviewFinding]: assert source.read_text(encoding="utf-8").count("result") == 2 +def test_with_previewed_simplification_findings_keeps_existing_packet_refs(tmp_path: Path) -> None: + source = tmp_path / "sample.py" + source.write_text( + "def total(values: list[int]) -> int:\n result = sum(values)\n return result\n", encoding="utf-8" + ) + packet = RemediationPacket( + issue="Simplify local code.", + recommended_action="inline", + safety_checks=["compare expression"], + validation_plan=["run targeted tests"], + safe_to_autofix=True, + patch_forecast_refs=["preview:previous.py:1"], + ) + finding = _finding(source).model_copy(update={"remediation_packet": packet}) + + def _apply(report: ReviewReport) -> list[ReviewFinding]: + preview_path = Path(report.findings[0].file) + preview_path.write_text("def total(values: list[int]) -> int:\n return sum(values)\n", encoding="utf-8") + return [report.findings[0]] + + previewed = with_previewed_simplification_findings(_report(finding), [source], _apply) + + assert previewed.findings[0].remediation_packet is not None + assert previewed.findings[0].remediation_packet.patch_forecast_refs == [ + "preview:previous.py:1", + f"preview:{source}:2", + ] + + +def test_with_mutation_evidence_keeps_non_safe_findings_without_signal(tmp_path: Path) -> None: + source = tmp_path / "sample.py" + source.write_text("def total(values: list[int]) -> int:\n return sum(values)\n", encoding="utf-8") + finding = _finding(source).model_copy(update={"guidance_kind": "needs_tests"}) + + report = with_mutation_evidence(_report(finding), [source]) + + assert report.findings[0].signal_trace is None + + def test_with_mutation_evidence_records_inconclusive_signal(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: source = tmp_path / "sample.py" source.write_text("def total(values: list[int]) -> int:\n return sum(values)\n", encoding="utf-8") @@ -64,3 +145,14 @@ def test_with_mutation_evidence_records_inconclusive_signal(monkeypatch: pytest. assert report.findings[0].signal_trace is not None assert report.findings[0].signal_trace[-1].source == "mutation" assert report.findings[0].signal_trace[-1].value == "inconclusive: mutmut unavailable" + + +def test_with_mutation_evidence_records_scaffolding_signal(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + source = tmp_path / "sample.py" + source.write_text("def total(values: list[int]) -> int:\n return sum(values)\n", encoding="utf-8") + monkeypatch.setattr("specfact_code_review.run.cleanup_evidence._mutation_tool_available", lambda: True) + + report = with_mutation_evidence(_report(_finding(source)), [source]) + + assert report.findings[0].signal_trace is not None + assert report.findings[0].signal_trace[-1].value == "inconclusive: mutation scaffolding only" diff --git a/tests/unit/specfact_code_review/run/test_commands.py b/tests/unit/specfact_code_review/run/test_commands.py index 9f28906..3858d01 100644 --- a/tests/unit/specfact_code_review/run/test_commands.py +++ b/tests/unit/specfact_code_review/run/test_commands.py @@ -1,5 +1,7 @@ from __future__ import annotations +import json +import re import subprocess from datetime import UTC, datetime from pathlib import Path @@ -23,6 +25,11 @@ "ai-bloat.redundant-intermediate": "inline", "ai-bloat.verbose-bool-return": "collapse", } +ANSI_RE = re.compile(r"\x1b\[[0-9;]*m") + + +def _strip_ansi(text: str) -> str: + return ANSI_RE.sub("", text) def _report(*, score: int = 85) -> ReviewReport: @@ -327,7 +334,17 @@ def test_run_command_rejects_preview_fixes_with_fix() -> None: ) assert result.exit_code == 2 - assert "Cannot combine --preview-fixes with --fix" in result.output + assert "Cannot combine --preview-fixes with --fix" in _strip_ansi(result.output) + + +def test_run_command_rejects_preview_fixes_without_simplify_focus() -> None: + result = runner.invoke( + app, + ["review", "run", "--preview-fixes", "tests/fixtures/review/clean_module.py"], + ) + + assert result.exit_code == 2 + assert "Use --preview-fixes only with --focus simplify" in _strip_ansi(result.output) def test_run_command_rejects_with_mutation_without_simplify_focus() -> None: @@ -337,7 +354,7 @@ def test_run_command_rejects_with_mutation_without_simplify_focus() -> None: ) assert result.exit_code == 2 - assert "Use --with-mutation only with --focus simplify" in result.output + assert "Use --with-mutation only with --focus simplify" in _strip_ansi(result.output) def test_preview_fixes_adds_patch_forecast_without_mutating_tracked_file(monkeypatch: Any, tmp_path: Path) -> None: @@ -394,6 +411,60 @@ def test_with_mutation_records_inconclusive_evidence_for_missing_tool(monkeypatc assert mutation_report.findings[0].signal_trace[-1].value == "inconclusive: mutmut unavailable" +def _blocking_shadow_report(target: Path) -> ReviewReport: + return ReviewReport( + run_id="review-run-001", + timestamp=datetime(2026, 3, 16, tzinfo=UTC), + score=85, + findings=[ + ReviewFinding( + category="tool_error", + severity="error", + tool="ast", + rule="tool_error", + file=str(target), + line=1, + message="Unable to parse Python source.", + fixable=False, + ) + ], + summary="Shadow-mode report with blocking finding.", + ).model_copy(update={"ci_exit_code": 0}) + + +@pytest.mark.parametrize("evidence_flag", ["preview_fixes", "with_mutation"]) +def test_cleanup_evidence_preserves_shadow_mode_ci_exit( + monkeypatch: Any, + tmp_path: Path, + evidence_flag: str, +) -> None: + target = tmp_path / "sample.py" + target.write_text("def total(values: list[int]) -> int:\n return sum(values)\n", encoding="utf-8") + monkeypatch.setattr( + "specfact_code_review.run.commands.run_review", lambda files, **kwargs: _blocking_shadow_report(target) + ) + monkeypatch.setattr("specfact_code_review.run.cleanup_evidence._mutation_tool_available", lambda: False) + + request = run_commands.ReviewRunRequest( + files=[target], + json_output=True, + out=tmp_path / "review-report.json", + focus_facets=("simplify",), + review_mode="shadow", + ) + if evidence_flag == "preview_fixes": + request = run_commands.ReviewRunRequest(**{**request.__dict__, "preview_fixes": True}) + else: + request = run_commands.ReviewRunRequest(**{**request.__dict__, "with_mutation": True}) + + exit_code, output = run_commands.run_command(request) + + assert exit_code == 0 + assert output == str(tmp_path / "review-report.json") + report_payload = json.loads((tmp_path / "review-report.json").read_text(encoding="utf-8")) + assert report_payload["ci_exit_code"] == 0 + + def test_apply_simplification_fixes_inlines_redundant_intermediate(tmp_path: Path) -> None: target = tmp_path / "sample.py" target.write_text( diff --git a/tests/unit/specfact_code_review/run/test_findings.py b/tests/unit/specfact_code_review/run/test_findings.py index 3748d25..1c15ebb 100644 --- a/tests/unit/specfact_code_review/run/test_findings.py +++ b/tests/unit/specfact_code_review/run/test_findings.py @@ -80,6 +80,44 @@ def _finding_data(**overrides: Unpack[ReviewFindingPayload]) -> ReviewFindingPay return data +def _agent_payload_finding() -> ReviewFinding: + return ReviewFinding( + **_finding_data( + category="ai_bloat", + severity="info", + tool="ast", + rule="ai-bloat.redundant-intermediate", + file="src/example.py", + line=1, + message="Simplify local code.", + fixable=True, + signal_trace=[ + SignalTraceEntry( + tool="ast", + source="ai-bloat.redundant-intermediate", + fired=True, + explanation="AST pattern matched a redundant intermediate assignment.", + ) + ], + preserve_reasons=[ + PreserveReasonEvidence( + reason="public_api", + evidence_refs=[EvidenceRef(path="src/example.py", start_line=1)], + explanation="Public API boundary.", + ) + ], + remediation_packet=RemediationPacket( + issue="Simplify local code.", + recommended_action="inspect", + possible_keep_reason="Public API boundary.", + safety_checks=["verify public behavior"], + validation_plan=["run targeted tests"], + safe_to_autofix=False, + ), + ) + ) + + def test_review_finding_accepts_valid_values() -> None: finding = ReviewFinding(**_finding_data()) @@ -485,6 +523,21 @@ def test_review_report_uses_schema_1_3_when_cleanup_forecast_is_present() -> Non assert report.cleanup_forecast.ai_bloat_index.weighted_bloat_points_per_kloc == 16.0 +def test_review_report_uses_schema_1_3_when_finding_agent_payload_is_present() -> None: + report = ReviewReport( + run_id="run-cleanup-handoff", + timestamp=datetime(2026, 5, 24, tzinfo=UTC), + score=85, + findings=[_agent_payload_finding()], + summary="Cleanup agent payload.", + ) + + assert report.schema_version == "1.3" + assert report.findings[0].signal_trace is not None + assert report.findings[0].preserve_reasons is not None + assert report.findings[0].remediation_packet is not None + + def test_reviewed_loc_rejects_total_mismatch() -> None: with pytest.raises(ValidationError, match=r"reviewed_loc.total must equal production \+ tests"): ReviewedLoc(production=80, tests=20, total=90) diff --git a/tests/unit/specfact_code_review/run/test_forecast.py b/tests/unit/specfact_code_review/run/test_forecast.py index e41133c..0f330fd 100644 --- a/tests/unit/specfact_code_review/run/test_forecast.py +++ b/tests/unit/specfact_code_review/run/test_forecast.py @@ -3,6 +3,8 @@ from pathlib import Path from typing import Literal, cast +from pytest import MonkeyPatch + from specfact_code_review.run.findings import ReviewFinding from specfact_code_review.run.forecast import build_cleanup_forecast @@ -33,17 +35,20 @@ def _finding(*, guidance_kind: str, deletion_lines: int) -> ReviewFinding: ) -def test_build_cleanup_forecast_counts_loc_and_weighted_bloat(tmp_path: Path) -> None: - source = tmp_path / "src" / "example.py" +def test_build_cleanup_forecast_counts_loc_and_weighted_bloat(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + monkeypatch.chdir(tmp_path) + source = Path("src/example.py") source.parent.mkdir() source.write_text("# comment\n\nvalue = 1\nprint(value)\n", encoding="utf-8") - test_file = tmp_path / "tests" / "test_example.py" + test_file = Path("tests/test_example.py") test_file.parent.mkdir() test_file.write_text("def test_example():\n assert True\n", encoding="utf-8") forecast = build_cleanup_forecast( [ _finding(guidance_kind="safe_mechanical", deletion_lines=2), + _finding(guidance_kind="needs_tests", deletion_lines=3), + _finding(guidance_kind="design_judgment", deletion_lines=4), _finding(guidance_kind="preserve", deletion_lines=5), ], [source, test_file], @@ -52,13 +57,31 @@ def test_build_cleanup_forecast_counts_loc_and_weighted_bloat(tmp_path: Path) -> assert forecast.reviewed_loc.production == 2 assert forecast.reviewed_loc.tests == 2 assert forecast.estimated_deletion_lines.low == 2 - assert forecast.estimated_deletion_lines.high == 2 + assert forecast.estimated_deletion_lines.expected == 5 + assert forecast.estimated_deletion_lines.high == 9 + assert forecast.by_guidance_kind["safe_mechanical"].weight == 1.0 + assert forecast.by_guidance_kind["needs_tests"].weight == 0.6 + assert forecast.by_guidance_kind["design_judgment"].weight == 0.25 + assert forecast.by_guidance_kind["preserve"].weight == 0.0 assert forecast.by_guidance_kind["preserve"].estimated_deletion_lines == 5 - assert forecast.ai_bloat_index.weighted_bloat_points_per_kloc == 250.0 + assert forecast.ai_bloat_index.weighted_bloat_points_per_kloc == 462.5 + + +def test_build_cleanup_forecast_counts_test_directory_as_tests(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + monkeypatch.chdir(tmp_path) + test_file = Path("unit_tests/test_example.py") + test_file.parent.mkdir() + test_file.write_text("def test_example():\n assert True\n", encoding="utf-8") + + forecast = build_cleanup_forecast([], [test_file]) + + assert forecast.reviewed_loc.production == 0 + assert forecast.reviewed_loc.tests == 2 -def test_build_cleanup_forecast_skips_undecodable_python_files(tmp_path: Path) -> None: - source = tmp_path / "legacy.py" +def test_build_cleanup_forecast_skips_undecodable_python_files(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + monkeypatch.chdir(tmp_path) + source = Path("legacy.py") source.write_bytes(b"\xff\xfe\x00") forecast = build_cleanup_forecast([_finding(guidance_kind="safe_mechanical", deletion_lines=2)], [source]) diff --git a/tests/unit/specfact_code_review/run/test_runner.py b/tests/unit/specfact_code_review/run/test_runner.py index 4db4420..4c6bdc5 100644 --- a/tests/unit/specfact_code_review/run/test_runner.py +++ b/tests/unit/specfact_code_review/run/test_runner.py @@ -271,7 +271,8 @@ def test_run_review_simplify_enforce_fails_only_safe_mechanical_recommendations( def test_run_review_simplify_forecast_counts_loc_and_weighted_bloat(monkeypatch: MonkeyPatch, tmp_path: Path) -> None: - source = tmp_path / "src/example.py" + monkeypatch.chdir(tmp_path) + source = Path("src/example.py") source.parent.mkdir(parents=True) source.write_text( "def one() -> int:\n" @@ -285,7 +286,7 @@ def test_run_review_simplify_forecast_counts_loc_and_weighted_bloat(monkeypatch: " return False\n", encoding="utf-8", ) - test_file = tmp_path / "tests/test_example.py" + test_file = Path("tests/test_example.py") test_file.parent.mkdir(parents=True) test_file.write_text("def test_example() -> None:\n assert True\n", encoding="utf-8") safe = _simplification_finding(category="ai_bloat", guidance_kind="safe_mechanical") @@ -441,6 +442,29 @@ def test_preserve_detection_treats_docstring_only_protocol_method_as_stub(tmp_pa assert "protocol_member" in {reason.reason for reason in reasons} +def test_preserve_detection_reloads_source_after_file_mutation(tmp_path: Path) -> None: + source = tmp_path / "api.py" + source.write_text( + "from typing import Protocol\n\nclass Handler(Protocol):\n def handle(self, payload: str) -> str: ...\n", + encoding="utf-8", + ) + finding = _simplification_finding(category="ai_bloat", guidance_kind="safe_mechanical").model_copy( + update={"file": str(source), "line": 4} + ) + reasons = _preserve_reasons_for_finding(finding, load_bearing=False) + assert "protocol_member" in {reason.reason for reason in reasons} + + source.write_text( + "def helper(payload: str) -> str:\n result = payload.strip()\n return result\n", + encoding="utf-8", + ) + changed_finding = finding.model_copy(update={"line": 3}) + + changed_reasons = _preserve_reasons_for_finding(changed_finding, load_bearing=False) + + assert "protocol_member" not in {reason.reason for reason in changed_reasons} + + def test_run_review_simplify_focus_preserves_tool_errors(monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr("specfact_code_review.run.runner.run_ruff", lambda files: []) monkeypatch.setattr("specfact_code_review.run.runner.run_radon", lambda files: [])