@ghl-ai/aw 0.1.83 → 0.1.84

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/commands/push.mjs +36 -24
  2. package/package.json +1 -1
package/commands/push.mjs CHANGED
@@ -416,30 +416,28 @@ function resolveAwDocsPublishConfig(projectRoot) {
416
416
  }
417
417
 
418
418
  async function removeTrackedAwSymlink(cloneDir) {
419
+ const awPath = join(cloneDir, '.aw');
420
+ // Only ever touch a STRAY on-disk `.aw` symlink (e.g. one the IDE/worktree
421
+ // `aw link` hook drops into the cache clone). Remove it from the WORKTREE
422
+ // ONLY, with a filesystem unlink — never a git command.
423
+ //
424
+ // The docs repo may track a real `.aw/` tree (ghl-aw-docs tracks
425
+ // `.aw/.aw_rules/**`). A `git rm .aw` here would stage a deletion of that
426
+ // committed content and push it away (this is exactly what corrupted
427
+ // master-sync). A plain unlink cannot stage anything, so it can never delete
428
+ // the committed tree — and since `.aw` is kept out of the sparse cone, the
429
+ // tracked tree is SKIP_WORKTREE and unaffected by removing the disk symlink.
419
430
  try {
420
- if (!lstatSync(join(cloneDir, '.aw')).isSymbolicLink()) return false;
431
+ if (!lstatSync(awPath).isSymbolicLink()) return false;
421
432
  } catch {
422
433
  return false;
423
434
  }
424
-
425
435
  try {
426
- await execFile('git', ['ls-files', '--error-unmatch', '.aw'], {
427
- cwd: cloneDir,
428
- encoding: 'utf8',
429
- });
436
+ rmSync(awPath, { force: true });
437
+ return true;
430
438
  } catch {
431
439
  return false;
432
440
  }
433
-
434
- // `.aw` is always kept inside the sparse-checkout cone (see AW_DOCS_CACHE_PATH
435
- // in ensureAwDocsRepoClone) so this remove works even on blobless/sparse
436
- // clones. Without that, git rejects the rm with "outside sparse-checkout
437
- // definition" against repos that track a real .aw/ tree (e.g. ghl-aw-docs).
438
- await execFile('git', ['rm', '-f', '.aw'], {
439
- cwd: cloneDir,
440
- encoding: 'utf8',
441
- });
442
- return true;
443
441
  }
444
442
 
445
443
  function parseStatusPaths(status) {
@@ -484,9 +482,24 @@ async function selfHealManagedAwCacheDirtiness(cloneDir, status) {
484
482
  });
485
483
 
486
484
  if (await hasTrackedAwCachePaths(cloneDir)) {
485
+ // Restore a dirty tracked `.aw` from HEAD. In a full clone this cleans the
486
+ // worktree + index (the original behaviour). Best-effort (.catch): when `.aw`
487
+ // is sparse-excluded (out of cone) this pathspec can't match and the call is
488
+ // a no-op — but a sparse-excluded `.aw` is SKIP_WORKTREE and won't be dirty
489
+ // in the first place. This can only restore/un-stage, never delete tracked
490
+ // content.
487
491
  await execFile('git', ['restore', '--staged', '--worktree', '--', '.aw'], {
488
492
  cwd: cloneDir,
489
493
  encoding: 'utf8',
494
+ }).catch((e) => {
495
+ const msg = (e?.stderr || e?.message || '').toString();
496
+ // Expected + ignorable: when `.aw` is sparse-excluded (out of cone) the
497
+ // pathspec cannot match, so the restore is simply a no-op.
498
+ if (/did not match any file|outside .*sparse-checkout/i.test(msg)) return;
499
+ // Anything else (permissions, index corruption, ...) is unexpected —
500
+ // surface it so a future `.aw` cache issue is diagnosable, but don't
501
+ // block the publish on a best-effort cleanup step.
502
+ fmt.logWarn(`AW docs cache: could not restore .aw (${msg.trim()})`);
490
503
  });
491
504
  }
492
505
 
@@ -583,12 +596,11 @@ async function getProjectSourceRepo(projectRoot) {
583
596
  const AW_DOCS_GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: '0', GCM_INTERACTIVE: 'never' };
584
597
  const AW_DOCS_CLONE_TIMEOUT_MS = Number(process.env.AW_DOCS_GIT_TIMEOUT_MS) || 180000;
585
598
  const AW_DOCS_NET_TIMEOUT_MS = Number(process.env.AW_DOCS_GIT_TIMEOUT_MS) || 120000;
586
- // `.aw` is always kept in the sparse-checkout cone. The docs repo may track a
587
- // real `.aw/` tree (e.g. ghl-aw-docs tracks `.aw/.aw_rules/**`), and the
588
- // cache-management code (removeTrackedAwSymlink, selfHeal*) runs write git ops
589
- // on `.aw`. Excluding it from the cone makes those ops fail with "outside
590
- // sparse-checkout definition" and breaks every publish against such a repo.
591
- const AW_DOCS_CACHE_PATH = '.aw';
599
+ // NOTE: `.aw` is intentionally NOT added to the sparse cone. The docs repo may
600
+ // track a real `.aw/` tree (ghl-aw-docs tracks `.aw/.aw_rules/**`); keeping it
601
+ // out of the cone leaves that tree SKIP_WORKTREE so no publish can materialise
602
+ // or (crucially) stage a deletion of it. Any stray on-disk `.aw` symlink is
603
+ // removed worktree-only via a filesystem unlink (see removeTrackedAwSymlink).
592
604
 
593
605
  async function awDocsSparseEnabled(cloneDir) {
594
606
  try {
@@ -625,7 +637,7 @@ async function ensureAwDocsRepoClone(home, publishConfig, sparseIncludePaths = [
625
637
  timeout: AW_DOCS_CLONE_TIMEOUT_MS,
626
638
  });
627
639
  if (wantSparse) {
628
- await execFile('git', ['sparse-checkout', 'set', '--cone', ...sparseIncludePaths, AW_DOCS_CACHE_PATH], {
640
+ await execFile('git', ['sparse-checkout', 'set', '--cone', ...sparseIncludePaths], {
629
641
  cwd: cloneDir,
630
642
  encoding: 'utf8',
631
643
  }).catch(() => { /* sparse unsupported → full checkout below */ });
@@ -661,7 +673,7 @@ async function ensureAwDocsRepoClone(home, publishConfig, sparseIncludePaths = [
661
673
  // clones that already use sparse-checkout (i.e. the blobless clones we
662
674
  // created) — legacy full clones are left untouched so there is no regression.
663
675
  if (wantSparse && await awDocsSparseEnabled(cloneDir)) {
664
- await execFile('git', ['sparse-checkout', 'add', ...sparseIncludePaths, AW_DOCS_CACHE_PATH], {
676
+ await execFile('git', ['sparse-checkout', 'add', ...sparseIncludePaths], {
665
677
  cwd: cloneDir,
666
678
  encoding: 'utf8',
667
679
  }).catch(() => { /* best effort — checkout below still works */ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.83",
3
+ "version": "0.1.84",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {