kd-cli 2.7.0__tar.gz → 2.7.1__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.
- {kd_cli-2.7.0/kd_cli.egg-info → kd_cli-2.7.1}/PKG-INFO +1 -1
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/article/__init__.py +1 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/client.py +38 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/CHANGELOG.md +10 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/operating-guide.md +1 -1
- {kd_cli-2.7.0 → kd_cli-2.7.1/kd_cli.egg-info}/PKG-INFO +1 -1
- {kd_cli-2.7.0 → kd_cli-2.7.1}/pyproject.toml +1 -1
- {kd_cli-2.7.0 → kd_cli-2.7.1}/LICENSE +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/MANIFEST.in +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/README.md +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/api/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/api/articles.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/api/http.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/api/hub.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/api/saved_queries.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/api/tags.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/api/youtrack.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/article_marker.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/auth.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cache.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/core/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/core/command_base.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/core/command_parser.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/core/confirm.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/core/namespaces.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/core/option_registry.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/input.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/main.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/alias/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/cache/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/init.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/issue/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/primer/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/project/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/query/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/release_notes.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/search/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/system/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/tag/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/time/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/namespaces/user/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/output.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/cli/tree.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/config.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/config_read.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/config_schema.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/connection.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/debug.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/exceptions.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/history.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/locking.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/article.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/assist.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/attachment.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/comment.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/common.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/issue.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/project.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/saved_query.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/search.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/tag.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/tag_entities.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/user.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/models/work_item.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/operating_guide.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/primer.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/query_parser.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/release_notes.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/command-synonyms.yaml +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/config-template.yaml +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/error-messages.yaml +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/primers/__init__.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/primers/hitl.yaml +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/resources/sdlc-doctrine.md +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd/sdlc.py +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd_cli.egg-info/SOURCES.txt +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd_cli.egg-info/dependency_links.txt +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd_cli.egg-info/entry_points.txt +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd_cli.egg-info/requires.txt +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/kd_cli.egg-info/top_level.txt +0 -0
- {kd_cli-2.7.0 → kd_cli-2.7.1}/setup.cfg +0 -0
|
@@ -141,6 +141,7 @@ class ArticleSearchCommand(Command):
|
|
|
141
141
|
examples=[
|
|
142
142
|
'kd /article search "project: {Code OnTime} {wix}"',
|
|
143
143
|
'kd /article search "project: {Intellect Solutions} content: HKEY_LOCAL_MACHINE"',
|
|
144
|
+
'kd /article search "project: {COT} tag: {kind:proposal}"',
|
|
144
145
|
'kd /article search "summary: timeout" --limit 10',
|
|
145
146
|
],
|
|
146
147
|
see_also=["/article list", "/search", "/tag articles"],
|
|
@@ -20,6 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import builtins
|
|
22
22
|
import difflib
|
|
23
|
+
import re
|
|
23
24
|
import shlex
|
|
24
25
|
from collections import OrderedDict
|
|
25
26
|
from collections.abc import Callable
|
|
@@ -1345,6 +1346,36 @@ def _is_unknown_query_param(exc: APIError) -> bool:
|
|
|
1345
1346
|
return ("parameter" in body) and ("unknown" in body or "unrecognized" in body)
|
|
1346
1347
|
|
|
1347
1348
|
|
|
1349
|
+
_TAG_BARE_COLON_LITERAL_RE = re.compile(r"\btag\s*:\s*(?!\{)\S*:\S*")
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
def _is_tag_literal_misparse(exc: APIError, query: str) -> bool:
|
|
1353
|
+
"""Heuristic: did YT's article query DSL parse a colon inside a
|
|
1354
|
+
bare ``tag:`` literal as a field separator?
|
|
1355
|
+
|
|
1356
|
+
Two gates, both required:
|
|
1357
|
+
|
|
1358
|
+
1. The 400 body matches the observed phrasing — *"... isn't used
|
|
1359
|
+
for the tag field"* or its non-curly variant.
|
|
1360
|
+
2. The user's *input query* contains a ``tag:`` clause followed
|
|
1361
|
+
by a non-brace token that itself contains a colon. Without
|
|
1362
|
+
this the same upstream phrase fires for legitimate unknown-tag
|
|
1363
|
+
errors (e.g. ``tag: NOPE``), where the brace-wrapping hint
|
|
1364
|
+
would be misleading.
|
|
1365
|
+
|
|
1366
|
+
The regex is intentionally conservative — it does not understand
|
|
1367
|
+
quoted strings or full YT-DSL grammar; that is what the M012
|
|
1368
|
+
verbatim-passthrough contract trades away. False negatives prefer
|
|
1369
|
+
the no-hint path (raw YT message still surfaces) over the noisy
|
|
1370
|
+
false positive of suggesting braces when no colon was typed.
|
|
1371
|
+
"""
|
|
1372
|
+
body = (exc.response_body or "").lower()
|
|
1373
|
+
body_match = "isn't used for the tag field" in body or "is not used for the tag field" in body
|
|
1374
|
+
if not body_match:
|
|
1375
|
+
return False
|
|
1376
|
+
return _TAG_BARE_COLON_LITERAL_RE.search(query) is not None
|
|
1377
|
+
|
|
1378
|
+
|
|
1348
1379
|
class ArticleOperations:
|
|
1349
1380
|
"""High-level knowledge base article operations."""
|
|
1350
1381
|
|
|
@@ -1392,6 +1423,13 @@ class ArticleOperations:
|
|
|
1392
1423
|
except APIError as exc:
|
|
1393
1424
|
if exc.status_code == 400 and _is_unknown_query_param(exc):
|
|
1394
1425
|
raise ValidationError(_CAPABILITY_MISSING_MSG) from exc
|
|
1426
|
+
if exc.status_code == 400 and _is_tag_literal_misparse(exc, query):
|
|
1427
|
+
raise ValidationError(
|
|
1428
|
+
f"{exc}\n"
|
|
1429
|
+
"Hint: tag names with colons need brace wrapping in YouTrack "
|
|
1430
|
+
"query syntax — use `tag: {kind:proposal}`, not "
|
|
1431
|
+
"`tag: kind:proposal`. See `kd --guide` §YouTrack Gotchas."
|
|
1432
|
+
) from exc
|
|
1395
1433
|
if exc.status_code == 400:
|
|
1396
1434
|
raise ValidationError(str(exc)) from exc
|
|
1397
1435
|
raise
|
|
@@ -4,6 +4,16 @@ All notable changes to kd-cli are recorded here. The project follows [Semantic V
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.7.1] — 2026-05-02
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- `kd /article search` now appends a brace-wrapping hint when YouTrack returns
|
|
11
|
+
the *"… isn't used for the tag field"* 400 caused by a bare colon-bearing
|
|
12
|
+
`tag:` literal (e.g. `tag: kind:proposal`). The upstream message stays
|
|
13
|
+
visible; kd points to the fix — `tag: {kind:proposal}` — and references
|
|
14
|
+
`kd --guide` §YouTrack Gotchas. Docs and `kd /article search --help` now
|
|
15
|
+
show the brace form (KDCLI-186).
|
|
16
|
+
|
|
7
17
|
## [2.7.0] — 2026-05-02
|
|
8
18
|
|
|
9
19
|
This release adds server-backed full-text search for knowledge-base
|
|
@@ -112,7 +112,7 @@ YouTrack's article query API is undocumented but supported on the YT tiers kd ta
|
|
|
112
112
|
|
|
113
113
|
Saved queries are YouTrack issue-search objects. `/query issues` executes them directly. `/query articles` only translates supported `project:` and `tag:` fields into article scans; issue-specific fields such as state or assignee are rejected for articles.
|
|
114
114
|
|
|
115
|
-
Tag names with colons or spaces need YouTrack query literal handling. `tag: type:case` is wrong; YouTrack reads `type` as the tag value and `case` as another field. Use `tag: {type:case}` in hand-written queries. kd command paths that compose tag queries use the shared literal helper.
|
|
115
|
+
Tag names with colons or spaces need YouTrack query literal handling in both issue and article queries (the article query DSL is the same syntax family). `tag: type:case` is wrong; YouTrack reads `type` as the tag value and `case` as another field. Use `tag: {type:case}` in hand-written queries — for `/issue list --query` and `/article search` alike. kd command paths that compose tag queries use the shared literal helper; `/article search` preserves the YouTrack 400 and appends a brace-wrapping hint when a bare colon-bearing `tag:` literal trips the misparse.
|
|
116
116
|
|
|
117
117
|
Repeated same-field clauses in YouTrack query syntax default to OR. `tag: A tag: B` matches either tag. kd multi-tag issue operations join tag clauses with explicit `and` when intersection is required.
|
|
118
118
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|