kd-cli 2.6.6__tar.gz → 2.7.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 (86) hide show
  1. {kd_cli-2.6.6/kd_cli.egg-info → kd_cli-2.7.0}/PKG-INFO +1 -1
  2. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/api/articles.py +16 -2
  3. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/article/__init__.py +59 -6
  4. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/client.py +41 -0
  5. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/CHANGELOG.md +21 -0
  6. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/operating-guide.md +4 -4
  7. {kd_cli-2.6.6 → kd_cli-2.7.0/kd_cli.egg-info}/PKG-INFO +1 -1
  8. {kd_cli-2.6.6 → kd_cli-2.7.0}/pyproject.toml +1 -1
  9. {kd_cli-2.6.6 → kd_cli-2.7.0}/LICENSE +0 -0
  10. {kd_cli-2.6.6 → kd_cli-2.7.0}/MANIFEST.in +0 -0
  11. {kd_cli-2.6.6 → kd_cli-2.7.0}/README.md +0 -0
  12. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/__init__.py +0 -0
  13. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/api/__init__.py +0 -0
  14. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/api/http.py +0 -0
  15. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/api/hub.py +0 -0
  16. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/api/saved_queries.py +0 -0
  17. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/api/tags.py +0 -0
  18. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/api/youtrack.py +0 -0
  19. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/article_marker.py +0 -0
  20. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/auth.py +0 -0
  21. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cache.py +0 -0
  22. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/__init__.py +0 -0
  23. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/core/__init__.py +0 -0
  24. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/core/command_base.py +0 -0
  25. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/core/command_parser.py +0 -0
  26. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/core/confirm.py +0 -0
  27. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/core/namespaces.py +0 -0
  28. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/core/option_registry.py +0 -0
  29. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/input.py +0 -0
  30. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/main.py +0 -0
  31. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/__init__.py +0 -0
  32. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/alias/__init__.py +0 -0
  33. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/cache/__init__.py +0 -0
  34. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/init.py +0 -0
  35. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/issue/__init__.py +0 -0
  36. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/primer/__init__.py +0 -0
  37. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/project/__init__.py +0 -0
  38. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/query/__init__.py +0 -0
  39. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/release_notes.py +0 -0
  40. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/search/__init__.py +0 -0
  41. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/system/__init__.py +0 -0
  42. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/tag/__init__.py +0 -0
  43. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/time/__init__.py +0 -0
  44. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/namespaces/user/__init__.py +0 -0
  45. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/output.py +0 -0
  46. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/cli/tree.py +0 -0
  47. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/config.py +0 -0
  48. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/config_read.py +0 -0
  49. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/config_schema.py +0 -0
  50. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/connection.py +0 -0
  51. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/debug.py +0 -0
  52. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/exceptions.py +0 -0
  53. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/history.py +0 -0
  54. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/locking.py +0 -0
  55. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/__init__.py +0 -0
  56. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/article.py +0 -0
  57. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/assist.py +0 -0
  58. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/attachment.py +0 -0
  59. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/comment.py +0 -0
  60. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/common.py +0 -0
  61. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/issue.py +0 -0
  62. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/project.py +0 -0
  63. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/saved_query.py +0 -0
  64. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/search.py +0 -0
  65. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/tag.py +0 -0
  66. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/tag_entities.py +0 -0
  67. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/user.py +0 -0
  68. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/models/work_item.py +0 -0
  69. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/operating_guide.py +0 -0
  70. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/primer.py +0 -0
  71. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/query_parser.py +0 -0
  72. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/release_notes.py +0 -0
  73. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/__init__.py +0 -0
  74. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/command-synonyms.yaml +0 -0
  75. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/config-template.yaml +0 -0
  76. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/error-messages.yaml +0 -0
  77. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/primers/__init__.py +0 -0
  78. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/primers/hitl.yaml +0 -0
  79. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/resources/sdlc-doctrine.md +0 -0
  80. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd/sdlc.py +0 -0
  81. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd_cli.egg-info/SOURCES.txt +0 -0
  82. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd_cli.egg-info/dependency_links.txt +0 -0
  83. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd_cli.egg-info/entry_points.txt +0 -0
  84. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd_cli.egg-info/requires.txt +0 -0
  85. {kd_cli-2.6.6 → kd_cli-2.7.0}/kd_cli.egg-info/top_level.txt +0 -0
  86. {kd_cli-2.6.6 → kd_cli-2.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.6.6
3
+ Version: 2.7.0
4
4
  Summary: Knowledge Dispatcher for issue trackers and knowledge bases
5
5
  Author: Sergiy Gladkyy
6
6
  License-Expression: MIT
@@ -1,12 +1,16 @@
1
1
  """Articles REST API wrapper.
2
2
 
3
3
  Covers ``/api/articles`` endpoints:
4
- - Articles: list (global + project-scoped), get, create, update, delete
4
+ - Articles: list (global + project-scoped), search, get, create, update, delete
5
5
  - Child articles: list, attach, detach parent
6
6
  - Comments: list, create
7
7
  - Attachments: list, get, create, delete
8
8
 
9
- Articles have no ``query`` parameter — filtering is project-scoped only.
9
+ The ``query`` parameter on ``/api/articles`` is undocumented in the
10
+ public REST reference but probed-and-supported on the YT tiers kd
11
+ targets (current cloud + on-prem 2023.2); ``search_articles`` is the
12
+ canonical full-text path. Servers that reject it surface as a 400 with
13
+ "unknown parameter" wording, classified at the SDK boundary.
10
14
  """
11
15
 
12
16
  from __future__ import annotations
@@ -103,6 +107,16 @@ class ArticlesAPI:
103
107
  )
