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.
- project_init/__init__.py +4 -0
- project_init/__main__.py +662 -0
- project_init/mcps.py +57 -0
- project_init/scaffold.py +374 -0
- project_init/templates/base/AGENTS.md.tmpl +50 -0
- project_init/templates/base/CLAUDE.md.tmpl +16 -0
- project_init/templates/base/CONTRIBUTING.md.tmpl +55 -0
- project_init/templates/base/GEMINI.md.tmpl +16 -0
- project_init/templates/base/LICENSE.tmpl +231 -0
- project_init/templates/base/SECURITY.md.tmpl +26 -0
- project_init/templates/base/docs/explanation/index.md +9 -0
- project_init/templates/base/docs/how-to/index.md +7 -0
- project_init/templates/base/docs/index.md.tmpl +20 -0
- project_init/templates/base/docs/reference/index.md +13 -0
- project_init/templates/base/docs/tutorials/index.md +7 -0
- project_init/templates/base/dot_claude/agents/README.md +30 -0
- project_init/templates/base/dot_claude/config.yaml.tmpl +31 -0
- project_init/templates/base/dot_claude/docs/README.md +26 -0
- project_init/templates/base/dot_claude/docs/adr/adr-001-memory-stack.md.tmpl +22 -0
- project_init/templates/base/dot_claude/docs/adr/adr-002-mcp-choices.md.tmpl +32 -0
- project_init/templates/base/dot_claude/docs/adr/adr-template.md +29 -0
- project_init/templates/base/dot_claude/docs/development/conventions.md.tmpl +31 -0
- project_init/templates/base/dot_claude/docs/development/testing.md +25 -0
- project_init/templates/base/dot_claude/docs/guides/developer-onboarding.md +110 -0
- project_init/templates/base/dot_claude/docs/guides/issue-metadata.md +27 -0
- project_init/templates/base/dot_claude/docs/guides/secrets.md +50 -0
- project_init/templates/base/dot_claude/docs/guides/using-memory.md +36 -0
- project_init/templates/base/dot_claude/hooks/README.md +15 -0
- project_init/templates/base/dot_claude/hooks/agent_guard_adapter.py.tmpl +64 -0
- project_init/templates/base/dot_claude/hooks/dag_workflow.py +610 -0
- project_init/templates/base/dot_claude/memory/MEMORY.md.tmpl +11 -0
- project_init/templates/base/dot_claude/memory/README.md +51 -0
- project_init/templates/base/dot_claude/memory/SCHEMA.md +52 -0
- project_init/templates/base/dot_claude/memory/feedback_conventions.md +11 -0
- project_init/templates/base/dot_claude/memory/project_context.md.tmpl +11 -0
- project_init/templates/base/dot_claude/memory/user_role.md +7 -0
- project_init/templates/base/dot_claude/project-init.md.tmpl +174 -0
- project_init/templates/base/dot_claude/rules/go.md +14 -0
- project_init/templates/base/dot_claude/rules/hooks.md +30 -0
- project_init/templates/base/dot_claude/rules/node.md +17 -0
- project_init/templates/base/dot_claude/rules/python.md +25 -0
- project_init/templates/base/dot_claude/scripts/README.md +15 -0
- project_init/templates/base/dot_claude/scripts/create_issue.sh +577 -0
- project_init/templates/base/dot_claude/scripts/create_nojira_pr.sh +3 -0
- project_init/templates/base/dot_claude/scripts/finish_pr.sh +3 -0
- project_init/templates/base/dot_claude/scripts/install_hooks.sh +55 -0
- project_init/templates/base/dot_claude/scripts/monitor_pr.sh +270 -0
- project_init/templates/base/dot_claude/scripts/promote_review.sh +3 -0
- project_init/templates/base/dot_claude/scripts/push_branch.sh +5 -0
- project_init/templates/base/dot_claude/scripts/push_wiki.sh +34 -0
- project_init/templates/base/dot_claude/scripts/setup_github.sh +219 -0
- project_init/templates/base/dot_claude/scripts/start_issue.sh +134 -0
- project_init/templates/base/dot_claude/settings.json.tmpl +83 -0
- project_init/templates/base/dot_claude/skills/README.md +12 -0
- project_init/templates/base/dot_claude/skills/plan/SKILL.md.tmpl +40 -0
- project_init/templates/base/dot_claude/vault/README.md +21 -0
- project_init/templates/base/dot_claude/vault/decisions/README.md +22 -0
- project_init/templates/base/dot_claude/vault/design/README.md +3 -0
- project_init/templates/base/dot_claude/vault/knowledge/README.md +5 -0
- project_init/templates/base/dot_claude/vault/sessions/README.md +5 -0
- project_init/templates/base/dot_devcontainer/devcontainer.json.tmpl +17 -0
- project_init/templates/base/dot_devcontainer/post-create.sh.tmpl +31 -0
- project_init/templates/base/dot_env.example.tmpl +13 -0
- project_init/templates/base/dot_github/CODEOWNERS.tmpl +12 -0
- project_init/templates/base/dot_github/ISSUE_TEMPLATE/bug.yml +98 -0
- project_init/templates/base/dot_github/ISSUE_TEMPLATE/chore.yml +82 -0
- project_init/templates/base/dot_github/ISSUE_TEMPLATE/config.yml +5 -0
- project_init/templates/base/dot_github/ISSUE_TEMPLATE/docs.yml +84 -0
- project_init/templates/base/dot_github/ISSUE_TEMPLATE/feature.yml +87 -0
- project_init/templates/base/dot_github/ISSUE_TEMPLATE/test.yml +90 -0
- project_init/templates/base/dot_github/copilot-instructions.md.tmpl +25 -0
- project_init/templates/base/dot_github/hooks/commit-msg +52 -0
- project_init/templates/base/dot_github/hooks/pre-commit +16 -0
- project_init/templates/base/dot_github/hooks/pre-push +51 -0
- project_init/templates/base/dot_github/pull_request_template.md +22 -0
- project_init/templates/base/dot_github/workflows/board-automation.yml +232 -0
- project_init/templates/base/dot_github/workflows/ci.yml.tmpl +204 -0
- project_init/templates/base/dot_github/workflows/docs.yml.tmpl +98 -0
- project_init/templates/base/dot_github/workflows/issue-validation.yml +72 -0
- project_init/templates/base/dot_github/workflows/review-status.yml +48 -0
- project_init/templates/base/dot_github/workflows/validate-pr.yml +103 -0
- project_init/templates/base/dot_gitignore.tmpl +41 -0
- project_init/templates/base/dot_golangci.yml.tmpl +20 -0
- project_init/templates/base/dot_vscode/extensions.json.tmpl +10 -0
- project_init/templates/base/dot_vscode/settings.json.tmpl +8 -0
- project_init/templates/base/eslint.config.mjs.tmpl +29 -0
- project_init/templates/base/justfile.tmpl +95 -0
- project_init/templates/base/mise.toml.tmpl +20 -0
- project_init/templates/base/mkdocs.yml.tmpl +32 -0
- project_init/templates/base/renovate.json +14 -0
- project_init/templates/base/ruff.toml.tmpl +31 -0
- project_init/templates/base/typedoc.json.tmpl +14 -0
- project_init/templates/codex/dot_agents/skills/add_adr/SKILL.md +33 -0
- project_init/templates/codex/dot_agents/skills/add_command/SKILL.md +63 -0
- project_init/templates/codex/dot_agents/skills/add_hook/SKILL.md +112 -0
- project_init/templates/codex/dot_agents/skills/audit/SKILL.md +146 -0
- project_init/templates/codex/dot_agents/skills/create_issue/SKILL.md +59 -0
- project_init/templates/codex/dot_agents/skills/github_workflow/SKILL.md +80 -0
- project_init/templates/codex/dot_agents/skills/request_review/SKILL.md +19 -0
- project_init/templates/codex/dot_agents/skills/review/SKILL.md +17 -0
- project_init/templates/codex/dot_agents/skills/save_memory/SKILL.md +17 -0
- project_init/templates/codex/dot_agents/skills/session_summary/SKILL.md +35 -0
- project_init/templates/codex/dot_agents/skills/start_task/SKILL.md +48 -0
- project_init/templates/codex/dot_agents/skills/status/SKILL.md +15 -0
- project_init/templates/codex/dot_codex/hooks.json.tmpl +17 -0
- project_init/templates/fallback/dot_claude/hooks/github_command_guard.sh +11 -0
- project_init/templates/fallback/dot_claude/hooks/post_edit_lint.sh +58 -0
- project_init/templates/fallback/dot_claude/hooks/pre_commit_gate.sh +81 -0
- project_init/templates/fallback/dot_claude/hooks/prod_guard.py +140 -0
- project_init/templates/fallback/dot_claude/hooks/session_setup.sh +62 -0
- project_init/templates/fallback/dot_claude/hooks/workflow_state_reminder.sh +72 -0
- project_init/templates/fallback/dot_claude/skills/INDEX.md +28 -0
- project_init/templates/fallback/dot_claude/skills/add_adr/SKILL.md +33 -0
- project_init/templates/fallback/dot_claude/skills/add_command/SKILL.md +63 -0
- project_init/templates/fallback/dot_claude/skills/add_hook/SKILL.md +112 -0
- project_init/templates/fallback/dot_claude/skills/audit/SKILL.md +146 -0
- project_init/templates/fallback/dot_claude/skills/create_issue/SKILL.md +59 -0
- project_init/templates/fallback/dot_claude/skills/github_workflow/SKILL.md +80 -0
- project_init/templates/fallback/dot_claude/skills/request_review/SKILL.md +19 -0
- project_init/templates/fallback/dot_claude/skills/review/SKILL.md +17 -0
- project_init/templates/fallback/dot_claude/skills/save_memory/SKILL.md +17 -0
- project_init/templates/fallback/dot_claude/skills/session_summary/SKILL.md +35 -0
- project_init/templates/fallback/dot_claude/skills/start_task/SKILL.md +48 -0
- project_init/templates/fallback/dot_claude/skills/status/SKILL.md +15 -0
- project_init/templates/gemini/dot_agents/skills/add_adr/SKILL.md +33 -0
- project_init/templates/gemini/dot_agents/skills/add_command/SKILL.md +63 -0
- project_init/templates/gemini/dot_agents/skills/add_hook/SKILL.md +112 -0
- project_init/templates/gemini/dot_agents/skills/audit/SKILL.md +146 -0
- project_init/templates/gemini/dot_agents/skills/create_issue/SKILL.md +59 -0
- project_init/templates/gemini/dot_agents/skills/github_workflow/SKILL.md +80 -0
- project_init/templates/gemini/dot_agents/skills/request_review/SKILL.md +19 -0
- project_init/templates/gemini/dot_agents/skills/review/SKILL.md +17 -0
- project_init/templates/gemini/dot_agents/skills/save_memory/SKILL.md +17 -0
- project_init/templates/gemini/dot_agents/skills/session_summary/SKILL.md +35 -0
- project_init/templates/gemini/dot_agents/skills/start_task/SKILL.md +48 -0
- project_init/templates/gemini/dot_agents/skills/status/SKILL.md +15 -0
- project_init/templates/gemini/dot_claude/scripts/setup_gemini.sh.tmpl +16 -0
- project_init/templates/gemini/dot_gemini-extension/commands/add_adr.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/add_command.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/add_hook.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/audit.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/create_issue.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/github_workflow.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/request_review.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/review.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/save_memory.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/session_summary.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/start_task.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/commands/status.toml +5 -0
- project_init/templates/gemini/dot_gemini-extension/gemini-extension.json.tmpl +6 -0
- project_init/templates/gemini/dot_gemini-extension/hooks/hooks.json.tmpl +18 -0
- project_init/templates/graphify/dot_claude/docs/guides/using-graphify.md +37 -0
- project_init/templates/graphify/dot_claude/rules/graphify.md +18 -0
- project_init/templates/graphify/dot_claude/scripts/setup_graphify.sh +40 -0
- project_init/templates/obsidian/dot_claude/scripts/lint_memory.sh +115 -0
- project_init/templates/obsidian/dot_claude/vault/decisions/adr-000-project-setup.md.tmpl +22 -0
- project_init/templates/obsidian/dot_claude/vault/dot_obsidian/README.md +31 -0
- project_init/templates/obsidian/dot_claude/vault/dot_obsidian/app.json +6 -0
- project_init/templates/obsidian/dot_claude/vault/dot_obsidian/community-plugins.json +1 -0
- project_init/templates/obsidian/dot_claude/vault/dot_obsidian/core-plugins.json +1 -0
- project_init/templates/obsidian/dot_claude/vault/log.md +6 -0
- project_init/templates/obsidian/dot_claude/vault/templates/decision.md +16 -0
- project_init/templates/obsidian/dot_claude/vault/templates/design-note.md +14 -0
- project_init/templates/obsidian/dot_claude/vault/templates/knowledge-note.md +12 -0
- project_init/templates/obsidian/dot_claude/vault/templates/session-note.md +16 -0
- project_init/templates/presets/obsidian-graphify.toml +16 -0
- project_init/templates/presets/obsidian-only.toml +14 -0
- project_init/upgrade.py +569 -0
- project_init-0.3.0.dist-info/METADATA +342 -0
- project_init-0.3.0.dist-info/RECORD +173 -0
- project_init-0.3.0.dist-info/WHEEL +4 -0
- project_init-0.3.0.dist-info/entry_points.txt +2 -0
- project_init-0.3.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Create a GitHub issue with typed labels and planning metadata.
|
|
3
|
+
# Priority, Size, Agent ready, and Confidence are set directly on the
|
|
4
|
+
# GitHub Project v2 board — not written to the issue body.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# .claude/scripts/create_issue.sh <type> "Short description" [metadata flags]
|
|
8
|
+
#
|
|
9
|
+
# Types: feat fix chore docs test
|
|
10
|
+
#
|
|
11
|
+
# Prints the created issue number to stdout so it can be piped:
|
|
12
|
+
# .claude/scripts/create_issue.sh feat "Add OAuth login" --priority high | xargs -I{} .claude/scripts/start_issue.sh {} feat
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
VALID_TYPES="feat fix chore docs test"
|
|
17
|
+
VALID_SCALES="epic task"
|
|
18
|
+
VALID_PRIORITIES="high medium low"
|
|
19
|
+
VALID_SIZES="XS S M L XL"
|
|
20
|
+
VALID_CONFIDENCES="high medium low unknown"
|
|
21
|
+
|
|
22
|
+
usage() {
|
|
23
|
+
cat <<'EOF'
|
|
24
|
+
Usage: create_issue.sh <type> "Short description" [options]
|
|
25
|
+
|
|
26
|
+
Types:
|
|
27
|
+
feat fix chore docs test
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--priority high|medium|low Set Priority on the project board
|
|
31
|
+
--size XS|S|M|L|XL Set Size on the project board
|
|
32
|
+
--agent-ready Yes|No Set Agent ready on the project board
|
|
33
|
+
--confidence high|medium|low|unknown Set Confidence on the project board
|
|
34
|
+
--area VALUE Record affected area in body metadata
|
|
35
|
+
--scale epic|task Mark as epic (parent) or task (leaf); adds scale label
|
|
36
|
+
--parent VALUE Link new issue as sub-issue of VALUE
|
|
37
|
+
Formats: 42, #42, owner/repo#42, or full issue URL
|
|
38
|
+
--reference VALUE Add a reference; repeatable
|
|
39
|
+
--dependency VALUE Add a dependency; repeatable
|
|
40
|
+
--acceptance VALUE Add an acceptance criterion; repeatable
|
|
41
|
+
--assignee USER Assign the issue
|
|
42
|
+
--milestone NAME Set milestone by name
|
|
43
|
+
--body-file FILE Append extra markdown body content
|
|
44
|
+
-h, --help Show this help
|
|
45
|
+
|
|
46
|
+
Sub-issues:
|
|
47
|
+
--parent links the new issue as a native GitHub sub-issue of the given parent.
|
|
48
|
+
Cross-repo parents use owner/repo#42 or the full issue URL.
|
|
49
|
+
--scale epic marks this issue as a parent work item (adds scale:epic label).
|
|
50
|
+
|
|
51
|
+
Metadata model:
|
|
52
|
+
GitHub labels: type and scale when labels exist or can be created.
|
|
53
|
+
GitHub Project fields: priority, size, agent-ready, confidence (set directly via GraphQL).
|
|
54
|
+
Markdown body: area, scale, parent, references, dependencies, acceptance criteria,
|
|
55
|
+
Definition of Ready, and Definition of Done.
|
|
56
|
+
|
|
57
|
+
Missing label fallback:
|
|
58
|
+
If a label is missing and cannot be created, issue creation continues without
|
|
59
|
+
that label because the same metadata is still stored in the markdown body.
|
|
60
|
+
EOF
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
|
64
|
+
usage
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [ $# -lt 2 ]; then
|
|
69
|
+
usage >&2
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
TYPE="$1"
|
|
74
|
+
DESCRIPTION="$2"
|
|
75
|
+
shift 2
|
|
76
|
+
|
|
77
|
+
PRIORITY=""
|
|
78
|
+
SIZE=""
|
|
79
|
+
AGENT_READY=""
|
|
80
|
+
CONFIDENCE=""
|
|
81
|
+
AREA=""
|
|
82
|
+
SCALE=""
|
|
83
|
+
PARENT=""
|
|
84
|
+
ASSIGNEE=""
|
|
85
|
+
MILESTONE=""
|
|
86
|
+
BODY_FILE=""
|
|
87
|
+
REFERENCES=()
|
|
88
|
+
DEPENDENCIES=()
|
|
89
|
+
ACCEPTANCE=()
|
|
90
|
+
|
|
91
|
+
contains_word() {
|
|
92
|
+
local needle="$1"
|
|
93
|
+
local haystack="$2"
|
|
94
|
+
echo "$haystack" | grep -qw "$needle"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
require_option_value() {
|
|
98
|
+
local option="$1"
|
|
99
|
+
local value="${2:-}"
|
|
100
|
+
if [ -z "$value" ]; then
|
|
101
|
+
echo "ERROR: missing value for '$option'" >&2
|
|
102
|
+
usage >&2
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
while [ $# -gt 0 ]; do
|
|
108
|
+
case "$1" in
|
|
109
|
+
--priority)
|
|
110
|
+
require_option_value "$1" "${2:-}"
|
|
111
|
+
PRIORITY="$2"
|
|
112
|
+
shift 2
|
|
113
|
+
;;
|
|
114
|
+
--size)
|
|
115
|
+
require_option_value "$1" "${2:-}"
|
|
116
|
+
SIZE="$2"
|
|
117
|
+
shift 2
|
|
118
|
+
;;
|
|
119
|
+
--agent-ready)
|
|
120
|
+
require_option_value "$1" "${2:-}"
|
|
121
|
+
AGENT_READY="$2"
|
|
122
|
+
shift 2
|
|
123
|
+
;;
|
|
124
|
+
--confidence)
|
|
125
|
+
require_option_value "$1" "${2:-}"
|
|
126
|
+
CONFIDENCE="$2"
|
|
127
|
+
shift 2
|
|
128
|
+
;;
|
|
129
|
+
--area)
|
|
130
|
+
require_option_value "$1" "${2:-}"
|
|
131
|
+
AREA="$2"
|
|
132
|
+
shift 2
|
|
133
|
+
;;
|
|
134
|
+
--scale)
|
|
135
|
+
require_option_value "$1" "${2:-}"
|
|
136
|
+
SCALE="$2"
|
|
137
|
+
shift 2
|
|
138
|
+
;;
|
|
139
|
+
--parent)
|
|
140
|
+
require_option_value "$1" "${2:-}"
|
|
141
|
+
PARENT="$2"
|
|
142
|
+
shift 2
|
|
143
|
+
;;
|
|
144
|
+
--reference)
|
|
145
|
+
require_option_value "$1" "${2:-}"
|
|
146
|
+
REFERENCES+=("$2")
|
|
147
|
+
shift 2
|
|
148
|
+
;;
|
|
149
|
+
--dependency)
|
|
150
|
+
require_option_value "$1" "${2:-}"
|
|
151
|
+
DEPENDENCIES+=("$2")
|
|
152
|
+
shift 2
|
|
153
|
+
;;
|
|
154
|
+
--acceptance)
|
|
155
|
+
require_option_value "$1" "${2:-}"
|
|
156
|
+
ACCEPTANCE+=("$2")
|
|
157
|
+
shift 2
|
|
158
|
+
;;
|
|
159
|
+
--assignee)
|
|
160
|
+
require_option_value "$1" "${2:-}"
|
|
161
|
+
ASSIGNEE="$2"
|
|
162
|
+
shift 2
|
|
163
|
+
;;
|
|
164
|
+
--milestone)
|
|
165
|
+
require_option_value "$1" "${2:-}"
|
|
166
|
+
MILESTONE="$2"
|
|
167
|
+
shift 2
|
|
168
|
+
;;
|
|
169
|
+
--body-file)
|
|
170
|
+
require_option_value "$1" "${2:-}"
|
|
171
|
+
BODY_FILE="$2"
|
|
172
|
+
shift 2
|
|
173
|
+
;;
|
|
174
|
+
-h|--help)
|
|
175
|
+
usage
|
|
176
|
+
exit 0
|
|
177
|
+
;;
|
|
178
|
+
*)
|
|
179
|
+
echo "ERROR: unknown option '$1'" >&2
|
|
180
|
+
usage >&2
|
|
181
|
+
exit 1
|
|
182
|
+
;;
|
|
183
|
+
esac
|
|
184
|
+
done
|
|
185
|
+
|
|
186
|
+
if ! contains_word "$TYPE" "$VALID_TYPES"; then
|
|
187
|
+
echo "ERROR: invalid type '$TYPE'. Valid types: $VALID_TYPES" >&2
|
|
188
|
+
exit 1
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
if [ -z "$DESCRIPTION" ]; then
|
|
192
|
+
echo "ERROR: description cannot be empty" >&2
|
|
193
|
+
exit 1
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
if [ -n "$PRIORITY" ] && ! contains_word "$PRIORITY" "$VALID_PRIORITIES"; then
|
|
197
|
+
echo "ERROR: invalid priority '$PRIORITY'. Valid: $VALID_PRIORITIES" >&2
|
|
198
|
+
exit 1
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
if [ -n "$SIZE" ] && ! contains_word "$SIZE" "$VALID_SIZES"; then
|
|
202
|
+
echo "ERROR: invalid size '$SIZE'. Valid: $VALID_SIZES" >&2
|
|
203
|
+
exit 1
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
if [ -n "$CONFIDENCE" ] && ! contains_word "$CONFIDENCE" "$VALID_CONFIDENCES"; then
|
|
207
|
+
echo "ERROR: invalid confidence '$CONFIDENCE'. Valid: $VALID_CONFIDENCES" >&2
|
|
208
|
+
exit 1
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
if [ -n "$SCALE" ] && ! contains_word "$SCALE" "$VALID_SCALES"; then
|
|
212
|
+
echo "ERROR: invalid scale '$SCALE'. Valid scales: $VALID_SCALES" >&2
|
|
213
|
+
exit 1
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
if [ -n "$BODY_FILE" ] && [ ! -f "$BODY_FILE" ]; then
|
|
217
|
+
echo "ERROR: body file not found: $BODY_FILE" >&2
|
|
218
|
+
exit 1
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# Parse --parent reference into owner / repo / number.
|
|
223
|
+
# Accepts: 42, #42, owner/repo#42, or full GitHub issue URL.
|
|
224
|
+
# Sets globals PARENT_OWNER, PARENT_REPO, PARENT_NUMBER.
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
PARENT_OWNER=""
|
|
227
|
+
PARENT_REPO=""
|
|
228
|
+
PARENT_NUMBER=""
|
|
229
|
+
|
|
230
|
+
parse_parent() {
|
|
231
|
+
local raw="${1#\#}" # strip leading #
|
|
232
|
+
if [[ "$raw" =~ ^https://github\.com/([^/]+)/([^/]+)/issues/([0-9]+)$ ]]; then
|
|
233
|
+
PARENT_OWNER="${BASH_REMATCH[1]}"
|
|
234
|
+
PARENT_REPO="${BASH_REMATCH[2]}"
|
|
235
|
+
PARENT_NUMBER="${BASH_REMATCH[3]}"
|
|
236
|
+
elif [[ "$raw" =~ ^([^/#]+)/([^#]+)#([0-9]+)$ ]]; then
|
|
237
|
+
PARENT_OWNER="${BASH_REMATCH[1]}"
|
|
238
|
+
PARENT_REPO="${BASH_REMATCH[2]}"
|
|
239
|
+
PARENT_NUMBER="${BASH_REMATCH[3]}"
|
|
240
|
+
elif [[ "$raw" =~ ^[0-9]+$ ]]; then
|
|
241
|
+
PARENT_OWNER=$(gh repo view --json owner -q .owner.login)
|
|
242
|
+
PARENT_REPO=$(gh repo view --json name -q .name)
|
|
243
|
+
PARENT_NUMBER="$raw"
|
|
244
|
+
else
|
|
245
|
+
echo "ERROR: cannot parse parent '$1'. Use: 42, #42, owner/repo#42, or full URL" >&2
|
|
246
|
+
exit 1
|
|
247
|
+
fi
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if [ -n "$PARENT" ]; then
|
|
251
|
+
parse_parent "$PARENT"
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
case "$TYPE" in
|
|
255
|
+
feat) TYPE_LABEL="feature" ;;
|
|
256
|
+
fix) TYPE_LABEL="bug" ;;
|
|
257
|
+
chore) TYPE_LABEL="chore" ;;
|
|
258
|
+
docs) TYPE_LABEL="documentation" ;;
|
|
259
|
+
test) TYPE_LABEL="test" ;;
|
|
260
|
+
esac
|
|
261
|
+
|
|
262
|
+
TITLE="$DESCRIPTION"
|
|
263
|
+
|
|
264
|
+
BODY_PATH=$(mktemp)
|
|
265
|
+
trap 'rm -f "$BODY_PATH"' EXIT
|
|
266
|
+
|
|
267
|
+
write_list() {
|
|
268
|
+
local fallback="$1"
|
|
269
|
+
shift
|
|
270
|
+
if [ "$#" -eq 0 ]; then
|
|
271
|
+
echo "- [ ] $fallback"
|
|
272
|
+
return
|
|
273
|
+
fi
|
|
274
|
+
for item in "$@"; do
|
|
275
|
+
[ -n "$item" ] && echo "- [ ] $item"
|
|
276
|
+
done
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
write_bullets() {
|
|
280
|
+
local fallback="$1"
|
|
281
|
+
shift
|
|
282
|
+
if [ "$#" -eq 0 ]; then
|
|
283
|
+
echo "- $fallback"
|
|
284
|
+
return
|
|
285
|
+
fi
|
|
286
|
+
for item in "$@"; do
|
|
287
|
+
[ -n "$item" ] && echo "- $item"
|
|
288
|
+
done
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
{
|
|
292
|
+
echo "## Summary"
|
|
293
|
+
echo
|
|
294
|
+
echo "$DESCRIPTION"
|
|
295
|
+
echo
|
|
296
|
+
echo "## Metadata"
|
|
297
|
+
echo
|
|
298
|
+
echo "- Type: $TYPE"
|
|
299
|
+
[ -n "$SCALE" ] && echo "- Scale: $SCALE"
|
|
300
|
+
[ -n "$AREA" ] && echo "- Area: $AREA"
|
|
301
|
+
if [ -n "$PARENT" ]; then
|
|
302
|
+
echo "- Parent: $PARENT_OWNER/$PARENT_REPO#$PARENT_NUMBER"
|
|
303
|
+
fi
|
|
304
|
+
if [ -n "$ASSIGNEE" ]; then
|
|
305
|
+
echo "- Assignee: @$ASSIGNEE"
|
|
306
|
+
fi
|
|
307
|
+
if [ -n "$MILESTONE" ]; then
|
|
308
|
+
echo "- Milestone: $MILESTONE"
|
|
309
|
+
fi
|
|
310
|
+
if [ "${#REFERENCES[@]}" -gt 0 ]; then
|
|
311
|
+
echo
|
|
312
|
+
echo "## References"
|
|
313
|
+
echo
|
|
314
|
+
write_bullets "None" "${REFERENCES[@]}"
|
|
315
|
+
fi
|
|
316
|
+
if [ "${#DEPENDENCIES[@]}" -gt 0 ]; then
|
|
317
|
+
echo
|
|
318
|
+
echo "## Dependencies"
|
|
319
|
+
echo
|
|
320
|
+
write_bullets "None" "${DEPENDENCIES[@]}"
|
|
321
|
+
fi
|
|
322
|
+
echo
|
|
323
|
+
echo "## Acceptance criteria"
|
|
324
|
+
echo
|
|
325
|
+
write_list "Define acceptance criteria before implementation" "${ACCEPTANCE[@]}"
|
|
326
|
+
echo
|
|
327
|
+
echo "## Definition of Ready"
|
|
328
|
+
echo
|
|
329
|
+
echo "- [ ] Acceptance criteria are clear enough to verify"
|
|
330
|
+
echo "- [ ] Dependencies, references, and affected areas are recorded"
|
|
331
|
+
echo
|
|
332
|
+
echo "## Definition of Done"
|
|
333
|
+
echo
|
|
334
|
+
echo "- [ ] Implementation is complete"
|
|
335
|
+
echo "- [ ] Relevant checks, tests, or manual validation are documented"
|
|
336
|
+
} > "$BODY_PATH"
|
|
337
|
+
|
|
338
|
+
if [ -n "$BODY_FILE" ]; then
|
|
339
|
+
{
|
|
340
|
+
echo
|
|
341
|
+
echo "## Additional context"
|
|
342
|
+
echo
|
|
343
|
+
cat "$BODY_FILE"
|
|
344
|
+
} >> "$BODY_PATH"
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
ensure_label() {
|
|
348
|
+
local name="$1"
|
|
349
|
+
local color="$2"
|
|
350
|
+
local description="$3"
|
|
351
|
+
if gh label list --search "$name" --json name -q '.[].name' 2>/dev/null | grep -Fxq "$name"; then
|
|
352
|
+
echo "$name"
|
|
353
|
+
return
|
|
354
|
+
fi
|
|
355
|
+
if gh label create "$name" --color "$color" --description "$description" >/dev/null 2>&1; then
|
|
356
|
+
echo "$name"
|
|
357
|
+
return
|
|
358
|
+
fi
|
|
359
|
+
echo "Warning: label missing and could not be created: $name" >&2
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
LABEL_ARGS=()
|
|
363
|
+
if LABEL=$(ensure_label "$TYPE_LABEL" "0075ca" "Issue type"); then
|
|
364
|
+
[ -n "$LABEL" ] && LABEL_ARGS+=(--label "$LABEL")
|
|
365
|
+
fi
|
|
366
|
+
if [ -n "$SCALE" ]; then
|
|
367
|
+
if LABEL=$(ensure_label "scale:$SCALE" "f9d0c4" "Issue scale"); then
|
|
368
|
+
[ -n "$LABEL" ] && LABEL_ARGS+=(--label "$LABEL")
|
|
369
|
+
fi
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
CREATE_ARGS=(--title "$TITLE" --body-file "$BODY_PATH")
|
|
373
|
+
if [ -n "$ASSIGNEE" ]; then
|
|
374
|
+
CREATE_ARGS+=(--assignee "$ASSIGNEE")
|
|
375
|
+
fi
|
|
376
|
+
if [ -n "$MILESTONE" ]; then
|
|
377
|
+
CREATE_ARGS+=(--milestone "$MILESTONE")
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
ISSUE_URL=$(gh issue create "${CREATE_ARGS[@]}" "${LABEL_ARGS[@]}")
|
|
381
|
+
ISSUE_NUMBER=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
|
|
382
|
+
|
|
383
|
+
# ---------------------------------------------------------------------------
|
|
384
|
+
# Set Priority / Size / Agent ready / Confidence directly on the GitHub
|
|
385
|
+
# Project v2 board. These are not written to the issue body — the board is
|
|
386
|
+
# the authoritative location for these fields.
|
|
387
|
+
# PROJECT_NUMBER defaults to 1; override with the env var if needed.
|
|
388
|
+
# ---------------------------------------------------------------------------
|
|
389
|
+
sync_project_fields() {
|
|
390
|
+
local issue_num="$1"
|
|
391
|
+
local project_num owner repo_name project_data project_id item_id
|
|
392
|
+
|
|
393
|
+
project_num="${PROJECT_NUMBER:-1}"
|
|
394
|
+
owner=$(gh repo view --json owner -q .owner.login)
|
|
395
|
+
repo_name=$(gh repo view --json name -q .name)
|
|
396
|
+
|
|
397
|
+
project_data=$(gh api graphql -f query='
|
|
398
|
+
query($owner: String!, $number: Int!) {
|
|
399
|
+
user(login: $owner) {
|
|
400
|
+
projectV2(number: $number) {
|
|
401
|
+
id
|
|
402
|
+
fields(first: 50) {
|
|
403
|
+
nodes {
|
|
404
|
+
... on ProjectV2SingleSelectField {
|
|
405
|
+
id name options { id name }
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
items(first: 100) {
|
|
410
|
+
nodes { id content { ... on Issue { number } } }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
organization(login: $owner) {
|
|
415
|
+
projectV2(number: $number) {
|
|
416
|
+
id
|
|
417
|
+
fields(first: 50) {
|
|
418
|
+
nodes {
|
|
419
|
+
... on ProjectV2SingleSelectField {
|
|
420
|
+
id name options { id name }
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
items(first: 100) {
|
|
425
|
+
nodes { id content { ... on Issue { number } } }
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}' -f owner="$owner" -F number="$project_num" 2>/dev/null) || true
|
|
430
|
+
[ -z "$project_data" ] && project_data='{}'
|
|
431
|
+
|
|
432
|
+
# Write project data to a temp file to avoid single-quote issues in shell args
|
|
433
|
+
local pdata_file
|
|
434
|
+
pdata_file=$(mktemp)
|
|
435
|
+
printf '%s' "$project_data" > "$pdata_file"
|
|
436
|
+
|
|
437
|
+
project_id=$(python3 - "$pdata_file" <<'PYEOF' 2>/dev/null || true
|
|
438
|
+
import sys, json
|
|
439
|
+
d = json.load(open(sys.argv[1])).get('data', {})
|
|
440
|
+
p = (d.get('user') or d.get('organization') or {}).get('projectV2') or {}
|
|
441
|
+
print(p.get('id', ''))
|
|
442
|
+
PYEOF
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
if [ -z "$project_id" ]; then
|
|
446
|
+
rm -f "$pdata_file"
|
|
447
|
+
echo "Warning: project #$project_num not found — skipping field sync (set PROJECT_NUMBER if needed)" >&2
|
|
448
|
+
return 0
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
item_id=$(python3 - "$pdata_file" "$issue_num" <<'PYEOF' 2>/dev/null || true
|
|
452
|
+
import sys, json
|
|
453
|
+
d = json.load(open(sys.argv[1])).get('data', {})
|
|
454
|
+
p = (d.get('user') or d.get('organization') or {}).get('projectV2') or {}
|
|
455
|
+
for item in (p.get('items') or {}).get('nodes', []):
|
|
456
|
+
if (item.get('content') or {}).get('number') == int(sys.argv[2]):
|
|
457
|
+
print(item['id'])
|
|
458
|
+
break
|
|
459
|
+
PYEOF
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
if [ -z "$item_id" ]; then
|
|
463
|
+
local issue_node_id
|
|
464
|
+
issue_node_id=$(gh api "repos/$owner/$repo_name/issues/$issue_num" --jq '.node_id' 2>/dev/null || true)
|
|
465
|
+
if [ -n "$issue_node_id" ]; then
|
|
466
|
+
item_id=$(gh api graphql -f query='
|
|
467
|
+
mutation($project: ID!, $content: ID!) {
|
|
468
|
+
addProjectV2ItemById(input: { projectId: $project, contentId: $content }) {
|
|
469
|
+
item { id }
|
|
470
|
+
}
|
|
471
|
+
}' -f project="$project_id" -f content="$issue_node_id" \
|
|
472
|
+
--jq '.data.addProjectV2ItemById.item.id' 2>/dev/null || true)
|
|
473
|
+
fi
|
|
474
|
+
fi
|
|
475
|
+
|
|
476
|
+
if [ -z "$item_id" ]; then
|
|
477
|
+
echo "Warning: could not add #$issue_num to project #$project_num — skipping field sync" >&2
|
|
478
|
+
return 0
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
set_field() {
|
|
482
|
+
local field_name="$1" option_name="$2"
|
|
483
|
+
[ -n "$option_name" ] || return 0
|
|
484
|
+
|
|
485
|
+
local field_id option_id
|
|
486
|
+
field_id=$(python3 - "$pdata_file" "$field_name" <<'PYEOF' 2>/dev/null || true
|
|
487
|
+
import sys, json
|
|
488
|
+
d = json.load(open(sys.argv[1])).get('data', {})
|
|
489
|
+
p = (d.get('user') or d.get('organization') or {}).get('projectV2') or {}
|
|
490
|
+
for f in (p.get('fields') or {}).get('nodes', []):
|
|
491
|
+
if f.get('name') == sys.argv[2]:
|
|
492
|
+
print(f.get('id', ''))
|
|
493
|
+
break
|
|
494
|
+
PYEOF
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
if [ -z "$field_id" ]; then
|
|
498
|
+
echo "Warning: project field '$field_name' not found — skipping" >&2
|
|
499
|
+
return 0
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
option_id=$(python3 - "$pdata_file" "$field_name" "$option_name" <<'PYEOF' 2>/dev/null || true
|
|
503
|
+
import sys, json
|
|
504
|
+
d = json.load(open(sys.argv[1])).get('data', {})
|
|
505
|
+
p = (d.get('user') or d.get('organization') or {}).get('projectV2') or {}
|
|
506
|
+
for f in (p.get('fields') or {}).get('nodes', []):
|
|
507
|
+
if f.get('name') == sys.argv[2]:
|
|
508
|
+
for opt in f.get('options', []):
|
|
509
|
+
if opt.get('name') == sys.argv[3]:
|
|
510
|
+
print(opt['id'])
|
|
511
|
+
break
|
|
512
|
+
break
|
|
513
|
+
PYEOF
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
if [ -z "$option_id" ]; then
|
|
517
|
+
echo "Warning: option '$option_name' not found in field '$field_name' — skipping" >&2
|
|
518
|
+
return 0
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
gh api graphql -f query='
|
|
522
|
+
mutation($project: ID!, $item: ID!, $field: ID!, $option: String!) {
|
|
523
|
+
updateProjectV2ItemFieldValue(input: {
|
|
524
|
+
projectId: $project
|
|
525
|
+
itemId: $item
|
|
526
|
+
fieldId: $field
|
|
527
|
+
value: { singleSelectOptionId: $option }
|
|
528
|
+
}) { projectV2Item { id } }
|
|
529
|
+
}' \
|
|
530
|
+
-f project="$project_id" -f item="$item_id" \
|
|
531
|
+
-f field="$field_id" -f option="$option_id" > /dev/null 2>&1 \
|
|
532
|
+
|| echo "Warning: failed to set '$field_name'='$option_name'" >&2
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
[ -n "$PRIORITY" ] && set_field "Priority" "$PRIORITY"
|
|
536
|
+
[ -n "$SIZE" ] && set_field "Size" "$SIZE"
|
|
537
|
+
[ -n "$AGENT_READY" ] && set_field "Agent ready" "$AGENT_READY"
|
|
538
|
+
[ -n "$CONFIDENCE" ] && set_field "Confidence" "$CONFIDENCE"
|
|
539
|
+
rm -f "$pdata_file"
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if [ -n "$PRIORITY" ] || [ -n "$SIZE" ] || [ -n "$AGENT_READY" ] || [ -n "$CONFIDENCE" ]; then
|
|
543
|
+
sync_project_fields "$ISSUE_NUMBER"
|
|
544
|
+
fi
|
|
545
|
+
|
|
546
|
+
# ---------------------------------------------------------------------------
|
|
547
|
+
# Link as a native GitHub sub-issue when --parent was specified.
|
|
548
|
+
# Uses the addSubIssue GraphQL mutation (supports cross-repo parents via URL).
|
|
549
|
+
# ---------------------------------------------------------------------------
|
|
550
|
+
if [ -n "$PARENT" ]; then
|
|
551
|
+
PARENT_NODE_ID=$(gh api graphql -f query='
|
|
552
|
+
query($owner: String!, $repo: String!, $number: Int!) {
|
|
553
|
+
repository(owner: $owner, name: $repo) {
|
|
554
|
+
issue(number: $number) { id }
|
|
555
|
+
}
|
|
556
|
+
}' \
|
|
557
|
+
-f owner="$PARENT_OWNER" \
|
|
558
|
+
-f repo="$PARENT_REPO" \
|
|
559
|
+
-F number="$PARENT_NUMBER" \
|
|
560
|
+
--jq '.data.repository.issue.id')
|
|
561
|
+
|
|
562
|
+
CHILD_NODE_ID=$(gh api "repos/:owner/:repo/issues/$ISSUE_NUMBER" --jq '.node_id')
|
|
563
|
+
|
|
564
|
+
gh api graphql -f query='
|
|
565
|
+
mutation($parent: ID!, $child: ID!) {
|
|
566
|
+
addSubIssue(input: { issueId: $parent, subIssueId: $child }) {
|
|
567
|
+
issue { number }
|
|
568
|
+
subIssue { number }
|
|
569
|
+
}
|
|
570
|
+
}' \
|
|
571
|
+
-f parent="$PARENT_NODE_ID" \
|
|
572
|
+
-f child="$CHILD_NODE_ID" > /dev/null
|
|
573
|
+
|
|
574
|
+
echo "Linked #$ISSUE_NUMBER as sub-issue of $PARENT_OWNER/$PARENT_REPO#$PARENT_NUMBER" >&2
|
|
575
|
+
fi
|
|
576
|
+
|
|
577
|
+
echo "$ISSUE_NUMBER"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Install git hooks from .github/hooks/ to .git/hooks/
|
|
3
|
+
# Run this once after cloning or when hooks are updated
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
8
|
+
GIT_HOOKS_SRC="$REPO_ROOT/.github/hooks"
|
|
9
|
+
GIT_HOOKS_DST="$REPO_ROOT/.git/hooks"
|
|
10
|
+
|
|
11
|
+
if [ ! -d "$GIT_HOOKS_DST" ]; then
|
|
12
|
+
echo "Error: .git/hooks directory not found. Are you in a git repository?"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ ! -d "$GIT_HOOKS_SRC" ]; then
|
|
17
|
+
echo "Warning: .github/hooks directory not found."
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "Installing git hooks from .github/hooks/ to .git/hooks/..."
|
|
22
|
+
|
|
23
|
+
for hook_file in "$GIT_HOOKS_SRC"/*; do
|
|
24
|
+
[ -f "$hook_file" ] || continue
|
|
25
|
+
|
|
26
|
+
hook_name=$(basename "$hook_file")
|
|
27
|
+
hook_dst="$GIT_HOOKS_DST/$hook_name"
|
|
28
|
+
|
|
29
|
+
if [ -e "$hook_dst" ] && [ ! -L "$hook_dst" ]; then
|
|
30
|
+
# File exists and is not a symlink - back it up
|
|
31
|
+
mv "$hook_dst" "$hook_dst.backup.$(date +%s)"
|
|
32
|
+
echo " Backed up existing $hook_name"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Create symlink (or copy if symlinks not available)
|
|
36
|
+
if cp -P "$hook_file" "$hook_dst" 2>/dev/null; then
|
|
37
|
+
chmod +x "$hook_dst"
|
|
38
|
+
else
|
|
39
|
+
echo " ✗ Failed to install $hook_name"
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
# The pre-commit hook scans staged changes with gitleaks (ADR-007).
|
|
45
|
+
# It fails open when gitleaks is missing — CI is the hard backstop — but
|
|
46
|
+
# local feedback is much faster, so nudge here.
|
|
47
|
+
if ! command -v gitleaks >/dev/null 2>&1; then
|
|
48
|
+
echo ""
|
|
49
|
+
echo "NOTE: gitleaks is not installed — the pre-commit secret scan will be"
|
|
50
|
+
echo "skipped locally (CI still scans). Install it for fast local feedback:"
|
|
51
|
+
echo " https://github.com/gitleaks/gitleaks#installing"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
echo "To reinstall hooks after pulling changes, run:"
|
|
55
|
+
echo " .claude/scripts/install_hooks.sh"
|