Skip to content

Commit 5ad6c05

Browse files
authored
Merge pull request #6 from morluto/feat/npm-onboarding
feat(onboarding): add npm setup and release flow
2 parents bb3cbfc + 1edef0c commit 5ad6c05

26 files changed

Lines changed: 1883 additions & 18 deletions

.github/workflows/ci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,15 @@ jobs:
249249
cache: true
250250
- name: Run integration tests
251251
run: go test -v -race -count=1 -tags=integration -timeout 300s ./...
252+
253+
npm:
254+
name: npm launcher and package metadata
255+
runs-on: ubuntu-latest
256+
timeout-minutes: 10
257+
steps:
258+
- uses: actions/checkout@v6
259+
- uses: actions/setup-node@v6
260+
with:
261+
node-version: 22
262+
- run: npm run test:npm
263+
- run: npm pack --dry-run

.github/workflows/release.yml

Lines changed: 118 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,80 @@ on:
66
- "v*"
77

88
permissions:
9-
contents: write
10-
packages: write
9+
contents: read
1110

1211
jobs:
12+
validate:
13+
name: Validate release commit
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 20
16+
steps:
17+
- uses: actions/checkout@v6
18+
- uses: actions/setup-go@v6
19+
with:
20+
go-version-file: go.mod
21+
cache: true
22+
- uses: actions/setup-node@v6
23+
with:
24+
node-version: 22
25+
- run: go test ./...
26+
- run: go vet ./...
27+
- run: test -z "$(gofmt -l .)"
28+
- run: npm run test:npm
29+
30+
build-npm-binaries:
31+
name: Build npm binary ${{ matrix.target }}
32+
needs: validate
33+
runs-on: ubuntu-latest
34+
timeout-minutes: 30
35+
strategy:
36+
fail-fast: false
37+
matrix:
38+
include:
39+
- { target: darwin-arm64, goos: darwin, goarch: arm64, binary: gitcontribute }
40+
- { target: darwin-x64, goos: darwin, goarch: amd64, binary: gitcontribute }
41+
- { target: linux-arm64, goos: linux, goarch: arm64, binary: gitcontribute }
42+
- { target: linux-x64, goos: linux, goarch: amd64, binary: gitcontribute }
43+
- { target: win32-x64, goos: windows, goarch: amd64, binary: gitcontribute.exe }
44+
steps:
45+
- uses: actions/checkout@v6
46+
- uses: actions/setup-go@v6
47+
with:
48+
go-version-file: go.mod
49+
cache: true
50+
- name: Build native binary
51+
shell: bash
52+
env:
53+
GOOS: ${{ matrix.goos }}
54+
GOARCH: ${{ matrix.goarch }}
55+
CGO_ENABLED: "0"
56+
TARGET: ${{ matrix.target }}
57+
BINARY: ${{ matrix.binary }}
58+
VERSION: ${{ github.ref_name }}
59+
run: |
60+
mkdir -p "dist/$TARGET"
61+
go build -trimpath -ldflags "-s -w -X main.version=${VERSION#v}" -o "dist/$TARGET/$BINARY" ./cmd/gitcontribute
62+
- uses: actions/upload-artifact@v7
63+
with:
64+
name: native-${{ matrix.target }}
65+
path: dist/${{ matrix.target }}/${{ matrix.binary }}
66+
if-no-files-found: error
67+
1368
goreleaser:
1469
name: Build and release
70+
needs: validate
1571
runs-on: ubuntu-latest
1672
timeout-minutes: 30
73+
permissions:
74+
contents: write
1775
steps:
1876
- uses: actions/checkout@v6
1977
with:
2078
fetch-depth: 0
21-
2279
- uses: actions/setup-go@v6
2380
with:
2481
go-version-file: go.mod
2582
cache: true
26-
2783
- name: Run GoReleaser
2884
uses: goreleaser/goreleaser-action@v7
2985
with:
@@ -33,24 +89,79 @@ jobs:
3389
env:
3490
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3591

92+
publish-npm:
93+
name: Publish npm package
94+
needs: [validate, build-npm-binaries, goreleaser]
95+
runs-on: ubuntu-latest
96+
timeout-minutes: 20
97+
environment: npm
98+
permissions:
99+
contents: write
100+
id-token: write
101+
steps:
102+
- uses: actions/checkout@v6
103+
- uses: actions/setup-node@v6
104+
with:
105+
node-version: 22
106+
registry-url: https://registry.npmjs.org
107+
- uses: actions/download-artifact@v8
108+
with:
109+
pattern: native-*
110+
path: dist
111+
- name: Normalize downloaded artifact layout
112+
shell: bash
113+
run: |
114+
for directory in dist/native-*; do
115+
target=${directory##*/native-}
116+
mkdir -p "dist/$target"
117+
cp "$directory"/* "dist/$target/"
118+
if [[ $target != win32-* ]]; then chmod +x "dist/$target/gitcontribute"; fi
119+
done
120+
- name: Verify release version
121+
shell: bash
122+
env:
123+
VERSION: ${{ github.ref_name }}
124+
run: node scripts/verify-release-version.mjs "${VERSION#v}"
125+
- run: npm run test:npm
126+
- run: npm run build:npm
127+
- name: Build and inspect npm tarball
128+
shell: bash
129+
run: |
130+
npm pack --pack-destination dist
131+
tarball=$(find dist -maxdepth 1 -name 'gitcontribute-*.tgz' -print -quit)
132+
test -n "$tarball"
133+
test "$(stat -c %s "$tarball")" -le 100000000
134+
sha256sum "$tarball" | sed 's# dist/# #' > dist/npm-SHA256SUMS
135+
npm install --prefix "$RUNNER_TEMP/npm-smoke" --ignore-scripts --no-audit --no-fund "$tarball"
136+
"$RUNNER_TEMP/npm-smoke/node_modules/.bin/gitcontribute" metadata --json
137+
- name: Publish npm package
138+
run: npm publish --provenance --access public
139+
- name: Attach npm package to GitHub release
140+
env:
141+
GH_TOKEN: ${{ github.token }}
142+
shell: bash
143+
run: |
144+
tarball=$(find dist -maxdepth 1 -name 'gitcontribute-*.tgz' -print -quit)
145+
gh release upload "${{ github.ref_name }}" "$tarball" dist/npm-SHA256SUMS --clobber
146+
36147
release-notes:
37148
name: Generate release notes
38149
runs-on: ubuntu-latest
39150
timeout-minutes: 5
40-
needs: goreleaser
151+
needs: [goreleaser, publish-npm]
152+
permissions:
153+
contents: write
41154
steps:
42155
- uses: actions/checkout@v6
43156
with:
44157
fetch-depth: 0
45-
46158
- name: Generate changelog
47159
uses: orhun/git-cliff-action@v4
48160
with:
49161
config: cliff.toml
50162
args: --latest --strip all
51163
env:
52164
OUTPUT: CHANGELOG.md
53-
54165
- name: Upload release notes
55166
uses: softprops/action-gh-release@v2
56167
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# === Binaries ===
22
/bin/
33
/dist/
4+
/npm/bin/native/
45
*.exe
56
*.exe~
67
*.dll

README.md

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,37 @@ persistence invariants, and side-effect rules. See
1414

1515
## Install
1616

17-
GitContribute requires Go 1.26 or newer. A C toolchain is not required because
18-
the SQLite driver is pure Go.
17+
The recommended installation and onboarding command requires Node.js 18 or
18+
newer. It runs the native GitContribute binary bundled in the npm package; Go
19+
and a C toolchain are not required.
1920

2021
```sh
21-
go install github.com/morluto/gitcontribute/cmd/gitcontribute@latest
22+
npx gitcontribute@latest setup
23+
```
24+
25+
Install a persistent shell command instead:
26+
27+
```sh
28+
npm install --global gitcontribute@latest
29+
gitcontribute setup
2230
```
2331

24-
Or build a checkout:
32+
Projects that want to pin the CLI version can install it as a development
33+
dependency and invoke it through `npx` or package scripts:
2534

2635
```sh
36+
npm install --save-dev gitcontribute
37+
npx gitcontribute setup --codex --yes
38+
```
39+
40+
The npm package has no install lifecycle and does not download an executable at
41+
install time. It contains native binaries for macOS ARM64/x64, Linux
42+
ARM64/x64, and Windows x64.
43+
44+
Developers with Go 1.26 or newer can install from source or build a checkout:
45+
46+
```sh
47+
go install github.com/morluto/gitcontribute/cmd/gitcontribute@latest
2748
go build -o gitcontribute ./cmd/gitcontribute
2849
```
2950

@@ -35,18 +56,40 @@ You also need a `git` executable. Optional helpers that the CLI can use:
3556
## Quick start
3657

3758
```sh
38-
gitcontribute init
39-
gitcontribute configure --token-source=env --token-source-key=GITHUB_TOKEN
40-
gitcontribute sync owner/repo
41-
gitcontribute search threads "connection timeout" --repo owner/repo --json
42-
gitcontribute dossier build owner/repo --json
59+
npx gitcontribute@latest setup
60+
npx gitcontribute@latest sync owner/repo
61+
npx gitcontribute@latest search threads "connection timeout" --repo owner/repo --json
62+
npx gitcontribute@latest dossier build owner/repo --json
4363
```
4464

4565
Run `gitcontribute --help` and `gitcontribute <command> --help` for full flag
4666
reference.
4767

4868
## Initialization, configuration, and authentication
4969

70+
`gitcontribute setup` is the normal entry point. It initializes the local
71+
corpus, selects a GitHub authentication source, and registers the MCP server
72+
with Codex and/or Claude Code. It remains local-only: adding `--repo owner/repo`
73+
creates a discovery source but does not contact GitHub or start a sync.
74+
75+
```sh
76+
gitcontribute setup # interactive
77+
gitcontribute setup --codex --yes # configure Codex
78+
gitcontribute setup --all-clients --yes # configure every supported client
79+
gitcontribute setup --codex --mcp-version latest --yes
80+
gitcontribute setup --token-source env \
81+
--token-source-key GITHUB_TOKEN --yes
82+
gitcontribute setup --codex --dry-run --json # inspect without writing
83+
gitcontribute remove --all-clients --yes # remove only MCP registrations
84+
gitcontribute upgrade --check # compare with the latest npm release
85+
gitcontribute upgrade --yes # update a global npm installation
86+
```
87+
88+
When setup runs through `npx`, client configuration launches a released npm
89+
version rather than recording an ephemeral npm-cache path. `remove` never
90+
deletes the corpus or GitContribute application configuration. See
91+
[the onboarding design](docs/onboarding.md) for the complete contract.
92+
5093
`gitcontribute init` creates the default corpus database and directories if they
5194
do not exist. It does not fetch anything from GitHub.
5295

cmd/gitcontribute/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import (
1616
"github.com/morluto/gitcontribute/internal/tui"
1717
)
1818

19-
const version = "dev"
19+
// version is replaced from the release tag with -ldflags.
20+
var version = "dev"
2021

2122
func main() {
2223
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)

docs/onboarding.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Onboarding and npm distribution
2+
3+
The primary installation and onboarding entry point is:
4+
5+
```sh
6+
npx gitcontribute@latest setup
7+
```
8+
9+
GitContribute remains a native Go application. The `gitcontribute` npm package
10+
contains a small Node.js launcher and precompiled binaries for macOS ARM64/x64,
11+
Linux ARM64/x64, and Windows x64. Installation has no lifecycle script
12+
and performs no binary download. The launcher chooses the host binary at run
13+
time and forwards standard streams, arguments, signals, and its exit status.
14+
15+
## Setup contract
16+
17+
Setup is a local capability. It may create the GitContribute configuration and
18+
corpus, register the MCP server with selected coding clients, and add an
19+
explicit repository source. It does not synchronize a repository, access
20+
GitHub, execute repository-controlled code, or mutate GitHub.
21+
22+
The setup engine plans and applies only GitContribute-owned entries:
23+
24+
- `[mcp_servers.gitcontribute]` in Codex TOML configuration;
25+
- `mcpServers.gitcontribute` in Claude JSON configuration.
26+
27+
Unrelated configuration is preserved. Repeated setup is idempotent. `remove`
28+
deletes only those entries; it never removes the GitContribute corpus or its
29+
application configuration. `--dry-run` performs validation without writes,
30+
and `--json` exposes per-step results for automation.
31+
32+
When invoked through npm, setup records an npm launcher such as:
33+
34+
```text
35+
npx --yes --package=gitcontribute@0.1.0 -- gitcontribute mcp
36+
```
37+
38+
It never records a temporary executable from the npm cache. Development builds
39+
use `gitcontribute@latest`; released builds use their exact version so a client
40+
configuration is reproducible. Re-running setup with a newer release updates
41+
the registration. `--mcp-version latest` opts into following the latest npm
42+
release instead.
43+
44+
## Release contract
45+
46+
One tag version controls the Go binaries and npm package. Release automation:
47+
48+
1. cross-compiles all supported native binaries with `CGO_ENABLED=0`;
49+
2. injects the tag version into the Go executable;
50+
3. assembles one npm package containing every binary;
51+
4. verifies the package has no install lifecycle;
52+
5. installs the tarball with `--ignore-scripts` and runs a smoke test;
53+
6. enforces a 100 MB compressed-package ceiling;
54+
7. publishes the npm package with provenance;
55+
8. creates a matching GitHub release.
56+
57+
The npm environment must be configured for trusted publishing before the first
58+
release. Package publication is an external mutation and is performed only by
59+
the tag-triggered release workflow.

internal/app/control.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/morluto/gitcontribute/internal/cli"
1616
"github.com/morluto/gitcontribute/internal/config"
1717
"github.com/morluto/gitcontribute/internal/github"
18+
clientsetup "github.com/morluto/gitcontribute/internal/setup"
1819
)
1920

2021
// Metadata reports deterministic application and local capability metadata.
@@ -172,7 +173,7 @@ func (s *Service) ControlStatus(ctx context.Context) (*cli.ControlStatusResult,
172173
// Doctor performs bounded local diagnostics. It reports authentication source
173174
// availability but never returns credential values or command output.
174175
func (s *Service) Doctor(ctx context.Context) (*cli.DoctorResult, error) {
175-
checks := make([]cli.DoctorCheck, 0, 7)
176+
checks := make([]cli.DoctorCheck, 0, 9)
176177
add := func(name string, required bool, err error, success string) {
177178
check := cli.DoctorCheck{Name: name, Required: required, Status: "ok", Message: success}
178179
if err != nil {
@@ -216,6 +217,16 @@ func (s *Service) Doctor(ctx context.Context) (*cli.DoctorResult, error) {
216217

217218
add("rg", false, lookPathError("rg"), "ripgrep is available")
218219

220+
if home := s.paths.HomeDir(); home != "" {
221+
for _, client := range clientsetup.Detect(home) {
222+
registered, _, checkErr := clientsetup.CheckRegistration(client, home)
223+
if checkErr == nil && !registered {
224+
checkErr = errors.New("client detected but GitContribute MCP registration is absent")
225+
}
226+
add("mcp_"+string(client), false, checkErr, "GitContribute MCP registration is present")
227+
}
228+
}
229+
219230
healthy := true
220231
for _, check := range checks {
221232
if check.Required && check.Status == "error" {

0 commit comments

Comments
 (0)