Skip to content

Commit ee6583a

Browse files
committed
Add OwnSharp.Cli: the single ownsharp check command (alpha gate A, #202)
Implements the packaging decision recorded in issue #202: one dotnet global tool (`OwnSharp.Cli`, command `ownsharp`) that wraps the existing extractor -> core pipeline into one install, exactly mirroring what scripts/own-check.sh already does by hand. - The Roslyn extractor (OwnSharp.Extractor) is pulled in via ProjectReference, unmodified; `check` invokes its bundled dll as a child process (`dotnet exec`), the same subprocess shape own-check.sh already uses. - The Python core (ownlang/) is vendored as loose *.py content, unmodified, and unpacked to ~/.ownsharp/core/<version>/ on first run -- never into the analyzed repo. It runs on the machine's own Python (OWN_PYTHON env var, else `py -3`/`python3`, >=3.11), with a fast, actionable, one-line failure (winget/apt/brew per OS) and no auto-download if none is found. - CLI flags mirror own-check.sh 1:1 (--format, --severity, --fail-on-finding, --emit-facts, --legacy, --stats, --body-throw-edges); the exit-code contract (0/1/>=2, plus 3 for "no Python") is unchanged from the scripts. No behaviour changes to the extractor or core -- packaging only, per the issue's guardrail. own-check.sh/.ps1 and action.yml are untouched. CI: a new ownsharp-cli-smoke job (matrix: ubuntu-latest + windows-latest) proves pack -> dotnet tool install --global -> ownsharp check finds a real leak on a clean runner, gated on a regression-ceiling timer, plus a separate assertion that the no-Python path fails fast with the actionable message. Docs: frontend/roslyn/OwnSharp.Cli/README.md (the tool itself), updates to alpha-readiness.md gate A, P-013's Scope, and the root README's local-quickstart paragraph -- all noting honestly that the package isn't published to nuget.org yet (build-and-install from source until then).
1 parent 9fc8c27 commit ee6583a

13 files changed

Lines changed: 779 additions & 14 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,3 +1543,89 @@ jobs:
15431543
# use, and an injected-source region-escape. A drop below the floor is a regression.
15441544
run: python scripts/benchmark.py --min-recall 25
15451545

1546+
# Alpha gate A (issue #202): the single delightful command, proven end-to-end
1547+
# on a clean runner — install -> check -> findings. Packaging only, no
1548+
# analysis-behaviour change: OwnSharp.Cli bundles the *unmodified* extractor
1549+
# (ProjectReference; invoked as a child process, same shape own-check.sh
1550+
# already uses) and vendors the *unmodified* ownlang/ core, run by the
1551+
# machine's own Python. Both ubuntu AND windows matter here specifically
1552+
# (not just "more coverage") — a dotnet-tool shim is a native apphost on
1553+
# Windows and a shell script on Unix, so they exercise genuinely different
1554+
# process-launch mechanics; ubuntu-only would not prove the Windows path.
1555+
ownsharp-cli-smoke:
1556+
name: ownsharp CLI (gate A) — clean install -> check -> findings
1557+
strategy:
1558+
fail-fast: false
1559+
matrix:
1560+
os: [ubuntu-latest, windows-latest]
1561+
runs-on: ${{ matrix.os }}
1562+
defaults:
1563+
run:
1564+
# bash (git-bash on Windows runners) so one script works on both legs;
1565+
# the thing under test is the ownsharp/dotnet/python binaries, not the
1566+
# shell driving them.
1567+
shell: bash
1568+
steps:
1569+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
1570+
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
1571+
with:
1572+
dotnet-version: "8.0.x"
1573+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
1574+
with:
1575+
python-version: "3.11"
1576+
- name: Put the dotnet global-tools shim dir on PATH
1577+
run: echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
1578+
# CI-only stand-in for "download the package from nuget.org" (not
1579+
# published there yet, see P-013's Non-goals) -- pack from the source
1580+
# this job already checked out. Deliberately OUTSIDE the timed window
1581+
# below: it is not part of the "install -> check" claim being proven.
1582+
- name: Pack OwnSharp.Cli (pulls in the extractor via ProjectReference)
1583+
run: dotnet pack frontend/roslyn/OwnSharp.Cli/OwnSharp.Cli.csproj -c Release -o "$RUNNER_TEMP/ownsharp-nupkg"
1584+
- name: A minimal leak, in a scratch dir OUTSIDE the repo
1585+
# Proves the tool needs nothing but itself + Python -- not the Own.NET
1586+
# checkout (a real end user obviously won't have this repo on disk).
1587+
run: |
1588+
mkdir -p "$RUNNER_TEMP/ownsharp-sample"
1589+
cat > "$RUNNER_TEMP/ownsharp-sample/Leak.cs" <<'EOF'
1590+
using System.IO;
1591+
public class Leaky
1592+
{
1593+
public void Run()
1594+
{
1595+
var s = new MemoryStream();
1596+
s.WriteByte(1);
1597+
}
1598+
}
1599+
EOF
1600+
- name: Start the clean-machine timer (install -> check -> findings)
1601+
run: echo "SMOKE_START=$(date +%s)" >> "$GITHUB_ENV"
1602+
- name: dotnet tool install --global (the one install the user runs)
1603+
run: dotnet tool install --global OwnSharp.Cli --version 0.1.0 --add-source "$RUNNER_TEMP/ownsharp-nupkg"
1604+
- name: ownsharp check finds the leak
1605+
run: |
1606+
set +e
1607+
out=$(ownsharp check "$RUNNER_TEMP/ownsharp-sample" --fail-on-finding 2>&1)
1608+
rc=$?
1609+
set -e
1610+
echo "$out"
1611+
[ "$rc" -eq 1 ] || { echo "FAIL: expected exit 1 (findings), got $rc"; exit 1; }
1612+
echo "$out" | grep -q "OWN001" || { echo "FAIL: expected OWN001 in the output"; exit 1; }
1613+
- name: Stop the timer -- report it, and gate on a generous regression ceiling
1614+
# A hard ceiling, not a precision claim: CI timing varies with runner
1615+
# load, so this is a regression guard (catch "it now takes 20 minutes"),
1616+
# not a rubber stamp of the "~3 minutes" marketing number itself.
1617+
run: |
1618+
elapsed=$(( $(date +%s) - SMOKE_START ))
1619+
echo "install -> check -> findings: ${elapsed}s"
1620+
[ "$elapsed" -lt 240 ] || { echo "FAIL: took ${elapsed}s (ceiling 240s) — see alpha-readiness.md gate A"; exit 1; }
1621+
- name: No Python found -> a fast, actionable failure (never an auto-download)
1622+
run: |
1623+
set +e
1624+
out=$(OWN_PYTHON=/definitely/does/not/exist/python3 ownsharp check "$RUNNER_TEMP/ownsharp-sample" 2>&1)
1625+
rc=$?
1626+
set -e
1627+
echo "$out"
1628+
[ "$rc" -eq 3 ] || { echo "FAIL: expected exit 3 (Python not found), got $rc"; exit 1; }
1629+
echo "$out" | grep -qi "OWN_PYTHON" || { echo "FAIL: expected the OWN_PYTHON-specific message"; exit 1; }
1630+
echo "$out" | grep -Eiq "winget|apt|brew|python.org" || { echo "FAIL: expected an actionable install hint"; exit 1; }
1631+

‎README.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ scripts/own-check.sh --format human -- /path/to/your/csharp/repo
2727
```
2828

2929
Needs Python 3.11+ and the .NET SDK on `PATH` — nothing to build, nothing to
30-
`pip install` (there's no packaged CLI yet; see
31-
[`docs/notes/alpha-readiness.md`](docs/notes/alpha-readiness.md) gate **A**).
30+
`pip install`. A packaged single-command CLI (`ownsharp check`) also exists —
31+
build-and-install-locally today, not yet published to nuget.org; see
32+
[`frontend/roslyn/OwnSharp.Cli/README.md`](frontend/roslyn/OwnSharp.Cli/README.md)
33+
and [`docs/notes/alpha-readiness.md`](docs/notes/alpha-readiness.md) gate **A**.
3234

3335
## One it actually found
3436

@@ -918,6 +920,8 @@ ownlang/
918920
test_spec.py # conformance: every spec/ rule fires on an example
919921
test_ownir.py # the OwnIR bridge: C# facts -> core -> OWN001 at the C# site
920922
frontend/roslyn/ # the C# extractor (Roslyn, CI-only) + .cs samples (P-001)
923+
OwnSharp.Extractor/ # ownsharp-extract (dotnet tool): facts only
924+
OwnSharp.Cli/ # ownsharp (dotnet tool, gate A): extractor + vendored core, one install
921925
rust/ # the Rust core migration (P-022): own-ir + own-syntax so far,
922926
# oracle-gated against this Python core — see rust/README.md
923927
pyproject.toml # gate: ruff + mypy --strict (see below)

‎README.ru.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ scripts/own-check.sh --format human -- /путь/к/вашему/csharp/репо
2828
```
2929

3030
Нужны Python 3.11+ и .NET SDK в `PATH` — ничего собирать, ничего ставить через
31-
`pip install` (упакованного CLI пока нет; см.
32-
[`docs/notes/alpha-readiness.md`](docs/notes/alpha-readiness.md), gate **A**).
31+
`pip install`. Есть и упакованный однокомандный CLI (`ownsharp check`) — сегодня
32+
собирается и ставится локально, в nuget.org ещё не опубликован; см.
33+
[`frontend/roslyn/OwnSharp.Cli/README.md`](frontend/roslyn/OwnSharp.Cli/README.md)
34+
и [`docs/notes/alpha-readiness.md`](docs/notes/alpha-readiness.md), gate **A**.
3335

3436
## Один реальный баг, который он нашёл
3537

‎docs/notes/alpha-readiness.md‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ The bar for "showable": a person can reproduce the wow in ~3 minutes
3636

3737
| | Item | Status (2026-06-27) | Gap to close |
3838
|---|------|--------------------|--------------|
39-
| **A** | `dotnet tool` one-command CLI | ◑ **partial** — the *extractor* is `PackAsTool` (`ownsharp-extract`, P-013); the core is Python. The delightful `ownsharp check MyApp.sln` single tool isn't packaged. | Wrap extractor+core into one `dotnet tool` (or a self-contained CLI) that takes a `.sln`/dir and prints findings. |
39+
| **A** | `dotnet tool` one-command CLI | ◑ **mostly built** (issue #202) — `OwnSharp.Cli` wraps extractor+core into one `dotnet tool install` → `ownsharp check <path\|.sln>`, proven install→check→findings on a clean ubuntu/windows runner in CI (`ownsharp-cli-smoke`). See [`frontend/roslyn/OwnSharp.Cli/README.md`](../../frontend/roslyn/OwnSharp.Cli/README.md). | Not published to nuget.org yet — today it's build-and-install-from-source only. Publishing (+ a real version scheme beyond `0.1.0`) is the remaining step. |
4040
| **B** | GitHub Action | ✅ **built** — `action.yml`: `path`/`severity`/`format` (`github` / `msbuild` / `human` / `sarif`), purple shield branding. Matches the "stupidly simple YAML" bar. | Publish to Marketplace; pin the 6-line usage in the README. |
4141
| **C** | SARIF / PR annotations | ✅ **built** — SARIF 2.1.0 + GitHub annotations + reachability/evidence (P-015). | — |
4242
| **D** | 5 core diagnostics | ✅ **built, well past** — OWN001/002/003, OWN014, DI001–005, POOL001–005, WPF001–005 (catalog). The comment's `SUB001/SUB002/TMR001/DISP001/DI001` all exist *semantically*; the `SUB/TMR/DISP` catalog rename is the deferred consolidation item, not new work. | (naming only) land the catalog rename with the OwnIR-v1/profile-label work. |
@@ -54,17 +54,19 @@ suppression → "why not Sonar/CodeQL"), every step of that path now exists.
5454

5555
## Honest verdict
5656

57-
**The engine is past alpha on *capability* (D/E strong, B/C built). F/G and the
58-
front door have since closed too — the remaining packaging gap is narrower:**
57+
**The engine is past alpha on *capability* (D/E strong, B/C built). F/G, the
58+
front door, and now A have all closed too — what's left is publishing, not
59+
building:**
5960

60-
1. a single `ownsharp check MyApp.sln` tool (**A**) — still open;
61+
1. ~~a single `ownsharp check MyApp.sln` tool (**A**)~~ — **built**, not yet published to nuget.org;
6162
2. ~~a wedge landing README + copy-paste quickstart (front door)~~ — **done**;
6263
3. ~~three packaged case studies from finds we already have (**F**)~~ — **done**;
6364
4. ~~one consolidated suppression / false-positive page (**G**)~~ — **done**.
6465

6566
None of those is research; all are the difference between "interesting PoC" and
66-
"people install it." **A** is now the one item standing between here and the
67-
day 1–30 milestone.
67+
"people install it." Publishing `OwnSharp.Cli` to nuget.org is now the one item
68+
standing between here and the day 1–30 milestone being *literally* copy-paste
69+
for a stranger.
6870

6971
## The 20% rule (other stacks)
7072

@@ -85,9 +87,10 @@ until the .NET alpha above is delicious. Do not let the spike exceed 20%.
8587

8688
## 90-day shape (sequencing, not a schedule)
8789

88-
- **Days 1–30 — make the .NET alpha tasty:** close A (B/C/D/E/F/G and the README
89-
front door already done). Suppression UX + bad/ok corpus polish continue as
90-
bug-driven follow-ups, not a blocking gate.
90+
- **Days 1–30 — make the .NET alpha tasty:** publish `OwnSharp.Cli` to
91+
nuget.org (A/B/C/D/E/F/G and the README front door are all otherwise done).
92+
Suppression UX + bad/ok corpus polish continue as bug-driven follow-ups, not
93+
a blocking gate.
9194
- **Days 31–60 — real-world proof:** run over 20–50 OSS .NET/WPF/Avalonia/WinForms
9295
repos; table of findings / confirmed / FP / unsupported; 2 case studies; compare
9396
with CodeQL / NetAnalyzers / Infer# where possible (the oracle, `docs/notes/oracle.md`).

‎docs/proposals/P-013-distribution-surface.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ and defer the native analyzer until (if ever) the core itself moves to .NET.
6565
(`dotnet tool install --global OwnSharp.Extractor` → `ownsharp-extract`).
6666
Honest caveat: the tool is only the C# *extractor*; the verdict is still the
6767
Python core. The script/Action are the complete product.
68+
- **`OwnSharp.Cli`** (alpha gate A, issue #202) — the single-install answer to
69+
that caveat: `dotnet tool install --global OwnSharp.Cli` → `ownsharp check
70+
<path|.sln>` wraps both stages in one tool. It bundles the *unmodified*
71+
extractor (`ProjectReference`, invoked as a child process) and vendors the
72+
*unmodified* `ownlang/` core (run on the machine's own Python, resolved via
73+
`OWN_PYTHON`/`py -3`/`python3`, `>=3.11`, fail-fast otherwise — never an
74+
auto-download). See [`frontend/roslyn/OwnSharp.Cli/README.md`](../../frontend/roslyn/OwnSharp.Cli/README.md)
75+
for the packaging shape and the rejected alternatives on record in the issue.
76+
Not yet published to nuget.org (Non-goals below, unchanged) —
77+
build-and-install from source until then; `own-check.sh`/`.ps1`/`action.yml`
78+
are untouched and remain the supported surfaces alongside it.
6879

6980
## Not the same as P-011
7081

0 commit comments

Comments
 (0)