@senad-d/branchme 0.1.6 → 0.1.8
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/.env.example +6 -2
- package/CHANGELOG.md +9 -5
- package/README.md +96 -19
- package/SECURITY.md +25 -7
- package/docs/PROJECT_DEFINITION_BRIEF.md +112 -77
- package/docs/SMOKE_TEST.md +21 -11
- package/docs/STRUCTURE.md +22 -17
- package/docs/TUI_CAPTURE.md +53 -22
- package/package.json +11 -7
- package/src/commands/branchme-command.ts +10 -1
- package/src/constants.ts +15 -0
- package/src/git-context.ts +22 -2
- package/src/git.ts +1124 -4
- package/src/github.ts +76 -27
- package/src/tools/branchme-tools.ts +340 -21
- package/src/types.ts +120 -0
- package/src/ui/branchme-panel.ts +14 -3
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senad-d/branchme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Pi extension for verified current-repository Git branch, worktree, push, and pull request workflows.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Senad Dizdarević <112484166+senad-d@users.noreply.github.com>",
|
|
8
8
|
"repository": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"branchme",
|
|
22
22
|
"git",
|
|
23
23
|
"git-branch",
|
|
24
|
+
"git-worktree",
|
|
24
25
|
"git-workflow",
|
|
25
26
|
"github",
|
|
26
27
|
"github-pr",
|
|
@@ -52,10 +53,11 @@
|
|
|
52
53
|
"test:coverage": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=spec --test-reporter=lcov --test-reporter-destination=stdout --test-reporter-destination=coverage/lcov.info test/*.test.mjs",
|
|
53
54
|
"smoke:pi": "node scripts/smoke-pi-runtime.mjs && node scripts/smoke-pi-git-context.mjs",
|
|
54
55
|
"smoke:pi:packed": "node scripts/smoke-pi-installed-package.mjs",
|
|
55
|
-
"
|
|
56
|
+
"smoke:worktree-handoff": "node scripts/smoke-worktree-handoff.mjs",
|
|
57
|
+
"check": "node --check scripts/check-format.mjs && node --check scripts/check-package-contents.mjs && node --check scripts/publish-npm.mjs && node --check scripts/smoke-pi-git-context.mjs && node --check scripts/smoke-pi-installed-package.mjs && node --check scripts/smoke-pi-runtime.mjs && node --check scripts/smoke-worktree-handoff.mjs",
|
|
56
58
|
"pack:dry-run": "npm pack --dry-run --json",
|
|
57
59
|
"check:pack": "node scripts/check-package-contents.mjs",
|
|
58
|
-
"validate": "npm run typecheck && npm run format:check && npm run test && npm run smoke:pi && npm run check && npm run check:pack",
|
|
60
|
+
"validate": "npm run typecheck && npm run format:check && npm run test && npm run smoke:pi && npm run check && npm run check:pack && npm run smoke:worktree-handoff",
|
|
59
61
|
"release:check": "npm run validate && npm run smoke:pi:packed"
|
|
60
62
|
},
|
|
61
63
|
"pi": {
|
|
@@ -65,15 +67,17 @@
|
|
|
65
67
|
"image": "https://raw.githubusercontent.com/senad-d/BranchMe/main/img/demo.gif"
|
|
66
68
|
},
|
|
67
69
|
"peerDependencies": {
|
|
70
|
+
"@earendil-works/pi-ai": "*",
|
|
68
71
|
"@earendil-works/pi-coding-agent": "*",
|
|
69
72
|
"@earendil-works/pi-tui": "*",
|
|
70
73
|
"typebox": "*"
|
|
71
74
|
},
|
|
72
75
|
"devDependencies": {
|
|
73
|
-
"@earendil-works/pi-
|
|
74
|
-
"@earendil-works/pi-
|
|
76
|
+
"@earendil-works/pi-ai": "^0.84.1",
|
|
77
|
+
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
78
|
+
"@earendil-works/pi-tui": "^0.84.1",
|
|
75
79
|
"@types/node": "^26.0.1",
|
|
76
80
|
"typebox": "^1.2.18",
|
|
77
|
-
"typescript": "^
|
|
81
|
+
"typescript": "^7.0.2"
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -25,7 +25,7 @@ export function getBranchMeHelpText(): string {
|
|
|
25
25
|
return [
|
|
26
26
|
"# BranchMe",
|
|
27
27
|
"",
|
|
28
|
-
"Current-repository
|
|
28
|
+
"Current-repository Git workflow tools for Pi.",
|
|
29
29
|
"",
|
|
30
30
|
"Commands only show info; BranchMe tools perform actions.",
|
|
31
31
|
"",
|
|
@@ -40,10 +40,19 @@ export function getBranchMeHelpText(): string {
|
|
|
40
40
|
"7. `push_branch` — push the current branch.",
|
|
41
41
|
"8. `pull_request` — open a PR after `push_branch` completes and GitHub sees the branches.",
|
|
42
42
|
"",
|
|
43
|
+
"## Worktree handoff",
|
|
44
|
+
"",
|
|
45
|
+
"- `list_worktrees` — inspect the main and linked worktrees in the current repository.",
|
|
46
|
+
"- `create_worktree` — create a linked worktree and return a ready handoff with an absolute `handoff.cwd`.",
|
|
47
|
+
"- A separate orchestrator starts the next Pi session or subagent in `handoff.cwd`.",
|
|
48
|
+
"- `remove_worktree` — remove a verified clean linked worktree while retaining its local branch.",
|
|
49
|
+
"- BranchMe does not change cwd, start Pi, copy `.env`, or remove branches automatically.",
|
|
50
|
+
"",
|
|
43
51
|
"## Requirements",
|
|
44
52
|
"",
|
|
45
53
|
"- Run inside a Git repo with `git` available.",
|
|
46
54
|
"- For PRs: GitHub `origin` and `GITHUB_TOKEN` or `GH_TOKEN` (environment or `.env`).",
|
|
55
|
+
"- Optional: set `BRANCHME_PR_AUTOFILL=true` in the environment or `.env` to generate omitted PR fields.",
|
|
47
56
|
"- `fetch_branch`, `pull_branch`, and `rebase_branch` require a configured upstream.",
|
|
48
57
|
"- `pull_branch` and `rebase_branch` require a clean working tree.",
|
|
49
58
|
"- `rebase_branch` rewrites local commits only when explicitly requested and auto-aborts on failure.",
|
package/src/constants.ts
CHANGED
|
@@ -9,6 +9,10 @@ export const PULL_BRANCH_TOOL_NAME = "pull_branch";
|
|
|
9
9
|
export const REBASE_BRANCH_TOOL_NAME = "rebase_branch";
|
|
10
10
|
export const PUSH_BRANCH_TOOL_NAME = "push_branch";
|
|
11
11
|
export const PULL_REQUEST_TOOL_NAME = "pull_request";
|
|
12
|
+
export const LIST_WORKTREES_TOOL_NAME = "list_worktrees";
|
|
13
|
+
export const CREATE_WORKTREE_TOOL_NAME = "create_worktree";
|
|
14
|
+
export const REMOVE_WORKTREE_TOOL_NAME = "remove_worktree";
|
|
15
|
+
export const PULL_REQUEST_AUTOFILL_ENV_NAME = "BRANCHME_PR_AUTOFILL";
|
|
12
16
|
|
|
13
17
|
export const BRANCHME_TOOL_NAMES = [
|
|
14
18
|
BRANCH_STATUS_TOOL_NAME,
|
|
@@ -19,6 +23,9 @@ export const BRANCHME_TOOL_NAMES = [
|
|
|
19
23
|
REBASE_BRANCH_TOOL_NAME,
|
|
20
24
|
PUSH_BRANCH_TOOL_NAME,
|
|
21
25
|
PULL_REQUEST_TOOL_NAME,
|
|
26
|
+
LIST_WORKTREES_TOOL_NAME,
|
|
27
|
+
CREATE_WORKTREE_TOOL_NAME,
|
|
28
|
+
REMOVE_WORKTREE_TOOL_NAME,
|
|
22
29
|
] as const;
|
|
23
30
|
|
|
24
31
|
export const GIT_STATUS_TIMEOUT_MS = 5_000;
|
|
@@ -27,12 +34,20 @@ export const GIT_FETCH_TIMEOUT_MS = 120_000;
|
|
|
27
34
|
export const GIT_PULL_TIMEOUT_MS = 120_000;
|
|
28
35
|
export const GIT_REBASE_TIMEOUT_MS = 120_000;
|
|
29
36
|
export const GIT_PUSH_TIMEOUT_MS = 120_000;
|
|
37
|
+
export const GIT_WORKTREE_MUTATION_TIMEOUT_MS = 120_000;
|
|
30
38
|
export const GITHUB_RELATED_PR_TIMEOUT_MS = 4_000;
|
|
31
39
|
|
|
32
40
|
export const GIT_CONTEXT_RECENT_COMMIT_LIMIT = 5;
|
|
33
41
|
export const GIT_CONTEXT_CHANGE_LIMIT = 20;
|
|
34
42
|
export const GIT_CONTEXT_VALUE_LIMIT_CHARS = 512;
|
|
35
43
|
export const GIT_CONTEXT_SUMMARY_LIMIT_CHARS = 4_000;
|
|
44
|
+
export const PULL_REQUEST_AUTOFILL_COMMIT_LIMIT = 20;
|
|
45
|
+
export const PULL_REQUEST_AUTOFILL_SUBJECT_LIMIT_CHARS = 256;
|
|
46
|
+
export const GIT_WORKTREE_RAW_OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
47
|
+
export const GIT_WORKTREE_ENTRY_LIMIT = 100;
|
|
48
|
+
export const GIT_WORKTREE_PATH_LIMIT_CHARS = 4_096;
|
|
49
|
+
export const GIT_WORKTREE_REASON_LIMIT_CHARS = 512;
|
|
50
|
+
export const GIT_WORKTREE_SUMMARY_LIMIT_CHARS = 4_000;
|
|
36
51
|
|
|
37
52
|
export const GITHUB_API_BASE_URL = "https://api.github.com";
|
|
38
53
|
export const GITHUB_API_VERSION = "2022-11-28";
|
package/src/git-context.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
getWorkingTreeStatus,
|
|
20
20
|
type GitCommandContext,
|
|
21
21
|
} from "./git.ts";
|
|
22
|
-
import { lookupRelatedPullRequest } from "./github.ts";
|
|
22
|
+
import { lookupRelatedPullRequest, resolvePullRequestAutofill } from "./github.ts";
|
|
23
23
|
import { redactSecrets } from "./redaction.ts";
|
|
24
24
|
import type { GitContextDetails, GitFileChange, RecentCommit, RelatedPullRequest } from "./types.ts";
|
|
25
25
|
|
|
@@ -138,6 +138,7 @@ export async function collectGitContext(
|
|
|
138
138
|
const recentCommits = await getRecentCommits(pi, rootCtx, options.signal);
|
|
139
139
|
const upstream = current.detached ? null : await getUpstreamBranch(pi, rootCtx, options.signal);
|
|
140
140
|
const warnings: string[] = [];
|
|
141
|
+
let pullRequestAutofill: boolean | null = null;
|
|
141
142
|
let ahead: number | null = null;
|
|
142
143
|
let behind: number | null = null;
|
|
143
144
|
|
|
@@ -152,6 +153,13 @@ export async function collectGitContext(
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
try {
|
|
157
|
+
pullRequestAutofill = await resolvePullRequestAutofill(options.env, { cwd: repoRoot, signal: options.signal });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throwIfCollectionAborted(options.signal);
|
|
160
|
+
warnings.push(`pull request autofill configuration unavailable (${safeFailureType(error)})`);
|
|
161
|
+
}
|
|
162
|
+
|
|
155
163
|
const relatedPullRequest = await collectRelatedPullRequest(pi, rootCtx, options);
|
|
156
164
|
|
|
157
165
|
return {
|
|
@@ -166,6 +174,7 @@ export async function collectGitContext(
|
|
|
166
174
|
...(relatedPullRequest.status === "found"
|
|
167
175
|
? { githubRepository: relatedPullRequest.pullRequest.repository }
|
|
168
176
|
: {}),
|
|
177
|
+
pullRequestAutofill,
|
|
169
178
|
workingTree: workingTreeStatus.workingTree,
|
|
170
179
|
unstagedChanges: workingTreeStatus.unstagedChanges,
|
|
171
180
|
relatedPullRequest,
|
|
@@ -223,6 +232,13 @@ function formatRelatedPullRequest(details: GitContextDetails, valueLimit: number
|
|
|
223
232
|
return `- Related PR: #${safeNonNegativeInteger(pullRequest.number)} ${quoteMetadata(pullRequest.title, valueLimit)}; repository ${quoteMetadata(repository, valueLimit)}; ${quoteMetadata(pullRequest.head, valueLimit)} -> ${quoteMetadata(pullRequest.base, valueLimit)}; ${quoteMetadata(pullRequest.url, valueLimit)}; ${quoteMetadata(pullRequest.state, valueLimit)}${draft}`;
|
|
224
233
|
}
|
|
225
234
|
|
|
235
|
+
function formatPullRequestAutofill(details: GitContextDetails): string {
|
|
236
|
+
let state = "unavailable";
|
|
237
|
+
if (details.pullRequestAutofill === true) state = "enabled";
|
|
238
|
+
if (details.pullRequestAutofill === false) state = "disabled";
|
|
239
|
+
return `- Pull request field autofill: ${state} (BRANCHME_PR_AUTOFILL)`;
|
|
240
|
+
}
|
|
241
|
+
|
|
226
242
|
function appendRecentCommits(lines: string[], selection: FormatSelection): void {
|
|
227
243
|
if (selection.commits.length === 0 && selection.omittedCommits === 0) {
|
|
228
244
|
lines.push("- Recent commits: none");
|
|
@@ -257,7 +273,10 @@ function renderGitContext(
|
|
|
257
273
|
formatWorkingTree(details),
|
|
258
274
|
];
|
|
259
275
|
appendUnstagedChanges(lines, selection);
|
|
260
|
-
lines.push(
|
|
276
|
+
lines.push(
|
|
277
|
+
formatRelatedPullRequest(details, selection.valueLimit),
|
|
278
|
+
formatPullRequestAutofill(details),
|
|
279
|
+
);
|
|
261
280
|
appendRecentCommits(lines, selection);
|
|
262
281
|
lines.push(
|
|
263
282
|
"",
|
|
@@ -295,6 +314,7 @@ function boundedFallbackContext(mode: GitContextFormatMode): string {
|
|
|
295
314
|
"- Working tree: unavailable",
|
|
296
315
|
"- Unstaged changes: [entries omitted]",
|
|
297
316
|
"- Related PR: unavailable",
|
|
317
|
+
"- Pull request field autofill: unavailable",
|
|
298
318
|
"- Recent commits: [entries omitted]",
|
|
299
319
|
"",
|
|
300
320
|
"Call branch_status to request a current bounded snapshot.",
|