project-init 0.3.0__tar.gz → 0.4.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 (298) hide show
  1. {project_init-0.3.0/plugins/project-init-workflow → project_init-0.4.0/.claude}/hooks/dag_workflow.py +11 -3
  2. {project_init-0.3.0 → project_init-0.4.0}/.claude/scripts/monitor_pr.sh +63 -28
  3. {project_init-0.3.0 → project_init-0.4.0}/.github/workflows/release.yml +6 -1
  4. {project_init-0.3.0 → project_init-0.4.0}/.github/workflows/validate-pr.yml +29 -0
  5. {project_init-0.3.0 → project_init-0.4.0}/PKG-INFO +1 -2
  6. {project_init-0.3.0 → project_init-0.4.0}/README.md +0 -1
  7. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-003-github-native-workflow.md +1 -1
  8. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-005-github-pr-board-workflow.md +1 -1
  9. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-011-pypi-trusted-publishing.md +6 -1
  10. {project_init-0.3.0 → project_init-0.4.0}/mkdocs.yml +10 -0
  11. {project_init-0.3.0/.claude → project_init-0.4.0/plugins/project-init-workflow}/hooks/dag_workflow.py +11 -3
  12. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/hooks/prod_guard.py +41 -10
  13. {project_init-0.3.0 → project_init-0.4.0}/pyproject.toml +1 -1
  14. {project_init-0.3.0 → project_init-0.4.0}/src/project_init/__init__.py +1 -1
  15. {project_init-0.3.0 → project_init-0.4.0}/src/project_init/__main__.py +72 -82
  16. {project_init-0.3.0 → project_init-0.4.0}/src/project_init/scaffold.py +58 -5
  17. {project_init-0.3.0 → project_init-0.4.0}/src/project_init/upgrade.py +55 -72
  18. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/hooks/dag_workflow.py +11 -3
  19. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/create_issue.sh +33 -17
  20. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/install_hooks.sh +6 -1
  21. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/monitor_pr.sh +18 -3
  22. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/start_issue.sh +5 -0
  23. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/workflows/board-automation.yml +13 -3
  24. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/workflows/ci.yml.tmpl +84 -6
  25. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/workflows/validate-pr.yml +2 -0
  26. {project_init-0.3.0 → project_init-0.4.0}/templates/base/justfile.tmpl +3 -3
  27. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/hooks/prod_guard.py +41 -10
  28. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_agent_overlays.py +17 -2
  29. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_governance.py +13 -0
  30. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_justfile.py +8 -0
  31. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_prod_guard.py +26 -0
  32. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_release_engineering.py +27 -1
  33. project_init-0.4.0/tests/contracts/test_repo_validate_pr.py +89 -0
  34. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_scaffold_obsidian.py +9 -0
  35. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_templates.py +170 -0
  36. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_cli.py +16 -0
  37. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_dag_workflow.py +23 -0
  38. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_hooks_and_safety.py +25 -0
  39. project_init-0.4.0/tests/integration/test_install_script.py +59 -0
  40. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_issue_metadata_workflow.py +28 -4
  41. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_overwrite_protection.py +49 -2
  42. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_readme_examples.py +14 -0
  43. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_upgrade.py +34 -5
  44. project_init-0.4.0/tests/unit/test_interactive_prompts.py +50 -0
  45. {project_init-0.3.0 → project_init-0.4.0}/tests/unit/test_presets.py +20 -0
  46. project_init-0.4.0/tests/unit/test_render_nesting.py +76 -0
  47. project_init-0.4.0/tests/unit/test_scaffold_inputs.py +53 -0
  48. {project_init-0.3.0 → project_init-0.4.0}/uv.lock +1 -1
  49. project_init-0.3.0/tests/unit/test_render_nesting.py +0 -35
  50. {project_init-0.3.0 → project_init-0.4.0}/.claude/hooks/github_command_guard.sh +0 -0
  51. {project_init-0.3.0 → project_init-0.4.0}/.claude/hooks/workflow_state_reminder.sh +0 -0
  52. {project_init-0.3.0 → project_init-0.4.0}/.claude/scripts/create_nojira_pr.sh +0 -0
  53. {project_init-0.3.0 → project_init-0.4.0}/.claude/scripts/finish_pr.sh +0 -0
  54. {project_init-0.3.0 → project_init-0.4.0}/.claude/scripts/promote_review.sh +0 -0
  55. {project_init-0.3.0 → project_init-0.4.0}/.claude/scripts/push_branch.sh +0 -0
  56. {project_init-0.3.0 → project_init-0.4.0}/.claude/scripts/push_wiki.sh +0 -0
  57. {project_init-0.3.0 → project_init-0.4.0}/.claude/settings.json +0 -0
  58. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/INDEX.md +0 -0
  59. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/README.md +0 -0
  60. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/add_command/SKILL.md +0 -0
  61. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/add_hook/SKILL.md +0 -0
  62. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/github_workflow/SKILL.md +0 -0
  63. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/session_summary/SKILL.md +0 -0
  64. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/start_task/SKILL.md +0 -0
  65. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/wiki/SKILL.md +0 -0
  66. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/wiki/templates/architecture.md +0 -0
  67. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/wiki/templates/implementation-guide.md +0 -0
  68. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/wiki/templates/preset-guide.md +0 -0
  69. {project_init-0.3.0 → project_init-0.4.0}/.claude/skills/wiki/templates/scaffolder-logic.md +0 -0
  70. {project_init-0.3.0 → project_init-0.4.0}/.claude-plugin/marketplace.json +0 -0
  71. {project_init-0.3.0 → project_init-0.4.0}/.gitattributes +0 -0
  72. {project_init-0.3.0 → project_init-0.4.0}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  73. {project_init-0.3.0 → project_init-0.4.0}/.github/ISSUE_TEMPLATE/chore.yml +0 -0
  74. {project_init-0.3.0 → project_init-0.4.0}/.github/ISSUE_TEMPLATE/docs.yml +0 -0
  75. {project_init-0.3.0 → project_init-0.4.0}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  76. {project_init-0.3.0 → project_init-0.4.0}/.github/ISSUE_TEMPLATE/test.yml +0 -0
  77. {project_init-0.3.0 → project_init-0.4.0}/.github/copilot-instructions.md +0 -0
  78. {project_init-0.3.0 → project_init-0.4.0}/.github/workflows/board-automation.yml +0 -0
  79. {project_init-0.3.0 → project_init-0.4.0}/.github/workflows/ci.yml +0 -0
  80. {project_init-0.3.0 → project_init-0.4.0}/.github/workflows/docs.yml +0 -0
  81. {project_init-0.3.0 → project_init-0.4.0}/.github/workflows/review-status.yml +0 -0
  82. {project_init-0.3.0 → project_init-0.4.0}/.gitignore +0 -0
  83. {project_init-0.3.0 → project_init-0.4.0}/AGENTS.md +0 -0
  84. {project_init-0.3.0 → project_init-0.4.0}/CLAUDE.md +0 -0
  85. {project_init-0.3.0 → project_init-0.4.0}/GEMINI.md +0 -0
  86. {project_init-0.3.0 → project_init-0.4.0}/LICENSE +0 -0
  87. {project_init-0.3.0 → project_init-0.4.0}/cliff.toml +0 -0
  88. {project_init-0.3.0 → project_init-0.4.0}/docs/README.md +0 -0
  89. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-001-scaffolder-design.md +0 -0
  90. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-002-dotglob-template-convention.md +0 -0
  91. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-004-obsidian-docs-integration.md +0 -0
  92. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-006-conventional-commit-titles.md +0 -0
  93. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-007-security-enforcement-layers.md +0 -0
  94. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-008-distribution-channel.md +0 -0
  95. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-009-graphify-memory-preset.md +0 -0
  96. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-010-plugin-marketplace-dual-ship.md +0 -0
  97. {project_init-0.3.0 → project_init-0.4.0}/docs/adr/adr-012-prod-safety-guard.md +0 -0
  98. {project_init-0.3.0 → project_init-0.4.0}/docs/development/build-reproducibility.md +0 -0
  99. {project_init-0.3.0 → project_init-0.4.0}/docs/development/contributing.md +0 -0
  100. {project_init-0.3.0 → project_init-0.4.0}/docs/development/template-system.md +0 -0
  101. {project_init-0.3.0 → project_init-0.4.0}/docs/development/testing.md +0 -0
  102. {project_init-0.3.0 → project_init-0.4.0}/docs/guides/using-project-init.md +0 -0
  103. {project_init-0.3.0 → project_init-0.4.0}/install.sh +0 -0
  104. {project_init-0.3.0 → project_init-0.4.0}/justfile +0 -0
  105. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/.claude-plugin/plugin.json +0 -0
  106. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/hooks/github_command_guard.sh +0 -0
  107. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/hooks/hooks.json +0 -0
  108. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/hooks/post_edit_lint.sh +0 -0
  109. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/hooks/pre_commit_gate.sh +0 -0
  110. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/hooks/session_setup.sh +0 -0
  111. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/hooks/workflow_state_reminder.sh +0 -0
  112. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/add_adr/SKILL.md +0 -0
  113. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/add_command/SKILL.md +0 -0
  114. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/add_hook/SKILL.md +0 -0
  115. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/audit/SKILL.md +0 -0
  116. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/create_issue/SKILL.md +0 -0
  117. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/github_workflow/SKILL.md +0 -0
  118. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/request_review/SKILL.md +0 -0
  119. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/review/SKILL.md +0 -0
  120. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/save_memory/SKILL.md +0 -0
  121. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/session_summary/SKILL.md +0 -0
  122. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/start_task/SKILL.md +0 -0
  123. {project_init-0.3.0 → project_init-0.4.0}/plugins/project-init-workflow/skills/status/SKILL.md +0 -0
  124. {project_init-0.3.0 → project_init-0.4.0}/renovate.json +0 -0
  125. {project_init-0.3.0 → project_init-0.4.0}/src/project_init/mcps.py +0 -0
  126. {project_init-0.3.0 → project_init-0.4.0}/templates/base/AGENTS.md.tmpl +0 -0
  127. {project_init-0.3.0 → project_init-0.4.0}/templates/base/CLAUDE.md.tmpl +0 -0
  128. {project_init-0.3.0 → project_init-0.4.0}/templates/base/CONTRIBUTING.md.tmpl +0 -0
  129. {project_init-0.3.0 → project_init-0.4.0}/templates/base/GEMINI.md.tmpl +0 -0
  130. {project_init-0.3.0 → project_init-0.4.0}/templates/base/LICENSE.tmpl +0 -0
  131. {project_init-0.3.0 → project_init-0.4.0}/templates/base/SECURITY.md.tmpl +0 -0
  132. {project_init-0.3.0 → project_init-0.4.0}/templates/base/docs/explanation/index.md +0 -0
  133. {project_init-0.3.0 → project_init-0.4.0}/templates/base/docs/how-to/index.md +0 -0
  134. {project_init-0.3.0 → project_init-0.4.0}/templates/base/docs/index.md.tmpl +0 -0
  135. {project_init-0.3.0 → project_init-0.4.0}/templates/base/docs/reference/index.md +0 -0
  136. {project_init-0.3.0 → project_init-0.4.0}/templates/base/docs/tutorials/index.md +0 -0
  137. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/agents/README.md +0 -0
  138. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/config.yaml.tmpl +0 -0
  139. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/README.md +0 -0
  140. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/adr/adr-001-memory-stack.md.tmpl +0 -0
  141. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/adr/adr-002-mcp-choices.md.tmpl +0 -0
  142. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/adr/adr-template.md +0 -0
  143. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/development/conventions.md.tmpl +0 -0
  144. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/development/testing.md +0 -0
  145. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/guides/developer-onboarding.md +0 -0
  146. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/guides/issue-metadata.md +0 -0
  147. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/guides/secrets.md +0 -0
  148. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/docs/guides/using-memory.md +0 -0
  149. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/hooks/README.md +0 -0
  150. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/hooks/agent_guard_adapter.py.tmpl +0 -0
  151. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/memory/MEMORY.md.tmpl +0 -0
  152. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/memory/README.md +0 -0
  153. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/memory/SCHEMA.md +0 -0
  154. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/memory/feedback_conventions.md +0 -0
  155. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/memory/project_context.md.tmpl +0 -0
  156. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/memory/user_role.md +0 -0
  157. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/project-init.md.tmpl +0 -0
  158. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/rules/go.md +0 -0
  159. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/rules/hooks.md +0 -0
  160. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/rules/node.md +0 -0
  161. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/rules/python.md +0 -0
  162. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/README.md +0 -0
  163. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/create_nojira_pr.sh +0 -0
  164. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/finish_pr.sh +0 -0
  165. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/promote_review.sh +0 -0
  166. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/push_branch.sh +0 -0
  167. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/push_wiki.sh +0 -0
  168. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/scripts/setup_github.sh +0 -0
  169. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/settings.json.tmpl +0 -0
  170. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/skills/README.md +0 -0
  171. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/skills/plan/SKILL.md.tmpl +0 -0
  172. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/vault/README.md +0 -0
  173. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/vault/decisions/README.md +0 -0
  174. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/vault/design/README.md +0 -0
  175. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/vault/knowledge/README.md +0 -0
  176. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_claude/vault/sessions/README.md +0 -0
  177. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_devcontainer/devcontainer.json.tmpl +0 -0
  178. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_devcontainer/post-create.sh.tmpl +0 -0
  179. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_env.example.tmpl +0 -0
  180. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/CODEOWNERS.tmpl +0 -0
  181. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/ISSUE_TEMPLATE/bug.yml +0 -0
  182. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/ISSUE_TEMPLATE/chore.yml +0 -0
  183. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/ISSUE_TEMPLATE/config.yml +0 -0
  184. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/ISSUE_TEMPLATE/docs.yml +0 -0
  185. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/ISSUE_TEMPLATE/feature.yml +0 -0
  186. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/ISSUE_TEMPLATE/test.yml +0 -0
  187. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/copilot-instructions.md.tmpl +0 -0
  188. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/hooks/commit-msg +0 -0
  189. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/hooks/pre-commit +0 -0
  190. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/hooks/pre-push +0 -0
  191. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/pull_request_template.md +0 -0
  192. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/workflows/docs.yml.tmpl +0 -0
  193. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/workflows/issue-validation.yml +0 -0
  194. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_github/workflows/review-status.yml +0 -0
  195. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_gitignore.tmpl +0 -0
  196. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_golangci.yml.tmpl +0 -0
  197. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_vscode/extensions.json.tmpl +0 -0
  198. {project_init-0.3.0 → project_init-0.4.0}/templates/base/dot_vscode/settings.json.tmpl +0 -0
  199. {project_init-0.3.0 → project_init-0.4.0}/templates/base/eslint.config.mjs.tmpl +0 -0
  200. {project_init-0.3.0 → project_init-0.4.0}/templates/base/mise.toml.tmpl +0 -0
  201. {project_init-0.3.0 → project_init-0.4.0}/templates/base/mkdocs.yml.tmpl +0 -0
  202. {project_init-0.3.0 → project_init-0.4.0}/templates/base/renovate.json +0 -0
  203. {project_init-0.3.0 → project_init-0.4.0}/templates/base/ruff.toml.tmpl +0 -0
  204. {project_init-0.3.0 → project_init-0.4.0}/templates/base/typedoc.json.tmpl +0 -0
  205. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/add_adr/SKILL.md +0 -0
  206. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/add_command/SKILL.md +0 -0
  207. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/add_hook/SKILL.md +0 -0
  208. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/audit/SKILL.md +0 -0
  209. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/create_issue/SKILL.md +0 -0
  210. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/github_workflow/SKILL.md +0 -0
  211. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/request_review/SKILL.md +0 -0
  212. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/review/SKILL.md +0 -0
  213. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/save_memory/SKILL.md +0 -0
  214. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/session_summary/SKILL.md +0 -0
  215. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/start_task/SKILL.md +0 -0
  216. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_agents/skills/status/SKILL.md +0 -0
  217. {project_init-0.3.0 → project_init-0.4.0}/templates/codex/dot_codex/hooks.json.tmpl +0 -0
  218. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/hooks/github_command_guard.sh +0 -0
  219. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/hooks/post_edit_lint.sh +0 -0
  220. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/hooks/pre_commit_gate.sh +0 -0
  221. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/hooks/session_setup.sh +0 -0
  222. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/hooks/workflow_state_reminder.sh +0 -0
  223. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/INDEX.md +0 -0
  224. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/add_adr/SKILL.md +0 -0
  225. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/add_command/SKILL.md +0 -0
  226. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/add_hook/SKILL.md +0 -0
  227. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/audit/SKILL.md +0 -0
  228. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/create_issue/SKILL.md +0 -0
  229. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/github_workflow/SKILL.md +0 -0
  230. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/request_review/SKILL.md +0 -0
  231. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/review/SKILL.md +0 -0
  232. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/save_memory/SKILL.md +0 -0
  233. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/session_summary/SKILL.md +0 -0
  234. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/start_task/SKILL.md +0 -0
  235. {project_init-0.3.0 → project_init-0.4.0}/templates/fallback/dot_claude/skills/status/SKILL.md +0 -0
  236. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/add_adr/SKILL.md +0 -0
  237. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/add_command/SKILL.md +0 -0
  238. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/add_hook/SKILL.md +0 -0
  239. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/audit/SKILL.md +0 -0
  240. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/create_issue/SKILL.md +0 -0
  241. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/github_workflow/SKILL.md +0 -0
  242. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/request_review/SKILL.md +0 -0
  243. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/review/SKILL.md +0 -0
  244. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/save_memory/SKILL.md +0 -0
  245. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/session_summary/SKILL.md +0 -0
  246. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/start_task/SKILL.md +0 -0
  247. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_agents/skills/status/SKILL.md +0 -0
  248. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_claude/scripts/setup_gemini.sh.tmpl +0 -0
  249. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/add_adr.toml +0 -0
  250. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/add_command.toml +0 -0
  251. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/add_hook.toml +0 -0
  252. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/audit.toml +0 -0
  253. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/create_issue.toml +0 -0
  254. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/github_workflow.toml +0 -0
  255. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/request_review.toml +0 -0
  256. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/review.toml +0 -0
  257. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/save_memory.toml +0 -0
  258. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/session_summary.toml +0 -0
  259. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/start_task.toml +0 -0
  260. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/commands/status.toml +0 -0
  261. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/gemini-extension.json.tmpl +0 -0
  262. {project_init-0.3.0 → project_init-0.4.0}/templates/gemini/dot_gemini-extension/hooks/hooks.json.tmpl +0 -0
  263. {project_init-0.3.0 → project_init-0.4.0}/templates/graphify/dot_claude/docs/guides/using-graphify.md +0 -0
  264. {project_init-0.3.0 → project_init-0.4.0}/templates/graphify/dot_claude/rules/graphify.md +0 -0
  265. {project_init-0.3.0 → project_init-0.4.0}/templates/graphify/dot_claude/scripts/setup_graphify.sh +0 -0
  266. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/scripts/lint_memory.sh +0 -0
  267. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/decisions/adr-000-project-setup.md.tmpl +0 -0
  268. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/dot_obsidian/README.md +0 -0
  269. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/dot_obsidian/app.json +0 -0
  270. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/dot_obsidian/community-plugins.json +0 -0
  271. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/dot_obsidian/core-plugins.json +0 -0
  272. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/log.md +0 -0
  273. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/templates/decision.md +0 -0
  274. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/templates/design-note.md +0 -0
  275. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/templates/knowledge-note.md +0 -0
  276. {project_init-0.3.0 → project_init-0.4.0}/templates/obsidian/dot_claude/vault/templates/session-note.md +0 -0
  277. {project_init-0.3.0 → project_init-0.4.0}/templates/presets/obsidian-graphify.toml +0 -0
  278. {project_init-0.3.0 → project_init-0.4.0}/templates/presets/obsidian-only.toml +0 -0
  279. {project_init-0.3.0 → project_init-0.4.0}/tests/__init__.py +0 -0
  280. {project_init-0.3.0 → project_init-0.4.0}/tests/conftest.py +0 -0
  281. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_agents_canonical.py +0 -0
  282. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_env_tooling.py +0 -0
  283. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_integrity.py +0 -0
  284. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_plugin_marketplace.py +0 -0
  285. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_quality_toolchain.py +0 -0
  286. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_renovate.py +0 -0
  287. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_scaffold_graphify.py +0 -0
  288. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_session_bootstrap.py +0 -0
  289. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_skill_index.py +0 -0
  290. {project_init-0.3.0 → project_init-0.4.0}/tests/contracts/test_wiki_skill.py +0 -0
  291. {project_init-0.3.0 → project_init-0.4.0}/tests/helpers.py +0 -0
  292. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_memory_starters.py +0 -0
  293. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_quality_gate_lint.py +0 -0
  294. {project_init-0.3.0 → project_init-0.4.0}/tests/integration/test_session_cold_start.py +0 -0
  295. {project_init-0.3.0 → project_init-0.4.0}/tests/smoke/test_packaging.py +0 -0
  296. {project_init-0.3.0 → project_init-0.4.0}/tests/unit/test_command_variables.py +0 -0
  297. {project_init-0.3.0 → project_init-0.4.0}/tests/unit/test_mcps.py +0 -0
  298. {project_init-0.3.0 → project_init-0.4.0}/tools/sync_plugin.py +0 -0
@@ -244,7 +244,11 @@ COMMAND_RULES: list[tuple[re.Pattern[str], str | None, str]] = [
244
244
  "Direct pushes to main/master are blocked. Open a feature branch and PR.",
245
245
  ),
246
246
  (
247
- re.compile(r"\bgh\s+api\s+repos/[^/\s]+/[^/\s]+/pulls/\d+/merge\b"),
247
+ # `[^&;]*?` allows flags between `gh api` and the endpoint (a real merge
248
+ # needs `--method PUT`/`-X PUT`, often written before the path). `|` is NOT
249
+ # excluded: a pipe inside a quoted flag (e.g. `--jq '.a|.b'`) must not let a
250
+ # merge slip past the guard — only `;`/`&` end the command segment (PI-198).
251
+ re.compile(r"\bgh\s+api\b[^&;]*?repos/[^/\s]+/[^/\s]+/pulls/\d+/merge\b"),
248
252
  "pr.merged",
249
253
  "Use .claude/scripts/monitor_pr.sh <pr> --merge instead of `gh api .../merge` so CI and review gates are honored.",
250
254
  ),
@@ -380,8 +384,12 @@ def cmd_push(branch: str | None, max_retries: int, *, force: bool = False) -> in
380
384
  if not branch:
381
385
  sys.stderr.write("push: no current branch\n")
