@senad-d/branchme 0.1.8 → 0.2.0
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/CHANGELOG.md +8 -5
- package/README.md +87 -14
- package/SECURITY.md +38 -4
- package/docs/PROJECT_DEFINITION_BRIEF.md +51 -17
- package/docs/SMOKE_TEST.md +10 -7
- package/docs/STRUCTURE.md +30 -18
- package/docs/TUI_CAPTURE.md +91 -32
- package/package.json +2 -2
- package/src/commands/branchme-command.ts +16 -2
- package/src/constants.ts +11 -0
- package/src/git-context.ts +81 -16
- package/src/git-integration.ts +673 -0
- package/src/git-retirement.ts +681 -0
- package/src/git.ts +313 -7
- package/src/tools/branchme-tools.ts +159 -4
- package/src/types.ts +191 -0
- package/src/ui/branchme-panel.ts +19 -3
package/src/types.ts
CHANGED
|
@@ -20,6 +20,27 @@ export interface WorkingTreeDetails {
|
|
|
20
20
|
untracked: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export interface BranchStatusAncestryQuery {
|
|
24
|
+
sourceBranch: string;
|
|
25
|
+
targetBranch: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface BranchStatusToolInput {
|
|
29
|
+
ancestry?: BranchStatusAncestryQuery;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface IntegrateBranchToolInput {
|
|
33
|
+
sourceBranch: string;
|
|
34
|
+
targetBranch: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RetireBranchToolInput {
|
|
38
|
+
branchName: string;
|
|
39
|
+
expectedHead: string;
|
|
40
|
+
targetBranch: string;
|
|
41
|
+
force: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
23
44
|
export type CreateWorktreeMode = "new" | "existing";
|
|
24
45
|
|
|
25
46
|
export type ListWorktreesToolInput = Record<string, never>;
|
|
@@ -165,6 +186,12 @@ export type RelatedPullRequest =
|
|
|
165
186
|
| { status: "none" }
|
|
166
187
|
| { status: "unavailable"; reason: string };
|
|
167
188
|
|
|
189
|
+
export interface BranchAncestryDetails extends BranchStatusAncestryQuery {
|
|
190
|
+
sourceHead: string;
|
|
191
|
+
targetHead: string;
|
|
192
|
+
isAncestor: boolean;
|
|
193
|
+
}
|
|
194
|
+
|
|
168
195
|
export interface BranchStatusDetails {
|
|
169
196
|
repoRoot: string;
|
|
170
197
|
currentBranch: string | null;
|
|
@@ -173,6 +200,7 @@ export interface BranchStatusDetails {
|
|
|
173
200
|
hasChanges: boolean;
|
|
174
201
|
ahead: number | null;
|
|
175
202
|
behind: number | null;
|
|
203
|
+
ancestry?: BranchAncestryDetails;
|
|
176
204
|
warnings?: string[];
|
|
177
205
|
githubRepository?: GitHubRepository;
|
|
178
206
|
}
|
|
@@ -239,6 +267,169 @@ export interface PushBranchDetails {
|
|
|
239
267
|
output: string;
|
|
240
268
|
}
|
|
241
269
|
|
|
270
|
+
export type IntegrateBranchStatus = "already_integrated" | "fast_forward" | "merge_commit" | "conflict";
|
|
271
|
+
|
|
272
|
+
export interface IntegrateBranchHeads {
|
|
273
|
+
sourceHead: string;
|
|
274
|
+
targetHead: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface IntegrateBranchRepositoryVerification {
|
|
278
|
+
worktreeRoot: string;
|
|
279
|
+
canonicalCommonGitDirectory: string;
|
|
280
|
+
identityPreserved: true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface IntegrateBranchControlWorktreeVerification {
|
|
284
|
+
targetBranch: string;
|
|
285
|
+
currentBranchPreserved: true;
|
|
286
|
+
cleanBefore: true;
|
|
287
|
+
cleanAfter: true;
|
|
288
|
+
operationStateAbsentBefore: true;
|
|
289
|
+
operationStateAbsentAfter: true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface IntegrateBranchFinalAncestryProof {
|
|
293
|
+
sourceHead: string;
|
|
294
|
+
previousTargetHead: string;
|
|
295
|
+
targetHead: string;
|
|
296
|
+
sourceIsAncestorOfTarget: boolean;
|
|
297
|
+
previousTargetIsAncestorOfTarget: boolean;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface IntegrateBranchVerification {
|
|
301
|
+
repository: IntegrateBranchRepositoryVerification;
|
|
302
|
+
controlWorktree: IntegrateBranchControlWorktreeVerification;
|
|
303
|
+
heads: {
|
|
304
|
+
before: IntegrateBranchHeads;
|
|
305
|
+
after: IntegrateBranchHeads;
|
|
306
|
+
};
|
|
307
|
+
finalAncestry: IntegrateBranchFinalAncestryProof;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface IntegrateBranchDetailsBase {
|
|
311
|
+
action: "integrate_branch";
|
|
312
|
+
request: IntegrateBranchToolInput;
|
|
313
|
+
verified: IntegrateBranchVerification;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export interface IntegrateBranchAlreadyIntegratedDetails extends IntegrateBranchDetailsBase {
|
|
317
|
+
status: "already_integrated";
|
|
318
|
+
mergeExecuted: false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface IntegrateBranchFastForwardDetails extends IntegrateBranchDetailsBase {
|
|
322
|
+
status: "fast_forward";
|
|
323
|
+
mergeExecuted: true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface IntegrateBranchMergeCommitDetails extends IntegrateBranchDetailsBase {
|
|
327
|
+
status: "merge_commit";
|
|
328
|
+
mergeExecuted: true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export interface IntegrateBranchConflictPathEntry {
|
|
332
|
+
/** Exact repository-relative identity; human-readable renderers must sanitize a separate display copy. */
|
|
333
|
+
path: string;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface IntegrateBranchConflictDetails extends IntegrateBranchDetailsBase {
|
|
337
|
+
status: "conflict";
|
|
338
|
+
mergeExecuted: true;
|
|
339
|
+
conflict: {
|
|
340
|
+
paths: IntegrateBranchConflictPathEntry[];
|
|
341
|
+
omitted: number;
|
|
342
|
+
abort: {
|
|
343
|
+
attempted: true;
|
|
344
|
+
succeeded: true;
|
|
345
|
+
};
|
|
346
|
+
restoration: {
|
|
347
|
+
verified: true;
|
|
348
|
+
repositoryIdentityPreserved: true;
|
|
349
|
+
controlWorktreeBranchPreserved: true;
|
|
350
|
+
sourceHeadPreserved: true;
|
|
351
|
+
targetHeadRestored: true;
|
|
352
|
+
operationStateCleared: true;
|
|
353
|
+
cleanControlWorktree: true;
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export type IntegrateBranchDetails =
|
|
359
|
+
| IntegrateBranchAlreadyIntegratedDetails
|
|
360
|
+
| IntegrateBranchFastForwardDetails
|
|
361
|
+
| IntegrateBranchMergeCommitDetails
|
|
362
|
+
| IntegrateBranchConflictDetails;
|
|
363
|
+
|
|
364
|
+
export type RetireBranchMode = "merged" | "forced_unmerged";
|
|
365
|
+
|
|
366
|
+
export interface RetireBranchRepositoryIdentity {
|
|
367
|
+
worktreeRoot: string;
|
|
368
|
+
canonicalCommonGitDirectory: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export interface RetireBranchRefIdentity {
|
|
372
|
+
branchName: string;
|
|
373
|
+
fullRef: string;
|
|
374
|
+
head: string;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export interface RetireBranchAbsentRefProof {
|
|
378
|
+
branchName: string;
|
|
379
|
+
fullRef: string;
|
|
380
|
+
absent: true;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export interface RetireBranchWorktreeOccupancyProof {
|
|
384
|
+
branchName: string;
|
|
385
|
+
completeInventoryInspected: true;
|
|
386
|
+
occupied: false;
|
|
387
|
+
matchingWorktreeCount: 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface RetireBranchDetails {
|
|
391
|
+
action: "retire_branch";
|
|
392
|
+
status: "retired";
|
|
393
|
+
mode: RetireBranchMode;
|
|
394
|
+
/** Exact normalized request values used for the verified retirement. */
|
|
395
|
+
request: RetireBranchToolInput;
|
|
396
|
+
verified: {
|
|
397
|
+
repository: {
|
|
398
|
+
before: RetireBranchRepositoryIdentity;
|
|
399
|
+
after: RetireBranchRepositoryIdentity;
|
|
400
|
+
identityPreserved: true;
|
|
401
|
+
};
|
|
402
|
+
refs: {
|
|
403
|
+
before: {
|
|
404
|
+
retiring: RetireBranchRefIdentity;
|
|
405
|
+
target: RetireBranchRefIdentity;
|
|
406
|
+
expectedHead: string;
|
|
407
|
+
expectedHeadMatches: true;
|
|
408
|
+
};
|
|
409
|
+
after: {
|
|
410
|
+
retiring: RetireBranchAbsentRefProof;
|
|
411
|
+
target: RetireBranchRefIdentity;
|
|
412
|
+
targetHeadPreserved: true;
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
ancestry: {
|
|
416
|
+
retiringHead: string;
|
|
417
|
+
targetHead: string;
|
|
418
|
+
retiringIsAncestorOfTarget: boolean;
|
|
419
|
+
};
|
|
420
|
+
worktreeOccupancy: {
|
|
421
|
+
before: RetireBranchWorktreeOccupancyProof;
|
|
422
|
+
after: RetireBranchWorktreeOccupancyProof;
|
|
423
|
+
};
|
|
424
|
+
mutation: {
|
|
425
|
+
exactLocalRefDeletionAttempted: true;
|
|
426
|
+
localBranchAbsentAfterDeletion: true;
|
|
427
|
+
directRemoteDeletionAttempted: false;
|
|
428
|
+
remoteTrackingRefDeletionAttempted: false;
|
|
429
|
+
};
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
242
433
|
export interface PullRequestDetails {
|
|
243
434
|
repository: GitHubRepository;
|
|
244
435
|
number: number;
|
package/src/ui/branchme-panel.ts
CHANGED
|
@@ -24,14 +24,14 @@ type PanelCell =
|
|
|
24
24
|
preserveLeading?: boolean;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
export type BranchMePanelSection = "status" | "workflow" | "worktrees";
|
|
27
|
+
export type BranchMePanelSection = "status" | "workflow" | "integration" | "worktrees";
|
|
28
28
|
|
|
29
29
|
const NARROW_MIN_WIDTH = 24;
|
|
30
30
|
const WIDE_MIN_WIDTH = 72;
|
|
31
31
|
const MAX_PANEL_WIDTH = 96;
|
|
32
32
|
const MAX_PANEL_HEIGHT = 16;
|
|
33
33
|
const BODY_HEIGHT = 9;
|
|
34
|
-
const PANEL_SECTIONS: BranchMePanelSection[] = ["status", "workflow", "worktrees"];
|
|
34
|
+
const PANEL_SECTIONS: BranchMePanelSection[] = ["status", "workflow", "integration", "worktrees"];
|
|
35
35
|
const ANSI_RESET = "\u001b[0m";
|
|
36
36
|
|
|
37
37
|
function truncateVisible(value: string, width: number, ellipsis = "…", pad = false): string {
|
|
@@ -144,7 +144,7 @@ function statusDetailRow(label: string, value: string): PanelCell {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
function workflowDetailRow(tool: string, description: string): PanelCell {
|
|
147
|
-
return layoutCell(` ${tool.padEnd(
|
|
147
|
+
return layoutCell(` ${tool.padEnd(17, " ")}-> ${sanitize(description)}`);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
function sectionTitle(section: BranchMePanelSection): string {
|
|
@@ -153,6 +153,8 @@ function sectionTitle(section: BranchMePanelSection): string {
|
|
|
153
153
|
return "STATUS";
|
|
154
154
|
case "workflow":
|
|
155
155
|
return "WORKFLOW";
|
|
156
|
+
case "integration":
|
|
157
|
+
return "LIFECYCLE";
|
|
156
158
|
case "worktrees":
|
|
157
159
|
return "WORKTREES";
|
|
158
160
|
}
|
|
@@ -180,6 +182,8 @@ function sectionFooter(section: BranchMePanelSection, data: BranchMePanelData):
|
|
|
180
182
|
return "status • current repository only • tools perform actions";
|
|
181
183
|
case "workflow":
|
|
182
184
|
return "workflow • inspect → change → fetch/pull/rebase → create → push → PR";
|
|
185
|
+
case "integration":
|
|
186
|
+
return "lifecycle • integrate → remove worktree separately → retire local ref";
|
|
183
187
|
case "worktrees":
|
|
184
188
|
return "worktrees • create → handoff cwd → next session • remove retains branch";
|
|
185
189
|
}
|
|
@@ -208,6 +212,18 @@ function sectionRows(section: BranchMePanelSection, data: BranchMePanelData): Pa
|
|
|
208
212
|
workflowDetailRow("push_branch", "current branch"),
|
|
209
213
|
workflowDetailRow("pull_request", "after push"),
|
|
210
214
|
];
|
|
215
|
+
case "integration":
|
|
216
|
+
return [
|
|
217
|
+
heading("LIFECYCLE"),
|
|
218
|
+
workflowDetailRow("integrate_branch", "exact local source -> target"),
|
|
219
|
+
workflowDetailRow("control target", "checked out + clean"),
|
|
220
|
+
workflowDetailRow("conflict", "automatic verified abort"),
|
|
221
|
+
workflowDetailRow("semantic intent", "separate developer workflow"),
|
|
222
|
+
workflowDetailRow("remove_worktree", "separate; branch retained"),
|
|
223
|
+
workflowDetailRow("retire_branch", "leased local ref deletion"),
|
|
224
|
+
workflowDetailRow("retirement guard", "unoccupied + target ancestry"),
|
|
225
|
+
workflowDetailRow("remote effects", "never delete remote refs"),
|
|
226
|
+
];
|
|
211
227
|
case "worktrees":
|
|
212
228
|
return [
|
|
213
229
|
heading("WORKTREES"),
|