@doingdev/opencode-claude-manager-plugin 0.1.35 → 0.1.43

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 (90) hide show
  1. package/dist/claude/claude-agent-sdk-adapter.js +1 -0
  2. package/dist/manager/git-operations.d.ts +10 -1
  3. package/dist/manager/git-operations.js +18 -3
  4. package/dist/manager/persistent-manager.d.ts +19 -3
  5. package/dist/manager/persistent-manager.js +21 -9
  6. package/dist/manager/session-controller.d.ts +8 -5
  7. package/dist/manager/session-controller.js +25 -20
  8. package/dist/metadata/claude-metadata.service.d.ts +12 -0
  9. package/dist/metadata/claude-metadata.service.js +38 -0
  10. package/dist/metadata/repo-claude-config-reader.d.ts +7 -0
  11. package/dist/metadata/repo-claude-config-reader.js +154 -0
  12. package/dist/plugin/agent-hierarchy.d.ts +9 -9
  13. package/dist/plugin/agent-hierarchy.js +25 -25
  14. package/dist/plugin/claude-manager.plugin.js +83 -46
  15. package/dist/plugin/orchestrator.plugin.d.ts +2 -0
  16. package/dist/plugin/orchestrator.plugin.js +116 -0
  17. package/dist/plugin/service-factory.js +3 -8
  18. package/dist/prompts/registry.js +100 -103
  19. package/dist/providers/claude-code-wrapper.d.ts +13 -0
  20. package/dist/providers/claude-code-wrapper.js +13 -0
  21. package/dist/safety/bash-safety.d.ts +21 -0
  22. package/dist/safety/bash-safety.js +62 -0
  23. package/dist/src/claude/claude-agent-sdk-adapter.d.ts +27 -0
  24. package/dist/src/claude/claude-agent-sdk-adapter.js +517 -0
  25. package/dist/src/claude/claude-session.service.d.ts +10 -0
  26. package/dist/src/claude/claude-session.service.js +18 -0
  27. package/dist/src/claude/session-live-tailer.d.ts +51 -0
  28. package/dist/src/claude/session-live-tailer.js +269 -0
  29. package/dist/src/claude/tool-approval-manager.d.ts +27 -0
  30. package/dist/src/claude/tool-approval-manager.js +232 -0
  31. package/dist/src/index.d.ts +6 -0
  32. package/dist/src/index.js +4 -0
  33. package/dist/src/manager/context-tracker.d.ts +33 -0
  34. package/dist/src/manager/context-tracker.js +106 -0
  35. package/dist/src/manager/git-operations.d.ts +12 -0
  36. package/dist/src/manager/git-operations.js +76 -0
  37. package/dist/src/manager/persistent-manager.d.ts +77 -0
  38. package/dist/src/manager/persistent-manager.js +170 -0
  39. package/dist/src/manager/session-controller.d.ts +44 -0
  40. package/dist/src/manager/session-controller.js +147 -0
  41. package/dist/src/plugin/agent-hierarchy.d.ts +60 -0
  42. package/dist/src/plugin/agent-hierarchy.js +157 -0
  43. package/dist/src/plugin/claude-manager.plugin.d.ts +2 -0
  44. package/dist/src/plugin/claude-manager.plugin.js +563 -0
  45. package/dist/src/plugin/service-factory.d.ts +12 -0
  46. package/dist/src/plugin/service-factory.js +38 -0
  47. package/dist/src/prompts/registry.d.ts +11 -0
  48. package/dist/src/prompts/registry.js +260 -0
  49. package/dist/src/state/file-run-state-store.d.ts +14 -0
  50. package/dist/src/state/file-run-state-store.js +85 -0
  51. package/dist/src/state/transcript-store.d.ts +15 -0
  52. package/dist/src/state/transcript-store.js +44 -0
  53. package/dist/src/types/contracts.d.ts +200 -0
  54. package/dist/src/types/contracts.js +1 -0
  55. package/dist/src/util/fs-helpers.d.ts +2 -0
  56. package/dist/src/util/fs-helpers.js +10 -0
  57. package/dist/src/util/project-context.d.ts +10 -0
  58. package/dist/src/util/project-context.js +105 -0
  59. package/dist/src/util/transcript-append.d.ts +7 -0
  60. package/dist/src/util/transcript-append.js +29 -0
  61. package/dist/test/claude-agent-sdk-adapter.test.d.ts +1 -0
  62. package/dist/test/claude-agent-sdk-adapter.test.js +459 -0
  63. package/dist/test/claude-manager.plugin.test.d.ts +1 -0
  64. package/dist/test/claude-manager.plugin.test.js +331 -0
  65. package/dist/test/context-tracker.test.d.ts +1 -0
  66. package/dist/test/context-tracker.test.js +138 -0
  67. package/dist/test/file-run-state-store.test.d.ts +1 -0
  68. package/dist/test/file-run-state-store.test.js +82 -0
  69. package/dist/test/git-operations.test.d.ts +1 -0
  70. package/dist/test/git-operations.test.js +90 -0
  71. package/dist/test/persistent-manager.test.d.ts +1 -0
  72. package/dist/test/persistent-manager.test.js +208 -0
  73. package/dist/test/project-context.test.d.ts +1 -0
  74. package/dist/test/project-context.test.js +92 -0
  75. package/dist/test/prompt-registry.test.d.ts +1 -0
  76. package/dist/test/prompt-registry.test.js +256 -0
  77. package/dist/test/session-controller.test.d.ts +1 -0
  78. package/dist/test/session-controller.test.js +149 -0
  79. package/dist/test/session-live-tailer.test.d.ts +1 -0
  80. package/dist/test/session-live-tailer.test.js +313 -0
  81. package/dist/test/tool-approval-manager.test.d.ts +1 -0
  82. package/dist/test/tool-approval-manager.test.js +264 -0
  83. package/dist/test/transcript-append.test.d.ts +1 -0
  84. package/dist/test/transcript-append.test.js +37 -0
  85. package/dist/test/transcript-store.test.d.ts +1 -0
  86. package/dist/test/transcript-store.test.js +50 -0
  87. package/dist/types/contracts.d.ts +3 -4
  88. package/dist/vitest.config.d.ts +2 -0
  89. package/dist/vitest.config.js +11 -0
  90. package/package.json +2 -2
@@ -0,0 +1,264 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ToolApprovalManager } from '../src/claude/tool-approval-manager.js';
3
+ describe('ToolApprovalManager', () => {
4
+ describe('evaluate()', () => {
5
+ it('allows Skill tool by default policy', () => {
6
+ const manager = new ToolApprovalManager();
7
+ const result = manager.evaluate('Skill', { command: 'my-skill' });
8
+ expect(result).toEqual({ behavior: 'allow' });
9
+ });
10
+ it('allows read-only tools by default policy', () => {
11
+ const manager = new ToolApprovalManager();
12
+ expect(manager.evaluate('Read', { file_path: '/tmp/foo.ts' })).toEqual({
13
+ behavior: 'allow',
14
+ });
15
+ expect(manager.evaluate('Grep', { pattern: 'foo' })).toEqual({
16
+ behavior: 'allow',
17
+ });
18
+ expect(manager.evaluate('Glob', { pattern: '*.ts' })).toEqual({
19
+ behavior: 'allow',
20
+ });
21
+ });
22
+ it('allows edit tools by default policy', () => {
23
+ const manager = new ToolApprovalManager();
24
+ expect(manager.evaluate('Edit', {
25
+ file_path: '/tmp/foo.ts',
26
+ old_string: 'a',
27
+ new_string: 'b',
28
+ })).toEqual({ behavior: 'allow' });
29
+ expect(manager.evaluate('Write', {
30
+ file_path: '/tmp/foo.ts',
31
+ content: 'hello',
32
+ })).toEqual({ behavior: 'allow' });
33
+ });
34
+ it('denies bash rm -rf on root', () => {
35
+ const manager = new ToolApprovalManager();
36
+ const result = manager.evaluate('Bash', { command: 'rm -rf /' });
37
+ expect(result.behavior).toBe('deny');
38
+ if (result.behavior === 'deny') {
39
+ expect(result.message).toContain('rm -rf');
40
+ }
41
+ });
42
+ it('denies git push --force', () => {
43
+ const manager = new ToolApprovalManager();
44
+ const result = manager.evaluate('Bash', {
45
+ command: 'git push --force origin main',
46
+ });
47
+ expect(result.behavior).toBe('deny');
48
+ if (result.behavior === 'deny') {
49
+ expect(result.message).toContain('Force push');
50
+ }
51
+ });
52
+ it('denies git reset --hard', () => {
53
+ const manager = new ToolApprovalManager();
54
+ const result = manager.evaluate('Bash', {
55
+ command: 'git reset --hard HEAD',
56
+ });
57
+ expect(result.behavior).toBe('deny');
58
+ if (result.behavior === 'deny') {
59
+ expect(result.message).toContain('git reset --hard');
60
+ }
61
+ });
62
+ it('allows normal bash commands', () => {
63
+ const manager = new ToolApprovalManager();
64
+ expect(manager.evaluate('Bash', { command: 'ls -la' })).toEqual({
65
+ behavior: 'allow',
66
+ });
67
+ expect(manager.evaluate('Bash', { command: 'pnpm test' })).toEqual({
68
+ behavior: 'allow',
69
+ });
70
+ });
71
+ it('returns allow when disabled', () => {
72
+ const manager = new ToolApprovalManager({ enabled: false });
73
+ const result = manager.evaluate('Bash', { command: 'rm -rf /' });
74
+ expect(result).toEqual({ behavior: 'allow' });
75
+ });
76
+ it('uses default action when no rule matches', () => {
77
+ const manager = new ToolApprovalManager({
78
+ rules: [],
79
+ defaultAction: 'deny',
80
+ });
81
+ const result = manager.evaluate('SomeUnknownTool', {});
82
+ expect(result.behavior).toBe('deny');
83
+ });
84
+ it('first matching rule wins', () => {
85
+ const manager = new ToolApprovalManager({
86
+ rules: [
87
+ {
88
+ id: 'deny-all-bash',
89
+ toolPattern: 'Bash',
90
+ action: 'deny',
91
+ denyMessage: 'No bash',
92
+ },
93
+ { id: 'allow-bash', toolPattern: 'Bash', action: 'allow' },
94
+ ],
95
+ defaultAction: 'allow',
96
+ enabled: true,
97
+ });
98
+ const result = manager.evaluate('Bash', { command: 'echo hi' });
99
+ expect(result.behavior).toBe('deny');
100
+ });
101
+ it('matches glob patterns in tool names', () => {
102
+ const manager = new ToolApprovalManager({
103
+ rules: [
104
+ {
105
+ id: 'deny-notebook',
106
+ toolPattern: 'Notebook*',
107
+ action: 'deny',
108
+ denyMessage: 'No notebooks',
109
+ },
110
+ ],
111
+ defaultAction: 'allow',
112
+ enabled: true,
113
+ });
114
+ expect(manager.evaluate('NotebookEdit', {}).behavior).toBe('deny');
115
+ expect(manager.evaluate('NotebookRead', {}).behavior).toBe('deny');
116
+ expect(manager.evaluate('Read', {}).behavior).toBe('allow');
117
+ });
118
+ it('matches inputPattern as substring of serialized input', () => {
119
+ const manager = new ToolApprovalManager({
120
+ rules: [
121
+ {
122
+ id: 'deny-secret-files',
123
+ toolPattern: '*',
124
+ inputPattern: '.env',
125
+ action: 'deny',
126
+ denyMessage: 'No .env access',
127
+ },
128
+ ],
129
+ defaultAction: 'allow',
130
+ enabled: true,
131
+ });
132
+ expect(manager.evaluate('Read', { file_path: '/project/.env' }).behavior).toBe('deny');
133
+ expect(manager.evaluate('Read', { file_path: '/project/src/index.ts' }).behavior).toBe('allow');
134
+ });
135
+ it('records decisions in the log', () => {
136
+ const manager = new ToolApprovalManager();
137
+ manager.evaluate('Read', { file_path: '/tmp/foo.ts' });
138
+ const decisions = manager.getDecisions();
139
+ expect(decisions).toHaveLength(1);
140
+ expect(decisions[0].toolName).toBe('Read');
141
+ expect(decisions[0].action).toBe('allow');
142
+ expect(decisions[0].matchedRuleId).toBe('allow-read');
143
+ });
144
+ });
145
+ describe('policy management', () => {
146
+ it('addRule inserts at specified position', () => {
147
+ const manager = new ToolApprovalManager({
148
+ rules: [],
149
+ enabled: true,
150
+ defaultAction: 'allow',
151
+ });
152
+ manager.addRule({ id: 'a', toolPattern: 'A', action: 'allow' });
153
+ manager.addRule({ id: 'b', toolPattern: 'B', action: 'allow' });
154
+ manager.addRule({ id: 'c', toolPattern: 'C', action: 'deny' }, 1);
155
+ const rules = manager.getPolicy().rules;
156
+ expect(rules.map((r) => r.id)).toEqual(['a', 'c', 'b']);
157
+ });
158
+ it('addRule appends to end by default', () => {
159
+ const manager = new ToolApprovalManager({
160
+ rules: [],
161
+ enabled: true,
162
+ defaultAction: 'allow',
163
+ });
164
+ manager.addRule({ id: 'a', toolPattern: 'A', action: 'allow' });
165
+ manager.addRule({ id: 'b', toolPattern: 'B', action: 'allow' });
166
+ expect(manager.getPolicy().rules.map((r) => r.id)).toEqual(['a', 'b']);
167
+ });
168
+ it('removeRule removes by ID and returns true', () => {
169
+ const manager = new ToolApprovalManager();
170
+ const removed = manager.removeRule('allow-read');
171
+ expect(removed).toBe(true);
172
+ expect(manager.getPolicy().rules.find((r) => r.id === 'allow-read')).toBeUndefined();
173
+ });
174
+ it('removeRule returns false for non-existent ID', () => {
175
+ const manager = new ToolApprovalManager();
176
+ expect(manager.removeRule('nonexistent')).toBe(false);
177
+ });
178
+ it('setPolicy replaces entire policy', () => {
179
+ const manager = new ToolApprovalManager();
180
+ manager.setPolicy({
181
+ rules: [{ id: 'only', toolPattern: '*', action: 'deny' }],
182
+ defaultAction: 'deny',
183
+ enabled: true,
184
+ });
185
+ expect(manager.getPolicy().rules).toHaveLength(1);
186
+ expect(manager.getPolicy().defaultAction).toBe('deny');
187
+ });
188
+ it('setDefaultAction changes the fallback', () => {
189
+ const manager = new ToolApprovalManager({
190
+ rules: [],
191
+ enabled: true,
192
+ defaultAction: 'allow',
193
+ });
194
+ manager.setDefaultAction('deny');
195
+ expect(manager.evaluate('Unknown', {}).behavior).toBe('deny');
196
+ });
197
+ it('setEnabled toggles the manager', () => {
198
+ const manager = new ToolApprovalManager({
199
+ rules: [
200
+ {
201
+ id: 'deny-all',
202
+ toolPattern: '*',
203
+ action: 'deny',
204
+ denyMessage: 'blocked',
205
+ },
206
+ ],
207
+ enabled: true,
208
+ defaultAction: 'deny',
209
+ });
210
+ expect(manager.evaluate('Read', {}).behavior).toBe('deny');
211
+ manager.setEnabled(false);
212
+ expect(manager.evaluate('Read', {}).behavior).toBe('allow');
213
+ });
214
+ });
215
+ describe('decision log', () => {
216
+ it('getDecisions returns most recent first', () => {
217
+ const manager = new ToolApprovalManager();
218
+ manager.evaluate('Read', {});
219
+ manager.evaluate('Grep', {});
220
+ manager.evaluate('Glob', {});
221
+ const decisions = manager.getDecisions();
222
+ expect(decisions[0].toolName).toBe('Glob');
223
+ expect(decisions[2].toolName).toBe('Read');
224
+ });
225
+ it('getDeniedDecisions filters to denials only', () => {
226
+ const manager = new ToolApprovalManager();
227
+ manager.evaluate('Read', {});
228
+ manager.evaluate('Bash', { command: 'rm -rf /' });
229
+ manager.evaluate('Grep', {});
230
+ const denied = manager.getDeniedDecisions();
231
+ expect(denied).toHaveLength(1);
232
+ expect(denied[0].toolName).toBe('Bash');
233
+ });
234
+ it('clearDecisions empties the log', () => {
235
+ const manager = new ToolApprovalManager();
236
+ manager.evaluate('Read', {});
237
+ manager.evaluate('Grep', {});
238
+ manager.clearDecisions();
239
+ expect(manager.getDecisions()).toHaveLength(0);
240
+ });
241
+ it('respects maxDecisions ring buffer limit', () => {
242
+ const manager = new ToolApprovalManager({}, 3);
243
+ manager.evaluate('Read', {});
244
+ manager.evaluate('Grep', {});
245
+ manager.evaluate('Glob', {});
246
+ manager.evaluate('Edit', {});
247
+ const decisions = manager.getDecisions();
248
+ expect(decisions).toHaveLength(3);
249
+ // Oldest (Read) should be evicted
250
+ expect(decisions.map((d) => d.toolName)).toEqual(['Edit', 'Glob', 'Grep']);
251
+ });
252
+ it('includes agentId when provided', () => {
253
+ const manager = new ToolApprovalManager();
254
+ manager.evaluate('Read', {}, { agentID: 'agent_123' });
255
+ expect(manager.getDecisions()[0].agentId).toBe('agent_123');
256
+ });
257
+ it('truncates inputPreview for large inputs', () => {
258
+ const manager = new ToolApprovalManager();
259
+ manager.evaluate('Read', { data: 'x'.repeat(1000) });
260
+ const decision = manager.getDecisions()[0];
261
+ expect(decision.inputPreview.length).toBeLessThanOrEqual(300);
262
+ });
263
+ });
264
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { appendTranscriptEvents, stripTrailingPartials } from '../src/util/transcript-append.js';
3
+ function ev(type, text) {
4
+ return { type, text };
5
+ }
6
+ describe('transcript-append', () => {
7
+ it('replaces the last partial when another partial arrives', () => {
8
+ const next = appendTranscriptEvents([], [ev('partial', 'a')]);
9
+ expect(next).toEqual([ev('partial', 'a')]);
10
+ const next2 = appendTranscriptEvents(next, [ev('partial', 'ab')]);
11
+ expect(next2).toEqual([ev('partial', 'ab')]);
12
+ });
13
+ it('strips trailing partials before a non-partial event', () => {
14
+ const base = appendTranscriptEvents([], [ev('partial', 'streaming')]);
15
+ const next = appendTranscriptEvents(base, [ev('assistant', 'Final answer.')]);
16
+ expect(next.map((e) => e.type)).toEqual(['assistant']);
17
+ expect(next[0]?.text).toBe('Final answer.');
18
+ });
19
+ it('stripTrailingPartials removes only trailing partials', () => {
20
+ const events = [
21
+ ev('init', 'init'),
22
+ ev('partial', 'x'),
23
+ ev('assistant', 'done'),
24
+ ev('partial', 'orphan'),
25
+ ];
26
+ expect(stripTrailingPartials(events).map((e) => e.type)).toEqual([
27
+ 'init',
28
+ 'partial',
29
+ 'assistant',
30
+ ]);
31
+ });
32
+ it('appends multiple incoming events in order with partial rules', () => {
33
+ const batch = appendTranscriptEvents([ev('init', 'init')], [ev('partial', 'p1'), ev('partial', 'p2'), ev('user', 'tool output')]);
34
+ expect(batch.map((e) => e.type)).toEqual(['init', 'user']);
35
+ expect(batch[1]?.text).toBe('tool output');
36
+ });
37
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { describe, expect, it, beforeEach, afterEach } from 'vitest';
4
+ import { TranscriptStore } from '../src/state/transcript-store.js';
5
+ function ev(type, text) {
6
+ return { type, text };
7
+ }
8
+ describe('TranscriptStore', () => {
9
+ const testDir = path.join(process.cwd(), '.test-transcript-store-' + process.pid);
10
+ let store;
11
+ beforeEach(async () => {
12
+ await fs.mkdir(testDir, { recursive: true });
13
+ store = new TranscriptStore('.claude-manager');
14
+ });
15
+ afterEach(async () => {
16
+ await fs.rm(testDir, { recursive: true, force: true });
17
+ });
18
+ it('returns empty array for nonexistent session', async () => {
19
+ const events = await store.readEvents(testDir, 'nonexistent');
20
+ expect(events).toEqual([]);
21
+ });
22
+ it('appends and reads events', async () => {
23
+ await store.appendEvents(testDir, 'ses_1', [ev('init', 'init'), ev('assistant', 'Hello.')]);
24
+ const events = await store.readEvents(testDir, 'ses_1');
25
+ expect(events).toEqual([ev('init', 'init'), ev('assistant', 'Hello.')]);
26
+ });
27
+ it('accumulates events across multiple appends', async () => {
28
+ await store.appendEvents(testDir, 'ses_2', [
29
+ ev('init', 'init'),
30
+ ev('assistant', 'First response.'),
31
+ ]);
32
+ await store.appendEvents(testDir, 'ses_2', [
33
+ ev('user', 'Follow-up question'),
34
+ ev('assistant', 'Second response.'),
35
+ ]);
36
+ const events = await store.readEvents(testDir, 'ses_2');
37
+ expect(events.map((e) => e.type)).toEqual(['init', 'assistant', 'user', 'assistant']);
38
+ expect(events[3].text).toBe('Second response.');
39
+ });
40
+ it('strips trailing partials before persisting', async () => {
41
+ await store.appendEvents(testDir, 'ses_3', [ev('init', 'init'), ev('partial', 'streaming...')]);
42
+ const events = await store.readEvents(testDir, 'ses_3');
43
+ expect(events).toEqual([ev('init', 'init')]);
44
+ });
45
+ it('skips write when newEvents is empty', async () => {
46
+ await store.appendEvents(testDir, 'ses_4', []);
47
+ const events = await store.readEvents(testDir, 'ses_4');
48
+ expect(events).toEqual([]);
49
+ });
50
+ });
@@ -1,7 +1,7 @@
1
1
  export interface ManagerPromptRegistry {
2
2
  ctoSystemPrompt: string;
3
- engineerPlanPrompt: string;
4
- engineerBuildPrompt: string;
3
+ engineerExplorePrompt: string;
4
+ engineerImplementPrompt: string;
5
5
  engineerSessionPrompt: string;
6
6
  modePrefixes: {
7
7
  plan: string;
@@ -113,7 +113,6 @@ export interface GitOperationResult {
113
113
  }
114
114
  export interface ActiveSessionState {
115
115
  sessionId: string;
116
- cwd: string;
117
116
  startedAt: string;
118
117
  totalTurns: number;
119
118
  totalCostUsd: number;
@@ -133,7 +132,7 @@ export interface PersistentRunMessageRecord {
133
132
  }
134
133
  export interface PersistentRunActionRecord {
135
134
  timestamp: string;
136
- type: 'git_diff' | 'git_commit' | 'git_reset' | 'compact' | 'clear';
135
+ type: 'git_diff' | 'git_commit' | 'git_reset' | 'git_status' | 'git_log' | 'compact' | 'clear';
137
136
  result: string;
138
137
  }
139
138
  export interface PersistentRunRecord {
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vite").UserConfig;
2
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ export default defineConfig({
3
+ test: {
4
+ environment: 'node',
5
+ include: ['test/**/*.test.ts'],
6
+ coverage: {
7
+ provider: 'v8',
8
+ reporter: ['text', 'lcov'],
9
+ },
10
+ },
11
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doingdev/opencode-claude-manager-plugin",
3
- "version": "0.1.35",
3
+ "version": "0.1.43",
4
4
  "description": "OpenCode plugin that orchestrates Claude Code sessions.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -52,6 +52,6 @@
52
52
  "format": "prettier --write .",
53
53
  "test": "vitest run",
54
54
  "knip": "knip",
55
- "release": "pnpm run build && pnpm version patch && pnpm publish"
55
+ "release": "pnpm run build && pnpm version patch && git push --follow-tags"
56
56
  }
57
57
  }