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.
- {kd_cli-2.6.4/kd_cli.egg-info → kd_cli-2.6.5}/PKG-INFO +1 -1
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/youtrack.py +5 -3
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/client.py +74 -2
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/exceptions.py +13 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/CHANGELOG.md +25 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/operating-guide.md +2 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5/kd_cli.egg-info}/PKG-INFO +1 -1
- {kd_cli-2.6.4 → kd_cli-2.6.5}/pyproject.toml +1 -1
- {kd_cli-2.6.4 → kd_cli-2.6.5}/LICENSE +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/MANIFEST.in +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/README.md +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/articles.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/http.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/hub.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/saved_queries.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/api/tags.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/article_marker.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/auth.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cache.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/command_base.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/command_parser.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/confirm.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/namespaces.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/core/option_registry.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/input.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/main.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/alias/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/article/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/cache/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/init.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/issue/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/primer/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/project/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/query/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/release_notes.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/search/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/system/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/tag/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/time/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/namespaces/user/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/output.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/cli/tree.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/config.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/config_read.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/config_schema.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/connection.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/debug.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/history.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/locking.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/article.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/assist.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/attachment.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/comment.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/common.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/issue.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/project.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/saved_query.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/search.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/tag.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/tag_entities.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/user.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/models/work_item.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/operating_guide.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/primer.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/query_parser.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/release_notes.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/command-synonyms.yaml +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/config-template.yaml +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/error-messages.yaml +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/primers/__init__.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/primers/hitl.yaml +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/resources/sdlc-doctrine.md +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd/sdlc.py +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/SOURCES.txt +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/dependency_links.txt +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/entry_points.txt +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/requires.txt +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/kd_cli.egg-info/top_level.txt +0 -0
- {kd_cli-2.6.4 → kd_cli-2.6.5}/setup.cfg +0 -0
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|