picocode-core 0.9.132 → 0.9.134

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/git.js +25 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picocode-core",
3
- "version": "0.9.132",
3
+ "version": "0.9.134",
4
4
  "description": "The agent runtime behind pico: sessions, tools, subagents, MCP, memory, and model access",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/git.js CHANGED
@@ -3,7 +3,7 @@ import { readFileSync, statSync, watch } from 'node:fs'
3
3
  import { dirname, isAbsolute, join, resolve } from 'node:path'
4
4
 
5
5
  const DEBOUNCE_MS = 500
6
- const POLL_MS = 15000
6
+ const POLL_MS = 5000
7
7
  const GIT_TIMEOUT_MS = 5000
8
8
 
9
9
  function findGitDir(startDir) {
@@ -48,6 +48,7 @@ export function createGitService({ onChange = () => {} } = {}) {
48
48
  let gitDir = null
49
49
  let epoch = 0
50
50
  let watcher = null
51
+ let treeWatcher = null
51
52
  let poll = null
52
53
  let debounce = null
53
54
  let child = null
@@ -58,6 +59,8 @@ export function createGitService({ onChange = () => {} } = {}) {
58
59
  epoch += 1
59
60
  watcher?.close()
60
61
  watcher = null
62
+ treeWatcher?.close()
63
+ treeWatcher = null
61
64
  if (poll) clearInterval(poll)
62
65
  poll = null
63
66
  if (debounce) clearTimeout(debounce)
@@ -85,6 +88,20 @@ export function createGitService({ onChange = () => {} } = {}) {
85
88
  } catch {
86
89
  watcher = null
87
90
  }
91
+ // the working tree too, so an edit shows up right away rather than at
92
+ // the next poll. recursive watching rides on FSEvents on macOS; the
93
+ // refresh is debounced so a burst of writes costs one git call
94
+ try {
95
+ treeWatcher = watch(root, { recursive: true }, (_event, file) => {
96
+ if (epoch !== started) return
97
+ const name = String(file ?? '')
98
+ if (name === '.git' || name.startsWith('.git/') || name.includes('node_modules')) return
99
+ refresh()
100
+ })
101
+ treeWatcher.on('error', () => {})
102
+ } catch {
103
+ treeWatcher = null
104
+ }
88
105
  poll = setInterval(run, POLL_MS)
89
106
  poll.unref?.()
90
107
  run()
@@ -107,7 +124,9 @@ export function createGitService({ onChange = () => {} } = {}) {
107
124
  }
108
125
  const started = epoch
109
126
  const branch = readBranch(gitDir)
110
- const proc = spawn('git', ['--no-optional-locks', 'diff', 'HEAD', '--shortstat'], {
127
+ // one shell round trip: the tracked diff stat, then a marker, then the
128
+ // porcelain status so untracked files count too
129
+ const proc = spawn('sh', ['-c', 'git --no-optional-locks diff HEAD --shortstat; echo __status__; git --no-optional-locks status --porcelain --untracked-files=all'], {
111
130
  cwd: root,
112
131
  stdio: ['ignore', 'pipe', 'ignore'],
113
132
  })
@@ -125,8 +144,10 @@ export function createGitService({ onChange = () => {} } = {}) {
125
144
  clearTimeout(timeout)
126
145
  if (child === proc) child = null
127
146
  if (epoch !== started) return
128
- const stats = code === 0 ? parseShortstat(output) : { added: 0, removed: 0 }
129
- const next = branch ? { branch, ...stats } : null
147
+ const [diff = '', status = ''] = output.split('__status__')
148
+ const stats = code === 0 ? parseShortstat(diff) : { added: 0, removed: 0 }
149
+ const untracked = code === 0 ? status.split('\n').filter((line) => line.startsWith('??')).length : 0
150
+ const next = branch ? { branch, ...stats, untracked } : null
130
151
  if (JSON.stringify(next) !== JSON.stringify(current)) {
131
152
  current = next
132
153
  onChange()