@openagt/skill-branches 0.0.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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/SKILL.md +70 -0
  3. package/bin/branches +11 -0
  4. package/dist/bin-dir.d.ts +7 -0
  5. package/dist/bin-dir.d.ts.map +1 -0
  6. package/dist/bin-dir.js +8 -0
  7. package/dist/bin-dir.js.map +1 -0
  8. package/dist/branch-links.d.ts +37 -0
  9. package/dist/branch-links.d.ts.map +1 -0
  10. package/dist/branch-links.js +67 -0
  11. package/dist/branch-links.js.map +1 -0
  12. package/dist/branch-names.d.ts +41 -0
  13. package/dist/branch-names.d.ts.map +1 -0
  14. package/dist/branch-names.js +54 -0
  15. package/dist/branch-names.js.map +1 -0
  16. package/dist/branch-state.d.ts +68 -0
  17. package/dist/branch-state.d.ts.map +1 -0
  18. package/dist/branch-state.js +146 -0
  19. package/dist/branch-state.js.map +1 -0
  20. package/dist/checkout.d.ts +25 -0
  21. package/dist/checkout.d.ts.map +1 -0
  22. package/dist/checkout.js +37 -0
  23. package/dist/checkout.js.map +1 -0
  24. package/dist/cli.d.ts +36 -0
  25. package/dist/cli.d.ts.map +1 -0
  26. package/dist/cli.js +310 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/command-link.d.ts +20 -0
  29. package/dist/command-link.d.ts.map +1 -0
  30. package/dist/command-link.js +37 -0
  31. package/dist/command-link.js.map +1 -0
  32. package/dist/file-branch.d.ts +150 -0
  33. package/dist/file-branch.d.ts.map +1 -0
  34. package/dist/file-branch.js +371 -0
  35. package/dist/file-branch.js.map +1 -0
  36. package/dist/git-exclude.d.ts +17 -0
  37. package/dist/git-exclude.d.ts.map +1 -0
  38. package/dist/git-exclude.js +30 -0
  39. package/dist/git-exclude.js.map +1 -0
  40. package/dist/git.d.ts +73 -0
  41. package/dist/git.d.ts.map +1 -0
  42. package/dist/git.js +164 -0
  43. package/dist/git.js.map +1 -0
  44. package/dist/index.d.ts +15 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +15 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/merge-hold.d.ts +20 -0
  49. package/dist/merge-hold.d.ts.map +1 -0
  50. package/dist/merge-hold.js +64 -0
  51. package/dist/merge-hold.js.map +1 -0
  52. package/dist/merge-watch.d.ts +68 -0
  53. package/dist/merge-watch.d.ts.map +1 -0
  54. package/dist/merge-watch.js +97 -0
  55. package/dist/merge-watch.js.map +1 -0
  56. package/dist/merge.d.ts +70 -0
  57. package/dist/merge.d.ts.map +1 -0
  58. package/dist/merge.js +55 -0
  59. package/dist/merge.js.map +1 -0
  60. package/dist/publish.d.ts +138 -0
  61. package/dist/publish.d.ts.map +1 -0
  62. package/dist/publish.js +215 -0
  63. package/dist/publish.js.map +1 -0
  64. package/dist/push.d.ts +53 -0
  65. package/dist/push.d.ts.map +1 -0
  66. package/dist/push.js +41 -0
  67. package/dist/push.js.map +1 -0
  68. package/dist/reclaim.d.ts +74 -0
  69. package/dist/reclaim.d.ts.map +1 -0
  70. package/dist/reclaim.js +87 -0
  71. package/dist/reclaim.js.map +1 -0
  72. package/dist/skill-links.d.ts +43 -0
  73. package/dist/skill-links.d.ts.map +1 -0
  74. package/dist/skill-links.js +50 -0
  75. package/dist/skill-links.js.map +1 -0
  76. package/dist/worktree-deps.d.ts +30 -0
  77. package/dist/worktree-deps.d.ts.map +1 -0
  78. package/dist/worktree-deps.js +128 -0
  79. package/dist/worktree-deps.js.map +1 -0
  80. package/dist/worktree.d.ts +220 -0
  81. package/dist/worktree.d.ts.map +1 -0
  82. package/dist/worktree.js +397 -0
  83. package/dist/worktree.js.map +1 -0
  84. package/package.json +53 -0
