opencode-gitwt 0.2.0 → 0.2.2

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 (3) hide show
  1. package/README.md +3 -11
  2. package/package.json +3 -4
  3. package/src/index.js +95 -56
package/README.md CHANGED
@@ -37,17 +37,9 @@ change the session directory. Use `gitwt_list` to inspect copies and
37
37
 
38
38
  ## Configuration
39
39
 
40
- The plugin uses the shared gitwt configuration: `.gitwt.jsonc` in the project
41
- and `~/.config/gitwt/config.jsonc` globally. Options include `remote`, `base`,
40
+ The plugin uses plain JSON: `.gitwt.json` in the project
41
+ and `~/.config/gitwt/config.json` globally. Options include `remote`, `base`,
42
42
  `prefix`, `worktreeDir`, and `hooks`. See the core repository for details.
43
43
 
44
44
  For the standalone `gitwt` command, run `npm install -g @voischev/gitwt`.
45
- Upgrading from 0.1.x: move `.opencode/gitwt.jsonc` to `.gitwt.jsonc` and global
46
- settings from `~/.config/opencode/gitwt.jsonc` to `~/.config/gitwt/config.jsonc`.
47
-
48
- ## Development
49
-
50
- GitHub is the primary repository; SourceCraft is a mirror. Releases publish to
51
- npm from GitHub with `NPM_TOKEN`.
52
-
53
- Run `npm ci --ignore-scripts`, `npm run check`, and `npm test`.
45
+ Move existing settings to these paths and remove comments and trailing commas.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-gitwt",
3
- "version": "0.2.0",
4
- "description": "Git worktree для OpenCode: создать, проверить, вернуть путь и явно удалить",
3
+ "version": "0.2.2",
4
+ "description": "Git worktree tools for OpenCode, powered by gitwt",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "exports": {
@@ -23,8 +23,7 @@
23
23
  "release": "node scripts/release.js"
24
24
  },
25
25
  "dependencies": {
26
- "@voischev/gitwt": "^0.1.3",
27
- "zod": "4.1.8"
26
+ "@voischev/gitwt": "^0.1.4"
28
27
  },
