Thank you for your interest in contributing to gitwise — the AI git toolbelt (commit / review / pr / release).
- Node.js >= 18 (matches
.nvmrc) - Git
- GitHub CLI (
gh) (optional, for PR creation)
git clone https://github.com/denisvieiradev/gitwise.git
cd gitwise
npm installnpm install from the root resolves every workspace under packages/* in a single pass.
gitwise is an npm workspaces monorepo:
gitwise/
├── package.json # private workspaces root (no shipped artifact)
├── tsconfig.base.json # shared compilerOptions every package extends
├── tsconfig.json # transitional: covers the legacy src/ tree
├── tsup.config.ts # shared bundler helper (defineGitwiseTsup)
├── jest.config.ts # aggregates per-package jest configs via `projects`
├── packages/ # publishable packages live here (see ADR-002)
│ ├── core/ # @denisvieiradev/gitwise-core (added in later tasks)
│ ├── cli/ # @denisvieiradev/gitwise (added in later tasks)
│ └── skills/ # @denisvieiradev/gitwise-skills (added in later tasks)
├── src/ # transitional source — migrated into packages/core in later tasks
├── __tests__/ # transitional tests — partitioned into per-package suites later
└── __mocks__/ # module mocks used by the transitional jest project
The packages/ directory exists as soon as the workspace skeleton is in place (with a .gitkeep until real packages land). Subsequent refactor tasks drop core, cli, and skills into it.
Root scripts delegate to workspaces and also keep the transitional legacy src/ build alive. They all run from the repo root.
| Script | What it does |
|---|---|
npm run build |
build:legacy then build:workspaces (both must succeed) |
npm run build:legacy |
Bundles the transitional src/cli/index.ts via tsup |
npm run build:workspaces |
npm run --workspaces --if-present build — fans out to every package |
npm run dev |
tsup --watch for the transitional CLI |
npm test |
Jest with projects aggregating the legacy suite plus any packages/*/jest.config.* |
npm run test:coverage |
Same as npm test with coverage reporting |
npm run lint |
tsc --noEmit (legacy) then npm run -ws --if-present lint |
npm run typecheck |
tsc --noEmit (legacy) then npm run -ws --if-present typecheck |
Workspace-scoped commands work the usual way:
# Run a script in a single workspace
npm run -w packages/core build
# Run a script in every workspace that defines it
npm run --workspaces --if-present test-
Create the directory under
packages/<name>/. -
Add a
package.jsonwith a unique"name","version"matching the locked monorepo version, and the scripts the root delegates to (build,test,lint,typecheck). -
Add
packages/<name>/tsconfig.jsonextending the root base:{ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "rootDir": "src" }, "include": ["src"] } -
Add
packages/<name>/tsup.config.tsthat consumes the shared helper:import { defineGitwiseTsup } from "../../tsup.config.js"; export default defineGitwiseTsup({ entry: ["src/index.ts"], outDir: "dist", });
-
Add
packages/<name>/jest.config.ts. The rootjest.config.tsauto-discovers anypackages/*/jest.config.{ts,js,mjs}and folds it into itsprojectsarray. -
Link sibling workspace deps via the workspace protocol when needed:
-
Run
npm installfrom the root to register the new workspace and re-hoistnode_modules.
The CLI is still served from the legacy src/cli/index.ts until the porting tasks complete. To exercise it:
npm run build
node dist/index.js --helpFor active development, use watch mode so the build updates as you edit:
npm run dev-
Fork the repository and create a branch from
main:git checkout -b feature/my-feature
-
Make your changes — write code, add tests, update docs if needed.
-
Run checks before committing:
npm run lint npm test npm run build -
Commit using Conventional Commits format:
git commit -m "feat: add support for X"Common prefixes:
feat:,fix:,docs:,chore:,refactor:,test:,ci: -
Push your branch and open a Pull Request against
main.
- Write TypeScript in strict mode (every workspace extends
tsconfig.base.json). - Follow ESM module conventions (
"type": "module"). - Add tests for new logic. Tests live next to the package that owns them (
packages/<name>/__tests__/) or — for transitional code — in the root__tests__/. - Keep PRs focused — one feature or fix per PR.
- Update the README if you add new commands or change behavior.
- Never commit API keys or
~/.gitwise/.envcontents.
gitwise is in Phase 0 of the release plan recorded in ADR-005:
all workspaces share a single locked version, and the release is cut from a
maintainer's machine using a small Node script. Phase 1 — when gw release
dogfoods itself against this repo — replaces the manual step. scripts/release.mjs
stays in the repo as the documented fallback after Phase 1 (per ADR-005).
-
Make sure
mainis green andCHANGELOG.mdhas an entry for the version you are about to cut. The release workflow uses the top##section ofCHANGELOG.mdas the GitHub release body. -
From the repo root, run the bump propagator. It accepts
patch,minor,major, or an explicitX.Y.Z:node scripts/release.mjs patch # or: node scripts/release.mjs 0.2.0The script bumps the root
package.json, propagates the same version to everypackages/*/package.json, stages the changes, creates achore(release): vX.Y.Zcommit, and tagsvX.Y.Z. It deliberately does not push. -
Push the commit and the tag:
git push origin HEAD git push origin vX.Y.Z
-
Pushing the tag triggers
.github/workflows/release.yml, which installs dependencies, builds every workspace, runs the full test suite, and only then publishes each package vianpm publish --workspaces --access publicand opens a GitHub release using the CHANGELOG entry. A failing test aborts the publish.
| Secret | Purpose |
|---|---|
NPM_TOKEN |
Authenticates npm publish against the npm registry |
GITHUB_TOKEN |
Default token used by gh release create (provided by Actions) |
If release.mjs ran but you have not pushed yet, undo it locally:
git tag -d vX.Y.Z
git reset --hard HEAD~1If the tag has already been pushed and CI has not yet published, delete the
remote tag (git push origin :refs/tags/vX.Y.Z) and start over. Once a
package version is on npm it cannot be reused; bump again instead.
Use GitHub Issues to report bugs or request features. Include:
- Steps to reproduce
- Expected vs actual behavior
- Your environment (Node.js version, OS, gitwise version)
Multi-step git flows that mutate repository state must use the Transaction primitive from @denisvieiradev/gitwise-core so that a mid-flow failure leaves the repository in its pre-command state rather than a partial state.
Pattern (see core/src/infra/transaction.ts):
const tx = new Transaction();
try {
const result = await tx.run({
name: "my-step",
apply: async () => { /* perform the mutation */ },
compensate: async (result) => { /* undo the mutation */ },
});
// ... more steps
} catch (err) {
await tx.rollback(wrapError(err), logger);
throw err;
}Key rules:
- Call
tx.run(step)for every side-effectful step. Steps are rolled back in LIFO order. - Each
compensatemust undo exactly what its pairedapplydid. Test both paths in isolation. - If
compensateitself can fail, log the failure but do not throw — the transaction surfaces aROLLBACK_PARTIALwarning automatically. - Acquire the repo lock (
acquireRepoLock) before the firsttx.runcall and release it infinally.
Worked example: core/src/commands/release.ts → prepareRelease() is the canonical reference implementation. It wraps branch creation, gitignore mutation, CHANGELOG, manifest writes, notes, and plan file as individual steps with compensating actions. See ADR-004 for the architectural rationale.
If a flow produces ROLLBACK_PARTIAL, users are directed to docs/recovery.md for manual recovery steps.
CodeQL and OSV-Scanner run on every PR and block merge on findings. In rare cases, a security scanner may block a release-critical hotfix.
Single-PR exception: a hotfix PR may be merged with an active CodeQL or OSV-Scanner finding only when all of the following are true:
- The finding is confirmed to be a false positive or an unfixable transitive dependency issue (not a code defect in gitwise itself).
- The maintainer explicitly labels the PR
hotfix-exceptionand documents the justification in the PR description. - A follow-up issue or PR is filed within 24 hours to resolve or suppress the finding with proper context (a
# noseccomment with explanation, or anosv-scanner.tomlignore entry with an expiry date — see Adding an OSV Ignore Entry).
The follow-up must be addressed within the next release cycle. A hotfix that closes without a follow-up is a policy violation. The maintainer is responsible for tracking it.
Every PR that touches subprocess invocation or file-path handling must maintain two categories of security tests:
Subprocess argument safety (packages/core/__tests__/unit/infra/): assert that execFile is always called with an array of arguments, never a shell-interpolated string. A future refactor that introduces shell: true or string concatenation in argument position must fail this test. The tests cover all wrappers in git.ts, github.ts, and claude-code.ts.
Sensitive-file blocklist (packages/core/__tests__/unit/): assert that every pattern in the blocklist matches representative sensitive paths (.env, id_rsa, *.pem, *.key, etc.) and that legitimate paths are not blocked. Changes to the blocklist require corresponding test updates to maintain coverage of both blocked and allowed path patterns.
If you add a new subprocess wrapper or extend the sensitive-file blocklist, add matching tests in the same PR. CI enforces an 80% coverage threshold; a new untested wrapper will cause the coverage gate to fail.
When OSV-Scanner surfaces a HIGH or CRITICAL finding that has no available fix (e.g., a transitive dependency with an unfixed CVE), you may acknowledge it via osv-scanner.toml. This file is checked in and reviewed as code.
Required fields for every ignore entry:
[[IgnoredVulns]]
id = "GHSA-xxxx-xxxx-xxxx" # the exact OSV/GHSA identifier
ignoreUntil = "2026-09-01" # REQUIRED expiry date — no open-ended ignores
reason = "No fix available upstream; tracking in #<issue-number>."Rules:
ignoreUntilis mandatory. The workflow fails the build when this date passes, forcing review.- Set the expiry to no more than 90 days out unless a longer upstream fix timeline is documented in
reason. - Include a GitHub issue number in
reasonso the finding can be tracked. - Entries that expire and are not renewed cause CI failures — resolve the underlying issue or file a new justified entry.
The OSV-Scanner ignore list is reviewed at each release cycle. Stale entries (past their ignoreUntil date) must be either renewed with fresh justification or removed as the dependency is updated.