kd-cli 2.6.4__tar.gz → 2.6.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. {kd_cli-2.6.4/kd_cli.egg-info → kd_cli-2.6.5}/PKG-INFO +1 -1
  2. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/youtrack.py +5 -3
  3. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/client.py +74 -2
  4. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/exceptions.py +13 -0
  5. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/CHANGELOG.md +25 -0
  6. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/operating-guide.md +2 -0
  7. {kd_cli-2.6.4 → kd_cli-2.6.5/kd_cli.egg-info}/PKG-INFO +1 -1
  8. {kd_cli-2.6.4 → kd_cli-2.6.5}/pyproject.toml +1 -1
  9. {kd_cli-2.6.4 → kd_cli-2.6.5}/LICENSE +0 -0
  10. {kd_cli-2.6.4 → kd_cli-2.6.5}/MANIFEST.in +0 -0
  11. {kd_cli-2.6.4 → kd_cli-2.6.5}/README.md +0 -0
  12. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/__init__.py +0 -0
  13. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/__init__.py +0 -0
  14. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/articles.py +0 -0
  15. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/http.py +0 -0
  16. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/hub.py +0 -0
  17. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/saved_queries.py +0 -0
  18. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/tags.py +0 -0
  19. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/article_marker.py +0 -0
  20. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/auth.py +0 -0
  21. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cache.py +0 -0
  22. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/__init__.py +0 -0
  23. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/__init__.py +0 -0
  24. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/command_base.py +0 -0
  25. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/command_parser.py +0 -0
  26. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/confirm.py +0 -0
  27. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/namespaces.py +0 -0
  28. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/option_registry.py +0 -0
  29. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/input.py +0 -0
  30. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/main.py +0 -0
  31. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/__init__.py +0 -0
  32. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/alias/__init__.py +0 -0
  33. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/article/__init__.py +0 -0
  34. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/cache/__init__.py +0 -0
  35. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/init.py +0 -0
  36. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/issue/__init__.py +0 -0
  37. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/primer/__init__.py +0 -0
  38. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/project/__init__.py +0 -0
  39. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/query/__init__.py +0 -0
  40. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/release_notes.py +0 -0
  41. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/search/__init__.py +0 -0
  42. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/system/__init__.py +0 -0
  43. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/tag/__init__.py +0 -0
  44. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/time/__init__.py +0 -0
  45. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/user/__init__.py +0 -0
  46. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/output.py +0 -0
  47. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/tree.py +0 -0
  48. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/config.py +0 -0
  49. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/config_read.py +0 -0
  50. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/config_schema.py +0 -0
  51. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/connection.py +0 -0
  52. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/debug.py +0 -0
  53. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/history.py +0 -0
  54. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/locking.py +0 -0
  55. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/__init__.py +0 -0
  56. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/article.py +0 -0
  57. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/assist.py +0 -0
  58. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/attachment.py +0 -0
  59. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/comment.py +0 -0
  60. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/common.py +0 -0
  61. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/issue.py +0 -0
  62. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/project.py +0 -0
  63. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/saved_query.py +0 -0
  64. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/search.py +0 -0
  65. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/tag.py +0 -0
  66. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/tag_entities.py +0 -0
  67. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/user.py +0 -0
  68. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/work_item.py +0 -0
  69. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/operating_guide.py +0 -0
  70. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/primer.py +0 -0
  71. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/query_parser.py +0 -0
  72. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/release_notes.py +0 -0
  73. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/__init__.py +0 -0
  74. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/command-synonyms.yaml +0 -0
  75. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/config-template.yaml +0 -0
  76. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/error-messages.yaml +0 -0
  77. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/primers/__init__.py +0 -0
  78. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/primers/hitl.yaml +0 -0
  79. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/sdlc-doctrine.md +0 -0
  80. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/sdlc.py +0 -0
  81. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/SOURCES.txt +0 -0
  82. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/dependency_links.txt +0 -0
  83. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/entry_points.txt +0 -0
  84. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/requires.txt +0 -0
  85. {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/top_level.txt +0 -0
  86. {kd_cli-2.6.4 → kd_cli-2.6.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.6.4
3
+ Version: 2.6.5
4
4
  Summary: Knowledge Dispatcher for issue trackers and knowledge bases
5
5
  Author: Sergiy Gladkyy
6
6
  License-Expression: MIT
@@ -20,7 +20,7 @@ import re
20
20
  from typing import Any
21
21
 
22
22
  from kd.api.http import HttpClient
23
- from kd.exceptions import KdError
23
+ from kd.exceptions import FieldDiscoveryError, KdError
24
24
  from kd.models import (
25
25
  AssistResponse,
26
26
  Attachment,
@@ -257,13 +257,15 @@ class YouTrackAPI:
257
257
  admin_fields: list[dict[str, Any]] = admin.get("customFields") or []
258
258
  if admin_fields:
259
259
  return admin_fields
260
- raise KdError(
260
+ raise FieldDiscoveryError(
261
261
  f"No custom fields discovered for project '{project_id}'.",
262
262
  suggestion=(
263
263
  "Either the project has no issues to sample from, or the "
264
264
  "active token lacks Read Project Full on this project. "
265
265
  "Create one issue (any contributor token works), or use a "
266
- "token with project-administration rights. See KDCLI-A-22."
266
+ "token with project-administration rights. Or omit "
267
+ "--type / --priority / --state to create a minimal first "
268
+ "issue, then update it via `/issue update`. See KDCLI-A-22."
267
269
  ),
268
270
  )
269
271
 
@@ -45,7 +45,14 @@ from kd.article_marker import (
45
45
  from kd.config import ResolvedConfig
46
46
  from kd.connection import ConnectionContext
47
47
  from kd.debug import DebugLog
48
- from kd.exceptions import APIError, ConfigError, KdError, NotFoundError, ValidationError
48
+ from kd.exceptions import (
49
+ APIError,
50
+ ConfigError,
51
+ FieldDiscoveryError,
52
+ KdError,
53
+ NotFoundError,
54
+ ValidationError,
55
+ )
49
56
  from kd.query_parser import youtrack_query_literal
50
57
  from kd.models import (
51
58
  Article,
@@ -178,6 +185,19 @@ _SLOT_DEFAULTS: dict[str, str] = {
178
185
  }
179
186
 
180
187
 
188
+ # Bootstrap synthesis dispatch: semantic slot -> project-side field
189
+ # ``$type`` used to look up the issue-side ``$type`` and payload builder
190
+ # in :data:`_FIELD_DISPATCH` when schema discovery returned no fields
191
+ # (KDCLI-171). All three semantic slots are wired: HITL #2 confirmed
192
+ # YouTrack accepts a synthetic ``StateIssueCustomField`` payload with a
193
+ # ``name``-only value reference on an empty fresh project.
194
+ _BOOTSTRAP_SLOT_TO_PROJECT_TYPE: dict[str, str] = {
195
+ "type": "EnumProjectCustomField",
196
+ "priority": "EnumProjectCustomField",
197
+ "state": "StateProjectCustomField",
198
+ }
199
+
200
+
181
201
  # ---------------------------------------------------------------------------
182
202
  # Article push — types and helpers
183
203
  # ---------------------------------------------------------------------------
@@ -806,9 +826,61 @@ class IssueOperations:
806
826
  ``project`` is the project shortName (KEY).
807
827
  ``slots`` maps semantic names to values, e.g. ``{"type": "Bug"}``.
808
828
  ``aliases`` maps semantic names to actual field names from config.
829
+
830
+ On a fresh (zero-issue + non-admin) project, schema discovery
831
+ returns no fields and ``resolve_semantic_fields`` raises
832
+ :class:`FieldDiscoveryError`. For semantic-slot creates we
833
+ synthesize a best-effort payload using the post-discovery
834
+ ``$type`` mapping and let YouTrack accept or reject (KDCLI-171).
835
+ Generic ``--field`` flows route through
836
+ :meth:`resolve_generic_fields` instead and stay uncaught — without
837
+ server-side ``$type`` knowledge, synthesizing a guess is unsafe.
809
838
  """
810
839
  project_obj = self._yt.get_project(project)
811
- return self._field_schema.resolve_semantic_fields(project_obj.id, slots, aliases)
840
+ try:
841
+ return self._field_schema.resolve_semantic_fields(project_obj.id, slots, aliases)
842
+ except FieldDiscoveryError as e:
843
+ return self._synthesize_for_empty_discovery(slots, aliases, original=e)
844
+
845
+ def _synthesize_for_empty_discovery(
846
+ self,
847
+ slots: dict[str, str],
848
+ aliases: dict[str, str] | None,
849
+ original: FieldDiscoveryError,
850
+ ) -> builtins.list[CustomField]:
851
+ """Synthesize CustomField payloads when schema discovery is empty.
852
+
853
+ Honors the alias layer (``state`` -> ``Stage`` for Scrum projects)
854
+ via ``aliases.get(slot, _SLOT_DEFAULTS[slot])`` — same shape as
855
+ :meth:`FieldSchema.resolve_semantic_fields`. Re-raises the
856
+ *original* :class:`FieldDiscoveryError` (full three-bullet hint
857
+ preserved verbatim) when any slot is not in
858
+ :data:`_BOOTSTRAP_SLOT_TO_PROJECT_TYPE` — partial-bootstrap is
859
+ not a valid posture; the operator either gets all slots they
860
+ asked for, or the unchanged improved hint pointing at all three
861
+ workarounds (sample issue, admin token, or omit-and-update).
862
+ """
863
+ aliases = aliases or {}
864
+ unsupported = [s for s in slots if s not in _BOOTSTRAP_SLOT_TO_PROJECT_TYPE]
865
+ if unsupported:
866
+ raise original
867
+
868
+ result: builtins.list[CustomField] = []
869
+ for slot, value in slots.items():
870
+ project_field_type = _BOOTSTRAP_SLOT_TO_PROJECT_TYPE[slot]
871
+ issue_type, builder = _FIELD_DISPATCH[(project_field_type, "")]
872
+ field_name = aliases.get(slot, _SLOT_DEFAULTS.get(slot, slot))
873
+ self._debug.debug(
874
+ "fields",
875
+ f"synthesizing slot '{slot}' -> field '{field_name}' "
876
+ f"($type={issue_type}) on empty discovery",
877
+ )
878
+ result.append(
879
+ CustomField.model_validate(
880
+ {"name": field_name, "value": builder(value), "$type": issue_type}
881
+ )
882
+ )
883
+ return result
812
884
 
813
885
  def resolve_generic_fields(
814
886
  self,
@@ -95,6 +95,19 @@ class ValidationError(KdError):
95
95
  """Invalid input before API call."""
96
96
 
97
97
 
98
+ class FieldDiscoveryError(KdError):
99
+ """Schema discovery returned no fields for a project.
100
+
101
+ Raised by ``list_project_custom_fields`` when both the per-issue
102
+ projection and the admin projection produce empty results — the
103
+ 'fresh project + non-admin token' bootstrap shape. Caught at the
104
+ SDK layer (``IssueOperations.resolve_custom_fields``) so semantic
105
+ create-with-slots flows can synthesize a best-effort payload;
106
+ generic ``--field`` flows let it propagate so the operator sees
107
+ the improved hint.
108
+ """
109
+
110
+
98
111
  class QueryLiteralError(KdError):
99
112
  """Name cannot be encoded as a YouTrack DSL value literal.
100
113
 
@@ -4,6 +4,31 @@ All notable changes to kd-cli are recorded here. The project follows [Semantic V
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.6.5] — 2026-04-30
8
+
9
+ This release closes M011 by adding empty-project bootstrap support to
10
+ `/issue create`. Together with M011 S1 + S2 (the Windows UTF-8 audit in
11
+ 2.6.4), the milestone delivers a kd-only path from a fresh primer to
12
+ the first ticket on a brand-new project on Windows: file IO, stdin,
13
+ and field-resolution all work without falling back to the web UI.
14
+
15
+ ### Fixed
16
+ - `/issue create` semantic bootstrap now succeeds on fresh
17
+ (zero-issue, contributor-token) projects: when both schema-discovery
18
+ paths return empty, kd synthesizes a best-effort payload for
19
+ `--type`, `--priority`, and `--state` using the post-discovery
20
+ `$type` mapping and lets YouTrack accept or reject. Pre-fix, kd
21
+ bailed before the API call with `Error: No custom fields discovered
22
+ for project '<id>'`, telling the operator to fall back to the web UI
23
+ for the very first issue — a kd-only bootstrap workflow could not
24
+ populate Type / Priority / State on a brand-new project.
25
+ `--field NAME=VALUE` still raises with the improved hint (kd cannot
26
+ guess server-side `$type` for arbitrary fields). The hint gains a
27
+ third bullet pointing at the simplest workaround: `Or omit --type /
28
+ --priority / --state to create a minimal first issue, then update it
29
+ via `/issue update``. See KDCLI-A-22 for the empty-project bootstrap
30
+ detail (KDCLI-171).
31
+
7
32
  ## [2.6.4] — 2026-04-30
8
33
 
9
34
  This release closes the Windows UTF-8 audit started in 2.6.3. The
@@ -126,6 +126,8 @@ Clear optional custom fields with an empty value, for example `kd /issue update
126
126
 
127
127
  `--field` supports bundle families (enum, state, owned, version, build), user fields, period, and string `SimpleProjectCustomField` subtypes; whitespace inside the value is preserved. Unsupported families (text, `Multi*` enum/user variants, and `SimpleProjectCustomField` with `integer` / `float` / `date` subtypes) fail client-side with a message naming both the project `$type` and the `fieldType.id` subtype so a follow-on bug report can skip the probe.
128
128
 
129
+ On fresh (zero-issue, contributor-token) projects where schema discovery returns no fields, kd synthesizes a best-effort bootstrap payload for `--type`, `--priority`, and `--state` and lets YouTrack accept or reject; YouTrack's rejection text surfaces unchanged so an unusual project setup is auditable upstream. `--field NAME=VALUE` still requires a sample issue or admin token because kd lacks server-side `$type` knowledge to synthesize generic fields safely. KDCLI-A-22 carries the detail.
130
+
129
131
  Attachment metadata download URLs may start with `/api/`. kd handles this internally on download commands; do not assume the raw URL can be appended unchanged to an API base URL in custom scripts.
130
132
 
131
133
  Tag existence, tag visibility, and tag sharing rights are different concerns. A visible tag may not be usable for every operation, and sharing settings are provider-specific security posture, not just display metadata.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.6.4
3
+ Version: 2.6.5
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.4"
7
+ version = "2.6.5"
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