qodo-cli 0.4.0__tar.gz → 0.9.0__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 (93) hide show
  1. qodo_cli-0.9.0/.pr_agent.toml +26 -0
  2. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/CHANGELOG.md +196 -0
  3. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/PKG-INFO +1 -1
  4. qodo_cli-0.9.0/best_practices.md +59 -0
  5. qodo_cli-0.9.0/docs/manual-verification.md +115 -0
  6. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/docs/qodo-skills-sources.md +53 -8
  7. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/pyproject.toml +1 -1
  8. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/__init__.py +2 -0
  9. qodo_cli-0.9.0/qodo/cli/_commands/config.py +395 -0
  10. qodo_cli-0.9.0/qodo/cli/_commands/doctor.py +262 -0
  11. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_commands/learn.py +4 -2
  12. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_commands/overview.py +14 -6
  13. qodo_cli-0.9.0/qodo/cli/_commands/review.py +308 -0
  14. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_commands/rules.py +32 -7
  15. qodo_cli-0.9.0/qodo/cli/_providers.py +947 -0
  16. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_qodo_api.py +92 -0
  17. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/explain/catalog.py +88 -14
  18. qodo_cli-0.9.0/tests/fixtures/rules_search_response.json +28 -0
  19. qodo_cli-0.9.0/tests/test_cli_introspection.py +197 -0
  20. qodo_cli-0.9.0/tests/test_config.py +244 -0
  21. qodo_cli-0.9.0/tests/test_contracts.py +157 -0
  22. qodo_cli-0.9.0/tests/test_review.py +1012 -0
  23. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/tests/test_rules.py +136 -0
  24. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/uv.lock +1 -1
  25. qodo_cli-0.4.0/qodo/cli/_commands/doctor.py +0 -124
  26. qodo_cli-0.4.0/qodo/cli/_commands/review.py +0 -139
  27. qodo_cli-0.4.0/qodo/cli/_providers.py +0 -333
  28. qodo_cli-0.4.0/tests/test_cli_introspection.py +0 -106
  29. qodo_cli-0.4.0/tests/test_review.py +0 -372
  30. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/agent-config/SKILL.md +0 -0
  31. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  32. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  33. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/SKILL.md +0 -0
  34. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
  35. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/prompts/review.md +0 -0
  36. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/prompts/write.md +0 -0
  37. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
  38. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  39. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  40. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/cicd/SKILL.md +0 -0
  41. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  42. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  43. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  44. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  45. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  46. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/communicate/SKILL.md +0 -0
  47. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  48. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  49. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  50. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  51. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  52. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  53. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  54. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  55. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  56. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  57. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/run-tests/SKILL.md +0 -0
  58. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  59. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  60. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  61. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  62. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  63. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/think/SKILL.md +0 -0
  64. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/think/scripts/think.sh +0 -0
  65. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/version-bump/SKILL.md +0 -0
  66. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  67. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.claude/skills.local.yaml.example +0 -0
  68. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.devague/current +0 -0
  69. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.devague/frames/qodo-cli-now-does-qodo-s-two-core-jobs-natively-fr.json +0 -0
  70. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.flake8 +0 -0
  71. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.github/workflows/publish.yml +0 -0
  72. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.github/workflows/tests.yml +0 -0
  73. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.gitignore +0 -0
  74. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/.markdownlint-cli2.yaml +0 -0
  75. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/AGENTS.colleague.md +0 -0
  76. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/CLAUDE.md +0 -0
  77. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/LICENSE +0 -0
  78. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/README.md +0 -0
  79. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/culture.yaml +0 -0
  80. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/docs/skill-sources.md +0 -0
  81. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/docs/specs/2026-06-16-qodo-cli-now-does-qodo-s-two-core-jobs-natively-fr.md +0 -0
  82. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/__init__.py +0 -0
  83. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/__main__.py +0 -0
  84. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_commands/__init__.py +0 -0
  85. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_commands/cli.py +0 -0
  86. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_commands/explain.py +0 -0
  87. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_commands/whoami.py +0 -0
  88. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_errors.py +0 -0
  89. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/cli/_output.py +0 -0
  90. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/qodo/explain/__init__.py +0 -0
  91. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/sonar-project.properties +0 -0
  92. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/tests/__init__.py +0 -0
  93. {qodo_cli-0.4.0 → qodo_cli-0.9.0}/tests/test_cli.py +0 -0
