@solaqua/gji 0.11.0 → 0.12.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.
Files changed (67) hide show
  1. package/README.md +82 -496
  2. package/dist/clean.js +2 -0
  3. package/dist/cli.d.ts +4 -0
  4. package/dist/cli.js +40 -4
  5. package/dist/context-card.d.ts +1 -0
  6. package/dist/context-card.js +25 -0
  7. package/dist/doctor.js +21 -0
  8. package/dist/done.js +2 -0
  9. package/dist/gji-bundle.mjs +988 -350
  10. package/dist/go.d.ts +1 -0
  11. package/dist/go.js +8 -0
  12. package/dist/hooks.d.ts +1 -0
  13. package/dist/hooks.js +7 -1
  14. package/dist/hub.d.ts +31 -0
  15. package/dist/hub.js +287 -0
  16. package/dist/ls.js +9 -1
  17. package/dist/new.d.ts +1 -0
  18. package/dist/new.js +9 -0
  19. package/dist/pr-open.js +2 -10
  20. package/dist/pull-requests.d.ts +1 -0
  21. package/dist/pull-requests.js +32 -3
  22. package/dist/remove.js +2 -0
  23. package/dist/repo.d.ts +1 -0
  24. package/dist/repo.js +4 -0
  25. package/dist/run-hook.js +2 -0
  26. package/dist/shell-completion.js +11 -3
  27. package/dist/slots.d.ts +8 -0
  28. package/dist/slots.js +61 -0
  29. package/dist/status.d.ts +2 -0
  30. package/dist/status.js +9 -1
  31. package/dist/task-command.d.ts +9 -0
  32. package/dist/task-command.js +29 -0
  33. package/dist/task.d.ts +9 -0
  34. package/dist/task.js +37 -0
  35. package/dist/terminal-text.d.ts +4 -0
  36. package/dist/terminal-text.js +72 -0
  37. package/dist/worktree-bootstrap.js +3 -0
  38. package/dist/worktree-info.d.ts +6 -1
  39. package/dist/worktree-info.js +14 -4
  40. package/dist/worktree-picker.d.ts +3 -3
  41. package/dist/worktree-picker.js +71 -82
  42. package/dist/worktree-sources.d.ts +1 -0
  43. package/dist/worktree-sources.js +57 -11
  44. package/man/man1/gji-back.1 +1 -1
  45. package/man/man1/gji-clean.1 +1 -1
  46. package/man/man1/gji-completion.1 +1 -1
  47. package/man/man1/gji-config.1 +1 -1
  48. package/man/man1/gji-doctor.1 +1 -1
  49. package/man/man1/gji-done.1 +1 -1
  50. package/man/man1/gji-go.1 +4 -1
  51. package/man/man1/gji-history.1 +1 -1
  52. package/man/man1/gji-init.1 +1 -1
  53. package/man/man1/gji-ls.1 +1 -1
  54. package/man/man1/gji-new.1 +4 -1
  55. package/man/man1/gji-open.1 +1 -1
  56. package/man/man1/gji-pr.1 +1 -1
  57. package/man/man1/gji-remove.1 +1 -1
  58. package/man/man1/gji-root.1 +1 -1
  59. package/man/man1/gji-run-hook.1 +1 -1
  60. package/man/man1/gji-status.1 +1 -1
  61. package/man/man1/gji-sync-files.1 +1 -1
  62. package/man/man1/gji-sync.1 +1 -1
  63. package/man/man1/gji-task.1 +16 -0
  64. package/man/man1/gji-undo.1 +1 -1
  65. package/man/man1/gji-warp.1 +1 -1
  66. package/man/man1/gji.1 +5 -1
  67. package/package.json +1 -1
package/dist/cli.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import { Command } from "commander";
2
+ import { type HubCommandDependencies } from "./hub.js";
2
3
  import { type PrOpenCommandDependencies } from "./pr-open.js";
3
4
  export interface RunCliOptions {
5
+ columns?: number;
4
6
  cwd?: string;
7
+ now?: number;
5
8
  prOpenDependencies?: Partial<PrOpenCommandDependencies>;
9
+ hubDependencies?: Partial<HubCommandDependencies>;
6
10
  stderr?: (chunk: string) => void;
7
11
  stdout?: (chunk: string) => void;
8
12
  }
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { runDoneCommand } from "./done.js";
10
10
  import { runGoCommand } from "./go.js";
11
11
  import { isHeadless } from "./headless.js";
12
12
  import { runHistoryCommand } from "./history-command.js";
13
+ import { runHubCommand } from "./hub.js";
13
14
  import { runInitCommand } from "./init.js";
14
15
  import { runLsCommand } from "./ls.js";
15
16
  import { runNewCommand } from "./new.js";
@@ -24,6 +25,7 @@ import { runHookCommand } from "./run-hook.js";
24
25
  import { runStatusCommand } from "./status.js";
