@ghl-ai/aw 0.1.36-beta.86 → 0.1.36-beta.88

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/init.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  // Uses core.hooksPath (git-lfs pattern) for system-wide hook interception.
5
5
  // Uses IDE tasks for auto-pull on workspace open.
6
6
 
7
- import { existsSync, writeFileSync, symlinkSync, lstatSync, readdirSync, readFileSync, rmSync, realpathSync, appendFileSync } from 'node:fs';
7
+ import { existsSync, mkdirSync, writeFileSync, symlinkSync, lstatSync, readdirSync, readFileSync, rmSync, realpathSync, appendFileSync } from 'node:fs';
8
8
  import { execSync } from 'node:child_process';
9
9
  import { join, dirname, sep } from 'node:path';
10
10
  import { homedir } from 'node:os';
@@ -103,6 +103,29 @@ function installIdeTasks() {
103
103
  }
104
104
  }
105
105
 
106
+ /**
107
+ * Write .vscode/settings.json so Cursor/VS Code always treats .aw/ as a git
108
+ * repository — even on first load before the symlink existed at scan time.
109
+ * Without this, the git panel is blank until the user reloads the IDE.
110
+ */
111
+ function installVscodeGitSettings(projectDir) {
112
+ if (!projectDir || projectDir === HOME) return;
113
+ try {
114
+ const vscodeDir = join(projectDir, '.vscode');
115
+ mkdirSync(vscodeDir, { recursive: true });
116
+ const settingsPath = join(vscodeDir, 'settings.json');
117
+ let settings = {};
118
+ if (existsSync(settingsPath)) {
119
+ try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch { /* corrupted — overwrite */ }
120
+ }
121
+ const repos = settings['git.additionalRepositories'] || [];
122
+ if (!repos.includes('.aw')) {
123
+ settings['git.additionalRepositories'] = [...repos, '.aw'];
124
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
125
+ }
126
+ } catch { /* best effort */ }
127
+ }
128
+
106
129
  const ALLOWED_NAMESPACES = ['platform', 'revex', 'mobile', 'commerce', 'leadgen', 'crm', 'marketplace', 'ai'];
107
130
 
108
131
  export async function initCommand(args) {
@@ -272,6 +295,7 @@ export async function initCommand(args) {
272
295
  if (!silent) fmt.logStep('Linked current project');
273
296
  } catch { /* best effort */ }
274
297
  }
298
+ installVscodeGitSettings(cwd !== HOME ? cwd : null);
275
299
 
276
300
  // Wire ~/.claude/.cursor/.codex to the project's registry when in a project,
277
301
  // so edits to project/.aw/.aw_registry/ are instantly visible in global IDE dirs.
@@ -398,6 +422,7 @@ export async function initCommand(args) {
398
422
  fmt.logStep('Linked current project');
399
423
  } catch { /* best effort */ }
400
424
  }
425
+ installVscodeGitSettings(cwd !== HOME ? cwd : null);
401
426
 
402
427
  // Step 5: Wire ~/.claude/.cursor/.codex to the project's registry when in a project,
403
428
  // so edits to project/.aw/.aw_registry/ are instantly visible in global IDE dirs.