@@ -0,0 +1,26 @@
1
+ # Qodo Merge / PR-Agent configuration for qodo-cli.
2
+ # Docs: https://docs.qodo.ai/qodo-documentation/qodo-merge/configuration/configuration-file
3
+ #
4
+ # Kept minimal on purpose (per Qodo's guidance: only override what you need).
5
+ # The repo's coding conventions live in best_practices.md at the root, which the
6
+ # reviewer auto-references; this file just reinforces a few intentional patterns
7
+ # that an earlier review flagged as false positives.
8
+
9
+ [pr_reviewer]
10
+ extra_instructions = """
11
+ qodo-cli is an unofficial, community CLI to manage Qodo, built as a
12
+ zero-runtime-dependency, stdlib-only Python package. Intentional patterns (do
13
+ not flag these as issues):
14
+ - Command handlers return a bare 0/1/None for the exit code. The EXIT_* constants
15
+ in qodo/cli/_errors.py are for CliError codes (the failure path), not for
16
+ handler return values. `return 0` in a handler is intentional and consistent
17
+ across every command.
18
+ - Nested argparse subparsers inherit the structured-error parser class via
19
+ `add_subparsers(parser_class=type(p))` (see qodo/cli/_commands/cli.py). Passing
20
+ `type(p)` is the established idiom and is equivalent to naming
21
+ `_CliArgumentParser` directly — it is not a missing parser_class.
22
+ - `qodo review resolve --reply` drives the user's own `gh` with their own auth;
23
+ the reply text belongs to the caller, so the tool must not auto-append an agent
24
+ signature.
25
+ See best_practices.md at the repo root for the full conventions.
26
+ """
@@ -5,6 +5,202 @@ All notable changes to this project will be documented in this file.
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/). This project
6
6
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.9.0] - 2026-06-17
9
+
10
+ ### Added
11
+
12
+ - **GitLab provider** for `qodo review` (via `glab`): find the open MR, list the
13
+ Qodo bot's notes across MR discussions (with the same parsed triage fields as
14
+ GitHub), reply, and resolve. GitLab's model is MR *discussions* (resolution is
15
+ at the discussion level — there is no `+1` marker, so resolving the discussion
16
+ *is* the acknowledgement). Implemented but **not live-tested** against a real
17
+ GitLab (we have none) — covered by mocked tests mirroring the GitHub ones, with
18
+ the `glab` REST shapes pinned in the citation ledger. (#10)
19
+
20
+ ### Changed
21
+
22
+ - Generalized the provider gate: `require_provider` (supersedes the GitHub-only
23
+ `require_github` on the `review` surface) now allows **GitHub + GitLab**;
24
+ Azure/Bitbucket/Gerrit still error with a clear "not wired yet". `review`
25
+ dispatches find/fetch/resolve through a provider-aware seam
26
+ (`find_pr` / `fetch_comments` / `resolve` / `prefetch_threads`). (#10)
27
+ - Internal refactor to clear SonarCloud maintainability findings on the stack —
28
+ no behavior change: split the `config` render/validate/init handlers and
29
+ `review._select_targets` into focused helpers (cognitive complexity ≤ 15),
30
+ de-nested the validate mark ternary, extracted `_parse_thread_node`, and named
31
+ the shared resolve-action labels (`_ACT_RESOLVE_THREAD` /
32
+ `_ACT_LOOKUP_DISCUSSION`) to drop duplicated literals. (PR #12 review)
33
+
34
+ ### Fixed
35
+
36
+ - **Self-hosted GitLab on a custom domain is now detected.** `resolve_provider`
37
+ gained a `glab_knows_host()` upgrade path mirroring the GHE `gh_knows_host()`
38
+ one: an `origin` whose host isn't `github.com`/`gitlab.com` is no longer
39
+ hard-failed as `unknown` when `glab auth status --hostname <host>` recognises
40
+ it — it resolves to `gitlab`. `gh` is consulted first (a host both CLIs know
41
+ resolves to `github`). Mocked-only, like the GHE path. (#10, PR #16 review)
42
+ - **Live smokes are now explicitly opt-in and skip (don't fail) when their
43
+ prerequisites are missing.** Both `test_contracts.py` smokes require a
44
+ deliberate `QODO_LIVE_SMOKE=1` switch, so a shell that merely exports
45
+ `QODO_API_KEY` for real `qodo rules` use no longer fires a network call during
46
+ a normal `pytest` run. The GHE smoke additionally gates on `gh` being present
47
+ and authenticated to the remote's host (`gh_knows_host`), so a `gh`-less or
48
+ unauthenticated box *skips* rather than failing on `resolve_provider → unknown`.
49
+ (#8, PR #15 review)
50
+ - **`qodo config init` is now symlink-safe.** It treats any existing path
51
+ (regular file, directory, FIFO, or a possibly-broken symlink) as occupied, and
52
+ under `--force` it refuses — with a structured `CliError` — to write *through* a
53
+ symlink (which could escape the repo root) or over a non-regular path (which
54
+ would crash). Targets are pre-scanned so a refusal aborts before any write,
55
+ never leaving a half-scaffold. (#7, PR #14 review)
56
+ - **`qodo config show`/`validate` degrade gracefully on an unreadable
57
+ `best_practices.md`.** `_inspect()` now guards the `best_practices.md` read
58
+ (`OSError`/`UnicodeDecodeError`) the same way the `.pr_agent.toml` read was
59
+ already guarded, so a permission/encoding problem reports a controlled
60
+ `readable: false` status instead of crashing the read-only verb with a generic
61
+ exit 1. (#7, PR #14 review)
62
+ - **Scope auto-detection is now truly non-raising.** `_origin_url()` wraps its
63
+ `subprocess.run(git …)` in `try/except OSError`, and `detect_scopes()` guards
64
+ `Path.cwd()` — so a git that vanishes mid-run or a deleted working directory
65
+ yields *no scope* (per the documented contract) instead of turning an optional
66
+ enhancement into a `qodo rules get` failure. (#9, PR #13 review)
67
+ - **`repo_slug()` no longer leaves `.git` in the slug for a trailing-slash
68
+ remote.** A URL like `https://host/org/repo.git/` now correctly yields
69
+ `org/repo` (the path is stripped of surrounding slashes *before* the `.git`
70
+ suffix), not `org/repo.git`. (#9, PR #13 review)
71
+ - **`review resolve`'s reaction-only fallback no longer flips the exit code.**
72
+ When no GitHub review thread maps to a comment, the `+1` reaction stands as the
73
+ acknowledgement marker; that documented fallback is now reported `ok=True` with
74
+ `fallback: true` (instead of `ok=False`), so `review resolve --all` exits 0 when
75
+ every actionable step succeeded. A genuine thread-resolve error still reports
76
+ `ok=False`. (GitLab is unchanged — it has no `+1` marker, so a missing discussion
77
+ is a real failure there.) (#3–#6, PR #12 review)
78
+ - **`review resolve --severity` rejects an invalid value instead of silently
79
+ resolving nothing.** A typo like `--severity HGIH` now fails at parse time with a
80
+ structured `error:`/`hint:` (exit 1) rather than matching no comments and exiting
81
+ 0. Stays case-insensitive (`high` → `HIGH`). (#3–#6, PR #12 review)
82
+
83
+ ## [0.8.1] - 2026-06-17
84
+
85
+ ### Added
86
+
87
+ - `tests/test_contracts.py` + `tests/fixtures/rules_search_response.json` — an
88
+ **offline contract test** that pins the Qodo `/rules/search` response shape
89
+ (relevance order, the `{id, name, content, severity}` fields, severities within
90
+ the known set, unknown extra fields passing through), so the parser is verified
91
+ without a Qodo subscription in CI. (#8)
92
+ - **Opt-in live smokes** (skipped by default): `test_live_rules_search_smoke`
93
+ (runs when `QODO_API_KEY` is set) and `test_live_ghe_resolves_to_github` (runs
94
+ when `QODO_CLI_GHE_REMOTE` points at a real GitHub Enterprise origin). (#8)
95
+ - `docs/manual-verification.md` — a manual checklist for the paths that need a
96
+ real system to exercise (live `qodo rules`, GitHub Enterprise resolution, the
97
+ non-GitHub provider gate), cross-referenced from the citation ledger. (#8)
98
+
99
+ ### Changed
100
+
101
+ ### Fixed
102
+
103
+ ## [0.8.0] - 2026-06-17
104
+
105
+ ### Added
106
+
107
+ - `qodo config` — a new noun group to manage the **repo-level** Qodo reviewer
108
+ config (`.pr_agent.toml` + `best_practices.md`), distinct from the *client*
109
+ `~/.qodo/config.json` that `qodo rules` reads: (#7)
110
+ - `config show` — report presence, the parsed `.pr_agent.toml` sections, and
111
+ `best_practices.md` status (read-only).
112
+ - `config validate` — validate the config (valid TOML, a config present) and
113
+ exit 1 when invalid; warns (without failing) on a missing `[pr_reviewer]`
114
+ section or an empty `best_practices.md`. Emits the rubric-shaped
115
+ `{valid, checks: [...]}` in `--json`.
116
+ - `config init [--force]` — scaffold a minimal `.pr_agent.toml` +
117
+ `best_practices.md` when absent; never overwrites without `--force`.
118
+ - `config overview` — describe the noun (rubric-required).
119
+
120
+ ### Changed
121
+
122
+ ### Fixed
123
+
124
+ ## [0.7.0] - 2026-06-17
125
+
126
+ ### Added
127
+
128
+ - `qodo rules get` now **auto-detects the rules scope** when `--scope` is
129
+ omitted, mirroring `qodo-get-rules`: the `org/repo` slug from the git `origin`
130
+ (SSH `git@host:org/repo(.git)`, HTTPS, and `ssh://` forms; multi-level
131
+ namespaces such as GitLab subgroups preserved) plus the module name from a
132
+ `modules/<name>/` path. Detection is non-raising — no git / no origin yields no
133
+ scope, and `scopes` is omitted entirely (never sent empty). The detected scope
134
+ is surfaced in `--json` (`scopes`) and the text header. (#9)
135
+ - `qodo rules get --no-scope` forces scope omission (skips auto-detection);
136
+ `--scope` continues to override detection. `--scope` and `--no-scope` are
137
+ mutually exclusive. (#9)
138
+
139
+ ### Changed
140
+
141
+ ### Fixed
142
+
143
+ ## [0.6.0] - 2026-06-17
144
+
145
+ ### Added
146
+
147
+ - `qodo review list` now parses each Qodo comment body into structured triage
148
+ fields surfaced in `--json` and the text table: `severity` (from the badge —
149
+ `Action required → HIGH`, `Review recommended → MEDIUM`, other → `LOW`, none →
150
+ `null`), `type` and `categories` (the `<code>` chips), `description` (the
151
+ `<pre>` block, HTML-entity-decoded), and `agent_prompt` (the remediation
152
+ block). Parsing is best-effort — an unrecognised body degrades to title-only.
153
+ (#3)
154
+ - `qodo review list --kind {inline,summary,all}` filters by comment kind so the
155
+ non-actionable summary rollups can be excluded. (#3)
156
+ - `qodo review resolve` now resolves the GitHub review **thread** via the GraphQL
157
+ `resolveReviewThread` mutation by default (mapping the REST comment id to its
158
+ thread node id), with `--no-resolve-thread` to skip and a graceful fallback to
159
+ the `+1` reaction when no thread maps to the comment. (#4)
160
+ - `qodo review resolve --all` / `--severity <S>` and multiple positional ids
161
+ resolve several inline comments in one call. (#5)
162
+ - `qodo review resolve --reply "..." --sign` appends the `culture.yaml` nick
163
+ signature (`- <nick> (Claude)`) to the reply, at most once (duplicate-guarded);
164
+ default stays unsigned. (#6)
165
+
166
+ ### Changed
167
+
168
+ - `qodo review resolve` is now **best-effort and per-action**: it reports each of
169
+ reply / acknowledge / resolve-thread per comment, so a posted reply whose
170
+ acknowledgement failed reads as partial success (exit 1) rather than a blanket
171
+ failure. The `resolve_comment()` return type changed from `list[str]` to
172
+ `list[{action, ok, detail}]`, and the `resolve --json` payload now carries a
173
+ `resolved` list with per-action results. (#5)
174
+
175
+ ### Fixed
176
+
177
+ ## [0.5.0] - 2026-06-17
178
+
179
+ ### Added
180
+
181
+ - `.pr_agent.toml` + `best_practices.md` at the repo root — the Qodo Merge
182
+ reviewer config (cite, don't fork). They codify this repo's intentional
183
+ patterns (bare `return 0` handlers, `parser_class=type(p)` subparser nesting,
184
+ the mechanics-only `review resolve --reply`) so Qodo reviews accurately and
185
+ stops raising them as violations.
186
+ - `qodo doctor` now also checks **Qodo setup**, against the current git repo:
187
+ `.pr_agent.toml` present, `best_practices.md` present, and whether a usable
188
+ Qodo API key is resolvable for `qodo rules` — `QODO_API_KEY`, else a non-empty
189
+ `API_KEY` in `~/.qodo/config.json` (a present-but-keyless or malformed file
190
+ fails the check with guidance, and never throws). These are advisory and each
191
+ carries a `remediation` that guides an agent through setup. Runs in any repo
192
+ (not just a source checkout). (Addresses #7.)
193
+
194
+ ### Changed
195
+
196
+ - `qodo doctor` `healthy` now depends on **error**-severity checks only;
197
+ `warning`/`info` checks surface guidance without flipping `healthy` or the
198
+ exit code. Fixes the `claude → CLAUDE.md` drift in the `doctor` explain entry
199
+ (this repo's backend is `colleague` → `AGENTS.colleague.md`).
200
+ - `learn` now describes `doctor` as "agent-identity invariants + Qodo setup" in
201
+ both its text and JSON payload, matching `overview` and the explain catalog
202
+ (self-description stays consistent across the introspection surfaces).
203
+
8
204
  ## [0.4.0] - 2026-06-17
9
205
 
10
206
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qodo-cli
3
- Version: 0.4.0
3
+ Version: 0.9.0
4
4
  Summary: Community CLI and agent to manage Qodo — the AI code reviewer and Qodo's other agents (requires a Qodo subscription). Unofficial: not affiliated with, authorized, or endorsed by Qodo; the Qodo name and trademark belong to Qodo Ltd.
5
5
  Project-URL: Homepage, https://github.com/agentculture/qodo-cli
6
6
  Project-URL: Issues, https://github.com/agentculture/qodo-cli/issues
@@ -0,0 +1,59 @@
1
+ # Best practices for qodo-cli
2
+
3
+ Repository-specific coding standards for the Qodo reviewer — and for any agent
4
+ working in this repo. `qodo-cli` is an unofficial, community CLI to manage Qodo,
5
+ built as a **zero-runtime-dependency, stdlib-only** Python package.
6
+
7
+ These conventions are pinned by the test suite and the agent-first rubric
8
+ (`teken cli doctor . --strict`); please review against them rather than against
9
+ generic defaults.
10
+
11
+ ## Dependencies
12
+
13
+ - Runtime dependencies must stay empty (`dependencies = []` in `pyproject.toml`).
14
+ Use only the Python standard library at runtime, and flag any new third-party
15
+ runtime import. `teken` and the lint/test tools are dev-only.
16
+
17
+ ## Exit codes
18
+
19
+ - Command handlers return a bare `0` / `1` / `None` for their exit code (`0` is
20
+ success). The `EXIT_*` constants in `qodo/cli/_errors.py` are for `CliError`
21
+ codes on the failure path, **not** for handler return values. A bare
22
+ `return 0` in a handler is intentional and consistent across every command —
23
+ do not flag it as a magic number.
24
+
25
+ ## Errors and output
26
+
27
+ - Every failure raises `CliError(code, message, remediation)`; no Python
28
+ traceback may leak to stderr. Text-mode errors render `error: <msg>` then
29
+ `hint: <remediation>` (the `hint:` prefix is required).
30
+ - Results go to stdout; errors and diagnostics go to stderr. Never mix the two,
31
+ in text or `--json` mode. Every command supports `--json`.
32
+
33
+ ## argparse
34
+
35
+ - Build subparsers with the structured-error parser class. Nested subparsers
36
+ inherit it via `add_subparsers(parser_class=type(p))` (see
37
+ `qodo/cli/_commands/cli.py`); passing `type(p)` is the established idiom and is
38
+ equivalent to naming `_CliArgumentParser` directly — it is not a missing
39
+ `parser_class`.
40
+ - Add the standard `--json` flag with `add_json_flag()` from `qodo.cli._output`
41
+ rather than re-declaring the literal.
42
+
43
+ ## The CLI is mechanics-only
44
+
45
+ - `qodo review resolve --reply` drives the user's own `gh` with their own auth;
46
+ the reply text belongs to the caller. The tool must not auto-append an agent
47
+ signature — that would mis-attribute human-authored replies. Signing is the
48
+ caller's responsibility (an opt-in flag may be added later).
49
+
50
+ ## Cite, don't import
51
+
52
+ - Behavior derived from `qodo-ai/qodo-skills` and the vendored `.claude/skills/`
53
+ kit is cited as the source of truth, not forked or vendored into runtime code.
54
+ Keep `docs/qodo-skills-sources.md` in sync when the upstream contract changes.
55
+
56
+ ## Keep the self-description in sync
57
+
58
+ - When adding a command, update `learn`, `overview`, and the explain catalog so
59
+ the rubric and the self-describing text stay consistent.
@@ -0,0 +1,115 @@
1
+ # Manual verification checklist
2
+
3
+ Two `qodo-cli` paths are implemented and unit-tested with mocks, but cannot be
4
+ exercised against the real systems in CI (we have no GitHub Enterprise instance
5
+ and no Qodo subscription in the CI environment). This checklist is how to verify
6
+ them by hand when a real system *is* available, plus the offline contract test
7
+ that pins the response shape in the meantime.
8
+
9
+ See `docs/qodo-skills-sources.md` for the resolved contracts these verify.
10
+
11
+ ## 1. Offline contract test (always runs)
12
+
13
+ `tests/test_contracts.py::test_rules_search_parses_recorded_response` asserts the
14
+ `/rules/search` parser against a recorded response fixture
15
+ (`tests/fixtures/rules_search_response.json`) — relevance order preserved, the
16
+ documented `{id, name, content, severity}` fields present, severities within the
17
+ known set, and unknown extra fields (e.g. `score`) passing through untouched.
18
+
19
+ If the Qodo API response shape ever changes, refresh the fixture from a real
20
+ response and update the contract test alongside the parser:
21
+
22
+ ```bash
23
+ # with a real key, capture a response shape (redact ids/content as needed):
24
+ QODO_API_KEY=… qodo rules get "validate input" --json
25
+ # then update tests/fixtures/rules_search_response.json + the contract test.
26
+ ```
27
+
28
+ ## 2. `qodo rules` against the real Qodo API
29
+
30
+ Prerequisite: a Qodo subscription and an API key, either in
31
+ `~/.qodo/config.json` (`"API_KEY"`) or exported as `QODO_API_KEY`.
32
+
33
+ ```bash
34
+ qodo doctor # qodo_client_config_present should pass
35
+ qodo rules get "validate all user input at trust boundaries"
36
+ qodo rules get "sql safety" --json | jq '.rules[].severity'
37
+ qodo rules get "anything" --no-scope --json | jq '.scopes' # -> null
38
+ ```
39
+
40
+ Expected: relevance-ranked rules with `ERROR` / `WARNING` / `RECOMMENDATION`
41
+ severities; `--json` carries `scopes` (the auto-detected `org/repo`, or `null`
42
+ under `--no-scope`); a missing key exits `2` with a `hint:` and never prompts.
43
+
44
+ Opt-in smoke (runs only when explicitly enabled — `QODO_LIVE_SMOKE` gates it so a
45
+ shell that merely exports `QODO_API_KEY` for real `qodo rules` use never fires a
46
+ network call during a normal `pytest` run):
47
+
48
+ ```bash
49
+ QODO_LIVE_SMOKE=1 QODO_API_KEY=… \
50
+ uv run pytest tests/test_contracts.py::test_live_rules_search_smoke -v
51
+ ```
52
+
53
+ ## 3. `qodo review` against a real GitHub Enterprise host
54
+
55
+ `resolve_provider()` upgrades an unknown host to `github` when `gh` is
56
+ authenticated to it (`gh auth status --hostname <host>`). This is covered by
57
+ mocked tests only — verify it against a real GHE instance like so:
58
+
59
+ ```bash
60
+ gh auth login --hostname ghe.your-company.com # one-time
61
+ cd /path/to/a/repo/whose/origin/is/that/GHE/host
62
+ qodo review list # should detect + list, not error
63
+ ```
64
+
65
+ Expected: the GHE remote resolves to `github` (no "not wired yet" / "could not
66
+ identify the git provider" error), and the Qodo bot's comments list as on
67
+ github.com. Then exercise resolution on a real PR:
68
+
69
+ ```bash
70
+ qodo review list --json | jq '.comments[] | {id, severity, type}'
71
+ qodo review resolve <comment-id> --reply "Verified." --sign
72
+ qodo review resolve --all --severity HIGH # batch
73
+ ```
74
+
75
+ Expected: the reply posts, the `+1` reaction lands, and the GitHub review thread
76
+ is marked resolved (via the GraphQL `resolveReviewThread` mutation); a comment
77
+ with no mappable thread falls back to reaction-only and is reported, not failed.
78
+
79
+ Opt-in smoke (runs only when opted in *and* `gh` is authenticated to the host —
80
+ otherwise it skips cleanly rather than failing on a `gh`-less / unauthenticated
81
+ box):
82
+
83
+ ```bash
84
+ QODO_LIVE_SMOKE=1 QODO_CLI_GHE_REMOTE=git@ghe.your-company.com:org/repo.git \
85
+ uv run pytest tests/test_contracts.py::test_live_ghe_resolves_to_github -v
86
+ ```
87
+
88
+ ## 4. `qodo review` against a self-hosted GitLab on a custom domain
89
+
90
+ `resolve_provider()` upgrades an unknown host to `gitlab` when `glab` is
91
+ authenticated to it (`glab auth status --hostname <host>`), mirroring the GHE
92
+ path above. `gitlab.com` is detected by hostname directly (no `glab` call);
93
+ `gh` is consulted before `glab`, so a host both CLIs know resolves to `github`.
94
+ Covered by mocked tests only — verify it against a real self-hosted instance:
95
+
96
+ ```bash
97
+ glab auth login --hostname gitlab.your-company.com # one-time
98
+ cd /path/to/a/repo/whose/origin/is/that/GitLab/host
99
+ qodo review list # should detect + list, not error
100
+ ```
101
+
102
+ Expected: the custom-domain remote resolves to `gitlab` (no "not wired yet" /
103
+ "could not identify the git provider" error), and the Qodo bot's notes list as
104
+ on gitlab.com. Resolution marks the note's MR *discussion* resolved (GitLab has
105
+ no `+1` marker).
106
+
107
+ ## 5. Provider gate (still-unwired providers)
108
+
109
+ An Azure/Bitbucket/Gerrit remote should fail with a clear, actionable message
110
+ rather than misbehaving:
111
+
112
+ ```bash
113
+ cd /path/to/an/azure/devops/repo
114
+ qodo review list # exit 2, "provider 'azure' is not wired yet" + hint
115
+ ```
@@ -30,8 +30,9 @@ calling agent — which keeps the CLI zero-dependency and model-agnostic.
30
30
  generates (call `rules get` once per query and merge).
31
31
  - **`qodo review`** — we own: detect the provider, find the open PR, fetch and
32
32
  filter the Qodo bot's comments, dedup by stable comment identity (id/url),
33
- reply, acknowledge. The agent owns: reading the flagged files, generating a
34
- fix, editing, and committing.
33
+ parse each body into structured triage fields, reply, acknowledge, and resolve
34
+ the review thread. The agent owns: reading the flagged files, generating a fix,
35
+ editing, and committing.
35
36
 
36
37
  ## Resolved contract — `qodo-get-rules`
37
38
 
@@ -49,6 +50,14 @@ calling agent — which keeps the CLI zero-dependency and model-agnostic.
49
50
  - **Request body:** `{"query": <str>, "top_k": <int>, "scopes": [<str>]}`.
50
51
  `scopes` is omitted entirely when empty (never `null` / `[]`). `top_k`
51
52
  defaults to `20`.
53
+ - **Scope auto-detection:** when `--scope` is omitted, the CLI mirrors the
54
+ skill — it derives the repository scope as the `org/repo` slug from
55
+ `git remote get-url origin` (SSH `git@host:org/repo(.git)`, HTTPS
56
+ `https://host/org/repo(.git)`, `ssh://…` forms; multi-level namespaces such as
57
+ GitLab subgroups are preserved) and a module scope `<name>` when the working
58
+ path contains `modules/<name>/`. Detection is non-raising: no git / no origin
59
+ yields no scope (omitted, not empty). `--scope` overrides detection;
60
+ `--no-scope` forces omission.
52
61
  - **Response:** `{"rules": [{"id", "name", "content", "severity"}]}`, ranked by
53
62
  relevance (most relevant first). Severity is one of `ERROR`, `WARNING`,
54
63
  `RECOMMENDATION`.
@@ -79,15 +88,39 @@ calling agent — which keeps the CLI zero-dependency and model-agnostic.
79
88
  - inline comments: `gh api repos/{owner}/{repo}/pulls/<pr>/comments --paginate`
80
89
  - reply: `gh api repos/{owner}/{repo}/pulls/<pr>/comments/<id>/replies -X POST -f body=<text>`
81
90
  - acknowledge: `gh api repos/{owner}/{repo}/pulls/comments/<id>/reactions -X POST -f content='+1'`
91
+ - resolve thread: map the comment's REST id to its review-thread node id via
92
+ `gh api graphql` (`repository.pullRequest.reviewThreads` → match
93
+ `comments.nodes.databaseId`), then `resolveReviewThread(input:{threadId})`.
94
+
95
+ - **Comment body structure (parsed by `qodo review list`):** a Qodo inline body
96
+ opens with a severity badge `<img ... alt="Action required">` (or
97
+ `"Review recommended"`); the title line carries `<code>` category chips
98
+ (e.g. `📘 Rule violation`, `≡ Correctness`); the `<pre>` block is the issue
99
+ description (HTML-entity-encoded); and a `<details><summary>Agent Prompt</summary>`
100
+ fenced block holds the remediation prompt. Severity map (the lever — extend as
101
+ Qodo adds badges): `Action required → HIGH`, `Review recommended → MEDIUM`,
102
+ any other badge → `LOW`, no badge → `null`. Parsing is best-effort; an
103
+ unrecognised body degrades to title-only with `null` fields.
104
+
105
+ - **GitLab (wired now), via `glab`:** GitLab's model is MR **discussions** (each
106
+ holds one or more **notes**); resolution is at the discussion level (no `+1`
107
+ marker — resolving the discussion *is* the acknowledgement). The project path
108
+ is the `namespace/repo` slug from `origin`, URL-encoded for the API.
109
+ - find MR: `glab api "projects/<proj>/merge_requests?source_branch=<branch>&state=opened"`
110
+ - notes: `glab api "projects/<proj>/merge_requests/<iid>/discussions?per_page=100" --paginate`
111
+ (keep notes whose `author.username` is a Qodo bot; skip `system` notes)
112
+ - reply: `glab api projects/<proj>/merge_requests/<iid>/discussions/<disc>/notes -X POST -f body=<text>`
113
+ - resolve: `glab api projects/<proj>/merge_requests/<iid>/discussions/<disc> -X PUT -f resolved=true`
114
+ - **Implemented but NOT live-tested** against a real GitLab (we have none);
115
+ covered by mocked tests mirroring the GitHub ones (the `glab` REST shapes
116
+ above are the contract). The provider gate (`require_provider`) now allows
117
+ `github` + `gitlab`.
82
118
 
83
119
  ### Follow-up providers (recognised, not yet wired)
84
120
 
85
121
  These are captured from `resources/providers.md` so wiring them is a lookup, not
86
122
  a re-investigation. Today the CLI raises a clear "not wired yet" error for them.
87
123
 
88
- - **GitLab (`glab`):** find `glab mr list --source-branch <branch>`; fetch
89
- `glab mr view <iid> --comments`; reply / resolve via
90
- `glab api /projects/:id/merge_requests/<iid>/discussions/...`.
91
124
  - **Azure DevOps (`az`):** find `az repos pr list --source-branch <branch> --status active`;
92
125
  fetch / reply / resolve via `az devops invoke --resource pullRequestThreads`.
93
126
  - **Bitbucket (`curl`):** REST under
@@ -96,8 +129,9 @@ a re-investigation. Today the CLI raises a clear "not wired yet" error for them.
96
129
  `git push origin HEAD:refs/for/<branch>`.
97
130
 
98
131
  True GitHub review-thread resolution (the GraphQL `resolveReviewThread`
99
- mutation) is also a follow-up; today `resolve` posts the `+1` reaction the
100
- upstream skill uses as a lightweight acknowledgement.
132
+ mutation) is **now wired**: `resolve` posts the `+1` reaction the upstream skill
133
+ uses *and* resolves the GitHub review thread by default (`--no-resolve-thread`
134
+ to skip), falling back to reaction-only when no thread maps to the comment.
101
135
 
102
136
  ## Non-goals (enforced)
103
137
 
@@ -107,11 +141,22 @@ upstream skill uses as a lightweight acknowledgement.
107
141
  - `qodo rules` does not implement an interactive login — it reuses existing
108
142
  credentials and errors when they are absent.
109
143
 
144
+ ## Verifying the contracts
145
+
146
+ The `/rules/search` response shape is pinned by an **offline contract test**
147
+ (`tests/test_contracts.py`) against a recorded fixture
148
+ (`tests/fixtures/rules_search_response.json`). Paths that need a real system to
149
+ exercise — `qodo rules` against the live API, and GitHub Enterprise provider
150
+ resolution — have **opt-in live smokes** (gated on `QODO_API_KEY` /
151
+ `QODO_CLI_GHE_REMOTE`, skipped by default) plus a manual checklist in
152
+ `docs/manual-verification.md`.
153
+
110
154
  ## Re-sync procedure
111
155
 
112
156
  1. Re-fetch the upstream `SKILL.md` and the `references/` / `resources/` files
113
157
  listed in the verb↔skill map above.
114
158
  2. Diff the resolved contract in this file against them (endpoint, headers,
115
159
  request/response schema, bot logins, provider commands).
116
- 3. Update `qodo/cli/_qodo_api.py` / `qodo/cli/_providers.py` and this ledger
160
+ 3. Update `qodo/cli/_qodo_api.py` / `qodo/cli/_providers.py`, the contract
161
+ fixture (`tests/fixtures/rules_search_response.json`), and this ledger
117
162
  together, then bump the version.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qodo-cli"
3
- version = "0.4.0"
3
+ version = "0.9.0"
4
4
  description = "Community CLI and agent to manage Qodo — the AI code reviewer and Qodo's other agents (requires a Qodo subscription). Unofficial: not affiliated with, authorized, or endorsed by Qodo; the Qodo name and trademark belong to Qodo Ltd."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -63,6 +63,7 @@ def _argv_has_json(argv: list[str] | None) -> bool:
63
63
 
64
64
  def _build_parser() -> argparse.ArgumentParser:
65
65
  from qodo.cli._commands import cli as _cli_group
66
+ from qodo.cli._commands import config as _config_group
66
67
  from qodo.cli._commands import doctor as _doctor_cmd
67
68
  from qodo.cli._commands import explain as _explain_cmd
68
69
  from qodo.cli._commands import learn as _learn_cmd
@@ -87,6 +88,7 @@ def _build_parser() -> argparse.ArgumentParser:
87
88
  # Qodo domain noun groups (the real surface):
88
89
  _rules_group.register(sub)
89
90
  _review_group.register(sub)
91
+ _config_group.register(sub)
90
92
  # Agent-first introspection verbs:
91
93
  _whoami_cmd.register(sub)
92
94
  _learn_cmd.register(sub)