agentsclimarketplace

Git conventions

Skill a-novel-kit/stack/.agents/skills/git-conventions

Development tools backing a-novel and a-novel-kit. Home of a-novel CLI and AI skills.

Install
npx -y skills add a-novel-kit/stack --skill git-conventions

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Git workspace hygiene (clean-tree pre-flight, pruning a scratch stack), branch naming, commit message format, and workflow conventions for Agora backend services. Use it whenever starting or finishing work in a checkout, creating a branch, writing a commit message, or grouping changes. Referenced by implement-feature and any other skill that touches git. Pair with attribute-ai-commits whenever an AI agent materially contributed to a commit.

SKILL.md

16.9 KB, as published. Nobody here has run it

Git Conventions

This skill governs workspace state, branch naming, and commit messages across all Agora backend services. Every branch and commit produced by an agent follows these conventions exactly — they drive automation (Renovate, CI tagging, changelogs) and signal intent to reviewers at a glance.


Commit Messages — Conventional Commits

All commits use the Conventional Commits format:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Types

TypeUse when…
featAdding a new capability (endpoint, field, algorithm)
fixCorrecting a bug or incorrect behaviour
refactorRestructuring code without changing behaviour or API surface
perfPerformance improvement with no functional change
testAdding or fixing tests only
docsDocumentation only (comments, doc.go, openapi.yaml, SKILL.md)
choreMaintenance that doesn't fit above (deps, CI, build scripts)
ciCI/CD pipeline changes only
revertReverting a previous commit

Never mix types in one commit. A commit that adds a handler AND its test is still feat — the test ships as part of the same deliverable. A commit that only adds tests for existing code is test.

Scopes

The scope is the area of the codebase affected. Use the layer name, not the feature name:

ScopeCovers
protoProtobuf definitions (internal/models/proto/)
migrationsDatabase schema (internal/models/migrations/)
daoData access layer (internal/dao/)
coreBusiness logic (internal/core/)
handlersgRPC and REST handlers (internal/handlers/)
configConfiguration (internal/config/)
libShared utilities (internal/lib/)
pkgExported Go client (pkg/go/)
pkg-jsExported JS/TS client (pkg/js/)
cmdTargets (cmd/)
buildsDockerfiles and compose files (builds/)
scriptsShell scripts (scripts/)
ciGitHub Actions workflows (.github/)
depsDependency bumps (go.mod, package.json)
skillsSkill documents (.agents/skills/)

When a commit touches several scopes of the same weight, pick the primary one. When the commit is genuinely cross-cutting (e.g., a rename that touches every layer), omit the scope.

Description

  • Imperative mood, present tense: "add key rotation endpoint" not "adds" or "added"
  • Under 72 characters
  • No period at the end
  • Describes the what, not the how — readers see the diff; they need the intent

The subject line carries the message. It is what git blame, git log --oneline, and the release notes show, and as far as most readers get. Spend the effort there.

Body

Default to no body. Most commits are a subject line and nothing else.

Repos squash-merge with COMMIT_MESSAGES, so every body on the branch is concatenated into the commit that lands on master. A five-commit branch with three-paragraph bodies becomes a wall of prose attached to a single line of history that nobody scrolls past.

A body earns its place only when it carries something neither the subject nor the diff can show:

  • A constraint that forced a non-obvious approach — the thing a future reader would otherwise "clean up" and break.
  • The failure a fix repairs, when the symptom is invisible in the diff (a race, a CI-only break, a bug in a dependency).

Then keep it to one or two sentences — three lines wrapped at 72 characters is the ceiling. Never restate the subject at greater length, summarise the diff, or list touched files.

Footers (BREAKING CHANGE:, Closes, Co-authored-by:) are not prose and are never trimmed.

Longer reasoning has better homes, all of which readers actually reach:

ReasoningGoes in
What the change does and why, for reviewersThe PR description
A design decision or rejected optionThe planning issue (body or discussion)
Something a reader of the code needsA code comment (see document-code)

AI co-author attribution

Load attribute-ai-commits whenever an AI agent materially contributed content included in the commit. Add its standard Co-authored-by: footer only when that skill's pushed registry marks the agent identity as verified.

Never invent a provider email, use another provider's bot, or add an unlinked display-only footer. The attribution skill owns registry lookup, missing-agent verification and registration, model-aware display names, and unavailable identities.

Breaking Changes

Prefix the description with ! and add a BREAKING CHANGE: footer:

feat(proto)!: remove deprecated KeyUsage enum value

BREAKING CHANGE: KeyUsage.LEGACY is removed. Callers using this value
must migrate to KeyUsage.AUTH before upgrading.

Flag any change that:

  • Removes or renames a protobuf field/message/service
  • Removes or renames an exported Go type, function, or constant in pkg/go
  • Removes or renames an exported TypeScript type or function in pkg/js
  • Removes or changes the semantics of a REST endpoint path or response shape
  • Changes a database column type or removes a column

Workspace Hygiene

Before you start

Start every task from freshly-pulled master, with a clean tree, in a checkout that is yours. Check this before the first edit — in the stack root and in each app/ or kit/ checkout the task will touch, since those are independent repos with independent states:

git -C <checkout> status --porcelain            # empty
git -C <checkout> checkout master               # be on master before pulling
git -C <checkout> pull --ff-only                # fast-forward to origin/master, no merge commit
git -C <checkout> rev-parse --abbrev-ref HEAD   # master

Always pull master before cutting the branch — never branch from a stale local master. A branch cut from a master that is days behind starts life already diverged: it re-runs work that landed since, collides in review with changes it never saw, and forces a rebase later that a pull --ff-only now would have avoided. --ff-only refuses to invent a merge commit — if local master has drifted (someone committed to it directly, which should not happen), it stops so you can look, rather than silently tangling histories. A branch whose parent is already merged (as a completed task's branch is, once its PR lands) is finished work; leave it and branch from master.

Uncommitted changes, or a checkout sitting on a branch other than master that you did not create, mean someone else is working in this checkout — the operator in another terminal, or a parallel agent session. The mere existence of stale unmerged branches is not that signal: a repo that has shipped hundreds of PRs carries dozens of finished branches nobody deleted, and none of them blocks cutting a fresh one from master — which is why the pre-flight keys on the current branch and the tree, not on branch --no-merged. When someone is working here, their work-in-progress is invisible to you, and stash, reset, checkout -f, or branching on top of it can destroy hours of work that exists nowhere else. Leave it untouched and take a checkout of your own.

A clean pre-flight expires immediately. It proves the checkout was free at that instant, and a checkout has one HEAD that nothing holds: a parallel session running git checkout between your checkout -b and your commit lands your commit on whatever branch it moved you to, and its own reset can then unlink it. For anything longer than a couple of commands, work in a worktree of your own — git refuses to check out a branch already checked out elsewhere, so the branch cannot be taken from you mid-task, and the shared object store keeps every commit you have made.

git worktree add <path-outside-the-repo> <branch>

After a collision nothing is lost: the commit is unreferenced, not deleted. Find it in git reflog, point your branch at it with git branch -f <branch> <sha>, and put local master back with git branch -f master origin/master. Clear your own stray files out of the shared tree so the other session does not commit them, and leave everything of theirs alone.

The daemon manages as many stacks as the machine supports. A_NOVEL_STACKS is its source of truth, formatted name:/path,name:/path with the first entry as the default; unset means a single default stack at ~/git-projects/a-novel.

a-novel core stacks list             # which stacks exist, and what they hold
a-novel core stacks new <name>       # clone the workspace into a fresh root
a-novel core sync --root=<new-root>  # populate it with the whitelisted repos

stacks new puts the checkout under the OS temp directory unless --root says otherwise, so a stack nobody prunes expires instead of accumulating. Add the printed entry to A_NOVEL_STACKS and a-novel core restart so daemon-backed verbs (a-novel run …) reach it, then work from there — see use-a-novel-cli.

Resuming a branch you created earlier in the same session is your own work; carry on with it.

When you are done

A stack synced for one task is scratch space. Left behind it becomes a stale checkout the next session mistakes for real work, plus containers and volumes that outlive the machine's reboot.

Prune it when development ends: every change reviewed and approved. Not at push, and not at green CI — review turns up work, and rebuilding a stack to answer one comment costs more than keeping it a few hours longer. Approval is the first moment the checkout is no longer needed.

a-novel core stacks list           # what each stack is still holding
a-novel core stacks prune <name>   # kill its targets + infra, clear its volumes, remove its files

Prune covers all three of a stack's allocations. Deleting the root by hand covers one: the containers keep running on their host ports and the volumes stay in the container store, because neither ever lived in the stack directory.

prune refuses the default stack, and refuses any stack still holding work that exists nowhere else — so run it without rehearsing the state first. It reports the A_NOVEL_STACKS entry to drop rather than editing your shell config.

Never reach for a-novel core kill --force as cleanup. It tears down every service's infra across every registered stack, the operator's included — the exact harm the pre-flight above exists to prevent.


Branch Naming

<type>/<area>/<short-description>
  • type: same vocabulary as commit types (feat, fix, refactor, chore, ci, docs)
  • area: the layer or subsystem being changed — use the scope name from the table above
  • short-description: kebab-case, 2–5 words, describes what the branch achieves

Examples

feat/proto/add-key-revoke-rpc
feat/handlers/grpc-jwk-revoke
fix/dao/search-returns-deleted-keys
refactor/core/extract-key-rotation-logic
chore/skills/feature-workflow
docs/pkg/update-client-examples

Branch names are lowercase kebab-case only. No underscores, no slashes inside a segment, no version numbers unless it's a release branch.


Commit Workflow

# 1. Stage only the files for this logical unit
git add internal/dao/pg.jwkRevoke.go internal/dao/pg.jwkRevoke_test.go

# 2. Commit with a conventional message — a subject line, nothing more
git commit -m "feat(dao): add soft-delete repository for key revocation"

# Only when the body carries what the diff cannot (HEREDOC for multi-line):
git commit -m "$(cat <<'EOF'
fix(dao): lock the key row before revoking

Concurrent revokes both read the key as ACTIVE and wrote two audit
entries; SELECT ... FOR UPDATE is what serialises them.
EOF
)"