104
108
  return [Article.model_validate(a) for a in data]
105
109
 
110
+ # --- Search ---
111
+
112
+ def search_articles(self, query: str, top: int = 50, skip: int = 0) -> list[Article]:
113
+ data = self._http.get(
114
+ "/articles",
115
+ fields=_ARTICLE_LIST_FIELDS,
116
+ params={"query": query, "$top": top, "$skip": skip},
117
+ )
118
+ return [Article.model_validate(a) for a in data]
119
+
106
120
  # --- Tag scan (articles with tags for client-side filtering) ---
107
121
 
108
122
  def list_articles_with_tags(self, top: int = 50, skip: int = 0) -> list[Article]:
@@ -1,6 +1,6 @@
1
1
  """Article namespace: /article
2
2
 
3
- Commands: list, show, children, create, update, delete, tree, move
3
+ Commands: list, search, show, pull, push, children, create, update, delete, tree, move
4
4
  Sub-namespaces: /article/comment (list, add), /article/attachment (list, add, show, download, delete)
5
5
  API layer: YouTrack REST API (/api/articles)
6
6
  """
@@ -81,17 +81,19 @@ class ArticleListCommand(Command):
81
81
  requires_client=True,
82
82
  usage="kd /article list [--project P] [--parent ID] [--limit N]",
83
83
  long_description=(
84
- "Articles do not support search queries. Use --project to filter "
85
- "by project, or --parent ARTICLE_ID to list direct children of "
86
- "another article. When --parent is given, --project is ignored "
87
- "because child listing is already scoped to the parent's project."
84
+ "List articles structurally — by project (--project) or by "
85
+ "parent (--parent ARTICLE_ID). For full-text / body / "
86
+ "field-bearing queries use `/article search`; this command "
87
+ "does not interpret query syntax. When --parent is given, "
88
+ "--project is ignored because child listing is already "
89
+ "scoped to the parent's project."
88
90
  ),
89
91
  examples=[
90
92
  "kd /article list --project FB",
91
93
  "kd /article list --project FB --limit 10",
92
94
  "kd /article list --parent FB-A-1",
93
95
  ],
94
- see_also=["/article show", "/article children"],
96
+ see_also=["/article show", "/article children", "/article search"],
95
97
  )
96
98
 
97
99
  def _register_options(self) -> None:
@@ -118,6 +120,56 @@ class ArticleListCommand(Command):
118
120
  return [_article_to_row(a) for a in articles]
119
121
 
120
122
 
