qodo-cli 0.5.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.
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/CHANGELOG.md +169 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/PKG-INFO +1 -1
- qodo_cli-0.9.0/docs/manual-verification.md +115 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/docs/qodo-skills-sources.md +53 -8
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/pyproject.toml +1 -1
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/__init__.py +2 -0
- qodo_cli-0.9.0/qodo/cli/_commands/config.py +395 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_commands/learn.py +2 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_commands/overview.py +13 -5
- qodo_cli-0.9.0/qodo/cli/_commands/review.py +308 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_commands/rules.py +32 -7
- qodo_cli-0.9.0/qodo/cli/_providers.py +947 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_qodo_api.py +92 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/explain/catalog.py +73 -11
- qodo_cli-0.9.0/tests/fixtures/rules_search_response.json +28 -0
- qodo_cli-0.9.0/tests/test_config.py +244 -0
- qodo_cli-0.9.0/tests/test_contracts.py +157 -0
- qodo_cli-0.9.0/tests/test_review.py +1012 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/tests/test_rules.py +136 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/uv.lock +1 -1
- qodo_cli-0.5.0/qodo/cli/_commands/review.py +0 -139
- qodo_cli-0.5.0/qodo/cli/_providers.py +0 -333
- qodo_cli-0.5.0/tests/test_review.py +0 -372
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/agent-config/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/cicd/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/communicate/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/think/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/think/scripts/think.sh +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/version-bump/SKILL.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.claude/skills.local.yaml.example +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.devague/current +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.devague/frames/qodo-cli-now-does-qodo-s-two-core-jobs-natively-fr.json +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.flake8 +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.github/workflows/publish.yml +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.github/workflows/tests.yml +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.gitignore +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.markdownlint-cli2.yaml +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/.pr_agent.toml +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/AGENTS.colleague.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/CLAUDE.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/LICENSE +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/README.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/best_practices.md +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/culture.yaml +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/docs/skill-sources.md +0 -0
- {qodo_cli-0.5.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
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/__init__.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/__main__.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_commands/__init__.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_commands/cli.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_commands/doctor.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_commands/explain.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_commands/whoami.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_errors.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/cli/_output.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/qodo/explain/__init__.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/sonar-project.properties +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/tests/__init__.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/tests/test_cli.py +0 -0
- {qodo_cli-0.5.0 → qodo_cli-0.9.0}/tests/test_cli_introspection.py +0 -0
|
@@ -5,6 +5,175 @@ 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
|
+
|
|
8
177
|
## [0.5.0] - 2026-06-17
|
|
9
178
|
|
|
10
179
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qodo-cli
|
|
3
|
-
Version: 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,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
|
-
|
|
34
|
-
|
|
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
|
|
100
|
-
|
|
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
|
|
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.
|
|
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)
|