382
386
  return 1
383
- if force and branch in ("main", "master"):
384
- sys.stderr.write("push: refusing to force-push main/master\n")
387
+ if branch in ("main", "master"):
388
+ # Refuse main/master for ANY push, not only force-pushes — otherwise
389
+ # running push_branch.sh while on main bypasses the direct-push guard,
390
+ # since the internal `git push` subprocess is invisible to the
391
+ # PreToolUse hook (PI-202).
392
+ sys.stderr.write("push: refusing to push main/master directly — open a feature branch + PR\n")
385
393
  return 1
386
394
 
387
395
  code, sha_out = _git(["rev-parse", branch])
@@ -16,10 +16,10 @@
16
16
  #
17
17
  # Full lifecycle for agents:
18
18
  # 1. .claude/scripts/monitor_pr.sh <n> --merge
19
- # 2. Exit 2 review comments printed read and address them, push
19
+ # 2. Exit 2 -> review comments printed -> read and address them, push
20
20
  # 3. Re-run with --review-cycle 1
21
- # 4. Exit 2 again address remaining comments, push
22
- # 5. Re-run with --review-cycle 2 admin-merge fires if still blocked
21
+ # 4. Exit 2 again -> address remaining comments, push
22
+ # 5. Re-run with --review-cycle 2 -> admin-merge fires if still blocked
23
23
  #
24
24
  # Review cycle policy:
25
25
  # Two fix cycles are required before admin-merge is allowed. This ensures
@@ -61,8 +61,8 @@ _count_pending() {
61
61
  echo "$1" | python3 -c "
62
62
  import json, sys
63
63
  data = json.load(sys.stdin)
64
- # Exclude review/decision it is a derived commit status that only appears after a review
65
- # is submitted. We detect review state directly via reviewDecision below.
64
+ # Exclude review/decision; it is a derived commit status that only appears
65
+ # after a review event. We detect review state directly via reviewDecision.
66
66
  print(sum(1 for c in data if c.get('name') != 'review/decision' and c.get('state') in ('PENDING', 'IN_PROGRESS', 'EXPECTED')))
67
67
  "
68
68
  }
@@ -101,15 +101,35 @@ _print_review_comments() {
101
101
  echo " Full PR: $(gh pr view "$PR_NUMBER" --json url -q '.url' 2>/dev/null || true)"
102
102
  }
103
103
 
104
+ _run_gh() {
105
+ local output
106
+ local status
107
+
108
+ set +e
109
+ output=$(GH_PROMPT_DISABLED=1 gh "$@" 2>&1)
110
+ status=$?
111
+ set -e
112
+
113
+ if [ -n "$output" ]; then
114
+ printf '%s\n' "$output" | grep -v "^$" || true
115
+ fi
116
+
117
+ return "$status"
118
+ }
119
+
104
120
  _admin_merge() {
105
- GH_PROMPT_DISABLED=1 gh pr merge "$PR_NUMBER" --squash --delete-branch --admin 2>&1 | grep -v "^$" || true
106
- echo "Merged PR #$PR_NUMBER (admin)"
121
+ if _run_gh pr merge "$PR_NUMBER" --squash --delete-branch --admin; then
122
+ echo "Merged PR #$PR_NUMBER (admin)"
123
+ else
124
+ echo "ERROR: admin merge failed for PR #$PR_NUMBER" >&2
125
+ return 1
126
+ fi
107
127
  }