123
+ class ArticleSearchCommand(Command):
124
+ """Server-side article full-text search via the YT article query DSL."""
125
+
126
+ def __init__(self) -> None:
127
+ super().__init__(
128
+ "search",
129
+ "Search articles by full-text query",
130
+ requires_client=True,
131
+ usage="kd /article search QUERY [--limit N] [--skip N]",
132
+ long_description=(
133
+ "Run a YouTrack article query against /api/articles?query=. "
134
+ "Matches body, summary, tags, and other YT-indexed fields; "
135
+ "results are server-side relevance-ranked. The upstream "
136
+ "query parameter is undocumented; servers that reject it "
137
+ "surface a specific capability error rather than silently "
138
+ "falling back to local substring search. See the article "
139
+ "search how-to for query syntax and field filters."
140
+ ),
141
+ examples=[
142
+ 'kd /article search "project: {Code OnTime} {wix}"',
143
+ 'kd /article search "project: {Intellect Solutions} content: HKEY_LOCAL_MACHINE"',
144
+ 'kd /article search "summary: timeout" --limit 10',
145
+ ],
146
+ see_also=["/article list", "/search", "/tag articles"],
147
+ )
148
+
149
+ def _register_options(self) -> None:
150
+ self.register_option(limit_option())
151
+ self.register_option(
152
+ OptionDeclaration(
153
+ name="skip",
154
+ layer=OptionLayer.COMMAND,
155
+ type=int,
156
+ default=0,
157
+ help_text="Skip the first N results (pagination).",
158
+ )
159
+ )
160
+
161
+ def execute(
162
+ self, args: list[str], options: ResolvedOptions, client: Any = None
163
+ ) -> CommandResult:
164
+ query = " ".join(args).strip()
165
+ if not query:
166
+ raise ValidationError("Usage: kd /article search QUERY")
167
+ limit = options.command.get("limit", 50)
168
+ skip = options.command.get("skip", 0)
169
+ articles = client.articles.search(query, top=limit, skip=skip)
170
+ return [_article_to_row(a) for a in articles]
171
+
172
+
121
173
  class ArticleShowCommand(Command):
122
174
  """Show article details."""
123
175
 
@@ -1058,6 +1110,7 @@ def create_article_namespace() -> Namespace:
1058
1110
  """Build the /article namespace."""
1059
1111
  ns = Namespace("article", "Knowledge base articles", path="article")
1060
1112
  ns.register_command("list", ArticleListCommand())
1113
+ ns.register_command("search", ArticleSearchCommand())
1061
1114
  ns.register_command("show", ArticleShowCommand())
1062
1115
  ns.register_command("pull", ArticlePullCommand())
1063
1116
  ns.register_command("push", ArticlePushCommand())
@@ -1325,6 +1325,26 @@ def _splice_body(old_body: str, block: str, position: str) -> str:
1325
1325
  return combined
1326
1326
 
1327
1327
 
1328
+ _CAPABILITY_MISSING_MSG = (
1329
+ "Article full-text search is not available on this YouTrack "
1330
+ "instance. The REST article query parameter is undocumented and "
1331
+ "this server rejected it. Use `/article list`, `/tag articles`, "
1332
+ "or `/cache search` for local cached content."
1333
+ )
1334
+
1335
+
1336
+ def _is_unknown_query_param(exc: APIError) -> bool:
1337
+ """Heuristic: did YT reject the ``query`` parameter as unknown?
1338
+
1339
+ YT versions that surface this path emit phrases like "unknown
1340
+ parameter" or "unrecognized parameter" in the 400 body. Refined
1341
+ once an instance that exhibits it surfaces during dogfooding;
1342
+ until then the heuristic is intentionally loose.
1343
+ """
1344
+ body = (exc.response_body or "").lower()
1345
+ return ("parameter" in body) and ("unknown" in body or "unrecognized" in body)
1346
+
1347
+
1328
1348
  class ArticleOperations:
1329
1349
  """High-level knowledge base article operations."""
1330
1350
 
@@ -1355,6 +1375,27 @@ class ArticleOperations:
1355
1375
  return self._articles.list_project_articles(project_obj.id, top=top, skip=skip)
1356
1376
  return self._articles.list_articles(top=top, skip=skip)
1357
1377
 
1378
+ def search(
1379
+ self,
1380
+ query: str,
1381
+ top: int = 50,
1382
+ skip: int = 0,
1383
+ ) -> builtins.list[Article]:
1384
+ """Server-side article full-text search via the YT article query
1385
+ engine. Wraps ``/api/articles?query=`` (undocumented but supported
1386
+ on current cloud + on-prem 2023.2). Server relevance order is
1387
+ preserved — no client-side re-sort, no local-substring fallback.
1388
+ """
1389
+ self._debug.info("api", f"searching articles query={query!r}")
1390
+ try:
1391
+ return self._articles.search_articles(query, top=top, skip=skip)
1392
+ except APIError as exc:
1393
+ if exc.status_code == 400 and _is_unknown_query_param(exc):
1394
+ raise ValidationError(_CAPABILITY_MISSING_MSG) from exc
1395
+ if exc.status_code == 400:
1396
+ raise ValidationError(str(exc)) from exc
1397
+ raise
1398
+
1358
1399
  def get(self, article_id: str) -> Article:
