whycode-cli 0.2.4__tar.gz → 0.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {whycode_cli-0.2.4/src/whycode_cli.egg-info → whycode_cli-0.2.5}/PKG-INFO +1 -1
  2. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/pyproject.toml +1 -1
  3. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/__init__.py +1 -1
  4. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/cli.py +39 -0
  5. whycode_cli-0.2.5/src/whycode/templates/github-workflow.yml +64 -0
  6. {whycode_cli-0.2.4 → whycode_cli-0.2.5/src/whycode_cli.egg-info}/PKG-INFO +1 -1
  7. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/tests/test_cli.py +33 -0
  8. whycode_cli-0.2.4/src/whycode/templates/github-workflow.yml +0 -40
  9. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/LICENSE +0 -0
  10. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/README.md +0 -0
  11. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/setup.cfg +0 -0
  12. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/__main__.py +0 -0
  13. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/git_facts.py +0 -0
  14. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/ignore.py +0 -0
  15. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/mcp_server.py +0 -0
  16. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/risk_card.py +0 -0
  17. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/scorer.py +0 -0
  18. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/signals.py +0 -0
  19. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/suppressions.py +0 -0
  20. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/templates/__init__.py +0 -0
  21. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode/templates/pre-commit +0 -0
  22. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode_cli.egg-info/SOURCES.txt +0 -0
  23. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode_cli.egg-info/dependency_links.txt +0 -0
  24. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode_cli.egg-info/entry_points.txt +0 -0
  25. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode_cli.egg-info/requires.txt +0 -0
  26. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/src/whycode_cli.egg-info/top_level.txt +0 -0
  27. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/tests/test_git_facts.py +0 -0
  28. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/tests/test_ignore.py +0 -0
  29. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/tests/test_scorer.py +0 -0
  30. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/tests/test_signals.py +0 -0
  31. {whycode_cli-0.2.4 → whycode_cli-0.2.5}/tests/test_suppressions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: whycode-cli
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Tells you what to be afraid of before you touch a file.
5
5
  Author: Kevin
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "whycode-cli"
7
- version = "0.2.4"
7
+ version = "0.2.5"
8
8
  description = "Tells you what to be afraid of before you touch a file."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """WhyCode — tells you what to be afraid of before touching a file."""
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.2.5"
@@ -238,6 +238,14 @@ def diff(
238
238
  json_out: bool = typer.Option(
239
239
  False, "--json", help="Emit machine-readable JSON instead of a table."
240
240
  ),
241
+ markdown: bool = typer.Option(
242
+ False,
243
+ "--markdown",
244
+ help=(
245
+ "Emit GitHub-flavoured markdown suitable for posting as a PR comment. "
246
+ "Pipe into a workflow step that calls `gh pr comment`."
247
+ ),
248
+ ),
241
249
  fail_on: str | None = typer.Option(
242
250
  None,
243
251
  "--fail-on",
@@ -305,6 +313,37 @@ def diff(
305
313
 
306
314
  flagged = [c for c in cards if _is_actionable(c)]
307
315
  quiet_n = len(cards) - len(flagged)
316
+ scope_md = "files staged for commit" if staged else f"files changed vs `{actual_base}`"
317
+ if markdown:
318
+ # Stable marker so a follow-up workflow step can find-and-update the
319
+ # same comment on subsequent pushes instead of stacking new ones.
320
+ print("<!-- whycode-comment -->")
321
+ print("## WhyCode risk briefing")
322
+ print()
323
+ print(f"**{len(files)} {scope_md}**")
324
+ print()
325
+ if not flagged:
326
+ print("Nothing flagged. Read the diff anyway.")
327
+ else:
328
+ print("| Score | Band | File | Top signal |")
329
+ print("| ----: | ---- | ---- | ---------- |")
330
+ for c in flagged:
331
+ top_signal = c.signals[0].headline.replace("|", "\\|")
332
+ print(
333
+ f"| {c.score.value} | {c.score.band.value} | "
334
+ f"`{c.path}` | {top_signal} |"
335
+ )
336
+ if quiet_n:
337
+ print()
338
+ print(f"_+ {quiet_n} file(s) changed with no flags._")
339
+ print()
340
+ print(
341
+ "_Run `whycode why <path>` for the full Risk Card on any of the above._"
342
+ )
343
+ if threshold is not None and any(c.score.value >= threshold for c in cards):
344
+ raise typer.Exit(1)
345
+ return
346
+
308
347
  scope = "staged for commit" if staged else f"changed vs {actual_base}"
309
348
  console.print(f"[bold]{len(files)} file(s) {scope}[/bold]")
310
349
  if not flagged:
@@ -0,0 +1,64 @@
1
+ # Risk-rank pull requests with WhyCode.
2
+ #
3
+ # On every PR this workflow:
4
+ # 1. Computes the Risk Card for each changed file (vs the PR base)
5
+ # 2. Prints a risk-ranked table to the job log
6
+ # 3. Posts (or updates) a single PR comment with the same table
7
+ #
8
+ # Advisory by default — humans decide. To turn it into a hard gate that
9
+ # blocks merging, append `--fail-on <band>` to the diff line:
10
+ # handle block at HANDLE WITH CARE (score >= 75)
11
+ # history block at READ HISTORY FIRST (score >= 50)
12
+ # look stricter — block at >= 25
13
+ name: WhyCode
14
+
15
+ on:
16
+ pull_request:
17
+ types: [opened, synchronize, reopened]
18
+
19
+ permissions:
20
+ contents: read
21
+ pull-requests: write # required to post / update the PR comment
22
+
23
+ jobs:
24
+ whycode:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - name: Check out PR
28
+ uses: actions/checkout@v4
29
+ with:
30
+ fetch-depth: 0 # WhyCode needs full history
31
+
32
+ - name: Set up Python
33
+ uses: actions/setup-python@v5
34
+ with:
35
+ python-version: "3.11"
36
+
37
+ - name: Install WhyCode
38
+ run: pip install whycode-cli
39
+
40
+ - name: Risk-rank files in this PR (job log)
41
+ run: whycode diff --base origin/${{ github.base_ref }}
42
+
43
+ - name: Build PR comment body
44
+ run: whycode diff --base origin/${{ github.base_ref }} --markdown > whycode-comment.md
45
+
46
+ - name: Post or update the PR comment
47
+ env:
48
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49
+ PR_NUMBER: ${{ github.event.pull_request.number }}
50
+ REPO: ${{ github.repository }}
51
+ run: |
52
+ set -euo pipefail
53
+ BODY=$(cat whycode-comment.md)
54
+ # Find any existing comment we previously posted (identified by the
55
+ # hidden HTML marker WhyCode emits at the top of the message).
56
+ EXISTING=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
57
+ --jq 'map(select(.body | contains("<!-- whycode-comment -->"))) | .[0].id // empty')
58
+ if [ -n "$EXISTING" ]; then
59
+ gh api -X PATCH "repos/${REPO}/issues/comments/${EXISTING}" \
60
+ -f body="$BODY" > /dev/null
61
+ else
62
+ gh api -X POST "repos/${REPO}/issues/${PR_NUMBER}/comments" \
63
+ -f body="$BODY" > /dev/null
64
+ fi
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: whycode-cli
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Tells you what to be afraid of before you touch a file.
5
5
  Author: Kevin
6
6
  License-Expression: MIT
@@ -508,6 +508,39 @@ def test_scan_no_ignore_brings_them_back(repo, days_ago) -> None: # type: ignor
508
508
  assert "CHANGELOG" in permissive_run.output or "src/app.py" in permissive_run.output
509
509
 
510
510
 
511
+ def test_diff_markdown_output(repo, days_ago) -> None: # type: ignore[no-untyped-def]
512
+ repo.commit("init", {"refund.py": "0"}, when=days_ago(120))
513
+ sha = repo.commit("feat: refund flow", {"refund.py": "1"}, when=days_ago(60))
514
+ repo.revert(sha, when=days_ago(50))
515
+ repo.commit(
516
+ "hotfix: regression",
517
+ {"refund.py": "2"},
518
+ body="incident #INC-42",
519
+ when=days_ago(10),
520
+ )
521
+ result = _invoke(repo.root, "diff", "--base", "HEAD~3", "--markdown")
522
+ assert result.exit_code == 0
523
+ out = result.output
524
+ # Hidden marker so the workflow can find-and-update its prior comment.
525
+ assert "<!-- whycode-comment -->" in out
526
+ # Markdown table syntax.
527
+ assert "| Score | Band |" in out
528
+ assert "| ----: |" in out
529
+ # File path appears as inline code.
530
+ assert "`refund.py`" in out
531
+
532
+
533
+ def test_diff_markdown_quiet_repo(repo, days_ago) -> None: # type: ignore[no-untyped-def]
534
+ repo.commit("init", {"a.py": "1"}, when=days_ago(40))
535
+ repo.commit("docs: tweak", {"a.py": "2"}, when=days_ago(20))
536
+ result = _invoke(repo.root, "diff", "--base", "HEAD~1", "--markdown")
537
+ assert result.exit_code == 0
538
+ out = result.output
539
+ assert "<!-- whycode-comment -->" in out
540
+ # No flagged files → friendly note instead of an empty table.
541
+ assert "Nothing flagged" in out
542
+
543
+
511
544
  def test_scan_respects_user_whycodeignore(repo, days_ago) -> None: # type: ignore[no-untyped-def]
512
545
  (repo.root / ".whycodeignore").write_text("internal/legacy.py\n")
513
546
  sha = repo.commit(
@@ -1,40 +0,0 @@
1
- # Risk-rank pull requests with WhyCode.
2
- #
3
- # On every PR this workflow computes the Risk Card for each changed file
4
- # (vs the PR base) and prints a risk-ranked table to the job log.
5
- # Advisory by default — humans decide.
6
- #
7
- # To turn it into a hard gate that blocks merging, append `--fail-on <band>`
8
- # to the `whycode diff` line below:
9
- # handle block at HANDLE WITH CARE (score >= 75)
10
- # history block at READ HISTORY FIRST (score >= 50)
11
- # look stricter — block at >= 25
12
- name: WhyCode
13
-
14
- on:
15
- pull_request:
16
- types: [opened, synchronize, reopened]
17
-
18
- permissions:
19
- contents: read
20
- pull-requests: read
21
-
22
- jobs:
23
- whycode:
24
- runs-on: ubuntu-latest
25
- steps:
26
- - name: Check out PR
27
- uses: actions/checkout@v4
28
- with:
29
- fetch-depth: 0 # WhyCode needs full history
30
-
31
- - name: Set up Python
32
- uses: actions/setup-python@v5
33
- with:
34
- python-version: "3.11"
35
-
36
- - name: Install WhyCode
37
- run: pip install whycode-cli
38
-
39
- - name: Risk-rank files in this PR
40
- run: whycode diff --base origin/${{ github.base_ref }}
File without changes
File without changes
File without changes