@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.
@@ -0,0 +1,197 @@
1
+ ---
2
+ name: aiw-reference
3
+ description: Operate the AIW CLI directly for workspace automation, Git review, diff inspection, AI commits, cmux layout launching, Worktrunk worktree lifecycle, workspace garbage collection, and finishing feature branches. Use when the user asks an agent to run AIW commands, clean workspaces, merge back to a target branch, open or create AIW workspaces, automate AIW flows, or explain AIW command behavior.
4
+ ---
5
+
6
+ # AIW Reference
7
+
8
+ ## Purpose
9
+
10
+ Use AIW as the orchestration layer around existing terminal tools. AIW decides the workflow path, runs dependency gates, and delegates to Worktrunk, cmux, lazygit, delta, yazi, nvim, Git, and agent CLIs.
11
+
12
+ ## Operating Rules
13
+
14
+ - Run real state checks before making lifecycle changes. Prefer `aiw doctor`, `git status --short`, and `aiw workspace list` as the first evidence.
15
+ - Treat these as high-impact operations: `aiw workspace done`, `aiw workspace remove`, `aiw workspace gc --apply`, `aiw workspace gc --yes`, and any `--force` use.
16
+ - Use dry-run or preview modes when available before applying changes. `gc --dry-run` is the default safe way to inspect cleanup.
17
+ - Do not run `aiw done` from the main checkout. It is only valid inside a feature worktree and refuses dirty worktrees.
18
+ - Do not silently stage files for `aiw commit`. AIW commit reads staged changes only; the user or agent must intentionally stage changes first.
19
+ - Keep personal AIW workflow files out of business repositories unless the user explicitly asks for project-local config.
20
+
21
+ ## Resolve the AIW Command
22
+
23
+ Use the installed `aiw` binary for normal workspace operations:
24
+
25
+ ```bash
26
+ aiw --help
27
+ ```
28
+
29
+ Use `npx @chlrc/aiw ...` for first-run or one-off package-based access:
30
+
31
+ ```bash
32
+ npx @chlrc/aiw doctor
33
+ ```
34
+
35
+ Use a local checkout only when developing or deeply customizing AIW itself:
36
+
37
+ ```bash
38
+ node bin/aiw --help
39
+ ```
40
+
41
+ ## Read-Only Orientation
42
+
43
+ Start with the smallest useful checks:
44
+
45
+ ```bash
46
+ aiw doctor
47
+ aiw doctor --gate workspace
48
+ git status --short
49
+ aiw workspace list
50
+ aiw ls
51
+ aiw als
52
+ aiw workspace list --json
53
+ aiw workspace states
54
+ ```
55
+
56
+ Use `--json` when automating decisions. The workspace table combines Worktrunk, Git, cmux, target-branch, merge-state, age, dirty, and GC signals.
57
+
58
+ ## Open or Create Workspaces
59
+
60
+ Create or switch to a worktree and open the standard three-pane cmux layout:
61
+
62
+ ```bash
63
+ aiw cmux-new --agent codex
64
+ aiw new --agent codex
65
+ aiw cmux-new --pick-repo --agent codex
66
+ aiw cmux-new --repo ~/Code/my-repo --branch feat/foo --agent codex --dry-run
67
+ aiw cmux-new --repo ~/Code/my-repo --branch feat/foo --base main --agent codex --dry-run
68
+ aiw cmux-new --local --agent codex
69
+ ```
70
+
71
+ Behavior:
72
+
73
+ - New branch from current `HEAD`: `wt switch --create <branch> --base @ -x "aiw layout --agent <agent>"`.
74
+ - New branch from a base branch: pass `--base <branch>` or `--from <branch>`.
75
+ - Existing branch: `wt switch <branch> -x "aiw layout --agent <agent>"`.
76
+ - Current checkout without Worktrunk: `--local`.
77
+
78
+ Open an existing workspace or branch:
79
+
80
+ ```bash
81
+ aiw workspace open feat/foo --agent codex --dry-run
82
+ aiw workspace open /path/to/worktree --agent codex --dry-run
83
+ aiw open feat/foo --agent codex
84
+ ```
85
+
86
+ ## Review, Diff, and Commit
87
+
88
+ Use AIW surfaces without replacing native tool responsibilities:
89
+
90
+ ```bash
91
+ aiw git
92
+ aiw diff
93
+ aiw diff --watch
94
+ aiw diff --staged
95
+ aiw files
96
+ aiw edit src/file.ts:10
97
+ aiw grep keyword
98
+ aiw tree 3
99
+ ```
100
+
101
+ Commit flow:
102
+
103
+ ```bash
104
+ git status --short
105
+ git add <path>
106
+ aiw commit --agent codex --dry-run
107
+ aiw commit --agent codex
108
+ ```
109
+
110
+ `aiw git` opens lazygit with the AIW overlay. In that lazygit session, `Ctrl-A` triggers `aiw commit --prompt ...` for staged changes while leaving native lazygit commit behavior available.
111
+
112
+ ## Finish a Feature Worktree
113
+
114
+ Use this flow when the user asks to merge a feature workspace back to a target branch:
115
+
116
+ ```bash
117
+ git status --short
118
+ aiw workspace list
119
+ aiw workspace done dev --no-close-cmux
120
+ aiw workspace done dev --agent codex --no-close-cmux
121
+ aiw workspace done dev --retries 3 --no-close-cmux
122
+ aiw workspace done dev --agent codex --retries 3 --no-close-cmux
123
+ ```
124
+
125
+ Rules:
126
+
127
+ - Run from the feature worktree, not the main workspace.
128
+ - Continue only when `git status --short` is empty.
129
+ - `done` defaults retries from `commit.retries` and restores the source worktree, target branch, and Worktrunk backup ref after failed merge attempts.
130
+ - Pass an explicit target such as `dev`, `main`, or `master` when the recorded AIW target is unclear.
131
+ - Use `--agent <name>` when the Worktrunk squash commit message should be generated by a specific AIW agent.
132
+ - If a Worktrunk squash commit fails commitlint with a fallback subject like `Squash commits from ...`, inspect `git status --short`; if the index now contains the squashed changes, create a valid Conventional Commit manually or restore from `refs/wt-backup/<branch>` before retrying.
133
+ - Omit `--no-close-cmux` when the user wants the matching cmux workspace closed after a successful Worktrunk merge.
134
+
135
+ Short alias:
136
+
137
+ ```bash
138
+ aiw done dev
139
+ ```
140
+
141
+ ## Garbage-Collect Workspaces
142
+
143
+ Preview first:
144
+
145
+ ```bash
146
+ aiw workspace gc --dry-run
147
+ aiw workspace gc --json
148
+ ```
149
+
150
+ AIW GC separates signals:
151
+
152
+ - `dirty`: blocks automatic cleanup.
153
+ - `merged`: branch is integrated, same commit, empty, or known contained by a target.
154
+ - `stale`: last commit age exceeds `workspace.stale_seconds`; stale alone does not make a workspace removable.
155
+
156
+ Only clean, merged, non-current workspaces are removable. Stale dirty or unmerged workspaces are warnings only.
157
+
158
+ Apply only when the user wants cleanup:
159
+
160
+ ```bash
161
+ aiw workspace gc --apply
162
+ aiw workspace gc --yes
163
+ ```
164
+
165
+ Short aliases:
166
+
167
+ ```bash
168
+ aiw gc --dry-run
169
+ aiw clean --dry-run
170
+ ```
171
+
172
+ ## Remove a Specific Workspace
173
+
174
+ Use removal for a named target after checking state:
175
+
176
+ ```bash
177
+ aiw workspace list
178
+ aiw workspace remove feat/foo
179
+ ```
180
+
181
+ `remove` performs a dirty check before calling Worktrunk. Use `--force` only when the user explicitly intends to hand the decision to Worktrunk:
182
+
183
+ ```bash
184
+ aiw workspace remove feat/foo --force
185
+ ```
186
+
187
+ ## Automation Pattern
188
+
189
+ For agent-run automation, report the exact evidence and command sequence:
190
+
191
+ 1. Show the current repo/worktree state.
192
+ 2. Show the AIW preview or dry-run output when available.
193
+ 3. State which operation is about to mutate worktrees, commits, or cmux.
194
+ 4. Run the apply command only when the user has authorized that operation.
195
+ 5. Verify with `aiw workspace list`, `git status --short`, or the relevant `doctor` gate.
196
+
197
+ Do not stop at a single local command if the user's request is an end-to-end AIW workflow. Carry the task through state inspection, action, verification, and a concise result.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "AIW Reference"
3
+ short_description: "Run AIW workspace automation safely"
4
+ default_prompt: "Use $aiw-reference to inspect AIW workspace state and cleanly finish or garbage-collect worktrees."
package/src/cli.mjs CHANGED
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { expandHome, loadConfig, resolveAgent, aiwBinPath } from "./config.mjs";
5
- import { runCommit } from "./commit.mjs";
5
+ import { runCommit, runCommitMessage } from "./commit.mjs";
6
6
  import { assertGate, printDoctor } from "./deps.mjs";
