@searls/turbocommit 0.12.0 → 0.12.1

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/lib/git.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const { execSync } = require('child_process')
2
+ const path = require('path')
2
3
 
3
4
  function git (args, opts = {}) {
4
5
  const cwd = opts.cwd || process.cwd()
@@ -17,6 +18,16 @@ function gitRoot (cwd) {
17
18
  }
18
19
  }
19
20
 
21
+ function gitCommonDir (cwd) {
22
+ try {
23
+ const out = git('rev-parse --git-common-dir', { cwd })
24
+ if (!out) return null
25
+ return path.isAbsolute(out) ? out : path.resolve(cwd, out)
26
+ } catch {
27
+ return null
28
+ }
29
+ }
30
+
20
31
  function hasChanges (cwd) {
21
32
  try {
22
33
  git('diff --quiet HEAD', { cwd })
@@ -72,6 +83,7 @@ function esc (s) {
72
83
  module.exports = {
73
84
  git,
74
85
  gitRoot,
86
+ gitCommonDir,
75
87
  hasChanges,
76
88
  addAndCommit,
77
89
  hasCommits,
package/lib/session.js CHANGED
@@ -1,28 +1,36 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const { ensureDir } = require('./io')
4
+ const { gitCommonDir } = require('./git')
4
5
 
5
6
  const BREADCRUMB_THRESHOLD_MS = 2000
6
7
  const STALE_TTL_MS = 24 * 60 * 60 * 1000
7
8
 
8
9
  function turbocommitDir (root) {
9
- return path.join(root, '.git', 'turbocommit')
10
+ if (!root) return null
11
+ const gitDir = gitCommonDir(root)
12
+ if (!gitDir) return null
13
+ return path.join(gitDir, 'turbocommit')
10
14
  }
11
15
 
12
16
  function breadcrumbDir (root) {
13
- return path.join(turbocommitDir(root), 'breadcrumbs')
17
+ const base = turbocommitDir(root)
18
+ return base && path.join(base, 'breadcrumbs')
14
19
  }
15
20
 
16
21
  function chainDir (root) {
17
- return path.join(turbocommitDir(root), 'chains')
22
+ const base = turbocommitDir(root)
23
+ return base && path.join(base, 'chains')
18
24
  }
19
25
 
20
26
  function pendingDir (root) {
21
- return path.join(turbocommitDir(root), 'pending')
27
+ const base = turbocommitDir(root)
28
+ return base && path.join(base, 'pending')
22
29
  }
23
30
 
24
31
  function watermarkDir (root) {
25
- return path.join(turbocommitDir(root), 'watermarks')
32
+ const base = turbocommitDir(root)
33
+ return base && path.join(base, 'watermarks')
26
34
  }
27
35
 
28
36
  /**
@@ -42,6 +50,7 @@ function handleSessionEnd (input, root) {
42
50
  if (!sessionId) return
43
51
 
44
52
  const dir = breadcrumbDir(root)
53
+ if (!dir) return
45
54
  ensureDir(dir)
46
55
  const data = { session_id: sessionId, timestamp: Date.now() }
47
56
  fs.writeFileSync(path.join(dir, sessionId + '.json'), JSON.stringify(data) + '\n')
@@ -67,7 +76,7 @@ function handleSessionStart (input, root) {
67
76
  if (source !== 'clear' && source !== 'resume') return
68
77
 
69
78
  const dir = breadcrumbDir(root)
70
- if (!fs.existsSync(dir)) return
79
+ if (!dir || !fs.existsSync(dir)) return
71
80
 
72
81
  // Scan breadcrumbs for closest match
73
82
  const now = Date.now()
@@ -108,6 +117,7 @@ function handleSessionStart (input, root) {
108
117
 
109
118
  // Write chain for this session
110
119
  const cDir = chainDir(root)
120
+ if (!cDir) return
111
121
  ensureDir(cDir)
112
122
  const chain = { parent: best.session_id, ancestors }
113
123
  fs.writeFileSync(path.join(cDir, sessionId + '.json'), JSON.stringify(chain) + '\n')
@@ -134,7 +144,9 @@ function getAncestors (root, sessionId) {
134
144
  */
135
145
  let pendingSeq = 0
136
146
  function savePending (root, sessionId, transcript) {
137
- const dir = path.join(pendingDir(root), sessionId)
147
+ const base = pendingDir(root)
148
+ if (!base) return
149
+ const dir = path.join(base, sessionId)
138
150
  ensureDir(dir)
139
151
  const timestamp = String(Date.now()) + '-' + String(pendingSeq++).padStart(4, '0')
140
152
  fs.writeFileSync(path.join(dir, timestamp + '.txt'), transcript)
@@ -146,8 +158,10 @@ function savePending (root, sessionId, transcript) {
146
158
  */
147
159
  function collectPending (root, sessionIds) {
148
160
  const results = []
161
+ const base = pendingDir(root)
162
+ if (!base) return results
149
163
  for (const sid of sessionIds) {
150
- const dir = path.join(pendingDir(root), sid)
164
+ const dir = path.join(base, sid)
151
165
  let files
152
166
  try {
153
167
  files = fs.readdirSync(dir).sort()
@@ -183,6 +197,7 @@ function readWatermark (root, sessionId) {
183
197
  */
184
198
  function saveWatermark (root, sessionId, pairs, commit) {
185
199
  const dir = watermarkDir(root)
200
+ if (!dir) return
186
201
  ensureDir(dir)
187
202
  fs.writeFileSync(path.join(dir, sessionId + '.json'), JSON.stringify({ pairs, commit }) + '\n')
188
203
  }
@@ -209,9 +224,11 @@ function resolveParentCommit (root, sessionId) {
209
224
  * Delete consumed pending + chain files after commit.
210
225
  */
211
226
  function cleanupConsumed (root, sessionIds) {
227
+ const base = pendingDir(root)
228
+ if (!base) return
212
229
  for (const sid of sessionIds) {
213
230
  // Remove pending dir
214
- const dir = path.join(pendingDir(root), sid)
231
+ const dir = path.join(base, sid)
215
232
  try {
216
233
  const files = fs.readdirSync(dir)
217
234
  for (const file of files) {
@@ -232,6 +249,7 @@ function cleanupStale (root, maxAgeMs) {
232
249
  const ttl = maxAgeMs != null ? maxAgeMs : STALE_TTL_MS
233
250
  const now = Date.now()
234
251
  const base = turbocommitDir(root)
252
+ if (!base) return
235
253
 
236
254
  for (const sub of ['breadcrumbs', 'chains', 'tracking', 'watermarks']) {
237
255
  const dir = path.join(base, sub)
package/lib/track.js CHANGED
@@ -1,16 +1,21 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const { ensureDir } = require('./io')
4
+ const { turbocommitDir } = require('./session')
4
5
 
5
6
  /**
6
- * Directory under .git where turbocommit stores tracking state.
7
+ * Directory under the git common dir where turbocommit stores tracking state.
8
+ * Returns null when the root isn't resolvable to a git repo (including when
9
+ * run outside a repo, which is not expected during normal operation).
7
10
  */
8
11
  function trackingDir (root) {
9
- return path.join(root, '.git', 'turbocommit', 'tracking')
12
+ const base = turbocommitDir(root)
13
+ return base && path.join(base, 'tracking')
10
14
  }
11
15
 
12
16
  function trackingPath (root, sessionId) {
13
- return path.join(trackingDir(root), sessionId + '.jsonl')
17
+ const dir = trackingDir(root)
18
+ return dir && path.join(dir, sessionId + '.jsonl')
14
19
  }
15
20
 
16
21
  /**
@@ -72,6 +77,7 @@ function handleTrack (input, root) {
72
77
  if (toolName === 'Bash' && typeof toolInput.command !== 'string') return
73
78
 
74
79
  const file = trackingPath(root, sessionId)
80
+ if (!file) return
75
81
  ensureDir(path.dirname(file))
76
82
  fs.appendFileSync(file, JSON.stringify(entry) + '\n')
77
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@searls/turbocommit",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Auto-commit after every Claude Code turn",
5
5
  "bin": {
6
6
  "turbocommit": "./cli.js"