@limina-labs/momentum 0.41.0 → 0.41.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.
@@ -116,23 +116,83 @@ function openBlockers(backlogBody) {
116
116
  return out;
117
117
  }
118
118
 
119
- /** Open lanes from a member's lane registry, when one exists. */
119
+ /**
120
+ * Statuses that mean a lane is still in flight. `landed` and `closed` lanes are
121
+ * spent — surfacing them is how BUG-029 reported 30 "open" lanes on a repo whose
122
+ * true state was 29 closed + 1 landed.
123
+ */
124
+ const IN_FLIGHT = new Set(['open', 'done']);
125
+
126
+ /**
127
+ * Resolve `<git-common-dir>/momentum/lanes` without running git.
128
+ *
129
+ * `.git` is a directory in a normal checkout, but a FILE in a linked worktree
130
+ * (`gitdir: /abs/path/.git/worktrees/<name>`), whose sibling `commondir` points
131
+ * back at the shared dir. Lane state lives under the COMMON dir, so a worktree
132
+ * that did not follow the pointer would see no lanes at all — and Rule 15 lane
133
+ * work happens in worktrees, which is exactly where this has to be right.
134
+ */
135
+ function laneAnchor(repoDir) {
136
+ const dotGit = path.join(repoDir, '.git');
137
+ let stat;
138
+ try { stat = fs.statSync(dotGit); } catch (_e) { return null; }
139
+
140
+ if (stat.isDirectory()) return path.join(dotGit, 'momentum', 'lanes');
141
+
142
+ const pointer = readIf(dotGit);
143
+ if (!pointer) return null;
144
+ const m = pointer.match(/^gitdir:\s*(.+)$/m);
145
+ if (!m) return null;
146
+ const gitdir = path.resolve(repoDir, m[1].trim());
147
+ const commondir = readIf(path.join(gitdir, 'commondir'));
148
+ const common = commondir
149
+ ? path.resolve(gitdir, commondir.trim())
150
+ : gitdir;
151
+ return path.join(common, 'momentum', 'lanes');
152
+ }
153
+
154
+ /**
155
+ * In-flight lanes for a member.
156
+ *
157
+ * The registry stores an array of lane **id strings**; per-lane detail lives in
158
+ * `<anchor>/<id>/manifest.json`. Reading the ids as objects was BUG-029 —
159
+ * every entry became `{status: undefined}`, and `undefined !== 'closed'` let the
160
+ * whole history through.
161
+ */
120
162
  function openLanes(repoDir) {
121
- const candidates = [
122
- path.join(repoDir, '.git', 'momentum', 'lanes', 'registry.json'),
123
- path.join(repoDir, '.momentum', 'lanes', 'registry.json'),
124
- ];
125
- for (const file of candidates) {
126
- const body = readIf(file);
163
+ const anchors = [laneAnchor(repoDir), path.join(repoDir, '.momentum', 'lanes')]
164
+ .filter(Boolean);
165
+
166
+ for (const anchor of anchors) {
167
+ const body = readIf(path.join(anchor, 'registry.json'));
127
168
  if (!body) continue;
169
+
170
+ let ids;
128
171
  try {
129
172
  const reg = JSON.parse(body);
130
- return (reg.lanes || [])
131
- .filter((l) => l && l.status !== 'closed' && l.status !== 'landed')
132
- .map((l) => ({ id: l.id, branch: l.branch, status: l.status }));
173
+ ids = Array.isArray(reg.lanes) ? reg.lanes : [];
133
174
  } catch (_e) {
134
175
  return [];
135
176
  }
177
+
178
+ const out = [];
179
+ for (const entry of ids) {
180
+ // Tolerate both shapes: the id-string form momentum writes today, and an
181
+ // inlined object, so a future registry format change degrades instead of
182
+ // silently reporting garbage again.
183
+ const id = typeof entry === 'string' ? entry : (entry && entry.id);
184
+ if (!id) continue;
185
+
186
+ let lane = (entry && typeof entry === 'object') ? entry : null;
187
+ if (!lane || !lane.status) {
188
+ const mf = readIf(path.join(anchor, id, 'manifest.json'));
189
+ if (!mf) continue;
190
+ try { lane = JSON.parse(mf); } catch (_e) { continue; }
191
+ }
192
+ if (!IN_FLIGHT.has(lane.status)) continue;
193
+ out.push({ id, branch: lane.branch || null, status: lane.status });
194
+ }
195
+ return out;
136
196
  }
137
197
  return [];
138
198
  }
@@ -228,6 +288,8 @@ function memberBrief(summary) {
228
288
 
229
289
  module.exports = {
230
290
  MAX_ITEMS,
291
+ IN_FLIGHT,
292
+ laneAnchor,
231
293
  MAX_TITLE,
232
294
  condense,
233
295
  activePhases,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limina-labs/momentum",
3
- "version": "0.41.0",
3
+ "version": "0.41.1",
4
4
  "description": "Agent-agnostic, specs-driven development framework — single project or multi-project ecosystem",
5
5
  "homepage": "https://trymomentum.github.io",
6
6
  "bin": {