project-init 0.3.0__py3-none-any.whl

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 (173) hide show
  1. project_init/__init__.py +4 -0
  2. project_init/__main__.py +662 -0
  3. project_init/mcps.py +57 -0
  4. project_init/scaffold.py +374 -0
  5. project_init/templates/base/AGENTS.md.tmpl +50 -0
  6. project_init/templates/base/CLAUDE.md.tmpl +16 -0
  7. project_init/templates/base/CONTRIBUTING.md.tmpl +55 -0
  8. project_init/templates/base/GEMINI.md.tmpl +16 -0
  9. project_init/templates/base/LICENSE.tmpl +231 -0
  10. project_init/templates/base/SECURITY.md.tmpl +26 -0
  11. project_init/templates/base/docs/explanation/index.md +9 -0
  12. project_init/templates/base/docs/how-to/index.md +7 -0
  13. project_init/templates/base/docs/index.md.tmpl +20 -0
  14. project_init/templates/base/docs/reference/index.md +13 -0
  15. project_init/templates/base/docs/tutorials/index.md +7 -0
  16. project_init/templates/base/dot_claude/agents/README.md +30 -0
  17. project_init/templates/base/dot_claude/config.yaml.tmpl +31 -0
  18. project_init/templates/base/dot_claude/docs/README.md +26 -0
  19. project_init/templates/base/dot_claude/docs/adr/adr-001-memory-stack.md.tmpl +22 -0
  20. project_init/templates/base/dot_claude/docs/adr/adr-002-mcp-choices.md.tmpl +32 -0
  21. project_init/templates/base/dot_claude/docs/adr/adr-template.md +29 -0
  22. project_init/templates/base/dot_claude/docs/development/conventions.md.tmpl +31 -0
  23. project_init/templates/base/dot_claude/docs/development/testing.md +25 -0
  24. project_init/templates/base/dot_claude/docs/guides/developer-onboarding.md +110 -0
  25. project_init/templates/base/dot_claude/docs/guides/issue-metadata.md +27 -0
  26. project_init/templates/base/dot_claude/docs/guides/secrets.md +50 -0
  27. project_init/templates/base/dot_claude/docs/guides/using-memory.md +36 -0
  28. project_init/templates/base/dot_claude/hooks/README.md +15 -0
  29. project_init/templates/base/dot_claude/hooks/agent_guard_adapter.py.tmpl +64 -0
  30. project_init/templates/base/dot_claude/hooks/dag_workflow.py +610 -0
  31. project_init/templates/base/dot_claude/memory/MEMORY.md.tmpl +11 -0
  32. project_init/templates/base/dot_claude/memory/README.md +51 -0
  33. project_init/templates/base/dot_claude/memory/SCHEMA.md +52 -0
  34. project_init/templates/base/dot_claude/memory/feedback_conventions.md +11 -0
  35. project_init/templates/base/dot_claude/memory/project_context.md.tmpl +11 -0
  36. project_init/templates/base/dot_claude/memory/user_role.md +7 -0
  37. project_init/templates/base/dot_claude/project-init.md.tmpl +174 -0
  38. project_init/templates/base/dot_claude/rules/go.md +14 -0
  39. project_init/templates/base/dot_claude/rules/hooks.md +30 -0
  40. project_init/templates/base/dot_claude/rules/node.md +17 -0
  41. project_init/templates/base/dot_claude/rules/python.md +25 -0
  42. project_init/templates/base/dot_claude/scripts/README.md +15 -0
  43. project_init/templates/base/dot_claude/scripts/create_issue.sh +577 -0
  44. project_init/templates/base/dot_claude/scripts/create_nojira_pr.sh +3 -0
  45. project_init/templates/base/dot_claude/scripts/finish_pr.sh +3 -0
  46. project_init/templates/base/dot_claude/scripts/install_hooks.sh +55 -0
  47. project_init/templates/base/dot_claude/scripts/monitor_pr.sh +270 -0
  48. project_init/templates/base/dot_claude/scripts/promote_review.sh +3 -0
  49. project_init/templates/base/dot_claude/scripts/push_branch.sh +5 -0
  50. project_init/templates/base/dot_claude/scripts/push_wiki.sh +34 -0
  51. project_init/templates/base/dot_claude/scripts/setup_github.sh +219 -0
  52. project_init/templates/base/dot_claude/scripts/start_issue.sh +134 -0
  53. project_init/templates/base/dot_claude/settings.json.tmpl +83 -0
  54. project_init/templates/base/dot_claude/skills/README.md +12 -0
  55. project_init/templates/base/dot_claude/skills/plan/SKILL.md.tmpl +40 -0
  56. project_init/templates/base/dot_claude/vault/README.md +21 -0
  57. project_init/templates/base/dot_claude/vault/decisions/README.md +22 -0
  58. project_init/templates/base/dot_claude/vault/design/README.md +3 -0
  59. project_init/templates/base/dot_claude/vault/knowledge/README.md +5 -0
  60. project_init/templates/base/dot_claude/vault/sessions/README.md +5 -0
  61. project_init/templates/base/dot_devcontainer/devcontainer.json.tmpl +17 -0
  62. project_init/templates/base/dot_devcontainer/post-create.sh.tmpl +31 -0
  63. project_init/templates/base/dot_env.example.tmpl +13 -0
  64. project_init/templates/base/dot_github/CODEOWNERS.tmpl +12 -0
  65. project_init/templates/base/dot_github/ISSUE_TEMPLATE/bug.yml +98 -0
  66. project_init/templates/base/dot_github/ISSUE_TEMPLATE/chore.yml +82 -0
  67. project_init/templates/base/dot_github/ISSUE_TEMPLATE/config.yml +5 -0
  68. project_init/templates/base/dot_github/ISSUE_TEMPLATE/docs.yml +84 -0
  69. project_init/templates/base/dot_github/ISSUE_TEMPLATE/feature.yml +87 -0
  70. project_init/templates/base/dot_github/ISSUE_TEMPLATE/test.yml +90 -0
  71. project_init/templates/base/dot_github/copilot-instructions.md.tmpl +25 -0
  72. project_init/templates/base/dot_github/hooks/commit-msg +52 -0
  73. project_init/templates/base/dot_github/hooks/pre-commit +16 -0
  74. project_init/templates/base/dot_github/hooks/pre-push +51 -0
  75. project_init/templates/base/dot_github/pull_request_template.md +22 -0
  76. project_init/templates/base/dot_github/workflows/board-automation.yml +232 -0
  77. project_init/templates/base/dot_github/workflows/ci.yml.tmpl +204 -0
  78. project_init/templates/base/dot_github/workflows/docs.yml.tmpl +98 -0
  79. project_init/templates/base/dot_github/workflows/issue-validation.yml +72 -0
  80. project_init/templates/base/dot_github/workflows/review-status.yml +48 -0
  81. project_init/templates/base/dot_github/workflows/validate-pr.yml +103 -0
  82. project_init/templates/base/dot_gitignore.tmpl +41 -0
  83. project_init/templates/base/dot_golangci.yml.tmpl +20 -0
  84. project_init/templates/base/dot_vscode/extensions.json.tmpl +10 -0
  85. project_init/templates/base/dot_vscode/settings.json.tmpl +8 -0
  86. project_init/templates/base/eslint.config.mjs.tmpl +29 -0
  87. project_init/templates/base/justfile.tmpl +95 -0
  88. project_init/templates/base/mise.toml.tmpl +20 -0
  89. project_init/templates/base/mkdocs.yml.tmpl +32 -0
  90. project_init/templates/base/renovate.json +14 -0
  91. project_init/templates/base/ruff.toml.tmpl +31 -0
  92. project_init/templates/base/typedoc.json.tmpl +14 -0
  93. project_init/templates/codex/dot_agents/skills/add_adr/SKILL.md +33 -0
  94. project_init/templates/codex/dot_agents/skills/add_command/SKILL.md +63 -0
  95. project_init/templates/codex/dot_agents/skills/add_hook/SKILL.md +112 -0
  96. project_init/templates/codex/dot_agents/skills/audit/SKILL.md +146 -0
  97. project_init/templates/codex/dot_agents/skills/create_issue/SKILL.md +59 -0
  98. project_init/templates/codex/dot_agents/skills/github_workflow/SKILL.md +80 -0
  99. project_init/templates/codex/dot_agents/skills/request_review/SKILL.md +19 -0
  100. project_init/templates/codex/dot_agents/skills/review/SKILL.md +17 -0
  101. project_init/templates/codex/dot_agents/skills/save_memory/SKILL.md +17 -0
  102. project_init/templates/codex/dot_agents/skills/session_summary/SKILL.md +35 -0
  103. project_init/templates/codex/dot_agents/skills/start_task/SKILL.md +48 -0
  104. project_init/templates/codex/dot_agents/skills/status/SKILL.md +15 -0
  105. project_init/templates/codex/dot_codex/hooks.json.tmpl +17 -0
  106. project_init/templates/fallback/dot_claude/hooks/github_command_guard.sh +11 -0
  107. project_init/templates/fallback/dot_claude/hooks/post_edit_lint.sh +58 -0
  108. project_init/templates/fallback/dot_claude/hooks/pre_commit_gate.sh +81 -0
  109. project_init/templates/fallback/dot_claude/hooks/prod_guard.py +140 -0
  110. project_init/templates/fallback/dot_claude/hooks/session_setup.sh +62 -0
  111. project_init/templates/fallback/dot_claude/hooks/workflow_state_reminder.sh +72 -0
  112. project_init/templates/fallback/dot_claude/skills/INDEX.md +28 -0
  113. project_init/templates/fallback/dot_claude/skills/add_adr/SKILL.md +33 -0
  114. project_init/templates/fallback/dot_claude/skills/add_command/SKILL.md +63 -0
  115. project_init/templates/fallback/dot_claude/skills/add_hook/SKILL.md +112 -0
  116. project_init/templates/fallback/dot_claude/skills/audit/SKILL.md +146 -0
  117. project_init/templates/fallback/dot_claude/skills/create_issue/SKILL.md +59 -0
  118. project_init/templates/fallback/dot_claude/skills/github_workflow/SKILL.md +80 -0
  119. project_init/templates/fallback/dot_claude/skills/request_review/SKILL.md +19 -0
  120. project_init/templates/fallback/dot_claude/skills/review/SKILL.md +17 -0
  121. project_init/templates/fallback/dot_claude/skills/save_memory/SKILL.md +17 -0
  122. project_init/templates/fallback/dot_claude/skills/session_summary/SKILL.md +35 -0
  123. project_init/templates/fallback/dot_claude/skills/start_task/SKILL.md +48 -0
  124. project_init/templates/fallback/dot_claude/skills/status/SKILL.md +15 -0
  125. project_init/templates/gemini/dot_agents/skills/add_adr/SKILL.md +33 -0
  126. project_init/templates/gemini/dot_agents/skills/add_command/SKILL.md +63 -0
  127. project_init/templates/gemini/dot_agents/skills/add_hook/SKILL.md +112 -0
  128. project_init/templates/gemini/dot_agents/skills/audit/SKILL.md +146 -0
  129. project_init/templates/gemini/dot_agents/skills/create_issue/SKILL.md +59 -0
  130. project_init/templates/gemini/dot_agents/skills/github_workflow/SKILL.md +80 -0
  131. project_init/templates/gemini/dot_agents/skills/request_review/SKILL.md +19 -0
  132. project_init/templates/gemini/dot_agents/skills/review/SKILL.md +17 -0
  133. project_init/templates/gemini/dot_agents/skills/save_memory/SKILL.md +17 -0
  134. project_init/templates/gemini/dot_agents/skills/session_summary/SKILL.md +35 -0
  135. project_init/templates/gemini/dot_agents/skills/start_task/SKILL.md +48 -0
  136. project_init/templates/gemini/dot_agents/skills/status/SKILL.md +15 -0
  137. project_init/templates/gemini/dot_claude/scripts/setup_gemini.sh.tmpl +16 -0
  138. project_init/templates/gemini/dot_gemini-extension/commands/add_adr.toml +5 -0
  139. project_init/templates/gemini/dot_gemini-extension/commands/add_command.toml +5 -0
  140. project_init/templates/gemini/dot_gemini-extension/commands/add_hook.toml +5 -0
  141. project_init/templates/gemini/dot_gemini-extension/commands/audit.toml +5 -0
  142. project_init/templates/gemini/dot_gemini-extension/commands/create_issue.toml +5 -0
  143. project_init/templates/gemini/dot_gemini-extension/commands/github_workflow.toml +5 -0
  144. project_init/templates/gemini/dot_gemini-extension/commands/request_review.toml +5 -0
  145. project_init/templates/gemini/dot_gemini-extension/commands/review.toml +5 -0
  146. project_init/templates/gemini/dot_gemini-extension/commands/save_memory.toml +5 -0
  147. project_init/templates/gemini/dot_gemini-extension/commands/session_summary.toml +5 -0
  148. project_init/templates/gemini/dot_gemini-extension/commands/start_task.toml +5 -0
  149. project_init/templates/gemini/dot_gemini-extension/commands/status.toml +5 -0
  150. project_init/templates/gemini/dot_gemini-extension/gemini-extension.json.tmpl +6 -0
  151. project_init/templates/gemini/dot_gemini-extension/hooks/hooks.json.tmpl +18 -0
  152. project_init/templates/graphify/dot_claude/docs/guides/using-graphify.md +37 -0
  153. project_init/templates/graphify/dot_claude/rules/graphify.md +18 -0
  154. project_init/templates/graphify/dot_claude/scripts/setup_graphify.sh +40 -0
  155. project_init/templates/obsidian/dot_claude/scripts/lint_memory.sh +115 -0
  156. project_init/templates/obsidian/dot_claude/vault/decisions/adr-000-project-setup.md.tmpl +22 -0
  157. project_init/templates/obsidian/dot_claude/vault/dot_obsidian/README.md +31 -0
  158. project_init/templates/obsidian/dot_claude/vault/dot_obsidian/app.json +6 -0
  159. project_init/templates/obsidian/dot_claude/vault/dot_obsidian/community-plugins.json +1 -0
  160. project_init/templates/obsidian/dot_claude/vault/dot_obsidian/core-plugins.json +1 -0
  161. project_init/templates/obsidian/dot_claude/vault/log.md +6 -0
  162. project_init/templates/obsidian/dot_claude/vault/templates/decision.md +16 -0
  163. project_init/templates/obsidian/dot_claude/vault/templates/design-note.md +14 -0
  164. project_init/templates/obsidian/dot_claude/vault/templates/knowledge-note.md +12 -0
  165. project_init/templates/obsidian/dot_claude/vault/templates/session-note.md +16 -0
  166. project_init/templates/presets/obsidian-graphify.toml +16 -0
  167. project_init/templates/presets/obsidian-only.toml +14 -0
  168. project_init/upgrade.py +569 -0
  169. project_init-0.3.0.dist-info/METADATA +342 -0
  170. project_init-0.3.0.dist-info/RECORD +173 -0
  171. project_init-0.3.0.dist-info/WHEEL +4 -0
  172. project_init-0.3.0.dist-info/entry_points.txt +2 -0
  173. project_init-0.3.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,270 @@
1
+ #!/bin/bash
2
+ # Wait for all CI checks on a PR, then optionally merge.
3
+ # Only prints failures or the final pass line — no per-refresh noise.
4
+ # Requires: gh, python3 (stdlib only — no jq dependency).
5
+ #
6
+ # Usage:
7
+ # .claude/scripts/monitor_pr.sh <pr-number> [--merge] [--review-cycle N] [--no-review]
8
+ #
9
+ # --merge: squash-merge and delete branch automatically when all checks pass.
10
+ # --review-cycle N: current review fix cycle count (0-based, default 0).
11
+ # When N >= MAX_REVIEW_CYCLES and review/decision is still failing or pending,
12
+ # force-merges with --admin.
13
+ # --no-review: skip all review waiting and admin-merge after CI passes.
14
+ # Use ONLY for solo-dev PRs where no human reviewer will ever respond.
15
+ # Do NOT use to avoid addressing legitimate review feedback.
16
+ #
17
+ # Full lifecycle for agents:
18
+ # 1. .claude/scripts/monitor_pr.sh <n> --merge
19
+ # 2. Exit 2 -> review comments printed -> read and address them, push
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
23
+ #
24
+ # Review cycle policy:
25
+ # Two fix cycles are required before admin-merge is allowed. This ensures
26
+ # review feedback (including Copilot comments) is read and addressed at
27
+ # least once before force-merging.
28
+
29
+ set -euo pipefail
30
+
31
+ PR_NUMBER="${1:-}"
32
+ MODE="${2:-}"
33
+ REVIEW_CYCLE=0
34
+ MAX_REVIEW_CYCLES=2
35
+ NO_REVIEW=0
36
+
37
+ if [ -z "$PR_NUMBER" ]; then
38
+ echo "Usage: monitor_pr.sh <pr-number> [--merge] [--review-cycle N] [--no-review]" >&2
39
+ exit 1
40
+ fi
41
+
42
+ if [ -n "$MODE" ] && [ "$MODE" != "--merge" ]; then
43
+ echo "Unknown option: $MODE" >&2
44
+ exit 2
45
+ fi
46
+
47
+ # Parse remaining flags (order-independent after position 2).
48
+ # Shift past <pr-number> and optional --merge; remaining args are flags.
49
+ shift 1 # drop PR_NUMBER
50
+ [ "$MODE" = "--merge" ] && shift 1 # drop --merge if present
51
+ while [ $# -gt 0 ]; do
52
+ case "$1" in
53
+ --review-cycle) REVIEW_CYCLE="${2:-0}"; shift 2 ;;
54
+ --review-cycle=*) REVIEW_CYCLE="${1#*=}"; shift ;;
55
+ --no-review) NO_REVIEW=1; shift ;;
56
+ *) echo "Unknown option: $1" >&2; exit 2 ;;
57
+ esac
58
+ done
59
+
60
+ _count_pending() {
61
+ echo "$1" | python3 -c "
62
+ import json, sys
63
+ data = json.load(sys.stdin)
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
+ print(sum(1 for c in data if c.get('name') != 'review/decision' and c.get('state') in ('PENDING', 'IN_PROGRESS', 'EXPECTED')))
67
+ "
68
+ }
69
+
70
+ _print_failures() {
71
+ echo "$1" | python3 -c "
72
+ import json, sys
73
+ data = json.load(sys.stdin)
74
+ bad = [
75
+ c for c in data
76
+ if c.get('name') != 'review/decision'
77
+ and (
78
+ c.get('bucket') in ('fail', 'cancel')
79
+ or c.get('state') in ('FAILURE', 'CANCELLED', 'TIMED_OUT', 'ERROR')
80
+ )
81
+ ]
82
+ for c in bad:
83
+ print(f\" {c['name']}: {c.get('state') or c.get('bucket')}\")
84
+ sys.exit(len(bad))
85
+ "
86
+ }
87
+
88
+ # Print review feedback — inline comments first, falls back to full PR comments view.
89
+ _print_review_comments() {
90
+ local inline
91
+ inline=$(
92
+ gh api "repos/{owner}/{repo}/pulls/$PR_NUMBER/comments" \
93
+ --jq '.[] | " \(.path):\(.line // "?") [\(.user.login)]\n \(.body)\n"' \
94
+ 2>/dev/null || true
95
+ )
96
+ if [ -n "$inline" ]; then
97
+ printf '%s\n' "$inline"
98
+ else
99
+ gh pr view "$PR_NUMBER" --comments 2>/dev/null || true
100
+ fi
101
+ echo " Full PR: $(gh pr view "$PR_NUMBER" --json url -q '.url' 2>/dev/null || true)"
102
+ }
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
+
120
+ _admin_merge() {
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
127
+ }
128
+
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.
131
+ # Returns: APPROVED | CHANGES_REQUESTED | REVIEW_REQUIRED | (empty = no review policy)
132
+ # Returns UNKNOWN on API failure — callers must treat this as fail-closed.
133
+ _get_review_decision() {
134
+ gh pr view "$PR_NUMBER" --json reviewDecision -q '.reviewDecision // ""' 2>/dev/null || echo "UNKNOWN"
135
+ }
136
+
137
+ # Check if any review activity exists (COMMENTED, APPROVED, or CHANGES_REQUESTED).
138
+ # Bot reviewers like Codex post COMMENTED reviews that don't change reviewDecision,
139
+ # so we use this as an early exit signal from the wait loop.
140
+ _has_review_activity() {
141
+ local count
142
+ count=$(gh pr view "$PR_NUMBER" --json reviews -q '.reviews | length' 2>/dev/null) || count=0
143
+ [ "$count" -gt 0 ]
144
+ }
145
+
146
+ # --- Wait for all CI checks (excludes review/decision commit status) ---
147
+ # Guard: if checks haven't registered yet (empty list), keep polling.
148
+ # An empty list is indistinguishable from "all done" without this guard,
149
+ # which caused premature merges before CI even started.
150
+ while true; do
151
+ CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket 2>/dev/null) || CHECKS="[]"
152
+ CHECK_COUNT=$(echo "$CHECKS" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
153
+ [ "$CHECK_COUNT" -eq 0 ] && { sleep 10; continue; }
154
+ PENDING=$(_count_pending "$CHECKS")
155
+ [ "$PENDING" -eq 0 ] && break
156
+ sleep 10
157
+ done
158
+
159
+ FAIL_CODE=0
160
+ _print_failures "$CHECKS" || FAIL_CODE=$?
161
+
162
+ if [ "$FAIL_CODE" -gt 0 ]; then
163
+ echo "CI failed on PR #$PR_NUMBER — fix the issues, push, then re-run this script."
164
+ exit 1
165
+ fi
166
+
167
+ # --no-review: explicit bypass — skip review gate entirely after CI passes.
168
+ # Use only for solo-dev PRs where no reviewer will ever respond.
169
+ if [ "$NO_REVIEW" -eq 1 ] && [ "$MODE" = "--merge" ]; then
170
+ echo "PR #$PR_NUMBER: CI passed. --no-review specified — skipping review gate."
171
+ _admin_merge; exit 0
172
+ fi
173
+
174
+ # --- Wait up to 6 min for a reviewer to act ---
175
+ # Query reviewDecision directly so this works before review-status.yml creates
176
+ # the derived review/decision commit status.
177
+ REVIEW_TIMEOUT=360
178
+ REVIEW_ELAPSED=0
179
+ REVIEW_DECISION=$(_get_review_decision)
180
+ if [ "$MODE" = "--merge" ] && [ "$REVIEW_CYCLE" -ge "$MAX_REVIEW_CYCLES" ] && [ "$REVIEW_DECISION" = "REVIEW_REQUIRED" ]; then
181
+ echo "Max review cycles ($MAX_REVIEW_CYCLES) reached — skipping reviewer wait and force-merging with admin override."
182
+ _admin_merge; exit 0
183
+ fi
184
+
185
+ if [ "$REVIEW_DECISION" = "REVIEW_REQUIRED" ] || [ "$REVIEW_DECISION" = "UNKNOWN" ]; then
186
+ echo "Waiting for reviewer (up to ${REVIEW_TIMEOUT}s, polling every 30s) — reviewDecision: ${REVIEW_DECISION}"
187
+ fi
188
+ while { [ "$REVIEW_DECISION" = "REVIEW_REQUIRED" ] || [ "$REVIEW_DECISION" = "UNKNOWN" ]; } && [ "$REVIEW_ELAPSED" -lt "$REVIEW_TIMEOUT" ]; do
189
+ sleep 30
190
+ REVIEW_ELAPSED=$((REVIEW_ELAPSED + 30))
191
+ REVIEW_DECISION=$(_get_review_decision)
192
+ echo " [${REVIEW_ELAPSED}s/${REVIEW_TIMEOUT}s] reviewDecision: ${REVIEW_DECISION:-none}"
193
+ # Early exit: if any review activity exists (even COMMENTED), stop waiting.
194
+ # Bot reviewers like Codex post comments without changing reviewDecision.
195
+ if [ "$REVIEW_DECISION" = "REVIEW_REQUIRED" ] && _has_review_activity; then
196
+ echo " Review comments detected — proceeding without waiting for formal approval."
197
+ break
198
+ fi
199
+ CHECKS=$(gh pr checks "$PR_NUMBER" --json name,state,bucket 2>/dev/null) || CHECKS="[]"
200
+ done
201
+
202
+ if [ "$REVIEW_DECISION" = "UNKNOWN" ]; then
203
+ echo "ERROR: could not fetch reviewDecision for PR #$PR_NUMBER — cannot verify review state." >&2
204
+ exit 2
205
+ fi
206
+
207
+ if [ "$REVIEW_DECISION" = "CHANGES_REQUESTED" ]; then
208
+ echo "Review/decision failed on PR #$PR_NUMBER (cycle $REVIEW_CYCLE/$MAX_REVIEW_CYCLES):"
209
+ _print_review_comments
210
+
211
+ if [ "$MODE" = "--merge" ]; then
212
+ if [ "$REVIEW_CYCLE" -ge "$MAX_REVIEW_CYCLES" ]; then
213
+ echo "Max review cycles ($MAX_REVIEW_CYCLES) reached — force-merging with admin override."
214
+ _admin_merge; exit 0
215
+ else
216
+ NEXT=$((REVIEW_CYCLE + 1))
217
+ echo "Address the comments above, push your changes, then re-run:"
218
+ echo " .claude/scripts/monitor_pr.sh $PR_NUMBER --merge --review-cycle $NEXT"
219
+ exit 2
220
+ fi
221
+ fi
222
+ exit 1
223
+ fi
224
+
225
+ if [ "$REVIEW_DECISION" = "REVIEW_REQUIRED" ]; then
226
+ echo "PR #$PR_NUMBER: review/decision still pending after ${REVIEW_TIMEOUT}s — no reviewer has acted."
227
+ echo " Full PR: $(gh pr view "$PR_NUMBER" --json url -q '.url' 2>/dev/null || true)"
228
+
229
+ if [ "$MODE" = "--merge" ]; then
230
+ if [ "$REVIEW_CYCLE" -ge "$MAX_REVIEW_CYCLES" ]; then
231
+ echo "Max review cycles ($MAX_REVIEW_CYCLES) reached — force-merging with admin override."
232
+ _admin_merge; exit 0
233
+ else
234
+ NEXT=$((REVIEW_CYCLE + 1))
235
+ echo "Request a review or wait for a reviewer, then re-run:"
236
+ echo " .claude/scripts/monitor_pr.sh $PR_NUMBER --merge --review-cycle $NEXT"
237
+ exit 2
238
+ fi
239
+ fi
240
+ exit 1
241
+ fi
242
+
243
+ PR_URL=$(gh pr view "$PR_NUMBER" --json url -q '.url')
244
+ echo "PR #$PR_NUMBER passed: $PR_URL"
245
+
246
+ if [ "$MODE" = "--merge" ]; then
247
+ MERGE_STATE=$(gh pr view "$PR_NUMBER" --json mergeStateStatus -q '.mergeStateStatus' 2>/dev/null || echo "UNKNOWN")
248
+
249
+ if [ "$MERGE_STATE" = "CLEAN" ] || [ "$MERGE_STATE" = "UNSTABLE" ]; then
250
+ if _run_gh pr merge "$PR_NUMBER" --squash --delete-branch; then
251
+ echo "Merged PR #$PR_NUMBER"
252
+ else
253
+ echo "ERROR: merge failed for PR #$PR_NUMBER" >&2
254
+ exit 1
255
+ fi
256
+ elif [ "$MERGE_STATE" = "BLOCKED" ]; then
257
+ echo "PR is blocked by review protection — merging with admin override."
258
+ _admin_merge
259
+ else
260
+ if ! _run_gh pr merge "$PR_NUMBER" --squash --delete-branch; then
261
+ if ! _run_gh pr merge "$PR_NUMBER" --squash --delete-branch --auto; then
262
+ echo "ERROR: could not merge or enable auto-merge for PR #$PR_NUMBER" >&2
263
+ exit 1
264
+ fi
265
+ echo "Auto-merge enabled for PR #$PR_NUMBER — will merge once all requirements are met."
266
+ else
267
+ echo "Merged PR #$PR_NUMBER"
268
+ fi
269
+ fi
270
+ fi
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ # Thin shim — actual logic lives in .claude/hooks/dag_workflow.py.
3
+ exec python3 "$(dirname "$0")/../hooks/dag_workflow.py" promote "$@"
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ # Thin shim — actual logic lives in .claude/hooks/dag_workflow.py.
3
+ # Kept under .claude/scripts/ so existing skill paths and agent muscle
4
+ # memory keep working.
5
+ exec python3 "$(dirname "$0")/../hooks/dag_workflow.py" push "$@"
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # push_wiki.sh — clone the GitHub wiki, update Home.md, and push.
3
+ # Usage: push_wiki.sh <repo-slug> <wiki-source-file>
4
+ # repo-slug e.g. owner/repo-name
5
+ # wiki-source-file path to the markdown file to write as Home.md
6
+ #
7
+ # The guard allowlists this script so it is not blocked by the git-push rule.
8
+
9
+ set -euo pipefail
10
+
11
+ if [[ $# -lt 2 ]]; then
12
+ echo "Usage: push_wiki.sh <repo-slug> <wiki-source-file>" >&2
13
+ exit 1
14
+ fi
15
+
16
+ REPO_SLUG="$1"
17
+ SOURCE_FILE="$2"
18
+ WIKI_DIR="$(mktemp -d)"
19
+ trap 'rm -rf "$WIKI_DIR"' EXIT
20
+
21
+ echo "Cloning wiki for $REPO_SLUG..."
22
+ git clone "https://github.com/${REPO_SLUG}.wiki.git" "$WIKI_DIR"
23
+
24
+ cp "$SOURCE_FILE" "$WIKI_DIR/Home.md"
25
+
26
+ cd "$WIKI_DIR"
27
+ git add Home.md
28
+ if git diff --cached --quiet; then
29
+ echo "Wiki already up to date."
30
+ exit 0
31
+ fi
32
+ git commit -m "Update Home page"
33
+ git push
34
+ echo "Wiki updated."
@@ -0,0 +1,219 @@
1
+ #!/bin/bash
2
+ # Configure or check GitHub repository governance for the scaffolded workflow.
3
+ #
4
+ # Usage: setup_github.sh [branch] [--protect]
5
+ # --protect apply baseline branch protection to the default branch
6
+ # (require CI green, require PR review, block force-push).
7
+ # Idempotent: the PUT endpoint replaces the existing config.
8
+ #
9
+ # Requires: gh and admin permission on the repository.
10
+
11
+ set -euo pipefail
12
+
13
+ BRANCH="main"
14
+ PROTECT=0
15
+ for arg in "$@"; do
16
+ case "$arg" in
17
+ --protect) PROTECT=1 ;;
18
+ --*) echo "Unknown option: $arg" >&2; exit 1 ;;
19
+ *) BRANCH="$arg" ;;
20
+ esac
21
+ done
22
+
23
+ if ! gh auth status >/dev/null 2>&1; then
24
+ echo "ERROR: gh is not authenticated. Run gh auth login first." >&2
25
+ exit 1
26
+ fi
27
+
28
+ REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
29
+ OWNER=${REPO%/*}
30
+ NAME=${REPO#*/}
31
+
32
+ echo "Configuring GitHub governance for $REPO ($BRANCH)"
33
+ # Default endpoint: repos/$OWNER/$NAME/branches/main/protection
34
+
35
+ if [ "$PROTECT" = 1 ]; then
36
+ PROTECTION=$(mktemp)
37
+ trap 'rm -f "$PROTECTION"' EXIT
38
+
39
+ # Contexts must match the scaffolded workflows ("<workflow> / <job name>").
40
+ # "CI / Integration tests" is omitted on purpose — that job is documented as
41
+ # user-adjustable; add it here once your integration suite is stable.
42
+ cat > "$PROTECTION" <<'JSON'
43
+ {
44
+ "required_status_checks": {
45
+ "strict": true,
46
+ "contexts": [
47
+ "CI / Lint and test",
48
+ "CI / Secret scan (gitleaks)",
49
+ "Validate PR / Check PR title, branch, and linked issue",
50
+ "review/decision"
51
+ ]
52
+ },
53
+ "enforce_admins": false,
54
+ "required_pull_request_reviews": {
55
+ "required_approving_review_count": 1,
56
+ "dismiss_stale_reviews": true,
57
+ "require_code_owner_reviews": false,
58
+ "require_last_push_approval": false
59
+ },
60
+ "restrictions": null,
61
+ "required_conversation_resolution": true,
62
+ "allow_force_pushes": false,
63
+ "allow_deletions": false
64
+ }
65
+ JSON
66
+
67
+ if gh api "repos/$OWNER/$NAME/branches/$BRANCH/protection" -X PUT --input "$PROTECTION" >/dev/null; then
68
+ echo "Branch protection applied to $BRANCH"
69
+ else
70
+ echo "WARNING: could not apply branch protection. Check admin permissions and repository plan." >&2
71
+ fi
72
+ else
73
+ echo "Skipping branch protection (pass --protect to apply: require CI green, require review, block force-push)"
74
+ fi
75
+
76
+ if gh api "repos/$OWNER/$NAME/code-review-settings" -X PUT -f copilot_code_review_enabled=true >/dev/null 2>&1; then
77
+ echo "Copilot code review enabled"
78
+ else
79
+ echo "WARNING: Enable Copilot code review manually if your plan supports it:" >&2
80
+ echo " https://github.com/$OWNER/$NAME/settings/code_review" >&2
81
+ fi
82
+
83
+ # --- GitHub Project board field provisioning ---
84
+ # Creates the single-select metadata fields used by board-automation.yml.
85
+ # Requires a token with 'project' scope (set PROJECT_TOKEN env var, or ensure
86
+ # gh auth token has project scope). Skips fields that already exist.
87
+ echo ""
88
+ echo "Provisioning GitHub Project board fields..."
89
+
90
+ PROJECT_NUMBER="${PROJECT_NUMBER:-1}"
91
+
92
+ # Use PROJECT_TOKEN if set, otherwise fall through to default gh auth
93
+ if [ -n "${PROJECT_TOKEN:-}" ]; then
94
+ export GH_TOKEN="$PROJECT_TOKEN"
95
+ fi
96
+
97
+ PROJECT_DATA=$(gh api graphql -f query='
98
+ query($owner: String!, $number: Int!) {
99
+ user(login: $owner) {
100
+ projectV2(number: $number) {
101
+ id
102
+ fields(first: 50) { nodes { ... on ProjectV2SingleSelectField { name } } }
103
+ }
104
+ }
105
+ organization(login: $owner) {
106
+ projectV2(number: $number) {
107
+ id
108
+ fields(first: 50) { nodes { ... on ProjectV2SingleSelectField { name } } }
109
+ }
110
+ }
111
+ }' -f owner="$OWNER" -F number="$PROJECT_NUMBER" 2>/dev/null || echo '{}')
112
+
113
+ PROJECT_ID=$(echo "$PROJECT_DATA" | jq -r \
114
+ '(.data.user.projectV2 // .data.organization.projectV2 // {}).id // empty')
115
+
116
+ if [ -z "$PROJECT_ID" ]; then
117
+ echo "WARNING: Project #$PROJECT_NUMBER not found for $REPO." >&2
118
+ echo " Ensure PROJECT_TOKEN has 'project' scope, or create these fields manually:" >&2
119
+ echo " • Priority — options: high, medium, low" >&2
120
+ echo " • Size — options: XS, S, M, L, XL" >&2
121
+ echo " • Agent ready — options: Yes, No" >&2
122
+ echo " • Confidence — options: high, medium, low, unknown" >&2
123
+ echo " • Type — options: feature, bug, chore, documentation, test" >&2
124
+ echo " Settings: https://github.com/users/$OWNER/projects/$PROJECT_NUMBER/settings/fields" >&2
125
+ else
126
+ EXISTING_FIELDS=$(echo "$PROJECT_DATA" | jq -r \
127
+ '((.data.user.projectV2 // .data.organization.projectV2).fields.nodes // [])[] | .name // empty')
128
+
129
+ ensure_single_select_field() {
130
+ local field_name="$1"
131
+ local mutation="$2"
132
+ if printf '%s\n' "$EXISTING_FIELDS" | grep -Fxq "$field_name"; then
133
+ echo " '$field_name' already exists — skipping"
134
+ return 0
135
+ fi
136
+ if gh api graphql -f query="$mutation" -f projectId="$PROJECT_ID" >/dev/null 2>&1; then
137
+ echo " Created '$field_name'"
138
+ else
139
+ # Repo: $REPO Project: #$PROJECT_NUMBER
140
+ echo " WARNING: could not create '$field_name' for $REPO — add it manually:" >&2
141
+ echo " https://github.com/users/$OWNER/projects/$PROJECT_NUMBER/settings/fields" >&2
142
+ fi
143
+ }
144
+
145
+ ensure_single_select_field "Priority" '
146
+ mutation($projectId: ID!) {
147
+ createProjectV2Field(input: {
148
+ projectId: $projectId
149
+ dataType: SINGLE_SELECT
150
+ name: "Priority"
151
+ singleSelectOptions: [
152
+ { name: "high", color: RED, description: "" }
153
+ { name: "medium", color: YELLOW, description: "" }
154
+ { name: "low", color: GRAY, description: "" }
155
+ ]
156
+ }) { projectV2Field { ... on ProjectV2SingleSelectField { id } } }
157
+ }'
158
+
159
+ ensure_single_select_field "Size" '
160
+ mutation($projectId: ID!) {
161
+ createProjectV2Field(input: {
162
+ projectId: $projectId
163
+ dataType: SINGLE_SELECT
164
+ name: "Size"
165
+ singleSelectOptions: [
166
+ { name: "XS", color: BLUE, description: "" }
167
+ { name: "S", color: GREEN, description: "" }
168
+ { name: "M", color: YELLOW, description: "" }
169
+ { name: "L", color: ORANGE, description: "" }
170
+ { name: "XL", color: RED, description: "" }
171
+ ]
172
+ }) { projectV2Field { ... on ProjectV2SingleSelectField { id } } }
173
+ }'
174
+
175
+ ensure_single_select_field "Agent ready" '
176
+ mutation($projectId: ID!) {
177
+ createProjectV2Field(input: {
178
+ projectId: $projectId
179
+ dataType: SINGLE_SELECT
180
+ name: "Agent ready"
181
+ singleSelectOptions: [
182
+ { name: "Yes", color: GREEN, description: "" }
183
+ { name: "No", color: GRAY, description: "" }
184
+ ]
185
+ }) { projectV2Field { ... on ProjectV2SingleSelectField { id } } }
186
+ }'
187
+
188
+ ensure_single_select_field "Confidence" '
189
+ mutation($projectId: ID!) {
190
+ createProjectV2Field(input: {
191
+ projectId: $projectId
192
+ dataType: SINGLE_SELECT
193
+ name: "Confidence"
194
+ singleSelectOptions: [
195
+ { name: "high", color: GREEN, description: "" }
196
+ { name: "medium", color: YELLOW, description: "" }
197
+ { name: "low", color: ORANGE, description: "" }
198
+ { name: "unknown", color: GRAY, description: "" }
199
+ ]
200
+ }) { projectV2Field { ... on ProjectV2SingleSelectField { id } } }
201
+ }'
202
+
203
+ ensure_single_select_field "Type" '
204
+ mutation($projectId: ID!) {
205
+ createProjectV2Field(input: {
206
+ projectId: $projectId
207
+ dataType: SINGLE_SELECT
208
+ name: "Type"
209
+ singleSelectOptions: [
210
+ { name: "feature", color: BLUE, description: "" }
211
+ { name: "bug", color: RED, description: "" }
212
+ { name: "chore", color: GRAY, description: "" }
213
+ { name: "documentation", color: PURPLE, description: "" }
214
+ { name: "test", color: YELLOW, description: "" }
215
+ ]
216
+ }) { projectV2Field { ... on ProjectV2SingleSelectField { id } } }
217
+ }'
218
+ fi
219
+
@@ -0,0 +1,134 @@
1
+ #!/bin/bash
2
+ # Start work on a GitHub issue: create branch, push, and open a draft PR.
3
+ #
4
+ # Usage:
5
+ # .claude/scripts/start_issue.sh <issue-number> <type>
6
+ #
7
+ # Types: feat fix chore docs test
8
+ #
9
+ # Composable with create_issue.sh:
10
+ # .claude/scripts/create_issue.sh feat "Add OAuth login" | xargs -I{} .claude/scripts/start_issue.sh {} feat
11
+
12
+ set -euo pipefail
13
+
14
+ VALID_TYPES="feat fix chore docs test"
15
+
16
+ usage() {
17
+ echo "Usage: start_issue.sh <issue-number> <type>"
18
+ echo ""
19
+ echo "Types: $VALID_TYPES"
20
+ echo ""
21
+ echo "Examples:"
22
+ echo " start_issue.sh 42 feat"
23
+ echo " start_issue.sh 99 fix"
24
+ exit 1
25
+ }
26
+
27
+ # --- Validate args ---
28
+ if [ $# -lt 2 ]; then
29
+ usage
30
+ fi
31
+
32
+ ISSUE_NUMBER="$1"
33
+ TYPE="$2"
34
+
35
+ if ! echo "$VALID_TYPES" | grep -qw "$TYPE"; then
36
+ echo "ERROR: invalid type '$TYPE'. Valid types: $VALID_TYPES" >&2
37
+ exit 1
38
+ fi
39
+
40
+ if ! [[ "$ISSUE_NUMBER" =~ ^[0-9]+$ ]]; then
41
+ echo "ERROR: issue number must be numeric, got '$ISSUE_NUMBER'" >&2
42
+ exit 1
43
+ fi
44
+
45
+ # --- Resolve project key / abbreviation ---
46
+ # Set PROJECT_KEY env var, add `project_key: PI` to .claude/config.yaml,
47
+ # or let the script derive one from the repository directory name.
48
+ derive_project_key() {
49
+ if [ -n "${PROJECT_KEY:-}" ]; then
50
+ echo "$PROJECT_KEY"
51
+ return
52
+ fi
53
+
54
+ local configured=""
55
+ configured=$(grep '^[[:space:]]*project_key:' .claude/config.yaml 2>/dev/null \
56
+ | head -n 1 \
57
+ | cut -d: -f2- \
58
+ | sed 's/#.*$//' \
59
+ | tr -d '[:space:]"' \
60
+ | tr -d "'" || true)
61
+ if [ -n "$configured" ]; then
62
+ echo "$configured"
63
+ return
64
+ fi
65
+
66
+ local repo_name=""
67
+ repo_name=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
68
+ echo "$repo_name" \
69
+ | tr '[:lower:]' '[:upper:]' \
70
+ | tr -cs 'A-Z0-9' '\n' \
71
+ | awk 'NF { printf substr($0, 1, 1) }' \
72
+ | cut -c1-10
73
+ }
74
+
75
+ PROJECT_KEY=$(derive_project_key)
76
+ PROJECT_KEY=$(echo "$PROJECT_KEY" | tr '[:lower:]' '[:upper:]' | tr -cd 'A-Z0-9')
77
+ if [ -z "$PROJECT_KEY" ]; then
78
+ PROJECT_KEY="PROJ"
79
+ fi
80
+
81
+ # --- Fetch issue title ---
82
+ ISSUE_TITLE=$(gh issue view "$ISSUE_NUMBER" --json title -q '.title' 2>/dev/null)
83
+ if [ -z "$ISSUE_TITLE" ]; then
84
+ echo "ERROR: issue #$ISSUE_NUMBER not found" >&2
85
+ exit 1
86
+ fi
87
+
88
+ ISSUE_REF="${PROJECT_KEY}-${ISSUE_NUMBER}"
89
+
90
+ # --- Derive branch name: <issue_type>/<project_abbr>-<issue_number>-<kebab-slug>, max 80 chars total ---
91
+ # Matches convention: feat/PI-42-add-oauth-login, fix/API-99-null-pointer
92
+ # Strip leading [type] prefix from issue title if present (e.g. "[feat] Add OAuth" -> "Add OAuth")
93
+ CLEAN_TITLE=$(echo "$ISSUE_TITLE" | sed 's/^\[[^]]*\] *//')
94
+ SLUG=$(echo "$CLEAN_TITLE" \
95
+ | tr '[:upper:]' '[:lower:]' \
96
+ | tr -cs 'a-z0-9' '-' \
97
+ | sed 's/^-//;s/-$//')
98
+ PREFIX="${ISSUE_REF}-"
99
+ MAX_SLUG=$(( 80 - ${#TYPE} - 1 - ${#PREFIX} )) # -1 for the /
100
+ if [ "$MAX_SLUG" -lt 12 ]; then
101
+ MAX_SLUG=12
102
+ fi
103
+ SLUG="${SLUG:0:$MAX_SLUG}"
104
+ SLUG="${SLUG%-}" # trim trailing dash if truncated mid-word
105
+ BRANCH="${TYPE}/${PREFIX}${SLUG}"
106
+
107
+ echo "Branch: $BRANCH"
108
+
109
+ # --- Guard: already on this branch or it already exists ---
110
+ CURRENT=$(git branch --show-current)
111
+ if [ "$CURRENT" = "$BRANCH" ]; then
112
+ echo "Already on branch $BRANCH"
113
+ elif git show-ref --verify --quiet "refs/heads/$BRANCH"; then
114
+ echo "Branch $BRANCH already exists - switching"
115
+ git checkout "$BRANCH"
116
+ else
117
+ git checkout -b "$BRANCH"
118
+ fi
119
+
120
+ # --- Push and set upstream (retry + remote-SHA verification) ---
121
+ .claude/scripts/push_branch.sh "$BRANCH"
122
+
123
+ # --- Open draft PR ---
124
+ # Conventional Commits with issue key as scope (ADR-006)
125
+ PR_TITLE="${TYPE}(${ISSUE_REF}): ${CLEAN_TITLE}"
126
+ PR_BODY="Closes #${ISSUE_NUMBER}"
127
+
128
+ PR_URL=$(gh pr create \
129
+ --draft \
130
+ --title "$PR_TITLE" \
131
+ --body "$PR_BODY")
132
+
133
+ echo "Draft PR: $PR_URL"
134
+