Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .quale/ci-history.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,24 @@
{"timestamp": 1779977056.047733, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 3, "blast_radius_count": 27, "mirror_gap_ratio": 0.156, "stable_touched_count": 3, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 2}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779977189.523247, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 3, "blast_radius_count": 27, "mirror_gap_ratio": 0.156, "stable_touched_count": 3, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 2}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779977298.7549012, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 3, "blast_radius_count": 27, "mirror_gap_ratio": 0.156, "stable_touched_count": 3, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 2}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779981329.1963623, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 27, "mirror_gap_ratio": 0.157, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 2}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779981416.7200527, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 27, "mirror_gap_ratio": 0.157, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 2}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779981934.0995564, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 9, "blast_radius_count": 0, "mirror_gap_ratio": 0.0, "stable_touched_count": 9, "max_blast_tier": "none", "hub_risk_flagged": [], "clone_flagged": [{"file": "SKILL.md", "clone_group": [".editorconfig", ".github/ISSUE_TEMPLATE/01-bug-report.md", ".github/ISSUE_TEMPLATE/02-feature-request.md"], "similarity": 0.167}, {"file": "CHANGELOG.md", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}, {"file": "docs/ALGORITHM.md", "clone_group": ["tests/schemas/agent_edit_schema.json"], "similarity": 0.231}], "new_identifier_count": 2}
{"timestamp": 1779982054.1775947, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 9, "blast_radius_count": 0, "mirror_gap_ratio": 0.0, "stable_touched_count": 9, "max_blast_tier": "none", "hub_risk_flagged": [], "clone_flagged": [{"file": "SKILL.md", "clone_group": [".editorconfig", ".github/ISSUE_TEMPLATE/01-bug-report.md", ".github/ISSUE_TEMPLATE/02-feature-request.md"], "similarity": 0.167}, {"file": "CHANGELOG.md", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}, {"file": "docs/ALGORITHM.md", "clone_group": ["tests/schemas/agent_edit_schema.json"], "similarity": 0.231}], "new_identifier_count": 2}
{"timestamp": 1779982222.5337775, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 9, "blast_radius_count": 0, "mirror_gap_ratio": 0.0, "stable_touched_count": 9, "max_blast_tier": "none", "hub_risk_flagged": [], "clone_flagged": [{"file": "SKILL.md", "clone_group": [".editorconfig", ".github/ISSUE_TEMPLATE/01-bug-report.md", ".github/ISSUE_TEMPLATE/02-feature-request.md"], "similarity": 0.167}, {"file": "CHANGELOG.md", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}, {"file": "docs/ALGORITHM.md", "clone_group": ["tests/schemas/agent_edit_schema.json"], "similarity": 0.231}], "new_identifier_count": 2}
{"timestamp": 1779983534.7062922, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 9, "blast_radius_count": 0, "mirror_gap_ratio": 0.0, "stable_touched_count": 9, "max_blast_tier": "none", "hub_risk_flagged": [], "clone_flagged": [{"file": "SKILL.md", "clone_group": [".editorconfig", ".github/ISSUE_TEMPLATE/01-bug-report.md", ".github/ISSUE_TEMPLATE/02-feature-request.md"], "similarity": 0.167}, {"file": "CHANGELOG.md", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}, {"file": "docs/ALGORITHM.md", "clone_group": ["tests/schemas/agent_edit_schema.json"], "similarity": 0.231}], "new_identifier_count": 2}
{"timestamp": 1779983558.0505624, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 9, "blast_radius_count": 0, "mirror_gap_ratio": 0.0, "stable_touched_count": 9, "max_blast_tier": "none", "hub_risk_flagged": [], "clone_flagged": [{"file": "SKILL.md", "clone_group": [".editorconfig", ".github/ISSUE_TEMPLATE/01-bug-report.md", ".github/ISSUE_TEMPLATE/02-feature-request.md"], "similarity": 0.167}, {"file": "CHANGELOG.md", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}, {"file": "docs/ALGORITHM.md", "clone_group": ["tests/schemas/agent_edit_schema.json"], "similarity": 0.231}], "new_identifier_count": 2}
{"timestamp": 1779984277.7516272, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 9, "blast_radius_count": 0, "mirror_gap_ratio": 0.0, "stable_touched_count": 9, "max_blast_tier": "none", "hub_risk_flagged": [], "clone_flagged": [{"file": "SKILL.md", "clone_group": [".editorconfig", ".github/ISSUE_TEMPLATE/01-bug-report.md", ".github/ISSUE_TEMPLATE/02-feature-request.md"], "similarity": 0.167}, {"file": "CHANGELOG.md", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}, {"file": "docs/ALGORITHM.md", "clone_group": ["tests/schemas/agent_edit_schema.json"], "similarity": 0.231}], "new_identifier_count": 2}
{"timestamp": 1779984385.8378215, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 9, "blast_radius_count": 0, "mirror_gap_ratio": 0.0, "stable_touched_count": 9, "max_blast_tier": "none", "hub_risk_flagged": [], "clone_flagged": [{"file": "SKILL.md", "clone_group": [".editorconfig", ".github/ISSUE_TEMPLATE/01-bug-report.md", ".github/ISSUE_TEMPLATE/02-feature-request.md"], "similarity": 0.167}, {"file": "CHANGELOG.md", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}, {"file": "docs/ALGORITHM.md", "clone_group": ["tests/schemas/agent_edit_schema.json"], "similarity": 0.231}], "new_identifier_count": 2}
{"timestamp": 1779984516.3846793, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 9, "blast_radius_count": 0, "mirror_gap_ratio": 0.0, "stable_touched_count": 9, "max_blast_tier": "none", "hub_risk_flagged": [], "clone_flagged": [{"file": "SKILL.md", "clone_group": [".editorconfig", ".github/ISSUE_TEMPLATE/01-bug-report.md", ".github/ISSUE_TEMPLATE/02-feature-request.md"], "similarity": 0.167}, {"file": "CHANGELOG.md", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}, {"file": "docs/ALGORITHM.md", "clone_group": ["tests/schemas/agent_edit_schema.json"], "similarity": 0.231}], "new_identifier_count": 2}
{"timestamp": 1779984862.5219462, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779984863.5196605, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779984921.3020675, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779984932.9459064, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779984933.9965522, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779985326.1378722, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779985337.1294284, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779985338.3734188, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779985490.708004, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779985500.4287434, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
{"timestamp": 1779985501.299929, "base_ref": "HEAD~1", "head_ref": "HEAD", "changed_files": 4, "blast_radius_count": 24, "mirror_gap_ratio": 0.156, "stable_touched_count": 4, "max_blast_tier": "critical", "hub_risk_flagged": [{"file": "quale/cli.py", "hub_rank": 3}], "clone_flagged": [{"file": ".quale/ci-history.jsonl", "clone_group": [".github/ISSUE_TEMPLATE/config.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/stale.yml"], "similarity": 0.083}], "new_identifier_count": 1}
3 changes: 3 additions & 0 deletions docs/FEATURE_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ These replace multiple old commands with single, mode-switched interfaces.
| `agent-bootstrap` | Utilities | One-shot agent bootstrap: explore + modules + stability + related files. |
| `fingerprint` | Utilities | Structural fingerprint of a file or entire repo. |
| `orient` | Utilities | One-call orientation: solve + triangulate + isolate. |
| `lifecycle` | History | Classify phrases as growing, stable, decaying, dead, seasonal. |
| `timeline` | History | Track phrases through git history. |
| `provenance` | History | Trace a phrase's presence through git history. |
66 changes: 56 additions & 10 deletions quale/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5291,6 +5291,11 @@ def risk_cmd(
result = {"critical_files": cr, "hub_files": dt, "capillary_files": ch}
if format == "json":
typer.echo(json.dumps(result, indent=2))
if ci and cr:
raise typer.Exit(1)
if ci and not cr:
typer.echo(f"{ICON_CHECK} No critical files — CI gate passed")
if format == "json":
return
if cr:
typer.echo(f"{ICON_WARN} Critical (Hub + Capillary):")
Expand All @@ -5306,6 +5311,8 @@ def risk_cmd(
typer.echo(f" {f}")
if ci and cr:
raise typer.Exit(1)
if ci and not cr:
typer.echo(f"{ICON_CHECK} No critical files — CI gate passed")

# ── Unified verify command ────────────────────────────────────────────────────

Expand Down Expand Up @@ -5340,13 +5347,23 @@ def verify_cmd(
if format == "json":
typer.echo(json.dumps(data, indent=2))
return
risk = data.get("risk", "?")
risk = data.get("risk") or data.get("post_edit_risk", "?")
file = data.get("file", "")
guide = data.get("guide", "")
typer.echo(f"File: {file}")
typer.echo(f"Risk: {risk}")
if guide:
typer.echo(f"Guide: {guide}")
vc = data.get("verification_candidates", [])
tier = data.get("tier", "")
if tier:
typer.echo(f"Tier: {tier}")
if vc:
typer.echo("Verification Candidates:")
for c in vc:
typer.echo(f" {ICON_PRIMARY} {c}")
elif not risk and not file and not guide:
typer.echo("(no output for this mode; use --format json for full data)")


# ── Unified health command ────────────────────────────────────────────────────
Expand Down Expand Up @@ -5375,8 +5392,18 @@ def health_cmd(
if format == "json":
typer.echo(json.dumps(data, indent=2))
return
g = data.get("spectral_gap", data.get("gap", {}).get("spectral_gap", 0))
typer.echo(f"Health: Spectral gap={g}")
porosity = data.get("porosity", {})
gap = data.get("gap", {})
sg = gap.get("spectral_gap", porosity.get("spectral_gap", "?"))
excess = porosity.get("excess_porosity", "?")
typer.echo(f"Health: spectral gap={sg} excess porosity={excess}")
if isinstance(excess, (int, float)):
if excess > 0.5:
typer.echo(" High coupling density — consider splitting modules")
elif excess < -0.5:
typer.echo(" Low coupling — well-separated modules")
else:
typer.echo(" Moderate coupling")

# ── Unified audit command ─────────────────────────────────────────────────────

Expand All @@ -5393,15 +5420,20 @@ def audit_cmd(
if not vgit.is_repo(p):
typer.echo("Not a git repository.", err=True)
raise typer.Exit(1)
data = {
"ci": ci_report(base_ref=base_ref, head_ref=head_ref, path=p),
"pr": check_pr_report(path=p, base_ref=base_ref, head_ref=head_ref),
"diff": check_diff_report(path=p, diff_ref=head_ref),
}
ci_data = ci_report(base_ref=base_ref, head_ref=head_ref, path=p)
pr_data = check_pr_report(path=p, base_ref=base_ref, head_ref=head_ref)
diff_data = check_diff_report(path=p, diff_ref=head_ref)
data = {"ci": ci_data, "pr": pr_data, "diff": diff_data}
if format == "json":
typer.echo(json.dumps(data, indent=2))
return
typer.echo(f"Audit: {base_ref}..{head_ref}")
if ci_data:
typer.echo(f" Blast radius: {ci_data.get('max_blast_tier', '?')}")
typer.echo(f" Mirror gap: {ci_data.get('mirror_gap_ratio', '?')}")
typer.echo(f" Test coverage: {ci_data.get('mirror_cov', '?')}")
if pr_data:
typer.echo(f" PR parity: {pr_data.get('parity_bit', '?')}")

# ── Unified temporal command ──────────────────────────────────────────────────

Expand All @@ -5418,16 +5450,30 @@ def temporal_cmd(
typer.echo("Not a git repository.", err=True)
raise typer.Exit(1)
if not file:
typer.echo("provide --file <path>", err=True)
typer.echo("provide --file <path> (e.g. --file src/main.py)", err=True)
raise typer.Exit(1)
data = decay_report(path=p, file_path=file)
if "error" in data and "/" not in file and "\\" not in file:
for candidate in [os.path.join(d, file) for d in os.listdir(p) if os.path.isdir(os.path.join(p, d))]:
if os.path.isfile(candidate):
data = decay_report(path=p, file_path=candidate)
break
if "error" in data:
typer.echo(data["error"], err=True)
raise typer.Exit(1)
if format == "json":
typer.echo(json.dumps(data, indent=2))
return
typer.echo(f"Temporal: {file}")
typer.echo(f"File: {file}")
decaying = data.get("decaying_patterns", [])
tracked = data.get("phrases_tracked", 0)
typer.echo(f"Phrases tracked: {tracked}")
if decaying:
typer.echo(f"Decaying patterns ({len(decaying)}):")
for d in decaying[:5]:
typer.echo(f" {ICON_WARN} {d.get('phrase', '?')} ({d.get('legacy_pattern', '?')} → {d.get('replacement', '?')})")
else:
typer.echo(" No decaying patterns detected")

if __name__ == "__main__":
_entry_main()
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/health_score.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"excess_porosity": -0.014038,
"excess_porosity": -0.014024,
"schema_version": null
}
Loading
Loading