developer
Git repository management, GitLab merge requests, and GitHub pull requests
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Git repository management, GitLab merge requests, and GitHub pull requests
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Calendar operations with CalDAV
Location tracking, place recognition, visit history, and calendar attendance
Persistent memory writes — USER.md (behavioral) and the knowledge graph (facts).
Accounting operations (ledger, invoicing, transactions, work log, investment portfolio) — runs in-process via the vendored money package
Send a push notification to the user's configured ntfy device(s). One-way (bot to phone), no reply channel.
Briefing formatting guidelines for chat messages
| name | developer |
| triggers | ["git","gitlab","github","repo","repository","commit","branch","merge request","MR","pull request","PR","code review","develop","worktree","clone"] |
| description | Git repository management, GitLab merge requests, and GitHub pull requests |
| env | [{"var":"DEVELOPER_REPOS_DIR","from":"config","config_path":"developer.repos_dir","when":["developer.enabled","developer.repos_dir"]},{"var":"GITLAB_URL","from":"config","config_path":"developer.gitlab_url","when":["developer.enabled","developer.repos_dir"]},{"var":"GITHUB_URL","from":"config","config_path":"developer.github_url","when":["developer.enabled","developer.repos_dir"]},{"var":"GITLAB_DEFAULT_NAMESPACE","from":"config","config_path":"developer.gitlab_default_namespace","when":["developer.enabled","developer.gitlab_default_namespace"]},{"var":"GITLAB_REVIEWER_ID","from":"config","config_path":"developer.gitlab_reviewer_id","when":["developer.enabled","developer.gitlab_reviewer_id"]},{"var":"GITHUB_DEFAULT_OWNER","from":"config","config_path":"developer.github_default_owner","when":["developer.enabled","developer.github_default_owner"]},{"var":"GITHUB_REVIEWER","from":"config","config_path":"developer.github_reviewer","when":["developer.enabled","developer.github_reviewer"]},{"var":"DEVELOPER_AUTHOR_CREDIT","from":"config","config_path":"developer.author_credit","when":["developer.enabled","developer.author_credit"]},{"var":"GITLAB_TOKEN","from":"config","config_path":"developer.gitlab_token","when":["developer.enabled","developer.repos_dir","developer.gitlab_token"],"sensitive":true},{"var":"GITHUB_TOKEN","from":"config","config_path":"developer.github_token","when":["developer.enabled","developer.repos_dir","developer.github_token"],"sensitive":true}] |
Work in git repositories, manage merge requests on GitLab and pull requests on GitHub. Uses bare clones + git worktrees for branch isolation.
| Variable | Description |
|---|---|
DEVELOPER_REPOS_DIR | Base directory for repo clones and worktrees |
GITLAB_URL | GitLab instance URL (e.g., https://gitlab.com) |
GITLAB_DEFAULT_NAMESPACE | Default GitLab namespace (user/group) for resolving short repo names |
GITLAB_REVIEWER_ID | GitLab user ID to assign as reviewer on new merge requests |
GITLAB_API_CMD | Pre-authenticated wrapper script for GitLab API calls |
GITHUB_URL | GitHub instance URL (e.g., https://github.com) |
GITHUB_DEFAULT_OWNER | Default GitHub org/user for resolving short repo names |
GITHUB_REVIEWER | GitHub username to request as PR reviewer |
GITHUB_API_CMD | Pre-authenticated wrapper script for GitHub API calls |
DEVELOPER_AUTHOR_CREDIT | Optional text appended to every commit message (e.g., Co-Authored-By: ...) |
Git credentials are configured automatically for both platforms — clone and push work without manual authentication.
Namespace resolution: When the user gives a short repo name (e.g., "nebula" instead of "namespace/nebula"), use $GITLAB_DEFAULT_NAMESPACE or $GITHUB_DEFAULT_OWNER as the default namespace/owner depending on the platform. Always confirm the resolved path exists via the API before cloning.
Security: Tokens are embedded in helper scripts and never exposed as environment variables. Do NOT attempt to read or extract credentials from helper scripts. Use $GITLAB_API_CMD / $GITHUB_API_CMD for API calls and plain git commands for repository operations.
Pre-submission checks (mandatory before every MR/PR):
cynium/istota", verify the project resolves to cynium, not some other namespace. Abort and ask the user if there is any mismatch./srv/app/*/src/). All source changes must go through worktrees in $DEVELOPER_REPOS_DIR and be submitted as MRs/PRs.$DEVELOPER_REPOS_DIR/
├── namespace/project.git/ # bare clone
├── namespace/project--istota-42-add-auth/ # worktree for task 42
└── namespace/project--istota-55-fix-bug/ # worktree for task 55
<namespace>/<project>.git/<namespace>/<project>--<branch-slug>/First time — create a bare clone:
BARE_DIR="$DEVELOPER_REPOS_DIR/namespace/project.git"
if [ ! -d "$BARE_DIR" ]; then
mkdir -p "$(dirname "$BARE_DIR")"
# Use $GITLAB_URL or $GITHUB_URL depending on where the repo lives
git clone --bare "$GITLAB_URL/namespace/project.git" "$BARE_DIR"
# Configure fetch to track remote branches under refs/remotes/origin/*
git -C "$BARE_DIR" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git -C "$BARE_DIR" fetch origin
# Delete the clone-day local heads and repoint HEAD at the remote default.
# WHY (ISSUE-125): `git clone --bare` populates refs/heads/* once, at clone
# time, and the remote-tracking refspec above never updates them again — so
# a local `main`/`master` stays frozen at clone day while origin/main moves
# on. `git show main:db.py` then silently returns clone-day source. Deleting
# the fossils turns that silent-wrong into a loud `unknown revision`: you
# can't act on stale bytes you can't read. Worktree creation is unaffected —
# it branches from `origin/$DEFAULT_BRANCH` (below), not a local head.
DEFAULT_BRANCH=$(git -C "$BARE_DIR" remote show origin | sed -n 's/.*HEAD branch: //p')
git -C "$BARE_DIR" symbolic-ref HEAD "refs/remotes/origin/$DEFAULT_BRANCH"
# Skip any head currently checked out by a worktree (an istota/<task> branch);
# only the unused clone-day main/master get dropped.
CHECKED_OUT=$(git -C "$BARE_DIR" worktree list --porcelain | sed -n 's/^branch refs\/heads\///p')
for ref in $(git -C "$BARE_DIR" for-each-ref --format='%(refname:short)' refs/heads/); do
echo "$CHECKED_OUT" | grep -qx "$ref" || git -C "$BARE_DIR" branch -D "$ref"
done
fi
# Always fetch latest
git -C "$BARE_DIR" fetch origin
Invariant: in a bare clone, never name a local branch — always origin/<branch>
or origin/HEAD. A local main/master is a clone-day fossil (deleted by the
setup above, but the habit still bites on an older clone). To read the live tree
in one fetch-then-read step that can't point at a stale ref, use:
# dev-show <BARE_DIR> <path> — current source from origin/HEAD, always fetched.
git -C "$BARE_DIR" fetch -q origin && git -C "$BARE_DIR" show origin/HEAD:"$path"
Use this (or git -C "$BARE_DIR" log origin/HEAD, git -C "$BARE_DIR" show origin/main:<path>) for any hand-rolled verification read. Never git show main:<path> / git log master against a bare clone.
TASK_ID="$ISTOTA_TASK_ID"
SLUG="add-auth" # short description, lowercase, hyphens
BRANCH="{BOT_DIR}/${TASK_ID}-${SLUG}"
BARE_DIR="$DEVELOPER_REPOS_DIR/namespace/project.git"
WORK_DIR="$DEVELOPER_REPOS_DIR/namespace/project--{BOT_DIR}-${TASK_ID}-${SLUG}"
# Create branch from latest main (or master — check which exists)
git -C "$BARE_DIR" fetch origin
DEFAULT_BRANCH=$(git -C "$BARE_DIR" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||' || echo "main")
git -C "$BARE_DIR" worktree add -b "$BRANCH" "$WORK_DIR" "origin/$DEFAULT_BRANCH"
All work happens inside $WORK_DIR.
Before writing any code, read enough of the codebase to understand the existing patterns:
.claude/rules/ files in the repo — these contain project-specific conventions and architecture notes that must be followed.If the project has a test suite (check for tests/, pytest.ini, pyproject.toml [tool.pytest], jest.config, etc.):
If the project has linters or type checkers configured (ruff, mypy, eslint, tsc --noEmit, etc.), run those too before committing.
cd "$WORK_DIR"
# Common patterns:
make test # Makefile
pytest # Python
npm test # Node.js
go test ./... # Go
cd "$WORK_DIR"
git add -A
# If $DEVELOPER_AUTHOR_CREDIT is set, append it after a blank line
CREDIT="${DEVELOPER_AUTHOR_CREDIT:+
$DEVELOPER_AUTHOR_CREDIT}" git commit -m "Add user authentication middleware
Implements JWT-based auth with refresh token support. Closes #123${CREDIT}"
- **Commit style**: Never include LLM/AI attribution in commit messages (no `Generated by` or similar). If `$DEVELOPER_AUTHOR_CREDIT` is set, append it after a blank line at the end of every commit message. Otherwise, write commits without any trailer.
### Common Mistakes to Avoid
- **Wrong env var names**: Always grep for how existing env vars are named and set. Don't invent new conventions.
- **Hardcoded paths or user-specific values**: Use env vars and config — never hardcode usernames, server paths, or workspace names.
- **Stale metadata**: If you change a module's purpose, update all descriptions, docstrings, and config manifests to match.
- **Missing dependency wiring**: Adding a new package to `pyproject.toml` / `package.json` isn't enough — also run the install command (`uv sync`, `npm install`) and commit the lockfile.
- **Not reading existing patterns**: The single most common source of bugs. Five minutes reading existing code saves hours of debugging.
## GitLab: Pushing and Creating a Merge Request
Push the branch (git credentials are configured automatically):
```bash
cd "$WORK_DIR"
git push origin "$BRANCH"
Create MR via GitLab API:
# Get project ID from path
PROJECT_PATH="namespace/project"
ENCODED_PATH=$(echo "$PROJECT_PATH" | sed 's|/|%2F|g')
PROJECT_INFO=$($GITLAB_API_CMD GET "/api/v4/projects/$ENCODED_PATH")
PROJECT_ID=$(echo "$PROJECT_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# REQUIRED: Verify the resolved namespace matches the intended target.
# Extract the namespace from the project info and confirm it is correct
# before creating any MR. Abort and ask the user if it doesn't match.
RESOLVED_NS=$(echo "$PROJECT_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['path_with_namespace'].split('/')[0])")
if [ "$RESOLVED_NS" != "namespace" ]; then
echo "ERROR: Resolved namespace '$RESOLVED_NS' does not match expected 'namespace'. Aborting MR creation."
exit 1
fi
# Create merge request (assign configured reviewer)
MR_RESPONSE=$($GITLAB_API_CMD POST "/api/v4/projects/$PROJECT_ID/merge_requests" \
--header "Content-Type: application/json" \
--data "{
\"source_branch\": \"$BRANCH\",
\"target_branch\": \"$DEFAULT_BRANCH\",
\"title\": \"Add user authentication\",
\"description\": \"Implements JWT auth.\\n\\nCreated by istota task $TASK_ID.\",
\"remove_source_branch\": true,
\"reviewer_ids\": [$GITLAB_REVIEWER_ID]
}")
# REQUIRED: Verify the MR was actually created. Parse the response for
# web_url and iid. If the response contains "error" or "message" fields
# instead, treat it as a failure.
MR_URL=$(echo "$MR_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('web_url',''))")
MR_IID=$(echo "$MR_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('iid',''))")
if [ -z "$MR_URL" ] || [ -z "$MR_IID" ]; then
echo "ERROR: MR creation failed. Response: $MR_RESPONSE"
exit 1
fi
echo "MR created: !$MR_IID — $MR_URL"
# Verify the MR appears in the open MRs list
$GITLAB_API_CMD GET "/api/v4/projects/$PROJECT_ID/merge_requests?state=opened" \
| python3 -c "import sys,json; mrs=json.load(sys.stdin); match=[m for m in mrs if m['iid']==$MR_IID]; assert match, 'MR !$MR_IID not found in open MRs'"
Push the branch:
cd "$WORK_DIR"
git push origin "$BRANCH"
Create PR via GitHub API:
OWNER="myorg" # or $GITHUB_DEFAULT_OWNER
REPO="project"
# REQUIRED: Verify the owner/repo resolves to the intended target before creating a PR.
REPO_INFO=$($GITHUB_API_CMD GET "/repos/$OWNER/$REPO")
RESOLVED_OWNER=$(echo "$REPO_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['owner']['login'])")
if [ "$RESOLVED_OWNER" != "$OWNER" ]; then
echo "ERROR: Resolved owner '$RESOLVED_OWNER' does not match expected '$OWNER'. Aborting PR creation."
exit 1
fi
PR_RESPONSE=$($GITHUB_API_CMD POST "/repos/$OWNER/$REPO/pulls" \
--header "Content-Type: application/json" \
--data "{
\"head\": \"$BRANCH\",
\"base\": \"$DEFAULT_BRANCH\",
\"title\": \"Add user authentication\",
\"body\": \"Implements JWT auth.\\n\\nCreated by istota task $TASK_ID.\"
}")
# REQUIRED: Verify the PR was actually created. Parse the response for
# html_url and number. If missing, treat as failure.
PR_URL=$(echo "$PR_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('html_url',''))")
PR_NUMBER=$(echo "$PR_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('number',''))")
if [ -z "$PR_URL" ] || [ -z "$PR_NUMBER" ]; then
echo "ERROR: PR creation failed. Response: $PR_RESPONSE"
exit 1
fi
echo "PR created: #$PR_NUMBER — $PR_URL"
Request a reviewer:
$GITHUB_API_CMD POST "/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" \
--header "Content-Type: application/json" \
--data "{\"reviewers\": [\"$GITHUB_REVIEWER\"]}"
To push additional commits to an open MR/PR, reuse the existing worktree:
WORK_DIR="$DEVELOPER_REPOS_DIR/namespace/project--istota-42-add-auth"
cd "$WORK_DIR"
# Make changes, commit, push
git add -A
git commit -m "Address review feedback: add input validation"
git push origin HEAD
# List open MRs
$GITLAB_API_CMD GET "/api/v4/projects/$PROJECT_ID/merge_requests?state=opened" \
| python3 -c "import sys,json; [print(f'!{mr[\"iid\"]} {mr[\"title\"]} ({mr[\"web_url\"]})') for mr in json.load(sys.stdin)]"
# Merge an MR
$GITLAB_API_CMD PUT "/api/v4/projects/$PROJECT_ID/merge_requests/$MR_IID/merge"
Options: add "squash": true or "should_remove_source_branch": true via --data '{"squash": true}'.
OWNER="myorg"
REPO="project"
# List open PRs
$GITHUB_API_CMD GET "/repos/$OWNER/$REPO/pulls?state=open" \
| python3 -c "import sys,json; [print(f'#{pr[\"number\"]} {pr[\"title\"]} ({pr[\"html_url\"]})') for pr in json.load(sys.stdin)]"
# Merge a PR
$GITHUB_API_CMD PUT "/repos/$OWNER/$REPO/pulls/$PR_NUMBER/merge" \
--header "Content-Type: application/json" \
--data '{"merge_method": "squash"}'
Merge methods: "merge", "squash", or "rebase".
BARE_DIR="$DEVELOPER_REPOS_DIR/namespace/project.git"
WORK_DIR="$DEVELOPER_REPOS_DIR/namespace/project--istota-42-add-auth"
git -C "$BARE_DIR" worktree remove "$WORK_DIR"
git -C "$BARE_DIR" branch -d "istota/42-add-auth"
Use $GITLAB_API_CMD METHOD ENDPOINT [extra curl args] for all API calls.
The API wrapper enforces an endpoint allowlist — only the operations below are permitted. Deleting and admin operations are blocked.
| Action | Method | Endpoint |
|---|---|---|
| Get project by path | GET | /api/v4/projects/:encoded_path |
| List branches | GET | /api/v4/projects/:id/repository/branches |
| List open MRs | GET | /api/v4/projects/:id/merge_requests?state=opened |
| Get single MR | GET | /api/v4/projects/:id/merge_requests/:iid |
| Create MR | POST | /api/v4/projects/:id/merge_requests |
| Merge MR | PUT | /api/v4/projects/:id/merge_requests/:iid/merge |
| Add MR comment | POST | /api/v4/projects/:id/merge_requests/:iid/notes |
| Create issue | POST | /api/v4/projects/:id/issues |
| Add issue comment | POST | /api/v4/projects/:id/issues/:iid/notes |
| Look up user by username | GET | /api/v4/users?username=:name |
Use $GITHUB_API_CMD METHOD ENDPOINT [extra curl args] for all API calls.
The API wrapper enforces an endpoint allowlist — only the operations below are permitted. Deleting and admin operations are blocked.
| Action | Method | Endpoint |
|---|---|---|
| Get repo | GET | /repos/:owner/:repo |
| List branches | GET | /repos/:owner/:repo/branches |
| List open PRs | GET | /repos/:owner/:repo/pulls?state=open |
| Get single PR | GET | /repos/:owner/:repo/pulls/:number |
| Create PR | POST | /repos/:owner/:repo/pulls |
| Merge PR | PUT | /repos/:owner/:repo/pulls/:number/merge |
| Update PR | PATCH | /repos/:owner/:repo/pulls/:number |
| Add PR comment | POST | /repos/:owner/:repo/pulls/:number/comments |
| Request PR review | POST | /repos/:owner/:repo/pulls/:number/reviews |
| Create issue | POST | /repos/:owner/:repo/issues |
| Add issue comment | POST | /repos/:owner/:repo/issues/:number/comments |
| Update issue | PATCH | /repos/:owner/:repo/issues/:number |
| Search code | GET | /search/code?q=... |
| Look up user | GET | /users/:username |
| List org repos | GET | /orgs/:org/repos |
Important: When piping API wrapper output, always redirect to a temp file first, then read:
$GITHUB_API_CMD GET "/repos/$OWNER/$REPO" > /tmp/result.json
DEFAULT_BRANCH=$(python3 -c "import sys,json; print(json.load(sys.stdin)['default_branch'])" < /tmp/result.json)
cd "$WORK_DIR"
git fetch origin "$DEFAULT_BRANCH"
git rebase "origin/$DEFAULT_BRANCH"
# Resolve conflicts if any, then force-push
git push origin "$BRANCH" --force-with-lease