package/git.mjs CHANGED
@@ -231,32 +231,74 @@ export function removeFromSparseCheckout(awHome, removePaths) {
231
231
  */
232
232
  export async function fetchAndMerge(awHome, { silent = true } = {}) {
233
233
  // ── 1. Branch guard ──────────────────────────────────────────────────────
234
- // If ~/.aw is on the wrong branch (e.g. initPersistentClone cloned but the
235
- // `git checkout chore/stream-registry` step failed, leaving HEAD on main),
236
- // stash any local changes and switch back before doing anything.
234
+ // If ~/.aw drifted off REGISTRY_BASE_BRANCH (e.g. partial init left it on
235
+ // `main`), repair the sparse checkout and switch back then RETURN EARLY.
236
+ // We never run fetch/rebase on a wrong-branch run: a --no-edit merge or a
237
+ // rebase between unrelated histories can disable sparse checkout and wipe
238
+ // the working tree. The NEXT aw init call (hook/IDE/user) will do the sync.
237
239
  try {
238
240
  const { stdout: branchOut } = await exec(
239
241
  `git -C "${awHome}" rev-parse --abbrev-ref HEAD`,
240
242
  );
241
243
  const currentBranch = branchOut.trim();
244
+
242
245
  if (currentBranch !== REGISTRY_BASE_BRANCH && currentBranch !== 'HEAD') {
243
- let stashed = false;
246
+ // Re-enable sparse checkout before switching branches.
247
+ // If sparse-checkout was disabled (e.g. by an old --no-edit merge),
248
+ // `init --no-cone` restores it from the existing sparse-checkout file.
249
+ // If the file is also empty/gone we set a safe fallback covering the
250
+ // whole registry so the checkout materialises *something*.
244
251
  try {
245
- const { stdout: stashOut } = await exec(
246
- `git -C "${awHome}" stash push --include-untracked -m "aw: branch-guard stash"`,
252
+ await exec(`git -C "${awHome}" sparse-checkout init --no-cone`);
253
+ const { stdout: listOut } = await exec(
254
+ `git -C "${awHome}" sparse-checkout list`,
247
255
  );
248
- stashed = !stashOut.includes('No local changes to save');
249
- } catch { /* best effort */ }
256
+ if (!listOut.trim()) {
257
+ // Paths were cleared restore a safe default (full registry tree)
258
+ await exec(
259
+ `git -C "${awHome}" sparse-checkout set "${REGISTRY_DIR}/"`,
260
+ );
261
+ }
262
+ } catch {
263
+ // Completely broken sparse config — force a safe default
264
+ try {
265
+ await exec(`git -C "${awHome}" sparse-checkout init --no-cone`);
266
+ await exec(
267
+ `git -C "${awHome}" sparse-checkout set "${REGISTRY_DIR}/"`,
268
+ );
269
+ } catch { /* best effort */ }
270
+ }
271
+
272
+ // Switch to the correct branch; this materialises files via lazy fetch.
250
273
  try {
251
274
  await exec(`git -C "${awHome}" checkout ${REGISTRY_BASE_BRANCH}`);
252
- } catch { /* if checkout fails, proceed — fetch will still work */ }
253
- if (stashed) {
254
- try { await exec(`git -C "${awHome}" stash pop`); } catch { /* best effort */ }
275
+ } catch {
276
+ // checkout failed — repo is in a bad state we cannot repair here.
277
+ // Return without touching anything; the user can run `aw init` to
278
+ // trigger a full re-clone via initPersistentClone.
279
+ return { updated: false, conflicts: [] };
255
280
  }
281
+
282
+ // Branch repaired. Do NOT continue to fetch/rebase on this same run —
283
+ // that combination is what caused the original wipe.
284
+ return { updated: false, conflicts: [] };
256
285
  }
257
- } catch { /* if branch detection fails, proceed */ }
286
+ } catch { /* branch detection failed proceed with normal sync */ }
287
+
288
+ // ── 2. Repair sparse checkout if disabled on correct branch ──────────────
289
+ // Guard: if something disabled sparse checkout while on the right branch
290
+ // (should be rare), re-enable before fetch so the working tree stays intact.
291
+ try {
292
+ await exec(`git -C "${awHome}" sparse-checkout list`);
293
+ } catch {
294
+ try {
295
+ await exec(`git -C "${awHome}" sparse-checkout init --no-cone`);
296
+ // Re-checkout to materialise files according to restored sparse config
297
+ await exec(`git -C "${awHome}" checkout ${REGISTRY_BASE_BRANCH}`);
298
+ } catch { /* best effort */ }
299
+ }
258
300
 
259
- // ── 2. Fetch ──────────────────────────────────────────────────────────────
301
+ // ── 3. Fetch ──────────────────────────────────────────────────────────────
260
302
  try {
261
303
  await exec(`git -C "${awHome}" fetch origin ${REGISTRY_BASE_BRANCH}`);
262
304
  } catch (e) {
@@ -266,7 +308,7 @@ export async function fetchAndMerge(awHome, { silent = true } = {}) {
266
308
  let updated = false;
267
309
  const conflicts = [];
268
310
 
269
- // ── 3. Fast-forward (clean case — no local commits ahead of remote) ──────
311
+ // ── 4. Fast-forward (clean case — no local commits ahead of remote) ──────
270
312
  try {
271
313
  const { stdout } = await exec(
272
314
  `git -C "${awHome}" merge origin/${REGISTRY_BASE_BRANCH} --ff-only`,
@@ -275,7 +317,7 @@ export async function fetchAndMerge(awHome, { silent = true } = {}) {
275
317
  return { updated, conflicts };
276
318
  } catch { /* ff-only failed — local commits exist, fall through to rebase */ }
277
319
 
278
- // ── 4. Rebase local commits onto remote ───────────────────────────────────
320
+ // ── 5. Rebase local commits onto remote ───────────────────────────────────
279
321
  // Rebase keeps history linear and never disables sparse checkout.
280
322
  try {
281
323
  await exec(`git -C "${awHome}" rebase origin/${REGISTRY_BASE_BRANCH}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.36-beta.86",
3
+ "version": "0.1.36-beta.88",
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",