kd-cli 2.5.1__tar.gz → 2.5.2__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 (82) hide show
  1. {kd_cli-2.5.1/kd_cli.egg-info → kd_cli-2.5.2}/PKG-INFO +1 -1
  2. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/article/__init__.py +26 -2
  3. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/client.py +32 -4
  4. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/resources/CHANGELOG.md +8 -0
  5. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/resources/operating-guide.md +2 -0
  6. {kd_cli-2.5.1 → kd_cli-2.5.2/kd_cli.egg-info}/PKG-INFO +1 -1
  7. {kd_cli-2.5.1 → kd_cli-2.5.2}/pyproject.toml +1 -1
  8. {kd_cli-2.5.1 → kd_cli-2.5.2}/LICENSE +0 -0
  9. {kd_cli-2.5.1 → kd_cli-2.5.2}/MANIFEST.in +0 -0
  10. {kd_cli-2.5.1 → kd_cli-2.5.2}/README.md +0 -0
  11. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/__init__.py +0 -0
  12. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/api/__init__.py +0 -0
  13. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/api/articles.py +0 -0
  14. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/api/http.py +0 -0
  15. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/api/hub.py +0 -0
  16. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/api/saved_queries.py +0 -0
  17. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/api/tags.py +0 -0
  18. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/api/youtrack.py +0 -0
  19. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/article_marker.py +0 -0
  20. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/auth.py +0 -0
  21. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/__init__.py +0 -0
  22. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/core/__init__.py +0 -0
  23. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/core/command_base.py +0 -0
  24. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/core/command_parser.py +0 -0
  25. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/core/confirm.py +0 -0
  26. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/core/namespaces.py +0 -0
  27. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/core/option_registry.py +0 -0
  28. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/input.py +0 -0
  29. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/main.py +0 -0
  30. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/__init__.py +0 -0
  31. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/alias/__init__.py +0 -0
  32. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/init.py +0 -0
  33. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/issue/__init__.py +0 -0
  34. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/primer/__init__.py +0 -0
  35. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/project/__init__.py +0 -0
  36. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/query/__init__.py +0 -0
  37. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/release_notes.py +0 -0
  38. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/search/__init__.py +0 -0
  39. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/system/__init__.py +0 -0
  40. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/tag/__init__.py +0 -0
  41. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/time/__init__.py +0 -0
  42. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/namespaces/user/__init__.py +0 -0
  43. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/output.py +0 -0
  44. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/cli/tree.py +0 -0
  45. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/config.py +0 -0
  46. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/config_read.py +0 -0
  47. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/config_schema.py +0 -0
  48. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/connection.py +0 -0
  49. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/debug.py +0 -0
  50. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/exceptions.py +0 -0
  51. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/history.py +0 -0
  52. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/locking.py +0 -0
  53. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/__init__.py +0 -0
  54. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/article.py +0 -0
  55. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/assist.py +0 -0
  56. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/attachment.py +0 -0
  57. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/comment.py +0 -0
  58. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/common.py +0 -0
  59. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/issue.py +0 -0
  60. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/project.py +0 -0
  61. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/saved_query.py +0 -0
  62. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/search.py +0 -0
  63. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/tag.py +0 -0
  64. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/tag_entities.py +0 -0
  65. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/user.py +0 -0
  66. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/models/work_item.py +0 -0
  67. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/operating_guide.py +0 -0
  68. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/primer.py +0 -0
  69. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/query_parser.py +0 -0
  70. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/release_notes.py +0 -0
  71. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/resources/__init__.py +0 -0
  72. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/resources/command-synonyms.yaml +0 -0
  73. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/resources/config-template.yaml +0 -0
  74. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/resources/error-messages.yaml +0 -0
  75. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/resources/primers/__init__.py +0 -0
  76. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd/resources/primers/hitl.yaml +0 -0
  77. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd_cli.egg-info/SOURCES.txt +0 -0
  78. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd_cli.egg-info/dependency_links.txt +0 -0
  79. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd_cli.egg-info/entry_points.txt +0 -0
  80. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd_cli.egg-info/requires.txt +0 -0
  81. {kd_cli-2.5.1 → kd_cli-2.5.2}/kd_cli.egg-info/top_level.txt +0 -0
  82. {kd_cli-2.5.1 → kd_cli-2.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.5.1
3
+ Version: 2.5.2
4
4
  Summary: Knowledge Dispatcher for issue trackers and knowledge bases
5
5
  Author: Sergiy Gladkyy
6
6
  License-Expression: MIT
@@ -181,7 +181,8 @@ class ArticlePushCommand(Command):
181
181
  requires_client=True,
182
182
  usage=(
183
183
  "kd /article push FILE --project P --title T "
184
- "[--slug S] [--parent ID] [--tag T] [--create-tags] [--force] [--dry-run]"
184
+ "[--slug S] [--parent ID] [--tag T] [--create-tags] "
185
+ "[--force] [--dry-run] [--no-marker-write]"
185
186
  ),
186
187
  long_description=(
187
188
  "Push a local markdown file to YouTrack as a knowledge base "
@@ -194,7 +195,13 @@ class ArticlePushCommand(Command):
194
195
  "the marker — treat the file as owned by push after the first "
195
196
  "run, like Cargo.lock. "
196
197
  "Refuses to overwrite a remotely-edited article unless --force "
197
- "is passed. --dry-run exits 1 on would-conflict, 0 on would-succeed."
198
+ "is passed. --dry-run exits 1 on would-conflict, 0 on would-succeed. "
199
+ "Pass --no-marker-write for one-way migrations where the local "
200
+ "file will be deleted after the push: the article is still "
201
+ "created/updated and tags are still applied, but the local file "
202
+ "is not rewritten. Re-running --no-marker-write pays the "
203
+ "scan-by-slug cost on every invocation since no upstream-id is "
204
+ "cached locally."
198
205
  ),
199
206
  examples=[
200
207
  'kd /article push docs/infra/dns-setup.md --project FB --title "DNS Setup Guide"',
@@ -204,6 +211,8 @@ class ArticlePushCommand(Command):
204
211
  'kd /article push guide.md --project FB --title "Guide" --tag "type:guide" --create-tags',
205
212
  'kd /article push guide.md --project FB --title "Guide" --dry-run',
206
213
  'kd /article push guide.md --project FB --title "Guide" --force',
214
+ 'kd /article push migrated.md --project FB --title "Migrated" --no-marker-write',
215
+ 'kd /article push migrated.md --project FB --title "Migrated" --no-marker-write --tag "type:guide"',
207
216
  ],
208
217
  see_also=[
209
218
  "/article pull",
@@ -274,6 +283,18 @@ class ArticlePushCommand(Command):
274
283
  help_text="Create missing tags before pushing",
275
284
  )
276
285
  )
