@selfagency/beans-mcp 0.1.3 → 0.4.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 (59) hide show
  1. package/README.md +63 -6
  2. package/{dist/beans-mcp-server.cjs → beans-mcp-server.cjs} +269 -34
  3. package/{dist/index.cjs → index.cjs} +269 -34
  4. package/{dist/index.d.ts → index.d.ts} +19 -1
  5. package/{dist/index.js → index.js} +269 -34
  6. package/package.json +28 -64
  7. package/.beans.yml +0 -6
  8. package/.claude/settings.local.json +0 -18
  9. package/.editorconfig +0 -13
  10. package/.github/dependabot.yml +0 -11
  11. package/.github/workflows/release.yml +0 -235
  12. package/.github/workflows/test.yml +0 -84
  13. package/.husky/pre-commit +0 -1
  14. package/.nvmrc +0 -1
  15. package/.oxfmtrc.json +0 -11
  16. package/.oxlintrc.json +0 -37
  17. package/.vscode/settings.json +0 -3
  18. package/CHANGELOG.md +0 -160
  19. package/CONTRIBUTING.md +0 -139
  20. package/LICENSE.txt +0 -21
  21. package/codeql/codeql-custom-queries-actions/README.md +0 -14
  22. package/codeql/codeql-custom-queries-actions/codeql-pack.lock.yml +0 -32
  23. package/codeql/codeql-custom-queries-actions/codeql-pack.yml +0 -7
  24. package/codeql/codeql-custom-queries-actions/qlpack.yml +0 -6
  25. package/codeql/codeql-custom-queries-actions/queries/github-script-without-tojson.ql +0 -18
  26. package/codeql/codeql-custom-queries-actions/queries/strict-external-action-pinning.ql +0 -18
  27. package/codeql/codeql-custom-queries-javascript/README.md +0 -14
  28. package/codeql/codeql-custom-queries-javascript/codeql-pack.lock.yml +0 -30
  29. package/codeql/codeql-custom-queries-javascript/codeql-pack.yml +0 -7
  30. package/codeql/codeql-custom-queries-javascript/qlpack.yml +0 -6
  31. package/codeql/codeql-custom-queries-javascript/queries/child-process-shell-apis.ql +0 -26
  32. package/codeql/codeql-custom-queries-javascript/queries/innerhtml-assignment.ql +0 -24
  33. package/dist/README.md +0 -307
  34. package/dist/beans-mcp-server.cjs.map +0 -1
  35. package/dist/index.cjs.map +0 -1
  36. package/dist/index.js.map +0 -1
  37. package/dist/package.json +0 -43
  38. package/pnpm-workspace.yaml +0 -2
  39. package/scripts/release.js +0 -433
  40. package/scripts/write-dist-package.js +0 -53
  41. package/src/cli.ts +0 -14
  42. package/src/index.ts +0 -21
  43. package/src/internal/graphql.ts +0 -33
  44. package/src/internal/queryHelpers.ts +0 -157
  45. package/src/server/BeansMcpServer.ts +0 -623
  46. package/src/server/backend.ts +0 -364
  47. package/src/test/BeansMcpServer.test.ts +0 -514
  48. package/src/test/handlers.unit.test.ts +0 -201
  49. package/src/test/parseCliArgs.test.ts +0 -69
  50. package/src/test/protocol.e2e.test.ts +0 -884
  51. package/src/test/queryHelpers.test.ts +0 -524
  52. package/src/test/startBeansMcpServer.test.ts +0 -146
  53. package/src/test/tools-integration.test.ts +0 -912
  54. package/src/test/utils.test.ts +0 -81
  55. package/src/types.ts +0 -46
  56. package/src/utils.ts +0 -20
  57. package/tsconfig.json +0 -24
  58. package/tsup.config.ts +0 -42
  59. package/vitest.config.ts +0 -18
