@ghl-ai/aw 0.1.36-beta.10 → 0.1.36-beta.12

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/commands/pull.mjs CHANGED
@@ -7,7 +7,7 @@ import { execSync } from 'node:child_process';
7
7
  import * as config from '../config.mjs';
8
8
  import * as fmt from '../fmt.mjs';
9
9
  import { chalk } from '../fmt.mjs';
10
- import { fetchAndMerge, addToSparseCheckout, isValidClone } from '../git.mjs';
10
+ import { fetchAndMerge, addToSparseCheckout, isValidClone, isWorktree, rebaseOntoOriginMain } from '../git.mjs';
11
11
  import { REGISTRY_DIR, REGISTRY_REPO, DOCS_SOURCE_DIR } from '../constants.mjs';
12
12
  import { linkWorkspace } from '../link.mjs';
13
13
  import { generateCommands, copyInstructions } from '../integrate.mjs';
@@ -73,6 +73,18 @@ export async function pullCommand(args) {
73
73
  log.logWarn(`Conflicts in: ${fetchResult.conflicts.join(', ')}`);
74
74
  }
75
75
 
76
+ // Always rebase project worktree's current branch onto origin/main
77
+ const localAw = join(cwd, '.aw');
78
+ if (cwd !== HOME && isWorktree(localAw)) {
79
+ try {
80
+ rebaseOntoOriginMain(localAw);
81
+ if (!silent) log.logStep('Local branch rebased onto latest main');
82
+ } catch (e) {
83
+ const msg = e.message?.split('\n').find(l => l.trim()) ?? e.message;
84
+ if (!silent) log.logWarn(`Rebase skipped: ${msg}`);
85
+ }
86
+ }
87
+
76
88
  // Sync content/ → platform/docs/
77
89
  syncDocs(AW_HOME, GLOBAL_AW_DIR);
78
90
 
