git-chopstick-core 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.9] — 2026-06-13
4
+
5
+ ### Added
6
+ - **Test fixtures**: Created `src/__tests__/fixtures/test-repo.bundle` — a pre-built git bundle with 6 commits, 3 branches, and 1 tag for faster test setup. Added `src/__tests__/fixture-helpers.ts` with `setupFixtureRepo()` to clone the bundle in ~10ms instead of building a repo from scratch. Refactored integration tests to use the fixture.
7
+ - **Release skill moved**: Moved from `docs/skills/release.md` to `.agents/skills/release-npm.md` and renamed to `release-npm`.
8
+ - **Global default branch**: Set `git config --global init.defaultBranch main`.
9
+
10
+ ### Changed
11
+ - **Fixture rebuild instructions**: Updated to use `git init -q` (relies on global `init.defaultBranch`).
12
+ - **`prepublishOnly` now runs tests**: Integration tests run during `npm publish` to catch regressions before they ship.
13
+
14
+ ---
15
+
3
16
  ## [0.1.8] — 2026-06-13
4
17
 
5
18
  ### Added
@@ -129,6 +142,7 @@
129
142
 
130
143
  ---
131
144
 
145
+ [0.1.9]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.9
132
146
  [0.1.8]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.8
133
147
  [0.1.7]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.7
134
148
  [0.1.6]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.6
@@ -138,4 +152,4 @@
138
152
  [0.1.2]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.2
139
153
  [0.1.1]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.1
140
154
  [0.1.0]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.0
141
- [Unreleased]: https://github.com/parkiyong/git-chopstick-core/compare/v0.1.8...HEAD
155
+ [Unreleased]: https://github.com/parkiyong/git-chopstick-core/compare/v0.1.9...HEAD
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-chopstick-core",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -0,0 +1,105 @@
1
+ /**
2
+ * To rebuild the fixture bundle:
3
+ *
4
+ * cd /tmp && rm -rf fixture-builder && mkdir fixture-builder && cd fixture-builder
5
+ * git init -q && git config user.email f@t.t && git config user.name F
6
+ * echo '# Test Repo' > README.md && echo 'node_modules/' > .gitignore
7
+ * git add .gitignore README.md && git commit -m 'initial: add README and gitignore'
8
+ * echo 'hello' > hello.txt && mkdir -p src && echo 'console.log("hi")' > src/index.js
9
+ * git add hello.txt src/index.js && git commit -m 'feat: add hello.txt and src/index.js'
10
+ * echo 'world' > hello.txt && echo 'console.log("hello world")' > src/index.js
11
+ * git add -A && git commit -m 'feat: update hello and src/index.js'
12
+ * git checkout -b feature/one && echo 'feature one content' > feature-one.txt
13
+ * git add feature-one.txt && git commit -m 'feat: feature one'
14
+ * git checkout main && echo 'another change' > another.txt
15
+ * git add another.txt && git commit -m 'feat: add another.txt'
16
+ * git merge feature/one --no-ff --no-edit -m 'merge: feature/one into main'
17
+ * git checkout -b feature/two && echo 'feature two content' > feature-two.txt
18
+ * git add feature-two.txt && git commit -m 'feat: feature two'
19
+ * git checkout main && git tag -a v0.1.0 -m 'v0.1.0'
20
+ * git bundle create test-repo.bundle --all
21
+ * cp test-repo.bundle <this-dir>/fixtures/
22
+ *
23
+ * This creates a bundle with 6 commits on main, 3 branches, and 1 annotated tag.
24
+ */
25
+ import { mkdtempSync } from 'fs'
26
+ import { execSync } from 'child_process'
27
+ import { tmpdir } from 'os'
28
+ import { dirname, join } from 'path'
29
+ import { fileURLToPath } from 'url'
30
+ import { Repository } from '../models/repository.js'
31
+
32
+ const __dirname = dirname(fileURLToPath(import.meta.url))
33
+
34
+ /**
35
+ * Path to the pre-built fixture repo bundle.
36
+ */
37
+ export const FIXTURE_BUNDLE_PATH = join(__dirname, 'fixtures', 'test-repo.bundle')
38
+
39
+ /**
40
+ * Clone the fixture bundle to a temp directory, configure git identity,
41
+ * create local branches for all remote-tracking branches, and return
42
+ * the path and a Repository object.
43
+ *
44
+ * Use this in `beforeAll` for faster test setup — avoids creating a repo
45
+ * from scratch for every test suite.
46
+ */
47
+ export function setupFixtureRepo(): { repoPath: string; repo: Repository } {
48
+ const repoPath = mkdtempSync(join(tmpdir(), 'gcctest-fixture-'))
49
+
50
+ execSync(`git clone "${FIXTURE_BUNDLE_PATH}" "${repoPath}"`, {
51
+ stdio: 'pipe',
52
+ })
53
+
54
+ execSync('git config user.email test@test.test', {
55
+ cwd: repoPath,
56
+ stdio: 'pipe',
57
+ })
58
+ execSync('git config user.name Test', {
59
+ cwd: repoPath,
60
+ stdio: 'pipe',
61
+ })
62
+
63
+ // Create local branches for all remote-tracking branches (bundle clones
64
+ // only create a local branch for HEAD — everything else becomes origin/*)
65
+ const localBranches = execSync(
66
+ 'git branch --format="%(refname:short)"',
67
+ { cwd: repoPath, encoding: 'utf-8', stdio: 'pipe' }
68
+ )
69
+ .trim()
70
+ .split('\n')
71
+ .filter(Boolean)
72
+
73
+ const remoteBranches = execSync(
74
+ 'git branch -r --format="%(refname:lstrip=3)"',
75
+ { cwd: repoPath, encoding: 'utf-8', stdio: 'pipe' }
76
+ )
77
+ .trim()
78
+ .split('\n')
79
+ .filter(Boolean)
80
+ .filter(b => b !== 'HEAD' && !localBranches.includes(b))
81
+
82
+ for (const branch of remoteBranches) {
83
+ execSync(`git branch --track "${branch}" "origin/${branch}"`, {
84
+ cwd: repoPath,
85
+ stdio: 'pipe',
86
+ })
87
+ }
88
+
89
+ const repo = new Repository(repoPath)
90
+ return { repoPath, repo }
91
+ }
92
+
93
+ /**
94
+ * Run a git command in the fixture repo.
95
+ */
96
+ export function git(repoPath: string, args: string) {
97
+ execSync(`git ${args}`, { cwd: repoPath, stdio: 'pipe' })
98
+ }
99
+
100
+ /**
101
+ * Clean up a fixture repo directory.
102
+ */
103
+ export function cleanupFixtureRepo(repoPath: string) {
104
+ execSync(`rm -rf ${repoPath}`)
105
+ }
@@ -6,32 +6,19 @@ import { join } from 'path'
6
6
  import {
7
7
  Repository, getStatus, getCommits, getBranches,
8
8
  createCommit, createBranch, deleteLocalBranch, renameBranch,
9
- getCurrentBranch, getRepositoryType,
9
+ getCurrentBranch, getRepositoryType, getAllTags,
10
10
  } from '../index.js'