1359
1400
  return self._articles.get_article(article_id)
1360
1401
 
@@ -4,6 +4,27 @@ All notable changes to kd-cli are recorded here. The project follows [Semantic V
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.7.0] — 2026-05-02
8
+
9
+ This release adds server-backed full-text search for knowledge-base
10
+ articles. Until now `/article list --project P` was the only way to
11
+ find articles without knowing their id; body and field queries
12
+ required a manual scan. `kd /article search QUERY` closes that gap
13
+ with relevance ranking.
14
+
15
+ ### Added
16
+ - `kd /article search QUERY [--limit N] [--skip N]` and
17
+ `client.articles.search()` — server-backed article full-text search
18
+ via the YouTrack article query DSL (same syntax family as issue
19
+ search; supports `project: {…}`, `content:`, `summary:`, `tag:`,
20
+ `comments:`, etc., relevance-ranked by YouTrack). Output columns
21
+ match `/article list`. The upstream REST `query` parameter is
22
+ undocumented but supported on YouTrack cloud and on-prem 2023.2;
23
+ instances that reject it surface a specific capability error
24
+ rather than silently falling back to local substring matching, so
25
+ scripts cannot accidentally trade ranked server results for
26
+ unranked summary scans (KDCLI-183).
27
+
7
28
  ## [2.6.6] — 2026-05-01
8
29
 
9
30
  This release adds pinned issues as a first-class primer section
@@ -76,9 +76,9 @@ Not every read has the same cost.
76
76
 
77
77
  **Direct reads** fetch one entity or one server-side page. Examples: `/issue show`, `/article show`, `/project show`, `/tag show`, `/query show`.
78
78
 
79
- **Query-backed reads** rely on an upstream query or filter. Examples: `/issue list --query`, `/query issues`, `/tag issues`, `/tag list --prefix`, `/tag list --namespace`. They are usually cheap but still depend on YouTrack query semantics.
79
+ **Query-backed reads** rely on an upstream query or filter. Examples: `/issue list --query`, `/query issues`, `/tag issues`, `/tag list --prefix`, `/tag list --namespace`, `/article search`. They are usually cheap but still depend on YouTrack query semantics.
80
80
 
81
- **Scan-backed reads** walk pages and filter client-side because YouTrack has no matching endpoint. Examples: `/tag articles`, the article half of `/tag entities`, article substring search in `/search`, and deep `/article tree` traversal. Use limits and project scoping.
81
+ **Scan-backed reads** walk pages and filter client-side because YouTrack has no matching endpoint. Examples: `/tag articles`, the article half of `/tag entities`, the article half of `/search --types article` (client-side substring), and deep `/article tree` traversal. Use limits and project scoping.
82
82
 
83
83
  Write-side commands do not prompt for confirmation unless their help says so. Pair destructive or broad operations with a read first:
84
84
 
@@ -108,7 +108,7 @@ The cache namespace (`kd /cache refresh|status|search|clear`) is read-side by co
108
108
 
109
109
  ## YouTrack Gotchas
110
110
 
111
- YouTrack articles are not queryable like issues. `/api/articles` has no issue-style `query` parameter, and there is no server-side article-by-tag endpoint. kd article tag lookup scans article pages that include `tags(id,name)` and filters locally.
111
+ YouTrack's article query API is undocumented but supported on the YT tiers kd targets (cloud + on-prem 2023.2). `kd /article search QUERY` calls `GET /api/articles?query=…` and returns relevance-ranked results matching across body, summary, tags, and comments. Instances that reject the parameter surface a specific capability error rather than falling back silently. Server-side article-by-tag is still missing — `/tag articles` scans article pages that include `tags(id,name)` and filters locally.
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
 
@@ -357,4 +357,4 @@ Use these sources together:
357
357
  - `docs/design/architecture.md` and ADRs for architectural constraints.
358
358
 
359
359
  ---
360
- Updated: 2026-04-26
360
+ Updated: 2026-05-02
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.6.6
3
+ Version: 2.7.0
4
4
  Summary: Knowledge Dispatcher for issue trackers and knowledge bases
5
5
  Author: Sergiy Gladkyy
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kd-cli"
7
- version = "2.6.6"
7
+ version = "2.7.0"
8
8
  description = "Knowledge Dispatcher for issue trackers and knowledge bases"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
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