@dexto/tools-plan 1.6.0 → 1.6.1

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 (38) hide show
  1. package/dist/errors.cjs +126 -0
  2. package/dist/errors.js +99 -64
  3. package/dist/index.cjs +36 -0
  4. package/dist/index.d.cts +224 -0
  5. package/dist/index.js +9 -12
  6. package/dist/plan-service-getter.cjs +16 -0
  7. package/dist/plan-service-getter.js +0 -1
  8. package/dist/plan-service.cjs +247 -0
  9. package/dist/plan-service.js +201 -215
  10. package/dist/plan-service.test.cjs +227 -0
  11. package/dist/plan-service.test.js +200 -222
  12. package/dist/tool-factory-config.cjs +38 -0
  13. package/dist/tool-factory-config.js +13 -30
  14. package/dist/tool-factory.cjs +71 -0
  15. package/dist/tool-factory.js +39 -35
  16. package/dist/tool-factory.test.cjs +96 -0
  17. package/dist/tool-factory.test.js +90 -95
  18. package/dist/tools/plan-create-tool.cjs +102 -0
  19. package/dist/tools/plan-create-tool.d.ts.map +1 -1
  20. package/dist/tools/plan-create-tool.js +77 -82
  21. package/dist/tools/plan-create-tool.test.cjs +174 -0
  22. package/dist/tools/plan-create-tool.test.js +142 -134
  23. package/dist/tools/plan-read-tool.cjs +65 -0
  24. package/dist/tools/plan-read-tool.d.ts.map +1 -1
  25. package/dist/tools/plan-read-tool.js +39 -41
  26. package/dist/tools/plan-read-tool.test.cjs +109 -0
  27. package/dist/tools/plan-read-tool.test.js +78 -87
  28. package/dist/tools/plan-review-tool.cjs +98 -0
  29. package/dist/tools/plan-review-tool.d.ts.map +1 -1
  30. package/dist/tools/plan-review-tool.js +73 -87
  31. package/dist/tools/plan-update-tool.cjs +92 -0
  32. package/dist/tools/plan-update-tool.d.ts.map +1 -1
  33. package/dist/tools/plan-update-tool.js +65 -73
  34. package/dist/tools/plan-update-tool.test.cjs +203 -0
  35. package/dist/tools/plan-update-tool.test.js +171 -154
  36. package/dist/types.cjs +44 -0
  37. package/dist/types.js +17 -24
  38. package/package.json +7 -6
@@ -1,163 +1,180 @@
1
- /**
2
- * Plan Update Tool Tests
3
- *
4
- * Tests for the plan_update tool including diff preview generation.
5
- */
6
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
7
- import * as path from 'node:path';
8
- import * as fs from 'node:fs/promises';
9
- import * as os from 'node:os';
10
- import { createPlanUpdateTool } from './plan-update-tool.js';
11
- import { PlanService } from '../plan-service.js';
12
- import { PlanErrorCode } from '../errors.js';
13
- import { DextoRuntimeError } from '@dexto/core';
14
- // Create mock logger
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import * as path from "node:path";
3
+ import * as fs from "node:fs/promises";
4
+ import * as os from "node:os";
5
+ import { createPlanUpdateTool } from "./plan-update-tool.js";
6
+ import { PlanService } from "../plan-service.js";
7
+ import { PlanErrorCode } from "../errors.js";
8
+ import { DextoRuntimeError } from "@dexto/core";
15
9
  const createMockLogger = () => {
16
- const logger = {
17
- debug: vi.fn(),
18
- silly: vi.fn(),
19
- info: vi.fn(),
20
- warn: vi.fn(),
21
- error: vi.fn(),
22
- trackException: vi.fn(),
23
- createChild: vi.fn(() => logger),
24
- setLevel: vi.fn(),
25
- getLevel: vi.fn(() => 'debug'),
26
- getLogFilePath: vi.fn(() => null),
27
- destroy: vi.fn(async () => undefined),
28
- };
29
- return logger;
10
+ const logger = {
11
+ debug: vi.fn(),
12
+ silly: vi.fn(),
13
+ info: vi.fn(),
14
+ warn: vi.fn(),
15
+ error: vi.fn(),
16
+ trackException: vi.fn(),
17
+ createChild: vi.fn(() => logger),
18
+ createFileOnlyChild: vi.fn(() => logger),
19
+ setLevel: vi.fn(),
20
+ getLevel: vi.fn(() => "debug"),
21
+ getLogFilePath: vi.fn(() => null),
22
+ destroy: vi.fn(async () => void 0)
23
+ };
24
+ return logger;
30
25
  };