7
7
  import { assertGitRoot, gitRoot, isDirty, resolveRepo, selectBranch } from "./git.mjs";
8
8
  import { runWorkspaceHook } from "./hooks.mjs";
@@ -12,7 +12,7 @@ import { commandExists, quoteShell, runInherit, sleep } from "./run.mjs";
12
12
  import { commandWorkspace, recordWorkspaceTarget } from "./workspace.mjs";
13
13
 
14
14
  export async function main(argv) {
15
- const command = argv[0] || "help";
15
+ const command = normalizeCommand(argv[0] || "help");
16
16
  const rest = argv.slice(1);
17
17
  const config = loadConfig();
18
18
 
@@ -32,6 +32,12 @@ export async function main(argv) {
32
32
  case "new":
33
33
  await commandCmuxNew(config, rest, command);
34
34
  return;
35
+ case "cmux":
36
+ if (normalizeCommand(rest[0] || "") === "new") {
37
+ await commandCmuxNew(config, rest.slice(1), "cmux-new");
38
+ return;
39
+ }
40
+ break;
35
41
  case "layout":
36
42
  await commandLayout(config, rest);
37
43
  return;
@@ -46,6 +52,8 @@ export async function main(argv) {
46
52
  await commandWorkspace(config, ["open", ...rest]);
47
53
  return;
48
54
  case "list":
55
+ case "ls":
56
+ case "als":
49
57
  await commandWorkspace(config, ["list", ...rest]);
50
58
  return;
51
59
  case "done":
@@ -64,6 +72,9 @@ export async function main(argv) {
64
72
  case "commit":
65
73
  await commandCommit(config, rest);
66
74
  return;
75
+ case "commit-message":
76
+ await commandCommitMessage(config, rest);
77
+ return;
67
78
  case "git":
68
79
  await commandGit(config, rest);
69
80
  return;
@@ -83,12 +94,11 @@ export async function main(argv) {
83
94
  case "tree":
84
95
  await commandTree(config, rest);
85
96
  return;
86
- default: {
87
- const error = new Error(`unknown command: ${command}`);
88
- error.exitCode = 2;
89
- throw error;
90
- }
91
97
  }
98
+
99
+ const error = new Error(`unknown command: ${command}`);
100
+ error.exitCode = 2;
101
+ throw error;
92
102
  }
93
103
 
94
104
  async function commandDoctor(config, argv) {
@@ -114,11 +124,9 @@ async function commandCmuxNew(config, argv, commandName) {
114
124
  const branchFromArgs = flags.positionals[0] && !isKnownAgent(config, flags.positionals[0])
115
125
  ? flags.positionals[0]
116
126
  : "";
117
- const agentFromArgs = commandName === "new"
118
- ? flags.positionals[1]
119
- : flags.positionals.find((item) => isKnownAgent(config, item));
120
- if (flags.local && (flags.branch || branchFromArgs || flags.create)) {
121
- const error = new Error("--local cannot be combined with --branch, a branch argument, or --create");
127
+ const agentFromArgs = flags.positionals.find((item) => isKnownAgent(config, item));
128
+ if (flags.local && (flags.branch || branchFromArgs || flags.create || flags.base)) {
129
+ const error = new Error("--local cannot be combined with --branch, a branch argument, --create, or --base");
122
130
  error.exitCode = 2;
123
131
  throw error;
124
132
  }
@@ -129,7 +137,8 @@ async function commandCmuxNew(config, argv, commandName) {
129
137
  const branchSelection = flags.local
130
138
  ? { local: true }
131
139
  : await selectBranch(repo, flags.branch || branchFromArgs, {
132
- forceCreate: flags.create
140
+ forceCreate: flags.create,
141
+ baseBranch: flags.base
133
142
  });
134
143
  const agent = await selectAgent(config, flags.agent || agentFromArgs);
135
144
 
@@ -152,7 +161,7 @@ async function commandCmuxNew(config, argv, commandName) {
152
161
 
153
162
  const layoutCommand = `${quoteShell(aiwBinPath())} layout --agent ${quoteShell(agent.name)}`;
154
163
  const wtArgs = branchSelection.create
155
- ? ["switch", "--create", branchSelection.branch, "--base", "@", "-x", layoutCommand]
164
+ ? ["switch", "--create", branchSelection.branch, "--base", branchSelection.baseBranch || "@", "-x", layoutCommand]
156
165
  : ["switch", branchSelection.branch, "-x", layoutCommand];
157
166
  if (flags.dryRun) {
158
167
  console.log(`cd ${quoteShell(repo)} && wt ${wtArgs.map(quoteShell).join(" ")}`);
@@ -240,6 +249,13 @@ async function commandCommit(config, argv) {
240
249
  await runCommit(config, flags);
241
250
  }
242
251
 
252
+ async function commandCommitMessage(config, argv) {
253
+ const flags = parseFlags(argv);
254
+ const agent = resolveAgent(config, flags.agent || config.commit.agent || config.defaults.agent);
255
+ assertGate("commit", config, agent);
256
+ runCommitMessage(config, flags);
257
+ }
258
+
243
259
  function resolveConfigFile(config, value) {
244
260
  if (!value) {
245
261
  return "";
@@ -325,6 +341,10 @@ function parseFlags(argv) {
325
341
  case "--branch":
326
342
  flags.branch = argv[++index];
327
343
  break;
344
+ case "--base":
345
+ case "--from":
346
+ flags.base = argv[++index];
347
+ break;
328
348
  case "--repo":
329
349
  flags.repo = argv[++index];
330
350
  break;
@@ -410,13 +430,18 @@ function printHelp() {
410
430
  Commands:
411
431
  init [--cmux-scope <home|code|none>] [--code-root <path>] [--config-dir <path>] [--dry-run]
412
432
  doctor [--json] [--gate <p0|init|layout|cmux-new|workspace|worktrunk|diff|commit>] [--agent <name>]
413
- cmux-new [--branch <branch>] [--agent <name>] [--repo <path>] [--pick-repo] [--create] [--local] [--dry-run]
433
+ cmux-new|new [--branch <branch>] [--base <branch>] [--agent <name>] [--repo <path>] [--pick-repo] [--create] [--local] [--dry-run]
414
434
  layout [--agent <name>] [--print-json] [--dry-run]
415
435
  workspace|ws <list|open|done|remove|gc> [options]
416
436
  commit [--agent <name>] [--prompt <text>] [--prompt-file <path>] [--retries <n>] [--dry-run] [--print-prompt]
417
- open | switch | list | done | remove | gc | clean
437
+ commit-message [--agent <name>] [--prompt <text>]
438
+ open | switch | list | ls | als | done | remove | gc | clean
418
439
  diff [--watch] [--staged] [--all]
419
440
  git | files [path] | edit <file[:line]> | grep <query> | pick | tree [depth]
420
441
 
421
442
  Main workflow commands run dependency gates before creating worktrees or opening cmux layouts.`);
422
443
  }
444
+
445
+ function normalizeCommand(command) {
446
+ return String(command || "").toLowerCase();
447
+ }
package/src/commit.mjs CHANGED
@@ -83,6 +83,37 @@ export async function runCommit(config, flags) {
83
83
  throw withExit(`git commit failed after ${retries} attempts`, 8);
84
84
  }
85
85
 
86
+ export function runCommitMessage(config, flags) {
87
+ const prompt = flags.prompt || readStdin();
88
+ if (!prompt.trim()) {
89
+ throw withExit("commit-message prompt is required on stdin or via --prompt", 2);
90
+ }
91
+ const repo = capture("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() });
92
+ const message = generateCommitMessage(config, flags, prompt, repo);
93
+ console.log(message);
94
+ }
95
+
96
+ function generateCommitMessage(config, flags, prompt, repo) {
97
+ const agent = resolveAgent(config, flags.agent || config.commit.agent || config.defaults.agent);
98
+ const agentResult = runAgentForText(agent, prompt, { cwd: repo });
99
+ if (!agentResult.ok) {
100
+ throw withExit(
101
+ [
102
+ `agent '${agent.name}' failed while generating commit message`,
103
+ agentResult.stderr.trim(),
104
+ agentResult.stdout.trim()
105
+ ].filter(Boolean).join("\n"),
106
+ agentResult.status || 1
107
+ );
108
+ }
109
+
110
+ const message = cleanAgentText(agentResult.stdout);
111
+ if (!message) {
112
+ throw withExit(`agent '${agent.name}' returned an empty commit message`, 7);
113
+ }
114
+ return message;
115
+ }
116
+
86
117
  function readStagedDiff(config, repo) {
87
118
  const diff = capture("git", ["diff", "--cached", "--no-ext-diff"], { cwd: repo });
88
119
  const statResult = tryCapture("git", ["diff", "--cached", "--stat"], { cwd: repo });
@@ -97,6 +128,14 @@ function readStagedDiff(config, repo) {
97
128
  };
98
129
  }
99
130
 
131
+ function readStdin() {
132
+ try {
133
+ return fs.readFileSync(0, "utf8");
134
+ } catch {
135
+ return "";
136
+ }
137
+ }
138
+
100
139
  function buildCommitPrompt({ basePrompt, staged, attempt, retries, lastFailure }) {
101
140
  const sections = [
102
141
  basePrompt,
package/src/deps.mjs CHANGED
@@ -126,29 +126,24 @@ export function printDoctor(config, options = {}) {
126
126
 
127
127
  function requirementsFor(profile, config, agent) {
128
128
  const agentCmd = agent?.cmd;
129
+ const gitDeps = [config.defaults.git || "lazygit", ...lazygitOverlayDeps(config)];
129
130
  switch (profile) {
130
131
  case "base":
131
132
  return req(["git"]);
132
133
  case "init":
133
- return req(["sh", "git", "cmux", "wt", "yazi", "nvim", "lazygit", "rg", "fzf", "bat", agentCmd].filter(Boolean), [
134
- ["cmux-git-diff", "delta"]
135
- ]);
134
+ return req(["sh", "git", "cmux", "wt", "yazi", "nvim", ...gitDeps, "rg", "fzf", "bat", agentCmd].filter(Boolean));
136
135
  case "layout":
137
- return req(["git", "cmux", "yazi", "lazygit", "nvim", agentCmd].filter(Boolean), [
138
- ["cmux-git-diff", "delta"]
139
- ]);
136
+ return req(["git", "cmux", "yazi", "nvim", ...gitDeps, agentCmd].filter(Boolean));
140
137
  case "cmux-new":
141
138
  case "new":
142
- return req(["git", "wt", "cmux", "yazi", "lazygit", "nvim", agentCmd].filter(Boolean), [
143
- ["cmux-git-diff", "delta"]
144
- ]);
139
+ return req(["git", "wt", "cmux", "yazi", "nvim", ...gitDeps, agentCmd].filter(Boolean));
145
140
  case "worktrunk":
146
141
  case "workspace":
147
142
  return req(["git", "wt"]);
148
143
  case "files":
149
144
  return req([config.defaults.files || "yazi"]);
150
145
  case "git":
151
- return req([config.defaults.git || "lazygit", ...(config.git.lazygit_config ? ["delta"] : [])]);
146
+ return req(gitDeps);
152
147
  case "edit":
153
148
  return req([config.defaults.editor || "nvim"]);
154
149
  case "grep":
@@ -170,3 +165,7 @@ function requirementsFor(profile, config, agent) {
170
165
  function req(commands, anyOf = []) {
171
166
  return { commands, anyOf };
172
167
  }
168
+
169
+ function lazygitOverlayDeps(config) {
170
+ return config.git.lazygit_config ? ["delta"] : [];
171
+ }
package/src/git.mjs CHANGED
@@ -86,19 +86,7 @@ export function currentBranch(repo) {
86
86
  }
87
87
 
88
88
  export function branchExists(repo, branch) {
89
- if (!branch) {
90
- return false;
91
- }
92
- const local = tryCapture("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], { cwd: repo });
93
- if (local.ok) {
94
- return true;
95
- }
96
- const remote = tryCapture("git", ["show-ref", "--verify", "--quiet", `refs/remotes/${branch}`], { cwd: repo });
97
- if (remote.ok) {
98
- return true;
99
- }
100
- const originRemote = tryCapture("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${branch}`], { cwd: repo });
101
- return originRemote.ok;
89
+ return Boolean(resolveBranchRef(repo, branch));
102
90
  }
103
91
 
104
92
  export function listBranches(repo) {
@@ -115,19 +103,34 @@ export function listBranches(repo) {
115
103
 
116
104
  export async function selectBranch(repo, requestedBranch, options = {}) {
117
105
  const current = currentBranch(repo);
106
+ const requestedBase = options.baseBranch || "";
107
+ const baseBranch = requestedBase ? resolveBranchRef(repo, requestedBase) : "";
108
+ if (requestedBase && !baseBranch) {
109
+ throw withExit(`base branch not found: ${requestedBase}`, 4);
110
+ }
118
111
  if (requestedBranch) {
119
- const create = options.forceCreate || !branchExists(repo, requestedBranch);
112
+ const branchAlreadyExists = branchExists(repo, requestedBranch);
113
+ const create = options.forceCreate || !branchAlreadyExists;
114
+ if (requestedBase && !create) {
115
+ throw withExit(`--base can only be used when creating a new branch: ${requestedBranch} already exists`, 2);
116
+ }
120
117
  return {
121
118
  branch: requestedBranch,
122
119
  create,
123
- targetBranch: create ? current : ""
120
+ baseBranch: create ? baseBranch || "@" : "",
121
+ targetBranch: create ? requestedBase || current : ""
124
122
  };
125
123
  }
126
124
 
127
125
  const branches = listBranches(repo).filter((branch) => branch !== current);
126
+ if (requestedBase) {
127
+ return createBranchSelection(repo, baseBranch, requestedBase);
128
+ }
129
+
128
130
  const createChoice = "Create new branch from current HEAD...";
131
+ const createFromBranchChoice = "Create new branch from existing branch...";
129
132
  const localChoice = "Open current checkout...";
130
- const selected = await pickFromList("Select worktree", [createChoice, localChoice, ...branches], {
133
+ const selected = await pickFromList("Select worktree", [createChoice, createFromBranchChoice, localChoice, ...branches], {
131
134
  defaultItem: createChoice
132
135
  });
133
136
  if (selected === localChoice) {
@@ -136,21 +139,46 @@ export async function selectBranch(repo, requestedBranch, options = {}) {
136
139
  };
137
140
  }
138
141
  if (selected !== createChoice) {
142
+ if (selected === createFromBranchChoice) {
143
+ const base = await pickFromList("Base branch", listBranches(repo), {
144
+ defaultItem: current
145
+ });
146
+ return createBranchSelection(repo, resolveBranchRef(repo, base), base);
147
+ }
139
148
  return {
140
149
  branch: selected,
141
150
  create: false
142
151
  };
143
152
  }
144
153
 
154
+ return {
155
+ ...(await promptNewBranch()),
156
+ create: true,
157
+ baseBranch: "@",
158
+ targetBranch: current
159
+ };
160
+ }
161
+
162
+ async function createBranchSelection(repo, baseBranch, targetBranch) {
163
+ if (!baseBranch) {
164
+ throw withExit(`base branch not found: ${targetBranch}`, 4);
165
+ }
166
+ return {
167
+ ...(await promptNewBranch()),
168
+ create: true,
169
+ baseBranch,
170
+ targetBranch
171
+ };
172
+ }
173
+
174
+ async function promptNewBranch() {
145
175
  const { askInput } = await import("./prompt.mjs");
146
176
  const branch = await askInput("New branch");
147
177
  if (!branch) {
148
178
  throw withExit("branch is required", 4);
149
179
  }
150
180
  return {
151
- branch,
152
- create: true,
153
- targetBranch: current
181
+ branch
154
182
  };
155
183
  }
156
184
 
@@ -198,6 +226,22 @@ function listRefs(repo, refPath) {
198
226
  }
199
227
  }
200
228
 
229
+ function resolveBranchRef(repo, branch) {
230
+ if (!branch) {
231
+ return "";
232
+ }
233
+ const local = tryCapture("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], { cwd: repo });
234
+ if (local.ok) {
235
+ return branch;
236
+ }
237
+ const remote = tryCapture("git", ["show-ref", "--verify", "--quiet", `refs/remotes/${branch}`], { cwd: repo });
238
+ if (remote.ok) {
239
+ return branch;
240
+ }
241
+ const originRemote = tryCapture("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${branch}`], { cwd: repo });
242
+ return originRemote.ok ? `origin/${branch}` : "";
243
+ }
244
+
201
245
  function isInside(child, parent) {
202
246
  const relative = path.relative(parent, child);
203
247
  return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
package/src/layout.mjs CHANGED
@@ -18,14 +18,7 @@ export function buildLayout(config, agentName) {
18
18
  terminalPane(agentTitle(agent.name), agentCommand)
19
19
  ]
20
20
  },
21
- {
22
- direction: "horizontal",
23
- split: 0.5,
24
- children: [
25
- terminalPane("Git", `${aiw} git`),
26
- terminalPane("Diff", `${aiw} diff --watch`)
27
- ]
28
- }
21
+ terminalPane("Git", `${aiw} git`)
29
22
  ]
30
23
  };
31
24
  }