flowviant 0.48.0 → 0.48.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/bin/lib/work.mjs CHANGED
@@ -259,7 +259,15 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
259
259
  * narrator: never awaited by a turn, every failure swallowed.
260
260
  */
261
261
  const WORKTREE_SWEEP_MS = 60_000;
262
+ /** How often the sweep refreshes `origin/<base>` before measuring. The
263
+ * behind-count is the whole point of the readout — "someone pushed while you
264
+ * were working" — and without a fetch it would only ever count what this
265
+ * machine already happened to have. Rarer than the sweep because a fetch is
266
+ * network, and a teammate's push being visible within three minutes is the
267
+ * same promise the rest of the product makes. */
268
+ const WORKTREE_FETCH_MS = 3 * 60_000;
262
269
  let lastWorktreeSweep = 0;
270
+ let lastWorktreeFetch = 0;
263
271
  let sweepingWorktrees = false;
264
272
  const postWorktrees = async (reports) => {
265
273
  if (!reports.length) return;
@@ -297,6 +305,17 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
297
305
  lastWorktreeSweep = Date.now();
298
306
  void (async () => {
299
307
  try {
308
+ // Refresh the base before measuring, so "3 new on main" means what a
309
+ // person thinks it means. Throttled, best-effort, and never fatal: an
310
+ // offline machine reports the counts it can still compute.
311
+ if (Date.now() - lastWorktreeFetch >= WORKTREE_FETCH_MS) {
312
+ lastWorktreeFetch = Date.now();
313
+ try {
314
+ git(['fetch', 'origin', '--quiet'], repoRoot);
315
+ } catch {
316
+ /* offline, or no remote — the numbers just age */
317
+ }
318
+ }
300
319
  const reports = [];
301
320
  for (const id of activeIds.slice(0, 20)) {
302
321
  const r = sessionWorktreeReport(id);
@@ -44,8 +44,10 @@ function countLines(buf) {
44
44
  /**
45
45
  * @param {string} wt the worktree directory
46
46
  * @param {string} baseRef the project's base ref (e.g. `origin/main`)
47
- * @returns {null | {branch:string, path:string, ahead:number, dirty:boolean,
48
- * additions:number, deletions:number, fileCount:number, truncated:number,
47
+ * @returns {null | {branch:string, path:string, ahead:number, behind:number,
48
+ * baseLabel:string, baseCommits:{sha:string, subject:string, author:string}[],
49
+ * dirty:boolean, additions:number, deletions:number, fileCount:number,
50
+ * truncated:number,
49
51
  * files:{path:string, added:number, deleted:number, binary?:boolean}[]}}
50
52
  */
51
53
  export function worktreeDiff(wt, baseRef) {
@@ -121,6 +123,32 @@ export function worktreeDiff(wt, baseRef) {
121
123
  } catch {
122
124
  /* leave at 0 */
123
125
  }
126
+ // WHAT LANDED WHILE YOU WERE WORKING. Not the branch's own history — the
127
+ // commits on BASE that this worktree doesn't have, which is the thing a
128
+ // person cannot see from inside their own session and the reason they end up
129
+ // rebasing onto a surprise. Freshness is the caller's job: these are only as
130
+ // current as the last fetch (reportWorktrees throttles one).
131
+ let behind = 0;
132
+ const baseCommits = [];
133
+ try {
134
+ behind = Number(git(['rev-list', '--count', `HEAD..${baseRef}`], wt)) || 0;
135
+ if (behind > 0) {
136
+ // %x1f is the unit separator — a subject can contain anything a person
137
+ // can type, tabs and pipes included, so the delimiter must be one that
138
+ // cannot appear in it.
139
+ const raw = git(
140
+ ['log', '-n', '3', '--format=%h%x1f%s%x1f%an', `HEAD..${baseRef}`],
141
+ wt
142
+ );
143
+ for (const line of raw.split('\n')) {
144
+ if (!line.trim()) continue;
145
+ const [sha, subject, author] = line.split('\x1f');
146
+ if (sha) baseCommits.push({ sha, subject: subject ?? '', author: author ?? '' });
147
+ }
148
+ }
149
+ } catch {
150
+ /* an unfetched or missing base — say nothing rather than "you're current" */
151
+ }
124
152
  let dirty = false;
125
153
  try {
126
154
  dirty = git(['status', '--porcelain'], wt) !== '';
@@ -137,6 +165,9 @@ export function worktreeDiff(wt, baseRef) {
137
165
  branch,
138
166
  path: wt,
139
167
  ahead,
168
+ behind,
169
+ baseLabel: String(baseRef).replace(/^origin\//, ''),
170
+ baseCommits,
140
171
  dirty,
141
172
  additions,
142
173
  deletions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.48.0",
3
+ "version": "0.48.1",
4
4
  "description": "Run your own coding CLIs as headless build agents for Flowviant — Claude Code or Codex, on your own credentials. Claims dispatched work, opens PRs, captures review evidence, and routes questions back to you.",
5
5
  "type": "module",
6
6
  "bin": {