@@ -1,201 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import {
3
- beanFileHandler,
4
- createHandler,
5
- deleteHandler,
6
- editHandler,
7
- getBeanById,
8
- initHandler,
9
- outputHandler,
10
- queryHandler,
11
- reopenHandler,
12
- updateHandler,
13
- viewHandler,
14
- } from '../server/BeansMcpServer';
15
-
16
- const sampleBean = {
17
- id: 'b1',
18
- slug: 'b1',
19
- path: '.beans/b1.md',
20
- title: 'B1',
21
- body: 'body',
22
- status: 'completed',
23
- type: 'task',
24
- };
25
-
26
- function makeBackend(overrides: Partial<any> = {}) {
27
- return {
28
- list: vi.fn(async () => [sampleBean, { ...sampleBean, id: 'b2', status: 'draft' }]),
29
- init: vi.fn(async (p?: string) => ({ ok: true, prefix: p })),
30
- create: vi.fn(async (input: any) => ({
31
- ...sampleBean,
32
- ...input,
33
- id: 'new',
34
- })),
35
- update: vi.fn(async (id: string, updates: any) => ({
36
- ...sampleBean,
37
- id,
38
- ...updates,
39
- })),
40
- delete: vi.fn(async (id: string) => ({ ok: true, id })),
41
- openConfig: vi.fn(async () => ({ configPath: '.beans.yml', content: 'x' })),
42
- graphqlSchema: vi.fn(async () => ''),
43
- readOutputLog: vi.fn(async ({ lines }: any) => ({
44
- path: 'p',
45
- content: 'log',
46
- linesReturned: lines ?? 0,
47
- })),
48
- readBeanFile: vi.fn(async (path: string) => ({ path, content: 'x' })),
49
- editBeanFile: vi.fn(async (path: string, content: string) => ({
50
- path,
51
- bytes: Buffer.byteLength(content, 'utf8'),
52
- })),
53
- createBeanFile: vi.fn(async (path: string, content: string, opts: any) => ({
54
- path,
55
- bytes: Buffer.byteLength(content, 'utf8'),
56
- created: true,
57
- })),
58
- deleteBeanFile: vi.fn(async (path: string) => ({ path, deleted: true })),
59
- ...overrides,
60
- };
61
- }
62
-
63
- describe('Handlers (unit)', () => {
64
- it('getBeanById returns bean when found', async () => {
65
- const backend = makeBackend();
66
- const b = await getBeanById(backend, 'b1');
67
- expect(b.id).toBe('b1');
68
- });
69
-
70
- it('getBeanById throws when not found', async () => {
71
- const backend = makeBackend({ list: vi.fn(async () => []) });
72
- await expect(getBeanById(backend, 'missing')).rejects.toThrow(/Bean not found/);
73
- });
74
-
75
- it('initHandler calls backend.init and wraps result', async () => {
76
- const backend = makeBackend();
77
- const res = await initHandler(backend)({ prefix: 'pfx' });
78
- expect(backend.init).toHaveBeenCalledWith('pfx');
79
- const data = JSON.parse(res.content?.[0]?.text ?? '{}');
80
- expect(data).toBeDefined();
81
- });
82
-
83
- it('viewHandler returns bean structured content', async () => {
84
- const backend = makeBackend();
85
- const res = await viewHandler(backend)({ beanId: 'b1' });
86
- const data = JSON.parse(res.content?.[0]?.text ?? '{}');
87
- expect(data.bean.id).toBe('b1');
88
- });
89
-
90
- it('createHandler delegates to backend.create', async () => {
91
- const backend = makeBackend();
92
- const res = await createHandler(backend)({ title: 'T', type: 't' });
93
- expect(backend.create).toHaveBeenCalled();
94
- const data = JSON.parse(res.content?.[0]?.text ?? '{}');
95
- expect(data.bean.id).toBe('new');
96
- });
97
-
98
- it('editHandler delegates to backend.update', async () => {
99
- const backend = makeBackend();
100
- const res = await editHandler(backend)({ beanId: 'b1', status: 'todo' });
101
- expect(backend.update).toHaveBeenCalledWith('b1', { status: 'todo' });
102
- const data = JSON.parse(res.content?.[0]?.text ?? '{}');
103
- expect(data.bean.status).toBe('todo');
104
- });
105
-
106
- it('updateHandler delegates body updates to backend.update', async () => {
107
- const backend = makeBackend({
108
- update: vi.fn(async (id: string, updates: any) => ({
109
- ...sampleBean,
110
- id,
111
- ...updates,
112
- })),
113
- });
114
- const res = await updateHandler(backend)({ beanId: 'b1', body: 'new body text' } as any);
115
- expect(backend.update).toHaveBeenCalledWith('b1', expect.objectContaining({ body: 'new body text' }));
116
- const data = JSON.parse(res.content?.[0]?.text ?? '{}');
117
- expect(data.bean.body).toBe('new body text');
118
- });
119
-
120
- it('reopenHandler throws if current status mismatches', async () => {
121
- const backend = makeBackend();
122
- await expect(
123
- reopenHandler(backend)({
124
- beanId: 'b1',
125
- requiredCurrentStatus: 'scrapped',
126
- targetStatus: 'todo',
127
- }),
128
- ).rejects.toThrow(/is not scrapped/);
129
- });
130
-
131
- it('reopenHandler updates when status matches', async () => {
132
- const backend = makeBackend();
133
- const res = await reopenHandler(backend)({
134
- beanId: 'b1',
135
- requiredCurrentStatus: 'completed',
136
- targetStatus: 'todo',
137
- });
138
- expect(backend.update).toHaveBeenCalled();
139
- const data = JSON.parse(res.content?.[0]?.text ?? '{}');
140
- expect(data.bean.status).toBe('todo');
141
- });
142
-
143
- it('deleteHandler enforces draft/scrapped unless force', async () => {
144
- const backend = makeBackend();
145
- await expect(deleteHandler(backend)({ beanId: 'b1', force: false })).rejects.toThrow(
146
- /Only draft and scrapped beans are deletable/,
147
- );
148
- const res = await deleteHandler(backend)({ beanId: 'b1', force: true });
149
- expect(backend.delete).toHaveBeenCalledWith('b1');
150
- });
151
-
152
- it('beanFileHandler routes operations', async () => {
153
- const backend = makeBackend();
154
- const _read = await beanFileHandler(backend)({
155
- operation: 'read',
156
- path: 'p',
157
- });
158
- expect(backend.readBeanFile).toHaveBeenCalledWith('p');
159
- const _edit = await beanFileHandler(backend)({
160
- operation: 'edit',
161
- path: 'p',
162
- content: 'c',
163
- });
164
- expect(backend.editBeanFile).toHaveBeenCalledWith('p', 'c');
165
- const _create = await beanFileHandler(backend)({
166
- operation: 'create',
167
- path: 'p',
168
- content: 'c',
169
- overwrite: true,
170
- });
171
- expect(backend.createBeanFile).toHaveBeenCalled();
172
- const _del = await beanFileHandler(backend)({
173
- operation: 'delete',
174
- path: 'p',
175
- });
176
- expect(backend.deleteBeanFile).toHaveBeenCalledWith('p');
177
- });
178
-
179
- it('beanFileHandler throws on unsupported operation', async () => {
180
- const backend = makeBackend();
181
- await expect(beanFileHandler(backend)({ operation: 'noop' as 'read', path: 'p' })).rejects.toThrow(
182
- 'Unsupported operation',
183
- );
184
- });
185
-
186
- it('outputHandler read and show', async () => {
187
- const backend = makeBackend();
188
- const _r = await outputHandler(backend)({ operation: 'read', lines: 10 });
189
- expect(backend.readOutputLog).toHaveBeenCalled();
190
- const s = await outputHandler(backend)({ operation: 'show' });
191
- const data = JSON.parse(s.content?.[0]?.text ?? '{}');
192
- expect(data.message).toMatch(/When using VS Code UI/);
193
- });
194
-
195
- it('queryHandler delegates to handleQueryOperation', async () => {
196
- const backend = makeBackend();
197
- const res = await queryHandler(backend)({ operation: 'refresh' });
198
- // handleQueryOperation returns value directly; ensure promise resolves
199
- expect(res).toBeDefined();
200
- });
201
- });
@@ -1,69 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { parseCliArgs } from '../server/BeansMcpServer';
3
-
4
- describe('parseCliArgs', () => {
5
- it('should parse workspace root positional argument', () => {
6
- const result = parseCliArgs(['/workspace']);
7
- expect(result.workspaceRoot).toBe('/workspace');
8
- expect(result.cliPath).toBe('beans');
9
- expect(result.workspaceExplicit).toBe(true);
10
- });
11
-
12
- it('should parse --workspace-root flag', () => {
13
- const result = parseCliArgs(['--workspace-root', '/workspace']);
14
- expect(result.workspaceRoot).toBe('/workspace');
15
- expect(result.workspaceExplicit).toBe(true);
16
- });
17
-
18
- it('should mark workspaceExplicit false when no workspace arg given', () => {
19
- const result = parseCliArgs([]);
20
- expect(result.workspaceExplicit).toBe(false);
21
- });
22
-
23
- it('should parse --cli-path flag', () => {
24
- const result = parseCliArgs(['--cli-path', '/usr/local/bin/beans']);
25
- expect(result.cliPath).toBe('/usr/local/bin/beans');
26
- });
27
-
28
- it('should parse --port flag', () => {
29
- const result = parseCliArgs(['--port', '8080']);
30
- expect(result.port).toBe(8080);
31
- });
32
-
33
- it('should parse --log-dir flag', () => {
34
- const result = parseCliArgs(['--log-dir', '/tmp/logs']);
35
- expect(result.logDir).toBe('/tmp/logs');
36
- });
37
-
38
- it('should reject suspicious CLI paths', () => {
39
- expect(() => {
40
- parseCliArgs(['--cli-path', 'beans; rm -rf /']);
41
- }).toThrow('Invalid CLI path');
42
- });
43
-
44
- describe('--help / -h', () => {
45
- afterEach(() => {
46
- vi.restoreAllMocks();
47
- });
48
-
49
- it('--help writes help text to stdout and exits with 0', () => {
50
- const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
51
- const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as (code?: number) => never);
52
-
53
- parseCliArgs(['--help']);
54
-
55
- expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('Usage'));
56
- expect(exitSpy).toHaveBeenCalledWith(0);
57
- });
58
-
59
- it('-h writes help text to stdout and exits with 0', () => {
60
- const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
61
- const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as (code?: number) => never);
62
-
63
- parseCliArgs(['-h']);
64
-
65
- expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('Usage'));
66
- expect(exitSpy).toHaveBeenCalledWith(0);
67
- });
68
- });
69
- });