atk-cli 0.2.1__tar.gz → 0.3.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 (142) hide show
  1. {atk_cli-0.2.1 → atk_cli-0.3.0}/.gitignore +7 -1
  2. atk_cli-0.3.0/CONTRIBUTING.md +90 -0
  3. atk_cli-0.3.0/Makefile +49 -0
  4. {atk_cli-0.2.1 → atk_cli-0.3.0}/PKG-INFO +1 -1
  5. {atk_cli-0.2.1 → atk_cli-0.3.0}/skills/create-atk-plugin/SKILL.md +16 -0
  6. atk_cli-0.3.0/skills/release/SKILL.md +289 -0
  7. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/cli.py +48 -0
  8. atk_cli-0.3.0/src/atk/commands/doctor.py +75 -0
  9. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/git.py +103 -9
  10. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/init.py +18 -7
  11. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_add.py +16 -10
  12. atk_cli-0.3.0/tests/test_doctor.py +81 -0
  13. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_git.py +133 -16
  14. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_init.py +5 -0
  15. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_remove.py +5 -4
  16. atk_cli-0.2.1/CONTRIBUTING.md +0 -51
  17. atk_cli-0.2.1/Makefile +0 -29
  18. {atk_cli-0.2.1 → atk_cli-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {atk_cli-0.2.1 → atk_cli-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {atk_cli-0.2.1 → atk_cli-0.3.0}/.github/pull_request_template.md +0 -0
  21. {atk_cli-0.2.1 → atk_cli-0.3.0}/.github/workflows/ci.yml +0 -0
  22. {atk_cli-0.2.1 → atk_cli-0.3.0}/.github/workflows/publish.yml +0 -0
  23. {atk_cli-0.2.1 → atk_cli-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  24. {atk_cli-0.2.1 → atk_cli-0.3.0}/LICENSE +0 -0
  25. {atk_cli-0.2.1 → atk_cli-0.3.0}/README.md +0 -0
  26. {atk_cli-0.2.1 → atk_cli-0.3.0}/SECURITY.md +0 -0
  27. {atk_cli-0.2.1 → atk_cli-0.3.0}/assets/demo-hero.gif +0 -0
  28. {atk_cli-0.2.1 → atk_cli-0.3.0}/assets/demo-search.gif +0 -0
  29. {atk_cli-0.2.1 → atk_cli-0.3.0}/assets/demo-status.gif +0 -0
  30. {atk_cli-0.2.1 → atk_cli-0.3.0}/assets/logo.png +0 -0
  31. {atk_cli-0.2.1 → atk_cli-0.3.0}/assets/tapes/demo-hero.tape +0 -0
  32. {atk_cli-0.2.1 → atk_cli-0.3.0}/assets/tapes/demo-search.tape +0 -0
  33. {atk_cli-0.2.1 → atk_cli-0.3.0}/assets/tapes/demo-status.tape +0 -0
  34. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/README.md +0 -0
  35. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/ROADMAP.md +0 -0
  36. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/backlog.md +0 -0
  37. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/legacy/backup-feature-spec.md +0 -0
  38. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/legacy/cli-architecture.md +0 -0
  39. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/legacy/service-yaml-spec.md +0 -0
  40. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/phases/phase-1-core-cli.md +0 -0
  41. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/phases/phase-11-git-sync.md +0 -0
  42. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/phases/phase-2-lifecycle.md +0 -0
  43. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/phases/phase-3-configuration.md +0 -0
  44. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/phases/phase-4-plugin-sources.md +0 -0
  45. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/specs/atk-spec.md +0 -0
  46. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/specs/commands-spec.md +0 -0
  47. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/specs/git-sync-spec.md +0 -0
  48. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/specs/home-spec.md +0 -0
  49. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/specs/mcp-agent-configure-spec.md +0 -0
  50. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/specs/plugin-schema.md +0 -0
  51. {atk_cli-0.2.1 → atk_cli-0.3.0}/docs/specs/registry-spec.md +0 -0
  52. {atk_cli-0.2.1 → atk_cli-0.3.0}/pyproject.toml +0 -0
  53. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/__init__.py +0 -0
  54. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/add.py +0 -0
  55. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/agents/__init__.py +0 -0
  56. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/agents/auggie_skill.py +0 -0
  57. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/agents/claude_skill.py +0 -0
  58. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/agents/codex_skill.py +0 -0
  59. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/agents/gemini_skill.py +0 -0
  60. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/agents/managed_section.py +0 -0
  61. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/agents/opencode_skill.py +0 -0
  62. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/agents/symlink_skill.py +0 -0
  63. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/banner.py +0 -0
  64. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/bootstrap.py +0 -0
  65. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/cli_logger.py +0 -0
  66. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/__init__.py +0 -0
  67. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/lifecycle.py +0 -0
  68. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/mcp.py +0 -0
  69. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/plug.py +0 -0
  70. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/preconditions.py +0 -0
  71. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/run.py +0 -0
  72. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/search.py +0 -0
  73. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/status.py +0 -0
  74. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/commands/upgrade.py +0 -0
  75. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/env.py +0 -0
  76. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/errors.py +0 -0
  77. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/exit_codes.py +0 -0
  78. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/fetch.py +0 -0
  79. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/git_source.py +0 -0
  80. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/home.py +0 -0
  81. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/lifecycle.py +0 -0
  82. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/manifest_schema.py +0 -0
  83. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/mcp.py +0 -0
  84. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/mcp_agents.py +0 -0
  85. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/mcp_configure.py +0 -0
  86. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/plugin.py +0 -0
  87. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/plugin_schema.py +0 -0
  88. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/registry.py +0 -0
  89. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/registry_schema.py +0 -0
  90. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/remove.py +0 -0
  91. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/sanitize.py +0 -0
  92. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/setup.py +0 -0
  93. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/source.py +0 -0
  94. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/update_check.py +0 -0
  95. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/upgrade.py +0 -0
  96. {atk_cli-0.2.1 → atk_cli-0.3.0}/src/atk/validation.py +0 -0
  97. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/__init__.py +0 -0
  98. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/agents/__init__.py +0 -0
  99. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/agents/test_auggie_skill.py +0 -0
  100. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/agents/test_claude_skill.py +0 -0
  101. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/agents/test_codex_skill.py +0 -0
  102. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/agents/test_gemini_skill.py +0 -0
  103. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/agents/test_opencode_skill.py +0 -0
  104. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/agents/test_symlink_skill.py +0 -0
  105. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/commands/__init__.py +0 -0
  106. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/commands/test_cli.py +0 -0
  107. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/commands/test_search.py +0 -0
  108. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/commands/test_status.py +0 -0
  109. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/conftest.py +0 -0
  110. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/full-plugin/README.md +0 -0
  111. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/full-plugin/SKILL.md +0 -0
  112. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/full-plugin/install.sh +0 -0
  113. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/full-plugin/mcp-server.sh +0 -0
  114. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/full-plugin/plugin.yaml +0 -0
  115. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/full-plugin/start.sh +0 -0
  116. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/full-plugin/status.sh +0 -0
  117. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/full-plugin/stop.sh +0 -0
  118. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/invalid-plugin/plugin.yaml +0 -0
  119. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/minimal-plugin/plugin.yaml +0 -0
  120. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/skill-only-plugin/SKILL.md +0 -0
  121. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/fixtures/plugins/skill-only-plugin/plugin.yaml +0 -0
  122. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_env.py +0 -0
  123. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_errors.py +0 -0
  124. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_git_proxy.py +0 -0
  125. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_git_source.py +0 -0
  126. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_home.py +0 -0
  127. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_lifecycle.py +0 -0
  128. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_manifest_schema.py +0 -0
  129. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_mcp.py +0 -0
  130. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_plug.py +0 -0
  131. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_plugin.py +0 -0
  132. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_plugin_schema.py +0 -0
  133. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_registry.py +0 -0
  134. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_repo_status.py +0 -0
  135. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_run.py +0 -0
  136. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_sanitize.py +0 -0
  137. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_setup.py +0 -0
  138. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_source.py +0 -0
  139. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_update_check.py +0 -0
  140. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_upgrade.py +0 -0
  141. {atk_cli-0.2.1 → atk_cli-0.3.0}/tests/test_version.py +0 -0
  142. {atk_cli-0.2.1 → atk_cli-0.3.0}/uv.lock +0 -0
@@ -207,4 +207,10 @@ marimo/_lsp/
207
207
  __marimo__/
208
208
 
209
209
  .idea
210
- img
210
+ img
211
+
212
+ # Derived skill copies (source of truth: skills/)
213
+ .claude/skills/
214
+
215
+ # Codanna index
216
+ .codanna/
@@ -0,0 +1,90 @@
1
+ # Contributing to ATK
2
+
3
+ Thanks for your interest. ATK is an early-stage tool — contributions are welcome.
4
+
5
+ ## Getting started
6
+
7
+ ```bash
8
+ git clone https://github.com/Svtoo/atk
9
+ cd atk
10
+ uv sync --dev
11
+ make install-hooks # Install pre-commit hook
12
+ make sync-skills # Copy skills to agent directories
13
+ ```
14
+
15
+ ## Development workflow
16
+
17
+ ### Pre-commit hook
18
+
19
+ `make install-hooks` installs a git pre-commit hook that:
20
+
21
+ 1. Runs `make check` (ruff lint, mypy type-check, pytest) — commit is blocked if any fail
22
+ 2. Runs `make sync-skills` — copies skills to agent-specific directories
23
+
24
+ The hook is local (`.git/hooks/pre-commit`) and not tracked in git. Run `make install-hooks` after cloning.
25
+
26
+ ### Skills
27
+
28
+ ATK includes skills (structured prompts) for AI coding agents. The source of truth is `skills/<name>/SKILL.md` at the project root. Skills are agent-agnostic — they work with any agent that supports structured prompts.
29
+
30
+ `make sync-skills` copies skills to agent-specific directories where they are discoverable:
31
+
32
+ | Agent | Target directory | Discovery |
33
+ |-------|-----------------|-----------|
34
+ | Claude Code | `.claude/skills/<name>/SKILL.md` | Auto-discovered as `/name` slash commands |
35
+
36
+ The agent directories (`.claude/skills/`) are gitignored — they contain derived copies, not source files. Always edit `skills/<name>/SKILL.md`, never the copies.
37
+
38
+ To add a new skill: create `skills/<your-skill>/SKILL.md` and run `make sync-skills`.
39
+
40
+ ## Making changes
41
+
42
+ - Open an issue first for non-trivial changes.
43
+ - Keep PRs focused — one concern per PR.
44
+ - Match existing code style (ruff + mypy strict).
45
+
46
+ ## Tests
47
+
48
+ All code changes must include automated tests.
49
+
50
+ ```bash
51
+ # Run the full test suite
52
+ uv run pytest
53
+
54
+ # Lint and type-check
55
+ uv run ruff check src tests
56
+ uv run mypy src
57
+
58
+ # All of the above in one command
59
+ make check
60
+ ```
61
+
62
+ If you're adding a command or changing CLI behaviour, also test it manually:
63
+
64
+ ```bash
65
+ uv run atk <your command>
66
+ ```
67
+
68
+ The CI must pass before a PR is merged.
69
+
70
+ ## Releases
71
+
72
+ Releases are managed via the `/release` skill (or `make release` for manual local builds). See `skills/release/SKILL.md` for the full process.
73
+
74
+ Key points:
75
+ - Versioning uses **hatch-vcs** — version is derived from git tags (`vX.Y.Z`), not hardcoded
76
+ - CI runs on push to `main`; publish to PyPI triggers on tag push matching `v*.*.*`
77
+ - PyPI does not allow re-uploads — once a version is published, it is permanent
78
+
79
+ ## Submitting a PR
80
+
81
+ 1. Fork and create a branch from `main`.
82
+ 2. Write or update tests for your change.
83
+ 3. Run `make check` — all must pass.
84
+ 4. Open a PR with a clear description of what changed and why.
85
+
86
+ ## Adding a registry plugin
87
+
88
+ Submit a PR to [atk-registry](https://github.com/Svtoo/atk-registry), not this repo.
89
+ See the registry README for schema requirements.
90
+
atk_cli-0.3.0/Makefile ADDED
@@ -0,0 +1,49 @@
1
+ .PHONY: test check release install-local install-pypi uninstall sync-skills install-hooks
2
+
3
+ # TDD cycle - run often
4
+ test:
5
+ uv run pytest
6
+
7
+ # Pre-commit validation - lint, type check, tests
8
+ check:
9
+ uv run ruff check src tests
10
+ uv run mypy src
11
+ uv run pytest
12
+
13
+ # Sync skills from skills/ to agent-specific directories
14
+ # Source of truth: skills/<name>/SKILL.md
15
+ # Currently supported agents: Claude Code (.claude/skills/)
16
+ sync-skills:
17
+ @mkdir -p .claude/skills
18
+ @for dir in skills/*/; do \
19
+ name=$$(basename "$$dir"); \
20
+ mkdir -p ".claude/skills/$$name"; \
21
+ cp "$$dir"SKILL.md ".claude/skills/$$name/SKILL.md" 2>/dev/null || true; \
22
+ done
23
+ @echo "Skills synced to .claude/skills/"
24
+
25
+ # Install git pre-commit hook that runs check + sync-skills
26
+ install-hooks:
27
+ @echo '#!/bin/sh' > .git/hooks/pre-commit
28
+ @echo 'make check || exit 1' >> .git/hooks/pre-commit
29
+ @echo 'make sync-skills' >> .git/hooks/pre-commit
30
+ @chmod +x .git/hooks/pre-commit
31
+ @echo "Pre-commit hook installed."
32
+
33
+ # Build and publish to PyPI
34
+ release:
35
+ uv build
36
+ uv publish
37
+
38
+ # Install from local source — editable, so changes are live without reinstalling
39
+ install-local:
40
+ uv tool install --editable . --reinstall
41
+
42
+ # Uninstall (works after either install-local or install-pypi)
43
+ uninstall:
44
+ uv tool uninstall atk-cli
45
+
46
+ # Install stable release from PyPI
47
+ install-pypi:
48
+ uv tool install atk-cli --reinstall
49
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atk-cli
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: AI Toolkit - Manage AI development tools through a git-backed, declarative manifest
5
5
  Project-URL: Homepage, https://github.com/Svtoo/atk
6
6
  Project-URL: Repository, https://github.com/Svtoo/atk
@@ -248,6 +248,22 @@ mcp:
248
248
  **Idempotency rule**: Always build from scratch. Always `rm -rf` and fresh clone/install — no conditional
249
249
  "if exists, pull; else clone" logic.
250
250
 
251
+ **External package managers silently skip when already installed — you MUST force re-install.** Bare
252
+ `uv tool install pkg@latest`, `npm install -g pkg@latest`, and `pip install pkg` are no-ops once the
253
+ package is present, even if upstream has shipped a newer version. The user runs `atk install <plugin>`
254
+ expecting an upgrade and gets nothing. Required flags per manager:
255
+
256
+ | Manager | Wrong (silently skips) | Right (always pulls latest) |
257
+ |---|---|---|
258
+ | uv tool | `uv tool install pkg@latest` | `uv tool install pkg@latest --reinstall` |
259
+ | Homebrew | `brew install pkg` | `brew upgrade pkg \|\| brew install pkg` |
260
+ | npm global | `npm install -g pkg@latest` | `npm install -g pkg@latest --force` |
261
+ | pipx | `pipx install pkg` | `pipx install pkg --force` |
262
+ | cargo | `cargo install pkg` | `cargo install pkg --force` |
263
+
264
+ If your install relies on a `curl ... \| sh` install script (codanna-style), that's usually fine —
265
+ those scripts typically replace the binary unconditionally. But verify before shipping.
266
+
251
267
  Use `set -e` in `install.sh`: fail fast on errors.
252
268
 
253
269
  ### start
@@ -0,0 +1,289 @@
1
+ ---
2
+ name: release
3
+ description: Create a new ATK release. Handles pre-flight checks, changelog generation, tagging, pushing, CI verification, and PyPI publication confirmation. Use when asked to cut a release, publish a new version, or ship changes.
4
+ argument-hint: "[version, e.g. 0.3.0]"
5
+ ---
6
+
7
+ # ATK Release Process
8
+
9
+ You are performing a release of the `atk-cli` package. This is a critical operation — follow every step in order. Do not skip steps. Do not assume success — verify it.
10
+
11
+ ## Versioning Strategy
12
+
13
+ ATK uses **hatch-vcs**: the version is derived from git tags at build time, not hardcoded.
14
+
15
+ - Tags follow `vX.Y.Z` (e.g., `v0.2.1`)
16
+ - **Patch** (`Z`): bug fixes, small features, non-breaking changes
17
+ - **Minor** (`Y`): new user-facing features, new commands, behavioral changes
18
+ - **Major** (`X`): breaking changes to CLI interface, manifest schema, or plugin API
19
+ - Pre-`v1.0.0`: minor bumps are for significant milestones; patches are the norm
20
+
21
+ PyPI receives the version without the `v` prefix (e.g., `0.2.1`).
22
+
23
+ **CRITICAL**: PyPI does not allow re-uploads. If a version is published, it is permanent. Never force-move a tag that has been pushed. If a mistake is published, bump to the next patch.
24
+
25
+ ---
26
+
27
+ ## Step 0: Determine Version
28
+
29
+ If version was provided as argument, use `v$ARGUMENTS`. Otherwise:
30
+
31
+ 1. Run `git tag --sort=-v:refname | head -1` to find the current latest tag
32
+ 2. Run `git log <latest-tag>..HEAD --oneline` to see what changed
33
+ 3. Classify the changes:
34
+ - Bug fixes only → **patch bump**
35
+ - New features, new commands → **minor bump**
36
+ - Breaking CLI/schema changes → **major bump**
37
+ 4. Present the proposed version to Sasha with reasoning. Wait for confirmation before proceeding.
38
+
39
+ ---
40
+
41
+ ## Step 1: Pre-Flight Checks
42
+
43
+ Run ALL of these checks. If ANY fail, stop and report.
44
+
45
+ ### 1a. Clean working tree
46
+
47
+ ```bash
48
+ git status --short
49
+ ```
50
+
51
+ EXPECT: Empty output (no uncommitted changes). If dirty, stop — all changes must be committed before release.
52
+
53
+ ### 1b. On main branch
54
+
55
+ ```bash
56
+ git branch --show-current
57
+ ```
58
+
59
+ EXPECT: `main`. Releases are only cut from main.
60
+
61
+ ### 1c. Up to date with remote
62
+
63
+ ```bash
64
+ git fetch origin main
65
+ git rev-list HEAD..origin/main --count
66
+ ```
67
+
68
+ EXPECT: `0` (no commits on origin/main that we don't have). If behind, stop — pull first.
69
+
70
+ ### 1d. Local checks pass
71
+
72
+ ```bash
73
+ make check
74
+ ```
75
+
76
+ EXPECT: Exit 0. This runs ruff (lint), mypy (types), and pytest (tests). All must be green.
77
+
78
+ ### 1e. CI is green on HEAD
79
+
80
+ ```bash
81
+ gh run list --branch main --limit 3
82
+ ```
83
+
84
+ EXPECT: The most recent CI run for HEAD is `completed` / `success`. If CI hasn't run for the current HEAD yet, wait or trigger it.
85
+
86
+ ### 1f. No open blockers
87
+
88
+ Check if there are any issues/PRs that should block this release:
89
+
90
+ ```bash
91
+ gh issue list --state open --label "blocker" --limit 5
92
+ ```
93
+
94
+ If blockers exist, report them and ask Sasha whether to proceed.
95
+
96
+ ---
97
+
98
+ ## Step 2: Changelog Analysis
99
+
100
+ Generate a thorough changelog by analyzing what changed since the last release.
101
+
102
+ ### 2a. Identify the last release
103
+
104
+ ```bash
105
+ git tag --sort=-v:refname | head -1
106
+ ```
107
+
108
+ ### 2b. List all commits since last release
109
+
110
+ ```bash
111
+ git log <last-tag>..HEAD --oneline --no-merges
112
+ ```
113
+
114
+ ### 2c. Detailed diff summary
115
+
116
+ ```bash
117
+ git diff <last-tag>..HEAD --stat
118
+ ```
119
+
120
+ ### 2d. Categorize changes
121
+
122
+ Read each commit and the diff. Organize into categories:
123
+
124
+ - **New Features**: new commands, new config options, new behaviors
125
+ - **Bug Fixes**: corrections to existing behavior
126
+ - **Breaking Changes**: anything that changes existing CLI interface, manifest format, or plugin contract
127
+ - **Documentation**: spec updates, roadmap changes, new phase docs
128
+ - **Internal**: refactors, test improvements, CI changes
129
+
130
+ ### 2e. Draft release notes
131
+
132
+ Write release notes in this format:
133
+
134
+ ```markdown
135
+ ## What's New
136
+
137
+ ### <Feature Name>
138
+ <2-3 sentence description with usage example>
139
+
140
+ ### <Feature Name>
141
+ ...
142
+
143
+ ## Bug Fixes
144
+ - <one-liner per fix>
145
+
146
+ ## Breaking Changes
147
+ - <one-liner with migration guidance>
148
+
149
+ ## Upgrade
150
+
151
+ \```bash
152
+ pip install --upgrade atk-cli
153
+ # or
154
+ uv tool upgrade atk-cli
155
+ \```
156
+
157
+ <Any migration notes if needed>
158
+ ```
159
+
160
+ Present the draft to Sasha for review before proceeding.
161
+
162
+ ---
163
+
164
+ ## Step 3: Manual Testing
165
+
166
+ Before tagging, manually verify the key changes work. This is NOT optional.
167
+
168
+ For each new feature or significant change:
169
+ 1. State what you're testing and why
170
+ 2. Run the actual command against a real (or temporary) ATK Home
171
+ 3. Verify the output matches expectations
172
+ 4. Report PASS or FAIL with evidence
173
+
174
+ At minimum, always test:
175
+ - `atk --version` outputs a valid version
176
+ - `atk status` works without errors (if ATK Home exists)
177
+
178
+ ---
179
+
180
+ ## Step 4: Tag and Push
181
+
182
+ Only proceed here if all previous steps passed and Sasha approved the release notes.
183
+
184
+ ### 4a. Create the tag
185
+
186
+ ```bash
187
+ git tag v<VERSION>
188
+ ```
189
+
190
+ ### 4b. Push the commit and tag
191
+
192
+ ```bash
193
+ git push origin main
194
+ git push origin v<VERSION>
195
+ ```
196
+
197
+ The tag push triggers the Publish to PyPI workflow automatically.
198
+
199
+ ---
200
+
201
+ ## Step 5: Wait for CI
202
+
203
+ Both workflows must succeed before the release is complete.
204
+
205
+ ### 5a. Monitor CI workflow (triggered by push to main)
206
+
207
+ ```bash
208
+ gh run list --branch main --limit 3
209
+ ```
210
+
211
+ Wait until the CI run for the current commit shows `completed` / `success`.
212
+
213
+ ### 5b. Monitor Publish workflow (triggered by tag push)
214
+
215
+ ```bash
216
+ gh run list --workflow publish.yml --limit 3
217
+ ```
218
+
219
+ Wait until the publish run shows `completed` / `success`.
220
+
221
+ If either workflow fails:
222
+ 1. Check logs: `gh run view <run-id> --log | tail -50`
223
+ 2. Report the failure to Sasha
224
+ 3. Do NOT proceed to creating the GitHub release
225
+ 4. If the publish failed, the tag is "burned" — PyPI may or may not have the version. Check with `curl -s https://pypi.org/pypi/atk-cli/<VERSION>/json` before deciding next steps.
226
+
227
+ ---
228
+
229
+ ## Step 6: Verify PyPI Publication
230
+
231
+ ```bash
232
+ curl -s https://pypi.org/pypi/atk-cli/<VERSION>/json | python3 -c "import sys,json; d=json.load(sys.stdin); print('Version:', d['info']['version']); print('Summary:', d['info']['summary'])"
233
+ ```
234
+
235
+ EXPECT: Version matches the release version. If this fails, the publish did not actually land on PyPI — investigate before creating the GitHub release.
236
+
237
+ ---
238
+
239
+ ## Step 7: Create GitHub Release
240
+
241
+ ```bash
242
+ gh release create v<VERSION> --title "v<VERSION> — <Short Title>" --notes "<release notes from Step 2>"
243
+ ```
244
+
245
+ Use a HEREDOC for multi-line notes:
246
+
247
+ ```bash
248
+ gh release create v<VERSION> --title "v<VERSION> — <Title>" --notes "$(cat <<'EOF'
249
+ <release notes here>
250
+ EOF
251
+ )"
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Step 8: Post-Release Verification
257
+
258
+ ### 8a. Verify the release page exists
259
+
260
+ ```bash
261
+ gh release view v<VERSION>
262
+ ```
263
+
264
+ ### 8b. Verify installability
265
+
266
+ ```bash
267
+ uv pip install atk-cli==<VERSION> --dry-run
268
+ ```
269
+
270
+ ### 8c. Report completion
271
+
272
+ Summarize to Sasha:
273
+ - Version released
274
+ - PyPI URL: `https://pypi.org/project/atk-cli/<VERSION>/`
275
+ - GitHub release URL: `https://github.com/Svtoo/atk/releases/tag/v<VERSION>`
276
+ - CI status: all green
277
+ - Publish status: confirmed on PyPI
278
+
279
+ ---
280
+
281
+ ## Failure Recovery
282
+
283
+ | Failure | Recovery |
284
+ |---------|----------|
285
+ | CI fails after tag push | Fix the issue, push fix to main, bump to next patch, re-tag |
286
+ | Publish fails but PyPI has the version | The version is burned — create GitHub release as-is |
287
+ | Publish fails and PyPI does NOT have it | Fix issue, delete the tag (`git tag -d v<X> && git push origin :refs/tags/v<X>`), re-tag same commit |
288
+ | Wrong content released | Cannot re-upload to PyPI. Bump patch, fix, release again |
289
+ | Tag pushed to wrong commit | If not yet on PyPI: delete remote tag, re-tag correct commit. If on PyPI: bump patch |
@@ -12,6 +12,7 @@ from rich.markdown import Markdown
12
12
  from atk import __version__, cli_logger, exit_codes
13
13
  from atk.add import AddCancelledError, InstallFailedError, add_plugin
14
14
  from atk.banner import print_banner
15
+ from atk.commands.doctor import run_doctor
15
16
  from atk.commands.lifecycle import run_lifecycle_cli, run_restart_single_cli, run_uninstall_cli
16
17
  from atk.commands.plug import plug_plugin, unplug_plugin
17
18
  from atk.commands.preconditions import (
@@ -361,6 +362,53 @@ def setup(
361
362
  raise typer.Exit(exit_codes.SUCCESS)
362
363
 
363
364
 
365
+ @app.command()
366
+ def doctor() -> None:
367
+ """Repair ATK Home: keep secrets gitignored and untrack any that leaked.
368
+
369
+ Fixes the historical bug where a local plugin's .env could be tracked
370
+ because its .gitignore exemption was ordered after the secret rule.
371
+ Safe to run anytime; idempotent.
372
+ """
373
+ atk_home = require_initialized_home()
374
+ manifest = load_manifest(atk_home)
375
+ if manifest.config.auto_commit:
376
+ require_git()
377
+
378
+ result = run_doctor(
379
+ atk_home,
380
+ auto_commit=manifest.config.auto_commit,
381
+ auto_push=manifest.config.auto_push,
382
+ )
383
+
384
+ if result.gitignore_fixed:
385
+ cli_logger.success("Re-ordered .gitignore so secret rules stay last")
386
+ else:
387
+ cli_logger.info(".gitignore already keeps secrets last")
388
+
389
+ if result.untracked_secrets:
390
+ cli_logger.warning(
391
+ f"Untracked {len(result.untracked_secrets)} secret file(s) "
392
+ "(kept on disk):"
393
+ )
394
+ for secret in result.untracked_secrets:
395
+ cli_logger.dim(f" • {secret}")
396
+ cli_logger.warning(
397
+ "These were committed before — ROTATE the affected keys, and note "
398
+ "they remain in git history (rewrite history if you need them gone)."
399
+ )
400
+
401
+ if result.committed:
402
+ cli_logger.success("Committed the repair")
403
+ elif (result.gitignore_fixed or result.untracked_secrets) and not manifest.config.auto_commit:
404
+ cli_logger.info("Changes staged — commit them when ready (auto_commit is off)")
405
+
406
+ if not result.gitignore_fixed and not result.untracked_secrets:
407
+ cli_logger.success("Nothing to fix — ATK Home is healthy")
408
+
409
+ raise typer.Exit(exit_codes.SUCCESS)
410
+
411
+
364
412
  @app.command()
365
413
  def mcp(
366
414
  plugin: Annotated[
@@ -0,0 +1,75 @@
1
+ """`atk doctor` — repair an ATK Home's .gitignore and untrack leaked secrets.
2
+
3
+ Historical bug: per-plugin `!plugins/<name>/**` exemptions were appended AFTER
4
+ the `*.env` rule. Because .gitignore is last-match-wins, those exemptions
5
+ re-included plugin .env files, which `atk add`'s auto-commit then committed.
6
+
7
+ This migration re-orders the secret rules last (idempotent) and untracks any
8
+ secret files that already slipped into the repo. It does NOT rotate keys or
9
+ rewrite history — both are surfaced to the user as follow-ups.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+
17
+ from atk.git import (
18
+ git_add,
19
+ git_commit,
20
+ git_push,
21
+ git_rm_cached,
22
+ list_tracked_secrets,
23
+ normalize_gitignore,
24
+ )
25
+
26
+
27
+ @dataclass
28
+ class DoctorResult:
29
+ """Outcome of a doctor run, for the CLI to report."""
30
+
31
+ gitignore_fixed: bool
32
+ untracked_secrets: list[str] = field(default_factory=list)
33
+ committed: bool = False
34
+
35
+
36
+ def run_doctor(
37
+ atk_home: Path,
38
+ *,
39
+ auto_commit: bool,
40
+ auto_push: bool = False,
41
+ ) -> DoctorResult:
42
+ """Repair the ATK Home .gitignore and untrack any committed secret files.
43
+
44
+ Order matters: normalise the .gitignore FIRST so the secret files become
45
+ ignored, THEN `git rm --cached` them — that way the subsequent stage can't
46
+ re-add a now-ignored file. Commits the repair when auto_commit is on,
47
+ mirroring the add/remove commands.
48
+
49
+ Args:
50
+ atk_home: Path to the (initialized) ATK Home directory.
51
+ auto_commit: Whether to commit the repair (from manifest config).
52
+ auto_push: Whether to push after committing (subordinate to commit).
53
+
54
+ Returns:
55
+ DoctorResult describing what changed.
56
+ """
57
+ gitignore_fixed = normalize_gitignore(atk_home)
58
+
59
+ tracked_secrets = list_tracked_secrets(atk_home)
60
+ if tracked_secrets:
61
+ git_rm_cached(atk_home, tracked_secrets)
62
+ if gitignore_fixed:
63
+ git_add(atk_home, [".gitignore"])
64
+
65
+ committed = False
66
+ if (gitignore_fixed or tracked_secrets) and auto_commit:
67
+ committed = git_commit(atk_home, "atk doctor: keep secrets out of git")
68
+ if committed and auto_push:
69
+ git_push(atk_home)
70
+
71
+ return DoctorResult(
72
+ gitignore_fixed=gitignore_fixed,
73
+ untracked_secrets=tracked_secrets,
74
+ committed=committed,
75
+ )