@@ -0,0 +1,37 @@
1
+ import { nodeGitRunner, BRANCHES_DIR, excludeFromGit } from '@openagt/agent-data';
2
+ import { agentBranchName } from './branch-names.js';
3
+ import { addWorktree, attachWorktree } from './worktree.js';
4
+ import { linkDependencies } from './worktree-deps.js';
5
+ import { reconcileBranchLinks } from './branch-links.js';
6
+ import { linkSkill } from './skill-links.js';
7
+ import { linkOwnCommand } from './command-link.js';
8
+ /** A new agent's checkout, on a fresh `agent-<id>` branch from `base` or origin's default branch. */
9
+ export async function createCheckout(repo, opts, git = nodeGitRunner()) {
10
+ const worktree = await addWorktree(repo, { agentId: opts.agentId, branch: agentBranchName(opts.agentId), ...(opts.base ? { base: opts.base } : {}) }, git);
11
+ await settle(repo, worktree.path, git, opts.skills);
12
+ return worktree;
13
+ }
14
+ /** A continued agent's checkout, back on the branch its work is on; a branch gone everywhere starts again from `base`, or origin's default branch. */
15
+ export async function attachCheckout(repo, opts, git = nodeGitRunner()) {
16
+ const worktree = await attachWorktree(repo, { agentId: opts.agentId, branch: opts.branch, ...(opts.base ? { base: opts.base } : {}) }, git);
17
+ await settle(repo, worktree.path, git, opts.skills);
18
+ return worktree;
19
+ }
20
+ /**
21
+ * What a checkout gets besides its files. All best-effort: the checkouts are the package's state
22
+ * and hidden from the project's git the moment the first one exists (#1600) — as an untracked
23
+ * directory at the root, `.branches/` would ride any sweeping `git add -A` onto a code branch —
24
+ * through the repository's own exclude file, so no tracked file changes; `node_modules` is
25
+ * gitignored, so a fresh checkout has none and a link that cannot be made is a worse run, not a
26
+ * failed one; the package's own command is linked into a checkout that has none, so the skill's
27
+ * `npx branches` runs it and downloads nothing (`command-link.ts`); a link under `.branches/` is a view, and the next reconcile pass makes it.
28
+ */
29
+ async function settle(repo, path, git, skills = []) {
30
+ await excludeFromGit(repo, `/${BRANCHES_DIR}`, undefined, git).catch(() => { });
31
+ await linkDependencies(repo, path).catch(() => []);
32
+ // After the project's own tools: a project that installed this package keeps its copy of the command.
33
+ await linkOwnCommand(repo, path, git);
34
+ await linkSkill(repo, path, undefined, git, skills);
35
+ await reconcileBranchLinks(repo, { git }).catch(() => { });
36
+ }
37
+ //# sourceMappingURL=checkout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout.js","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACjG,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAsB,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,SAAS,EAAkB,MAAM,kBAAkB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAclD,qGAAqG;AACrG,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,IAAyD,EACzD,GAAG,GAAc,aAAa,EAAE;IAEhC,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;IAC1J,MAAM,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IACnD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,sJAAsJ;AACtJ,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,IAAyE,EACzE,GAAG,GAAc,aAAa,EAAE;IAEhC,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;IAC3I,MAAM,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IACnD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,MAAM,CAAC,IAAY,EAAE,IAAY,EAAE,GAAc,EAAE,MAAM,GAAyB,EAAE;IACjG,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,YAAY,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IAC9E,MAAM,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IAClD,sGAAsG;IACtG,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAA;IACrC,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC,CAAA;IACnD,MAAM,oBAAoB,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;AAC3D,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { type GitRunner } from '@openagt/agent-data';
2
+ /**
3
+ * The command line over the package (#1725): the same functions a caller's code calls, for an agent
4
+ * (and a person) in a shell. One implementation, every surface a caller.
5
+ *
6
+ * The contract: JSON on stdout, one line for a person on stderr, and the exit code says how it
7
+ * went — 0 for a result, 1 for a refusal or a git failure, 2 for a command that could not be
8
+ * read. A refusal is a rule saying no (a dirty tree, a name that is not a session name); it is
9
+ * reported on stdout as `{ ok: false, reason }` so a caller parsing the output learns why, and
10
+ * on stderr so a person does.
11
+ *
12
+ * Where the project is comes from the working directory: for every command that acts on the
13
+ * project (`create`, `attach`, `show`, `list`, `remove`, `prune`, and `push --branch`) it is
14
+ * the checkout whose `.branches/` the working directory is under, else the checkout itself — so
15
+ * the same command works from inside an agent's checkout; the commands that act on one checkout
16
+ * (`name`, `status`, a bare `push`) act on the one the working directory is in.
17
+ *
18
+ * Git only (#1820): what the project's git host does with a pushed branch, the pull request and its
19
+ * merge, is the git host package's own command; this one never names it.
20
+ */
21
+ export declare const USAGE = "usage: branches <command>\n\n create <id> [--base <ref>] a checkout for agent <id>, on a fresh branch agent-<id>\n attach <id> <branch> [--base <ref>]\n a checkout for agent <id>, on an existing branch; one that is gone starts again from <ref>\n name <name> rename this checkout's branch to agent-<name>; prints the name it got\n status [path] the checkout's branch, whether it is clean, whether it is on the remote\n show [--from <commit>] <branch>...\n what each branch holds and where it stands: its commits and files beyond the base, whether it is pushed, merged, and what its checkout left uncommitted;\n measured from <commit>, the one the branches started from, in place of the default branch; a full commit id in place of a branch is read as a branch that ends there\n push [--branch <b>] push this checkout's branch, or branch <b>, to origin; a dirty checkout is refused\n merge <branch> merge branch <branch> into the default branch, in the project's folder, then delete it; a conflict changes nothing and names the files\n list [--sizes] every agent checkout under .branches/\n remove <id> [--from <commit>] reclaim agent <id>'s checkout, once its branch holds everything in it; the branch stays, nothing is pushed;\n <commit> is the one its branch started from, in place of the default branch\n [--discard] ... or drop it whatever it holds, uncommitted work included; the branch stays\n prune remove, for every checkout\n\nJSON on stdout. Exit code 1 for a refusal or a git failure (the reason on stderr), 2 for a usage error.";
22
+ /** The streams and the working directory a run of the CLI sees. */
23
+ export interface CliIo {
24
+ cwd: string;
25
+ stdout: (line: string) => void;
26
+ stderr: (line: string) => void;
27
+ }
28
+ /** A refusal: a rule said no, and the caller learns which. */
29
+ export type CliRefusal = {
30
+ ok: false;
31
+ reason: string;
32
+ [key: string]: unknown;
33
+ };
34
+ /** Run the CLI: `argv` is everything after the program name. Resolves to the exit code. */
35
+ export declare function runCli(argv: string[], io: CliIo, git?: GitRunner): Promise<number>;
36
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0C,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAsB5F;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,KAAK,yvDAkBsF,CAAA;AAExG,mEAAmE;AACnE,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AAED,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAA;AAY9E,2FAA2F;AAC3F,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,GAAE,SAA2B,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBzG"}
package/dist/cli.js ADDED
@@ -0,0 +1,310 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { basename, resolve } from 'node:path';
3
+ import { realpath, stat } from 'node:fs/promises';
4
+ import { nodeGitRunner, checkoutRoot, gitReason } from '@openagt/agent-data';
5
+ import { agentBranchName, agentIdFromWorktreeDir, isSafeAgentId, sessionNameOf } from './branch-names.js';
6
+ import { branchPushed, currentBranch, isWorktreeRoot, nameBranch, projectRoot, worktreeBranch, worktreeClean, worktreeDirEntries, worktreePath, worktreeSize, } from './worktree.js';
7
+ import { createCheckout, attachCheckout } from './checkout.js';
8
+ import { reconcileBranchLinks } from './branch-links.js';
9
+ import { discardWorktree, reclaimWorktree } from './reclaim.js';
10
+ import { pushBranchByName, pushCheckout } from './push.js';
11
+ import { mergeBranch } from './merge.js';
12
+ import { readBranchStates } from './branch-state.js';
13
+ /**
14
+ * The command line over the package (#1725): the same functions a caller's code calls, for an agent
15
+ * (and a person) in a shell. One implementation, every surface a caller.
16
+ *
17
+ * The contract: JSON on stdout, one line for a person on stderr, and the exit code says how it
18
+ * went — 0 for a result, 1 for a refusal or a git failure, 2 for a command that could not be
19
+ * read. A refusal is a rule saying no (a dirty tree, a name that is not a session name); it is
20
+ * reported on stdout as `{ ok: false, reason }` so a caller parsing the output learns why, and
21
+ * on stderr so a person does.
22
+ *
23
+ * Where the project is comes from the working directory: for every command that acts on the
24
+ * project (`create`, `attach`, `show`, `list`, `remove`, `prune`, and `push --branch`) it is
25
+ * the checkout whose `.branches/` the working directory is under, else the checkout itself — so
26
+ * the same command works from inside an agent's checkout; the commands that act on one checkout
27
+ * (`name`, `status`, a bare `push`) act on the one the working directory is in.
28
+ *
29
+ * Git only (#1820): what the project's git host does with a pushed branch, the pull request and its
30
+ * merge, is the git host package's own command; this one never names it.
31
+ */
32
+ export const USAGE = `usage: branches <command>
33
+
34
+ create <id> [--base <ref>] a checkout for agent <id>, on a fresh branch agent-<id>
35
+ attach <id> <branch> [--base <ref>]
36
+ a checkout for agent <id>, on an existing branch; one that is gone starts again from <ref>
37
+ name <name> rename this checkout's branch to agent-<name>; prints the name it got
38
+ status [path] the checkout's branch, whether it is clean, whether it is on the remote
39
+ show [--from <commit>] <branch>...
40
+ what each branch holds and where it stands: its commits and files beyond the base, whether it is pushed, merged, and what its checkout left uncommitted;
41
+ measured from <commit>, the one the branches started from, in place of the default branch; a full commit id in place of a branch is read as a branch that ends there
42
+ push [--branch <b>] push this checkout's branch, or branch <b>, to origin; a dirty checkout is refused
43
+ merge <branch> merge branch <branch> into the default branch, in the project's folder, then delete it; a conflict changes nothing and names the files
44
+ list [--sizes] every agent checkout under .branches/
45
+ remove <id> [--from <commit>] reclaim agent <id>'s checkout, once its branch holds everything in it; the branch stays, nothing is pushed;
46
+ <commit> is the one its branch started from, in place of the default branch
47
+ [--discard] ... or drop it whatever it holds, uncommitted work included; the branch stays
48
+ prune remove, for every checkout
49
+
50
+ JSON on stdout. Exit code 1 for a refusal or a git failure (the reason on stderr), 2 for a usage error.`;
51
+ /** Thrown inside a command to end it with a refusal. */
52
+ class Refused extends Error {
53
+ outcome;
54
+ line;
55
+ constructor(outcome, line) {
56
+ super(line);
57
+ this.outcome = outcome;
58
+ this.line = line;
59
+ }
60
+ }
61
+ /** Thrown inside a command for an argument that cannot be read: usage on stderr, exit 2. */
62
+ class Usage extends Error {
63
+ }
64
+ /** Run the CLI: `argv` is everything after the program name. Resolves to the exit code. */
65
+ export async function runCli(argv, io, git = nodeGitRunner()) {
66
+ const [command, ...rest] = argv;
67
+ const run = command && Object.hasOwn(COMMANDS, command) ? COMMANDS[command] : undefined;
68
+ if (!run) {
69
+ io.stderr(USAGE);
70
+ return 2;
71
+ }
72
+ try {
73
+ io.stdout(JSON.stringify(await run(rest, io.cwd, git)));
74
+ return 0;
75
+ }
76
+ catch (err) {
77
+ if (err instanceof Usage) {
78
+ io.stderr(`${err.message}\n\n${USAGE}`);
79
+ return 2;
80
+ }
81
+ if (err instanceof Refused) {
82
+ io.stdout(JSON.stringify(err.outcome));
83
+ io.stderr(err.line);
84
+ return 1;
85
+ }
86
+ const detail = gitReason(err);
87
+ io.stdout(JSON.stringify({ ok: false, reason: 'git-failed', detail }));
88
+ io.stderr(detail);
89
+ return 1;
90
+ }
91
+ }
92
+ const COMMANDS = {
93
+ async create(args, cwd, git) {
94
+ const { positionals, values } = parse(args, { base: { type: 'string' } }, 1);
95
+ const agentId = agentIdArg(positionals[0]);
96
+ const repo = await project(cwd, git);
97
+ return { ok: true, ...(await createCheckout(repo, { agentId, ...(values.base ? { base: values.base } : {}) }, git)) };
98
+ },
99
+ async attach(args, cwd, git) {
100
+ const { positionals, values } = parse(args, { base: { type: 'string' } }, 2);
101
+ const [agentId, branch] = [agentIdArg(positionals[0]), positionals[1]];
102
+ const repo = await project(cwd, git);
103
+ return { ok: true, ...(await attachCheckout(repo, { agentId, branch, ...(values.base ? { base: values.base } : {}) }, git)) };
104
+ },
105
+ async name(args, cwd, git) {
106
+ const { positionals } = parse(args, {}, 1);
107
+ const name = positionals[0];
108
+ const checkout = await inRepo(() => checkoutRoot(cwd, git));
109
+ const outcome = await nameBranch(checkout, name, git);
110
+ if (!outcome.ok)
111
+ throw new Refused(outcome, NAME_REFUSALS[outcome.reason](name, checkout));
112
+ // The `.branches/<name>` link follows the rename now, not at the next reconcile.
113
+ await reconcileBranchLinks(await projectRoot(checkout, git), { git });
114
+ return outcome;
115
+ },
116
+ async status(args, cwd, git) {
117
+ const { positionals } = parse(args, {}, 0, 1);
118
+ const path = positionals[0] ? resolve(cwd, positionals[0]) : await inRepo(() => checkoutRoot(cwd, git));
119
+ if (!(await isWorktreeRoot(path, git)))
120
+ throw new Refused({ ok: false, reason: 'not-a-worktree', path }, `${path} is not a git worktree`);
121
+ const branch = await currentBranch(path, git);
122
+ const clean = await worktreeClean(path, git);
123
+ const onRemote = branch ? await branchPushed(await projectRoot(path, git), branch, git) : false;
124
+ return { ok: true, path, ...(branch ? { branch } : {}), clean, onRemote };
125
+ },
126
+ async show(args, cwd, git) {
127
+ const { values, positionals } = parse(args, { from: { type: 'string' } }, 1, Infinity);
128
+ if (values.from !== undefined && !values.from.trim())
129
+ throw new Usage('--from names a commit');
130
+ const repo = await project(cwd, git);
131
+ return readBranchStates(repo, positionals, git, values.from);
132
+ },
133
+ async push(args, cwd, git) {
134
+ const { values } = parse(args, { branch: { type: 'string' } }, 0);
135
+ if (values.branch !== undefined) {
136
+ if (!values.branch.trim())
137
+ throw new Usage('--branch names a branch');
138
+ const outcome = await pushBranchByName(await project(cwd, git), values.branch, git);
139
+ if (!outcome.ok)
140
+ throw new Refused(outcome, pushRefusalLine(values.branch, outcome));
141
+ return outcome;
142
+ }
143
+ const checkout = await inRepo(() => checkoutRoot(cwd, git));
144
+ const outcome = await pushCheckout(checkout, git);
145
+ if (!outcome.ok)
146
+ throw new Refused(outcome, pushRefusalLine(checkout, outcome));
147
+ return outcome;
148
+ },
149
+ async merge(args, cwd, git) {
150
+ const { positionals } = parse(args, {}, 1);
151
+ const repo = await project(cwd, git);
152
+ const outcome = await mergeBranch(repo, positionals[0], git);
153
+ if (!outcome.ok)
154
+ throw new Refused(outcome, mergeRefusalLine(outcome));
155
+ // A link named after a branch that just went is stale from this moment.
156
+ if (outcome.deleted)
157
+ await reconcileBranchLinks(repo, { git });
158
+ return outcome;
159
+ },
160
+ async list(args, cwd, git) {
161
+ const { values } = parse(args, { sizes: { type: 'boolean' } }, 0);
162
+ const repo = await project(cwd, git);
163
+ const rows = [];
164
+ for (const entry of await worktreeDirEntries(repo)) {
165
+ const branch = await worktreeBranch(entry.path, git);
166
+ const sizeBytes = values.sizes ? await worktreeSize(entry.path) : undefined;
167
+ const name = sessionNameOf(branch, entry.agentId);
168
+ rows.push({ ...entry, ...(branch ? { branch } : {}), ...(name ? { name } : {}), ...(sizeBytes === undefined ? {} : { sizeBytes }) });
169
+ }
170
+ return rows;
171
+ },
172
+ async remove(args, cwd, git) {
173
+ const { positionals, values } = parse(args, { discard: { type: 'boolean' }, from: { type: 'string' } }, 1);
174
+ if (values.from !== undefined && !values.from.trim())
175
+ throw new Usage('--from names a commit');
176
+ const agentId = agentIdArg(positionals[0]);
177
+ const repo = await project(cwd, git);
178
+ const outcome = values.discard ? await discard(repo, agentId, git) : await reclaim(repo, agentId, git, values.from);
179
+ if (!outcome.ok)
180
+ throw new Refused(outcome, refusalLine(agentId, outcome));
181
+ // A link named after a branch that just went with its checkout is stale from this moment.
182
+ await reconcileBranchLinks(repo, { git });
183
+ return outcome;
184
+ },
185
+ async prune(args, cwd, git) {
186
+ parse(args, {}, 0);
187
+ const repo = await project(cwd, git);
188
+ const removed = [];
189
+ const skipped = [];
190
+ for (const { agentId } of await worktreeDirEntries(repo)) {
191
+ const outcome = await reclaim(repo, agentId, git);
192
+ if (outcome.ok)
193
+ removed.push(agentId);
194
+ else
195
+ skipped.push({ agentId, reason: outcome.reason, detail: refusalLine(agentId, outcome) });
196
+ }
197
+ // Once for the whole pass: a reconcile reads every checkout that is left.
198
+ if (removed.length)
199
+ await reconcileBranchLinks(repo, { git });
200
+ return { ok: true, removed, skipped };
201
+ },
202
+ };
203
+ /**
204
+ * One agent's checkout under the reclaim rule; a missing checkout is its own refusal. The
205
+ * argument may be the session name a rename link carries rather than the id, so the birth
206
+ * branch is read off the checkout's own directory, never off the argument (#1757): a link's
207
+ * name is the branch the agent chose, and that one is not the birth branch.
208
+ */
209
+ async function reclaim(repo, agentId, git, from) {
210
+ const path = worktreePath(repo, agentId);
211
+ if (!(await stat(path).then(s => s.isDirectory(), () => false)))
212
+ return { ok: false, reason: 'no-checkout', agentId };
213
+ const checkout = await realpath(path);
214
+ return reclaimWorktree(repo, checkout, { birthBranch: agentBranchName(agentIdFromWorktreeDir(basename(checkout))), ...(from !== undefined ? { from } : {}), git });
215
+ }
216
+ /** One agent's checkout dropped whatever it holds; a missing checkout is its own refusal. */
217
+ async function discard(repo, agentId, git) {
218
+ const path = worktreePath(repo, agentId);
219
+ if (!(await stat(path).then(s => s.isDirectory(), () => false)))
220
+ return { ok: false, reason: 'no-checkout', agentId };
221
+ return discardWorktree(repo, await realpath(path), { git });
222
+ }
223
+ /** Why a checkout stayed, as one line for a person. */
224
+ function refusalLine(agentId, outcome) {
225
+ const reason = outcome.reason;
226
+ switch (reason) {
227
+ case 'no-checkout':
228
+ return `no checkout for agent ${agentId}`;
229
+ case 'not-a-worktree':
230
+ return `agent ${agentId}'s directory is not a git worktree; left alone`;
231
+ case 'no-branch':
232
+ return `agent ${agentId}'s checkout is on no branch; kept`;
233
+ case 'dirty':
234
+ return `${branchOf(outcome)} has uncommitted work; the checkout was kept`;
235
+ }
236
+ }
237
+ const branchOf = (outcome) => String(outcome.branch);
238
+ /** Why a branch was not pushed, as one line for a person. */
239
+ function mergeRefusalLine(outcome) {
240
+ switch (outcome.reason) {
241
+ case 'no-branch':
242
+ return `no branch ${outcome.branch} on this machine`;
243
+ case 'no-default-branch':
244
+ return 'the project has no main branch to merge into';
245
+ case 'not-on-default':
246
+ return `the project's folder is on ${outcome.current ?? 'no branch'}, not on ${outcome.into}; switch it to ${outcome.into}, then merge`;
247
+ case 'dirty':
248
+ return `${outcome.branch} has uncommitted work; commit or delete it, then merge`;
249
+ case 'conflict':
250
+ return `${outcome.branch} does not merge cleanly into ${outcome.into}: it conflicts in ${outcome.files.join(', ')}; nothing was changed`;
251
+ case 'merge-failed':
252
+ return `${outcome.branch} could not be merged into ${outcome.into}: ${outcome.detail}`;
253
+ }
254
+ }
255
+ function pushRefusalLine(subject, outcome) {
256
+ switch (outcome.reason) {
257
+ case 'not-a-worktree':
258
+ return `${subject} is not a git worktree`;
259
+ case 'no-branch':
260
+ return outcome.branch !== undefined ? `no branch ${outcome.branch}, here or on origin` : `${subject} is on no branch`;
261
+ case 'dirty':
262
+ return `${outcome.branch} has uncommitted work; commit or delete it, then push`;
263
+ case 'push-failed':
264
+ return `${outcome.branch} could not be pushed: ${outcome.detail ?? 'the push did not land'}`;
265
+ }
266
+ }
267
+ const NAME_REFUSALS = {
268
+ 'invalid-name': name => `${name} is not a session name: use [a-z0-9-]+`,
269
+ 'not-a-worktree': (_, checkout) => `${checkout} is not a git worktree`,
270
+ 'no-branch': (_, checkout) => `${checkout} is on no branch`,
271
+ 'not-an-agent-branch': (_, checkout) => `${checkout} is not on an agent branch; only agent-* branches are renamed`,
272
+ };
273
+ /** An agent id is path-safe, or the command has nothing to name a checkout with. */
274
+ function agentIdArg(agentId) {
275
+ if (!isSafeAgentId(agentId))
276
+ throw new Refused({ ok: false, reason: 'invalid-id', agentId }, `${agentId} is not an agent id`);
277
+ return agentId;
278
+ }
279
+ /** The project the working directory belongs to. */
280
+ async function project(cwd, git) {
281
+ return inRepo(() => projectRoot(cwd, git));
282
+ }
283
+ /**
284
+ * Outside a repo, the commands have nothing to act on: said as a refusal, not a git failure.
285
+ * Only git's own "not a git repository" reads as that; a timeout, a missing git, or a corrupt
286
+ * repo stays the failure it is.
287
+ */
288
+ async function inRepo(read) {
289
+ try {
290
+ return await read();
291
+ }
292
+ catch (err) {
293
+ if (!/not a git repository/i.test(err instanceof Error ? err.message : String(err)))
294
+ throw err;
295
+ throw new Refused({ ok: false, reason: 'not-a-repo' }, 'not inside a git repository');
296
+ }
297
+ }
298
+ /** `parseArgs` with the positional count checked: too few or too many is a usage error. */
299
+ function parse(args, options, min, max = min) {
300
+ try {
301
+ const parsed = parseArgs({ args, options, allowPositionals: true, strict: true });
302
+ if (parsed.positionals.length < min || parsed.positionals.length > max)
303
+ throw new Usage(`expected ${max === min ? min : max === Infinity ? `at least ${min}` : `${min} to ${max}`} argument(s), got ${parsed.positionals.length}`);
304
+ return parsed;
305
+ }
306
+ catch (err) {
307
+ throw err instanceof Usage ? err : new Usage(err instanceof Error ? err.message : String(err));
308
+ }
309
+ }
310
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type GitRunner } from '@openagt/agent-data';
2
+ /**
3
+ * This package's command, linked into a checkout that has none: `node_modules/.bin/branches`.
4
+ *
5
+ * The skill tells the agent to run `npx branches`. In a project that depends on this package the
6
+ * checkout already has the command, linked with the project's other tools. In a project with
7
+ * nothing installed (an empty folder a dashboard added) it has none, and `npx` then downloads
8
+ * whatever package holds the name `branches` on npm and runs it: a stranger's code, in the
9
+ * person's project. With the command linked here `npx` finds it in the checkout and downloads
10
+ * nothing.
11
+ *
12
+ * Hidden from the project's git through the repository's exclude file, like the skill's link: the
13
+ * link is the package's state, not the agent's work. Best-effort: a command already there (the
14
+ * project's own copy) is left alone, and a link that cannot be made is a worse run, not a failed
15
+ * one.
16
+ */
17
+ /** The command's name, and where a checkout holds its tools. */
18
+ export declare const COMMAND = "branches";
19
+ export declare function linkOwnCommand(repo: string, checkout: string, git?: GitRunner): Promise<void>;
20
+ //# sourceMappingURL=command-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-link.d.ts","sourceRoot":"","sources":["../src/command-link.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAGnF;;;;;;;;;;;;;;GAcG;AAEH,gEAAgE;AAChE,eAAO,MAAM,OAAO,aAAa,CAAA;AAGjC,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,SAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAWpH"}
@@ -0,0 +1,37 @@
1
+ import { lstat, mkdir, symlink } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { excludeFromGit, nodeGitRunner } from '@openagt/agent-data';
4
+ import { CLI_BIN_DIR } from './bin-dir.js';
5
+ /**
6
+ * This package's command, linked into a checkout that has none: `node_modules/.bin/branches`.
7
+ *
8
+ * The skill tells the agent to run `npx branches`. In a project that depends on this package the
9
+ * checkout already has the command, linked with the project's other tools. In a project with
10
+ * nothing installed (an empty folder a dashboard added) it has none, and `npx` then downloads
11
+ * whatever package holds the name `branches` on npm and runs it: a stranger's code, in the
12
+ * person's project. With the command linked here `npx` finds it in the checkout and downloads
13
+ * nothing.
14
+ *
15
+ * Hidden from the project's git through the repository's exclude file, like the skill's link: the
16
+ * link is the package's state, not the agent's work. Best-effort: a command already there (the
17
+ * project's own copy) is left alone, and a link that cannot be made is a worse run, not a failed
18
+ * one.
19
+ */
20
+ /** The command's name, and where a checkout holds its tools. */
21
+ export const COMMAND = 'branches';
22
+ const BIN_DIR = 'node_modules/.bin';
23
+ export async function linkOwnCommand(repo, checkout, git = nodeGitRunner()) {
24
+ const link = join(checkout, BIN_DIR, COMMAND);
25
+ // lstat, not stat: a link that is there counts, whatever it points at.
26
+ if (await lstat(link).then(() => true, () => false))
27
+ return;
28
+ await excludeFromGit(repo, `/${BIN_DIR}/${COMMAND}`, undefined, git).catch(() => { });
29
+ try {
30
+ await mkdir(join(checkout, BIN_DIR), { recursive: true });
31
+ await symlink(join(CLI_BIN_DIR, COMMAND), link);
32
+ }
33
+ catch {
34
+ // A filesystem that refuses the link: the agent still starts.
35
+ }
36
+ }
37
+ //# sourceMappingURL=command-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-link.js","sourceRoot":"","sources":["../src/command-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAkB,MAAM,qBAAqB,CAAA;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C;;;;;;;;;;;;;;GAcG;AAEH,gEAAgE;AAChE,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,CAAA;AACjC,MAAM,OAAO,GAAG,mBAAmB,CAAA;AAEnC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,QAAgB,EAAE,GAAG,GAAc,aAAa,EAAE;IACnG,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IAC7C,uEAAuE;IACvE,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC;QAAE,OAAM;IAC3D,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,OAAO,IAAI,OAAO,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IACpF,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACzD,MAAM,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,CAAA;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,8DAA8D;IAChE,CAAC;AACH,CAAC"}
@@ -0,0 +1,150 @@
1
+ import { type GitRunner } from './git.js';
2
+ /** The persistent checkout of `branch` under a project: `<repo>/.branches/<branch>`. */
3
+ export declare function fileBranchPath(repo: string, branch: string): string;
4
+ /** Injectable seams; production takes the defaults. */
5
+ export interface FileBranchDeps {
6
+ git?: GitRunner;
7
+ log?: (message: string) => void;
8
+ }
9
+ /**
10
+ * Make sure the branch and its persistent checkout exist. Idempotent and cheap when everything is
11
+ * in place (one `git rev-parse` against the checkout); never throws — a project this cannot be
12
+ * set up in reports why and is left alone.
13
+ */
14
+ export declare function ensureFileBranch(repo: string, branch: string, deps?: FileBranchDeps): Promise<{
15
+ ok: boolean;
16
+ error?: string;
17
+ }>;
18
+ /**
19
+ * What one funneled write did. `changed: false` is the clean no-op (the op wrote nothing new).
20
+ * A failure says whether the change still landed as a local commit (`committed` — the push is
21
+ * what failed, and the next cycle carries it out) or nothing survived at all.
22
+ */
23
+ export type FileBranchWrite = {
24
+ ok: true;
25
+ changed: boolean;
26
+ pushed: boolean;
27
+ } | {
28
+ ok: false;
29
+ committed: boolean;
30
+ error: string;
31
+ };
32
+ /** A writer's message: fixed, or resolved after the op ran (a batch only knows what it did then). */
33
+ export type CommitMessage = string | (() => string);
34
+ /**
35
+ * Apply one change to the branch through its persistent checkout: sync with origin, run `op`
36
+ * against the checkout, commit whatever it changed, push. The single funnel a long-lived
37
+ * process's writes go through.
38
+ *
39
+ * `op` must be re-runnable: when the push loses a race with another writer, the cycle re-syncs
40
+ * and runs it again against the fresher state rather than force-fitting a stale commit — the op
41
+ * *is* the intent, the commit is just its serialization. Two attempts; a push that still fails
42
+ * (the network, most likely) keeps the commit local and reports the error — the next cycle's sync
43
+ * rebases it onto whatever origin has by then, and the next push carries it.
44
+ *
45
+ * Never throws: callers run on background ticks with nothing to catch it.
46
+ */
47
+ export declare function withFileBranch(repo: string, branch: string, message: CommitMessage, op: (dir: string) => Promise<void>, deps?: FileBranchDeps): Promise<FileBranchWrite>;
48
+ /** How a pull went: converged with origin, or why it could not. */
49
+ export type FileBranchSync = {
50
+ ok: true;
51
+ } | {
52
+ ok: false;
53
+ error: string;
54
+ };
55
+ /**
56
+ * The eager pull: sync the persistent checkout with origin so this machine reads what other
57
+ * machines and cloud sessions committed, without waiting for the next local write — and push
58
+ * anything a failed cycle left stranded locally, via the same owed-push rule as the writer.
59
+ * Ensures the checkout exists, so a fresh clone converges on its first tick. Never throws.
60
+ *
61
+ * Reports why it could not converge: a push origin rejects, or no origin to converge with at
62
+ * all. The writer treats a remote-less repo as fine — the commit is safe locally — but a sync's
63
+ * whole job is to meet the other machines, and a repo nothing can reach is an error state the
64
+ * caller has to surface, not a mode this supports.
65
+ */
66
+ export declare function pullFileBranch(repo: string, branch: string, deps?: FileBranchDeps): Promise<FileBranchSync>;
67
+ /**
68
+ * The repository `cwd` belongs to: the directory holding the repo's real `.git`. From the main
69
+ * checkout that is `cwd` itself; from an agent's worktree it is the repo the worktree was made
70
+ * from — where the persistent checkout lives, and the address every funneled write goes to.
71
+ * `undefined` outside any repo.
72
+ */
73
+ export declare function fileBranchRepo(cwd: string, git?: GitRunner): Promise<string | undefined>;
74
+ /**
75
+ * Read one file off the branch, from anywhere in the repo: the persistent checkout when this
76
+ * `cwd`'s repo has one, else `git show` against the local branch (worktrees share the repo's
77
+ * refs, so an agent's checkout reads the same files without holding any of them), else against
78
+ * `origin/…`. `undefined` when the file exists in none of them. Never throws.
79
+ */
80
+ export declare function readBranchFile(cwd: string, branch: string, rel: string, opts?: {
81
+ fresh?: boolean;
82
+ }, deps?: FileBranchDeps): Promise<string | undefined>;
83
+ /**
84
+ * The entries of one directory on the branch, by name — a read off the ref, like
85
+ * {@link readBranchFile}, for a reader that holds no checkout. `[]` for a directory the branch
86
+ * does not have. Never throws.
87
+ */
88
+ export declare function listBranchDir(cwd: string, branch: string, dir: string, opts?: {
89
+ fresh?: boolean;
90
+ }, deps?: FileBranchDeps): Promise<string[]>;
91
+ /** Reads off one ref of the branch, for a reader that opens many files: fetched once, up front. */
92
+ export interface BranchReader {
93
+ /** The ref every read goes to: origin's copy when the repo has a remote, else the local branch. */
94
+ ref: string;
95
+ /** One file's content, or `undefined` when the ref has no such file. */
96
+ read: (rel: string) => Promise<string | undefined>;
97
+ /** One directory's entries by name; `[]` when the ref has no such directory. */
98
+ list: (dir: string) => Promise<string[]>;
99
+ }
100
+ /**
101
+ * Open the branch for reading from any clone, holding no checkout: fetch origin's copy once and
102
+ * read that (a one-shot reader — a command an agent runs — must see what other writers pushed,
103
+ * its own detached write included, which the local ref never moves for), else the local branch
104
+ * when there is no remote. Every read then goes to that one ref; nothing is fetched again.
105
+ */
106
+ export declare function openBranchReader(cwd: string, branch: string, deps?: FileBranchDeps): Promise<BranchReader>;
107
+ /** What a detached write did, or why it could not: the repo has no remote to carry it. */
108
+ export type DetachedWrite = {
109
+ ok: true;
110
+ changed: boolean;
111
+ } | {
112
+ ok: false;
113
+ reason: 'no-remote';
114
+ };
115
+ /**
116
+ * Apply one change to the branch as a one-shot remote writer, from any clone: fetch origin's
117
+ * tip, check it out in a throwaway worktree, run `op` against it, commit, push straight to the
118
+ * branch, remove the worktree. A push that loses a race re-fetches, resets to origin's tip and
119
+ * re-runs the op, twice, the same intent rule as the funnel; a push that still fails throws with
120
+ * git's reason.
121
+ *
122
+ * Never the persistent checkout: that is a long-lived process's, whose funnel sweeps its
123
+ * checkout with `git add -A` and resets it on failure, so a second writer inside it would be
124
+ * committed under the wrong message or wiped. The local branch ref is not moved either — the
125
+ * persistent checkout, when there is one, converges on its own next pull.
126
+ *
127
+ * A branch origin does not have yet is born by the write itself, parentless. A repo with no
128
+ * remote refuses: a change nothing can reach is the caller's error state, not a mode.
129
+ */
130
+ export declare function writeFileBranchDetached(cwd: string, branch: string, message: CommitMessage, op: (dir: string) => Promise<void>, deps?: FileBranchDeps): Promise<DetachedWrite>;
131
+ /**
132
+ * The file seams an operation inside a funneled write needs — read, write, delete and list plain
133
+ * files under the directory the funnel hands it. Injectable so an operation is unit-testable off
134
+ * disk and git; production takes the defaults. Parents are created on write because the
135
+ * directory itself is not a given: retiring the last file removes it (git keeps no empty dirs),
136
+ * and a branch is born without it.
137
+ */
138
+ export interface BranchFileFs {
139
+ /** Read one file; rejects when it cannot — a caller reads the rejection as "absent". */
140
+ read: (path: string) => Promise<string>;
141
+ /** Write one file, creating parents. */
142
+ write: (path: string, content: string) => Promise<void>;
143
+ /** Delete one file. */
144
+ remove: (path: string) => Promise<void>;
145
+ /** The files under a directory, by filename; a missing directory reads as none. */
146
+ list: (dir: string) => Promise<string[]>;
147
+ }
148
+ /** {@link BranchFileFs} over `node:fs/promises`. */
149
+ export declare function nodeBranchFileFs(): BranchFileFs;
150
+ //# sourceMappingURL=file-branch.d.ts.map