25
26
  import { runSyncCommand } from "./sync.js";
26
27
  import { runSyncFilesCommand } from "./sync-files-command.js";
28
+ import { runTaskCommand } from "./task-command.js";
27
29
  import { runUndoCommand } from "./undo.js";
28
30
  import { runWarpCommand } from "./warp.js";
29
31
  export function createProgram() {
@@ -56,7 +58,7 @@ export async function runCli(argv, options = {}) {
56
58
  const cwd = options.cwd ?? process.cwd();
57
59
  const stdout = options.stdout ?? (() => undefined);
58
60
  const stderr = options.stderr ?? (() => undefined);
59
- if (shouldRegisterCurrentRepo(argv)) {
61
+ if (shouldRegisterCurrentRepo(argv) || isHubInvocation(argv)) {
60
62
  await maybeRegisterCurrentRepo(cwd);
61
63
  }
62
64
  const program = createProgram();
@@ -65,9 +67,16 @@ export async function runCli(argv, options = {}) {
65
67
  writeOut: stdout,
66
68
  });
67
69
  program.exitOverride();
68
- if (argv.length === 0) {
69
- program.outputHelp();
70
- return { exitCode: 0 };
70
+ if (isHubInvocation(argv)) {
71
+ const exitCode = await runHubCommand({
72
+ columns: options.columns,
73
+ cwd,
74
+ json: argv.includes("--json"),
75
+ now: options.now,
76
+ stderr,
77
+ stdout,
78
+ }, options.hubDependencies);
79
+ return { exitCode };
71
80
  }
72
81
  try {
73
82
  attachCommandActions(program, {
@@ -131,6 +140,9 @@ function shouldRegisterCurrentRepo(argv) {
131
140
  "init",
132
141
  ].includes(command ?? ""));
133
142
  }
143
+ function isHubInvocation(argv) {
144
+ return argv.length === 0 || (argv.length === 1 && argv[0] === "--json");
145
+ }
134
146
  async function maybeRegisterCurrentRepo(cwd) {
135
147
  try {
136
148
  const { repoRoot } = await detectRepository(cwd);
@@ -149,6 +161,7 @@ function registerCommands(program) {
149
161
  .option("--from-current", "base the new branch on the current worktree instead of the main worktree (cannot be combined with --detached)")
150
162
  .option("--no-fetch", "skip refreshing the remote default branch before creating the new branch")
151
163
  .option("--take", "move current uncommitted changes into the new worktree")
164
+ .option("--task <description>", "record the purpose of the new worktree")
152
165
  .option("--copy", "copy current uncommitted changes instead of moving them (requires --take)")
153
166
  .option("--open", "open the new worktree in an editor after creation")
154
167
  .option("--editor <cli>", "editor CLI to use with --open (code, cursor, zed, …)")
@@ -221,6 +234,7 @@ function registerCommands(program) {
221
234
  .description("resolve and jump to a worktree path")
222
235
  .option("--root", "navigate to the main repository root")
223
236
  .option("--print", "print the resolved worktree path explicitly")
237
+ .option("--quiet", "suppress the context card")
224
238
  .option("--json", "emit JSON for an existing worktree destination")
225
239
  .action(notImplemented("go"));
226
240
  program
@@ -233,6 +247,12 @@ function registerCommands(program) {
233
247
  .description("summarize repository and worktree health")
234
248
  .option("--json", "print repository and worktree health as JSON")
235
249
  .action(notImplemented("status"));
250
+ program
251
+ .command("task [task]")
252
+ .description("show or update the current worktree task")
253
+ .option("--clear", "clear the current worktree task")
254
+ .option("--json", "emit task metadata as JSON")
255
+ .action(notImplemented("task"));
236
256
  program
237
257
  .command("sync")
238
258
  .description("fetch and update one or all worktrees")
@@ -329,6 +349,7 @@ function attachCommandActions(program, options) {
329
349
  json: commandOptions.json,
330
350
  open: commandOptions.open,
331
351
  take: commandOptions.take,
352
+ task: commandOptions.task,
332
353
  });
333
354
  if (exitCode !== 0) {
334
355
  throw commanderExit(exitCode);
@@ -492,6 +513,7 @@ function attachCommandActions(program, options) {
492
513
  cwd: options.cwd,
493
514
  json: commandOptions.json,
494
515
  print: commandOptions.print,
516
+ quiet: commandOptions.quiet,
495
517
  root: commandOptions.root,
496
518
  stderr: options.stderr,
497
519
  stdout: options.stdout,
@@ -524,6 +546,20 @@ function attachCommandActions(program, options) {
524
546
  throw commanderExit(exitCode);
525
547
  }
526
548
  });
549
+ program.commands
550
+ .find((command) => command.name() === "task")
551
+ ?.action(async (task, commandOptions) => {
552
+ const exitCode = await runTaskCommand({
553
+ clear: commandOptions.clear,
554
+ cwd: options.cwd,
555
+ json: commandOptions.json,
556
+ stderr: options.stderr,
557
+ task,
558
+ stdout: options.stdout,
559
+ });
560
+ if (exitCode !== 0)
561
+ throw commanderExit(exitCode);
562
+ });
527
563
  program.commands
528
564
  .find((command) => command.name() === "sync")
529
565
  ?.action(async (commandOptions) => {
@@ -0,0 +1 @@
1
+ export declare function renderContextCard(worktreePath: string): Promise<string | null>;
@@ -0,0 +1,25 @@
1
+ import { listWorktrees } from "./repo.js";
2
+ import { readTask } from "./task.js";
3
+ import { readWorktreeInfo } from "./worktree-info.js";
4
+ export async function renderContextCard(worktreePath) {
5
+ const worktree = (await listWorktrees(worktreePath)).find((entry) => entry.path === worktreePath);
6
+ if (!worktree)
7
+ return null;
8
+ const info = await readWorktreeInfo(worktree);
9
+ const task = await readTask(worktreePath);
10
+ if (!task)
11
+ return null;
12
+ const rows = [`┌ ${info.branch ?? "(detached)"}`];
13
+ rows.push(`│ task ${task.task}`);
14
+ rows.push(`│ state ${formatState(info)}`);
15
+ if (info.lastCommitTimestamp !== null)
16
+ rows.push(`│ last ${formatLastCommit(info)}`);
17
+ rows.push("└");
18
+ return rows.join("\n");
19
+ }
20
+ function formatState(info) {
21
+ return `${info.status}${info.upstream.kind === "tracked" ? ` · ↑${info.upstream.ahead} ↓${info.upstream.behind}` : ""}`;
22
+ }
23
+ function formatLastCommit(info) {
24
+ return new Date(info.lastCommitTimestamp).toISOString();
25
+ }
package/dist/doctor.js CHANGED
@@ -12,6 +12,7 @@ import { detectRepository } from "./repo.js";
12
12
  import { loadRegistry, REGISTRY_FILE_PATH, removeMissingRegistryEntries, } from "./repo-registry.js";
13
13
  import { resolveSupportedShell } from "./shell.js";
14
14
  import { executableExists, hasShellIntegration, resolveCompletionPath, resolveShellConfigPath, } from "./shell-setup.js";
15
+ import { loadSlots } from "./slots.js";
15
16
  const execFileAsync = promisify(execFile);
16
17
  const MINIMUM_GIT_VERSION = { major: 2, minor: 17 };
17
18
  export async function runDoctorCommand(options) {
@@ -82,10 +83,30 @@ async function collectDoctorInspection(cwd, home, shell) {
82
83
  await checkWorktreeBase(repository, effectiveConfig, home),
83
84
  registry.check,
84
85
  await checkEditor(effectiveConfig),
86
+ await checkOrphanSlots(home),
85
87
  ],
86
88
  missingRegistryPaths: registry.missingPaths,
87
89
  };
88
90
  }
91
+ async function checkOrphanSlots(home) {
92
+ const slots = await loadSlots(home);
93
+ const missing = (await Promise.all(Object.keys(slots).map(async (path) => {
94
+ try {
95
+ await access(path);
96
+ return false;
97
+ }
98
+ catch {
99
+ return true;
100
+ }
101
+ }))).filter(Boolean).length;
102
+ return {
103
+ id: "slots",
104
+ message: missing === 0
105
+ ? "worktree slots healthy"
106
+ : `${missing} orphaned worktree slot${missing === 1 ? "" : "s"}`,
107
+ status: "ok",
108
+ };
109
+ }
89
110
  function buildDoctorFixes(missingRegistryPaths) {
90
111
  if (missingRegistryPaths.length === 0)
91
112
  return [];
package/dist/done.js CHANGED
@@ -7,6 +7,7 @@ import { loadHistory } from "./history.js";
7
7
  import { extractHooks, runHook } from "./hooks.js";
8
8
  import { detectRepository, listWorktrees } from "./repo.js";
9
9
  import { writeShellOutput } from "./shell-handoff.js";
10
+ import { releaseWorktreeSlot } from "./slots.js";
10
11
  import { finalizeUndoOperation, recordUndoOperation } from "./undo.js";
11
12
  import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeForceRemovalError, removeWorktree, } from "./worktree-management.js";
12
13
  const DONE_OUTPUT_FILE_ENV = "GJI_DONE_OUTPUT_FILE";
@@ -122,6 +123,7 @@ export async function runDoneCommand(options) {
122
123
  }
123
124
  }
124
125
  await finalizeUndoOperation(journal, [target]);
126
+ await releaseWorktreeSlot(target.path);
125
127
  await runGit(repository.repoRoot, ["worktree", "prune"]).catch(() => undefined);
126
128
  const shouldMove = options.branch === undefined || target.path === repository.currentRoot;
127
129
  const movedTo = shouldMove