beth-copilot 1.0.18 → 1.1.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 +41 -28
- package/README.md +87 -247
- package/bin/cli.js +158 -358
- package/dist/__tests__/smoke.test.d.ts +8 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +49 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/cli/commands/beads.e2e.test.d.ts +13 -0
- package/dist/cli/commands/beads.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/beads.e2e.test.js +526 -0
- package/dist/cli/commands/beads.e2e.test.js.map +1 -0
- package/dist/cli/commands/cli-edge-cases.e2e.test.d.ts +32 -0
- package/dist/cli/commands/cli-edge-cases.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/cli-edge-cases.e2e.test.js +162 -0
- package/dist/cli/commands/cli-edge-cases.e2e.test.js.map +1 -0
- package/dist/cli/commands/close.d.ts +89 -0
- package/dist/cli/commands/close.d.ts.map +1 -0
- package/dist/cli/commands/close.e2e.test.d.ts +27 -0
- package/dist/cli/commands/close.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/close.e2e.test.js +252 -0
- package/dist/cli/commands/close.e2e.test.js.map +1 -0
- package/dist/cli/commands/close.js +309 -0
- package/dist/cli/commands/close.js.map +1 -0
- package/dist/cli/commands/close.test.d.ts +15 -0
- package/dist/cli/commands/close.test.d.ts.map +1 -0
- package/dist/cli/commands/close.test.js +634 -0
- package/dist/cli/commands/close.test.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +23 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +93 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/doctor.test.js +209 -0
- package/dist/cli/commands/doctor.test.js.map +1 -1
- package/dist/cli/commands/framework-isolation.test.d.ts +30 -0
- package/dist/cli/commands/framework-isolation.test.d.ts.map +1 -0
- package/dist/cli/commands/framework-isolation.test.js +119 -0
- package/dist/cli/commands/framework-isolation.test.js.map +1 -0
- package/dist/cli/commands/help.e2e.test.js +4 -4
- package/dist/cli/commands/help.e2e.test.js.map +1 -1
- package/dist/cli/commands/init-logic.e2e.test.d.ts +37 -0
- package/dist/cli/commands/init-logic.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/init-logic.e2e.test.js +305 -0
- package/dist/cli/commands/init-logic.e2e.test.js.map +1 -0
- package/dist/cli/commands/land.d.ts +142 -0
- package/dist/cli/commands/land.d.ts.map +1 -0
- package/dist/cli/commands/land.js +647 -0
- package/dist/cli/commands/land.js.map +1 -0
- package/dist/cli/commands/land.test.d.ts +20 -0
- package/dist/cli/commands/land.test.d.ts.map +1 -0
- package/dist/cli/commands/land.test.js +622 -0
- package/dist/cli/commands/land.test.js.map +1 -0
- package/dist/cli/commands/mcp.e2e.test.js +22 -29
- package/dist/cli/commands/mcp.e2e.test.js.map +1 -1
- package/dist/cli/commands/pipeline.e2e.test.js +20 -20
- package/dist/cli/commands/pipeline.e2e.test.js.map +1 -1
- package/dist/cli/commands/pre-push-guard.d.ts +84 -0
- package/dist/cli/commands/pre-push-guard.d.ts.map +1 -0
- package/dist/cli/commands/pre-push-guard.e2e.test.d.ts +24 -0
- package/dist/cli/commands/pre-push-guard.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/pre-push-guard.e2e.test.js +171 -0
- package/dist/cli/commands/pre-push-guard.e2e.test.js.map +1 -0
- package/dist/cli/commands/pre-push-guard.js +257 -0
- package/dist/cli/commands/pre-push-guard.js.map +1 -0
- package/dist/cli/commands/pre-push-guard.test.d.ts +15 -0
- package/dist/cli/commands/pre-push-guard.test.d.ts.map +1 -0
- package/dist/cli/commands/pre-push-guard.test.js +397 -0
- package/dist/cli/commands/pre-push-guard.test.js.map +1 -0
- package/dist/cli/commands/quickstart-expanded.e2e.test.d.ts +23 -0
- package/dist/cli/commands/quickstart-expanded.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/quickstart-expanded.e2e.test.js +179 -0
- package/dist/cli/commands/quickstart-expanded.e2e.test.js.map +1 -0
- package/dist/cli/commands/quickstart.d.ts.map +1 -1
- package/dist/cli/commands/quickstart.js +7 -23
- package/dist/cli/commands/quickstart.js.map +1 -1
- package/dist/cli/commands/quickstart.test.js +40 -67
- package/dist/cli/commands/quickstart.test.js.map +1 -1
- package/dist/core/agents/suite.test.js +4 -2
- package/dist/core/agents/suite.test.js.map +1 -1
- package/dist/core/agents/tools.test.js +5 -1
- package/dist/core/agents/tools.test.js.map +1 -1
- package/dist/index.d.ts +3 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -10
- package/dist/index.js.map +1 -1
- package/package.json +15 -9
- package/sbom.json +2011 -819
- package/templates/.github/agents/beth.agent.md +220 -66
- package/templates/.github/agents/developer.agent.md +53 -90
- package/templates/.github/agents/product-manager.agent.md +15 -68
- package/templates/.github/agents/researcher.agent.md +20 -71
- package/templates/.github/agents/security-reviewer.agent.md +29 -81
- package/templates/.github/agents/tester.agent.md +40 -69
- package/templates/.github/agents/ux-designer.agent.md +20 -74
- package/templates/.github/copilot-instructions.md +217 -225
- package/templates/AGENTS.md +108 -20
- package/templates/mcp.json.example +0 -3
- package/dist/cli/commands/client-config.d.ts +0 -31
- package/dist/cli/commands/client-config.d.ts.map +0 -1
- package/dist/cli/commands/client-config.e2e.test.d.ts +0 -15
- package/dist/cli/commands/client-config.e2e.test.d.ts.map +0 -1
- package/dist/cli/commands/client-config.e2e.test.js +0 -556
- package/dist/cli/commands/client-config.e2e.test.js.map +0 -1
- package/dist/cli/commands/client-config.js +0 -73
- package/dist/cli/commands/client-config.js.map +0 -1
- package/dist/cli/commands/client-config.test.d.ts +0 -6
- package/dist/cli/commands/client-config.test.d.ts.map +0 -1
- package/dist/cli/commands/client-config.test.js +0 -133
- package/dist/cli/commands/client-config.test.js.map +0 -1
- package/dist/cli/commands/init-quickstart.e2e.test.d.ts +0 -11
- package/dist/cli/commands/init-quickstart.e2e.test.d.ts.map +0 -1
- package/dist/cli/commands/init-quickstart.e2e.test.js +0 -221
- package/dist/cli/commands/init-quickstart.e2e.test.js.map +0 -1
- package/dist/core/context.d.ts +0 -171
- package/dist/core/context.d.ts.map +0 -1
- package/dist/core/context.js +0 -353
- package/dist/core/context.js.map +0 -1
- package/dist/core/context.test.d.ts +0 -8
- package/dist/core/context.test.d.ts.map +0 -1
- package/dist/core/context.test.js +0 -253
- package/dist/core/context.test.js.map +0 -1
- package/dist/core/handoffs.d.ts +0 -151
- package/dist/core/handoffs.d.ts.map +0 -1
- package/dist/core/handoffs.js +0 -220
- package/dist/core/handoffs.js.map +0 -1
- package/dist/core/handoffs.test.d.ts +0 -8
- package/dist/core/handoffs.test.d.ts.map +0 -1
- package/dist/core/handoffs.test.js +0 -231
- package/dist/core/handoffs.test.js.map +0 -1
- package/dist/core/orchestrator.d.ts +0 -246
- package/dist/core/orchestrator.d.ts.map +0 -1
- package/dist/core/orchestrator.js +0 -514
- package/dist/core/orchestrator.js.map +0 -1
- package/dist/core/orchestrator.test.d.ts +0 -8
- package/dist/core/orchestrator.test.d.ts.map +0 -1
- package/dist/core/orchestrator.test.js +0 -517
- package/dist/core/orchestrator.test.js.map +0 -1
- package/dist/core/router.d.ts +0 -102
- package/dist/core/router.d.ts.map +0 -1
- package/dist/core/router.js +0 -178
- package/dist/core/router.js.map +0 -1
- package/dist/core/router.test.d.ts +0 -8
- package/dist/core/router.test.d.ts.map +0 -1
- package/dist/core/router.test.js +0 -215
- package/dist/core/router.test.js.map +0 -1
- package/dist/init.test.js +0 -288
- package/dist/providers/azure.d.ts +0 -147
- package/dist/providers/azure.d.ts.map +0 -1
- package/dist/providers/azure.js +0 -491
- package/dist/providers/azure.js.map +0 -1
- package/dist/providers/azure.test.d.ts +0 -11
- package/dist/providers/azure.test.d.ts.map +0 -1
- package/dist/providers/azure.test.js +0 -330
- package/dist/providers/azure.test.js.map +0 -1
- package/dist/providers/config.d.ts +0 -87
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -193
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/config.test.d.ts +0 -7
- package/dist/providers/config.test.d.ts.map +0 -1
- package/dist/providers/config.test.js +0 -370
- package/dist/providers/config.test.js.map +0 -1
- package/dist/providers/index.d.ts +0 -18
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js +0 -14
- package/dist/providers/index.js.map +0 -1
- package/dist/providers/interface.d.ts +0 -191
- package/dist/providers/interface.d.ts.map +0 -1
- package/dist/providers/interface.js +0 -94
- package/dist/providers/interface.js.map +0 -1
- package/dist/providers/retry.d.ts +0 -128
- package/dist/providers/retry.d.ts.map +0 -1
- package/dist/providers/retry.js +0 -205
- package/dist/providers/retry.js.map +0 -1
- package/dist/providers/retry.test.d.ts +0 -7
- package/dist/providers/retry.test.d.ts.map +0 -1
- package/dist/providers/retry.test.js +0 -439
- package/dist/providers/retry.test.js.map +0 -1
- package/dist/providers/streaming.d.ts +0 -157
- package/dist/providers/streaming.d.ts.map +0 -1
- package/dist/providers/streaming.js +0 -233
- package/dist/providers/streaming.js.map +0 -1
- package/dist/providers/streaming.test.d.ts +0 -7
- package/dist/providers/streaming.test.d.ts.map +0 -1
- package/dist/providers/streaming.test.js +0 -372
- package/dist/providers/streaming.test.js.map +0 -1
- package/dist/providers/types.d.ts +0 -209
- package/dist/providers/types.d.ts.map +0 -1
- package/dist/providers/types.js +0 -53
- package/dist/providers/types.js.map +0 -1
- package/dist/providers/types.test.d.ts +0 -7
- package/dist/providers/types.test.d.ts.map +0 -1
- package/dist/providers/types.test.js +0 -141
- package/dist/providers/types.test.js.map +0 -1
- package/dist/tools/cli/beads.d.ts +0 -27
- package/dist/tools/cli/beads.d.ts.map +0 -1
- package/dist/tools/cli/beads.js +0 -172
- package/dist/tools/cli/beads.js.map +0 -1
- package/dist/tools/cli/beads.test.d.ts +0 -8
- package/dist/tools/cli/beads.test.d.ts.map +0 -1
- package/dist/tools/cli/beads.test.js +0 -264
- package/dist/tools/cli/beads.test.js.map +0 -1
- package/dist/tools/cli/editFile.d.ts +0 -17
- package/dist/tools/cli/editFile.d.ts.map +0 -1
- package/dist/tools/cli/editFile.js +0 -125
- package/dist/tools/cli/editFile.js.map +0 -1
- package/dist/tools/cli/editFile.test.d.ts +0 -8
- package/dist/tools/cli/editFile.test.d.ts.map +0 -1
- package/dist/tools/cli/editFile.test.js +0 -177
- package/dist/tools/cli/editFile.test.js.map +0 -1
- package/dist/tools/cli/readFile.d.ts +0 -25
- package/dist/tools/cli/readFile.d.ts.map +0 -1
- package/dist/tools/cli/readFile.js +0 -118
- package/dist/tools/cli/readFile.js.map +0 -1
- package/dist/tools/cli/readFile.test.d.ts +0 -8
- package/dist/tools/cli/readFile.test.d.ts.map +0 -1
- package/dist/tools/cli/readFile.test.js +0 -194
- package/dist/tools/cli/readFile.test.js.map +0 -1
- package/dist/tools/cli/search.d.ts +0 -16
- package/dist/tools/cli/search.d.ts.map +0 -1
- package/dist/tools/cli/search.js +0 -261
- package/dist/tools/cli/search.js.map +0 -1
- package/dist/tools/cli/search.test.d.ts +0 -8
- package/dist/tools/cli/search.test.d.ts.map +0 -1
- package/dist/tools/cli/search.test.js +0 -172
- package/dist/tools/cli/search.test.js.map +0 -1
- package/dist/tools/cli/subagent.d.ts +0 -43
- package/dist/tools/cli/subagent.d.ts.map +0 -1
- package/dist/tools/cli/subagent.js +0 -99
- package/dist/tools/cli/subagent.js.map +0 -1
- package/dist/tools/cli/subagent.test.d.ts +0 -8
- package/dist/tools/cli/subagent.test.d.ts.map +0 -1
- package/dist/tools/cli/subagent.test.js +0 -190
- package/dist/tools/cli/subagent.test.js.map +0 -1
- package/dist/tools/cli/terminal.d.ts +0 -19
- package/dist/tools/cli/terminal.d.ts.map +0 -1
- package/dist/tools/cli/terminal.js +0 -164
- package/dist/tools/cli/terminal.js.map +0 -1
- package/dist/tools/cli/terminal.test.d.ts +0 -8
- package/dist/tools/cli/terminal.test.d.ts.map +0 -1
- package/dist/tools/cli/terminal.test.js +0 -161
- package/dist/tools/cli/terminal.test.js.map +0 -1
- package/dist/tools/index.d.ts +0 -25
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -41
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/interface.d.ts +0 -64
- package/dist/tools/interface.d.ts.map +0 -1
- package/dist/tools/interface.js +0 -37
- package/dist/tools/interface.js.map +0 -1
- package/dist/tools/interface.test.d.ts +0 -7
- package/dist/tools/interface.test.d.ts.map +0 -1
- package/dist/tools/interface.test.js +0 -179
- package/dist/tools/interface.test.js.map +0 -1
- package/dist/tools/mcp/bridge.d.ts +0 -48
- package/dist/tools/mcp/bridge.d.ts.map +0 -1
- package/dist/tools/mcp/bridge.js +0 -128
- package/dist/tools/mcp/bridge.js.map +0 -1
- package/dist/tools/mcp/bridge.test.d.ts +0 -8
- package/dist/tools/mcp/bridge.test.d.ts.map +0 -1
- package/dist/tools/mcp/bridge.test.js +0 -300
- package/dist/tools/mcp/bridge.test.js.map +0 -1
- package/dist/tools/mcp/client.d.ts +0 -135
- package/dist/tools/mcp/client.d.ts.map +0 -1
- package/dist/tools/mcp/client.js +0 -263
- package/dist/tools/mcp/client.js.map +0 -1
- package/dist/tools/mcp/client.test.d.ts +0 -8
- package/dist/tools/mcp/client.test.d.ts.map +0 -1
- package/dist/tools/mcp/client.test.js +0 -390
- package/dist/tools/mcp/client.test.js.map +0 -1
- package/dist/tools/registry.d.ts +0 -82
- package/dist/tools/registry.d.ts.map +0 -1
- package/dist/tools/registry.js +0 -99
- package/dist/tools/registry.js.map +0 -1
- package/dist/tools/registry.test.d.ts +0 -7
- package/dist/tools/registry.test.d.ts.map +0 -1
- package/dist/tools/registry.test.js +0 -199
- package/dist/tools/registry.test.js.map +0 -1
- package/dist/tools/suite.test.d.ts +0 -11
- package/dist/tools/suite.test.d.ts.map +0 -1
- package/dist/tools/suite.test.js +0 -119
- package/dist/tools/suite.test.js.map +0 -1
- package/dist/tools/types.d.ts +0 -75
- package/dist/tools/types.d.ts.map +0 -1
- package/dist/tools/types.js +0 -30
- package/dist/tools/types.js.map +0 -1
- package/dist/tools/types.test.d.ts +0 -7
- package/dist/tools/types.test.d.ts.map +0 -1
- package/dist/tools/types.test.js +0 -178
- package/dist/tools/types.test.js.map +0 -1
- package/templates/.vscode/mcp.json +0 -20
- package/templates/CLAUDE.md +0 -129
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"beads.test.d.ts","sourceRoot":"","sources":["../../../src/tools/cli/beads.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Beads Tool Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for the beads tool implementation.
|
|
5
|
-
* Uses dependency injection (createBeadsTool) to mock execFile.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it } from 'node:test';
|
|
8
|
-
import assert from 'node:assert';
|
|
9
|
-
import { createBeadsTool } from './beads.js';
|
|
10
|
-
import { ToolError } from '../types.js';
|
|
11
|
-
/** Create a ToolContext with sensible defaults */
|
|
12
|
-
function createContext(overrides) {
|
|
13
|
-
return {
|
|
14
|
-
workingDir: '/tmp/test',
|
|
15
|
-
permissions: overrides?.permissions ?? {
|
|
16
|
-
allowFileRead: false,
|
|
17
|
-
allowFileWrite: false,
|
|
18
|
-
allowTerminal: true,
|
|
19
|
-
allowNetwork: false,
|
|
20
|
-
},
|
|
21
|
-
signal: overrides?.signal,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Create a mock execFile function that records calls and returns configured output.
|
|
26
|
-
*
|
|
27
|
-
* @param responses - Map of first-arg command to { stdout, stderr, error }
|
|
28
|
-
* @returns [mockFn, calls] — the mock function and an array of recorded call args
|
|
29
|
-
*/
|
|
30
|
-
function createMockExec(responses) {
|
|
31
|
-
const calls = [];
|
|
32
|
-
const mockFn = (file, args, _options, callback) => {
|
|
33
|
-
calls.push({ file, args: [...args] });
|
|
34
|
-
// First call is --version check, subsequent calls are the actual command
|
|
35
|
-
const isVersionCheck = args.includes('--version');
|
|
36
|
-
const response = isVersionCheck
|
|
37
|
-
? (responses?.version ?? { stdout: 'beads 1.0.0', stderr: '', error: null })
|
|
38
|
-
: (responses?.command ?? { stdout: 'ok', stderr: '', error: null });
|
|
39
|
-
// Use setImmediate to keep callback async like real execFile
|
|
40
|
-
setImmediate(() => {
|
|
41
|
-
callback(response.error ?? null, response.stdout ?? '', response.stderr ?? '');
|
|
42
|
-
});
|
|
43
|
-
// Return a minimal ChildProcess-like object
|
|
44
|
-
return { kill: () => { } };
|
|
45
|
-
};
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
-
return [mockFn, calls];
|
|
48
|
-
}
|
|
49
|
-
describe('beads tool', () => {
|
|
50
|
-
describe('metadata', () => {
|
|
51
|
-
it('should have name "beads"', () => {
|
|
52
|
-
const [mockFn] = createMockExec();
|
|
53
|
-
const tool = createBeadsTool(mockFn);
|
|
54
|
-
assert.strictEqual(tool.name, 'beads');
|
|
55
|
-
});
|
|
56
|
-
it('should have a description', () => {
|
|
57
|
-
const [mockFn] = createMockExec();
|
|
58
|
-
const tool = createBeadsTool(mockFn);
|
|
59
|
-
assert.ok(tool.description.length > 0);
|
|
60
|
-
});
|
|
61
|
-
it('should have an inputSchema with command required', () => {
|
|
62
|
-
const [mockFn] = createMockExec();
|
|
63
|
-
const tool = createBeadsTool(mockFn);
|
|
64
|
-
assert.strictEqual(tool.inputSchema.type, 'object');
|
|
65
|
-
assert.ok(Array.isArray(tool.inputSchema.required));
|
|
66
|
-
assert.ok(tool.inputSchema.required.includes('command'));
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
describe('command building — create', () => {
|
|
70
|
-
it('should build args for create with title only', async () => {
|
|
71
|
-
const [mockFn, calls] = createMockExec();
|
|
72
|
-
const tool = createBeadsTool(mockFn);
|
|
73
|
-
await tool.execute({ command: 'create', args: { title: 'Fix bug' } }, createContext());
|
|
74
|
-
// Second call is the actual command (first is --version)
|
|
75
|
-
assert.deepStrictEqual(calls[1].args, ['create', 'Fix bug']);
|
|
76
|
-
});
|
|
77
|
-
it('should build args for create with all optional args', async () => {
|
|
78
|
-
const [mockFn, calls] = createMockExec();
|
|
79
|
-
const tool = createBeadsTool(mockFn);
|
|
80
|
-
await tool.execute({
|
|
81
|
-
command: 'create',
|
|
82
|
-
args: {
|
|
83
|
-
title: 'Feature X',
|
|
84
|
-
description: 'Build feature X',
|
|
85
|
-
type: 'epic',
|
|
86
|
-
priority: 1,
|
|
87
|
-
parent: 'abc-123',
|
|
88
|
-
deps: 'dep-1,dep-2',
|
|
89
|
-
label: 'in_progress',
|
|
90
|
-
},
|
|
91
|
-
}, createContext());
|
|
92
|
-
const cmdArgs = calls[1].args;
|
|
93
|
-
assert.strictEqual(cmdArgs[0], 'create');
|
|
94
|
-
assert.strictEqual(cmdArgs[1], 'Feature X');
|
|
95
|
-
assert.ok(cmdArgs.includes('--description=Build feature X'));
|
|
96
|
-
assert.ok(cmdArgs.includes('--type'));
|
|
97
|
-
assert.ok(cmdArgs.includes('epic'));
|
|
98
|
-
assert.ok(cmdArgs.includes('-p'));
|
|
99
|
-
assert.ok(cmdArgs.includes('1'));
|
|
100
|
-
assert.ok(cmdArgs.includes('--parent'));
|
|
101
|
-
assert.ok(cmdArgs.includes('abc-123'));
|
|
102
|
-
assert.ok(cmdArgs.includes('--deps'));
|
|
103
|
-
assert.ok(cmdArgs.includes('dep-1,dep-2'));
|
|
104
|
-
assert.ok(cmdArgs.includes('-l'));
|
|
105
|
-
assert.ok(cmdArgs.includes('in_progress'));
|
|
106
|
-
});
|
|
107
|
-
it('should reject create without title', async () => {
|
|
108
|
-
const [mockFn] = createMockExec();
|
|
109
|
-
const tool = createBeadsTool(mockFn);
|
|
110
|
-
await assert.rejects(() => tool.execute({ command: 'create', args: {} }, createContext()), (err) => {
|
|
111
|
-
assert.ok(err instanceof ToolError);
|
|
112
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
113
|
-
assert.ok(err.message.includes('title'));
|
|
114
|
-
return true;
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
describe('command building — close', () => {
|
|
119
|
-
it('should build args for close', async () => {
|
|
120
|
-
const [mockFn, calls] = createMockExec();
|
|
121
|
-
const tool = createBeadsTool(mockFn);
|
|
122
|
-
await tool.execute({ command: 'close', args: { id: 'issue-1' } }, createContext());
|
|
123
|
-
assert.deepStrictEqual(calls[1].args, ['close', 'issue-1']);
|
|
124
|
-
});
|
|
125
|
-
it('should reject close without id', async () => {
|
|
126
|
-
const [mockFn] = createMockExec();
|
|
127
|
-
const tool = createBeadsTool(mockFn);
|
|
128
|
-
await assert.rejects(() => tool.execute({ command: 'close', args: {} }, createContext()), (err) => {
|
|
129
|
-
assert.ok(err instanceof ToolError);
|
|
130
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
131
|
-
assert.ok(err.message.includes('id'));
|
|
132
|
-
return true;
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
describe('command building — list', () => {
|
|
137
|
-
it('should build args for list', async () => {
|
|
138
|
-
const [mockFn, calls] = createMockExec();
|
|
139
|
-
const tool = createBeadsTool(mockFn);
|
|
140
|
-
await tool.execute({ command: 'list' }, createContext());
|
|
141
|
-
assert.deepStrictEqual(calls[1].args, ['list']);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
describe('command building — ready', () => {
|
|
145
|
-
it('should build args for ready', async () => {
|
|
146
|
-
const [mockFn, calls] = createMockExec();
|
|
147
|
-
const tool = createBeadsTool(mockFn);
|
|
148
|
-
await tool.execute({ command: 'ready' }, createContext());
|
|
149
|
-
assert.deepStrictEqual(calls[1].args, ['ready']);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
describe('command building — show', () => {
|
|
153
|
-
it('should build args for show', async () => {
|
|
154
|
-
const [mockFn, calls] = createMockExec();
|
|
155
|
-
const tool = createBeadsTool(mockFn);
|
|
156
|
-
await tool.execute({ command: 'show', args: { id: 'issue-2' } }, createContext());
|
|
157
|
-
assert.deepStrictEqual(calls[1].args, ['show', 'issue-2']);
|
|
158
|
-
});
|
|
159
|
-
it('should reject show without id', async () => {
|
|
160
|
-
const [mockFn] = createMockExec();
|
|
161
|
-
const tool = createBeadsTool(mockFn);
|
|
162
|
-
await assert.rejects(() => tool.execute({ command: 'show', args: {} }, createContext()), (err) => {
|
|
163
|
-
assert.ok(err instanceof ToolError);
|
|
164
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
165
|
-
assert.ok(err.message.includes('id'));
|
|
166
|
-
return true;
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
describe('command building — dep-tree', () => {
|
|
171
|
-
it('should build args for dep-tree', async () => {
|
|
172
|
-
const [mockFn, calls] = createMockExec();
|
|
173
|
-
const tool = createBeadsTool(mockFn);
|
|
174
|
-
await tool.execute({ command: 'dep-tree', args: { id: 'issue-3' } }, createContext());
|
|
175
|
-
assert.deepStrictEqual(calls[1].args, ['dep', 'tree', 'issue-3']);
|
|
176
|
-
});
|
|
177
|
-
it('should reject dep-tree without id', async () => {
|
|
178
|
-
const [mockFn] = createMockExec();
|
|
179
|
-
const tool = createBeadsTool(mockFn);
|
|
180
|
-
await assert.rejects(() => tool.execute({ command: 'dep-tree', args: {} }, createContext()), (err) => {
|
|
181
|
-
assert.ok(err instanceof ToolError);
|
|
182
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
183
|
-
assert.ok(err.message.includes('id'));
|
|
184
|
-
return true;
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
describe('permissions', () => {
|
|
189
|
-
it('should throw PERMISSION_DENIED when allowTerminal is false', async () => {
|
|
190
|
-
const [mockFn] = createMockExec();
|
|
191
|
-
const tool = createBeadsTool(mockFn);
|
|
192
|
-
const ctx = createContext({
|
|
193
|
-
permissions: {
|
|
194
|
-
allowFileRead: false,
|
|
195
|
-
allowFileWrite: false,
|
|
196
|
-
allowTerminal: false,
|
|
197
|
-
allowNetwork: false,
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
await assert.rejects(() => tool.execute({ command: 'list' }, ctx), (err) => {
|
|
201
|
-
assert.ok(err instanceof ToolError);
|
|
202
|
-
assert.strictEqual(err.code, 'PERMISSION_DENIED');
|
|
203
|
-
return true;
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
describe('bd not installed', () => {
|
|
208
|
-
it('should throw NOT_FOUND when bd --version fails', async () => {
|
|
209
|
-
const [mockFn] = createMockExec({
|
|
210
|
-
version: { error: new Error('command not found: bd'), stdout: '', stderr: '' },
|
|
211
|
-
});
|
|
212
|
-
const tool = createBeadsTool(mockFn);
|
|
213
|
-
await assert.rejects(() => tool.execute({ command: 'list' }, createContext()), (err) => {
|
|
214
|
-
assert.ok(err instanceof ToolError);
|
|
215
|
-
assert.strictEqual(err.code, 'NOT_FOUND');
|
|
216
|
-
assert.ok(err.message.includes('bd'));
|
|
217
|
-
return true;
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
describe('command execution failure', () => {
|
|
222
|
-
it('should throw EXECUTION_FAILED on bd errors', async () => {
|
|
223
|
-
const [mockFn] = createMockExec({
|
|
224
|
-
command: { error: new Error('bd close failed'), stdout: '', stderr: 'no such issue' },
|
|
225
|
-
});
|
|
226
|
-
const tool = createBeadsTool(mockFn);
|
|
227
|
-
await assert.rejects(() => tool.execute({ command: 'close', args: { id: 'bad-id' } }, createContext()), (err) => {
|
|
228
|
-
assert.ok(err instanceof ToolError);
|
|
229
|
-
assert.strictEqual(err.code, 'EXECUTION_FAILED');
|
|
230
|
-
return true;
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
describe('invalid command', () => {
|
|
235
|
-
it('should throw INVALID_INPUT for unknown command', async () => {
|
|
236
|
-
const [mockFn] = createMockExec();
|
|
237
|
-
const tool = createBeadsTool(mockFn);
|
|
238
|
-
await assert.rejects(() => tool.execute({ command: 'purge' }, createContext()), (err) => {
|
|
239
|
-
assert.ok(err instanceof ToolError);
|
|
240
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
241
|
-
return true;
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
describe('successful output', () => {
|
|
246
|
-
it('should return success with trimmed output', async () => {
|
|
247
|
-
const [mockFn] = createMockExec({
|
|
248
|
-
command: { stdout: ' issue-1: Fix bug\n', stderr: '', error: null },
|
|
249
|
-
});
|
|
250
|
-
const tool = createBeadsTool(mockFn);
|
|
251
|
-
const result = await tool.execute({ command: 'list' }, createContext());
|
|
252
|
-
assert.strictEqual(result.success, true);
|
|
253
|
-
assert.strictEqual(result.output, 'issue-1: Fix bug');
|
|
254
|
-
});
|
|
255
|
-
it('should include command and args in metadata', async () => {
|
|
256
|
-
const [mockFn] = createMockExec();
|
|
257
|
-
const tool = createBeadsTool(mockFn);
|
|
258
|
-
const result = await tool.execute({ command: 'ready' }, createContext());
|
|
259
|
-
assert.strictEqual(result.metadata?.command, 'ready');
|
|
260
|
-
assert.deepStrictEqual(result.metadata?.args, ['ready']);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
//# sourceMappingURL=beads.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"beads.test.js","sourceRoot":"","sources":["../../../src/tools/cli/beads.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EditFile Tool
|
|
3
|
-
*
|
|
4
|
-
* Replaces an exact string occurrence in a file.
|
|
5
|
-
* Uses atomic write (write to temp file, then rename) to prevent
|
|
6
|
-
* data loss on failure. Validates paths against traversal and
|
|
7
|
-
* injection attacks using the shared path validation utilities.
|
|
8
|
-
*/
|
|
9
|
-
import type { Tool } from '../interface.js';
|
|
10
|
-
/**
|
|
11
|
-
* EditFile tool implementation.
|
|
12
|
-
*
|
|
13
|
-
* Reads a file, replaces exactly one occurrence of oldString with newString,
|
|
14
|
-
* and writes the result back atomically (write temp + rename).
|
|
15
|
-
*/
|
|
16
|
-
export declare const editFileTool: Tool;
|
|
17
|
-
//# sourceMappingURL=editFile.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"editFile.d.ts","sourceRoot":"","sources":["../../../src/tools/cli/editFile.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAwB5C;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,IA8G1B,CAAC"}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EditFile Tool
|
|
3
|
-
*
|
|
4
|
-
* Replaces an exact string occurrence in a file.
|
|
5
|
-
* Uses atomic write (write to temp file, then rename) to prevent
|
|
6
|
-
* data loss on failure. Validates paths against traversal and
|
|
7
|
-
* injection attacks using the shared path validation utilities.
|
|
8
|
-
*/
|
|
9
|
-
import { readFile, writeFile, rename } from 'node:fs/promises';
|
|
10
|
-
import { dirname, join } from 'node:path';
|
|
11
|
-
import { randomBytes } from 'node:crypto';
|
|
12
|
-
import { validateFilePath } from './readFile.js';
|
|
13
|
-
import { ToolError } from '../types.js';
|
|
14
|
-
/** Input schema for the editFile tool */
|
|
15
|
-
const inputSchema = {
|
|
16
|
-
type: 'object',
|
|
17
|
-
properties: {
|
|
18
|
-
filePath: {
|
|
19
|
-
type: 'string',
|
|
20
|
-
description: 'Absolute path to the file to edit',
|
|
21
|
-
},
|
|
22
|
-
oldString: {
|
|
23
|
-
type: 'string',
|
|
24
|
-
description: 'Exact string to find and replace (must match exactly once)',
|
|
25
|
-
},
|
|
26
|
-
newString: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
description: 'Replacement string',
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
required: ['filePath', 'oldString', 'newString'],
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* EditFile tool implementation.
|
|
35
|
-
*
|
|
36
|
-
* Reads a file, replaces exactly one occurrence of oldString with newString,
|
|
37
|
-
* and writes the result back atomically (write temp + rename).
|
|
38
|
-
*/
|
|
39
|
-
export const editFileTool = {
|
|
40
|
-
name: 'editFile',
|
|
41
|
-
description: 'Replace an exact string in a file. The oldString must match exactly once.',
|
|
42
|
-
inputSchema,
|
|
43
|
-
async execute(input, context) {
|
|
44
|
-
// Check permission
|
|
45
|
-
if (!context.permissions.allowFileWrite) {
|
|
46
|
-
throw new ToolError('File write permission denied', 'PERMISSION_DENIED', 'editFile');
|
|
47
|
-
}
|
|
48
|
-
// Validate path
|
|
49
|
-
const resolvedPath = validateFilePath(input.filePath, 'editFile');
|
|
50
|
-
// Validate oldString / newString
|
|
51
|
-
const oldString = input.oldString;
|
|
52
|
-
const newString = input.newString;
|
|
53
|
-
if (typeof oldString !== 'string') {
|
|
54
|
-
throw new ToolError('oldString is required and must be a string', 'INVALID_INPUT', 'editFile');
|
|
55
|
-
}
|
|
56
|
-
if (typeof newString !== 'string') {
|
|
57
|
-
throw new ToolError('newString is required and must be a string', 'INVALID_INPUT', 'editFile');
|
|
58
|
-
}
|
|
59
|
-
if (oldString.length === 0) {
|
|
60
|
-
throw new ToolError('oldString cannot be empty', 'INVALID_INPUT', 'editFile');
|
|
61
|
-
}
|
|
62
|
-
// Read file
|
|
63
|
-
let contents;
|
|
64
|
-
try {
|
|
65
|
-
contents = await readFile(resolvedPath, 'utf-8');
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
const nodeErr = err;
|
|
69
|
-
if (nodeErr.code === 'ENOENT') {
|
|
70
|
-
throw new ToolError(`File not found: ${resolvedPath}`, 'NOT_FOUND', 'editFile', { cause: nodeErr });
|
|
71
|
-
}
|
|
72
|
-
if (nodeErr.code === 'EACCES') {
|
|
73
|
-
throw new ToolError(`Permission denied: ${resolvedPath}`, 'PERMISSION_DENIED', 'editFile', { cause: nodeErr });
|
|
74
|
-
}
|
|
75
|
-
throw new ToolError(`Failed to read file: ${nodeErr.message}`, 'EXECUTION_FAILED', 'editFile', { cause: nodeErr });
|
|
76
|
-
}
|
|
77
|
-
// Count occurrences of oldString
|
|
78
|
-
let count = 0;
|
|
79
|
-
let searchStart = 0;
|
|
80
|
-
while (true) {
|
|
81
|
-
const idx = contents.indexOf(oldString, searchStart);
|
|
82
|
-
if (idx === -1)
|
|
83
|
-
break;
|
|
84
|
-
count++;
|
|
85
|
-
searchStart = idx + oldString.length;
|
|
86
|
-
}
|
|
87
|
-
if (count === 0) {
|
|
88
|
-
throw new ToolError('oldString not found in file. Ensure the string matches exactly, including whitespace and indentation.', 'EXECUTION_FAILED', 'editFile');
|
|
89
|
-
}
|
|
90
|
-
if (count > 1) {
|
|
91
|
-
throw new ToolError(`oldString found ${count} times — ambiguous match, include more context to match exactly once`, 'EXECUTION_FAILED', 'editFile');
|
|
92
|
-
}
|
|
93
|
-
// Replace (exactly one occurrence)
|
|
94
|
-
const updated = contents.replace(oldString, newString);
|
|
95
|
-
// Atomic write: write to temp file, then rename
|
|
96
|
-
const dir = dirname(resolvedPath);
|
|
97
|
-
const tempSuffix = randomBytes(8).toString('hex');
|
|
98
|
-
const tempPath = join(dir, `.beth-edit-${tempSuffix}.tmp`);
|
|
99
|
-
try {
|
|
100
|
-
await writeFile(tempPath, updated, 'utf-8');
|
|
101
|
-
await rename(tempPath, resolvedPath);
|
|
102
|
-
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
// Best-effort cleanup of temp file on failure
|
|
105
|
-
try {
|
|
106
|
-
const { unlink } = await import('node:fs/promises');
|
|
107
|
-
await unlink(tempPath);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
// Ignore cleanup errors
|
|
111
|
-
}
|
|
112
|
-
const nodeErr = err;
|
|
113
|
-
throw new ToolError(`Failed to write file: ${nodeErr.message}`, 'EXECUTION_FAILED', 'editFile', { cause: nodeErr });
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
success: true,
|
|
117
|
-
output: `Successfully replaced string in ${resolvedPath}`,
|
|
118
|
-
metadata: {
|
|
119
|
-
filePath: resolvedPath,
|
|
120
|
-
replacements: 1,
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
//# sourceMappingURL=editFile.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"editFile.js","sourceRoot":"","sources":["../../../src/tools/cli/editFile.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,yCAAyC;AACzC,MAAM,WAAW,GAAoB;IACnC,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE;QACV,QAAQ,EAAE;YACR,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,mCAAmC;SACjD;QACD,SAAS,EAAE;YACT,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,4DAA4D;SAC1E;QACD,SAAS,EAAE;YACT,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,oBAAoB;SAClC;KACF;IACD,QAAQ,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,IAAI,EAAE,UAAU;IAChB,WAAW,EAAE,2EAA2E;IACxF,WAAW;IAEX,KAAK,CAAC,OAAO,CAAC,KAA8B,EAAE,OAAoB;QAChE,mBAAmB;QACnB,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC;YACxC,MAAM,IAAI,SAAS,CAAC,8BAA8B,EAAE,mBAAmB,EAAE,UAAU,CAAC,CAAC;QACvF,CAAC;QAED,gBAAgB;QAChB,MAAM,YAAY,GAAG,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAElE,iCAAiC;QACjC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;QAClC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;QAElC,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAClC,MAAM,IAAI,SAAS,CAAC,4CAA4C,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;QACjG,CAAC;QACD,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAClC,MAAM,IAAI,SAAS,CAAC,4CAA4C,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;QACjG,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,SAAS,CAAC,2BAA2B,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;QAChF,CAAC;QAED,YAAY;QACZ,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,OAAO,GAAG,GAA4B,CAAC;YAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,IAAI,SAAS,CAAC,mBAAmB,YAAY,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YACtG,CAAC;YACD,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,IAAI,SAAS,CAAC,sBAAsB,YAAY,EAAE,EAAE,mBAAmB,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YACjH,CAAC;YACD,MAAM,IAAI,SAAS,CACjB,wBAAwB,OAAO,CAAC,OAAO,EAAE,EACzC,kBAAkB,EAClB,UAAU,EACV,EAAE,KAAK,EAAE,OAAO,EAAE,CACnB,CAAC;QACJ,CAAC;QAED,iCAAiC;QACjC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;YACrD,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,MAAM;YACtB,KAAK,EAAE,CAAC;YACR,WAAW,GAAG,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC;QACvC,CAAC;QAED,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,MAAM,IAAI,SAAS,CACjB,uGAAuG,EACvG,kBAAkB,EAClB,UAAU,CACX,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,IAAI,SAAS,CACjB,mBAAmB,KAAK,sEAAsE,EAC9F,kBAAkB,EAClB,UAAU,CACX,CAAC;QACJ,CAAC;QAED,mCAAmC;QACnC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAEvD,gDAAgD;QAChD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;QAClC,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,UAAU,MAAM,CAAC,CAAC;QAE3D,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5C,MAAM,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,8CAA8C;YAC9C,IAAI,CAAC;gBACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;gBACpD,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,wBAAwB;YAC1B,CAAC;YACD,MAAM,OAAO,GAAG,GAA4B,CAAC;YAC7C,MAAM,IAAI,SAAS,CACjB,yBAAyB,OAAO,CAAC,OAAO,EAAE,EAC1C,kBAAkB,EAClB,UAAU,EACV,EAAE,KAAK,EAAE,OAAO,EAAE,CACnB,CAAC;QACJ,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,mCAAmC,YAAY,EAAE;YACzD,QAAQ,EAAE;gBACR,QAAQ,EAAE,YAAY;gBACtB,YAAY,EAAE,CAAC;aAChB;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"editFile.test.d.ts","sourceRoot":"","sources":["../../../src/tools/cli/editFile.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EditFile Tool Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for the editFile tool implementation.
|
|
5
|
-
* Uses node:test and node:assert with real file I/O via tmpdir.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
8
|
-
import assert from 'node:assert';
|
|
9
|
-
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import { tmpdir } from 'node:os';
|
|
12
|
-
import { editFileTool } from './editFile.js';
|
|
13
|
-
import { ToolError } from '../types.js';
|
|
14
|
-
/** Create a ToolContext with sensible defaults */
|
|
15
|
-
function createContext(overrides) {
|
|
16
|
-
return {
|
|
17
|
-
workingDir: overrides?.workingDir ?? '/tmp',
|
|
18
|
-
permissions: overrides?.permissions ?? {
|
|
19
|
-
allowFileRead: true,
|
|
20
|
-
allowFileWrite: true,
|
|
21
|
-
allowTerminal: false,
|
|
22
|
-
allowNetwork: false,
|
|
23
|
-
},
|
|
24
|
-
signal: overrides?.signal,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
describe('editFile tool', () => {
|
|
28
|
-
let tempDir;
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
tempDir = mkdtempSync(join(tmpdir(), 'beth-editfile-'));
|
|
31
|
-
});
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
34
|
-
});
|
|
35
|
-
describe('metadata', () => {
|
|
36
|
-
it('should have name "editFile"', () => {
|
|
37
|
-
assert.strictEqual(editFileTool.name, 'editFile');
|
|
38
|
-
});
|
|
39
|
-
it('should have a description', () => {
|
|
40
|
-
assert.ok(editFileTool.description.length > 0);
|
|
41
|
-
});
|
|
42
|
-
it('should have an inputSchema with required fields', () => {
|
|
43
|
-
assert.strictEqual(editFileTool.inputSchema.type, 'object');
|
|
44
|
-
const required = editFileTool.inputSchema.required;
|
|
45
|
-
assert.ok(required.includes('filePath'));
|
|
46
|
-
assert.ok(required.includes('oldString'));
|
|
47
|
-
assert.ok(required.includes('newString'));
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
describe('successful edits', () => {
|
|
51
|
-
it('should replace a string successfully', async () => {
|
|
52
|
-
const filePath = join(tempDir, 'test.txt');
|
|
53
|
-
writeFileSync(filePath, 'hello world');
|
|
54
|
-
const result = await editFileTool.execute({ filePath, oldString: 'hello', newString: 'goodbye' }, createContext());
|
|
55
|
-
assert.strictEqual(result.success, true);
|
|
56
|
-
assert.strictEqual(readFileSync(filePath, 'utf-8'), 'goodbye world');
|
|
57
|
-
});
|
|
58
|
-
it('should handle multi-line replacements', async () => {
|
|
59
|
-
const filePath = join(tempDir, 'multi.txt');
|
|
60
|
-
writeFileSync(filePath, 'line 1\nline 2\nline 3');
|
|
61
|
-
const result = await editFileTool.execute({ filePath, oldString: 'line 2', newString: 'replaced line' }, createContext());
|
|
62
|
-
assert.strictEqual(result.success, true);
|
|
63
|
-
assert.strictEqual(readFileSync(filePath, 'utf-8'), 'line 1\nreplaced line\nline 3');
|
|
64
|
-
});
|
|
65
|
-
it('should handle empty newString (deletion)', async () => {
|
|
66
|
-
const filePath = join(tempDir, 'delete.txt');
|
|
67
|
-
writeFileSync(filePath, 'keep this remove this keep that');
|
|
68
|
-
const result = await editFileTool.execute({ filePath, oldString: 'remove this ', newString: '' }, createContext());
|
|
69
|
-
assert.strictEqual(result.success, true);
|
|
70
|
-
assert.strictEqual(readFileSync(filePath, 'utf-8'), 'keep this keep that');
|
|
71
|
-
});
|
|
72
|
-
it('should return filePath and replacement count in metadata', async () => {
|
|
73
|
-
const filePath = join(tempDir, 'meta.txt');
|
|
74
|
-
writeFileSync(filePath, 'content');
|
|
75
|
-
const result = await editFileTool.execute({ filePath, oldString: 'content', newString: 'new content' }, createContext());
|
|
76
|
-
assert.strictEqual(result.metadata?.filePath, filePath);
|
|
77
|
-
assert.strictEqual(result.metadata?.replacements, 1);
|
|
78
|
-
});
|
|
79
|
-
it('should preserve file encoding (UTF-8 with special chars)', async () => {
|
|
80
|
-
const filePath = join(tempDir, 'unicode.txt');
|
|
81
|
-
const content = 'Hello 世界 🌍 café naïve';
|
|
82
|
-
writeFileSync(filePath, content, 'utf-8');
|
|
83
|
-
const result = await editFileTool.execute({ filePath, oldString: '世界', newString: 'World' }, createContext());
|
|
84
|
-
assert.strictEqual(result.success, true);
|
|
85
|
-
assert.strictEqual(readFileSync(filePath, 'utf-8'), 'Hello World 🌍 café naïve');
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
describe('match validation', () => {
|
|
89
|
-
it('should reject when oldString not found', async () => {
|
|
90
|
-
const filePath = join(tempDir, 'notfound.txt');
|
|
91
|
-
writeFileSync(filePath, 'hello world');
|
|
92
|
-
await assert.rejects(() => editFileTool.execute({ filePath, oldString: 'goodbye', newString: 'hi' }, createContext()), (err) => {
|
|
93
|
-
assert.ok(err instanceof ToolError);
|
|
94
|
-
assert.strictEqual(err.code, 'EXECUTION_FAILED');
|
|
95
|
-
assert.ok(err.message.includes('not found'));
|
|
96
|
-
return true;
|
|
97
|
-
});
|
|
98
|
-
// File should be unchanged
|
|
99
|
-
assert.strictEqual(readFileSync(filePath, 'utf-8'), 'hello world');
|
|
100
|
-
});
|
|
101
|
-
it('should reject when oldString matches multiple times', async () => {
|
|
102
|
-
const filePath = join(tempDir, 'ambiguous.txt');
|
|
103
|
-
writeFileSync(filePath, 'foo bar foo baz foo');
|
|
104
|
-
await assert.rejects(() => editFileTool.execute({ filePath, oldString: 'foo', newString: 'qux' }, createContext()), (err) => {
|
|
105
|
-
assert.ok(err instanceof ToolError);
|
|
106
|
-
assert.strictEqual(err.code, 'EXECUTION_FAILED');
|
|
107
|
-
assert.ok(err.message.includes('ambiguous'));
|
|
108
|
-
return true;
|
|
109
|
-
});
|
|
110
|
-
// File should be unchanged
|
|
111
|
-
assert.strictEqual(readFileSync(filePath, 'utf-8'), 'foo bar foo baz foo');
|
|
112
|
-
});
|
|
113
|
-
it('should reject empty oldString', async () => {
|
|
114
|
-
const filePath = join(tempDir, 'empty.txt');
|
|
115
|
-
writeFileSync(filePath, 'content');
|
|
116
|
-
await assert.rejects(() => editFileTool.execute({ filePath, oldString: '', newString: 'x' }, createContext()), (err) => {
|
|
117
|
-
assert.ok(err instanceof ToolError);
|
|
118
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
119
|
-
return true;
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
describe('path validation', () => {
|
|
124
|
-
it('should reject path traversal attempts', async () => {
|
|
125
|
-
await assert.rejects(() => editFileTool.execute({ filePath: '/tmp/../../../etc/passwd', oldString: 'a', newString: 'b' }, createContext()), (err) => {
|
|
126
|
-
assert.ok(err instanceof ToolError);
|
|
127
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
128
|
-
assert.ok(err.message.includes('traversal'));
|
|
129
|
-
return true;
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
it('should reject shell injection in paths', async () => {
|
|
133
|
-
await assert.rejects(() => editFileTool.execute({ filePath: '/tmp/file; rm -rf /', oldString: 'a', newString: 'b' }, createContext()), (err) => {
|
|
134
|
-
assert.ok(err instanceof ToolError);
|
|
135
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
136
|
-
return true;
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
it('should reject relative paths', async () => {
|
|
140
|
-
await assert.rejects(() => editFileTool.execute({ filePath: 'relative/path.txt', oldString: 'a', newString: 'b' }, createContext()), (err) => {
|
|
141
|
-
assert.ok(err instanceof ToolError);
|
|
142
|
-
assert.strictEqual(err.code, 'INVALID_INPUT');
|
|
143
|
-
assert.ok(err.message.includes('absolute'));
|
|
144
|
-
return true;
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
describe('error handling', () => {
|
|
149
|
-
it('should return NOT_FOUND for missing files', async () => {
|
|
150
|
-
await assert.rejects(() => editFileTool.execute({ filePath: join(tempDir, 'nonexistent.txt'), oldString: 'a', newString: 'b' }, createContext()), (err) => {
|
|
151
|
-
assert.ok(err instanceof ToolError);
|
|
152
|
-
assert.strictEqual(err.code, 'NOT_FOUND');
|
|
153
|
-
return true;
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
it('should return PERMISSION_DENIED when allowFileWrite is false', async () => {
|
|
157
|
-
const filePath = join(tempDir, 'readonly.txt');
|
|
158
|
-
writeFileSync(filePath, 'content');
|
|
159
|
-
const ctx = createContext({
|
|
160
|
-
permissions: {
|
|
161
|
-
allowFileRead: true,
|
|
162
|
-
allowFileWrite: false,
|
|
163
|
-
allowTerminal: false,
|
|
164
|
-
allowNetwork: false,
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
await assert.rejects(() => editFileTool.execute({ filePath, oldString: 'content', newString: 'new' }, ctx), (err) => {
|
|
168
|
-
assert.ok(err instanceof ToolError);
|
|
169
|
-
assert.strictEqual(err.code, 'PERMISSION_DENIED');
|
|
170
|
-
return true;
|
|
171
|
-
});
|
|
172
|
-
// File should be unchanged
|
|
173
|
-
assert.strictEqual(readFileSync(filePath, 'utf-8'), 'content');
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
//# sourceMappingURL=editFile.test.js.map
|