package/commands/push.mjs CHANGED
@@ -12,11 +12,8 @@ import { walkRegistryTree } from '../registry.mjs';
12
12
  import {
13
13
  detectChanges,
14
14
  getStagedFiles,
15
- commitToCurrentBranch,
16
- updatePushBranch,
17
15
  createPushBranch,
18
16
  checkoutMain,
19
- getCurrentBranch,
20
17
  isValidClone,
21
18
  isWorktree,
22
19
  getLocalRegistryDir,
@@ -26,19 +23,7 @@ const PUSHABLE_TYPES = ['agents', 'skills', 'commands', 'evals'];
26
23
 
27
24
  // ── PR content generation ────────────────────────────────────────────
28
25
 
29
- // Persistent push branch for a worktree: push/<slug> derived from worktree/<slug>.
30
- // Same branch is reused across pushes — force-pushed each time, one open PR.
31
- function getPushBranchName(awHome) {
32
- const branch = getCurrentBranch(awHome);
33
- if (branch?.startsWith('worktree/')) {
34
- return `push/${branch.slice('worktree/'.length)}`;
35
- }
36
- // Fallback for non-worktree or detached HEAD
37
- const shortId = Date.now().toString(36).slice(-5);
38
- return `push/batch-${shortId}`;
39
- }
40
-
41
- // Timestamped branch for the global-clone (non-worktree) flow.
26
+ // Auto-generate a branch name from the files being pushed.
42
27
  function generateBranchName(files) {
43
28
  const shortId = Date.now().toString(36).slice(-5);
44
29
  const namespaces = [...new Set(files.map(f => f.namespace))];
@@ -265,6 +250,7 @@ function createOrUpdatePR(awHome, branch, prTitle, prBody) {
265
250
  const url = execFileSync('gh', [
266
251
  'pr', 'create',
267
252
  '--base', REGISTRY_BASE_BRANCH,
253
+ '--head', branch,
268
254
  '--title', prTitle,
269
255
  '--body', prBody,
270
256
  ], { cwd: awHome, encoding: 'utf8' }).trim();
@@ -280,10 +266,9 @@ function createOrUpdatePR(awHome, branch, prTitle, prBody) {
280
266
  // ── Main push pipeline ────────────────────────────────────────────────
281
267
 
282
268
  // worktreeFlow: true when awHome is a project worktree (not the global clone).
283
- // - Commits to the current worktree branch (changes stay visible locally).
284
- // - Force-updates persistent push/<slug> branch no branch switching.
285
- // - Reuses same PR across pushes (force-push updates it).
286
- // Global flow (worktreeFlow=false): legacy branch-per-push on global clone.
269
+ // - Always creates a new branch from current state, commits, pushes, stays there.
270
+ // - Every aw push = one new branch + one new PR. No force-push, no reuse.
271
+ // Global flow (worktreeFlow=false): same but returns to main after push.
287
272
  function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false) {
288
273
  const added = files.filter(f => !f.deleted);
289
274
  const deleted = files.filter(f => f.deleted);
@@ -333,14 +318,13 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
333
318
  let finalBranch;
334
319
  try {
335
320
  if (worktreeFlow) {
336
- // ── Worktree flow ──────────────────────────────────────────────────
337
- // 1. Commit to the current worktree branch changes stay in local history.
338
- commitToCurrentBranch(awHome, pathsToStage, commitMsg, preStaged);
339
- // 2. Force-update push/<slug> to HEAD (no branch switch), then force-push.
340
- finalBranch = updatePushBranch(awHome, getPushBranchName(awHome));
321
+ // ── Worktree flow ─────────────────────────────────────────────────
322
+ // Always create a new branch from current state, commit, push, stay there.
323
+ // Every push = one new branch + one new PR. No force-push, no branch reuse.
324
+ finalBranch = createPushBranch(awHome, generateBranchName(files), pathsToStage, commitMsg, preStaged);
341
325
  } else {
342
- // ── Global clone flow ──────────────────────────────────────────────
343
- // Checkout main → create timestamped branch → commit → push → return to main.
326
+ // ── Global clone flow ─────────────────────────────────────────────
327
+ // Checkout main → create branch → commit → push → return to main.
344
328
  if (!preStaged) {
345
329
  try { checkoutMain(awHome); } catch (e) {
346
330
  s.stop(chalk.red('Push failed'));
@@ -367,7 +351,7 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
367
351
  fmt.logInfo(`New namespace${newNamespaces.length > 1 ? 's' : ''} ${chalk.cyan(newNamespaces.join(', '))} — CODEOWNERS entr${newNamespaces.length > 1 ? 'ies' : 'y'} added`);
368
352
  }
369
353
  if (worktreeFlow) {
370
- fmt.logInfo(chalk.dim('Changes committed locallyiterate freely, push again to update the PR'));
354
+ fmt.logInfo(chalk.dim(`On branch ${finalBranch}run aw push again to open a new PR`));
371
355
  }
372
356
  fmt.logSuccess(`PR: ${chalk.cyan(prUrl)}`);
373
357
  fmt.outro('Push complete');
@@ -391,7 +375,6 @@ export function pushCommand(args) {
391
375
  const registrySubDir = join(awHome, REGISTRY_DIR);
392
376
  const workspaceDir = getLocalRegistryDir(cwd, join(HOME, '.aw_registry'));
393
377
 
394
- // Save current worktree branch so we can restore it after push
395
378
  const worktreeFlow = hasWorktree;
396
379
 
397
380
  fmt.intro('aw push');
@@ -530,7 +513,7 @@ export function pushCommand(args) {
530
513
  slug,
531
514
  isDir,
532
515
  deleted: false,
533
- }], awHome, dryRun, worktreeBranch);
516
+ }], awHome, dryRun, worktreeFlow);
534
517
  }
535
518
 
536
519
  // ── Utilities ─────────────────────────────────────────────────────────
package/git.mjs CHANGED
@@ -404,6 +404,21 @@ export function checkoutMain(awHome) {
404
404
  }
405
405
  }
406
406
 
407
+ /**
408
+ * Rebase the current branch onto origin/main.
409
+ * Assumes `git fetch` has already been run (e.g. via fetchAndMerge).
410
+ * Throws if there are conflicts — caller should surface the message.
411
+ */
412
+ export function rebaseOntoOriginMain(awHome) {
413
+ try {
414
+ execSync(`git -C "${awHome}" rebase origin/${REGISTRY_BASE_BRANCH}`, { stdio: 'pipe' });
415
+ } catch (e) {
416
+ // Surface stderr so callers can show a meaningful message
417
+ const stderr = e.stderr?.toString().trim() || e.stdout?.toString().trim() || e.message;
418
+ throw new Error(stderr);
419
+ }
420
+ }
421
+
407
422
  // ── Project worktree operations ────────────────────────────────────────────────
408
423
 
409
424
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.36-beta.10",
3
+ "version": "0.1.36-beta.12",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {