Skip to content

chore: add drift-cli fix, calibrate, validate command compatibility stubs (ADR-100 phase 5b-stubs-b)#723

Open
mick-gsk wants to merge 1 commit into
mainfrom
split/576-714b-commands-stubs-fix
Open

chore: add drift-cli fix, calibrate, validate command compatibility stubs (ADR-100 phase 5b-stubs-b)#723
mick-gsk wants to merge 1 commit into
mainfrom
split/576-714b-commands-stubs-fix

Conversation

@mick-gsk
Copy link
Copy Markdown
Owner

@mick-gsk mick-gsk commented May 3, 2026

Split from #576 (ADR-100 monorepo migration). Part of the PR decomposition into atomic, reviewable units.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR replaces several drift.commands.* command implementations with compatibility re-export stubs that forward to the new drift_cli.commands.* modules as part of the ADR-100 monorepo migration (phase 5b stubs).

Changes:

  • Removed legacy Click command implementations from src/drift/commands/* and replaced them with re-export shims to drift_cli.commands.*.
  • Added sys.modules aliasing so imports of drift.commands.<cmd> resolve to the corresponding drift_cli.commands.<cmd> module.
  • Kept public command entrypoints (e.g., validate, fix_plan, config, etc.) available under the old import paths for compatibility.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/drift/commands/verify.py Re-export shim forwarding verify to drift_cli.commands.verify
src/drift/commands/validate_cmd.py Re-export shim forwarding validate to drift_cli.commands.validate_cmd
src/drift/commands/suppress.py Re-export shim forwarding suppress subcommands to drift_cli.commands.suppress
src/drift/commands/setup.py Re-export shim forwarding setup to drift_cli.commands.setup
src/drift/commands/self_improve.py Re-export shim forwarding self-improve commands to drift_cli.commands.self_improve
src/drift/commands/self_analyze.py Re-export shim forwarding self_analyze to drift_cli.commands.self_analyze
src/drift/commands/preset.py Re-export shim forwarding preset commands to drift_cli.commands.preset
src/drift/commands/patch_cmd.py Re-export shim forwarding patch commands to drift_cli.commands.patch_cmd
src/drift/commands/init_cmd.py Re-export shim forwarding init to drift_cli.commands.init_cmd
src/drift/commands/fix_plan.py Re-export shim forwarding fix_plan to drift_cli.commands.fix_plan
src/drift/commands/feedback.py Re-export shim forwarding feedback commands to drift_cli.commands.feedback
src/drift/commands/config_cmd.py Re-export shim forwarding config commands to drift_cli.commands.config_cmd
src/drift/commands/completions.py Re-export shim forwarding completions to drift_cli.commands.completions
src/drift/commands/calibrate.py Re-export shim forwarding calibrate commands to drift_cli.commands.calibrate

Comment on lines +6 to 17
from drift_cli.commands.suppress import (
audit_suppressions as audit_suppressions,
)
@click.option(
"--config",
"-c",
type=click.Path(path_type=Path),
default=None,
help="Config file path.",
from drift_cli.commands.suppress import (
interactive as interactive,
)
@click.option(
"--since",
default=90,
type=int,
help="Days of git history to consider (default: 90).",
from drift_cli.commands.suppress import (
list_suppressions as list_suppressions,
)
@click.option(
"--dry-run",
is_flag=True,
default=False,
help="Show decisions without writing any changes to disk.",
from drift_cli.commands.suppress import ( # noqa: F401
suppress as suppress,
)
Comment on lines +6 to 20
from drift_cli.commands.self_improve import (
apply as apply,
)
@click.option(
"--dry-run",
is_flag=True,
default=False,
help="Print planned actions without writing any files.",
from drift_cli.commands.self_improve import (
close as close,
)
@click.option(
"--output-dir",
type=click.Path(file_okay=False, path_type=Path),
default=None,
help="Directory for action artefacts (default: <proposals_dir>/../../dsol_actions).",
from drift_cli.commands.self_improve import (
ledger as ledger,
)
@click.option(
"--format",
"output_format",
type=click.Choice(["text", "json"]),
default="text",
from drift_cli.commands.self_improve import (
run as run,
)
def apply(
proposals_path: Path,
dry_run: bool,
output_dir: Path | None,
output_format: str,
) -> None:
"""Write action-artefacts for each proposal (ADR-098 write-back).

Produces human-reviewable Markdown files (ADR stubs, triage guides,
audit action notes) — never modifies source code or scoring config.
"""
raw = json.loads(proposals_path.read_text(encoding="utf-8"))
report = ImprovementReport.model_validate(raw)

if output_dir is None:
# Default: work_artifacts/dsol_actions/<cycle_ts>/
output_dir = proposals_path.parent.parent.parent / "dsol_actions" / report.cycle_ts

created: list[str] = []

for p in report.proposals:
kind = p.kind
if kind == "stale_audit":
filename = f"stale_audit_action_{_safe_stem(report.cycle_ts)}.md"
content = (
f"# Stale Audit Action — {report.cycle_ts}\n\n"
f"**Proposal:** `{p.proposal_id}`\n\n"
f"**Rationale:** {p.rationale}\n\n"
f"## Required Action\n\n"
f"{p.suggested_action}\n\n"
f"```sh\nmake audit-diff\n```\n"
)
elif kind == "regressive_signal":
stem = _safe_stem(p.signal_type or p.proposal_id)
filename = f"adr_stub_{stem}.md"
content = (
f"# ADR Draft — Regressive Signal: {p.signal_type or p.proposal_id}\n\n"
f"- Status: proposed\n"
f"- Generated by DSOL cycle: {report.cycle_ts}\n\n"
f"## Context\n\n{p.rationale}\n\n"
f"## Suggested Action\n\n{p.suggested_action}\n\n"
f"## Decision\n\n_[Maintainer fills in]_\n\n"
f"## Consequences\n\n_[Maintainer fills in]_\n"
)
elif kind == "fp_rate_exceeded":
stem = _safe_stem(p.signal_type or p.proposal_id)
filename = f"fp_triage_{stem}.md"
content = (
f"# FP Triage — {p.signal_type or p.proposal_id}\n\n"
f"**Generated by DSOL cycle:** {report.cycle_ts}\n\n"
f"## Finding\n\n{p.rationale}\n\n"
f"## Triage Steps\n\n{p.suggested_action}\n\n"
f"1. Open `benchmark_results/oracle_fp_report.json`\n"
f"2. Review labeled samples for signal `{p.signal_type}`\n"
f"3. Classify root cause (threshold / scope / semantic)\n"
f"4. Decide: adjust threshold, add suppression, or file ADR\n"
)
elif kind == "hotspot_finding":
stem = _safe_stem(p.proposal_id)
filename = f"hotspot_{stem}.md"
content = (
f"# Hotspot Finding Action — {report.cycle_ts}\n\n"
f"**Proposal:** `{p.proposal_id}`\n"
f"**Signal:** `{p.signal_type}`\n"
f"**File:** `{p.file_path}`\n"
f"**Severity:** `{p.severity}`\n\n"
f"## Rationale\n\n{p.rationale}\n\n"
f"## Suggested Action\n\n{p.suggested_action}\n"
)
else:
stem = _safe_stem(p.proposal_id)
filename = f"action_{stem}.md"
content = (
f"# Action — {p.proposal_id}\n\n"
f"**Cycle:** {report.cycle_ts}\n\n"
f"## Rationale\n\n{p.rationale}\n\n"
f"## Suggested Action\n\n{p.suggested_action}\n"
)

if dry_run:
click.echo(f"[dry-run] would write: {output_dir / filename}")
else:
path = _write_action(output_dir, filename, content)
created.append(str(path))

if output_format == "json":
click.echo(json.dumps({"dry_run": dry_run, "created": created}))
else:
if dry_run:
click.echo(f"Dry-run complete. {len(report.proposals)} action(s) planned.")
else:
click.echo(f"Applied {len(created)} action artefact(s) to {output_dir}")


@self_improve.command()
@click.argument("proposal_id")
@click.option(
"--note",
"outcome_note",
default="",
help="Short outcome note (e.g. 'implemented in PR #42').",
from drift_cli.commands.self_improve import ( # noqa: F401
self_improve as self_improve,
)
Comment on lines +6 to +10
from drift_cli.commands.verify import ( # noqa: F401
verify as verify,
)
@click.option(
"--scope",
default=None,
help="Comma-separated file paths to restrict verification scope.",
)
def verify(
repo: Path,
ref: str | None,
uncommitted: bool,
staged_only: bool,
fail_on: str,
baseline: Path | None,
output_format: str,
exit_zero: bool,
output_file: Path | None,
scope: str | None,
) -> None:
"""Verify structural coherence after edits — binary pass/fail verdict.

Designed for CI pipelines and agent workflows. Returns PASS when no
new findings above the severity threshold are introduced and the
drift score has not degraded.

\b
Examples:
drift verify # Quick check, rich output
drift verify --format json # Machine-readable verdict
drift verify --ref main --no-uncommitted # Compare against explicit ref
drift verify --fail-on medium # Stricter threshold
drift verify --scope src/api.py,src/db.py # Restrict to specific files
drift verify --exit-zero # Report-only mode
"""
from drift.api.verify import verify as api_verify

scope_files = [s.strip() for s in scope.split(",") if s.strip()] if scope else None

result = api_verify(
path=str(repo),
ref=ref,
uncommitted=uncommitted if not staged_only else False,
staged_only=staged_only,
fail_on=fail_on,
baseline=str(baseline) if baseline else None,
scope_files=scope_files,
)

# Handle error responses.
if result.get("type") == "error":
if output_format == "json":
_emit_machine_output(json.dumps(result, indent=2, default=str), output_file)
else:
console.print(
f"[bold red]{fail_glyph(console)} Verify error:[/bold red]"
f" {result.get('message', '')}"
)
if not exit_zero:
sys.exit(EXIT_FINDINGS_ABOVE_THRESHOLD)
return

passed = result.get("pass", False)

if output_format == "json":
_emit_machine_output(json.dumps(result, indent=2, default=str), output_file)
else:
_render_rich_verdict(result, console)

if not passed and not exit_zero:
sys.exit(EXIT_FINDINGS_ABOVE_THRESHOLD)


def _render_rich_verdict(result: dict, con: Console) -> None:
"""Render a human-readable pass/fail verdict."""
from rich.panel import Panel
from rich.table import Table

passed = result.get("pass", False)
delta = result.get("score_delta", 0.0)
direction = result.get("direction", "stable")
introduced = result.get("findings_introduced_count", 0)
resolved = result.get("findings_resolved_count", 0)
blocking = result.get("blocking_reasons", [])

if passed:
con.print(
Panel(
f"[bold green]{ok_glyph(con)} PASS[/bold green] — "
f"No structural coherence degradation detected.\n\n"
f" Score delta: {delta:+.4f} ({direction})\n"
f" New findings: {introduced} | Resolved: {resolved}",
title="[bold green]drift verify[/bold green]",
border_style="green",
)
)
else:
# Build blocking reasons table.
table = Table(show_header=True, header_style="bold red")
table.add_column("Type", style="dim")
table.add_column("Reason")
table.add_column("File", style="dim")

for reason in blocking[:10]:
table.add_row(
reason.get("type", ""),
reason.get("reason", ""),
reason.get("file", ""),
)

con.print(
Panel(
f"[bold red]{fail_glyph(con)} FAIL[/bold red] — "
f"{len(blocking)} blocking reason(s) detected.\n\n"
f" Score delta: {delta:+.4f} ({direction})\n"
f" New findings: {introduced} | Resolved: {resolved}",
title="[bold red]drift verify[/bold red]",
border_style="red",
)
)
con.print(table)
con.print(
"\n[dim]Run [bold]drift fix-plan[/bold] for a prioritized repair plan.[/dim]"
)
_sys.modules[__name__] = _importlib.import_module("drift_cli.commands.verify")
Comment on lines +24 to +26
from drift_cli.commands.calibrate import (
run as run,
)
Comment on lines +6 to +8
from drift_cli.commands.completions import ( # noqa: F401
completions as completions,
)
Comment on lines +9 to +11
from drift_cli.commands.config_cmd import (
schema as schema,
)
Comment on lines +12 to +14
from drift_cli.commands.config_cmd import (
show as show,
)
Comment on lines +6 to +8
from drift_cli.commands.preset import ( # noqa: F401
preset as preset,
)
Comment on lines +9 to +11
from drift_cli.commands.preset import (
preset_list as preset_list,
)
Comment on lines +12 to +14
from drift_cli.commands.preset import (
preset_show as preset_show,
)
@github-actions github-actions Bot added the cli Changes to CLI commands label May 3, 2026
@mick-gsk mick-gsk closed this May 4, 2026
@mick-gsk mick-gsk deleted the split/576-714b-commands-stubs-fix branch May 4, 2026 05:09
@mick-gsk mick-gsk restored the split/576-714b-commands-stubs-fix branch May 4, 2026 05:14
@mick-gsk mick-gsk reopened this May 4, 2026
@mick-gsk mick-gsk added release:maintenance Include this PR under Maintenance in release notes size/XL Diff ≥ 500 lines — consider splitting labels May 4, 2026
@github-actions github-actions Bot added agent-review-requested Agent review was requested automatically lane/standard Fixes and features — standard review path labels May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-review-requested Agent review was requested automatically cli Changes to CLI commands lane/standard Fixes and features — standard review path release:maintenance Include this PR under Maintenance in release notes size/XL Diff ≥ 500 lines — consider splitting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants