specify-cli 0.16.0__tar.gz → 0.16.1__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 (532) hide show
  1. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/codeql.yml +2 -2
  2. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/lint.yml +1 -1
  3. {specify_cli-0.16.0 → specify_cli-0.16.1}/CHANGELOG.md +26 -0
  4. {specify_cli-0.16.0 → specify_cli-0.16.1}/PKG-INFO +1 -1
  5. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/core.md +2 -0
  6. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/agent-context/scripts/python/update_agent_context.py +26 -11
  7. {specify_cli-0.16.0 → specify_cli-0.16.1}/pyproject.toml +1 -1
  8. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/python/check_prerequisites.py +21 -4
  9. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/python/setup_tasks.py +21 -4
  10. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_download_security.py +21 -3
  11. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_utils.py +2 -2
  12. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/commands/bundle/__init__.py +11 -1
  13. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/commands/init.py +64 -16
  14. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/events.py +9 -1
  15. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/extensions/__init__.py +11 -7
  16. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/catalog.py +10 -1
  17. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/hermes/__init__.py +37 -7
  18. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/kimi/__init__.py +13 -3
  19. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/manifest.py +1 -2
  20. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/presets/__init__.py +20 -3
  21. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/shared_infra.py +47 -2
  22. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/_commands.py +6 -0
  23. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/test_update_agent_context_python_parity.py +69 -33
  24. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integration/test_bundler_local_install.py +44 -0
  25. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_cli.py +93 -0
  26. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_events.py +47 -0
  27. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_base_markdown.py +1 -0
  28. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_base_skills.py +86 -0
  29. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_base_toml.py +1 -0
  30. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_base_yaml.py +1 -0
  31. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_catalog.py +202 -128
  32. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_cline.py +1 -0
  33. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_copilot.py +3 -0
  34. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_generic.py +2 -0
  35. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_skill_frontmatter_quoting.py +63 -0
  36. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_check_prerequisites_python_parity.py +46 -0
  37. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_download_security.py +188 -0
  38. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_extensions.py +25 -0
  39. specify_cli-0.16.1/tests/test_init_output_markup.py +176 -0
  40. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_presets.py +107 -0
  41. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_setup_tasks_python_parity.py +25 -0
  42. specify_cli-0.16.1/tests/test_shared_infra_gitignore.py +103 -0
  43. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_workflows.py +51 -0
  44. {specify_cli-0.16.0 → specify_cli-0.16.1}/.devcontainer/devcontainer.json +0 -0
  45. {specify_cli-0.16.0 → specify_cli-0.16.1}/.devcontainer/post-create.sh +0 -0
  46. {specify_cli-0.16.0 → specify_cli-0.16.1}/.editorconfig +0 -0
  47. {specify_cli-0.16.0 → specify_cli-0.16.1}/.gitattributes +0 -0
  48. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/CODEOWNERS +0 -0
  49. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/ISSUE_TEMPLATE/agent_request.yml +0 -0
  50. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  51. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/ISSUE_TEMPLATE/bundle_submission.yml +0 -0
  52. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  53. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/ISSUE_TEMPLATE/extension_submission.yml +0 -0
  54. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  55. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/ISSUE_TEMPLATE/preset_submission.yml +0 -0
  56. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  57. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/aw/actions-lock.json +0 -0
  58. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/dependabot.yml +0 -0
  59. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/scripts/check_security_requirements.py +0 -0
  60. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/security-audit-requirements.txt +0 -0
  61. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/skills/add-community-extension/SKILL.md +0 -0
  62. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/RELEASE-PROCESS.md +0 -0
  63. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/add-community-bundle.lock.yml +0 -0
  64. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/add-community-bundle.md +0 -0
  65. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/add-community-extension.lock.yml +0 -0
  66. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/add-community-extension.md +0 -0
  67. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/add-community-preset.lock.yml +0 -0
  68. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/add-community-preset.md +0 -0
  69. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/bug-assess.lock.yml +0 -0
  70. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/bug-assess.md +0 -0
  71. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/bug-fix.lock.yml +0 -0
  72. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/bug-fix.md +0 -0
  73. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/bug-test.lock.yml +0 -0
  74. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/bug-test.md +0 -0
  75. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/catalog-assign.yml +0 -0
  76. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/docs.yml +0 -0
  77. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/publish-pypi.yml +0 -0
  78. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/release-trigger.yml +0 -0
  79. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/release.yml +0 -0
  80. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/security.yml +0 -0
  81. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/stale.yml +0 -0
  82. {specify_cli-0.16.0 → specify_cli-0.16.1}/.github/workflows/test.yml +0 -0
  83. {specify_cli-0.16.0 → specify_cli-0.16.1}/.gitignore +0 -0
  84. {specify_cli-0.16.0 → specify_cli-0.16.1}/.markdownlint-cli2.jsonc +0 -0
  85. {specify_cli-0.16.0 → specify_cli-0.16.1}/.pre-commit-config.yaml +0 -0
  86. {specify_cli-0.16.0 → specify_cli-0.16.1}/.zenodo.json +0 -0
  87. {specify_cli-0.16.0 → specify_cli-0.16.1}/AGENTS.md +0 -0
  88. {specify_cli-0.16.0 → specify_cli-0.16.1}/CITATION.cff +0 -0
  89. {specify_cli-0.16.0 → specify_cli-0.16.1}/CODE_OF_CONDUCT.md +0 -0
  90. {specify_cli-0.16.0 → specify_cli-0.16.1}/CONTRIBUTING.md +0 -0
  91. {specify_cli-0.16.0 → specify_cli-0.16.1}/DEVELOPMENT.md +0 -0
  92. {specify_cli-0.16.0 → specify_cli-0.16.1}/LICENSE +0 -0
  93. {specify_cli-0.16.0 → specify_cli-0.16.1}/README.md +0 -0
  94. {specify_cli-0.16.0 → specify_cli-0.16.1}/README.zh-CN.md +0 -0
  95. {specify_cli-0.16.0 → specify_cli-0.16.1}/SECURITY.md +0 -0
  96. {specify_cli-0.16.0 → specify_cli-0.16.1}/SUPPORT.md +0 -0
  97. {specify_cli-0.16.0 → specify_cli-0.16.1}/bundles/catalog.community.json +0 -0
  98. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/.gitignore +0 -0
  99. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/README.md +0 -0
  100. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/community/bundles.md +0 -0
  101. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/community/extensions.md +0 -0
  102. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/community/friends.md +0 -0
  103. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/community/overview.md +0 -0
  104. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/community/presets.md +0 -0
  105. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/community/walkthroughs.md +0 -0
  106. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/concepts/complex-features.md +0 -0
  107. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/concepts/sdd.md +0 -0
  108. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/concepts/spec-of-specs.md +0 -0
  109. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/concepts/spec-persistence.md +0 -0
  110. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/docfx.json +0 -0
  111. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/guides/evolving-specs.md +0 -0
  112. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/guides/monorepo.md +0 -0
  113. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/index.md +0 -0
  114. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/install/air-gapped.md +0 -0
  115. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/install/one-time.md +0 -0
  116. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/install/pipx.md +0 -0
  117. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/install/pypi.md +0 -0
  118. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/install/uv.md +0 -0
  119. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/installation.md +0 -0
  120. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/local-development.md +0 -0
  121. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/quickstart.md +0 -0
  122. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/agentic-bugfix.md +0 -0
  123. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/agentic-sdd.md +0 -0
  124. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/authentication.md +0 -0
  125. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/bundles.md +0 -0
  126. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/extensions.md +0 -0
  127. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/integrations.md +0 -0
  128. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/overview.md +0 -0
  129. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/presets.md +0 -0
  130. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/reference/workflows.md +0 -0
  131. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/template/public/main.css +0 -0
  132. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/toc.yml +0 -0
  133. {specify_cli-0.16.0 → specify_cli-0.16.1}/docs/upgrade.md +0 -0
  134. {specify_cli-0.16.0 → specify_cli-0.16.1}/examples/bundles/business-analyst/README.md +0 -0
  135. {specify_cli-0.16.0 → specify_cli-0.16.1}/examples/bundles/business-analyst/bundle.yml +0 -0
  136. {specify_cli-0.16.0 → specify_cli-0.16.1}/examples/bundles/developer/README.md +0 -0
  137. {specify_cli-0.16.0 → specify_cli-0.16.1}/examples/bundles/developer/bundle.yml +0 -0
  138. {specify_cli-0.16.0 → specify_cli-0.16.1}/examples/bundles/product-manager/README.md +0 -0
  139. {specify_cli-0.16.0 → specify_cli-0.16.1}/examples/bundles/product-manager/bundle.yml +0 -0
  140. {specify_cli-0.16.0 → specify_cli-0.16.1}/examples/bundles/security-researcher/README.md +0 -0
  141. {specify_cli-0.16.0 → specify_cli-0.16.1}/examples/bundles/security-researcher/bundle.yml +0 -0
  142. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/EXTENSION-API-REFERENCE.md +0 -0
  143. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/EXTENSION-DEVELOPMENT-GUIDE.md +0 -0
  144. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/EXTENSION-PUBLISHING-GUIDE.md +0 -0
  145. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/EXTENSION-USER-GUIDE.md +0 -0
  146. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/README.md +0 -0
  147. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/RFC-EXTENSION-SYSTEM.md +0 -0
  148. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/agent-context/README.md +0 -0
  149. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/agent-context/agent-context-config.yml +0 -0
  150. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/agent-context/agent-context-defaults.json +0 -0
  151. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/agent-context/commands/speckit.agent-context.update.md +0 -0
  152. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/agent-context/extension.yml +0 -0
  153. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/agent-context/scripts/bash/update-agent-context.sh +0 -0
  154. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +0 -0
  155. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/assess/README.md +0 -0
  156. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/assess/commands/speckit.assess.decide.md +0 -0
  157. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/assess/commands/speckit.assess.define.md +0 -0
  158. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/assess/commands/speckit.assess.intake.md +0 -0
  159. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/assess/commands/speckit.assess.research.md +0 -0
  160. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/assess/commands/speckit.assess.shape.md +0 -0
  161. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/assess/extension.yml +0 -0
  162. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/bug/README.md +0 -0
  163. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/bug/commands/speckit.bug.assess.md +0 -0
  164. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/bug/commands/speckit.bug.fix.md +0 -0
  165. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/bug/commands/speckit.bug.test.md +0 -0
  166. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/bug/extension.yml +0 -0
  167. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/catalog.community.json +0 -0
  168. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/catalog.json +0 -0
  169. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/README.md +0 -0
  170. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/commands/speckit.git.commit.md +0 -0
  171. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/commands/speckit.git.feature.md +0 -0
  172. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/commands/speckit.git.initialize.md +0 -0
  173. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/commands/speckit.git.remote.md +0 -0
  174. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/commands/speckit.git.validate.md +0 -0
  175. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/config-template.yml +0 -0
  176. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/extension.yml +0 -0
  177. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/git-config.yml +0 -0
  178. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/bash/auto-commit.sh +0 -0
  179. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/bash/create-new-feature-branch.sh +0 -0
  180. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/bash/git-common.sh +0 -0
  181. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/bash/initialize-repo.sh +0 -0
  182. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
  183. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +0 -0
  184. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/powershell/git-common.ps1 +0 -0
  185. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
  186. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/python/auto_commit.py +0 -0
  187. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/python/create_new_feature_branch.py +0 -0
  188. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/python/git_common.py +0 -0
  189. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/git/scripts/python/initialize_repo.py +0 -0
  190. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/selftest/commands/selftest.md +0 -0
  191. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/selftest/extension.yml +0 -0
  192. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/template/.gitignore +0 -0
  193. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/template/CHANGELOG.md +0 -0
  194. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/template/EXAMPLE-README.md +0 -0
  195. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/template/LICENSE +0 -0
  196. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/template/README.md +0 -0
  197. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/template/commands/example.md +0 -0
  198. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/template/config-template.yml +0 -0
  199. {specify_cli-0.16.0 → specify_cli-0.16.1}/extensions/template/extension.yml +0 -0
  200. {specify_cli-0.16.0 → specify_cli-0.16.1}/integrations/CONTRIBUTING.md +0 -0
  201. {specify_cli-0.16.0 → specify_cli-0.16.1}/integrations/README.md +0 -0
  202. {specify_cli-0.16.0 → specify_cli-0.16.1}/integrations/catalog.community.json +0 -0
  203. {specify_cli-0.16.0 → specify_cli-0.16.1}/integrations/catalog.json +0 -0
  204. {specify_cli-0.16.0 → specify_cli-0.16.1}/media/bootstrap-claude-code.gif +0 -0
  205. {specify_cli-0.16.0 → specify_cli-0.16.1}/media/logo_large.webp +0 -0
  206. {specify_cli-0.16.0 → specify_cli-0.16.1}/media/logo_small.webp +0 -0
  207. {specify_cli-0.16.0 → specify_cli-0.16.1}/media/spec-kit-video-header.jpg +0 -0
  208. {specify_cli-0.16.0 → specify_cli-0.16.1}/media/specify_cli.gif +0 -0
  209. {specify_cli-0.16.0 → specify_cli-0.16.1}/newsletters/2026-April.md +0 -0
  210. {specify_cli-0.16.0 → specify_cli-0.16.1}/newsletters/2026-February.md +0 -0
  211. {specify_cli-0.16.0 → specify_cli-0.16.1}/newsletters/2026-July.md +0 -0
  212. {specify_cli-0.16.0 → specify_cli-0.16.1}/newsletters/2026-June.md +0 -0
  213. {specify_cli-0.16.0 → specify_cli-0.16.1}/newsletters/2026-March.md +0 -0
  214. {specify_cli-0.16.0 → specify_cli-0.16.1}/newsletters/2026-May.md +0 -0
  215. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/ARCHITECTURE.md +0 -0
  216. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/PUBLISHING.md +0 -0
  217. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/README.md +0 -0
  218. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/catalog.community.json +0 -0
  219. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/catalog.json +0 -0
  220. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/constitution-sync/README.md +0 -0
  221. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/constitution-sync/commands/speckit.constitution.md +0 -0
  222. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/constitution-sync/preset.yml +0 -0
  223. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/lean/README.md +0 -0
  224. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/lean/commands/speckit.constitution.md +0 -0
  225. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/lean/commands/speckit.implement.md +0 -0
  226. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/lean/commands/speckit.plan.md +0 -0
  227. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/lean/commands/speckit.specify.md +0 -0
  228. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/lean/commands/speckit.tasks.md +0 -0
  229. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/lean/preset.yml +0 -0
  230. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/scaffold/README.md +0 -0
  231. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/scaffold/commands/speckit.myext.myextcmd.md +0 -0
  232. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/scaffold/commands/speckit.specify.md +0 -0
  233. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/scaffold/preset.yml +0 -0
  234. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/scaffold/templates/myext-template.md +0 -0
  235. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/scaffold/templates/spec-template.md +0 -0
  236. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/self-test/commands/speckit.specify.md +0 -0
  237. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/self-test/commands/speckit.wrap-test.md +0 -0
  238. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/self-test/preset.yml +0 -0
  239. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/self-test/templates/checklist-template.md +0 -0
  240. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/self-test/templates/constitution-template.md +0 -0
  241. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/self-test/templates/plan-template.md +0 -0
  242. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/self-test/templates/spec-template.md +0 -0
  243. {specify_cli-0.16.0 → specify_cli-0.16.1}/presets/self-test/templates/tasks-template.md +0 -0
  244. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/bash/check-prerequisites.sh +0 -0
  245. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/bash/common.sh +0 -0
  246. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/bash/create-new-feature.sh +0 -0
  247. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/bash/setup-plan.sh +0 -0
  248. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/bash/setup-tasks.sh +0 -0
  249. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/powershell/check-prerequisites.ps1 +0 -0
  250. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/powershell/common.ps1 +0 -0
  251. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/powershell/create-new-feature.ps1 +0 -0
  252. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/powershell/setup-plan.ps1 +0 -0
  253. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/powershell/setup-tasks.ps1 +0 -0
  254. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/python/common.py +0 -0
  255. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/python/create_new_feature.py +0 -0
  256. {specify_cli-0.16.0 → specify_cli-0.16.1}/scripts/python/setup_plan.py +0 -0
  257. {specify_cli-0.16.0 → specify_cli-0.16.1}/spec-driven.md +0 -0
  258. {specify_cli-0.16.0 → specify_cli-0.16.1}/spec-kit.code-workspace +0 -0
  259. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/__init__.py +0 -0
  260. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_agent_config.py +0 -0
  261. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_assets.py +0 -0
  262. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_console.py +0 -0
  263. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_github_http.py +0 -0
  264. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_init_options.py +0 -0
  265. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_invocation_style.py +0 -0
  266. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_project.py +0 -0
  267. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_toml_string.py +0 -0
  268. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/_version.py +0 -0
  269. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/agents.py +0 -0
  270. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/authentication/__init__.py +0 -0
  271. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/authentication/azure_devops.py +0 -0
  272. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/authentication/base.py +0 -0
  273. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/authentication/config.py +0 -0
  274. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/authentication/github.py +0 -0
  275. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/authentication/http.py +0 -0
  276. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/__init__.py +0 -0
  277. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/commands_impl/__init__.py +0 -0
  278. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/commands_impl/catalog_config.py +0 -0
  279. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/lib/__init__.py +0 -0
  280. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/lib/project.py +0 -0
  281. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/lib/versioning.py +0 -0
  282. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/lib/yamlio.py +0 -0
  283. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/models/__init__.py +0 -0
  284. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/models/catalog.py +0 -0
  285. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/models/manifest.py +0 -0
  286. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/models/records.py +0 -0
  287. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/__init__.py +0 -0
  288. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/adapters.py +0 -0
  289. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/catalog_stack.py +0 -0
  290. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/conflict.py +0 -0
  291. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/installer.py +0 -0
  292. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/packager.py +0 -0
  293. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/primitives.py +0 -0
  294. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/references.py +0 -0
  295. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/resolver.py +0 -0
  296. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/bundler/services/validator.py +0 -0
  297. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/catalogs.py +0 -0
  298. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/commands/__init__.py +0 -0
  299. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/commands/event.py +0 -0
  300. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/extensions/_commands.py +0 -0
  301. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integration_runtime.py +0 -0
  302. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integration_scaffold.py +0 -0
  303. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integration_state.py +0 -0
  304. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integration_status.py +0 -0
  305. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/__init__.py +0 -0
  306. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/_commands.py +0 -0
  307. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/_helpers.py +0 -0
  308. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/_install_commands.py +0 -0
  309. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/_migrate_commands.py +0 -0
  310. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/_query_commands.py +0 -0
  311. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/_scaffold_commands.py +0 -0
  312. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/agy/__init__.py +0 -0
  313. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/alquimia/__init__.py +0 -0
  314. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/amp/__init__.py +0 -0
  315. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/auggie/__init__.py +0 -0
  316. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/base.py +0 -0
  317. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/bob/__init__.py +0 -0
  318. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/claude/__init__.py +0 -0
  319. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/cline/__init__.py +0 -0
  320. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/codebuddy/__init__.py +0 -0
  321. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/codex/__init__.py +0 -0
  322. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/copilot/__init__.py +0 -0
  323. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/cursor_agent/__init__.py +0 -0
  324. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/devin/__init__.py +0 -0
  325. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/droid/__init__.py +0 -0
  326. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/firebender/__init__.py +0 -0
  327. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/forge/__init__.py +0 -0
  328. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/gemini/__init__.py +0 -0
  329. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/generic/__init__.py +0 -0
  330. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/goose/__init__.py +0 -0
  331. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/grok/__init__.py +0 -0
  332. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/junie/__init__.py +0 -0
  333. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/kilocode/__init__.py +0 -0
  334. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/kiro_cli/__init__.py +0 -0
  335. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/lingma/__init__.py +0 -0
  336. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/omp/__init__.py +0 -0
  337. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/opencode/__init__.py +0 -0
  338. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/pi/__init__.py +0 -0
  339. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/qodercli/__init__.py +0 -0
  340. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/qwen/__init__.py +0 -0
  341. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/rovodev/__init__.py +0 -0
  342. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/shai/__init__.py +0 -0
  343. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/tabnine/__init__.py +0 -0
  344. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/trae/__init__.py +0 -0
  345. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/vibe/__init__.py +0 -0
  346. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/zcode/__init__.py +0 -0
  347. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/integrations/zed/__init__.py +0 -0
  348. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/presets/_commands.py +0 -0
  349. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/__init__.py +0 -0
  350. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/base.py +0 -0
  351. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/catalog.py +0 -0
  352. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/engine.py +0 -0
  353. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/expressions.py +0 -0
  354. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/overlays/__init__.py +0 -0
  355. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/overlays/_commands.py +0 -0
  356. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/overlays/composer.py +0 -0
  357. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/overlays/layer_sources.py +0 -0
  358. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/overlays/merge.py +0 -0
  359. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/overlays/schema.py +0 -0
  360. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/__init__.py +0 -0
  361. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/command/__init__.py +0 -0
  362. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/do_while/__init__.py +0 -0
  363. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/fan_in/__init__.py +0 -0
  364. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/fan_out/__init__.py +0 -0
  365. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/gate/__init__.py +0 -0
  366. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/if_then/__init__.py +0 -0
  367. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/init/__init__.py +0 -0
  368. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/prompt/__init__.py +0 -0
  369. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/shell/__init__.py +0 -0
  370. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/switch/__init__.py +0 -0
  371. {specify_cli-0.16.0 → specify_cli-0.16.1}/src/specify_cli/workflows/steps/while_loop/__init__.py +0 -0
  372. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/checklist-template.md +0 -0
  373. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/analyze.md +0 -0
  374. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/checklist.md +0 -0
  375. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/clarify.md +0 -0
  376. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/constitution.md +0 -0
  377. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/converge.md +0 -0
  378. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/implement.md +0 -0
  379. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/plan.md +0 -0
  380. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/specify.md +0 -0
  381. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/tasks.md +0 -0
  382. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/commands/taskstoissues.md +0 -0
  383. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/constitution-template.md +0 -0
  384. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/plan-template.md +0 -0
  385. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/spec-template.md +0 -0
  386. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/tasks-template.md +0 -0
  387. {specify_cli-0.16.0 → specify_cli-0.16.1}/templates/vscode-settings.json +0 -0
  388. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/__init__.py +0 -0
  389. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/auth_helpers.py +0 -0
  390. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/bundler_helpers.py +0 -0
  391. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/conftest.py +0 -0
  392. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/contract/test_bundle_cli.py +0 -0
  393. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/contract/test_catalog_schema.py +0 -0
  394. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/contract/test_manifest_schema.py +0 -0
  395. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/contract/test_wheel_bundled_presets.py +0 -0
  396. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/contract/test_wheel_core_pack_scripts.py +0 -0
  397. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/__init__.py +0 -0
  398. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/assess/__init__.py +0 -0
  399. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/assess/test_assess_extension.py +0 -0
  400. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/bug/__init__.py +0 -0
  401. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/bug/test_bug_extension.py +0 -0
  402. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/git/__init__.py +0 -0
  403. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/git/test_git_extension.py +0 -0
  404. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/git/test_git_extension_python_parity.py +0 -0
  405. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/test_agent_context_cli_free.py +0 -0
  406. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/test_extension_agent_context.py +0 -0
  407. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/extensions/test_update_agent_context_feature_json.py +0 -0
  408. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/hooks/TESTING.md +0 -0
  409. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/hooks/plan.md +0 -0
  410. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/hooks/spec.md +0 -0
  411. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/hooks/tasks.md +0 -0
  412. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/http_helpers.py +0 -0
  413. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integration/test_bundler_catalog_stack.py +0 -0
  414. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integration/test_bundler_init_install.py +0 -0
  415. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integration/test_bundler_install_flow.py +0 -0
  416. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integration/test_bundler_offline.py +0 -0
  417. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integration/test_bundler_security_paths.py +0 -0
  418. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/__init__.py +0 -0
  419. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/conftest.py +0 -0
  420. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_base.py +0 -0
  421. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_extra_args.py +0 -0
  422. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_home_isolation.py +0 -0
  423. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_agy.py +0 -0
  424. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_alquimia.py +0 -0
  425. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_amp.py +0 -0
  426. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_auggie.py +0 -0
  427. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_bob.py +0 -0
  428. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_claude.py +0 -0
  429. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_codebuddy.py +0 -0
  430. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_codex.py +0 -0
  431. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_cursor_agent.py +0 -0
  432. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_devin.py +0 -0
  433. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_droid.py +0 -0
  434. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_firebender.py +0 -0
  435. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_forge.py +0 -0
  436. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_gemini.py +0 -0
  437. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_goose.py +0 -0
  438. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_grok.py +0 -0
  439. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_hermes.py +0 -0
  440. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_junie.py +0 -0
  441. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_kilocode.py +0 -0
  442. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_kimi.py +0 -0
  443. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_kiro_cli.py +0 -0
  444. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_lingma.py +0 -0
  445. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_omp.py +0 -0
  446. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_opencode.py +0 -0
  447. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_pi.py +0 -0
  448. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_qodercli.py +0 -0
  449. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_qwen.py +0 -0
  450. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_rovodev.py +0 -0
  451. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_scaffold.py +0 -0
  452. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_shai.py +0 -0
  453. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_state.py +0 -0
  454. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_subcommand.py +0 -0
  455. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_tabnine.py +0 -0
  456. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_trae.py +0 -0
  457. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_vibe.py +0 -0
  458. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_zcode.py +0 -0
  459. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_integration_zed.py +0 -0
  460. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_manifest.py +0 -0
  461. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/integrations/test_registry.py +0 -0
  462. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/parity_helpers.py +0 -0
  463. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/self_upgrade_helpers.py +0 -0
  464. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_agent_config_consistency.py +0 -0
  465. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_authentication.py +0 -0
  466. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_branch_numbering.py +0 -0
  467. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_check_prerequisites_paths_only.py +0 -0
  468. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_check_tool.py +0 -0
  469. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_cli_version.py +0 -0
  470. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_command_template_py_scripts.py +0 -0
  471. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_commands_package.py +0 -0
  472. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_console_imports.py +0 -0
  473. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_create_new_feature_python_parity.py +0 -0
  474. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_extension_add_path_traversal.py +0 -0
  475. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_extension_registration.py +0 -0
  476. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_extension_skills.py +0 -0
  477. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_extension_update_hardening.py +0 -0
  478. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_github_http.py +0 -0
  479. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_github_workflows.py +0 -0
  480. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_init_dir.py +0 -0
  481. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_init_dir_cli.py +0 -0
  482. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_live_transient_windows.py +0 -0
  483. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_merge.py +0 -0
  484. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_post_process.py +0 -0
  485. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_ps1_encoding.py +0 -0
  486. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_registrar_path_traversal.py +0 -0
  487. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_security_workflow.py +0 -0
  488. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_self_upgrade_detection.py +0 -0
  489. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_self_upgrade_execution.py +0 -0
  490. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_self_upgrade_guidance.py +0 -0
  491. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_self_upgrade_verification.py +0 -0
  492. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_setup_plan_feature_json.py +0 -0
  493. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_setup_plan_no_overwrite.py +0 -0
  494. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_setup_plan_python_parity.py +0 -0
  495. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_setup_tasks.py +0 -0
  496. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_shared_infra_integrity.py +0 -0
  497. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_skill_placeholder_py.py +0 -0
  498. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_specify_template_numbering.py +0 -0
  499. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_timestamp_branches.py +0 -0
  500. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_upgrade.py +0 -0
  501. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_utils.py +0 -0
  502. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_utils_assets_imports.py +0 -0
  503. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_version_imports.py +0 -0
  504. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/test_workflow_run_without_project.py +0 -0
  505. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundle_download_url.py +0 -0
  506. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_adapters.py +0 -0
  507. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_catalog_config.py +0 -0
  508. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_conflict.py +0 -0
  509. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_packager.py +0 -0
  510. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_primitives.py +0 -0
  511. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_records.py +0 -0
  512. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_references.py +0 -0
  513. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_resolver.py +0 -0
  514. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_validator.py +0 -0
  515. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_versioning.py +0 -0
  516. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/unit/test_bundler_yamlio.py +0 -0
  517. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/workflows/conftest.py +0 -0
  518. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/workflows/test_overlay_commands.py +0 -0
  519. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/workflows/test_overlay_composer.py +0 -0
  520. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/workflows/test_overlay_layer_sources.py +0 -0
  521. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/workflows/test_overlay_merge.py +0 -0
  522. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/workflows/test_overlay_schema.py +0 -0
  523. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/workflows/test_overlay_security.py +0 -0
  524. {specify_cli-0.16.0 → specify_cli-0.16.1}/tests/workflows/test_resolver_integration.py +0 -0
  525. {specify_cli-0.16.0 → specify_cli-0.16.1}/workflows/ARCHITECTURE.md +0 -0
  526. {specify_cli-0.16.0 → specify_cli-0.16.1}/workflows/PUBLISHING.md +0 -0
  527. {specify_cli-0.16.0 → specify_cli-0.16.1}/workflows/README.md +0 -0
  528. {specify_cli-0.16.0 → specify_cli-0.16.1}/workflows/catalog.community.json +0 -0
  529. {specify_cli-0.16.0 → specify_cli-0.16.1}/workflows/catalog.json +0 -0
  530. {specify_cli-0.16.0 → specify_cli-0.16.1}/workflows/speckit/workflow.yml +0 -0
  531. {specify_cli-0.16.0 → specify_cli-0.16.1}/workflows/step-catalog.community.json +0 -0
  532. {specify_cli-0.16.0 → specify_cli-0.16.1}/workflows/step-catalog.json +0 -0
@@ -22,11 +22,11 @@ jobs:
22
22
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
23
 
24
24
  - name: Initialize CodeQL
25
- uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
25
+ uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4
26
26
  with:
27
27
  languages: ${{ matrix.language }}
28
28
 
29
29
  - name: Perform CodeQL Analysis
30
- uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
30
+ uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4
31
31
  with:
32
32
  category: "/language:${{ matrix.language }}"
@@ -37,7 +37,7 @@ jobs:
37
37
  fi
38
38
 
39
39
  - name: Run markdownlint-cli2
40
- uses: DavidAnson/markdownlint-cli2-action@6bf21b07787794f89a243495939cd651942aeabe # v24.1.0
40
+ uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0
41
41
  with:
42
42
  globs: |
43
43
  '**/*.md'
@@ -2,6 +2,32 @@
2
2
 
3
3
  <!-- insert new changelog below this comment -->
4
4
 
5
+ ## [0.16.1] - 2026-08-07
6
+
7
+ ### Changed
8
+
9
+ - fix(integrations): wrap a non-UTF-8 catalog response (#4011)
10
+ - fix(events): skip an unreadable command template (#3956)
11
+ - fix(bundle): wrap malformed YAML in a local .zip bundle manifest (#4013)
12
+ - chore(deps): bump github/codeql-action/analyze from 4.37.3 to 4.37.5 (#4005)
13
+ - chore(deps): bump DavidAnson/markdownlint-cli2-action (#4006)
14
+ - fix(agent-context): recurse for nested plans in Python mtime fallback (#3757)
15
+ - fix: add utf-8 encoding to extension and preset registry file I/O (#3834)
16
+ - fix(init): escape user-supplied values in `specify init` output (#3787)
17
+ - fix: bound response read in integration catalog fetch (#3812)
18
+ - fix: use missing_ok for temp file cleanup to avoid masking errors (#3803)
19
+ - fix(workflows): handle an unreadable run state in `workflow status` (#3999)
20
+ - feat(init): scaffold managed .specify/.gitignore (#4000)
21
+ - fix(scripts): stop check-prerequisites text mode crashing on a legacy stdout code page (#3890)
22
+ - fix(presets): return None for an unreadable layer in resolve_content (#3959)
23
+ - fix(extensions): start fresh on a non-UTF-8 extension registry (#3998)
24
+ - Fix init-force-preset-desync: reapply presets/extensions on init --here --force (#3995)
25
+ - fix(skills): apply the line-anchored delimiter scan to hermes and kimi (#3739)
26
+ - fix(archives): wrap the bare EOFError a truncated tar.gz raises (#3938)
27
+ - test(integrations): guard multiline/control-char SKILL.md frontmatter escaping (#3392)
28
+ - fix(scripts): stop setup-tasks text mode crashing on a legacy code page (#3892)
29
+ - chore: release 0.16.0, begin 0.16.1.dev0 development (#3992)
30
+
5
31
  ## [0.16.0] - 2026-08-05
6
32
 
7
33
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specify-cli
3
- Version: 0.16.0
3
+ Version: 0.16.1
4
4
  Summary: Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD).
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -57,6 +57,8 @@ specify init my-project --integration copilot --preset compliance
57
57
 
58
58
  > **Two resolution axes.** `SPECIFY_INIT_DIR` selects the **project** (which directory contains `.specify/`); `SPECIFY_FEATURE_DIRECTORY` / `.specify/feature.json` select the **feature** within that project. They are independent — project first, then feature.
59
59
 
60
+ > **Version control.** `specify init` scaffolds a managed `.specify/.gitignore` that excludes machine-local state — `feature.json` (the current-feature pointer, rewritten on every feature switch) and per-machine extension `extensions/*/local-config.yml` overrides — while leaving everything else under `.specify/` (constitution, templates, scripts, extension config) shareable so teams stay aligned. Like the rest of `.specify/`'s shared scripts and templates, the file is tracked in the shared-infrastructure manifest: your edits are preserved on re-init and `specify init --here --force` restores the managed content. It is intentionally left in place by `specify integration uninstall`, which only removes the uninstalled agent's own files.
61
+
60
62
  > **Symlinked project roots.** `SPECIFY_INIT_DIR` relocates *where* the project is, not *how* a command treats symlinks: each command keeps its existing cwd-path stance. Commands that traverse and write project files through broad input paths (`bundle`, `workflow run <file>`) refuse a symlinked `.specify/` to preserve write confinement. Other project-scoped commands keep their existing behavior when `SPECIFY_INIT_DIR` points at a project root, which may include following a symlinked `.specify/`.
61
63
 
62
64
  ## Check Installed Tools
@@ -11,8 +11,8 @@ Usage: update_agent_context.py [plan_path]
11
11
 
12
12
  When ``plan_path`` is omitted, the script derives it from
13
13
  ``.specify/feature.json`` (written by /speckit-specify). Falls back to the most
14
- recently modified ``plan.md`` anywhere under ``specs/`` (including nested scoped
15
- layouts such as ``specs/<scope>/<feature>/plan.md``) only when feature.json is
14
+ recently modified ``plan.md`` found anywhere under ``specs/`` scoped layouts
15
+ nest it as ``specs/<scope>/<feature>/plan.md`` only when feature.json is
16
16
  absent or its plan does not exist yet.
17
17
  """
18
18
 
@@ -173,16 +173,31 @@ def _resolve_plan_path(project_root: str) -> str:
173
173
 
174
174
  if not plan_path:
175
175
  root = Path(project_root).resolve()
176
- plans = sorted(
177
- (root / "specs").rglob("plan.md"),
178
- key=lambda p: p.stat().st_mtime,
179
- reverse=True,
180
- )
181
- if plans:
176
+ specs = root / "specs"
177
+
178
+ def _resolved_rel(p: Path) -> Path | None:
179
+ # Resolve symlinks before checking containment: relative_to() is
180
+ # lexical and would otherwise accept a plan reached through a specs/
181
+ # symlink that points outside the project, emitting an
182
+ # in-project-looking path for an out-of-project file (or picking it
183
+ # as "most recent").
182
184
  try:
183
- plan_path = plans[0].relative_to(root).as_posix()
184
- except ValueError:
185
- plan_path = ""
185
+ return p.resolve().relative_to(root)
186
+ except (OSError, ValueError):
187
+ return None
188
+
189
+ # Recurse (rather than the old one-level specs/*/plan.md glob) so scoped
190
+ # layouts created via SPECIFY_FEATURE_DIRECTORY, e.g.
191
+ # specs/<scope>/<feature>/plan.md, are still discovered when
192
+ # feature.json is absent (#3024). Mirrors the bash and PowerShell twins.
193
+ candidates = []
194
+ for p in specs.rglob("plan.md"):
195
+ rel = _resolved_rel(p)
196
+ if rel is not None:
197
+ candidates.append((p, rel))
198
+ candidates.sort(key=lambda pr: pr[0].stat().st_mtime, reverse=True)
199
+ if candidates:
200
+ plan_path = candidates[0][1].as_posix()
186
201
  return plan_path
187
202
 
188
203
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "specify-cli"
3
- version = "0.16.0"
3
+ version = "0.16.1"
4
4
  description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -130,14 +130,31 @@ def _print_paths_only(paths: FeaturePaths, json_mode: bool) -> None:
130
130
  print(f"TASKS: {paths.tasks}")
131
131
 
132
132
 
133
+ def _status_marker(ok: bool) -> str:
134
+ """Return the status glyph, downgraded to ASCII when stdout cannot encode it.
135
+
136
+ On Windows sys.stdout falls back to the ANSI code page whenever it is not a
137
+ console - a pipe or a file redirect, which is how agents and workflow steps
138
+ invoke these scripts - and U+2713 is unencodable in cp1252, so printing it
139
+ raised UnicodeEncodeError and aborted the report right after
140
+ "AVAILABLE_DOCS:". "[OK]"/"[FAIL]" is the ASCII rendering these markers
141
+ already have in-tree: see Test-FileExists in scripts/powershell/common.ps1
142
+ and normalize_status_text in tests/parity_helpers.py.
143
+ """
144
+ glyph = "✓" if ok else "✗"
145
+ try:
146
+ glyph.encode(getattr(sys.stdout, "encoding", None) or "utf-8")
147
+ except (LookupError, UnicodeEncodeError):
148
+ return "[OK]" if ok else "[FAIL]"
149
+ return glyph
150
+
151
+
133
152
  def _check_file(path: Path, description: str) -> None:
134
- marker = "✓" if path.is_file() else "✗"
135
- print(f" {marker} {description}")
153
+ print(f" {_status_marker(path.is_file())} {description}")
136
154
 
137
155
 
138
156
  def _check_dir(path: Path, description: str) -> None:
139
- marker = "✓" if _dir_has_entries(path) else "✗"
140
- print(f" {marker} {description}")
157
+ print(f" {_status_marker(_dir_has_entries(path))} {description}")
141
158
 
142
159
 
143
160
  def _print_text_results(paths: FeaturePaths, include_tasks: bool) -> None:
@@ -55,14 +55,31 @@ def _available_docs(paths: FeaturePaths) -> list[str]:
55
55
  return docs
56
56
 
57
57
 
58
+ def _status_marker(ok: bool) -> str:
59
+ """Return the status glyph, downgraded to ASCII when stdout cannot encode it.
60
+
61
+ On Windows sys.stdout falls back to the ANSI code page whenever it is not a
62
+ console - a pipe or a file redirect, which is how agents and workflow steps
63
+ invoke these scripts - and U+2713 is unencodable in cp1252, so printing it
64
+ raised UnicodeEncodeError and aborted the report mid-listing.
65
+ "[OK]"/"[FAIL]" is the ASCII rendering these markers already have in-tree:
66
+ see Test-FileExists in scripts/powershell/common.ps1 and
67
+ normalize_status_text in tests/parity_helpers.py.
68
+ """
69
+ glyph = "✓" if ok else "✗"
70
+ try:
71
+ glyph.encode(getattr(sys.stdout, "encoding", None) or "utf-8")
72
+ except (LookupError, UnicodeEncodeError):
73
+ return "[OK]" if ok else "[FAIL]"
74
+ return glyph
75
+
76
+
58
77
  def _check_file(path: Path, description: str) -> None:
59
- marker = "✓" if path.is_file() else "✗"
60
- print(f" {marker} {description}")
78
+ print(f" {_status_marker(path.is_file())} {description}")
61
79
 
62
80
 
63
81
  def _check_dir(path: Path, description: str) -> None:
64
- marker = "✓" if _dir_has_entries(path) else "✗"
65
- print(f" {marker} {description}")
82
+ print(f" {_status_marker(_dir_has_entries(path))} {description}")
66
83
 
67
84
 
68
85
  def main(argv: list[str] | None = None) -> int:
@@ -10,6 +10,7 @@ import struct
10
10
  import tarfile
11
11
  import unicodedata
12
12
  import zipfile
13
+ import zlib
13
14
  from collections.abc import Iterator
14
15
  from contextlib import ExitStack, contextmanager
15
16
  from ipaddress import IPv4Address, IPv6Address, ip_address
@@ -69,6 +70,19 @@ _ZIP_MAX_COMMENT_BYTES = (1 << 16) - 1
69
70
  _BOUNDED_ZIP_COMPRESSION_METHODS = frozenset(
70
71
  (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED)
71
72
  )
73
+ #: Decompression failures a truncated or corrupt gzip stream raises from
74
+ #: ``tarfile``. Most are wrapped in ``TarError``, but two escape raw, and
75
+ #: neither derives from ``TarError`` or ``OSError``, so both bypass a
76
+ #: ``(TarError, OSError)`` handler:
77
+ #:
78
+ #: * ``EOFError`` -- from the gzip layer when the stream ends before its
79
+ #: end-of-stream marker, i.e. a truncated archive.
80
+ #: * ``zlib.error`` -- from a corrupt deflate block. ``tarfile`` converts this
81
+ #: to ``ReadError`` while reading a member *header*, but the forward seek it
82
+ #: performs to skip member *data* sits outside that conversion, so a corrupt
83
+ #: region past the first header escapes raw.
84
+ _TAR_DECOMPRESSION_ERRORS = (tarfile.TarError, EOFError, zlib.error)
85
+
72
86
  _ARCHIVE_CONTENT_TYPES: dict[str, ArchiveFormat] = {
73
87
  "application/gzip": "tar.gz",
74
88
  "application/x-gzip": "tar.gz",
@@ -166,7 +180,11 @@ def detect_archive_format(
166
180
  try:
167
181
  with tarfile.open(fileobj=archive_file, mode="r:gz"):
168
182
  is_tar_gz = True
169
- except tarfile.TarError:
183
+ except _TAR_DECOMPRESSION_ERRORS:
184
+ # A truncated gzip stream raises a bare EOFError here rather
185
+ # than a TarError, so catching only TarError let it escape
186
+ # this probe as a raw exception instead of leaving
187
+ # ``is_tar_gz`` False and reporting the format mismatch.
170
188
  pass
171
189
  archive_file.seek(0)
172
190
  except OSError as exc:
@@ -1077,7 +1095,7 @@ def safe_extract_tar(
1077
1095
  mode="r:gz",
1078
1096
  fileobj=archive_file,
1079
1097
  )
1080
- except (tarfile.TarError, OSError) as exc:
1098
+ except (*_TAR_DECOMPRESSION_ERRORS, OSError) as exc:
1081
1099
  _raise_from(error_type, f"Invalid tar.gz archive: {archive_path}", exc)
1082
1100
 
1083
1101
  with archive:
@@ -1149,7 +1167,7 @@ def safe_extract_tar(
1149
1167
  f"of {max_total_bytes} bytes",
1150
1168
  )
1151
1169
  validated.append((member, normalized_name, is_dir))
1152
- except (tarfile.TarError, OSError) as exc:
1170
+ except (*_TAR_DECOMPRESSION_ERRORS, OSError) as exc:
1153
1171
  _raise_from(
1154
1172
  error_type,
1155
1173
  f"Invalid tar.gz archive: {archive_path}",
@@ -192,8 +192,8 @@ def handle_vscode_settings(sub_item, dest_file, rel_path, verbose=False, tracker
192
192
 
193
193
  os.replace(temp_path, target_file)
194
194
  except Exception:
195
- if temp_path and temp_path.exists():
196
- temp_path.unlink()
195
+ if temp_path:
196
+ temp_path.unlink(missing_ok=True)
197
197
  raise
198
198
 
199
199
  try:
@@ -771,7 +771,17 @@ def _local_manifest_source(arg: str):
771
771
  error_type=BundlerError,
772
772
  label="bundle manifest",
773
773
  )
774
- data = _yaml.safe_load(io.BytesIO(raw))
774
+ try:
775
+ data = _yaml.safe_load(io.BytesIO(raw))
776
+ except _yaml.YAMLError as exc:
777
+ # The sibling directory/bundle.yml branches reach YAML through
778
+ # load_yaml(), which turns a parse failure into a BundlerError. This
779
+ # branch parses inline, so without this it raises a raw YAMLError --
780
+ # neither a ValueError nor an OSError -- which escapes
781
+ # bundle_install()'s `except BundlerError` as a traceback.
782
+ raise BundlerError(
783
+ f"Invalid YAML in bundle.yml inside '{candidate}': {exc}"
784
+ ) from exc
775
785
  return BundleManifest.from_dict(data)
776
786
 
777
787
  if candidate.name == "bundle.yml" or candidate.suffix in (".yml", ".yaml"):
@@ -3,13 +3,16 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
+ import shlex
6
7
  import shutil
8
+ import subprocess
7
9
  import sys
8
10
  from pathlib import Path
9
11
  from typing import Any
10
12
 
11
13
  import typer
12
14
  from rich.live import Live
15
+ from rich.markup import escape as _escape_markup
13
16
  from rich.panel import Panel
14
17
 
15
18
  from .._agent_config import (
@@ -169,6 +172,25 @@ def _install_extension_during_init(project_path: Path, ext_spec: str, speckit_ve
169
172
  return f"{manifest.name} v{manifest.version} installed"
170
173
 
171
174
 
175
+ def _shell_quote_arg(value: str) -> str:
176
+ """Quote *value* as one argument for the shells of the host OS.
177
+
178
+ The Next Steps ``cd`` line is copy-pasted into whichever shell ran
179
+ ``specify init``, so it is quoted for the host the same way
180
+ ``_version._render_argv`` renders its copy-pasteable installer command:
181
+ ``list2cmdline`` on Windows, ``shlex.quote`` elsewhere. The Windows branch
182
+ must emit double quotes -- ``cd 'my project'`` is a path-not-found in
183
+ cmd.exe, while ``cd "my project"`` is accepted by cmd.exe, PowerShell and
184
+ Git Bash alike. A value needing no quoting is returned unchanged.
185
+
186
+ Whitespace only. PowerShell also glob-expands ``[``/``]`` and expands
187
+ ``$``/backtick inside double quotes, so such a name still needs
188
+ ``Set-Location -LiteralPath`` there -- syntax invalid in cmd.exe and sh, so
189
+ this shell-neutral line cannot cover it.
190
+ """
191
+ return subprocess.list2cmdline([value]) if os.name == "nt" else shlex.quote(value)
192
+
193
+
172
194
  def ensure_constitution_from_template(
173
195
  project_path: Path, tracker: StepTracker | None = None
174
196
  ) -> None:
@@ -351,7 +373,10 @@ def register(app: typer.Typer) -> None:
351
373
  if integration:
352
374
  resolved_integration = get_integration(integration)
353
375
  if not resolved_integration:
354
- console.print(f"[red]Error:[/red] Unknown integration: '{integration}'")
376
+ console.print(
377
+ f"[red]Error:[/red] Unknown integration: "
378
+ f"'{_escape_markup(str(integration))}'"
379
+ )
355
380
  available = ", ".join(sorted(INTEGRATION_REGISTRY))
356
381
  console.print(f"[yellow]Available integrations:[/yellow] {available}")
357
382
  raise typer.Exit(1)
@@ -428,26 +453,27 @@ def register(app: typer.Typer) -> None:
428
453
  project_path = Path(project_name).resolve()
429
454
  dir_existed_before = project_path.exists()
430
455
  if project_path.exists():
456
+ safe_name = _escape_markup(str(project_name))
431
457
  if not project_path.is_dir():
432
458
  console.print(
433
- f"[red]Error:[/red] '{project_name}' exists but is not a directory."
459
+ f"[red]Error:[/red] '{safe_name}' exists but is not a directory."
434
460
  )
435
461
  raise typer.Exit(1)
436
462
  existing_items = list(project_path.iterdir())
437
463
  if force:
438
464
  if existing_items:
439
465
  console.print(
440
- f"[yellow]Warning:[/yellow] Directory '{project_name}' is not empty ({len(existing_items)} items)"
466
+ f"[yellow]Warning:[/yellow] Directory '{safe_name}' is not empty ({len(existing_items)} items)"
441
467
  )
442
468
  console.print(
443
469
  "[yellow]Template files will be merged with existing content and may overwrite existing files[/yellow]"
444
470
  )
445
471
  console.print(
446
- f"[cyan]--force supplied: merging into existing directory '[cyan]{project_name}[/cyan]'[/cyan]"
472
+ f"[cyan]--force supplied: merging into existing directory '[cyan]{safe_name}[/cyan]'[/cyan]"
447
473
  )
448
474
  else:
449
475
  error_panel = Panel(
450
- f"Directory already exists: '[cyan]{project_name}[/cyan]'\n"
476
+ f"Directory already exists: '[cyan]{safe_name}[/cyan]'\n"
451
477
  "Please choose a different project name or remove the existing directory.\n"
452
478
  "Use [bold]--force[/bold] to merge into the existing directory.",
453
479
  title="[red]Directory Conflict[/red]",
@@ -461,7 +487,7 @@ def register(app: typer.Typer) -> None:
461
487
  if integration:
462
488
  if integration not in AGENT_CONFIG:
463
489
  console.print(
464
- f"[red]Error:[/red] Invalid integration '{integration}'. Choose from: {', '.join(AGENT_CONFIG.keys())}"
490
+ f"[red]Error:[/red] Invalid integration '{_escape_markup(str(integration))}'. Choose from: {', '.join(AGENT_CONFIG.keys())}"
465
491
  )
466
492
  raise typer.Exit(1)
467
493
  selected_ai = integration
@@ -500,12 +526,14 @@ def register(app: typer.Typer) -> None:
500
526
  setup_lines = [
501
527
  "[cyan]Specify Project Setup[/cyan]",
502
528
  "",
503
- f"{'Project':<15} [green]{project_path.name}[/green]",
504
- f"{'Working Path':<15} [dim]{current_dir}[/dim]",
529
+ f"{'Project':<15} [green]{_escape_markup(project_path.name)}[/green]",
530
+ f"{'Working Path':<15} [dim]{_escape_markup(str(current_dir))}[/dim]",
505
531
  ]
506
532
 
507
533
  if not here:
508
- setup_lines.append(f"{'Target Path':<15} [dim]{project_path}[/dim]")
534
+ setup_lines.append(
535
+ f"{'Target Path':<15} [dim]{_escape_markup(str(project_path))}[/dim]"
536
+ )
509
537
 
510
538
  console.print(
511
539
  Panel("\n".join(setup_lines), border_style="cyan", padding=(1, 2))
@@ -532,7 +560,7 @@ def register(app: typer.Typer) -> None:
532
560
  if script_type:
533
561
  if script_type not in SCRIPT_TYPE_CHOICES:
534
562
  console.print(
535
- f"[red]Error:[/red] Invalid script type '{script_type}'. Choose from: {', '.join(SCRIPT_TYPE_CHOICES.keys())}"
563
+ f"[red]Error:[/red] Invalid script type '{_escape_markup(str(script_type))}'. Choose from: {', '.join(SCRIPT_TYPE_CHOICES.keys())}"
536
564
  )
537
565
  raise typer.Exit(1)
538
566
  selected_script = script_type
@@ -571,8 +599,6 @@ def register(app: typer.Typer) -> None:
571
599
  tracker.add(key, label)
572
600
 
573
601
  if extensions:
574
- from rich.markup import escape as _escape_markup
575
-
576
602
  for i, ext_spec in enumerate(extensions):
577
603
  tracker.add(
578
604
  f"extension-{i}", f"Install extension: {_escape_markup(ext_spec)}"
@@ -635,6 +661,30 @@ def register(app: typer.Typer) -> None:
635
661
  )
636
662
  manifest.save()
637
663
 
664
+ if force:
665
+ from ..integrations._helpers import (
666
+ _register_extensions_for_agent,
667
+ _register_presets_for_agent,
668
+ )
669
+
670
+ _register_extensions_for_agent(
671
+ project_path,
672
+ resolved_integration.key,
673
+ force=True,
674
+ continuing=(
675
+ "The project was re-initialized, but installed extensions"
676
+ " may need re-registration."
677
+ ),
678
+ )
679
+ _register_presets_for_agent(
680
+ project_path,
681
+ resolved_integration.key,
682
+ continuing=(
683
+ "The project was re-initialized, but installed presets"
684
+ " may need re-registration."
685
+ ),
686
+ )
687
+
638
688
  integration_settings = _with_integration_setting(
639
689
  {},
640
690
  resolved_integration.key,
@@ -803,8 +853,6 @@ def register(app: typer.Typer) -> None:
803
853
 
804
854
  # Install extensions specified via --extension
805
855
  if extensions:
806
- from rich.markup import escape as _escape_markup
807
-
808
856
  from ..extensions._commands import _refresh_events_and_warn
809
857
 
810
858
  speckit_ver = get_speckit_version()
@@ -894,7 +942,7 @@ def register(app: typer.Typer) -> None:
894
942
  if agent_folder:
895
943
  security_notice = Panel(
896
944
  f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n"
897
- f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.",
945
+ f"Consider adding [cyan]{_escape_markup(str(agent_folder))}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.",
898
946
  title="[yellow]Agent Folder Security[/yellow]",
899
947
  border_style="yellow",
900
948
  padding=(1, 2),
@@ -905,7 +953,7 @@ def register(app: typer.Typer) -> None:
905
953
  steps_lines = []
906
954
  if not here:
907
955
  steps_lines.append(
908
- f"1. Go to the project folder: [cyan]cd {project_name}[/cyan]"
956
+ f"1. Go to the project folder: [cyan]cd {_escape_markup(_shell_quote_arg(str(project_name)))}[/cyan]"
909
957
  )
910
958
  step_num = 2
911
959
  else:
@@ -548,7 +548,15 @@ def _resolve_event_command_argv(
548
548
  """
549
549
  from .integrations.base import IntegrationBase
550
550
 
551
- content = template_path.read_text(encoding="utf-8")
551
+ try:
552
+ content = template_path.read_text(encoding="utf-8")
553
+ except (OSError, UnicodeDecodeError):
554
+ # An unreadable or undecodable template cannot declare a runnable
555
+ # script. Degrade to "no argv" like every other failure in this
556
+ # resolver (missing frontmatter, malformed YAML, absent scripts)
557
+ # instead of leaking a raw traceback through
558
+ # resolve_and_run_event_command.
559
+ return None
552
560
  m = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
553
561
  if not m:
554
562
  return None
@@ -660,8 +660,13 @@ class ExtensionRegistry:
660
660
  if not isinstance(data.get("extensions"), dict):
661
661
  data["extensions"] = {}
662
662
  return data
663
- except (json.JSONDecodeError, FileNotFoundError):
664
- # Corrupted or missing registry, start fresh
663
+ except (json.JSONDecodeError, UnicodeDecodeError, FileNotFoundError):
664
+ # Corrupted or missing registry, start fresh. A registry whose
665
+ # bytes cannot be decoded as UTF-8 is the same corruption class as
666
+ # malformed JSON — only the exception type differs, and it is
667
+ # raised by the text-mode read before JSON parsing begins. OSError
668
+ # is deliberately not caught: the data may be intact on disk, and
669
+ # starting fresh would let a later _save() wipe it.
665
670
  return {"schema_version": self.SCHEMA_VERSION, "extensions": {}}
666
671
 
667
672
  def _save(self):
@@ -4310,11 +4315,10 @@ class ConfigManager:
4310
4315
  Returns an empty list if the registry is missing or corrupted
4311
4316
  (fresh project, ad-hoc test harness) so ``_get_env_config`` degrades
4312
4317
  to its pre-fix behaviour rather than crashing. ``UnicodeError`` is
4313
- caught alongside ``OSError`` because ``ExtensionRegistry._load()``
4314
- opens the file in text mode and only handles ``JSONDecodeError`` /
4315
- ``FileNotFoundError``, so a registry file with non-UTF-8 bytes would
4316
- otherwise surface a ``UnicodeDecodeError`` here and break *every*
4317
- config read instead of degrading gracefully.
4318
+ kept alongside ``OSError`` as belt-and-braces: ``_load()`` now starts
4319
+ fresh on non-UTF-8 registry bytes itself, but catching it here too
4320
+ keeps this call site degrading gracefully rather than breaking *every*
4321
+ config read if that handling ever regresses.
4318
4322
 
4319
4323
  Used by ``_get_env_config`` to detect env vars whose remainder claims
4320
4324
  a longer, sibling-owned prefix (e.g. ``SPECKIT_GIT_HOOKS_URL`` is
@@ -207,7 +207,7 @@ class IntegrationCatalog(CatalogStackBase):
207
207
  max_bytes=MAX_JSON_METADATA_BYTES,
208
208
  error_type=IntegrationCatalogError,
209
209
  label=f"catalog from {entry.url}",
210
- )
210
+ ).decode("utf-8")
211
211
  )
212
212
 
213
213
  shape_error = _catalog_shape_error(catalog_data)
@@ -237,6 +237,15 @@ class IntegrationCatalog(CatalogStackBase):
237
237
  raise IntegrationCatalogError(
238
238
  f"Failed to fetch catalog from {entry.url}: {exc}"
239
239
  )
240
+ except UnicodeDecodeError as exc:
241
+ # A non-UTF-8 response body fails at .decode() before json.loads()
242
+ # ever runs, so JSONDecodeError below does not cover it (the two are
243
+ # sibling ValueError subclasses, not parent/child). Without this the
244
+ # raw UnicodeDecodeError escapes _get_merged_integrations()'s
245
+ # "warn and skip this catalog" handler and kills the whole command.
246
+ raise IntegrationCatalogError(
247
+ f"Catalog from {entry.url} is not valid UTF-8: {exc}"
248
+ )
240
249
  except json.JSONDecodeError as exc:
241
250
  raise IntegrationCatalogError(
242
251
  f"Invalid JSON in catalog from {entry.url}: {exc}"
@@ -121,13 +121,27 @@ class HermesIntegration(SkillsIntegration):
121
121
  command_name = src_file.stem # e.g. "plan"
122
122
  skill_name = f"speckit-{command_name.replace('.', '-')}"
123
123
 
124
- # Parse frontmatter for description
124
+ # Parse frontmatter for description. Locate the closing ``---`` on
125
+ # its own line rather than with ``raw.split("---", 2)`` — a bare
126
+ # substring split stops at the first ``---`` *anywhere*, including
127
+ # one inside a value such as ``description: Separate sections
128
+ # with ---``, which truncates the frontmatter and drops later keys.
129
+ # The block between the delimiters is parsed unstripped so trailing
130
+ # newlines in literal (``|``) block scalars survive.
125
131
  frontmatter: dict[str, Any] = {}
126
132
  if raw.startswith("---"):
127
- parts = raw.split("---", 2)
128
- if len(parts) >= 3:
133
+ fm_lines = raw.splitlines(keepends=True)
134
+ fm_close = next(
135
+ (
136
+ i
137
+ for i in range(1, len(fm_lines))
138
+ if fm_lines[i].rstrip() == "---"
139
+ ),
140
+ None,
141
+ )
142
+ if fm_close is not None:
129
143
  try:
130
- fm = yaml.safe_load(parts[1])
144
+ fm = yaml.safe_load("".join(fm_lines[1:fm_close]))
131
145
  if isinstance(fm, dict):
132
146
  frontmatter = fm
133
147
  except yaml.YAMLError:
@@ -143,10 +157,26 @@ class HermesIntegration(SkillsIntegration):
143
157
  project_root=project_root,
144
158
  )
145
159
  # Strip the processed frontmatter — we rebuild it for skills.
160
+ # Scan for the closing ``---`` on its own line rather than
161
+ # ``split("---", 2)`` so a ``---`` embedded in a value does not
162
+ # truncate the frontmatter and spill it into the body.
146
163
  if processed_body.startswith("---"):
147
- parts = processed_body.split("---", 2)
148
- if len(parts) >= 3:
149
- processed_body = parts[2]
164
+ body_lines = processed_body.splitlines(keepends=True)
165
+ close_idx = next(
166
+ (
167
+ i
168
+ for i in range(1, len(body_lines))
169
+ if body_lines[i].rstrip() == "---"
170
+ ),
171
+ None,
172
+ )
173
+ if close_idx is not None:
174
+ # Keep whatever trails the ``---`` marker on the closing
175
+ # line so the body stays byte-for-byte identical to
176
+ # ``split("---", 2)[2]`` for well-formed templates.
177
+ processed_body = body_lines[close_idx][3:] + "".join(
178
+ body_lines[close_idx + 1 :]
179
+ )
150
180
 
151
181
  # Select description
152
182
  description = frontmatter.get("description", "")