31
26
  function createToolContext(logger, overrides = {}) {
32
- return { logger, ...overrides };
27
+ return { logger, ...overrides };
33
28
  }
34
- describe('plan_update tool', () => {
35
- let logger;
36
- let tempDir;
37
- let planService;
38
- beforeEach(async () => {
39
- logger = createMockLogger();
40
- const rawTempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dexto-plan-update-test-'));
41
- tempDir = await fs.realpath(rawTempDir);
42
- planService = new PlanService({ basePath: tempDir }, logger);
43
- vi.clearAllMocks();
29
+ describe("plan_update tool", () => {
30
+ let logger;
31
+ let tempDir;
32
+ let planService;
33
+ beforeEach(async () => {
34
+ logger = createMockLogger();
35
+ const rawTempDir = await fs.mkdtemp(path.join(os.tmpdir(), "dexto-plan-update-test-"));
36
+ tempDir = await fs.realpath(rawTempDir);
37
+ planService = new PlanService({ basePath: tempDir }, logger);
38
+ vi.clearAllMocks();
39
+ });
40
+ afterEach(async () => {
41
+ try {
42
+ await fs.rm(tempDir, { recursive: true, force: true });
43
+ } catch {
44
+ }
45
+ });
46
+ describe("generatePreview", () => {
47
+ it("should return DiffDisplayData with unified diff", async () => {
48
+ const tool = createPlanUpdateTool(async () => planService);
49
+ const sessionId = "test-session";
50
+ const originalContent = "# Plan\n\n## Steps\n1. First step";
51
+ const newContent = "# Plan\n\n## Steps\n1. First step\n2. Second step";
52
+ const previewFn = tool.presentation?.preview;
53
+ expect(previewFn).toBeDefined();
54
+ await planService.create(sessionId, originalContent);
55
+ const preview = await previewFn(
56
+ { content: newContent },
57
+ createToolContext(logger, { sessionId })
58
+ );
59
+ expect(preview.type).toBe("diff");
60
+ expect(preview.title).toBe("Update Plan");
61
+ expect(preview.filename).toContain(sessionId);
62
+ expect(preview.filename).toMatch(/plan\.md$/);
63
+ expect(preview.unified).toContain("-1. First step");
64
+ expect(preview.unified).toContain("+1. First step");
65
+ expect(preview.unified).toContain("+2. Second step");
66
+ expect(preview.additions).toBeGreaterThan(0);
44
67
  });
45
- afterEach(async () => {
46
- try {
47
- await fs.rm(tempDir, { recursive: true, force: true });
48
- }
49
- catch {
50
- // Ignore cleanup errors
51
- }
68
+ it("should throw error when plan does not exist", async () => {
69
+ const tool = createPlanUpdateTool(async () => planService);
70
+ const sessionId = "test-session";
71
+ const previewFn = tool.presentation?.preview;
72
+ expect(previewFn).toBeDefined();
73
+ try {
74
+ await previewFn(
75
+ { content: "# New Content" },
76
+ createToolContext(logger, { sessionId })
77
+ );
78
+ expect.fail("Should have thrown an error");
79
+ } catch (error) {
80
+ expect(error).toBeInstanceOf(DextoRuntimeError);
81
+ expect(error.code).toBe(PlanErrorCode.PLAN_NOT_FOUND);
82
+ }
52
83
  });
53
- describe('generatePreview', () => {
54
- it('should return DiffDisplayData with unified diff', async () => {
55
- const tool = createPlanUpdateTool(async () => planService);
56
- const sessionId = 'test-session';
57
- const originalContent = '# Plan\n\n## Steps\n1. First step';
58
- const newContent = '# Plan\n\n## Steps\n1. First step\n2. Second step';
59
- await planService.create(sessionId, originalContent);
60
- const preview = (await tool.generatePreview({ content: newContent }, createToolContext(logger, { sessionId })));
61
- expect(preview.type).toBe('diff');
62
- // Path is now absolute, check it ends with the expected suffix
63
- expect(preview.filename).toContain(sessionId);
64
- expect(preview.filename).toMatch(/plan\.md$/);
65
- expect(preview.unified).toContain('-1. First step');
66
- expect(preview.unified).toContain('+1. First step');
67
- expect(preview.unified).toContain('+2. Second step');
68
- expect(preview.additions).toBeGreaterThan(0);
69
- });
70
- it('should throw error when plan does not exist', async () => {
71
- const tool = createPlanUpdateTool(async () => planService);
72
- const sessionId = 'test-session';
73
- try {
74
- await tool.generatePreview({ content: '# New Content' }, createToolContext(logger, { sessionId }));
75
- expect.fail('Should have thrown an error');
76
- }
77
- catch (error) {
78
- expect(error).toBeInstanceOf(DextoRuntimeError);
79
- expect(error.code).toBe(PlanErrorCode.PLAN_NOT_FOUND);
80
- }
81
- });
82
- it('should throw error when sessionId is missing', async () => {
83
- const tool = createPlanUpdateTool(async () => planService);
84
- try {
85
- await tool.generatePreview({ content: '# Content' }, createToolContext(logger));
86
- expect.fail('Should have thrown an error');
87
- }
88
- catch (error) {
89
- expect(error).toBeInstanceOf(DextoRuntimeError);
90
- expect(error.code).toBe(PlanErrorCode.SESSION_ID_REQUIRED);
91
- }
92
- });
93
- it('should show deletions in diff', async () => {
94
- const tool = createPlanUpdateTool(async () => planService);
95
- const sessionId = 'test-session';
96
- const originalContent = '# Plan\n\nLine to remove\nKeep this';
97
- const newContent = '# Plan\n\nKeep this';
98
- await planService.create(sessionId, originalContent);
99
- const preview = (await tool.generatePreview({ content: newContent }, createToolContext(logger, { sessionId })));
100
- expect(preview.deletions).toBeGreaterThan(0);
101
- expect(preview.unified).toContain('-Line to remove');
102
- });
84
+ it("should throw error when sessionId is missing", async () => {
85
+ const tool = createPlanUpdateTool(async () => planService);
86
+ const previewFn = tool.presentation?.preview;
87
+ expect(previewFn).toBeDefined();
88
+ try {
89
+ await previewFn({ content: "# Content" }, createToolContext(logger));
90
+ expect.fail("Should have thrown an error");
91
+ } catch (error) {
92
+ expect(error).toBeInstanceOf(DextoRuntimeError);
93
+ expect(error.code).toBe(PlanErrorCode.SESSION_ID_REQUIRED);
94
+ }
103
95
  });
104
- describe('execute', () => {
105
- it('should update plan content and return success', async () => {
106
- const tool = createPlanUpdateTool(async () => planService);
107
- const sessionId = 'test-session';
108
- const originalContent = '# Original Plan';
109
- const newContent = '# Updated Plan';
110
- await planService.create(sessionId, originalContent);
111
- const result = (await tool.execute({ content: newContent }, createToolContext(logger, { sessionId })));
112
- expect(result.success).toBe(true);
113
- // Path is now absolute, check it ends with the expected suffix
114
- expect(result.path).toContain(sessionId);
115
- expect(result.path).toMatch(/plan\.md$/);
116
- // Verify content was updated
117
- const plan = await planService.read(sessionId);
118
- expect(plan.content).toBe(newContent);
119
- });
120
- it('should include _display data with diff', async () => {
121
- const tool = createPlanUpdateTool(async () => planService);
122
- const sessionId = 'test-session';
123
- await planService.create(sessionId, '# Original');
124
- const result = (await tool.execute({ content: '# Updated' }, createToolContext(logger, { sessionId })));
125
- expect(result._display).toBeDefined();
126
- expect(result._display.type).toBe('diff');
127
- expect(result._display.unified).toContain('-# Original');
128
- expect(result._display.unified).toContain('+# Updated');
129
- });
130
- it('should throw error when plan does not exist', async () => {
131
- const tool = createPlanUpdateTool(async () => planService);
132
- const sessionId = 'non-existent';
133
- try {
134
- await tool.execute({ content: '# Content' }, createToolContext(logger, { sessionId }));
135
- expect.fail('Should have thrown an error');
136
- }
137
- catch (error) {
138
- expect(error).toBeInstanceOf(DextoRuntimeError);
139
- expect(error.code).toBe(PlanErrorCode.PLAN_NOT_FOUND);
140
- }
141
- });
142
- it('should throw error when sessionId is missing', async () => {
143
- const tool = createPlanUpdateTool(async () => planService);
144
- try {
145
- await tool.execute({ content: '# Content' }, createToolContext(logger));
146
- expect.fail('Should have thrown an error');
147
- }
148
- catch (error) {
149
- expect(error).toBeInstanceOf(DextoRuntimeError);
150
- expect(error.code).toBe(PlanErrorCode.SESSION_ID_REQUIRED);
151
- }
152
- });
153
- it('should preserve plan status after update', async () => {
154
- const tool = createPlanUpdateTool(async () => planService);
155
- const sessionId = 'test-session';
156
- await planService.create(sessionId, '# Plan');
157
- await planService.updateMeta(sessionId, { status: 'approved' });
158
- await tool.execute({ content: '# Updated Plan' }, createToolContext(logger, { sessionId }));
159
- const plan = await planService.read(sessionId);
160
- expect(plan.meta.status).toBe('approved');
161
- });
96
+ it("should show deletions in diff", async () => {
97
+ const tool = createPlanUpdateTool(async () => planService);
98
+ const sessionId = "test-session";
99
+ const originalContent = "# Plan\n\nLine to remove\nKeep this";
100
+ const newContent = "# Plan\n\nKeep this";
101
+ const previewFn = tool.presentation?.preview;
102
+ expect(previewFn).toBeDefined();
103
+ await planService.create(sessionId, originalContent);
104
+ const preview = await previewFn(
105
+ { content: newContent },
106
+ createToolContext(logger, { sessionId })
107
+ );
108
+ expect(preview.deletions).toBeGreaterThan(0);
109
+ expect(preview.unified).toContain("-Line to remove");
162
110
  });
111
+ });
112
+ describe("execute", () => {
113
+ it("should update plan content and return success", async () => {
114
+ const tool = createPlanUpdateTool(async () => planService);
115
+ const sessionId = "test-session";
116
+ const originalContent = "# Original Plan";
117
+ const newContent = "# Updated Plan";
118
+ await planService.create(sessionId, originalContent);
119
+ const result = await tool.execute(
120
+ { content: newContent },
121
+ createToolContext(logger, { sessionId })
122
+ );
123
+ expect(result.success).toBe(true);
124
+ expect(result.path).toContain(sessionId);
125
+ expect(result.path).toMatch(/plan\.md$/);
126
+ const plan = await planService.read(sessionId);
127
+ expect(plan.content).toBe(newContent);
128
+ });
129
+ it("should include _display data with diff", async () => {
130
+ const tool = createPlanUpdateTool(async () => planService);
131
+ const sessionId = "test-session";
132
+ await planService.create(sessionId, "# Original");
133
+ const result = await tool.execute(
134
+ { content: "# Updated" },
135
+ createToolContext(logger, { sessionId })
136
+ );
137
+ expect(result._display).toBeDefined();
138
+ expect(result._display.type).toBe("diff");
139
+ expect(result._display.title).toBe("Update Plan");
140
+ expect(result._display.unified).toContain("-# Original");
141
+ expect(result._display.unified).toContain("+# Updated");
142
+ });
143
+ it("should throw error when plan does not exist", async () => {
144
+ const tool = createPlanUpdateTool(async () => planService);
145
+ const sessionId = "non-existent";
146
+ try {
147
+ await tool.execute(
148
+ { content: "# Content" },
149
+ createToolContext(logger, { sessionId })
150
+ );
151
+ expect.fail("Should have thrown an error");
152
+ } catch (error) {
153
+ expect(error).toBeInstanceOf(DextoRuntimeError);
154
+ expect(error.code).toBe(PlanErrorCode.PLAN_NOT_FOUND);
155
+ }
156
+ });
157
+ it("should throw error when sessionId is missing", async () => {
158
+ const tool = createPlanUpdateTool(async () => planService);
159
+ try {
160
+ await tool.execute({ content: "# Content" }, createToolContext(logger));
161
+ expect.fail("Should have thrown an error");
162
+ } catch (error) {
163
+ expect(error).toBeInstanceOf(DextoRuntimeError);
164
+ expect(error.code).toBe(PlanErrorCode.SESSION_ID_REQUIRED);
165
+ }
166
+ });
167
+ it("should preserve plan status after update", async () => {
168
+ const tool = createPlanUpdateTool(async () => planService);
169
+ const sessionId = "test-session";
170
+ await planService.create(sessionId, "# Plan");
171
+ await planService.updateMeta(sessionId, { status: "approved" });
172
+ await tool.execute(
173
+ { content: "# Updated Plan" },
174
+ createToolContext(logger, { sessionId })
175
+ );
176
+ const plan = await planService.read(sessionId);
177
+ expect(plan.meta.status).toBe("approved");
178
+ });
179
+ });
163
180
  });
package/dist/types.cjs ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var types_exports = {};
20
+ __export(types_exports, {
21
+ PlanMetaSchema: () => PlanMetaSchema,
22
+ PlanStatusSchema: () => PlanStatusSchema
23
+ });
24
+ module.exports = __toCommonJS(types_exports);
25
+ var import_zod = require("zod");
26
+ const PlanStatusSchema = import_zod.z.enum([
27
+ "draft",
28
+ "approved",
29
+ "in_progress",
30
+ "completed",
31
+ "abandoned"
32
+ ]);
33
+ const PlanMetaSchema = import_zod.z.object({
34
+ sessionId: import_zod.z.string().describe("Session ID this plan belongs to"),
35
+ status: PlanStatusSchema.default("draft").describe("Current plan status"),
36
+ title: import_zod.z.string().optional().describe("Plan title"),
37
+ createdAt: import_zod.z.number().describe("Unix timestamp when plan was created"),
38
+ updatedAt: import_zod.z.number().describe("Unix timestamp when plan was last updated")
39
+ });
40
+ // Annotate the CommonJS export names for ESM import in node:
41
+ 0 && (module.exports = {
42
+ PlanMetaSchema,
43
+ PlanStatusSchema
44
+ });
package/dist/types.js CHANGED
@@ -1,26 +1,19 @@
1
- /**
2
- * Plan Types and Schemas
3
- *
4
- * Defines the structure of plans and their metadata.
5
- */
6
- import { z } from 'zod';
7
- /**
8
- * Plan status values
9
- */
10
- export const PlanStatusSchema = z.enum([
11
- 'draft',
12
- 'approved',
13
- 'in_progress',
14
- 'completed',
15
- 'abandoned',
1
+ import { z } from "zod";
2
+ const PlanStatusSchema = z.enum([
3
+ "draft",
4
+ "approved",
5
+ "in_progress",
6
+ "completed",
7
+ "abandoned"
16
8
  ]);
17
- /**
18
- * Plan metadata stored alongside the plan content
19
- */
20
- export const PlanMetaSchema = z.object({
21
- sessionId: z.string().describe('Session ID this plan belongs to'),
22
- status: PlanStatusSchema.default('draft').describe('Current plan status'),
23
- title: z.string().optional().describe('Plan title'),
24
- createdAt: z.number().describe('Unix timestamp when plan was created'),
25
- updatedAt: z.number().describe('Unix timestamp when plan was last updated'),
9
+ const PlanMetaSchema = z.object({
10
+ sessionId: z.string().describe("Session ID this plan belongs to"),
11
+ status: PlanStatusSchema.default("draft").describe("Current plan status"),
12
+ title: z.string().optional().describe("Plan title"),
13
+ createdAt: z.number().describe("Unix timestamp when plan was created"),
14
+ updatedAt: z.number().describe("Unix timestamp when plan was last updated")
26
15
  });
16
+ export {
17
+ PlanMetaSchema,
18
+ PlanStatusSchema
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexto/tools-plan",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Implementation planning tools with session-linked plans",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,20 +17,21 @@
17
17
  "dependencies": {
18
18
  "diff": "^7.0.0",
19
19
  "zod": "^3.24.1",
20
- "@dexto/agent-config": "1.6.0",
21
- "@dexto/core": "1.6.0"
20
+ "@dexto/agent-config": "1.6.1",
21
+ "@dexto/core": "1.6.1"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/diff": "^7.0.0",
25
25
  "@types/node": "^22.10.5",
26
26
  "dotenv": "^16.4.7",
27
+ "tsup": "^8.0.0",
27
28
  "typescript": "^5.7.2",
28
29
  "vitest": "^2.1.8"
29
30
  },
30
- "author": "Dexto",
31
- "license": "MIT",
31
+ "license": "Elastic-2.0",
32
32
  "scripts": {
33
- "build": "tsc",
33
+ "build": "tsup && node ../../scripts/clean-tsbuildinfo.mjs && tsc -b tsconfig.json --emitDeclarationOnly",
34
+ "dev": "tsup --watch",
34
35
  "clean": "rm -rf dist",
35
36
  "typecheck": "tsc --noEmit",
36
37
  "test": "vitest run",