@ghl-ai/aw 0.1.36-beta.73 → 0.1.36-beta.74
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 +10 -5
- package/commands/nuke.mjs +31 -16
- package/commands/status.mjs +39 -7
- package/git.mjs +13 -9
- package/package.json +1 -1
package/commands/init.mjs
CHANGED
|
@@ -235,10 +235,13 @@ export async function initCommand(args) {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
const isInsideAw = cwd.endsWith(`${sep}.aw`) || cwd.includes(`${sep}.aw${sep}`);
|
|
238
|
-
if (
|
|
238
|
+
// Only skip if already a valid symlink (new model). Old git worktrees must be migrated.
|
|
239
|
+
const awLink = join(cwd, '.aw');
|
|
240
|
+
const isAlreadySymlink = (() => { try { return lstatSync(awLink).isSymbolicLink() && existsSync(awLink); } catch { return false; } })();
|
|
241
|
+
if (cwd !== HOME && !isInsideAw && !isAlreadySymlink) {
|
|
239
242
|
try {
|
|
240
243
|
addProjectWorktree(AW_HOME, cwd);
|
|
241
|
-
if (!silent) fmt.logStep('Linked current project
|
|
244
|
+
if (!silent) fmt.logStep('Linked current project');
|
|
242
245
|
} catch { /* best effort */ }
|
|
243
246
|
}
|
|
244
247
|
|
|
@@ -356,12 +359,14 @@ export async function initCommand(args) {
|
|
|
356
359
|
} catch { /* not there, fine */ }
|
|
357
360
|
}
|
|
358
361
|
|
|
359
|
-
// Step 4: Link current project as a
|
|
362
|
+
// Step 4: Link current project as a symlink to ~/.aw (gives IDE git panel, shared across all workspaces)
|
|
360
363
|
const isInsideAw = cwd.endsWith(`${sep}.aw`) || cwd.includes(`${sep}.aw${sep}`);
|
|
361
|
-
|
|
364
|
+
const awLinkFresh = join(cwd, '.aw');
|
|
365
|
+
const isAlreadySymlinkFresh = (() => { try { return lstatSync(awLinkFresh).isSymbolicLink() && existsSync(awLinkFresh); } catch { return false; } })();
|
|
366
|
+
if (cwd !== HOME && !isInsideAw && !isAlreadySymlinkFresh) {
|
|
362
367
|
try {
|
|
363
368
|
addProjectWorktree(AW_HOME, cwd);
|
|
364
|
-
fmt.logStep('Linked current project
|
|
369
|
+
fmt.logStep('Linked current project');
|
|
365
370
|
} catch { /* best effort */ }
|
|
366
371
|
}
|
|
367
372
|
|
package/commands/nuke.mjs
CHANGED
|
@@ -260,30 +260,45 @@ export async function nukeCommand(args) {
|
|
|
260
260
|
try { if (existsSync(p)) rmSync(p, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
// Phase 4: git worktrees
|
|
263
|
+
// Phase 4: project .aw/ links — covers both old git worktrees and new symlinks
|
|
264
264
|
const AW_HOME = join(HOME, '.aw');
|
|
265
|
+
const wtSpinner = fmt.spinner();
|
|
266
|
+
wtSpinner.start('Removing project .aw/ links...');
|
|
267
|
+
let wtRemoved = 0;
|
|
268
|
+
|
|
269
|
+
// 4a. Remove old git worktrees (worktree/* branches in ~/.aw)
|
|
265
270
|
if (existsSync(AW_HOME)) {
|
|
266
271
|
try {
|
|
267
272
|
const worktrees = listProjectWorktrees(AW_HOME);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
for (const wt of worktrees) {
|
|
273
|
-
try {
|
|
274
|
-
await exec(`git -C "${AW_HOME}" worktree remove "${wt.path}" --force`);
|
|
275
|
-
wtRemoved++;
|
|
276
|
-
} catch {
|
|
277
|
-
try { rmSync(wt.path, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
278
|
-
wtRemoved++;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
try { await exec(`git -C "${AW_HOME}" worktree prune`); } catch { /* best effort */ }
|
|
282
|
-
wtSpinner.stop(`Removed ${wtRemoved} project worktree${wtRemoved > 1 ? 's' : ''}`);
|
|
273
|
+
for (const wt of worktrees) {
|
|
274
|
+
try { await exec(`git -C "${AW_HOME}" worktree remove "${wt.path}" --force`); } catch {}
|
|
275
|
+
try { rmSync(wt.path, { recursive: true, force: true }); } catch {}
|
|
276
|
+
wtRemoved++;
|
|
283
277
|
}
|
|
278
|
+
try { await exec(`git -C "${AW_HOME}" worktree prune`); } catch {}
|
|
284
279
|
} catch { /* best effort */ }
|
|
285
280
|
}
|
|
286
281
|
|
|
282
|
+
// 4b. Remove new-style .aw symlinks pointing to ~/.aw
|
|
283
|
+
// These are created by the symlink model and are NOT tracked by git worktree list.
|
|
284
|
+
try {
|
|
285
|
+
const { stdout: awLinks } = await exec(
|
|
286
|
+
`find "${HOME}" -maxdepth 5 -name ".aw" -type l 2>/dev/null || true`,
|
|
287
|
+
{ encoding: 'utf8', timeout: 30000 }
|
|
288
|
+
);
|
|
289
|
+
for (const linkPath of awLinks.trim().split('\n').filter(Boolean)) {
|
|
290
|
+
try {
|
|
291
|
+
const target = readlinkSync(linkPath);
|
|
292
|
+
if (target === AW_HOME || target.endsWith('/.aw')) {
|
|
293
|
+
unlinkSync(linkPath);
|
|
294
|
+
wtRemoved++;
|
|
295
|
+
}
|
|
296
|
+
} catch { /* best effort */ }
|
|
297
|
+
}
|
|
298
|
+
} catch { /* best effort */ }
|
|
299
|
+
|
|
300
|
+
wtSpinner.stop(`Removed ${wtRemoved} project .aw/ link${wtRemoved !== 1 ? 's' : ''}`);
|
|
301
|
+
|
|
287
302
|
// Phase 6: remove registry clone + docs
|
|
288
303
|
const cloneSpinner = fmt.spinner();
|
|
289
304
|
cloneSpinner.start('Removing registry clone...');
|
package/commands/status.mjs
CHANGED
|
@@ -89,22 +89,54 @@ export function statusCommand(args) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// ── Uncommitted local changes (on top of drift) ───────────────────────────
|
|
92
|
-
const
|
|
93
|
-
|
|
92
|
+
const rawLocal = detectChanges(AW_HOME, REGISTRY_DIR);
|
|
93
|
+
// Filter out internal config files that are managed by `aw init`, not the user
|
|
94
|
+
const skipFiles = new Set(['.sync-config.json']);
|
|
95
|
+
const filterConfig = arr => arr.filter(e => !skipFiles.has(e.registryPath));
|
|
96
|
+
const local = {
|
|
97
|
+
modified: filterConfig(rawLocal.modified),
|
|
98
|
+
added: filterConfig(rawLocal.added),
|
|
99
|
+
deleted: filterConfig(rawLocal.deleted),
|
|
100
|
+
untracked: filterConfig(rawLocal.untracked),
|
|
101
|
+
};
|
|
102
|
+
const localNew = [...local.added, ...local.untracked];
|
|
103
|
+
const localDirty = [...local.modified, ...localNew, ...local.deleted];
|
|
94
104
|
|
|
95
105
|
if (localDirty.length > 0) {
|
|
96
106
|
const localParts = [
|
|
97
107
|
(local.modified.length > 0) ? chalk.yellow(`${local.modified.length} modified`) : null,
|
|
98
|
-
(
|
|
108
|
+
(localNew.length > 0) ? chalk.green(`${localNew.length} new`) : null,
|
|
99
109
|
(local.deleted.length > 0) ? chalk.dim(`${local.deleted.length} deleted`) : null,
|
|
100
110
|
].filter(Boolean);
|
|
101
111
|
fmt.logWarn(`Uncommitted local changes: ${localParts.join(chalk.dim(' · '))} — run ${chalk.dim('aw push')} to commit & open PR`);
|
|
112
|
+
|
|
113
|
+
if (localNew.length > 0) {
|
|
114
|
+
fmt.note(
|
|
115
|
+
localNew.map(f => chalk.green(` + ${f.registryPath}`)).join('\n'),
|
|
116
|
+
chalk.green('New (uncommitted)')
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (local.modified.length > 0) {
|
|
120
|
+
fmt.note(
|
|
121
|
+
local.modified.map(f => chalk.yellow(` ~ ${f.registryPath}`)).join('\n'),
|
|
122
|
+
chalk.yellow('Modified (uncommitted)')
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (local.deleted.length > 0) {
|
|
126
|
+
fmt.note(
|
|
127
|
+
local.deleted.map(f => chalk.dim(` - ${f.registryPath}`)).join('\n'),
|
|
128
|
+
chalk.dim('Deleted (uncommitted)')
|
|
129
|
+
);
|
|
130
|
+
}
|
|
102
131
|
}
|
|
103
132
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const aheadStr =
|
|
107
|
-
|
|
133
|
+
const ahead = commitsAheadOfMain(AW_HOME);
|
|
134
|
+
if (ahead > 0) {
|
|
135
|
+
const aheadStr = chalk.yellow(`${ahead} commit${ahead !== 1 ? 's' : ''} ahead of main`);
|
|
136
|
+
const branchStr = isOnMain ? '' : ` on ${chalk.yellow(branch)}`;
|
|
137
|
+
fmt.logWarn(`${aheadStr}${branchStr} — run ${chalk.dim('aw push')} to open a PR or ${chalk.dim('aw pull')} to sync`);
|
|
138
|
+
} else if (!isOnMain) {
|
|
139
|
+
fmt.logWarn(`On branch ${chalk.yellow(branch)} — run ${chalk.dim('aw push')} to open a PR or ${chalk.dim('aw pull')} to sync`);
|
|
108
140
|
}
|
|
109
141
|
|
|
110
142
|
// Hints
|
package/git.mjs
CHANGED
|
@@ -404,18 +404,22 @@ export async function createPushBranch(awHome, branchName, files, commitMsg, pre
|
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
/**
|
|
407
|
-
* Count commits on the current branch that are not yet in main.
|
|
407
|
+
* Count commits on the current branch that are not yet in origin/main.
|
|
408
|
+
* Falls back to local main if origin/main is not available (e.g. offline).
|
|
408
409
|
*/
|
|
409
410
|
export function commitsAheadOfMain(awHome) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
411
|
+
for (const base of [`origin/${REGISTRY_BASE_BRANCH}`, REGISTRY_BASE_BRANCH]) {
|
|
412
|
+
try {
|
|
413
|
+
const out = execSync(
|
|
414
|
+
`git -C "${awHome}" rev-list --count ${base}..HEAD`,
|
|
415
|
+
{ stdio: 'pipe', encoding: 'utf8' },
|
|
416
|
+
);
|
|
417
|
+
return parseInt(out.trim(), 10) || 0;
|
|
418
|
+
} catch {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
418
421
|
}
|
|
422
|
+
return 0;
|
|
419
423
|
}
|
|
420
424
|
|
|
421
425
|
/**
|