tipalti 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tipalti-0.0.1/.claude/skills/gh-issues/SKILL.md +45 -0
- tipalti-0.0.1/.claude/skills/gh-issues/scripts/gh-issues.sh +52 -0
- tipalti-0.0.1/.claude/skills/notebooklm/SKILL.md +47 -0
- tipalti-0.0.1/.claude/skills/notebooklm/scripts/get-repo-sources.sh +200 -0
- tipalti-0.0.1/.claude/skills/pr-review/SKILL.md +120 -0
- tipalti-0.0.1/.claude/skills/pr-review/scripts/portability-lint.sh +57 -0
- tipalti-0.0.1/.claude/skills/pr-review/scripts/pr-batch.sh +57 -0
- tipalti-0.0.1/.claude/skills/pr-review/scripts/pr-comments.sh +100 -0
- tipalti-0.0.1/.claude/skills/pr-review/scripts/pr-reply.sh +66 -0
- tipalti-0.0.1/.claude/skills/pr-review/scripts/pr-status.sh +161 -0
- tipalti-0.0.1/.claude/skills/pr-review/scripts/workflow.sh +99 -0
- tipalti-0.0.1/.claude/skills/pypi-maintainer/SKILL.md +75 -0
- tipalti-0.0.1/.claude/skills/pypi-maintainer/scripts/switch-source.sh +102 -0
- tipalti-0.0.1/.claude/skills/run-tests/SKILL.md +50 -0
- tipalti-0.0.1/.claude/skills/run-tests/scripts/test.sh +52 -0
- tipalti-0.0.1/.claude/skills/sonarclaude/SKILL.md +84 -0
- tipalti-0.0.1/.claude/skills/sonarclaude/scripts/sonar.sh +263 -0
- tipalti-0.0.1/.claude/skills/version-bump/SKILL.md +66 -0
- tipalti-0.0.1/.claude/skills/version-bump/scripts/bump.py +178 -0
- tipalti-0.0.1/.claude/skills.local.yaml.example +14 -0
- tipalti-0.0.1/.flake8 +7 -0
- tipalti-0.0.1/.github/workflows/publish.yml +88 -0
- tipalti-0.0.1/.github/workflows/tests.yml +114 -0
- tipalti-0.0.1/.gitignore +222 -0
- tipalti-0.0.1/.markdownlint-cli2.yaml +19 -0
- tipalti-0.0.1/CHANGELOG.md +15 -0
- tipalti-0.0.1/CLAUDE.md +88 -0
- tipalti-0.0.1/LICENSE +21 -0
- tipalti-0.0.1/PKG-INFO +63 -0
- tipalti-0.0.1/README.md +46 -0
- tipalti-0.0.1/pyproject.toml +70 -0
- tipalti-0.0.1/tests/test_cli_learn.py +39 -0
- tipalti-0.0.1/tests/test_cli_smoke.py +24 -0
- tipalti-0.0.1/tests/test_cli_whoami.py +21 -0
- tipalti-0.0.1/tipalti/__init__.py +13 -0
- tipalti-0.0.1/tipalti/__main__.py +10 -0
- tipalti-0.0.1/tipalti/cli/__init__.py +84 -0
- tipalti-0.0.1/tipalti/cli/_commands/__init__.py +0 -0
- tipalti-0.0.1/tipalti/cli/_commands/explain.py +38 -0
- tipalti-0.0.1/tipalti/cli/_commands/learn.py +82 -0
- tipalti-0.0.1/tipalti/cli/_commands/whoami.py +29 -0
- tipalti-0.0.1/tipalti/cli/_errors.py +41 -0
- tipalti-0.0.1/tipalti/cli/_output.py +53 -0
- tipalti-0.0.1/tipalti/explain/__init__.py +24 -0
- tipalti-0.0.1/tipalti/explain/catalog.py +88 -0
- tipalti-0.0.1/uv.lock +467 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gh-issues
|
|
3
|
+
description: >
|
|
4
|
+
Fetch GitHub issues with full body and comments. Use when checking issues,
|
|
5
|
+
reviewing bug reports, or the user says "check issues", "fetch issues",
|
|
6
|
+
"issue #N", or references GitHub issue numbers.
|
|
7
|
+
triggers:
|
|
8
|
+
- check issues
|
|
9
|
+
- fetch issues
|
|
10
|
+
- issue #
|
|
11
|
+
- github issues
|
|
12
|
+
- verify issues
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# GitHub Issues Skill
|
|
16
|
+
|
|
17
|
+
Fetch one or more GitHub issues with full body text and all comments.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Single issue
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bash .claude/skills/gh-issues/scripts/gh-issues.sh 191
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Range
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bash .claude/skills/gh-issues/scripts/gh-issues.sh 191-197
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Specific list
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bash .claude/skills/gh-issues/scripts/gh-issues.sh 191 195 197
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Explicit repo
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bash .claude/skills/gh-issues/scripts/gh-issues.sh --repo owner/repo 42-50
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Output is JSON per issue with: number, title, state, labels, body, and comments array.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Fetch GitHub issues with full body and comments
|
|
3
|
+
# Usage: gh-issues.sh [RANGE|NUMBER] [--repo OWNER/REPO]
|
|
4
|
+
# gh-issues.sh 191-197 # range
|
|
5
|
+
# gh-issues.sh 191 # single
|
|
6
|
+
# gh-issues.sh 191 192 195 # list
|
|
7
|
+
# gh-issues.sh --repo foo/bar 5 # explicit repo
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
REPO_FLAG=""
|
|
12
|
+
NUMBERS=()
|
|
13
|
+
|
|
14
|
+
while [[ $# -gt 0 ]]; do
|
|
15
|
+
case "$1" in
|
|
16
|
+
--repo)
|
|
17
|
+
if [[ $# -lt 2 || -z "$2" ]]; then
|
|
18
|
+
echo "Error: --repo requires a value (OWNER/REPO)" >&2
|
|
19
|
+
echo "Usage: gh-issues.sh [RANGE|NUMBER...] [--repo OWNER/REPO]" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
REPO_FLAG="--repo $2"
|
|
23
|
+
shift 2 ;;
|
|
24
|
+
*-*) # range like 191-197
|
|
25
|
+
IFS='-' read -r start end <<< "$1"
|
|
26
|
+
for ((i=start; i<=end; i++)); do NUMBERS+=("$i"); done
|
|
27
|
+
shift ;;
|
|
28
|
+
*) NUMBERS+=("$1"); shift ;;
|
|
29
|
+
esac
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
if [[ ${#NUMBERS[@]} -eq 0 ]]; then
|
|
33
|
+
echo "Usage: gh-issues.sh [RANGE|NUMBER...] [--repo OWNER/REPO]" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
for num in "${NUMBERS[@]}"; do
|
|
38
|
+
echo "========================================"
|
|
39
|
+
echo "ISSUE #${num}"
|
|
40
|
+
echo "========================================"
|
|
41
|
+
# shellcheck disable=SC2086
|
|
42
|
+
gh issue view "$num" $REPO_FLAG --json number,title,state,labels,body,comments \
|
|
43
|
+
--jq '{
|
|
44
|
+
number: .number,
|
|
45
|
+
title: .title,
|
|
46
|
+
state: .state,
|
|
47
|
+
labels: [.labels[].name],
|
|
48
|
+
body: .body,
|
|
49
|
+
comments: [.comments[] | {author: .author.login, body: .body}]
|
|
50
|
+
}' 2>/dev/null || echo "ERROR: Could not fetch issue #${num}"
|
|
51
|
+
echo
|
|
52
|
+
done
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: notebooklm
|
|
3
|
+
description: >
|
|
4
|
+
Generate GitHub links to repo documentation for NotebookLM ingestion.
|
|
5
|
+
Use when: the user wants to create a NotebookLM notebook about a project,
|
|
6
|
+
needs doc links for a repo, or says "notebooklm", "notebook sources",
|
|
7
|
+
"get repo sources", or "doc links".
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# NotebookLM — Repo Source Links
|
|
11
|
+
|
|
12
|
+
Generate GitHub blob URLs for all documentation in the current repo, ready to paste into Google NotebookLM as sources.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- Creating a NotebookLM notebook about a project
|
|
17
|
+
- Gathering all doc links for a repo
|
|
18
|
+
- Exporting documentation URLs for any external tool
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Categorized output (default)
|
|
24
|
+
bash .claude/skills/notebooklm/scripts/get-repo-sources.sh
|
|
25
|
+
|
|
26
|
+
# Plain URLs only (for copy-paste)
|
|
27
|
+
bash .claude/skills/notebooklm/scripts/get-repo-sources.sh --plain
|
|
28
|
+
|
|
29
|
+
# Include plans and specs
|
|
30
|
+
bash .claude/skills/notebooklm/scripts/get-repo-sources.sh --all
|
|
31
|
+
|
|
32
|
+
# Override branch
|
|
33
|
+
bash .claude/skills/notebooklm/scripts/get-repo-sources.sh --branch develop
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Options
|
|
37
|
+
|
|
38
|
+
| Flag | Default | Description |
|
|
39
|
+
|------|---------|-------------|
|
|
40
|
+
| `--all` | off | Include plans, specs, and changelogs |
|
|
41
|
+
| `--plain` | off | Output only URLs, one per line (no headers) |
|
|
42
|
+
| `--branch NAME` | auto-detect | Override the git branch used in URLs |
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
- Must be run inside a git repository with a GitHub remote
|
|
47
|
+
- `git` CLI available
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# get-repo-sources — Generate GitHub URLs for repo docs, ready for NotebookLM
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
# --- Defaults ---
|
|
6
|
+
INCLUDE_ALL=false
|
|
7
|
+
PLAIN=false
|
|
8
|
+
BRANCH=""
|
|
9
|
+
|
|
10
|
+
# --- Parse args ---
|
|
11
|
+
usage() {
|
|
12
|
+
echo "Usage: get-repo-sources.sh [--all] [--plain] [--branch NAME]"
|
|
13
|
+
echo " --all Include plans, specs, and changelogs"
|
|
14
|
+
echo " --plain Output plain URLs only (no headers)"
|
|
15
|
+
echo " --branch Override branch (default: auto-detect)"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
while [[ $# -gt 0 ]]; do
|
|
19
|
+
case "$1" in
|
|
20
|
+
--all) INCLUDE_ALL=true; shift ;;
|
|
21
|
+
--plain) PLAIN=true; shift ;;
|
|
22
|
+
--branch)
|
|
23
|
+
if [[ $# -lt 2 || -z "$2" ]]; then
|
|
24
|
+
echo "Error: --branch requires a value" >&2
|
|
25
|
+
usage >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
BRANCH="$2"
|
|
29
|
+
shift 2 ;;
|
|
30
|
+
-h|--help)
|
|
31
|
+
usage
|
|
32
|
+
exit 0
|
|
33
|
+
;;
|
|
34
|
+
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
# --- Detect repo root ---
|
|
39
|
+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || {
|
|
40
|
+
echo "Error: not inside a git repository" >&2; exit 1
|
|
41
|
+
}
|
|
42
|
+
cd "$REPO_ROOT"
|
|
43
|
+
|
|
44
|
+
# --- Detect GitHub URL ---
|
|
45
|
+
REMOTE_URL="$(git remote get-url origin 2>/dev/null)" || {
|
|
46
|
+
echo "Error: no 'origin' remote found" >&2; exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Normalise to https://github.com/OWNER/REPO
|
|
50
|
+
GITHUB_URL="$REMOTE_URL"
|
|
51
|
+
GITHUB_URL="${GITHUB_URL%.git}"
|
|
52
|
+
if [[ "$GITHUB_URL" == git@github.com:* ]]; then
|
|
53
|
+
GITHUB_URL="https://github.com/${GITHUB_URL#git@github.com:}"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# --- Detect branch (handle detached HEAD) ---
|
|
57
|
+
if [[ -z "$BRANCH" ]]; then
|
|
58
|
+
BRANCH="$(git branch --show-current 2>/dev/null || true)"
|
|
59
|
+
fi
|
|
60
|
+
if [[ -z "$BRANCH" ]]; then
|
|
61
|
+
echo "Error: cannot determine the current branch (detached HEAD?)." >&2
|
|
62
|
+
echo "Hint: pass --branch <name> with a branch that exists on origin." >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# --- Verify the remote ref exists locally ---
|
|
67
|
+
REMOTE_REF="origin/${BRANCH}"
|
|
68
|
+
if ! git show-ref --verify --quiet "refs/remotes/${REMOTE_REF}"; then
|
|
69
|
+
echo "Error: remote ref '${REMOTE_REF}' is not available locally." >&2
|
|
70
|
+
echo "Hint: run 'git fetch origin ${BRANCH}', push the branch first, or pass --branch with an existing remote branch name." >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
BASE_URL="${GITHUB_URL}/blob/${BRANCH}"
|
|
75
|
+
|
|
76
|
+
# --- Collect all .md files from remote branch (only files that exist on GitHub) ---
|
|
77
|
+
ALL_FILES=$(git ls-tree -r --name-only "${REMOTE_REF}" \
|
|
78
|
+
| grep '\.md$' \
|
|
79
|
+
| grep -v -E '^(\.github/|\.claude/|\.pytest_cache/|node_modules/|\.venv/|__pycache__/|\.mypy_cache/)' \
|
|
80
|
+
| sort)
|
|
81
|
+
|
|
82
|
+
# --- Filter ---
|
|
83
|
+
filter_file() {
|
|
84
|
+
local f="$1"
|
|
85
|
+
# Always exclude dot-dirs and lock files
|
|
86
|
+
[[ "$f" == .github/* ]] && return 1
|
|
87
|
+
|
|
88
|
+
if [[ "$INCLUDE_ALL" == "false" ]]; then
|
|
89
|
+
# Exclude plans, specs, changelogs by default
|
|
90
|
+
[[ "$f" == *superpowers/plans/* ]] && return 1
|
|
91
|
+
[[ "$f" == *superpowers/specs/* ]] && return 1
|
|
92
|
+
[[ "$f" == CHANGELOG.md ]] && return 1
|
|
93
|
+
fi
|
|
94
|
+
return 0
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
FILTERED=()
|
|
98
|
+
while IFS= read -r f; do
|
|
99
|
+
[[ -z "$f" ]] && continue
|
|
100
|
+
if filter_file "$f"; then
|
|
101
|
+
FILTERED+=("$f")
|
|
102
|
+
fi
|
|
103
|
+
done <<< "$ALL_FILES"
|
|
104
|
+
|
|
105
|
+
if [[ ${#FILTERED[@]} -eq 0 ]]; then
|
|
106
|
+
echo "No documentation files found." >&2
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# --- Categorise ---
|
|
111
|
+
declare -a CAT_CORE=() CAT_ARCH=() CAT_START=() CAT_FEATURES=() CAT_PROTOCOL=()
|
|
112
|
+
declare -a CAT_CLIENTS=() CAT_USECASES=() CAT_PACKAGES=() CAT_PLANS=() CAT_SPECS=() CAT_OTHER=()
|
|
113
|
+
|
|
114
|
+
categorise() {
|
|
115
|
+
local f="$1"
|
|
116
|
+
local base
|
|
117
|
+
base="$(basename "$f")"
|
|
118
|
+
|
|
119
|
+
# Path-based categories first (more specific)
|
|
120
|
+
case "$f" in
|
|
121
|
+
*protocol/*) CAT_PROTOCOL+=("$f"); return ;;
|
|
122
|
+
*clients/*) CAT_CLIENTS+=("$f"); return ;;
|
|
123
|
+
*use-cases/*) CAT_USECASES+=("$f"); return ;;
|
|
124
|
+
*packages/*) CAT_PACKAGES+=("$f"); return ;;
|
|
125
|
+
*superpowers/plans*) CAT_PLANS+=("$f"); return ;;
|
|
126
|
+
*superpowers/specs*) CAT_SPECS+=("$f"); return ;;
|
|
127
|
+
*plugins/*) CAT_OTHER+=("$f"); return ;;
|
|
128
|
+
*skills/*) CAT_OTHER+=("$f"); return ;;
|
|
129
|
+
esac
|
|
130
|
+
|
|
131
|
+
# Root-level core files (only match files at repo root)
|
|
132
|
+
case "$f" in
|
|
133
|
+
README.md|CLAUDE.md|index.md) CAT_CORE+=("$f"); return ;;
|
|
134
|
+
CHANGELOG.md) CAT_OTHER+=("$f"); return ;;
|
|
135
|
+
esac
|
|
136
|
+
|
|
137
|
+
# By filename keywords
|
|
138
|
+
case "$base" in
|
|
139
|
+
layer*|overview*|design*|server-architecture*|*-spec*)
|
|
140
|
+
CAT_ARCH+=("$f"); return ;;
|
|
141
|
+
getting-started*|cli*|grow-your-agent*)
|
|
142
|
+
CAT_START+=("$f"); return ;;
|
|
143
|
+
*) ;;
|
|
144
|
+
esac
|
|
145
|
+
|
|
146
|
+
# Docs directory features
|
|
147
|
+
if [[ "$f" == docs/* ]]; then
|
|
148
|
+
CAT_FEATURES+=("$f")
|
|
149
|
+
else
|
|
150
|
+
CAT_OTHER+=("$f")
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for f in "${FILTERED[@]}"; do
|
|
155
|
+
categorise "$f"
|
|
156
|
+
done
|
|
157
|
+
|
|
158
|
+
# --- Output ---
|
|
159
|
+
print_urls() {
|
|
160
|
+
local label="$1"; shift
|
|
161
|
+
local -n arr=$1
|
|
162
|
+
|
|
163
|
+
if [[ ${#arr[@]} -eq 0 ]]; then return; fi
|
|
164
|
+
|
|
165
|
+
if [[ "$PLAIN" == "false" ]]; then
|
|
166
|
+
echo ""
|
|
167
|
+
echo "--- ${label} ---"
|
|
168
|
+
fi
|
|
169
|
+
for f in "${arr[@]}"; do
|
|
170
|
+
echo "${BASE_URL}/${f}"
|
|
171
|
+
done
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if [[ "$PLAIN" == "false" ]]; then
|
|
175
|
+
echo "=== Documentation Links for NotebookLM ==="
|
|
176
|
+
echo "Repository: ${GITHUB_URL}"
|
|
177
|
+
echo "Branch: ${BRANCH}"
|
|
178
|
+
echo "Files: ${#FILTERED[@]}"
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
print_urls "Core" CAT_CORE
|
|
182
|
+
print_urls "Architecture" CAT_ARCH
|
|
183
|
+
print_urls "Getting Started" CAT_START
|
|
184
|
+
print_urls "Features" CAT_FEATURES
|
|
185
|
+
print_urls "Protocol" CAT_PROTOCOL
|
|
186
|
+
print_urls "Clients" CAT_CLIENTS
|
|
187
|
+
print_urls "Use Cases" CAT_USECASES
|
|
188
|
+
print_urls "Packages" CAT_PACKAGES
|
|
189
|
+
print_urls "Plans" CAT_PLANS
|
|
190
|
+
print_urls "Specs" CAT_SPECS
|
|
191
|
+
print_urls "Other" CAT_OTHER
|
|
192
|
+
|
|
193
|
+
if [[ "$PLAIN" == "false" ]]; then
|
|
194
|
+
echo ""
|
|
195
|
+
echo "=== Plain URLs (copy-paste block) ==="
|
|
196
|
+
echo ""
|
|
197
|
+
for f in "${FILTERED[@]}"; do
|
|
198
|
+
echo "${BASE_URL}/${f}"
|
|
199
|
+
done
|
|
200
|
+
fi
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pr-review
|
|
3
|
+
description: >
|
|
4
|
+
Steward-specific PR workflow: branch, commit, push, PR, wait for Qodo/Copilot,
|
|
5
|
+
triage, fix, reply, resolve. Adds a portability lint (no absolute /home paths,
|
|
6
|
+
no per-user dotfile refs in committed docs), an alignment-delta check when
|
|
7
|
+
CLAUDE.md or culture.yaml change, and greenfield-aware test/version-bump
|
|
8
|
+
steps. Use when: creating PRs in steward, handling review feedback, or the
|
|
9
|
+
user says "create PR", "review comments", "address feedback", "resolve threads".
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# PR Review — Steward edition
|
|
13
|
+
|
|
14
|
+
Steward's PRs touch agent prompts, `culture.yaml` configs, and cross-project
|
|
15
|
+
guidance. The generic `pr-review` skills don't know that, so they miss two
|
|
16
|
+
classes of bugs Steward keeps producing:
|
|
17
|
+
|
|
18
|
+
- **Path leaks** — committing absolute home-directory paths that work only on
|
|
19
|
+
the author's machine. (PR #1 had four of these.)
|
|
20
|
+
- **Per-user config dependencies** — referencing a dotfile under the user's
|
|
21
|
+
home directory in repo guidance, breaking reproducibility for other
|
|
22
|
+
contributors and CI.
|
|
23
|
+
|
|
24
|
+
This skill specializes Culture's `pr-review` to catch both up front, plus an
|
|
25
|
+
alignment-delta step when Steward-affecting files change. The workflow is
|
|
26
|
+
encapsulated in `scripts/workflow.sh` — follow that, not a manual checklist.
|
|
27
|
+
|
|
28
|
+
## Prerequisites
|
|
29
|
+
|
|
30
|
+
Hard requirements: `gh` (GitHub CLI), `jq`, `bash`, `python3` (stdlib only),
|
|
31
|
+
`curl` (used by `pr-status.sh`).
|
|
32
|
+
|
|
33
|
+
Soft requirement: `PyYAML` is needed **only for suffix mode** of the sibling
|
|
34
|
+
`agent-config` skill, where it parses Culture's server manifest. Path mode
|
|
35
|
+
and every `pr-review` script work without it. If suffix mode runs without
|
|
36
|
+
PyYAML it exits with a clear install hint.
|
|
37
|
+
|
|
38
|
+
Per-machine paths (sibling-project layout) live in
|
|
39
|
+
`.claude/skills.local.yaml`; see the committed `.example` for the schema.
|
|
40
|
+
|
|
41
|
+
## How to run
|
|
42
|
+
|
|
43
|
+
`scripts/workflow.sh` is the entry point. Subcommands:
|
|
44
|
+
|
|
45
|
+
| Command | Purpose |
|
|
46
|
+
|---------|---------|
|
|
47
|
+
| `workflow.sh lint` | Portability lint on the current diff (staged + unstaged). |
|
|
48
|
+
| `workflow.sh poll <PR>` | Fetch and display all review comments. |
|
|
49
|
+
| `workflow.sh delta` | Dump each sibling project's `CLAUDE.md` head + `culture.yaml`. |
|
|
50
|
+
| `workflow.sh reply <PR>` | Batch reply (JSONL on stdin) and resolve threads. |
|
|
51
|
+
| `workflow.sh help` | Print this list. |
|
|
52
|
+
|
|
53
|
+
The vendored single-comment helpers — `pr-reply.sh`, `pr-status.sh` — live
|
|
54
|
+
next to `workflow.sh` and are usable directly when batching isn't appropriate.
|
|
55
|
+
|
|
56
|
+
## End-to-end flow
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
git checkout -b <type>/<desc>
|
|
60
|
+
# ... edit ...
|
|
61
|
+
.claude/skills/pr-review/scripts/workflow.sh lint
|
|
62
|
+
git commit -am "..." && git push -u origin <branch>
|
|
63
|
+
gh pr create --title "..." --body "..." # title <70 chars, body signed "- Claude"
|
|
64
|
+
sleep 300 # wait for Qodo + Copilot
|
|
65
|
+
.claude/skills/pr-review/scripts/workflow.sh poll <PR>
|
|
66
|
+
# triage; if CLAUDE.md/culture.yaml/.claude/skills changed:
|
|
67
|
+
.claude/skills/pr-review/scripts/workflow.sh delta
|
|
68
|
+
# fix, re-lint, push
|
|
69
|
+
.claude/skills/pr-review/scripts/workflow.sh reply <PR> < replies.jsonl
|
|
70
|
+
gh pr checks <PR>
|
|
71
|
+
# Wait for human merge — never merge yourself.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Branch naming: `fix/<desc>`, `feat/<desc>`, `docs/<desc>`, `skill/<name>`.
|
|
75
|
+
Commit/PR signature: `- Claude` (workspace convention). The reply script
|
|
76
|
+
auto-appends `- Claude` only if the body isn't already signed, so JSONL
|
|
77
|
+
entries can include or omit it.
|
|
78
|
+
|
|
79
|
+
## Triage rules
|
|
80
|
+
|
|
81
|
+
For every comment, decide **FIX** or **PUSHBACK** with reasoning.
|
|
82
|
+
|
|
83
|
+
Default to **FIX** for: portability complaints (always valid for Steward —
|
|
84
|
+
recurring bug class), test or doc requests, style nits aligned with workspace
|
|
85
|
+
conventions.
|
|
86
|
+
|
|
87
|
+
Default to **PUSHBACK** for: architecture opinions that conflict with workspace
|
|
88
|
+
`CLAUDE.md` or the all-backends rule; greenfield false-positives (e.g. "add
|
|
89
|
+
tests" before there's any source — defer to a later PR, don't refuse).
|
|
90
|
+
|
|
91
|
+
### Alignment-delta rule
|
|
92
|
+
|
|
93
|
+
If the PR touches `CLAUDE.md`, `culture.yaml`, or anything under
|
|
94
|
+
`.claude/skills/`, run `workflow.sh delta` **before** declaring FIX or
|
|
95
|
+
PUSHBACK on each comment. The script dumps the head of every sibling
|
|
96
|
+
project's `CLAUDE.md` plus the full `culture.yaml`, using `sibling_projects`
|
|
97
|
+
from `skills.local.yaml`. Note any sibling that needs a follow-up PR and
|
|
98
|
+
mention it in your reply.
|
|
99
|
+
|
|
100
|
+
## Greenfield-aware steps
|
|
101
|
+
|
|
102
|
+
The lint and the workflow script are always-on. Stack-specific steps are
|
|
103
|
+
conditional and currently no-op (greenfield repo):
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
[ -d tests ] && [ -f pyproject.toml ] && uv run pytest tests/ -x -q
|
|
107
|
+
[ -f pyproject.toml ] && bump_version_per_project_convention # see project README
|
|
108
|
+
[ -f .markdownlint-cli2.yaml ] && markdownlint-cli2 "$(git diff --name-only --cached '*.md')"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Revisit each line as the corresponding stack element actually lands.
|
|
112
|
+
|
|
113
|
+
## Reply etiquette
|
|
114
|
+
|
|
115
|
+
Every comment must get a reply — no silent fixes. Always pass `--resolve`
|
|
116
|
+
when batch-replying so threads close automatically. Reference the
|
|
117
|
+
review-comment IDs in the fix-up commit message. Steward currently has no
|
|
118
|
+
SonarCloud integration and isn't a registered mesh agent, so skip the
|
|
119
|
+
sonarclaude check and the post-merge IRC ping that Culture's `pr-review`
|
|
120
|
+
includes — those will return when Steward joins those systems.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Portability lint: catch path leaks and per-user config dependencies in
|
|
3
|
+
# committed docs/configs before they ship in a PR. Steward's recurring bug
|
|
4
|
+
# class.
|
|
5
|
+
#
|
|
6
|
+
# Usage: portability-lint.sh [--all]
|
|
7
|
+
# default: lint files modified vs HEAD (staged + unstaged)
|
|
8
|
+
# --all: lint all tracked files
|
|
9
|
+
#
|
|
10
|
+
# Exits 0 if clean, 1 if any leak is found.
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
mode="${1:-diff}"
|
|
15
|
+
case "$mode" in
|
|
16
|
+
--all) files=$(git ls-files -- ':(exclude)*.lock') ;;
|
|
17
|
+
diff|--diff) files=$(git diff --diff-filter=AMR --name-only HEAD -- ':(exclude)*.lock') ;;
|
|
18
|
+
*) echo "Usage: $(basename "$0") [--all]" >&2; exit 2 ;;
|
|
19
|
+
esac
|
|
20
|
+
|
|
21
|
+
[ -z "$files" ] && { echo "(no files to check)"; exit 0; }
|
|
22
|
+
|
|
23
|
+
# ----- Check 1: hard-coded /home/<user>/... paths -----
|
|
24
|
+
hits1=$(echo "$files" | xargs -r grep -nE '/home/[a-z][a-z0-9_-]+/' 2>/dev/null || true)
|
|
25
|
+
|
|
26
|
+
# ----- Check 2: per-user dotfile *config* refs in committed docs/configs -----
|
|
27
|
+
# Carve-outs (allowed, NOT flagged):
|
|
28
|
+
# - ~/.claude/skills/<x>/scripts/ vendored tool calls
|
|
29
|
+
# - ~/.culture/ Culture mesh data this skill is supposed to read
|
|
30
|
+
md_yaml=$(echo "$files" | grep -E '\.(md|ya?ml|toml|json|jsonc)$' || true)
|
|
31
|
+
if [ -n "$md_yaml" ]; then
|
|
32
|
+
hits2=$(echo "$md_yaml" | xargs -r grep -nE '~/\.[A-Za-z]' 2>/dev/null \
|
|
33
|
+
| grep -vE '~/\.claude/skills/[^[:space:]"]+/scripts/' \
|
|
34
|
+
| grep -vE '~/\.culture/' \
|
|
35
|
+
|| true)
|
|
36
|
+
else
|
|
37
|
+
hits2=""
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
fail=0
|
|
41
|
+
if [ -n "$hits1" ]; then
|
|
42
|
+
echo "❌ Hard-coded /home/<user>/ paths:"
|
|
43
|
+
echo "$hits1" | sed 's/^/ /'
|
|
44
|
+
echo " Fix: use ../sibling, repo URL, or \$WORKSPACE/sibling instead."
|
|
45
|
+
fail=1
|
|
46
|
+
fi
|
|
47
|
+
if [ -n "$hits2" ]; then
|
|
48
|
+
[ "$fail" -eq 1 ] && echo
|
|
49
|
+
echo "❌ Per-user ~/.<dotfile> config refs in committed doc/config:"
|
|
50
|
+
echo "$hits2" | sed 's/^/ /'
|
|
51
|
+
echo " Allowed carve-outs: ~/.claude/skills/.../scripts/ (tool calls), ~/.culture/ (mesh data)."
|
|
52
|
+
echo " Otherwise: commit a repo-local config or document a portable lookup."
|
|
53
|
+
fail=1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
[ "$fail" -eq 0 ] && echo "✓ portability lint clean ($(echo "$files" | wc -l | tr -d ' ') files checked)"
|
|
57
|
+
exit $fail
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Batch reply to PR review comments from JSONL on stdin.
|
|
5
|
+
# Each line: {"comment_id": 123, "body": "reply text"}
|
|
6
|
+
# Usage: pr-batch.sh [--repo OWNER/REPO] [--resolve] PR_NUMBER < input.jsonl
|
|
7
|
+
|
|
8
|
+
REPO=""
|
|
9
|
+
RESOLVE=false
|
|
10
|
+
|
|
11
|
+
while [[ $# -gt 0 ]]; do
|
|
12
|
+
case "$1" in
|
|
13
|
+
--repo) REPO="$2"; shift 2 ;;
|
|
14
|
+
--resolve) RESOLVE=true; shift ;;
|
|
15
|
+
*) break ;;
|
|
16
|
+
esac
|
|
17
|
+
done
|
|
18
|
+
|
|
19
|
+
PR_NUMBER="${1:?Usage: pr-batch.sh [--repo OWNER/REPO] [--resolve] PR_NUMBER < input.jsonl}"
|
|
20
|
+
|
|
21
|
+
if [[ -z "$REPO" ]]; then
|
|
22
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
RESOLVE_FLAG=""
|
|
27
|
+
if [[ "$RESOLVE" == true ]]; then
|
|
28
|
+
RESOLVE_FLAG="--resolve"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
SUCCESS=0
|
|
32
|
+
FAIL=0
|
|
33
|
+
|
|
34
|
+
while IFS= read -r line; do
|
|
35
|
+
# Skip empty lines
|
|
36
|
+
[[ -z "$line" ]] && continue
|
|
37
|
+
|
|
38
|
+
COMMENT_ID=$(echo "$line" | jq -r '.comment_id')
|
|
39
|
+
BODY=$(echo "$line" | jq -r '.body')
|
|
40
|
+
|
|
41
|
+
if [[ "$COMMENT_ID" == "null" || "$BODY" == "null" ]]; then
|
|
42
|
+
echo "SKIP: invalid line: $line"
|
|
43
|
+
((FAIL++)) || true
|
|
44
|
+
continue
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo "--- Comment $COMMENT_ID ---"
|
|
48
|
+
if bash "$SCRIPT_DIR/pr-reply.sh" --repo "$REPO" $RESOLVE_FLAG "$PR_NUMBER" "$COMMENT_ID" "$BODY"; then
|
|
49
|
+
((SUCCESS++)) || true
|
|
50
|
+
else
|
|
51
|
+
echo "FAILED: comment $COMMENT_ID"
|
|
52
|
+
((FAIL++)) || true
|
|
53
|
+
fi
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
echo ""
|
|
57
|
+
echo "Done: $SUCCESS succeeded, $FAIL failed"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Fetch and display all PR feedback in one pass:
|
|
5
|
+
# 1. Inline review comments (with thread resolve status)
|
|
6
|
+
# 2. Issue comments (qodo summaries, sonarcloud, etc.)
|
|
7
|
+
# 3. Top-level reviews with a non-empty body (copilot overview, etc.)
|
|
8
|
+
#
|
|
9
|
+
# Usage: pr-comments.sh [--repo OWNER/REPO] PR_NUMBER
|
|
10
|
+
|
|
11
|
+
REPO=""
|
|
12
|
+
|
|
13
|
+
while [[ $# -gt 0 ]]; do
|
|
14
|
+
case "$1" in
|
|
15
|
+
--repo) REPO="$2"; shift 2 ;;
|
|
16
|
+
*) break ;;
|
|
17
|
+
esac
|
|
18
|
+
done
|
|
19
|
+
|
|
20
|
+
PR_NUMBER="${1:?Usage: pr-comments.sh [--repo OWNER/REPO] PR_NUMBER}"
|
|
21
|
+
|
|
22
|
+
if [[ -z "$REPO" ]]; then
|
|
23
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# ── Section 1: inline review comments ─────────────────────────────────────
|
|
27
|
+
THREADS_JSON=$(gh api graphql -f query="
|
|
28
|
+
{
|
|
29
|
+
repository(owner: \"${REPO%%/*}\", name: \"${REPO##*/}\") {
|
|
30
|
+
pullRequest(number: $PR_NUMBER) {
|
|
31
|
+
reviewThreads(first: 100) {
|
|
32
|
+
nodes {
|
|
33
|
+
id
|
|
34
|
+
isResolved
|
|
35
|
+
comments(first: 100) {
|
|
36
|
+
nodes { databaseId }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}" --jq '.data.repository.pullRequest.reviewThreads.nodes')
|
|
43
|
+
|
|
44
|
+
# Build a map from every comment ID in every thread → its thread metadata,
|
|
45
|
+
# so replies in a thread also show resolved status (not just the first comment).
|
|
46
|
+
THREAD_MAP=$(echo "$THREADS_JSON" | jq -r '
|
|
47
|
+
[.[] as $t | $t.comments.nodes[] | {
|
|
48
|
+
comment_id: .databaseId,
|
|
49
|
+
thread_id: $t.id,
|
|
50
|
+
resolved: $t.isResolved
|
|
51
|
+
}]
|
|
52
|
+
')
|
|
53
|
+
|
|
54
|
+
INLINE=$(gh api "repos/$REPO/pulls/$PR_NUMBER/comments" --paginate)
|
|
55
|
+
INLINE_COUNT=$(echo "$INLINE" | jq 'length')
|
|
56
|
+
|
|
57
|
+
echo "════════════════ INLINE REVIEW COMMENTS ($INLINE_COUNT) ════════════════"
|
|
58
|
+
echo "$INLINE" | jq -r --argjson threads "$THREAD_MAP" '
|
|
59
|
+
.[] | . as $c |
|
|
60
|
+
($threads | map(select(.comment_id == $c.id)) | first // {resolved: "unknown", thread_id: "?"}) as $t |
|
|
61
|
+
"──────────────────────────────────────────────────",
|
|
62
|
+
"ID: \($c.id) | Thread: \(if $t.resolved == true then "RESOLVED" elif $t.resolved == false then "UNRESOLVED" else "?" end) | Reply-to: \($c.in_reply_to_id // "none")",
|
|
63
|
+
"File: \($c.path):\($c.original_line // $c.line // "?")",
|
|
64
|
+
"Thread ID: \($t.thread_id)",
|
|
65
|
+
"Author: \($c.user.login)",
|
|
66
|
+
"",
|
|
67
|
+
($c.body | split("\n") | if length > 10 then .[:10] + ["... (truncated)"] else . end | join("\n")),
|
|
68
|
+
""
|
|
69
|
+
'
|
|
70
|
+
|
|
71
|
+
# ── Section 2: issue comments (general PR comments) ───────────────────────
|
|
72
|
+
ISSUE=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" --paginate)
|
|
73
|
+
ISSUE_COUNT=$(echo "$ISSUE" | jq 'length')
|
|
74
|
+
|
|
75
|
+
echo ""
|
|
76
|
+
echo "════════════════ ISSUE COMMENTS ($ISSUE_COUNT) ════════════════"
|
|
77
|
+
echo "$ISSUE" | jq -r '
|
|
78
|
+
.[] |
|
|
79
|
+
"──────────────────────────────────────────────────",
|
|
80
|
+
"ID: \(.id) | Author: \(.user.login) | Created: \(.created_at)",
|
|
81
|
+
"",
|
|
82
|
+
(.body | split("\n") | if length > 10 then .[:10] + ["... (truncated)"] else . end | join("\n")),
|
|
83
|
+
""
|
|
84
|
+
'
|
|
85
|
+
|
|
86
|
+
# ── Section 3: top-level reviews with a body ──────────────────────────────
|
|
87
|
+
REVIEWS=$(gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" --paginate)
|
|
88
|
+
REVIEWS_WITH_BODY=$(echo "$REVIEWS" | jq '[.[] | select((.body // "") != "")]')
|
|
89
|
+
REVIEW_COUNT=$(echo "$REVIEWS_WITH_BODY" | jq 'length')
|
|
90
|
+
|
|
91
|
+
echo ""
|
|
92
|
+
echo "════════════════ TOP-LEVEL REVIEWS ($REVIEW_COUNT) ════════════════"
|
|
93
|
+
echo "$REVIEWS_WITH_BODY" | jq -r '
|
|
94
|
+
.[] |
|
|
95
|
+
"──────────────────────────────────────────────────",
|
|
96
|
+
"Review ID: \(.id) | Author: \(.user.login) | State: \(.state) | Submitted: \(.submitted_at)",
|
|
97
|
+
"",
|
|
98
|
+
(.body | split("\n") | if length > 10 then .[:10] + ["... (truncated)"] else . end | join("\n")),
|
|
99
|
+
""
|
|
100
|
+
'
|