108
128
 
109
- # Query the PR's aggregate review decision directly — source of truth regardless of
110
- # whether the review/decision commit status has been posted yet.
129
+ # Query the PR's aggregate review decision directly — source of truth regardless
130
+ # of whether the review/decision commit status has been posted yet.
111
131
  # Returns: APPROVED | CHANGES_REQUESTED | REVIEW_REQUIRED | (empty = no review policy)
112
- # Returns UNKNOWN on API failure — callers must treat this as fail-closed (do not merge).
132
+ # Returns UNKNOWN on API failure — callers must treat this as fail-closed.
113
133
  _get_review_decision() {
114
134
  gh pr view "$PR_NUMBER" --json reviewDecision -q '.reviewDecision // ""' 2>/dev/null || echo "UNKNOWN"
115
135
  }
@@ -126,14 +146,28 @@ _has_review_activity() {
126
146
  # --- Wait for all CI checks (excludes review/decision commit status) ---
127
147
  # Guard: if checks haven't registered yet (empty list), keep polling.
128
148
  # An empty list is indistinguishable from "all done" without this guard,
129
- # which caused #104 to merge before CI even started.
149
+ # which caused premature merges before CI even started. Bounded by CI_TIMEOUT
150
+ # so a required check that never registers can't hang the script — and any
151
+ # autonomous caller — forever; fail closed on timeout (PI-186/PI-203).
152
+ CI_TIMEOUT=900
153
+ CI_ELAPSED=0
130
154
  while true; do
131
155
  CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket 2>/dev/null) || CHECKS="[]"
132
156
  CHECK_COUNT=$(echo "$CHECKS" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
133
- [ "$CHECK_COUNT" -eq 0 ] && { sleep 10; continue; }
134
- PENDING=$(_count_pending "$CHECKS")
135
- [ "$PENDING" -eq 0 ] && break
157
+ if [ "$CHECK_COUNT" -gt 0 ] && [ "$(_count_pending "$CHECKS")" -eq 0 ]; then
158
+ break
159
+ fi
160
+ if [ "$CI_ELAPSED" -ge "$CI_TIMEOUT" ]; then
161
+ echo "PR #$PR_NUMBER: CI did not settle within ${CI_TIMEOUT}s. Still pending:"
162
+ echo "$CHECKS" | python3 -c "import json,sys
163
+ for c in json.load(sys.stdin):
164
+ if c.get('name') != 'review/decision' and c.get('state') in ('PENDING','IN_PROGRESS','EXPECTED'):
165
+ print(' -', c.get('name'))" 2>/dev/null || true
166
+ echo "Re-run once the check registers, or investigate why it never started."
167
+ exit 1
168
+ fi
136
169
  sleep 10
170
+ CI_ELAPSED=$((CI_ELAPSED + 10))
137
171
  done
138
172
 
139
173
  FAIL_CODE=0
@@ -151,14 +185,14 @@ if [ "$NO_REVIEW" -eq 1 ] && [ "$MODE" = "--merge" ]; then
151
185
  _admin_merge; exit 0
152
186
  fi
153
187
 
154
- # --- Wait up to 6 min for a reviewer to act (bounded replacement for the original infinite wait) ---
155
- # We query reviewDecision directly so this works even before the review/decision commit
156
- # status is created (which only happens after the first review event fires review-status.yml).
188
+ # --- Wait up to 6 min for a reviewer to act ---
189
+ # Query reviewDecision directly so this works before review-status.yml creates
190
+ # the derived review/decision commit status.
157
191
  REVIEW_TIMEOUT=360
158
192
  REVIEW_ELAPSED=0
159
193
  REVIEW_DECISION=$(_get_review_decision)
160
194
  if [ "$MODE" = "--merge" ] && [ "$REVIEW_CYCLE" -ge "$MAX_REVIEW_CYCLES" ] && [ "$REVIEW_DECISION" = "REVIEW_REQUIRED" ]; then
161
- echo "Max review cycles ($MAX_REVIEW_CYCLES) reached — force-merging with admin override."
195
+ echo "Max review cycles ($MAX_REVIEW_CYCLES) reached — skipping reviewer wait and force-merging with admin override."
162
196
  _admin_merge; exit 0
163
197
  fi
164
198
 
@@ -176,17 +210,14 @@ while { [ "$REVIEW_DECISION" = "REVIEW_REQUIRED" ] || [ "$REVIEW_DECISION" = "UN
176
210
  echo " Review comments detected — proceeding without waiting for formal approval."
177
211
  break
178
212
  fi
179
- # Refresh CHECKS too so late-arriving CI failures are caught
180
213
  CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket 2>/dev/null) || CHECKS="[]"
181
214
  done
182
215
 
183
- # Fail closed: if reviewDecision could not be fetched, do not merge
184
216
  if [ "$REVIEW_DECISION" = "UNKNOWN" ]; then
185
217
  echo "ERROR: could not fetch reviewDecision for PR #$PR_NUMBER — cannot verify review state." >&2
186
218
  exit 2
187
219
  fi
188
220
 
189
- # Handle review outcome
190
221
  if [ "$REVIEW_DECISION" = "CHANGES_REQUESTED" ]; then
191
222
  echo "Review/decision failed on PR #$PR_NUMBER (cycle $REVIEW_CYCLE/$MAX_REVIEW_CYCLES):"
192
223
  _print_review_comments
@@ -227,20 +258,24 @@ PR_URL=$(gh pr view "$PR_NUMBER" --json url -q '.url')
227
258
  echo "PR #$PR_NUMBER passed: $PR_URL"
228
259
 
229
260
  if [ "$MODE" = "--merge" ]; then
230
- # Check merge state: CLEAN = mergeable now, BLOCKED = review/branch protection gating
231
261
  MERGE_STATE=$(gh pr view "$PR_NUMBER" --json mergeStateStatus -q '.mergeStateStatus' 2>/dev/null || echo "UNKNOWN")
232
262
 
233
263
  if [ "$MERGE_STATE" = "CLEAN" ] || [ "$MERGE_STATE" = "UNSTABLE" ]; then
234
- GH_PROMPT_DISABLED=1 gh pr merge "$PR_NUMBER" --squash --delete-branch 2>&1 | grep -v "^$" || true
235
- echo "Merged PR #$PR_NUMBER"
264
+ if _run_gh pr merge "$PR_NUMBER" --squash --delete-branch; then
265
+ echo "Merged PR #$PR_NUMBER"
266
+ else
267
+ echo "ERROR: merge failed for PR #$PR_NUMBER" >&2
268
+ exit 1
269
+ fi
236
270
  elif [ "$MERGE_STATE" = "BLOCKED" ]; then
237
- # CI passed but review protection is the only gate — admin merge (owner bypass)
238
271
  echo "PR is blocked by review protection — merging with admin override."
239
272
  _admin_merge
240
273
  else
241
- # Unknown state try direct first, fall back to --auto
242
- if ! GH_PROMPT_DISABLED=1 gh pr merge "$PR_NUMBER" --squash --delete-branch 2>/dev/null; then
243
- GH_PROMPT_DISABLED=1 gh pr merge "$PR_NUMBER" --squash --delete-branch --auto 2>&1 | grep -v "^$" || true
274
+ if ! _run_gh pr merge "$PR_NUMBER" --squash --delete-branch; then
275
+ if ! _run_gh pr merge "$PR_NUMBER" --squash --delete-branch --auto; then
276
+ echo "ERROR: could not merge or enable auto-merge for PR #$PR_NUMBER" >&2
277
+ exit 1
278
+ fi
244
279
  echo "Auto-merge enabled for PR #$PR_NUMBER — will merge once all requirements are met."
245
280
  else
246
281
  echo "Merged PR #$PR_NUMBER"
@@ -50,7 +50,12 @@ jobs:
50
50
  - name: Create GitHub Release
51
51
  uses: softprops/action-gh-release@v3
52
52
  with:
53
- files: dist/*
53
+ # Only the build artifacts — an unscoped glob also picks up the
54
+ # tracked dist/.gitignore and uploads it as a stray
55
+ # `default.gitignore` asset (PI-183).
56
+ files: |
57
+ dist/*.whl
58
+ dist/*.tar.gz
54
59
  body_path: RELEASE_NOTES.md
55
60
 
56
61
  # PyPI via trusted publishing (ADR-011): OIDC, no tokens to custody.
@@ -62,3 +62,32 @@ jobs:
62
62
  echo "ERROR: PR body must include 'Closes #<number>' to auto-close the linked issue"
63
63
  exit 1
64
64
  fi
65
+
66
+ # Extract the issue key from the title scope, e.g. fix(PI-7): ... -> PI-7.
67
+ TITLE_KEY=$(echo "$PR_TITLE" | sed -nE 's/.*\(([A-Z][A-Z0-9]*-[0-9]+)\).*/\1/p')
68
+ TITLE_KEY_NUM=$(echo "$TITLE_KEY" | grep -oE '[0-9]+$' || true)
69
+
70
+ # Defense in depth: when the branch is *also* issue-linked, its key must
71
+ # match the title scope key. Otherwise a PR on branch fix/PI-99-* with
72
+ # title fix(PI-1): ... + body "Closes #1" passes every other check while
73
+ # closing the wrong issue off the wrong branch (PI-200 review).
74
+ BRANCH_KEY_NUM=$(echo "$PR_BRANCH" | sed -nE 's#^(feat|fix|chore|docs|test)/[A-Z][A-Z0-9]*-([0-9]+)-.*#\2#p')
75
+ if [ -n "$TITLE_KEY_NUM" ] && [ -n "$BRANCH_KEY_NUM" ] && [ "$TITLE_KEY_NUM" != "$BRANCH_KEY_NUM" ]; then
76
+ echo "ERROR: branch issue key (#$BRANCH_KEY_NUM) and title scope key ($TITLE_KEY -> #$TITLE_KEY_NUM) reference different issues."
77
+ echo "Rename the branch or fix the title so both point at the same issue."
78
+ exit 1
79
+ fi
80
+
81
+ # Verify the title scope key number is among the Closes numbers — a
82
+ # type(PI-N): ... PR must Closes #N (PI-N maps to issue #N), so it
83
+ # can't auto-close an unrelated issue (PI-200).
84
+ if [ -n "$TITLE_KEY_NUM" ]; then
85
+ CLOSES_NUMS=$(echo "$PR_BODY" | grep -oiE 'closes\s+#[0-9]+' | grep -oE '[0-9]+' || true)
86
+ if echo "$CLOSES_NUMS" | grep -qx "$TITLE_KEY_NUM"; then
87
+ echo "Closes #$TITLE_KEY_NUM matches the title scope key $TITLE_KEY"
88
+ else
89
+ echo "ERROR: title scope key $TITLE_KEY (#$TITLE_KEY_NUM) is not among the Closes numbers: $(echo $CLOSES_NUMS | tr '\n' ' ')"
90
+ echo "A '$TITLE_KEY' PR must 'Closes #$TITLE_KEY_NUM' — PI-N maps to issue #N."
91
+ exit 1
92
+ fi
93
+ fi
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: project-init
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Scaffolder for agentic-development infrastructure inside any project
5
5
  Project-URL: Repository, https://github.com/VytCepas/project-init
6
6
  Project-URL: Documentation, https://vytcepas.github.io/project-init/
@@ -329,7 +329,6 @@ project-init/
329
329
  │ ├── obsidian/ # Obsidian vault overlay (both presets)
330
330
  │ ├── graphify/ # Graphify memory overlay
331
331
  │ └── presets/ # toml preset definitions
332
- ├── examples/ # sample scaffolded outputs
333
332
  └── tests/ # focused pytest modules by behavior area
334
333
  ```
335
334
 
@@ -298,7 +298,6 @@ project-init/
298
298
  │ ├── obsidian/ # Obsidian vault overlay (both presets)
299
299
  │ ├── graphify/ # Graphify memory overlay
300
300
  │ └── presets/ # toml preset definitions
301
- ├── examples/ # sample scaffolded outputs
302
301
  └── tests/ # focused pytest modules by behavior area
303
302
  ```
304
303
 
@@ -1,7 +1,7 @@
1
1
  # ADR-003: GitHub-native project management — GitHub Projects + Issues over Linear
2
2
 
3
3
  **Date:** 2026-04-27
4
- **Status:** Accepted
4
+ **Status:** Accepted — superseded in part by [ADR-006](adr-006-conventional-commit-titles.md) (the PR/commit title format)
5
5
 
6
6
  ## Context
7
7
 
@@ -1,7 +1,7 @@
1
1
  # ADR-005: GitHub PR and board lifecycle workflow
2
2
 
3
3
  **Date:** 2026-04-27
4
- **Status:** Accepted
4
+ **Status:** Accepted — superseded in part by [ADR-006](adr-006-conventional-commit-titles.md) (the PR/commit title format)
5
5
 
6
6
  ## Context
7
7
 
@@ -28,7 +28,9 @@ Publish every tagged release to PyPI from the existing `release.yml` via a
28
28
 
29
29
  - Runs only after the GitHub Release job succeeds (`needs: release`).
30
30
  - Authenticates via OIDC (`id-token: write`) against the `pypi`
31
- environment protection rules can be added on the environment later.
31
+ environment, which is restricted to `v*` tags so only a tagged release
32
+ can deploy to it (PI-184); a required reviewer can be added later if a
33
+ manual approval gate before each publish is wanted.
32
34
  - Uses `pypa/gh-action-pypi-publish` (the canonical action; `release/v1`
33
35
  per its own guidance — Renovate's `pinGitHubActionDigests` preset will
34
36
  pin it to a digest on its next run, like every other action here).
@@ -46,6 +48,9 @@ Before the first publish, register the *pending publisher* on pypi.org
46
48
  repository `project-init`, workflow `release.yml`, environment `pypi`.
47
49
  The first tagged release after that claims the name.
48
50
 
51
+ Done for v0.3.0 (#171): the publisher is registered, the `pypi`
52
+ environment exists and is tag-restricted, and v0.3.0 is live on PyPI.
53
+
49
54
  ## Consequences
50
55
 
51
56
  - Anyone can install without trusting a curl-pipe-bash script.
@@ -20,11 +20,21 @@ theme:
20
20
 
21
21
  nav:
22
22
  - Home: README.md
23
+ - Guide:
24
+ - Using project-init: guides/using-project-init.md
23
25
  - Architecture Decisions:
24
26
  - Overview: adr/adr-001-scaffolder-design.md
25
27
  - dot_ prefix convention: adr/adr-002-dotglob-template-convention.md
26
28
  - GitHub-native workflow: adr/adr-003-github-native-workflow.md
27
29
  - Obsidian + docs integration: adr/adr-004-obsidian-docs-integration.md
30
+ - PR & board lifecycle: adr/adr-005-github-pr-board-workflow.md
31
+ - Conventional commit titles: adr/adr-006-conventional-commit-titles.md
32
+ - Security enforcement layers: adr/adr-007-security-enforcement-layers.md
33
+ - Distribution channel: adr/adr-008-distribution-channel.md
34
+ - Graphify memory preset: adr/adr-009-graphify-memory-preset.md
35
+ - Plugin marketplace dual-ship: adr/adr-010-plugin-marketplace-dual-ship.md
36
+ - PyPI trusted publishing: adr/adr-011-pypi-trusted-publishing.md
37
+ - Prod-safety guard: adr/adr-012-prod-safety-guard.md
28
38
  - Development:
29
39
  - Template system: development/template-system.md
30
40
  - Testing: development/testing.md
@@ -244,7 +244,11 @@ COMMAND_RULES: list[tuple[re.Pattern[str], str | None, str]] = [
244
244
  "Direct pushes to main/master are blocked. Open a feature branch and PR.",
245
245
  ),
246
246
  (
247
- re.compile(r"\bgh\s+api\s+repos/[^/\s]+/[^/\s]+/pulls/\d+/merge\b"),
247
+ # `[^&;]*?` allows flags between `gh api` and the endpoint (a real merge
248
+ # needs `--method PUT`/`-X PUT`, often written before the path). `|` is NOT
249
+ # excluded: a pipe inside a quoted flag (e.g. `--jq '.a|.b'`) must not let a
250
+ # merge slip past the guard — only `;`/`&` end the command segment (PI-198).
251
+ re.compile(r"\bgh\s+api\b[^&;]*?repos/[^/\s]+/[^/\s]+/pulls/\d+/merge\b"),
248
252
  "pr.merged",
249
253
  "Use .claude/scripts/monitor_pr.sh <pr> --merge instead of `gh api .../merge` so CI and review gates are honored.",
250
254
  ),
@@ -380,8 +384,12 @@ def cmd_push(branch: str | None, max_retries: int, *, force: bool = False) -> in
380
384
  if not branch:
381
385
  sys.stderr.write("push: no current branch\n")
382
386
  return 1
383
- if force and branch in ("main", "master"):
384
- sys.stderr.write("push: refusing to force-push main/master\n")
387
+ if branch in ("main", "master"):
388
+ # Refuse main/master for ANY push, not only force-pushes — otherwise
389
+ # running push_branch.sh while on main bypasses the direct-push guard,
390
+ # since the internal `git push` subprocess is invisible to the
391
+ # PreToolUse hook (PI-202).
392
+ sys.stderr.write("push: refusing to push main/master directly — open a feature branch + PR\n")
385
393
  return 1
386
394
 
387
395
  code, sha_out = _git(["rev-parse", branch])
@@ -64,8 +64,21 @@ def _find_config(start: Path) -> Path | None:
64
64
  return None
65
65
 
66
66
 
67
+ def _unquote(value: str) -> str:
68
+ """Strip one pair of matching surrounding quotes, leaving mismatched or
69
+ single quotes intact so ``'foo"`` is not silently corrupted (PI-187 review).
70
+ """
71
+ if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
72
+ return value[1:-1]
73
+ return value
74
+
75
+
67
76
  def _allow_patterns(root: Path) -> list[re.Pattern[str]]:
68
- """Read the safety.allow JSON list from .claude/config.yaml (fail-open).
77
+ """Read the safety.allow list from .claude/config.yaml (fail-open).
78
+
79
+ Accepts both an inline JSON list (``allow: ["a", "b"]``) and a multi-line
80
+ YAML list (``allow:`` on its own line followed by ``- "a"`` items). The
81
+ inline-only parser silently dropped the natural YAML form to ``[]`` (PI-187).
69
82
 
70
83
  *root* is the Bash tool's cwd, which may be a subdirectory after `cd` —
71
84
  the config is located by walking up the tree.
@@ -73,22 +86,40 @@ def _allow_patterns(root: Path) -> list[re.Pattern[str]]:
73
86
  config = _find_config(root)
74
87
  if config is None:
75
88
  return []
89
+ patterns: list[str] = []
76
90
  try:
77
91
  in_safety = False
92
+ in_allow = False
78
93
  for line in config.read_text(encoding="utf-8").splitlines():
79
94
  if line.startswith("safety:"):
80
95
  in_safety = True
81
96
  continue
82
- if in_safety:
83
- if line.strip() and not line.startswith(" "):
84
- break
85
- stripped = line.strip()
86
- if stripped.startswith("allow:"):
87
- raw = stripped.split(":", 1)[1].strip()
88
- return [re.compile(p) for p in json.loads(raw)]
97
+ if not in_safety:
98
+ continue
99
+ if line.strip() and not line.startswith((" ", "\t")):
100
+ break # a column-0 key ends the safety block
101
+ stripped = line.strip()
102
+ if not stripped or stripped.startswith("#"):
103
+ continue
104
+ if in_allow and stripped.startswith("- "):
105
+ patterns.append(_unquote(stripped[2:].strip()))
106
+ continue
107
+ in_allow = False
108
+ if stripped.startswith("allow:"):
109
+ raw = stripped.split(":", 1)[1].strip()
110
+ if raw:
111
+ parsed = json.loads(raw) # inline JSON list
112
+ # A non-list allow (JSON string/object/number) must not be
113
+ # iterated character-by-character into an over-permissive
114
+ # allowlist or crash the guard — ignore it and keep
115
+ # guarding (PI-187 review).
116
+ if isinstance(parsed, list):
117
+ patterns.extend(p for p in parsed if isinstance(p, str))
118
+ else:
119
+ in_allow = True # multi-line YAML list follows
120
+ return [re.compile(p) for p in patterns if p]
89
121
  except (OSError, json.JSONDecodeError, re.error):
90
- pass
91
- return []
122
+ return []
92
123
 
93
124
 
94
125
  def evaluate(command: str, permission_mode: str, allow: list[re.Pattern[str]]) -> dict | None:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "project-init"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Scaffolder for agentic-development infrastructure inside any project"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,4 +1,4 @@
1
1
  """project-init — scaffolder for agentic-development infrastructure."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.4.0"
4
4
  __repo_url__ = "https://github.com/VytCepas/project-init"