11
+ import {
12
+ setupFixtureRepo, cleanupFixtureRepo, git,
13
+ } from './fixture-helpers.js'
11
14
 
12
15
  let repoPath: string
13
16
  let repo: Repository
14
17
 
15
- function git(args: string) {
16
- execSync(`git ${args}`, { cwd: repoPath, stdio: 'pipe' })
17
- }
18
-
19
- function writeFile(path: string, content: string) {
20
- writeFileSync(join(repoPath, path), content)
21
- }
22
-
23
18
  beforeAll(() => {
24
- repoPath = mkdtempSync(join(tmpdir(), 'gcctest-'))
25
- git('init -q')
26
- git('config user.email test@test.test')
27
- git('config user.name Test')
28
-
29
- // Create an initial commit so we have history for detached HEAD tests
30
- writeFile('README.md', '# test')
31
- git('add README.md')
32
- git('commit -m "initial"')
33
-
34
- repo = new Repository(repoPath)
19
+ const fixture = setupFixtureRepo()
20
+ repoPath = fixture.repoPath
21
+ repo = fixture.repo
35
22
  })
36
23
 
37
24
  describe('getRepositoryType', () => {
@@ -52,16 +39,15 @@ describe('getRepositoryType', () => {
52
39
  describe('getCurrentBranch', () => {
53
40
  it('returns the current branch name', async () => {
54
41
  const branch = await getCurrentBranch(repoPath)
55
- expect(branch).toBeTruthy()
56
- expect(typeof branch).toBe('string')
42
+ expect(branch).toBe('main')
57
43
  })
58
44
 
59
45
  it('returns undefined when HEAD is detached', async () => {
60
- git('checkout --detach')
46
+ git(repoPath, 'checkout --detach')
61
47
  const branch = await getCurrentBranch(repoPath)
62
48
  expect(branch).toBeUndefined()
63
49
  // Reset back to a branch for subsequent tests
64
- git('checkout -')
50
+ git(repoPath, 'checkout main')
65
51
  })
66
52
  })
67
53
 
@@ -69,12 +55,11 @@ describe('getStatus', () => {
69
55
  it('returns a status with branch info', async () => {
70
56
  const status = await getStatus(repo)
71
57
  expect(status).toBeTruthy()
72
- expect(status!.currentBranch).toBeTruthy()
73
- expect(typeof status!.currentBranch).toBe('string')
58
+ expect(status!.currentBranch).toBe('main')
74
59
  })
75
60
 
76
61
  it('detects untracked files', async () => {
77
- writeFile('untracked.txt', 'hello')
62
+ writeFileSync(join(repoPath, 'untracked.txt'), 'hello')
78
63
  const status = await getStatus(repo)
79
64
  expect(status!.workingDirectory.files.length).toBeGreaterThanOrEqual(1)
80
65
  const untracked = status!.workingDirectory.files.find(
@@ -92,46 +77,57 @@ describe('getStatus', () => {
92
77
  })
93
78
 
94
79
  describe('getCommits', () => {
95
- it('returns commits in order', async () => {
96
- // Create another commit so we have history to read
97
- writeFile('commits-test.txt', 'data')
98
- git('add commits-test.txt')
99
- git('commit -m "test commit for getCommits"')
100
-
80
+ it('returns commits in order (fixture has 6 commits reachable from main)', async () => {
101
81
  const commits = await getCommits(repo, 'HEAD', 10)
102
- expect(commits.length).toBeGreaterThanOrEqual(1)
103
- expect(commits[0].summary).toBe('test commit for getCommits')
82
+ expect(commits.length).toBe(6)
104
83
  expect(commits[0].sha).toBeTruthy()
105
84
  expect(commits[0].author).toBeTruthy()
85
+ // The most recent commit should be the merge
86
+ expect(commits[0].summary).toBe('merge: feature/one into main')
87
+ // The initial commit should be the last one
88
+ expect(commits[5].summary).toBe('initial: add README and gitignore')
89
+ })
90
+
91
+ it('returns commits for feature/two branch', async () => {
92
+ const commits = await getCommits(repo, 'feature/two', 10)
93
+ expect(commits.length).toBeGreaterThanOrEqual(1)
94
+ expect(commits[0].summary).toBe('feat: feature two')
106
95
  })
107
96
  })
108
97
 
109
98
  describe('Branch operations', () => {
110
- it('creates, lists, renames, and deletes branches', async () => {
111
- // Create a branch
99
+ it('includes feature/one and feature/two', async () => {
100
+ const branches = await getBranches(repo)
101
+ const names = branches.map(b => b.nameWithoutRemote)
102
+ expect(names).toContain('main')
103
+ expect(names).toContain('feature/one')
104
+ expect(names).toContain('feature/two')
105
+ })
106
+
107
+ it('creates, renames, and deletes a branch', async () => {
112
108
  await createBranch(repo, 'test-feature', 'HEAD')
113
109
  let branches = await getBranches(repo)
114
- const names = branches.map(b => b.name)
110
+ const names = branches.map(b => b.nameWithoutRemote)
115
111
  expect(names).toContain('test-feature')
116
112
 
117
113
  // Rename the branch
118
- const featureBranch = branches.find(b => b.name === 'test-feature')!
114
+ const featureBranch = branches.find(b => b.nameWithoutRemote === 'test-feature')!
119
115
  await renameBranch(repo, featureBranch, 'test-feature-renamed')
120
116
  branches = await getBranches(repo)
121
- expect(branches.map(b => b.name)).toContain('test-feature-renamed')
122
- expect(branches.map(b => b.name)).not.toContain('test-feature')
117
+ expect(branches.map(b => b.nameWithoutRemote)).toContain('test-feature-renamed')
118
+ expect(branches.map(b => b.nameWithoutRemote)).not.toContain('test-feature')
123
119
 
124
120
  // Delete the branch
125
121
  await deleteLocalBranch(repo, 'test-feature-renamed')
126
122
  branches = await getBranches(repo)
127
- expect(branches.map(b => b.name)).not.toContain('test-feature-renamed')
123
+ expect(branches.map(b => b.nameWithoutRemote)).not.toContain('test-feature-renamed')
128
124
  })
129
125
  })
130
126
 
131
127
  describe('createCommit', () => {
132
128
  it('creates a commit with files', async () => {
133
- writeFile('commit-test.txt', 'commit data')
134
- git('add commit-test.txt')
129
+ writeFileSync(join(repoPath, 'commit-test.txt'), 'commit data')
130
+ git(repoPath, 'add commit-test.txt')
135
131
 
136
132
  const status = await getStatus(repo)
137
133
  const files = status!.workingDirectory.files.filter(
@@ -140,17 +136,23 @@ describe('createCommit', () => {
140
136
 
141
137
  const sha = await createCommit(repo, 'feat: add commit-test.txt', files)
142
138
  expect(sha).toBeTruthy()
143
- // createCommit returns the abbreviated SHA from git commit output (7+ chars)
144
139
  expect(sha.length).toBeGreaterThanOrEqual(7)
145
140
 
146
- // Verify the commit exists — the full SHA from getCommits should start
147
- // with the abbreviated SHA returned by createCommit
141
+ // Verify the commit exists
148
142
  const commits = await getCommits(repo, 'HEAD', 5)
149
143
  expect(commits[0].sha.startsWith(sha)).toBe(true)
150
144
  expect(commits[0].summary).toBe('feat: add commit-test.txt')
151
145
  })
152
146
  })
153
147
 
148
+ describe('Tag operations', () => {
149
+ it('lists tags from the fixture', async () => {
150
+ const tags = await getAllTags(repo)
151
+ expect(tags.size).toBeGreaterThanOrEqual(1)
152
+ expect(tags.has('v0.1.0')).toBe(true)
153
+ })
154
+ })
155
+
154
156
  afterAll(() => {
155
- execSync(`rm -rf ${repoPath}`)
157
+ cleanupFixtureRepo(repoPath)
156
158
  })
@@ -1,82 +0,0 @@
1
- ---
2
- name: release
3
- description: Automated release workflow for git-chopstick-core — version bump, changelog, build, validation, and npm publish.
4
- ---
5
-
6
- # Release: git-chopstick-core
7
-
8
- > Loaded via `skill("release")`. Run this whenever you need to publish a new release.
9
-
10
- ## Process
11
-
12
- ### 1. Read current state
13
-
14
- Read `package.json`, `CHANGELOG.md`, and run `git log --oneline -5` to understand what has changed since the last release.
15
-
16
- ### 2. Determine version bump
17
-
18
- Check what has changed:
19
-
20
- - **Breaking API changes** (removed exports, changed signatures) → minor bump (0.x.0)
21
- - **New features** (new functions, new exports) → patch bump (0.0.x)
22
- - **Bug fixes, docs, internal refactoring** → patch bump (0.0.x)
23
-
24
- Current version: read from `package.json`. Bump accordingly.
25
-
26
- ### 3. Update `CHANGELOG.md`
27
-
28
- Insert a new `## [VERSION] — YYYY-MM-DD` section at the top (below the `# Changelog` heading) with:
29
-
30
- - `### Added` — new features, exports, helpers
31
- - `### Changed` — behavioral changes, renames, config changes
32
- - `### Fixed` — bug fixes
33
-
34
- Then update the link references at the bottom:
35
-
36
- - Add `[VERSION]: https://github.com/parkiyong/git-chopstick-core/releases/tag/vVERSION`
37
- - Update `[Unreleased]: .../compare/vVERSION...HEAD`
38
-
39
- ### 4. Bump `package.json`
40
-
41
- Update `"version"` field.
42
-
43
- ### 5. Build and validate
44
-
45
- Run all of these (use parallel agents):
46
-
47
- - `npm run typecheck` — must pass with zero errors
48
- - `npm run build` — must produce a clean `dist/`
49
- - `npm test` — all integration tests must pass
50
- - `npm pack --dry-run` — confirm all of:
51
- - `dist/` is included (expect ~351 files, ~700kB)
52
- - `CHANGELOG.md`, `docs/`, and `src/__tests__/integration.test.ts` appear in the file list
53
- - `package.json`, `README.md`, `LICENSE` are present (npm includes these automatically)
54
- - No unexpected files are leaking through (check for test artifacts, `.ts` sources outside `dist/`)
55
-
56
- If any fail, fix before proceeding.
57
-
58
- ### 6. Stage and commit
59
-
60
- ```
61
- git add -A
62
- git commit -m "vVERSION: <short summary of changes>"
63
- ```
64
-
65
- ### 7. Push and publish
66
-
67
- ```
68
- git push origin main
69
- npm publish
70
- ```
71
-
72
- ### 8. Verify
73
-
74
- - Confirm npm shows the new version: `npm view git-chopstick-core version`
75
- - Confirm the tag exists on GitHub: `git tag -l`
76
- - Suggest followup tasks
77
-
78
- ## Notes
79
-
80
- - `prepublishOnly` runs `npm run typecheck && npm test && npm run build` automatically — integration tests must pass before publish can proceed. This catches API regressions, broken imports, and test failures before they ship.
81
- You still validate in step 5 beforehand rather than relying solely on `prepublishOnly`.
82
- - If this is the first release in a session, run `setup-matt-pocock-skills` first to configure issue tracker context.