git-repokit 0.9.10__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 (174) hide show
  1. git_repokit-0.9.10.dist-info/METADATA +422 -0
  2. git_repokit-0.9.10.dist-info/RECORD +174 -0
  3. git_repokit-0.9.10.dist-info/WHEEL +5 -0
  4. git_repokit-0.9.10.dist-info/entry_points.txt +2 -0
  5. git_repokit-0.9.10.dist-info/licenses/LICENSE +9 -0
  6. git_repokit-0.9.10.dist-info/top_level.txt +1 -0
  7. repokit/__init__.py +8 -0
  8. repokit/__main__.py +12 -0
  9. repokit/__version__.py +4 -0
  10. repokit/_vendor/__init__.py +0 -0
  11. repokit/_vendor/ailib/__init__.py +1 -0
  12. repokit/_vendor/ailib/analyzer.py +177 -0
  13. repokit/_vendor/ailib/backend.py +95 -0
  14. repokit/_vendor/ailib/cache.py +97 -0
  15. repokit/_vendor/ailib/parsers.py +52 -0
  16. repokit/_vendor/ailib/transports/__init__.py +16 -0
  17. repokit/_vendor/ailib/transports/cli.py +312 -0
  18. repokit/_vendor/ailib/transports/openai_compat.py +383 -0
  19. repokit/_vendor/ailib/transports/prompt_file.py +41 -0
  20. repokit/_vendor/ailib/types.py +224 -0
  21. repokit/auth_integration.py +244 -0
  22. repokit/badges.py +183 -0
  23. repokit/branch_utils.py +364 -0
  24. repokit/cli.py +2997 -0
  25. repokit/common_kit.py +202 -0
  26. repokit/config.py +686 -0
  27. repokit/defaults.py +220 -0
  28. repokit/directory_analyzer.py +1244 -0
  29. repokit/directory_profiles.py +423 -0
  30. repokit/git_config.py +370 -0
  31. repokit/github_create.py +142 -0
  32. repokit/github_setup.py +248 -0
  33. repokit/guardrails.py +707 -0
  34. repokit/history_cleaner.py +449 -0
  35. repokit/history_protection.py +434 -0
  36. repokit/hooks/pre-commit-guardrails +122 -0
  37. repokit/hooks/pre-commit-template +28 -0
  38. repokit/layers.py +510 -0
  39. repokit/licenses/0bsd.txt +14 -0
  40. repokit/licenses/LICENSES.md +27 -0
  41. repokit/licenses/__init__.py +142 -0
  42. repokit/licenses/agpl-3.0.txt +661 -0
  43. repokit/licenses/all-rights-reserved.txt +10 -0
  44. repokit/licenses/apache-2.0.txt +201 -0
  45. repokit/licenses/bsd-2-clause.txt +24 -0
  46. repokit/licenses/bsd-3-clause.txt +28 -0
  47. repokit/licenses/bsl-1.0.txt +23 -0
  48. repokit/licenses/cc-by-4.0.txt +395 -0
  49. repokit/licenses/cc-by-sa-4.0.txt +427 -0
  50. repokit/licenses/cc0-1.0.txt +121 -0
  51. repokit/licenses/epl-2.0.txt +277 -0
  52. repokit/licenses/gpl-2.0.txt +339 -0
  53. repokit/licenses/gpl-3.0.txt +674 -0
  54. repokit/licenses/isc.txt +15 -0
  55. repokit/licenses/lgpl-2.1.txt +504 -0
  56. repokit/licenses/lgpl-3.0.txt +165 -0
  57. repokit/licenses/mit.txt +21 -0
  58. repokit/licenses/mpl-2.0.txt +373 -0
  59. repokit/licenses/unlicense.txt +24 -0
  60. repokit/readme_blocks.py +112 -0
  61. repokit/remote_integration.py +627 -0
  62. repokit/repo_manager.py +2796 -0
  63. repokit/template_engine.py +391 -0
  64. repokit/templates/ai/claude/CLAUDE-nested.md.template +25 -0
  65. repokit/templates/ai/claude/CLAUDE.md.template +98 -0
  66. repokit/templates/ai/claude/instructions/step1_context_rebuilder.md +33 -0
  67. repokit/templates/ai/claude/instructions/step2_dev_workflow_process.md +67 -0
  68. repokit/templates/ai/claude/instructions/step3_context_bridge.md +52 -0
  69. repokit/templates/common/CHANGELOG.md.template +15 -0
  70. repokit/templates/common/CONTRIBUTING.md.template +31 -0
  71. repokit/templates/common/README.md.template +30 -0
  72. repokit/templates/common/gitattributes.template +17 -0
  73. repokit/templates/common/gitignore.template +141 -0
  74. repokit/templates/extras/FUNDING.yml.template +13 -0
  75. repokit/templates/extras/ROADMAP.md.template +17 -0
  76. repokit/templates/extras/dependabot.yml.template +22 -0
  77. repokit/templates/extras/one-offs-README.md.template +28 -0
  78. repokit/templates/extras/platform-support.md.template +11 -0
  79. repokit/templates/extras/pull_request_template.md.template +25 -0
  80. repokit/templates/extras/release-template.md.template +34 -0
  81. repokit/templates/extras/settings.json.template +11 -0
  82. repokit/templates/extras/stale.yml.template +14 -0
  83. repokit/templates/github/CODEOWNERS.template +11 -0
  84. repokit/templates/github/ISSUE_TEMPLATE/bug-report.md.template +29 -0
  85. repokit/templates/github/ISSUE_TEMPLATE/feature-request.md.template +19 -0
  86. repokit/templates/github/issues/notes.md.template +18 -0
  87. repokit/templates/github/issues/roadmap.md.template +15 -0
  88. repokit/templates/hooks/pre-commit.template +64 -0
  89. repokit/templates/layers/archetypes/any/claude-plugin/.claude-plugin/marketplace.json +20 -0
  90. repokit/templates/layers/archetypes/any/claude-plugin/plugin/.claude-plugin/plugin.json +15 -0
  91. repokit/templates/layers/archetypes/any/claude-plugin/plugin/README.md +28 -0
  92. repokit/templates/layers/archetypes/c-cpp/cmake-app/.github/workflows/ci-c-cpp.yml +24 -0
  93. repokit/templates/layers/archetypes/c-cpp/cmake-app/.repokit-common.toml.rkappend +4 -0
  94. repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/launch.json +29 -0
  95. repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/tasks.json +26 -0
  96. repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakeLists.txt +47 -0
  97. repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakePresets.json +23 -0
  98. repokit/templates/layers/archetypes/c-cpp/cmake-app/cmake/GenerateStamp.cmake +45 -0
  99. repokit/templates/layers/archetypes/c-cpp/cmake-app/src/main.cpp +27 -0
  100. repokit/templates/layers/archetypes/c-cpp/cmake-app/tests/test_version.cpp +20 -0
  101. repokit/templates/layers/archetypes/c-cpp/cmake-app/vcpkg.json +5 -0
  102. repokit/templates/layers/archetypes/c-cpp/minimal/.vscode/launch.json +27 -0
  103. repokit/templates/layers/archetypes/c-cpp/minimal/Makefile +10 -0
  104. repokit/templates/layers/archetypes/c-cpp/minimal/build.bat +7 -0
  105. repokit/templates/layers/archetypes/c-cpp/minimal/src/__RK_SNAKE__.c +26 -0
  106. repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/launch.json +17 -0
  107. repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/tasks.json +20 -0
  108. repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.sln +19 -0
  109. repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.vcxproj +41 -0
  110. repokit/templates/layers/archetypes/c-cpp/msbuild/src/main.cpp +26 -0
  111. repokit/templates/layers/archetypes/js-ts/claude-plugin/.repokit-common.toml +28 -0
  112. repokit/templates/layers/archetypes/js-ts/claude-plugin/.vscode/launch.json +34 -0
  113. repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/hooks.json +15 -0
  114. repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/scripts/session-start.mjs +23 -0
  115. repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/tests/manifest.test.js +30 -0
  116. repokit/templates/layers/archetypes/js-ts/node/.repokit-common.toml.rkappend +6 -0
  117. repokit/templates/layers/archetypes/js-ts/node/package.json +13 -0
  118. repokit/templates/layers/archetypes/js-ts/node-package/.github/workflows/release.yml +39 -0
  119. repokit/templates/layers/archetypes/js-ts/node-package/package.json +32 -0
  120. repokit/templates/layers/archetypes/js-ts/node-package/src/index.ts +13 -0
  121. repokit/templates/layers/archetypes/js-ts/node-package/tests/index.test.ts +11 -0
  122. repokit/templates/layers/archetypes/js-ts/script/.vscode/launch.json +34 -0
  123. repokit/templates/layers/archetypes/js-ts/script/__RK_PROJECT__.js +37 -0
  124. repokit/templates/layers/archetypes/js-ts/script/tests/version.test.js +26 -0
  125. repokit/templates/layers/archetypes/js-ts/ts/package.json +18 -0
  126. repokit/templates/layers/archetypes/js-ts/ts/tsconfig.json +15 -0
  127. repokit/templates/layers/archetypes/python/claude-plugin/.github/workflows/ci-python.yml +44 -0
  128. repokit/templates/layers/archetypes/python/claude-plugin/.repokit-common.toml +21 -0
  129. repokit/templates/layers/archetypes/python/claude-plugin/.vscode/launch.json +33 -0
  130. repokit/templates/layers/archetypes/python/claude-plugin/VERSION +1 -0
  131. repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/hooks.json +15 -0
  132. repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/run-hook.mjs +42 -0
  133. repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/session_start.py +31 -0
  134. repokit/templates/layers/archetypes/python/claude-plugin/plugin/tests/test_manifest.py +42 -0
  135. repokit/templates/layers/archetypes/python/pypi/.github/workflows/release.yml +63 -0
  136. repokit/templates/layers/archetypes/python/pypi/.vscode/launch.json +43 -0
  137. repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__init__.py +5 -0
  138. repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__main__.py +6 -0
  139. repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/_version.py +96 -0
  140. repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/cli.py +18 -0
  141. repokit/templates/layers/archetypes/python/pypi/pyproject.toml +71 -0
  142. repokit/templates/layers/archetypes/python/pypi/tests/conftest.py +11 -0
  143. repokit/templates/layers/archetypes/python/pypi/tests/test_version.py +50 -0
  144. repokit/templates/layers/archetypes/python/script/.github/workflows/ci-python.yml +43 -0
  145. repokit/templates/layers/archetypes/python/script/.repokit-common.toml +14 -0
  146. repokit/templates/layers/archetypes/python/script/.vscode/launch.json +33 -0
  147. repokit/templates/layers/archetypes/python/script/VERSION +1 -0
  148. repokit/templates/layers/archetypes/python/script/__RK_PROJECT__.py +40 -0
  149. repokit/templates/layers/archetypes/python/script/tests/test_version.py +25 -0
  150. repokit/templates/layers/archetypes/python/setuppy/requirements.txt +8 -0
  151. repokit/templates/layers/archetypes/python/setuppy/setup.py +23 -0
  152. repokit/templates/layers/languages/c-cpp/.gitignore.rkappend +20 -0
  153. repokit/templates/layers/languages/c-cpp/.repokit-common.toml +7 -0
  154. repokit/templates/layers/languages/c-cpp/include/__RK_SNAKE__/version.h +20 -0
  155. repokit/templates/layers/languages/c-cpp/src/app.rc +25 -0
  156. repokit/templates/layers/languages/js-ts/.editorconfig +15 -0
  157. repokit/templates/layers/languages/js-ts/.github/workflows/ci-js-ts.yml +41 -0
  158. repokit/templates/layers/languages/js-ts/.gitignore.rkappend +13 -0
  159. repokit/templates/layers/languages/js-ts/.repokit-common.toml +19 -0
  160. repokit/templates/layers/languages/js-ts/.vscode/launch.json +24 -0
  161. repokit/templates/layers/languages/js-ts/VERSION +1 -0
  162. repokit/templates/layers/languages/python/.github/workflows/ci-python.yml +76 -0
  163. repokit/templates/layers/languages/python/.gitignore.rkappend +15 -0
  164. repokit/templates/layers/languages/python/.vscode/launch.json +33 -0
  165. repokit/templates/layers/manifest.toml +302 -0
  166. repokit/templates/vault/CLAUDE.md +29 -0
  167. repokit/templates/vault/claude/_maps/MOC-project.md +15 -0
  168. repokit/templates/vault/claude/_oracle/backlinks.md +3 -0
  169. repokit/templates/vault/claude/_oracle/concepts.md +12 -0
  170. repokit/templates/vault/claude/_oracle/manifest.md +10 -0
  171. repokit/templates/vault/claude/_todo/ACTIVE-WORK.md +18 -0
  172. repokit/templates/vault/claude/_todo/OPEN-LOOPS.md +14 -0
  173. repokit/utils.py +316 -0
  174. repokit/vault.py +248 -0
@@ -0,0 +1,248 @@
1
+ """The GitHub half of a new project (0.9.3): what `/github-issues-setup` and
2
+ `/create-project` did by hand after the repository existed, behind the
3
+ `github_setup` setting (a list in the config, or `--github-setup a,b|all`).
4
+
5
+ The order is the skill's own and it matters: the labels and the two
6
+ evergreen issues go in BETWEEN `gh repo create` and the first push. The first
7
+ push lets Dependabot (the `dependabot` extra) open a pull request, which would
8
+ take issue number 1; the Roadmap and the Quick Notes must be #1 and #2
9
+ (djdarcy, 2026-10-22). After the push: discussions, topics, the `pypi`
10
+ environment for a preset that publishes there. Every step is idempotent: a
11
+ second run creates nothing that exists. ghtraf creates gists and needs a
12
+ secret only the person can set, so its commands are printed, not run.
13
+
14
+ All gh calls name the repository (`--repo OWNER/NAME`), so nothing depends on
15
+ the working directory; `run` is injectable so the tests can answer for gh.
16
+ Design: 2026-10-22__22-06-57__dev-workflow-process__0-9-x-parity-with-the-old-route.md (row 0.9.3).
17
+ """
18
+ import json
19
+ import os
20
+ import re
21
+ import string
22
+ import tempfile
23
+ from pathlib import Path
24
+ from typing import Callable, List, Optional, Tuple
25
+
26
+ STEPS = ("labels", "issues", "pin", "discussions", "topics", "pypi-environment", "traffic")
27
+
28
+ # /github-issues-setup, Step 1 (names, colours, descriptions as the skill has them)
29
+ LABELS = [
30
+ ("pinned", "d4c5f9", "Permanently open issue"),
31
+ ("evergreen", "2ea44f", "Never close -- living document updated over time"),
32
+ ("roadmap", "0075ca", "Project roadmap"),
33
+ ("scratchpad", "f9d0c4", "Quick notes and scratch space"),
34
+ ("architecture", "bfd4f2", "Structural decisions"),
35
+ ("epic", "5319e7", "Large multi-phase initiative"),
36
+ ("ideas", "c5def5", "Exploratory ideas"),
37
+ ("CurrentTask", "0e8a16", "Currently being worked on"),
38
+ ("NextTask", "e6b800", "Next item to pick up"),
39
+ ]
40
+ # Labels a created project's own files name (its issue templates' `labels:`,
41
+ # dependabot.yml's `labels:`), with GitHub's and Dependabot's usual colours.
42
+ # A repository created by gh has no default labels, so without these an issue
43
+ # filed from the bug template, or a Dependabot pull request, would ask for a
44
+ # label that does not exist (0.9.6; the live golden had them from the template).
45
+ REFERENCED = {
46
+ "bug": ("d73a4a", "Something isn't working"),
47
+ "enhancement": ("a2eeef", "New feature or request"),
48
+ "documentation": ("0075ca", "Improvements or additions to documentation"),
49
+ "dependencies": ("0366d6", "Pull requests that update a dependency file"),
50
+ "github-actions": ("000000", "Pull requests that update GitHub Actions"),
51
+ "pip": ("2b67c6", "Pull requests that update Python dependencies"),
52
+ }
53
+
54
+
55
+ def referenced_labels(repo_root: str) -> List[str]:
56
+ """Every label the project's issue templates and dependabot.yml name, in
57
+ the order first seen."""
58
+ names: List[str] = []
59
+ gh_dir = Path(repo_root, ".github")
60
+ for path in sorted((gh_dir / "ISSUE_TEMPLATE").glob("*.md")) if (gh_dir / "ISSUE_TEMPLATE").is_dir() else []:
61
+ m = re.search(r"^labels:\s*(.+)$", path.read_text(encoding="utf-8"), re.M)
62
+ if m:
63
+ names += [n.strip().strip("'\"") for n in m.group(1).split(",")]
64
+ dependabot = gh_dir / "dependabot.yml"
65
+ if dependabot.is_file():
66
+ names += re.findall(r'^\s*-\s*"?([A-Za-z0-9_.-]+)"?\s*$',
67
+ "\n".join(_labels_blocks(dependabot.read_text(encoding="utf-8"))), re.M)
68
+ seen: List[str] = []
69
+ for n in names:
70
+ if n and n not in seen:
71
+ seen.append(n)
72
+ return seen
73
+
74
+
75
+ def _labels_blocks(text: str) -> List[str]:
76
+ """The list items under each `labels:` key of a YAML file, as lines."""
77
+ out, lines = [], text.splitlines()
78
+ for i, line in enumerate(lines):
79
+ if line.strip() == "labels:":
80
+ indent = len(line) - len(line.lstrip())
81
+ for nxt in lines[i + 1:]:
82
+ if nxt.strip().startswith("-") and len(nxt) - len(nxt.lstrip()) >= indent:
83
+ out.append(nxt)
84
+ else:
85
+ break
86
+ return out
87
+
88
+
89
+ ISSUES = [ # title, labels, template
90
+ ("Roadmap", "pinned,roadmap,evergreen", "roadmap.md.template"),
91
+ ("Quick Notes -- Bugs, Features, Ideas", "pinned,scratchpad,evergreen", "notes.md.template"),
92
+ ]
93
+ TEMPLATES = Path(__file__).resolve().parent / "templates" / "github" / "issues"
94
+ PUBLISHES_TO_PYPI = ("python/pypi",)
95
+
96
+ Runner = Callable[[List[str]], Tuple[int, str]]
97
+
98
+
99
+ def setup_names(requested) -> List[str]:
100
+ """The steps to run, in STEPS order; `all` means every one; unknown refused."""
101
+ wanted = list(requested or [])
102
+ if "all" in wanted:
103
+ return list(STEPS)
104
+ unknown = [w for w in wanted if w not in STEPS]
105
+ if unknown:
106
+ raise ValueError(f"unknown github-setup steps {', '.join(unknown)}; known: {', '.join(STEPS)}, all")
107
+ return [s for s in STEPS if s in wanted]
108
+
109
+
110
+ def render_issue(template: str, project_name: str) -> str:
111
+ text = (TEMPLATES / template).read_text(encoding="utf-8")
112
+ return string.Template(text).safe_substitute(project_name=project_name)
113
+
114
+
115
+ class GhSetup:
116
+ """The steps for one repository. Each method returns report lines."""
117
+
118
+ def __init__(self, gh: List[str], repo: str, run: Runner):
119
+ self.gh, self.repo, self.run = gh, repo, run
120
+
121
+ def _gh(self, *args: str) -> Tuple[int, str]:
122
+ return self.run(self.gh + list(args))
123
+
124
+ # --- before the first push ------------------------------------------------
125
+
126
+ def labels(self, referenced: Optional[List[str]] = None) -> List[str]:
127
+ """The nine house labels, then any label the project's own files name
128
+ (referenced_labels) that the repository lacks."""
129
+ rc, out = self._gh("label", "list", "--repo", self.repo, "--limit", "200", "--json", "name")
130
+ if rc != 0:
131
+ return [f"labels: could not list them: {out}"]
132
+ have = {l["name"] for l in json.loads(out or "[]")}
133
+ wanted = list(LABELS) + [(n, *REFERENCED.get(n, ("ededed", ""))) for n in (referenced or [])
134
+ if n not in {l[0] for l in LABELS}]
135
+ made = []
136
+ for name, color, desc in wanted:
137
+ if name in have:
138
+ continue
139
+ cmd = ["label", "create", name, "--repo", self.repo, "--color", color]
140
+ if desc:
141
+ cmd += ["--description", desc]
142
+ rc, out = self._gh(*cmd)
143
+ if rc != 0:
144
+ return [f"labels: creating {name} failed: {out}"]
145
+ made.append(name)
146
+ have.add(name)
147
+ return [f"labels: created {', '.join(made)}" if made else "labels: all already there"]
148
+
149
+ def _issues(self) -> Optional[list]:
150
+ rc, out = self._gh("issue", "list", "--repo", self.repo, "--state", "all", "--limit", "100", "--json", "number,title")
151
+ return json.loads(out or "[]") if rc == 0 else None
152
+
153
+ def issues(self, project_name: str, drafts_dir: Optional[str] = None) -> List[str]:
154
+ existing = self._issues()
155
+ if existing is None:
156
+ return ["issues: could not list them"]
157
+ titles = {i["title"] for i in existing}
158
+ if all(t in titles for t, _, _ in ISSUES):
159
+ return ["issues: Roadmap and Quick Notes already there"]
160
+ if existing:
161
+ # They would not be #1 and #2: refuse rather than take later numbers.
162
+ return [f"issues: the repository already has {len(existing)} issue(s); Roadmap and Quick Notes "
163
+ "were not created, because they would not be #1 and #2"]
164
+ report = []
165
+ for title, labels, template in ISSUES:
166
+ body = render_issue(template, project_name)
167
+ if drafts_dir and os.path.isdir(drafts_dir):
168
+ Path(drafts_dir, "issue_" + template.replace(".md.template", ".md")).write_text(body, encoding="utf-8")
169
+ with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as f:
170
+ f.write(body)
171
+ body_file = f.name
172
+ try:
173
+ rc, out = self._gh("issue", "create", "--repo", self.repo, "--title", title,
174
+ "--label", labels, "--body-file", body_file)
175
+ finally:
176
+ os.unlink(body_file)
177
+ if rc != 0:
178
+ return report + [f"issues: creating {title!r} failed: {out}"]
179
+ m = re.search(r"/issues/(\d+)", out)
180
+ report.append(f"issues: #{m.group(1) if m else '?'} {title}")
181
+ return report
182
+
183
+ def pin(self) -> List[str]:
184
+ existing = self._issues() or []
185
+ owner, name = self.repo.split("/", 1)
186
+ q = ('query { repository(owner: "%s", name: "%s") { pinnedIssues(first: 10) '
187
+ '{ nodes { issue { number } } } } }') % (owner, name)
188
+ rc, out = self._gh("api", "graphql", "-f", f"query={q}")
189
+ pinned = set()
190
+ if rc == 0:
191
+ try:
192
+ nodes = json.loads(out)["data"]["repository"]["pinnedIssues"]["nodes"]
193
+ pinned = {n["issue"]["number"] for n in nodes}
194
+ except (ValueError, KeyError, TypeError):
195
+ pinned = set()
196
+ report = []
197
+ for title, _, _ in ISSUES:
198
+ number = next((i["number"] for i in existing if i["title"] == title), None)
199
+ if number is None:
200
+ report.append(f"pin: {title!r} not found")
201
+ continue
202
+ if number in pinned:
203
+ report.append(f"pin: #{number} already pinned")
204
+ continue
205
+ rc, node = self._gh("issue", "view", str(number), "--repo", self.repo, "--json", "id", "-q", ".id")
206
+ if rc != 0 or not node.strip():
207
+ report.append(f"pin: could not read #{number}'s id: {node}")
208
+ continue
209
+ m = 'mutation { pinIssue(input: {issueId: "%s"}) { issue { number } } }' % node.strip()
210
+ rc, out = self._gh("api", "graphql", "-f", f"query={m}")
211
+ report.append(f"pin: #{number} pinned" if rc == 0 else f"pin: #{number} failed: {out}")
212
+ return report
213
+
214
+ # --- after the push ---------------------------------------------------------
215
+
216
+ def discussions(self) -> List[str]:
217
+ rc, out = self._gh("repo", "edit", self.repo, "--enable-discussions")
218
+ return ["discussions: on" if rc == 0 else f"discussions: failed: {out}"]
219
+
220
+ def topics(self, topics: List[str]) -> List[str]:
221
+ if not topics:
222
+ return ["topics: none named (--topics a,b)"]
223
+ args = ["repo", "edit", self.repo]
224
+ for t in topics:
225
+ args += ["--add-topic", t]
226
+ rc, out = self._gh(*args)
227
+ return [f"topics: {', '.join(topics)}" if rc == 0 else f"topics: failed: {out}"]
228
+
229
+ def pypi_environment(self, archetype: Optional[str]) -> List[str]:
230
+ if archetype not in PUBLISHES_TO_PYPI:
231
+ return []
232
+ rc, out = self._gh("api", f"repos/{self.repo}/environments/pypi", "-X", "PUT")
233
+ return ["pypi-environment: created (register the trusted publisher on PyPI: workflow release.yml, environment pypi)"
234
+ if rc == 0 else f"pypi-environment: failed: {out}"]
235
+
236
+ def traffic(self, project_name: str) -> List[str]:
237
+ return ["traffic: ghtraf creates gists and needs a secret only you can set, so it is not run here.",
238
+ f" From the project: `ghtraf create --help` (the old route used --configure --ci-workflows "
239
+ f"--non-interactive), then `gh secret set TRAFFIC_GIST_TOKEN --repo {self.repo}`"]
240
+
241
+
242
+ def reminders(repo: str, extras: List[str]) -> List[str]:
243
+ """What has no API: said once, after the setup."""
244
+ out = [f"CI: watch the first run with `gh run list -R {repo} --limit 5`"]
245
+ if "funding" in extras:
246
+ out.append("Sponsorships: Settings > General > Features > Sponsorships (FUNDING.yml is in .github/)")
247
+ out.append("Pages, if the project serves docs/: Settings > Pages > main, /docs")
248
+ return out