Rules

  • A commit is its subject line. Add a body only when it says something the subject and the diff cannot — see Body.
  • One logical unit per commit. A DAO file + its test = one commit; a migration file = one commit. Never combine DAO + service in a single commit.
  • Stage explicit paths — never git add -A / git add .. These checkouts keep sibling worktrees under an untracked tmp/ (see Before you start). A blanket add stages each tmp/wt-* as an embedded-repo gitlink — a bogus submodule ref that rides your commit onto master if it slips through review. git show --stat HEAD betrays it as a tmp/wt-… | 1 + line. Name the files the logical unit touched; the git add … in this workflow is a list on purpose.
  • Generated files belong in the same commit as the change that required them. Proto Go bindings (internal/models/proto/gen/) and mocks (internal/handlers/mocks/, internal/core/mocks/) never get their own commit — stage them with the .proto or interface change that required pnpm generate:go.
  • Never commit secrets. .env files, APP_MASTER_KEY values, real credentials.
  • Never skip hooks (--no-verify) unless explicitly asked.
  • Never amend a pushed commit. Create a new commit instead.
  • Never push to master/main — not force-push, not a plain push — without explicit consent. This is the one git action that is never safe by default. Most contributors lack the access, so the guardrail is already enforced for them; on an admin account it is yours to hold, because you have the rights to bypass it and nothing else will stop you. Feature branches carry no such risk: they cannot damage shared history, so pushing a branch and opening its PR never needs permission — see Branch and PR freedom.

Branch and PR Freedom

Pushing a feature branch and opening a pull request are always safe — never ask permission for either. A branch touches no shared history; a pull request only proposes. The sole thing that can harm the repo is a push to master/main, which is prohibited without explicit consent (above). Everything short of that is free, and the freedom is the point: work that lives only in your local checkout is one crashed session away from gone.

Once a branch has a commit, open a pull request for it — a draft one if it is not review-ready. A PR is how work becomes tracked: it survives session loss, shows the operator what you did, and gives CI something to run. Do not sit on committed-but-unpushed work waiting for a "ship it".

A draft PR has no rules. It requests no review, blocks nothing, and triggers no merge automation. You may --force-with-lease over it freely, redirect its base, or delete it outright if the direction changes — none of that costs anyone anything. So default to opening one early: the downside is zero and the upside is that nothing you did is ever stranded. Push and PR mechanics live in open-pull-request; this rule is only when (always, once committed) and whether to ask (never).


Pull Request Description

Open a PR (via gh pr create) from this template:

gh pr create --title "<type>(<scope>): <description>" --body "$(cat <<'EOF'
## Summary

<1-3 bullet points describing what changed and why.>

## Layers changed

- **DAO**: <what changed>
- **Services**: <what changed>
- **Handlers**: <what changed>
- **Proto / OpenAPI**: <what changed>
- **pkg/go**: <what changed>
- **pkg/js**: <what changed>

## Breaking changes

None. / <List any breaking changes with migration steps.>

## Test plan

- [ ] `a-novel test --type=go -y` passes
- [ ] `a-novel test --type=pnpm -y` passes (if pkg/js changed)
- [ ] <Any manual verification steps>
EOF
)"
  • Keep the title under 70 characters and in Conventional Commits format.
  • Be explicit about breaking changes — reviewers should not have to hunt for them.
  • Skip the layers that were not affected rather than writing "no change" for each.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.