@chlrc/aiw 0.1.0 → 0.1.1
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/README.md +70 -24
- package/README.zh-CN.md +70 -23
- package/package.json +2 -1
- package/skills/aiw-init/SKILL.md +109 -0
- package/skills/aiw-init/agents/openai.yaml +4 -0
- package/skills/aiw-reference/SKILL.md +197 -0
- package/skills/aiw-reference/agents/openai.yaml +4 -0
- package/src/cli.mjs +41 -16
- package/src/commit.mjs +39 -0
- package/src/deps.mjs +9 -10
- package/src/git.mjs +63 -19
- package/src/layout.mjs +1 -8
- package/src/workspace.mjs +368 -6
package/src/workspace.mjs
CHANGED
|
@@ -23,10 +23,12 @@ const ANSI = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export async function commandWorkspace(config, argv) {
|
|
26
|
-
const subcommand = argv[0] || "list";
|
|
26
|
+
const subcommand = normalizeCommand(argv[0] || "list");
|
|
27
27
|
const rest = argv.slice(1);
|
|
28
28
|
switch (subcommand) {
|
|
29
29
|
case "list":
|
|
30
|
+
case "ls":
|
|
31
|
+
case "als":
|
|
30
32
|
case "status":
|
|
31
33
|
await workspaceList(config, rest);
|
|
32
34
|
return;
|
|
@@ -255,19 +257,232 @@ async function workspaceDone(config, argv) {
|
|
|
255
257
|
}
|
|
256
258
|
const closeTarget = flags.closeCmux ? cmuxWorkspaceRefForPath(repo) : "";
|
|
257
259
|
const mergeArgs = await withSelectedMergeTarget(repo, flags.passthrough);
|
|
260
|
+
const target = mergeTarget(mergeArgs) || defaultDoneTarget(repo);
|
|
261
|
+
assertTargetWorktreeClean(repo, target);
|
|
262
|
+
const retries = doneRetryCount(config, flags);
|
|
263
|
+
const restorePlan = createDoneRestorePlan(repo, currentBranch(repo), target);
|
|
264
|
+
const mergeEnv = worktrunkMergeEnv(config, repo, flags);
|
|
258
265
|
await runWorkspaceHook(config, "pre_remove", {
|
|
259
266
|
repo,
|
|
260
267
|
cwd: repo,
|
|
261
268
|
workspacePath: repo,
|
|
262
|
-
branch:
|
|
263
|
-
target
|
|
269
|
+
branch: restorePlan.sourceBranch,
|
|
270
|
+
target
|
|
264
271
|
});
|
|
265
|
-
await
|
|
272
|
+
await runMergeWithRetry(repo, mergeArgs, retries, restorePlan, mergeEnv);
|
|
266
273
|
if (closeTarget) {
|
|
267
274
|
closeCmuxWorkspace(closeTarget);
|
|
268
275
|
}
|
|
269
276
|
}
|
|
270
277
|
|
|
278
|
+
async function runMergeWithRetry(repo, mergeArgs, retries, restorePlan, mergeEnv) {
|
|
279
|
+
let lastError = null;
|
|
280
|
+
let lastRestore = null;
|
|
281
|
+
for (let attempt = 1; attempt <= retries; attempt += 1) {
|
|
282
|
+
console.log(`[aiw done] merge attempt ${attempt}/${retries}`);
|
|
283
|
+
try {
|
|
284
|
+
await runInherit("wt", ["merge", ...mergeArgs], { cwd: repo, env: mergeEnv });
|
|
285
|
+
return;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
lastError = error;
|
|
288
|
+
lastRestore = restoreDoneAttempt(restorePlan);
|
|
289
|
+
if (!lastRestore.ok) {
|
|
290
|
+
throw withExit(
|
|
291
|
+
[
|
|
292
|
+
`aiw workspace done failed and rollback was incomplete after attempt ${attempt}/${retries}`,
|
|
293
|
+
lastRestore.summary
|
|
294
|
+
].filter(Boolean).join("\n"),
|
|
295
|
+
error.exitCode || 1
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if (attempt < retries) {
|
|
299
|
+
console.error(`[aiw done] merge failed; restored workspace state and retrying (${attempt + 1}/${retries})`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
throw withExit(
|
|
305
|
+
[
|
|
306
|
+
`aiw workspace done failed after ${retries} attempt(s)`,
|
|
307
|
+
lastRestore?.summary || "",
|
|
308
|
+
lastError?.message ? `last error: ${lastError.message}` : ""
|
|
309
|
+
].filter(Boolean).join("\n"),
|
|
310
|
+
lastError?.exitCode || 1
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function assertTargetWorktreeClean(repo, targetBranch) {
|
|
315
|
+
const targetPath = worktreePathForBranch(repo, targetBranch);
|
|
316
|
+
if (!targetPath || normalizePath(targetPath) === normalizePath(repo) || !isDirty(targetPath)) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const error = new Error(`target worktree has uncommitted changes: ${targetPath}; clean or stash it before aiw workspace done`);
|
|
320
|
+
error.exitCode = 5;
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function doneRetryCount(config, flags) {
|
|
325
|
+
const value = flags.retries === undefined
|
|
326
|
+
? Number(config.commit?.retries || 3)
|
|
327
|
+
: flags.retries;
|
|
328
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
329
|
+
const error = new Error("--retries must be a positive number");
|
|
330
|
+
error.exitCode = 2;
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
return Math.floor(value);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function createDoneRestorePlan(repo, sourceBranch, targetBranch) {
|
|
337
|
+
const worktrees = readGitWorktreeList(repo);
|
|
338
|
+
const sourcePath = normalizePath(repo);
|
|
339
|
+
const targetWorktree = worktreePathForBranchFromEntries(worktrees, targetBranch);
|
|
340
|
+
const targetRef = localBranchRef(repo, targetBranch);
|
|
341
|
+
const backupRef = sourceBranch ? `refs/wt-backup/${sourceBranch}` : "";
|
|
342
|
+
const backupHead = backupRef ? gitHead(repo, backupRef) : "";
|
|
343
|
+
return {
|
|
344
|
+
sourcePath,
|
|
345
|
+
sourceBranch,
|
|
346
|
+
sourceHead: gitHead(repo, "HEAD"),
|
|
347
|
+
targetBranch,
|
|
348
|
+
targetRef,
|
|
349
|
+
targetHead: targetRef ? gitHead(repo, targetRef) : "",
|
|
350
|
+
targetWorktree,
|
|
351
|
+
targetWasClean: targetWorktree ? !isDirty(targetWorktree) : true,
|
|
352
|
+
backupRef,
|
|
353
|
+
backupHead,
|
|
354
|
+
backupExisted: Boolean(backupHead),
|
|
355
|
+
managementPath: managementWorktreeFor(worktrees, sourcePath)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function restoreDoneAttempt(plan) {
|
|
360
|
+
const actions = [];
|
|
361
|
+
const failures = [];
|
|
362
|
+
restoreSourceWorktree(plan, actions, failures);
|
|
363
|
+
restoreTargetBranch(plan, actions, failures);
|
|
364
|
+
restoreBackupRef(plan, actions, failures);
|
|
365
|
+
|
|
366
|
+
const sourceDirty = gitRootIfExists(plan.sourcePath) ? gitStatusShort(plan.sourcePath) : "";
|
|
367
|
+
if (sourceDirty) {
|
|
368
|
+
failures.push(`source worktree is still dirty:\n${sourceDirty}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
ok: failures.length === 0,
|
|
373
|
+
summary: [
|
|
374
|
+
actions.length > 0 ? `rollback actions: ${actions.join("; ")}` : "rollback actions: none needed",
|
|
375
|
+
...failures.map((failure) => `rollback failure: ${failure}`)
|
|
376
|
+
].join("\n")
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function restoreSourceWorktree(plan, actions, failures) {
|
|
381
|
+
if (!plan.sourceHead) {
|
|
382
|
+
failures.push("missing source HEAD snapshot");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (gitRootIfExists(plan.sourcePath)) {
|
|
387
|
+
restoreExistingWorktree(plan.sourcePath, plan.sourceHead, "source worktree", actions, failures);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (!plan.sourceBranch) {
|
|
392
|
+
failures.push(`source worktree was removed and branch is unknown: ${plan.sourcePath}`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const cwd = restoreGitCwd(plan);
|
|
397
|
+
if (!cwd) {
|
|
398
|
+
failures.push(`source worktree was removed and no management worktree is available: ${plan.sourcePath}`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const added = runRestoreGit(cwd, ["worktree", "add", "-B", plan.sourceBranch, plan.sourcePath, plan.sourceHead], actions, failures, `recreate source worktree ${plan.sourcePath}`);
|
|
403
|
+
if (added) {
|
|
404
|
+
restoreExistingWorktree(plan.sourcePath, plan.sourceHead, "source worktree", actions, failures);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function restoreTargetBranch(plan, actions, failures) {
|
|
409
|
+
if (!plan.targetRef || !plan.targetHead) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (plan.targetWorktree && normalizePath(plan.targetWorktree) !== normalizePath(plan.sourcePath)) {
|
|
414
|
+
if (!plan.targetWasClean) {
|
|
415
|
+
actions.push(`left pre-existing dirty target worktree untouched: ${plan.targetWorktree}`);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (gitRootIfExists(plan.targetWorktree)) {
|
|
419
|
+
restoreExistingWorktree(plan.targetWorktree, plan.targetHead, `target worktree ${plan.targetBranch}`, actions, failures);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
restoreRef(plan, plan.targetRef, plan.targetHead, `target branch ${plan.targetBranch}`, actions, failures);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function restoreBackupRef(plan, actions, failures) {
|
|
428
|
+
if (!plan.backupRef) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (plan.backupExisted) {
|
|
432
|
+
restoreRef(plan, plan.backupRef, plan.backupHead, "worktrunk backup ref", actions, failures);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (gitHead(restoreGitCwd(plan), plan.backupRef)) {
|
|
436
|
+
deleteRef(plan, plan.backupRef, "worktrunk backup ref", actions, failures);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function restoreExistingWorktree(worktreePath, head, label, actions, failures) {
|
|
441
|
+
abortGitOperations(worktreePath);
|
|
442
|
+
runRestoreGit(worktreePath, ["reset", "--hard", head], actions, failures, `reset ${label}`);
|
|
443
|
+
runRestoreGit(worktreePath, ["clean", "-fd"], actions, failures, `clean ${label}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function restoreRef(plan, ref, head, label, actions, failures) {
|
|
447
|
+
const cwd = restoreGitCwd(plan);
|
|
448
|
+
if (!cwd) {
|
|
449
|
+
failures.push(`cannot restore ${label}; no management worktree is available`);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
runRestoreGit(cwd, ["update-ref", ref, head], actions, failures, `restore ${label}`);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function deleteRef(plan, ref, label, actions, failures) {
|
|
456
|
+
const cwd = restoreGitCwd(plan);
|
|
457
|
+
if (!cwd) {
|
|
458
|
+
failures.push(`cannot delete ${label}; no management worktree is available`);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
runRestoreGit(cwd, ["update-ref", "-d", ref], actions, failures, `delete ${label}`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function restoreGitCwd(plan) {
|
|
465
|
+
return gitRootIfExists(plan.sourcePath) ||
|
|
466
|
+
gitRootIfExists(plan.managementPath) ||
|
|
467
|
+
gitRootIfExists(plan.targetWorktree);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function runRestoreGit(cwd, args, actions, failures, label) {
|
|
471
|
+
const result = tryCapture("git", args, { cwd });
|
|
472
|
+
if (result.ok) {
|
|
473
|
+
actions.push(label);
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
failures.push(`${label}: ${result.stderr || result.stdout || "git failed"}`);
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function abortGitOperations(worktreePath) {
|
|
481
|
+
tryCapture("git", ["rebase", "--abort"], { cwd: worktreePath });
|
|
482
|
+
tryCapture("git", ["merge", "--abort"], { cwd: worktreePath });
|
|
483
|
+
tryCapture("git", ["cherry-pick", "--abort"], { cwd: worktreePath });
|
|
484
|
+
}
|
|
485
|
+
|
|
271
486
|
async function workspaceRemove(config, argv) {
|
|
272
487
|
assertGate("workspace", config);
|
|
273
488
|
if (hasHelpFlag(argv)) {
|
|
@@ -643,6 +858,47 @@ function currentBranch(repo) {
|
|
|
643
858
|
return result.ok ? result.stdout : "";
|
|
644
859
|
}
|
|
645
860
|
|
|
861
|
+
function gitHead(repo, rev) {
|
|
862
|
+
if (!repo || !rev) {
|
|
863
|
+
return "";
|
|
864
|
+
}
|
|
865
|
+
const result = tryCapture("git", ["rev-parse", "--verify", rev], { cwd: repo });
|
|
866
|
+
return result.ok ? result.stdout : "";
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function gitStatusShort(repo) {
|
|
870
|
+
const result = tryCapture("git", ["status", "--porcelain"], { cwd: repo });
|
|
871
|
+
return result.ok ? result.stdout : "";
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function localBranchRef(repo, branch) {
|
|
875
|
+
if (!branch) {
|
|
876
|
+
return "";
|
|
877
|
+
}
|
|
878
|
+
const ref = `refs/heads/${branch}`;
|
|
879
|
+
const result = tryCapture("git", ["show-ref", "--verify", "--quiet", ref], { cwd: repo });
|
|
880
|
+
return result.ok ? ref : "";
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function worktreePathForBranch(repo, branch) {
|
|
884
|
+
return worktreePathForBranchFromEntries(readGitWorktreeList(repo), branch);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function worktreePathForBranchFromEntries(worktrees, branch) {
|
|
888
|
+
if (!branch) {
|
|
889
|
+
return "";
|
|
890
|
+
}
|
|
891
|
+
return normalizePath(worktrees.find((worktree) => worktree.branch === branch)?.path || "");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function managementWorktreeFor(worktrees, sourcePath) {
|
|
895
|
+
const normalizedSource = normalizePath(sourcePath);
|
|
896
|
+
const candidate = worktrees.find((worktree) => {
|
|
897
|
+
return worktree.path && normalizePath(worktree.path) !== normalizedSource;
|
|
898
|
+
});
|
|
899
|
+
return normalizePath(candidate?.path || sourcePath);
|
|
900
|
+
}
|
|
901
|
+
|
|
646
902
|
function mergedIntoTargets(repo, branch, targets) {
|
|
647
903
|
return targets.filter((target) => {
|
|
648
904
|
if (!target || target === branch) {
|
|
@@ -1098,6 +1354,70 @@ function hasDryRunFlag(argv) {
|
|
|
1098
1354
|
return argv.includes("--dry-run");
|
|
1099
1355
|
}
|
|
1100
1356
|
|
|
1357
|
+
function worktrunkMergeEnv(config, repo, flags) {
|
|
1358
|
+
if (!mergeNeedsCommitGeneration(flags.passthrough)) {
|
|
1359
|
+
return process.env;
|
|
1360
|
+
}
|
|
1361
|
+
if (process.env.WORKTRUNK_COMMIT__GENERATION__COMMAND) {
|
|
1362
|
+
return process.env;
|
|
1363
|
+
}
|
|
1364
|
+
if (worktrunkCommitGenerationConfigured(repo, flags.passthrough)) {
|
|
1365
|
+
return process.env;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
const agent = resolveAgent(config, flags.agent || config.commit.agent || config.defaults.agent);
|
|
1369
|
+
assertGate("commit", config, agent);
|
|
1370
|
+
const command = `${quoteShell(aiwBinPath())} commit-message --agent ${quoteShell(agent.name)}`;
|
|
1371
|
+
return {
|
|
1372
|
+
...process.env,
|
|
1373
|
+
WORKTRUNK_COMMIT__GENERATION__COMMAND: command
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function mergeNeedsCommitGeneration(argv) {
|
|
1378
|
+
return !argv.includes("--no-squash") && !argv.includes("--no-commit");
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function worktrunkCommitGenerationConfigured(repo, argv) {
|
|
1382
|
+
const result = tryCapture("wt", ["config", "show", "--format", "json", ...worktrunkConfigArgs(argv)], { cwd: repo });
|
|
1383
|
+
if (!result.ok || !result.stdout) {
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
try {
|
|
1387
|
+
const config = JSON.parse(result.stdout);
|
|
1388
|
+
return Boolean(
|
|
1389
|
+
commitGenerationCommand(config.user?.config) ||
|
|
1390
|
+
commitGenerationCommand(config.project?.config) ||
|
|
1391
|
+
commitGenerationCommand(config.system?.config)
|
|
1392
|
+
);
|
|
1393
|
+
} catch {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
function worktrunkConfigArgs(argv) {
|
|
1399
|
+
const args = [];
|
|
1400
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1401
|
+
const arg = argv[index];
|
|
1402
|
+
if (arg === "--config" && argv[index + 1]) {
|
|
1403
|
+
args.push("--config", argv[index + 1]);
|
|
1404
|
+
index += 1;
|
|
1405
|
+
} else if (arg.startsWith("--config=")) {
|
|
1406
|
+
args.push("--config", arg.slice("--config=".length));
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
return args;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
function commitGenerationCommand(config) {
|
|
1413
|
+
if (!config || typeof config !== "object") {
|
|
1414
|
+
return "";
|
|
1415
|
+
}
|
|
1416
|
+
return stringValue(config.commit?.generation?.command) ||
|
|
1417
|
+
stringValue(config["commit.generation"]?.command) ||
|
|
1418
|
+
stringValue(config["commit.generation.command"]);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1101
1421
|
async function withSelectedMergeTarget(repo, argv) {
|
|
1102
1422
|
if (mergeTarget(argv)) {
|
|
1103
1423
|
return argv;
|
|
@@ -1142,6 +1462,24 @@ function defaultMergeTarget(branches) {
|
|
|
1142
1462
|
: branches.find((branch) => branch === "develop") || branches[0];
|
|
1143
1463
|
}
|
|
1144
1464
|
|
|
1465
|
+
function defaultDoneTarget(repo) {
|
|
1466
|
+
const current = currentBranch(repo);
|
|
1467
|
+
const branches = listLocalBranches(repo).filter((branch) => branch !== current);
|
|
1468
|
+
const remoteDefault = defaultRemoteBranch(repo);
|
|
1469
|
+
if (remoteDefault && branches.includes(remoteDefault)) {
|
|
1470
|
+
return remoteDefault;
|
|
1471
|
+
}
|
|
1472
|
+
return ["main", "master", "dev", "develop"].find((branch) => branches.includes(branch)) || "";
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function defaultRemoteBranch(repo) {
|
|
1476
|
+
const result = tryCapture("git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], { cwd: repo });
|
|
1477
|
+
if (!result.ok || !result.stdout) {
|
|
1478
|
+
return "";
|
|
1479
|
+
}
|
|
1480
|
+
return result.stdout.startsWith("origin/") ? result.stdout.slice("origin/".length) : result.stdout;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1145
1483
|
function dirtyRemoveTargets(repo, argv) {
|
|
1146
1484
|
const targets = removeTargets(argv);
|
|
1147
1485
|
if (targets.length === 0) {
|
|
@@ -1345,13 +1683,24 @@ function parseWorkspaceFlags(argv) {
|
|
|
1345
1683
|
function parseDoneFlags(argv) {
|
|
1346
1684
|
const flags = {
|
|
1347
1685
|
closeCmux: true,
|
|
1686
|
+
agent: "",
|
|
1687
|
+
retries: undefined,
|
|
1348
1688
|
passthrough: []
|
|
1349
1689
|
};
|
|
1350
|
-
for (
|
|
1690
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1691
|
+
const arg = argv[index];
|
|
1351
1692
|
if (arg === "--no-close-cmux") {
|
|
1352
1693
|
flags.closeCmux = false;
|
|
1353
1694
|
} else if (arg === "--close-cmux") {
|
|
1354
1695
|
flags.closeCmux = true;
|
|
1696
|
+
} else if (arg === "--agent") {
|
|
1697
|
+
flags.agent = argv[++index] || "";
|
|
1698
|
+
} else if (arg.startsWith("--agent=")) {
|
|
1699
|
+
flags.agent = arg.slice("--agent=".length);
|
|
1700
|
+
} else if (arg === "--retries") {
|
|
1701
|
+
flags.retries = Number(argv[++index]);
|
|
1702
|
+
} else if (arg.startsWith("--retries=")) {
|
|
1703
|
+
flags.retries = Number(arg.slice("--retries=".length));
|
|
1355
1704
|
} else {
|
|
1356
1705
|
flags.passthrough.push(arg);
|
|
1357
1706
|
}
|
|
@@ -1403,7 +1752,8 @@ Commands:
|
|
|
1403
1752
|
status [--json] Alias for list
|
|
1404
1753
|
open [target] [--agent name] [--remotes] Open picker or target with the AIW cmux layout
|
|
1405
1754
|
switch [target] Alias for open
|
|
1406
|
-
done [target] [--no-close-cmux]
|
|
1755
|
+
done [target] [--agent name] [--retries n] [--no-close-cmux]
|
|
1756
|
+
Merge the current feature worktree, cleanup, then close cmux workspace
|
|
1407
1757
|
remove [wt-remove-args...] Remove worktrees after dirty check
|
|
1408
1758
|
gc|clean [--dry-run] [--apply|--yes] [--json] [--stale-seconds n]
|
|
1409
1759
|
Preview or remove safe worktrees; stale warnings are not removed
|
|
@@ -1411,6 +1761,8 @@ Commands:
|
|
|
1411
1761
|
|
|
1412
1762
|
Short aliases:
|
|
1413
1763
|
aiw ws list aiw workspace list
|
|
1764
|
+
aiw ws ls aiw workspace list
|
|
1765
|
+
aiw als aiw workspace list
|
|
1414
1766
|
aiw list aiw workspace list
|
|
1415
1767
|
aiw ws open [target] aiw workspace open [target]
|
|
1416
1768
|
aiw open [target] aiw workspace open [target]
|
|
@@ -1420,3 +1772,13 @@ Short aliases:
|
|
|
1420
1772
|
aiw gc aiw workspace gc
|
|
1421
1773
|
aiw clean aiw workspace gc`);
|
|
1422
1774
|
}
|
|
1775
|
+
|
|
1776
|
+
function normalizeCommand(command) {
|
|
1777
|
+
return String(command || "").toLowerCase();
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function withExit(message, exitCode) {
|
|
1781
|
+
const error = new Error(message);
|
|
1782
|
+
error.exitCode = exitCode;
|
|
1783
|
+
return error;
|
|
1784
|
+
}
|