286
+ self.register_option(
287
+ OptionDeclaration(
288
+ name="no-marker-write",
289
+ layer=OptionLayer.COMMAND,
290
+ type=bool,
291
+ default=False,
292
+ help_text=(
293
+ "Skip the local kd-meta marker rewrite for one-way "
294
+ "migrations; the server write and tag apply still run"
295
+ ),
296
+ )
297
+ )
277
298
 
278
299
  def execute(
279
300
  self, args: list[str], options: ResolvedOptions, client: Any = None
@@ -300,6 +321,7 @@ class ArticlePushCommand(Command):
300
321
  dry_run=bool(options.command.get("dry-run")),
301
322
  tags=tags or None,
302
323
  create_tags=bool(options.command.get("create-tags")),
324
+ write_marker=not bool(options.command.get("no-marker-write")),
303
325
  )
304
326
  return {
305
327
  "ok": True,
@@ -310,6 +332,8 @@ class ArticlePushCommand(Command):
310
332
  "dry_run": result.dry_run,
311
333
  "tags_applied": result.tags_applied,
312
334
  "tags_failed": result.tags_failed,
335
+ "marker_written": result.marker_written,
336
+ "parent": result.parent,
313
337
  }
314
338
 
315
339
 
@@ -195,6 +195,11 @@ class PushResult:
195
195
  - ``remote_updated_sec`` is the epoch-seconds timestamp of the
196
196
  article's ``updated`` field on the upstream side after the push.
197
197
  Zero for dry-runs that did not resolve an existing article.
198
+ - ``marker_written`` is ``True`` iff the local file was rewritten
199
+ on this invocation. ``False`` for dry-runs and for
200
+ ``write_marker=False`` pushes (one-way migrations).
201
+ - ``parent`` is the ``id_readable`` of the target article's parent
202
+ on the upstream side, or ``None`` for root articles.
198
203
  """
199
204
 
200
205
  action: Literal["create", "update"]
@@ -205,6 +210,8 @@ class PushResult:
205
210
  dry_run: bool
206
211
  tags_applied: builtins.list[str] = dataclass_field(default_factory=list)
207
212
  tags_failed: builtins.list[str] = dataclass_field(default_factory=list)
213
+ marker_written: bool = False
214
+ parent: str | None = None
208
215
 
209
216
 
210
217
  def _derive_slug_from_path(file_path: Path) -> str:
@@ -1304,6 +1311,7 @@ class ArticleOperations:
1304
1311
  dry_run: bool = False,
1305
1312
  tags: builtins.list[str] | None = None,
1306
1313
  create_tags: bool = False,
1314
+ write_marker: bool = True,
1307
1315
  ) -> PushResult:
1308
1316
  """Idempotently upsert a local markdown file as a YouTrack article.
1309
1317
 
@@ -1314,6 +1322,10 @@ class ArticleOperations:
1314
1322
 
1315
1323
  On success, the local file is rewritten atomically to refresh
1316
1324
  the marker's ``upstream-id`` and ``upstream-updated`` fields.
