| name | git-workflow-and-versioning |
| description | Structures git workflow practices, adapted for the Mezon Mentor Bot ("MeKnow") platform โ the platform/ git submodule of funix-mezon-bot. Use when making any code change. Use when committing, branching, resolving conflicts, writing Conventional Commit messages, opening PRs via gh, bumping the superproject gitlink, maintaining CHANGELOG.md, or organizing work across parallel streams. |
| tags | ["infra"] |
Git Workflow and Versioning
Overview
Git is your safety net. Treat commits as save points, branches as sandboxes, and history as documentation. With AI agents generating code at high speed, disciplined version control is the mechanism that keeps changes manageable, reviewable, and reversible.
This repo is a git submodule. mezon-bot-ai is consumed as the platform/ submodule of the funix-mezon-bot superproject (customer pack). All branches, PRs, and merges in this skill happen inside the submodule (default branch main). Updating the superproject to point at a new submodule commit โ the gitlink bump โ is a separate, manual step that is never automated (see "The Submodule Gitlink Bump").
When to Use
Always. Every code change flows through git.
Core Principles
Trunk-Based Development (Recommended)
Keep main always deployable. Work in short-lived feature branches that merge back within 1-3 days. Long-lived development branches are hidden costs โ they diverge, create merge conflicts, and delay integration. DORA research consistently shows trunk-based development correlates with high-performing engineering teams.
main โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ (always deployable โ the submodule's default branch)
โฒ โฑ โฒ โฑ
โโโโโโฑ โโโโฑ โ short-lived feature branches (1-3 days)
This maps onto the OpenSpec change lifecycle, which is spec-first and two-PR: one change โ
a spec/<change> branch + spec PR (proposal + delta specs + synced canonical specs; opened by
/solo:spec-pr, merged by a human before any code) โ then a feat/<change> branch + code PR
(implementation; opened by /solo:implement, merged by a human). Both PRs live inside the platform/
submodule. Never commit on main โ branch first; the agent never merges to main (a human
merges both PRs). The superproject gitlink bump (cd <superproject> && git add platform && git commit)
is a separate manual step.
- Dev branches are costs. Every day a branch lives, it accumulates merge risk.
- Release branches are acceptable. When you need to stabilize a release while main moves forward.
- Feature flags > long branches. Prefer deploying incomplete work behind flags rather than keeping it on a branch for weeks.
1. Commit Early, Commit Often
Each successful increment gets its own commit. Don't accumulate large uncommitted changes.
Work pattern:
Implement slice โ run toolchain gates โ Commit โ Next slice
Not this:
Implement everything โ Hope it works โ Giant commit
Commits are save points. If the next change breaks something, you can revert to the last known-good state instantly. When implementing an OpenSpec change with /solo:implement, tick each task in tasks.md and commit per work-unit (RedโGreenโone commit).
2. Atomic Commits
Each commit does one logical thing:
# Good: Each commit is self-contained
git log --oneline
a1b2c3d feat(rag-core): add ACL filter to retrieve_kb query
d4e5f6g feat(bot-policy): add refuse_if no_citations to filter stage
h7i8j9k test(rag-core): cover cross-tenant isolation in retrieve_kb
m1n2o3p docs: update retrieve-kb-tool spec for the ACL change
# Bad: Everything mixed together
x1y2z3a add retrieval feature, fix ingestion, bump deps, refactor transport
3. Descriptive Messages โ Conventional Commits
This repo uses Conventional Commits. Messages explain the why, not just the what, and every commit ends with the Co-Authored-By trailer:
# Good: Explains intent, Conventional Commit type, trailer
feat(rag-core): enforce server-side ACL inside retrieve_kb
Filter KB chunks by the inherited caller identity inside retrieve_kb so
the model can never widen its own access. Keeps the tenant_id-everywhere
and server-side-ACL invariants intact instead of post-filtering results.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Bad: Describes what's obvious from the diff, no trailer
update retrieve_kb.py
Format:
<type>(<scope>): <short description>
<optional body explaining why, not what>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Types:
feat โ New feature
fix โ Bug fix
refactor โ Code change that neither fixes a bug nor adds a feature
test โ Adding or updating tests
docs โ Documentation only
chore โ Tooling, dependencies, config
Common scopes mirror the repo layout (packages/apps): rag-core, agent-core,
ingest-core, llm-transport, bot-policy, backend, portal, worker-mello,
worker-mezon-go, kb-mcp.
4. Keep Concerns Separate
Don't combine formatting changes with behavior changes. Don't combine refactors with features. Each type of change should be a separate commit โ and ideally a separate PR / OpenSpec change:
# Good: Separate concerns
git commit -m "refactor(llm-transport): extract MiniMax client helper"
git commit -m "feat(rag-core): pin temperature 0 on synthesize/cite/filter"
# Bad: Mixed concerns
git commit -m "refactor transport and add temperature pinning"
Separate refactoring from feature work. A refactoring change and a feature change are two different changes โ submit them separately. This makes each change easier to review, revert, and understand in history. Small cleanups (renaming a variable) can be included in a feature commit at reviewer discretion.
5. Size Your Changes
Target ~100 lines per commit/PR. Changes over ~1000 lines should be split.
~100 lines โ Easy to review, easy to revert
~300 lines โ Acceptable for a single logical change
~1000 lines โ Split into smaller changes
A well-scoped OpenSpec change usually lands in this range. If a proposal's tasks.md implies a >1000-line diff, consider splitting it into multiple changes during /solo:propose.
Branching Strategy
Feature Branches
main (always deployable โ the submodule default branch)
โ
โโโ feat/retrieve-kb-acl โ One OpenSpec change per branch
โโโ feat/agent-teams โ Parallel work
โโโ fix/citation-grounding โ Bug fixes
- Branch from
main (the submodule's default branch), inside the platform/ submodule
- Keep branches short-lived (merge within 1-3 days) โ long-lived branches are hidden costs
- Delete branches after merge
- Prefer feature flags over long-lived branches for incomplete features
Branch Naming
feat/<change-name> โ feat/retrieve-kb-acl (matches the OpenSpec change slug)
fix/<short-description> โ fix/citation-grounding
chore/<short-description> โ chore/bump-pyright
refactor/<short-description> โ refactor/llm-transport
docs/<short-description> โ docs/openspec-workflow
The Submodule Gitlink Bump
This is the load-bearing addition for this repo. All the work above happens inside the platform/ submodule. Pointing the funix-mezon-bot superproject at a new platform commit is a separate, manual step โ never auto-pushed by /solo:implement or any automation.
Inside the submodule (this repo):
feat/<change> โ PR โ review โ merge to main โ /solo:archive
Then, manually, in the SUPERPROJECT (funix-mezon-bot):
cd <funix-mezon-bot>
git -C platform pull # or check out the merged main commit
git add platform # stage the new gitlink (submodule SHA)
git commit -m "chore: bump platform to <short-sha>"
# push the superproject ONLY when a human decides to โ never automated
Rules:
- Never commit a gitlink bump as part of submodule work โ it lives in the superproject.
- Never auto-push the superproject. The gitlink bump is a deliberate, human-gated step.
- A bad gitlink bump is rolled back in the superproject (commit the previous
platform
SHA); the submodule history is untouched.
Working with Worktrees
For parallel AI agent work, use git worktrees to run multiple branches simultaneously:
git worktree add ../meknow-retrieve-kb-acl feat/retrieve-kb-acl
git worktree add ../meknow-agent-teams feat/agent-teams
git worktree remove ../meknow-retrieve-kb-acl
Caveat for DB-backed work: DB-dependent Python tests need pgvector +
TEST_DATABASE_URL. Two worktrees running migrations/tests against the same
database will collide โ point each at a separate test DB (and alembic upgrade head each) or serialize the runs. Note worktrees of a submodule have their own
caveats; prefer separate clones if a worktree fights the submodule layout.
Benefits:
- Multiple agents can work on different changes simultaneously
- No branch switching needed (each directory has its own branch)
- If one experiment fails, delete the worktree โ nothing is lost
- Changes are isolated until explicitly merged
Automated Worktree Flow (/solo:implement --worktree)
The --worktree flag on /solo:implement automates the worktree lifecycle for a single
change's implementation. The PR is the final output โ switch to the next ticket
after it's created. Post-merge tasks (archive etc.) are a separate workflow.
/solo:implement cNNNN-<change> --worktree
What happens:
- Preflight validates the handoff, spec contract, and toolchain in the main checkout.
- Worktree is created automatically (
isolation: 'worktree' on the agent call).
- Inside the worktree:
git switch -c feat/<change> main โ all phases
(Implement โ Verify โ Review โ Evidence โ Sync โ Changelog โ chore commit).
- Stops after the chore commit โ no push, no PR. Test locally.
- Worktree is cleaned up after the agent returns.
- Branch
feat/<change> persists in shared refs.
- Run
/solo:code-pr <change> to archive + push + create the PR.
Parallel workflow example:
/solo:propose "change-a"
/solo:spec "change-a"
/solo:spec-pr "change-a"
/solo:implement "change-a" --worktree
/solo:code-pr "change-a"
/solo:propose "change-b"
/solo:spec "change-b"
Caveat for DB-backed tests: Two worktrees running DB-dependent pytest suites
against the same TEST_DATABASE_URL can collide. Mitigations: serialize DB changes,
use separate test DBs, or note that DB tests skip gracefully without pgvector.
Worktree Lifecycle
| Step | Action | Responsible |
|---|
| Create | Runtime creates via agent({isolation:'worktree'}) | Automatic |
| Feature branch | git switch -c feat/<change> main inside worktree | Agent |
| Implementation | All ship-code phases (implement โ changelog) | Agent |
| Cleanup | Worktree removed after agent returns | Runtime |
| Local test | Inspect branch, run tests | You (touch point 3) |
| Archive + PR | /solo:code-pr <change> โ archive + push + gh pr create | Agent |
| PR review | Remote code review โ merge (archive lands on main) | You (touch point 4) |
No manual worktree cleanup is needed โ the workflow runtime handles it.
The Save Point Pattern
Agent starts work
โ
โโโ Makes a change
โ โโโ toolchain gates pass? โ Commit โ Continue
โ โโโ gates fail? โ Revert to last commit โ Investigate
โ
โโโ Change complete โ All commits form a clean history
This pattern means you never lose more than one increment of work. If an agent goes off the rails, git reset --hard HEAD takes you back to the last successful state.
Change Summaries
After any modification, provide a structured summary. This makes review easier, documents scope discipline, and surfaces unintended changes:
CHANGES MADE:
- packages/rag-core/retrieve_kb.py: ACL filter now applied inside the query
- packages/rag-core/tests/test_retrieve_kb.py: cross-tenant isolation case
THINGS I DIDN'T TOUCH (intentionally):
- packages/ingest-core: chunking is unaffected by this change
- apps/portal: no API surface change, so generated types stay put
POTENTIAL CONCERNS:
- Cache keys must include the ACL-cohort hash โ confirm they weren't widened.
- Needs an alembic migration? No โ the index already exists.
The "DIDN'T TOUCH" section shows you exercised scope discipline and didn't go on an unsolicited renovation.
Pre-Commit Hygiene
Before every commit, run the owning toolchain's gates for what you touched:
git diff --staged
git diff --staged | grep -iE "password|secret|api_key|token|MINIMAX_API_KEY|DATABASE_URL"
uv --directory <member> run ruff check . && uv --directory <member> run ruff format --check . && \
uv --directory <member> run pyright && uv --directory <member> run python -m pytest -q
go build ./... && go vet ./... && go test -race ./...
pnpm typecheck && pnpm lint && pnpm test
solo-spec validate "<change>" --strict
If you want a local guard, a simple .git/hooks/pre-commit that runs the touched toolchain's lint + format check (ruff check/ruff format --check, or gofmt -l, or pnpm lint) is sufficient.
Handling Generated Files
- Commit dependency manifests (
pyproject.toml/uv.lock, go.mod/go.sum,
apps/portal/package.json/pnpm-lock.yaml), alembic migrations under
apps/backend/migrations/, and OpenSpec artifacts under .solo/spec/.
- Commit generated OpenAPI types for the portal โ but regenerate them from the
backend spec, never hand-edit (the OpenAPI-is-the-contract invariant).
- Don't commit build output (
dist/, __pycache__/, .pytest_cache/,
node_modules/, Go binaries, cover.out), local config, or any .env with real
secrets.
.gitignore should cover at least: dist/, __pycache__/, node_modules/,
*.env, cover.out, and editor cruft.
Using Git for Debugging
git bisect start
git bisect bad HEAD
git bisect good <known-good-commit>
git bisect run uv --directory packages/rag-core run python -m pytest -q -k TestName
git log --oneline -20
git diff HEAD~5..HEAD -- packages/rag-core/
git blame packages/rag-core/retrieve_kb.py
git log --grep="tenant" --oneline
CHANGELOG Discipline
The repo keeps a root CHANGELOG.md in Keep a Changelog format โ one bullet per shipped change. /solo:implement generates the entry for you when shipping an approved change; you rarely hand-edit it. The shape:
# Changelog
## [Unreleased]
### Added
- retrieve_kb now enforces ACL server-side using the inherited caller identity.
### Fixed
- Cross-tenant leak where a cache key omitted tenant_id.
Opening a PR
This repo uses the gh CLI, run inside the submodule. /solo:implement runs this for you; to do it by hand:
gh pr create --base main --title "feat(rag-core): server-side ACL in retrieve_kb" --body "$(cat <<'EOF'
## Summary
- ...
## Test plan
- uv --directory packages/rag-core run ruff check . && pyright && pytest -q
- bash benchmarks/gates/tenant-isolation-test.sh
๐ค Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
/solo:implement stops at PR opened โ it does not auto-merge. Archiving the change with /solo:archive is a separate, post-merge human step. The superproject gitlink bump is yet another separate manual step (see "The Submodule Gitlink Bump").
Common Rationalizations
| Rationalization | Reality |
|---|
| "I'll commit when the feature is done" | One giant commit is impossible to review, debug, or revert. Commit each slice. |
| "The message doesn't matter" | Messages are documentation. Use Conventional Commits + the Co-Authored-By trailer. |
| "I'll squash it all later" | Squashing destroys the development narrative. Prefer clean incremental commits. |
| "Branches add overhead" | Short-lived branches are free. Long-lived branches are the problem โ merge within 1-3 days. |
| "I'll just commit on main" | Don't. Branch first (feat/<change>); main stays deployable. |
| "I'll bump the superproject gitlink in the same flow" | No. The gitlink bump is a separate, manual, human-gated step in the superproject. |
| "I'll split this change later" | Large changes are harder to review, riskier to deploy, and harder to revert. Split before submitting. |
Red Flags
- Large uncommitted changes accumulating
- Commit messages like "fix", "update", "misc" โ or missing the Co-Authored-By trailer
- Commits made directly on
main
- Formatting changes mixed with behavior changes
- Committing build output (
dist/, __pycache__/, node_modules/) or a .env with a real MINIMAX_API_KEY / DATABASE_URL
- A gitlink bump committed inside the submodule (it belongs in the superproject)
- An automated push of the superproject gitlink bump
- Long-lived branches that diverge significantly from main
- Force-pushing to shared branches
Project notes
- One OpenSpec change โ a
spec/<change> branch + SPEC PR (the contract), then a
feat/<change> branch + CODE PR, inside the platform/ submodule. Drive
non-trivial work through /solo:propose โ /solo:spec โ /solo:spec-pr โ
/solo:implement โ /solo:address-review โ /solo:archive.
/solo:implement is the autonomous lane: ship-plan (group into work-units) โ
ship-code (each unit RedโGreenโone commit) โ verify (the resolver-selected
per-toolchain gates + solo-spec validate "<change>" --strict) โ reconcile delta vs
canonical (drift โ stop) โ prepend the CHANGELOG.md entry โ commit (with the
Co-Authored-By trailer) โ push โ
open the PR via gh, then STOPS at the PR (no auto-merge). --dry-run stops
before push/PR.
/solo:archive runs after the PR merges, moving the change to
.solo/spec/changes/archive/YYYY-MM-DD-<change>/.
- The superproject gitlink bump (
cd <funix-mezon-bot> && git add platform && git commit) is a separate, manual step that is never automated or auto-pushed.
- Capture verification evidence under
.solo/spec/changes/<name>/evidence/.
- See the sibling
ci-cd-and-automation skill for the CI gates (the four-job
rag-engine.yml, checked out submodules: recursive) these commits must pass, and
documentation-and-adrs for when a change needs an ADR / spec update.
Verification
For every commit: