Skip to content

Latest commit

 

History

History
184 lines (155 loc) · 10.2 KB

File metadata and controls

184 lines (155 loc) · 10.2 KB

owen — the single command (alpha gate A, issue #202)

Public facade: Owen, package Owen.Cli, command owen. The project/namespace stay OwnSharp.Cli internally — this is a public-facing rename, not an internal refactor; see docs/notes/owen-public-facade.md for the full rationale and what did/didn't change.

Owen finds lifetime and resource-contract bugs. It is language-neutral at the OwnIR/core level; this distribution currently includes the .NET/C# frontend only — owen check <path|.sln|.csproj> wraps the two existing pipeline stages — the Roslyn extractor (OwnSharp.Extractor, P-013) and the Python core (ownlang/) — into one dotnet tool install. Same pipeline scripts/own-check.sh already chains by hand; this is that, packaged.

*.cs --[bundled extractor, in a child process]--> facts.json --[vendored core, run on system Python]--> findings

Packaging shape (design decision, issue #202)

  • The extractor is unmodified, pulled in via ProjectReference — its build output (dll + .deps.json/.runtimeconfig.json + Roslyn dependencies) rides along in this tool's own pack payload because PackAsTool packs the full publish closure. check invokes it as a child process (dotnet exec <bundled>/ownsharp-extract.dll ... — the extractor's own internal filename, unaffected by the public facade).
  • The core is unmodified, vendored as loose *.py content (see the .csproj) and unpacked to ~/.owen/core/<version>/ on first run (falling back to a previous ~/.ownsharp/core/<version>/ if already unpacked there by an older install — a plain reuse, not a migration) — never into the analyzed repo. It runs on the machine's own Python; nothing is embedded, compiled, or downloaded.
  • Python resolution: OWEN_PYTHON env var (used exactly as given, no fallback — an explicit override that fails is a config error, not a "keep guessing" case); OWN_PYTHON is honored as a temporary, deprecated fallback (prints a note to stderr when it's the one actually used); else py -3 (Windows) / python3 (elsewhere), version-checked to be >=3.11. No Python found → a fast, one-line, actionable failure (winget/apt/brew/python.org, per OS) — never an auto-download.
  • Unsupported input fails explicitly: a path that isn't a .cs/.csproj/ .sln file and isn't a directory containing any .cs file exits 4 with an explicit message — never a silent "0 findings" clean scan.
  • Rejected alternatives (embedding a CPython runtime, self-contained PyInstaller binaries as the default, waiting for the Rust core, porting the core to C#) are on the record in the issue; do not re-litigate them here.

Build & install locally

Not published to nuget.org yet (P-013's Non-goals) — build and install from source:

dotnet pack frontend/roslyn/OwnSharp.Cli/OwnSharp.Cli.csproj -c Release -o /tmp/owen-nupkg
dotnet tool install --global Owen.Cli --version 0.1.0 --add-source /tmp/owen-nupkg

owen check MyApp.sln                                    # human output
owen check . --format github --fail-on-finding           # PR annotations, non-zero on a leak
owen check . --format sarif > owen.sarif                 # feed github/codeql-action/upload-sarif

Uninstall/upgrade: dotnet tool uninstall --global Owen.Cli, then reinstall as above (bump --version if you rebuilt with a new <Version>).

Engine selection (#262 Stage 1)

Python is the default and the reference. --engine selects otherwise:

Value What runs
python the vendored Python core — the default and the reference
rust the Rust core (own-cli ownir) instead
compare both, over one captured input, reporting the reference's result only when they agree byte for byte

rust and compare need the candidate binary's absolute path in OWEN_RUST_CORE. There is deliberately no discovery — no PATH lookup, no rust/target/ probing, no "first binary found" — because discovery is how a stale binary silently stands in for the one you meant to test. A missing, empty, nonexistent, non-file or non-executable OWEN_RUST_CORE is a configuration error (exit 2) with one actionable message, and it never falls back to Python.

There is no silent fallback anywhere: a Rust failure is never turned into a Python success. An unexpected Rust child status becomes owen's internal-error exit (5) with the raw status kept in the diagnostic report's child_exit_code, rather than escaping as a meaningless number.

compare is a development/CI seam for the migration, not yet a promised public feature. When the engines disagree — or when either fails — owen exits 5 with reproduction evidence rather than picking a winner: a reference and a candidate that disagree mean owen cannot honestly emit one answer.

On native Windows compare diverges on every run, and that is #262's declared Windows A/B/C behaviour change being visible rather than a defect. Measured on a Windows runner: the Python reference writes CRLF line endings where the Rust core writes LF, so the two engines' bytes differ even for pure-ASCII output — the encoding half (cp1252 vs canonical UTF-8, and the reference's occasional UnicodeEncodeError) is the further difference on non-ASCII output. Canonical/Linux reference parity is claimed and Rust cross-platform byte portability is claimed; native-Windows Python byte parity is explicitly not. Use compare against the Linux reference; on Windows, read a divergence as the recorded difference, not as a finding.

Rollback is explicit: select --engine python (or simply stop passing --engine). Nothing about Stage 1 moves the public default.

Flags (mirror scripts/own-check.sh 1:1)

Flag Default
--format {human,github,msbuild,sarif} human finding surface
--severity {error,warning} error how findings are shown
--engine {python,rust,compare} python which analysis engine runs (#262 Stage 1)
--fail-on-finding off exit with the core's code (1 = findings) instead of always 0
--emit-facts <path> — also write the intermediate OwnIR facts.json
--legacy off flat name-based local-IDisposable detector instead of --flow-locals
--stats off print flow-locals coverage to stderr
--body-throw-edges off opt-in: flag body-level (no-try) dispose-not-called-on-throw

Exit codes: 0 clean, 1 findings (only with --fail-on-finding), 2 a usage or contract error (bad flags, bad facts, a drifted contract, or an unusable OWEN_RUST_CORE under --engine rust|compare), 3 no usable Python found, 4 no supported input found (nothing matching the included frontend), 5 an internal error — a bug in owen or a stage it drives (extractor/core crash). An internal error is never silence, never a clean scan, and never a raw stack trace by default: one short message, plus a deterministic diagnostic report at ~/.owen/diag/last-failure.json (tool/OS/ runtime identity, command line, stage, cause — no source file contents; sharing facts stays the explicit --emit-facts action). --debug (or OWEN_DEBUG=1) prints the full technical cause instead.

Known limitations (alpha)

What is unsupported by design — distinct from bugs (which we want reported):

  • .NET / C# frontend only. .cs, .csproj, .sln. Anything else is exit 4 ("no supported input"), never a silent clean scan.
  • A non-compiling project is analyzed anyway — symbol-tolerantly. Roslyn compile errors are deliberately ignored (the analysis reads symbols, not IL); unresolved external references degrade to advisory notes (OWN050/OWN051), never to invented findings. Consequence: a broken build does not fail owen check, and findings that depend on an unresolved type may be missed — check the project compiles if a finding you expected is absent.
  • Alpha rule scope, not a general leak detector: event-subscription lifetime (the WPF/WinForms +=-without-reachable--= family), timers, local IDisposable flows, DI lifetime mismatches, pooled-buffer misuse.
  • Static analysis only. A finding is a lifetime-contract violation with the evidence the code shows — not a runtime-proven leak. Runtime retention proof is separate tooling.
  • Vocabulary is versioned and fails loud. Facts from a mismatched extractor/core pair are a hard exit 2 by contract — never a guess.
  • Python ≥ 3.11 required at run time; never auto-installed.
  • Analysis of WPF-shaped code does not require Windows; only running WPF apps does.

Anything outside this list that ends in a crash, a wrong exit code, or a wrong finding is a bug — please use the "owen CLI problem" issue template.

Release process

Versioning policy, the release pipeline (.github/workflows/owen-cli-release.yml), the deterministic-pack verification, package-metadata audit, and the release checklist live in docs/notes/owen-cli-release.md — not published to nuget.org yet; that note tracks exactly what's still needed (a license, a maintainer-approved publish) before it can be.

CI proof

ownsharp-cli-smoke in .github/workflows/ci.yml (matrix: ubuntu-latest + windows-latest) proves, on a clean runner: pack → dotnet tool install --global → owen --help/--version/unknown-command → owen check finds a real leak (--fail-on-finding exits 1, OWN001 in the output) and stays silent on clean code (exit 0) → the SARIF surface carries the Owen driver name → unsupported input fails explicitly (exit 4, never a clean scan) → OWEN_PYTHON (and the deprecated OWN_PYTHON fallback, with its deprecation note) both resolve Python correctly → the no-Python path fails fast with an actionable message → the timed install-to-findings window stays under a regression ceiling. Both platforms matter here specifically, not just "more coverage": a dotnet tool shim is a native apphost on Windows and a shell script on Unix — genuinely different process-launch mechanics, so ubuntu-only would not have proven the Windows path.