@ghl-ai/aw 0.1.36-beta.92 → 0.1.36-beta.94

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/git.mjs +60 -22
  2. package/package.json +1 -1
package/git.mjs CHANGED
@@ -230,19 +230,60 @@ export function removeFromSparseCheckout(awHome, removePaths) {
230
230
  * @param {{ silent?: boolean }} opts
231
231
  */
232
232
  export async function fetchAndMerge(awHome, { silent = true } = {}) {
233
- // Design: always rebase the CURRENT branch (whatever it is) onto
234
- // origin/REGISTRY_BASE_BRANCH. We never switch branches here.
233
+ // ── 1. Branch classification ──────────────────────────────────────────────
234
+ // There are three possible states for ~/.aw:
235
235
  //
236
- // - On REGISTRY_BASE_BRANCH: fast-forward (normal case)
237
- // - On a push branch (upload/, sync/, remove/...): rebase so remote
238
- // changes land under our local commits stacked PR model
239
- // - On any other branch (e.g. main from a failed init): fast-forward
240
- // or rebase brings it up to date without wiping sparse checkout
241
- //
242
- // The old wipe bug was caused by `--no-edit` merge between unrelated
243
- // histories, which disabled sparse checkout. Rebase never does this.
236
+ // A. REGISTRY_BASE_BRANCH — normal, fast-forward sync
237
+ // B. Push branch (upload/ sync/ remove/) — intentional, rebase remote
238
+ // changes under our local commits (stacked PR model)
239
+ // C. Anything else (e.g. `main` from a failed initPersistentClone)
240
+ // drift/broken state; re-enable sparse checkout and switch back to
241
+ // REGISTRY_BASE_BRANCH before syncing. We must NOT rebase here:
242
+ // `main` and REGISTRY_BASE_BRANCH are unrelated trees in platform-docs
243
+ // and rebasing between them on a blob:none clone wipes the working tree.
244
+
245
+ const PUSH_PREFIXES = ['upload/', 'remove/', 'sync/'];
246
+
247
+ let currentBranch = REGISTRY_BASE_BRANCH; // assume good unless detection succeeds
248
+ try {
249
+ const { stdout } = await exec(`git -C "${awHome}" rev-parse --abbrev-ref HEAD`);
250
+ currentBranch = stdout.trim();
251
+ } catch { /* proceed with assumption */ }
252
+
253
+ const isPushBranch = PUSH_PREFIXES.some(p => currentBranch.startsWith(p));
254
+ const isBaseBranch = currentBranch === REGISTRY_BASE_BRANCH;
255
+ const isDrift = !isBaseBranch && !isPushBranch && currentBranch !== 'HEAD';
256
+
257
+ if (isDrift) {
258
+ // Re-enable sparse checkout (may have been disabled by an old --no-edit merge).
259
+ // After `git sparse-checkout init --no-cone`, git resets paths to `/*` + `!/*/`
260
+ // (root files only, no subdirectories) — which excludes .aw_registry/.
261
+ // We must explicitly set the registry path so files materialise on checkout.
262
+ try {
263
+ await exec(`git -C "${awHome}" sparse-checkout init --no-cone`);
264
+ const { stdout: listOut } = await exec(`git -C "${awHome}" sparse-checkout list`);
265
+ const hasPaths = listOut.trim().split('\n').filter(Boolean).some(p => p.includes(REGISTRY_DIR));
266
+ if (!hasPaths) {
267
+ await exec(`git -C "${awHome}" sparse-checkout set "${REGISTRY_DIR}/"`);
268
+ }
269
+ } catch {
270
+ try {
271
+ await exec(`git -C "${awHome}" sparse-checkout init --no-cone`);
272
+ await exec(`git -C "${awHome}" sparse-checkout set "${REGISTRY_DIR}/"`);
273
+ } catch { /* best effort */ }
274
+ }
244
275
 
245
- // ── 1. Fetch ──────────────────────────────────────────────────────────────
276
+ // Switch to the correct branch — materialises files via lazy blob fetch
277
+ try {
278
+ await exec(`git -C "${awHome}" checkout ${REGISTRY_BASE_BRANCH}`);
279
+ } catch {
280
+ return { updated: false, conflicts: [] }; // can't repair; leave for full re-init
281
+ }
282
+
283
+ // Drift repaired. Continue below to also fetch + fast-forward this run.
284
+ }
285
+
286
+ // ── 2. Fetch ──────────────────────────────────────────────────────────────
246
287
  try {
247
288
  await exec(`git -C "${awHome}" fetch origin ${REGISTRY_BASE_BRANCH}`);
248
289
  } catch (e) {
@@ -252,7 +293,7 @@ export async function fetchAndMerge(awHome, { silent = true } = {}) {
252
293
  let updated = false;
253
294
  const conflicts = [];
254
295
 
255
- // ── 2. Fast-forward (no local commits ahead of remote) ───────────────────
296
+ // ── 3. Fast-forward (no local commits ahead of remote) ───────────────────
256
297
  try {
257
298
  const { stdout } = await exec(
258
299
  `git -C "${awHome}" merge origin/${REGISTRY_BASE_BRANCH} --ff-only`,
@@ -261,27 +302,24 @@ export async function fetchAndMerge(awHome, { silent = true } = {}) {
261
302
  return { updated, conflicts };
262
303
  } catch { /* local commits exist — fall through to rebase */ }
263
304
 
264
- // ── 3. Rebase current branch onto remote REGISTRY_BASE_BRANCH ────────────
265
- // Keeps history linear. Works for push branches (stacks our commits on
266
- // top of latest remote) and never disables sparse checkout.
305
+ // ── 4. Rebase current branch onto remote REGISTRY_BASE_BRANCH ────────────
306
+ // For push branches: stacks our local commits on top of latest remote.
307
+ // For base branch: only reached if local commits exist (unusual).
308
+ // Never uses --no-edit merge — that disables sparse checkout on blob:none.
267
309
  try {
268
310
  await exec(`git -C "${awHome}" rebase origin/${REGISTRY_BASE_BRANCH}`);
269
311
  updated = true;
270
312
  } catch {
271
- // Collect conflicting files
272
313
  try {
273
- const { stdout } = await exec(
274
- `git -C "${awHome}" diff --name-only --diff-filter=U`,
275
- );
314
+ const { stdout } = await exec(`git -C "${awHome}" diff --name-only --diff-filter=U`);
276
315
  conflicts.push(...stdout.trim().split('\n').filter(Boolean));
277
316
  } catch { /* best effort */ }
278
317
 
279
318
  if (silent) {
280
- // Background (hook/IDE): abort so repo stays usable.
281
- // Conflicts will surface on next explicit `aw init`.
319
+ // Background: abort so repo stays usable; surface on next explicit aw init
282
320
  try { await exec(`git -C "${awHome}" rebase --abort`); } catch { /* best effort */ }
283
321
  }
284
- // Interactive: leave conflict markers for the user to resolve in editor.
322
+ // Interactive: leave conflict markers for the user to resolve
285
323
  }
286
324
 
287
325
  return { updated, conflicts };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.36-beta.92",
3
+ "version": "0.1.36-beta.94",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": "bin.js",