29
28
  "devDependencies": {
30
29
  "@biomejs/biome": "2.6.3"
package/src/index.js CHANGED
@@ -1,64 +1,103 @@
1
- import { createWorktree, listWorktrees, removeWorktree, resolveWorktree } from "@voischev/gitwt";
2
- import { z } from "zod";
1
+ import {
2
+ createWorktree,
3
+ GitwtError,
4
+ listWorktrees,
5
+ removeWorktree,
6
+ resolveWorktree,
7
+ } from "@voischev/gitwt";
3
8
 
4
9
  export async function GitwtPlugin({ directory }, config = {}) {
5
- return {
6
- tool: {
7
- gitwt_create: {
8
- description:
9
- "Создать и проверить новый worktree. Продолжайте текущую сессию, используя возвращённый worktreePath в командах и файловых операциях. Сохраняйте worktree при паузе и передаче задачи.",
10
- args: {
11
- branch: z.string().describe("Имя новой ветки"),
12
- base: z.string().optional().describe("Базовая remote-ветка"),
13
- from: z
14
- .string()
15
- .optional()
16
- .describe("Явная локальная ссылка или проверенный commit вместо remote-базы"),
17
- path: z.string().optional().describe("Полный путь нового worktree"),
18
- noHooks: z.boolean().optional().describe("Явно отключить хуки для создаваемого worktree"),
10
+ const tool = {
11
+ gitwt_create: {
12
+ description:
13
+ "Create and verify a new worktree. Use worktreePath for commands and files in the current session. Keep the worktree when pausing or handing off a task.",
14
+ args: {
15
+ branch: { type: "string", description: "New branch name" },
16
+ base: { type: "string", description: "Remote base branch" },
17
+ from: {
18
+ type: "string",
19
+ description: "Local ref or verified commit instead of a remote base",
19
20
  },
20
- execute: async (args, ctx) =>
21
- JSON.stringify(
22
- await createWorktree(ctx.directory ?? directory, {
23
- ...args,
24
- signal: ctx.abort,
25
- config: { ...config, base: args.base ?? config.base },
26
- }),
27
- ),
28
- },
29
- gitwt_list: {
30
- description:
31
- "Показать worktree из реестра Git. status проверяет dirty и доступные ahead/behind; null означает неизвестное состояние.",
32
- args: { status: z.boolean().optional() },
33
- execute: async (args, ctx) =>
34
- JSON.stringify(await listWorktrees(ctx.directory ?? directory, args)),
35
- },
36
- gitwt_cd: {
37
- description:
38
- "Найти worktree по точной ветке или полному пути и вернуть его путь, HEAD и статус. Каталог текущей сессии не меняется: используйте worktreePath явно.",
39
- args: { target: z.string() },
40
- execute: async ({ target }, ctx) =>
41
- JSON.stringify(
42
- await resolveWorktree(ctx.directory ?? directory, target, { status: true }),
43
- ),
21
+ path: { type: "string", description: "Destination path" },
22
+ noHooks: { type: "boolean", description: "Disable hooks for this worktree" },
44
23
  },
45
- gitwt_remove: {
46
- description:
47
- "Только по явной просьбе удалить указанный worktree, убедившись, что он больше не используется. force разрешает потерю изменений, но не обходит ошибку preRemove. Без автокоммитов и отложенной очистки.",
48
- args: {
49
- target: z.string(),
50
- force: z.boolean().optional(),
51
- keepBranch: z.boolean().optional(),
52
- },
53
- execute: async ({ target, force, keepBranch }, ctx) =>
54
- JSON.stringify(
55
- await removeWorktree(ctx.directory ?? directory, target, {
56
- force,
57
- deleteBranch: !keepBranch,
58
- signal: ctx.abort,
59
- }),
60
- ),
24
+ execute: async (args, ctx) =>
25
+ JSON.stringify(
26
+ await createWorktree(ctx.directory ?? directory, {
27
+ ...args,
28
+ signal: ctx.abort,
29
+ config: { ...config, base: args.base ?? config.base },
30
+ }),
31
+ ),
32
+ },
33
+ gitwt_list: {
34
+ description:
35
+ "List Git worktrees. With status, check dirty and available ahead/behind counts; null means unknown.",
36
+ args: { status: { type: "boolean", description: "Check live worktree status" } },
37
+ execute: async (args, ctx) =>
38
+ JSON.stringify(await listWorktrees(ctx.directory ?? directory, args)),
39
+ },
40
+ gitwt_cd: {
41
+ description:
42
+ "Resolve a branch or full path to a worktree with HEAD and status. Use worktreePath explicitly; this does not change the session directory.",
43
+ args: { target: { type: "string", description: "Exact branch name or full worktree path" } },
44
+ execute: async ({ target }, ctx) =>
45
+ JSON.stringify(await resolveWorktree(ctx.directory ?? directory, target, { status: true })),
46
+ },
47
+ gitwt_remove: {
48
+ description:
49
+ "Remove a worktree only when explicitly requested and no longer in use. Force discards local changes but does not bypass a failing preRemove hook.",
50
+ args: {
51
+ target: { type: "string", description: "Exact branch name or full worktree path" },
52
+ force: { type: "boolean", description: "Allow discarding local changes" },
53
+ keepBranch: { type: "boolean", description: "Keep the branch after removing the worktree" },
61
54
  },
55
+ execute: async ({ target, force, keepBranch }, ctx) =>
56
+ JSON.stringify(
57
+ await removeWorktree(ctx.directory ?? directory, target, {
58
+ force,
59
+ deleteBranch: !keepBranch,
60
+ signal: ctx.abort,
61
+ }),
62
+ ),
63
+ },
64
+ };
65
+ const required = {
66
+ gitwt_create: ["branch"],
67
+ gitwt_list: [],
68
+ gitwt_cd: ["target"],
69
+ gitwt_remove: ["target"],
70
+ };
71
+ for (const [id, definition] of Object.entries(tool)) {
72
+ const execute = definition.execute;
73
+ definition.execute = async (args, ctx) => {
74
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
75
+ throw new GitwtError("invalid_arguments", `${id}: expected an argument object`);
76
+ }
77
+ for (const key of Object.keys(args)) {
78
+ if (!Object.hasOwn(definition.args, key)) {
79
+ throw new GitwtError("invalid_arguments", `${id}: unknown argument ${key}`);
80
+ }
81
+ }
82
+ for (const [key, schema] of Object.entries(definition.args)) {
83
+ if (args[key] === undefined && !required[id].includes(key)) continue;
84
+ if (typeof args[key] !== schema.type) {
85
+ throw new GitwtError("invalid_arguments", `${id}: ${key} must be ${schema.type}`);
86
+ }
87
+ }
88
+ return execute(args, ctx);
89
+ };
90
+ }
91
+ return {
92
+ tool,
93
+ "tool.definition": async ({ toolID }, output) => {
94
+ if (!Object.hasOwn(tool, toolID)) return;
95
+ output.jsonSchema = {
96
+ type: "object",
97
+ properties: tool[toolID].args,
98
+ required: required[toolID],
99
+ additionalProperties: false,
100
+ };
62
101
  },
63
102
  };
64
103
  }