1325
+ Pass ``write_marker=False`` for one-way migrations where the
1326
+ local file will be deleted after the push — the outgoing
1327
+ server payload is unchanged, only the local write-back is
1328
+ skipped. See ``PushResult.marker_written`` (KDCLI-110).
1317
1329
  """
1318
1330
  # Step 1: read the local file and parse the marker.
1319
1331
  try:
@@ -1437,8 +1449,12 @@ class ArticleOperations:
1437
1449
  # Dry-run bails out here with all validation done but no writes.
1438
1450
  if dry_run:
1439
1451
  existing_sec = 0
1440
- if existing_remote is not None and existing_remote.updated is not None:
1441
- existing_sec = existing_remote.updated // 1000
1452
+ existing_parent: str | None = None
1453
+ if existing_remote is not None:
1454
+ if existing_remote.updated is not None:
1455
+ existing_sec = existing_remote.updated // 1000
1456
+ if existing_remote.parent_article is not None:
1457
+ existing_parent = existing_remote.parent_article.id_readable
1442
1458
  return PushResult(
1443
1459
  action="update" if is_update else "create",
1444
1460
  article_id=existing_remote.id_readable if existing_remote else "",
@@ -1446,6 +1462,8 @@ class ArticleOperations:
1446
1462
  path=file_path,
1447
1463
  remote_updated_sec=existing_sec,
1448
1464
  dry_run=True,
1465
+ marker_written=False,
1466
+ parent=existing_parent,
1449
1467
  )
1450
1468
 
1451
1469
  # Step 7: build outgoing content. `upstream-updated` is intentionally
@@ -1514,10 +1532,18 @@ class ArticleOperations:
1514
1532
  )
1515
1533
  final_content = render_content(final)
1516
1534
 
1517
- # Step 10: atomic local write-back.
1518
- _atomic_write_text(file_path, final_content)
1535
+ # Step 10: atomic local write-back (skipped for one-way
1536
+ # migration flows that opt out via write_marker=False; the
1537
+ # server payload in Step 7 is unchanged either way).
1538
+ if write_marker:
1539
+ _atomic_write_text(file_path, final_content)
1519
1540
 
1520
1541
  # Step 11: return result.
1542
+ result_parent: str | None = (
1543
+ result_article.parent_article.id_readable
1544
+ if result_article.parent_article is not None
1545
+ else None
1546
+ )
1521
1547
  push_result = PushResult(
1522
1548
  action="update" if is_update else "create",
1523
1549
  article_id=result_article.id_readable,
@@ -1527,6 +1553,8 @@ class ArticleOperations:
1527
1553
  dry_run=False,
1528
1554
  tags_applied=tags_applied,
1529
1555
  tags_failed=tags_failed,
1556
+ marker_written=write_marker,
1557
+ parent=result_parent,
1530
1558
  )
1531
1559
  if tags_failed:
1532
1560
  raise KdError(
@@ -4,6 +4,14 @@ All notable changes to kd-cli are recorded here. The project follows [Semantic V
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.5.2] — 2026-04-19
8
+
9
+ ### Fixed
10
+ - `kd /article push` one-way migrations no longer force `git rm -f`. Before: push always rewrote the local markdown file with a `kd-meta` marker on success, which left the file "locally modified" and made `git rm` refuse without `-f` on migrations that delete the local copy after push. Now: pass `--no-marker-write` to skip the local rewrite. The upstream article is still created or updated, `--tag` values are still applied, and the outgoing server payload still carries a marker with the slug — so scan-by-slug resolves the article on any later push even without the local file. Default behavior is unchanged; the flag is opt-in (KDCLI-110).
11
+
12
+ ### Added
13
+ - `kd /article push` structured output (`-o yaml` / `-o json`) now carries two sibling keys on every push: `marker_written` (`false` for dry-runs and `--no-marker-write` pushes, `true` on the default path after the local rewrite) and `parent` (readable id of the target article's upstream parent, or `null` for root articles). Scripts can assert local-file state and the article's place in the tree from the push response directly (KDCLI-110).
14
+
7
15
  ## [2.5.1] — 2026-04-19
8
16
 
9
17
  ### Fixed
@@ -158,6 +158,8 @@ Use `/article show` for metadata plus rendered content. Use `/article pull` when
158
158
 
159
159
  Do not use push for one-off web edits or broad tree migration. It is single-file by design. If the remote changed, pull, diff, merge by hand, then push again. Use `--force` only after inspecting the remote state.
160
160
 
161
+ For one-way migrations where the local file will be deleted after the push, pass `--no-marker-write`. The server article is still created or updated and tags are still applied, but kd skips the local `kd-meta` rewrite so `git rm` works without `-f`. The structured response gains `marker_written: false`; re-running `--no-marker-write` pays the scan-by-slug cost every invocation because no `upstream-id` is cached locally.
162
+
161
163
  When pushing with `--tag`, kd validates all tags before the article write. Missing tags abort with commands to list the namespace, create the tag, retry without it, or apply the tag separately after push. `--create-tags` is the explicit opt-in for automation that should create missing tags first.
162
164
 
163
165
  `/article create --tag` applies the same preflight: every tag must exist, missing tags abort before the article write (no orphan article on a typo), and the structured response reflects post-tag state so it matches an immediate `/article show` on the returned id. There is no `--create-tags` on `/article create` — curate the namespace with `/tag create` first, or omit `--tag` and apply tags after creation via `/tag add ARTICLE-ID TAG`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.5.1
3
+ Version: 2.5.2
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.5.1"
7
+ version = "2.5.2"
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