@dexto/tools-plan 1.5.8 → 1.6.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/dist/index.d.ts +1 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -29
- package/dist/plan-service-getter.d.ts +4 -0
- package/dist/plan-service-getter.d.ts.map +1 -0
- package/dist/plan-service-getter.js +1 -0
- package/dist/plan-service.d.ts +2 -2
- package/dist/plan-service.d.ts.map +1 -1
- package/dist/plan-service.js +7 -7
- package/dist/plan-service.test.js +6 -0
- package/dist/tool-factory-config.d.ts +32 -0
- package/dist/tool-factory-config.d.ts.map +1 -0
- package/dist/tool-factory-config.js +30 -0
- package/dist/tool-factory.d.ts +4 -0
- package/dist/tool-factory.d.ts.map +1 -0
- package/dist/tool-factory.js +36 -0
- package/dist/tool-factory.test.d.ts +7 -0
- package/dist/tool-factory.test.d.ts.map +1 -0
- package/dist/tool-factory.test.js +100 -0
- package/dist/tools/plan-create-tool.d.ts +15 -3
- package/dist/tools/plan-create-tool.d.ts.map +1 -1
- package/dist/tools/plan-create-tool.js +29 -18
- package/dist/tools/plan-create-tool.test.js +47 -22
- package/dist/tools/plan-read-tool.d.ts +6 -3
- package/dist/tools/plan-read-tool.d.ts.map +1 -1
- package/dist/tools/plan-read-tool.js +10 -7
- package/dist/tools/plan-read-tool.test.js +31 -19
- package/dist/tools/plan-review-tool.d.ts +14 -5
- package/dist/tools/plan-review-tool.d.ts.map +1 -1
- package/dist/tools/plan-review-tool.js +16 -12
- package/dist/tools/plan-update-tool.d.ts +12 -3
- package/dist/tools/plan-update-tool.d.ts.map +1 -1
- package/dist/tools/plan-update-tool.js +14 -10
- package/dist/tools/plan-update-tool.test.js +40 -28
- package/package.json +4 -5
- package/.dexto-plugin/plugin.json +0 -7
- package/dist/tool-provider.d.ts +0 -44
- package/dist/tool-provider.d.ts.map +0 -1
- package/dist/tool-provider.js +0 -81
- package/dist/tool-provider.test.d.ts +0 -7
- package/dist/tool-provider.test.d.ts.map +0 -1
- package/dist/tool-provider.test.js +0 -185
- package/skills/plan/SKILL.md +0 -102
|
@@ -12,22 +12,34 @@ import { PlanService } from '../plan-service.js';
|
|
|
12
12
|
import { PlanErrorCode } from '../errors.js';
|
|
13
13
|
import { DextoRuntimeError } from '@dexto/core';
|
|
14
14
|
// Create mock logger
|
|
15
|
-
const createMockLogger = () =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
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;
|
|
30
|
+
};
|
|
31
|
+
function createToolContext(logger, overrides = {}) {
|
|
32
|
+
return { logger, ...overrides };
|
|
33
|
+
}
|
|
22
34
|
describe('plan_update tool', () => {
|
|
23
|
-
let
|
|
35
|
+
let logger;
|
|
24
36
|
let tempDir;
|
|
25
37
|
let planService;
|
|
26
38
|
beforeEach(async () => {
|
|
27
|
-
|
|
39
|
+
logger = createMockLogger();
|
|
28
40
|
const rawTempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dexto-plan-update-test-'));
|
|
29
41
|
tempDir = await fs.realpath(rawTempDir);
|
|
30
|
-
planService = new PlanService({ basePath: tempDir },
|
|
42
|
+
planService = new PlanService({ basePath: tempDir }, logger);
|
|
31
43
|
vi.clearAllMocks();
|
|
32
44
|
});
|
|
33
45
|
afterEach(async () => {
|
|
@@ -40,12 +52,12 @@ describe('plan_update tool', () => {
|
|
|
40
52
|
});
|
|
41
53
|
describe('generatePreview', () => {
|
|
42
54
|
it('should return DiffDisplayData with unified diff', async () => {
|
|
43
|
-
const tool = createPlanUpdateTool(planService);
|
|
55
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
44
56
|
const sessionId = 'test-session';
|
|
45
57
|
const originalContent = '# Plan\n\n## Steps\n1. First step';
|
|
46
58
|
const newContent = '# Plan\n\n## Steps\n1. First step\n2. Second step';
|
|
47
59
|
await planService.create(sessionId, originalContent);
|
|
48
|
-
const preview = (await tool.generatePreview({ content: newContent }, { sessionId }));
|
|
60
|
+
const preview = (await tool.generatePreview({ content: newContent }, createToolContext(logger, { sessionId })));
|
|
49
61
|
expect(preview.type).toBe('diff');
|
|
50
62
|
// Path is now absolute, check it ends with the expected suffix
|
|
51
63
|
expect(preview.filename).toContain(sessionId);
|
|
@@ -56,10 +68,10 @@ describe('plan_update tool', () => {
|
|
|
56
68
|
expect(preview.additions).toBeGreaterThan(0);
|
|
57
69
|
});
|
|
58
70
|
it('should throw error when plan does not exist', async () => {
|
|
59
|
-
const tool = createPlanUpdateTool(planService);
|
|
71
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
60
72
|
const sessionId = 'test-session';
|
|
61
73
|
try {
|
|
62
|
-
await tool.generatePreview({ content: '# New Content' }, { sessionId });
|
|
74
|
+
await tool.generatePreview({ content: '# New Content' }, createToolContext(logger, { sessionId }));
|
|
63
75
|
expect.fail('Should have thrown an error');
|
|
64
76
|
}
|
|
65
77
|
catch (error) {
|
|
@@ -68,9 +80,9 @@ describe('plan_update tool', () => {
|
|
|
68
80
|
}
|
|
69
81
|
});
|
|
70
82
|
it('should throw error when sessionId is missing', async () => {
|
|
71
|
-
const tool = createPlanUpdateTool(planService);
|
|
83
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
72
84
|
try {
|
|
73
|
-
await tool.generatePreview({ content: '# Content' },
|
|
85
|
+
await tool.generatePreview({ content: '# Content' }, createToolContext(logger));
|
|
74
86
|
expect.fail('Should have thrown an error');
|
|
75
87
|
}
|
|
76
88
|
catch (error) {
|
|
@@ -79,24 +91,24 @@ describe('plan_update tool', () => {
|
|
|
79
91
|
}
|
|
80
92
|
});
|
|
81
93
|
it('should show deletions in diff', async () => {
|
|
82
|
-
const tool = createPlanUpdateTool(planService);
|
|
94
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
83
95
|
const sessionId = 'test-session';
|
|
84
96
|
const originalContent = '# Plan\n\nLine to remove\nKeep this';
|
|
85
97
|
const newContent = '# Plan\n\nKeep this';
|
|
86
98
|
await planService.create(sessionId, originalContent);
|
|
87
|
-
const preview = (await tool.generatePreview({ content: newContent }, { sessionId }));
|
|
99
|
+
const preview = (await tool.generatePreview({ content: newContent }, createToolContext(logger, { sessionId })));
|
|
88
100
|
expect(preview.deletions).toBeGreaterThan(0);
|
|
89
101
|
expect(preview.unified).toContain('-Line to remove');
|
|
90
102
|
});
|
|
91
103
|
});
|
|
92
104
|
describe('execute', () => {
|
|
93
105
|
it('should update plan content and return success', async () => {
|
|
94
|
-
const tool = createPlanUpdateTool(planService);
|
|
106
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
95
107
|
const sessionId = 'test-session';
|
|
96
108
|
const originalContent = '# Original Plan';
|
|
97
109
|
const newContent = '# Updated Plan';
|
|
98
110
|
await planService.create(sessionId, originalContent);
|
|
99
|
-
const result = (await tool.execute({ content: newContent }, { sessionId }));
|
|
111
|
+
const result = (await tool.execute({ content: newContent }, createToolContext(logger, { sessionId })));
|
|
100
112
|
expect(result.success).toBe(true);
|
|
101
113
|
// Path is now absolute, check it ends with the expected suffix
|
|
102
114
|
expect(result.path).toContain(sessionId);
|
|
@@ -106,20 +118,20 @@ describe('plan_update tool', () => {
|
|
|
106
118
|
expect(plan.content).toBe(newContent);
|
|
107
119
|
});
|
|
108
120
|
it('should include _display data with diff', async () => {
|
|
109
|
-
const tool = createPlanUpdateTool(planService);
|
|
121
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
110
122
|
const sessionId = 'test-session';
|
|
111
123
|
await planService.create(sessionId, '# Original');
|
|
112
|
-
const result = (await tool.execute({ content: '# Updated' }, { sessionId }));
|
|
124
|
+
const result = (await tool.execute({ content: '# Updated' }, createToolContext(logger, { sessionId })));
|
|
113
125
|
expect(result._display).toBeDefined();
|
|
114
126
|
expect(result._display.type).toBe('diff');
|
|
115
127
|
expect(result._display.unified).toContain('-# Original');
|
|
116
128
|
expect(result._display.unified).toContain('+# Updated');
|
|
117
129
|
});
|
|
118
130
|
it('should throw error when plan does not exist', async () => {
|
|
119
|
-
const tool = createPlanUpdateTool(planService);
|
|
131
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
120
132
|
const sessionId = 'non-existent';
|
|
121
133
|
try {
|
|
122
|
-
await tool.execute({ content: '# Content' }, { sessionId });
|
|
134
|
+
await tool.execute({ content: '# Content' }, createToolContext(logger, { sessionId }));
|
|
123
135
|
expect.fail('Should have thrown an error');
|
|
124
136
|
}
|
|
125
137
|
catch (error) {
|
|
@@ -128,9 +140,9 @@ describe('plan_update tool', () => {
|
|
|
128
140
|
}
|
|
129
141
|
});
|
|
130
142
|
it('should throw error when sessionId is missing', async () => {
|
|
131
|
-
const tool = createPlanUpdateTool(planService);
|
|
143
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
132
144
|
try {
|
|
133
|
-
await tool.execute({ content: '# Content' },
|
|
145
|
+
await tool.execute({ content: '# Content' }, createToolContext(logger));
|
|
134
146
|
expect.fail('Should have thrown an error');
|
|
135
147
|
}
|
|
136
148
|
catch (error) {
|
|
@@ -139,11 +151,11 @@ describe('plan_update tool', () => {
|
|
|
139
151
|
}
|
|
140
152
|
});
|
|
141
153
|
it('should preserve plan status after update', async () => {
|
|
142
|
-
const tool = createPlanUpdateTool(planService);
|
|
154
|
+
const tool = createPlanUpdateTool(async () => planService);
|
|
143
155
|
const sessionId = 'test-session';
|
|
144
156
|
await planService.create(sessionId, '# Plan');
|
|
145
157
|
await planService.updateMeta(sessionId, { status: 'approved' });
|
|
146
|
-
await tool.execute({ content: '# Updated Plan' }, { sessionId });
|
|
158
|
+
await tool.execute({ content: '# Updated Plan' }, createToolContext(logger, { sessionId }));
|
|
147
159
|
const plan = await planService.read(sessionId);
|
|
148
160
|
expect(plan.meta.status).toBe('approved');
|
|
149
161
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexto/tools-plan",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Implementation planning tools with session-linked plans",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,14 +12,13 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist"
|
|
16
|
-
".dexto-plugin",
|
|
17
|
-
"skills"
|
|
15
|
+
"dist"
|
|
18
16
|
],
|
|
19
17
|
"dependencies": {
|
|
20
18
|
"diff": "^7.0.0",
|
|
21
19
|
"zod": "^3.24.1",
|
|
22
|
-
"@dexto/
|
|
20
|
+
"@dexto/agent-config": "1.6.0",
|
|
21
|
+
"@dexto/core": "1.6.0"
|
|
23
22
|
},
|
|
24
23
|
"devDependencies": {
|
|
25
24
|
"@types/diff": "^7.0.0",
|
package/dist/tool-provider.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plan Tools Provider
|
|
3
|
-
*
|
|
4
|
-
* Provides implementation planning tools:
|
|
5
|
-
* - plan_create: Create a new plan for the session
|
|
6
|
-
* - plan_read: Read the current plan
|
|
7
|
-
* - plan_update: Update the existing plan
|
|
8
|
-
* - plan_review: Request user review of the plan (shows plan content with approval options)
|
|
9
|
-
*/
|
|
10
|
-
import { z } from 'zod';
|
|
11
|
-
import type { CustomToolProvider } from '@dexto/core';
|
|
12
|
-
/**
|
|
13
|
-
* Configuration schema for Plan tools provider
|
|
14
|
-
*/
|
|
15
|
-
declare const PlanToolsConfigSchema: z.ZodObject<{
|
|
16
|
-
type: z.ZodLiteral<"plan-tools">;
|
|
17
|
-
basePath: z.ZodDefault<z.ZodString>;
|
|
18
|
-
enabledTools: z.ZodOptional<z.ZodArray<z.ZodEnum<["plan_create", "plan_read", "plan_update", "plan_review"]>, "many">>;
|
|
19
|
-
}, "strict", z.ZodTypeAny, {
|
|
20
|
-
type: "plan-tools";
|
|
21
|
-
basePath: string;
|
|
22
|
-
enabledTools?: ("plan_create" | "plan_read" | "plan_update" | "plan_review")[] | undefined;
|
|
23
|
-
}, {
|
|
24
|
-
type: "plan-tools";
|
|
25
|
-
basePath?: string | undefined;
|
|
26
|
-
enabledTools?: ("plan_create" | "plan_read" | "plan_update" | "plan_review")[] | undefined;
|
|
27
|
-
}>;
|
|
28
|
-
type PlanToolsConfig = z.output<typeof PlanToolsConfigSchema>;
|
|
29
|
-
/**
|
|
30
|
-
* Plan tools provider
|
|
31
|
-
*
|
|
32
|
-
* Provides implementation planning tools:
|
|
33
|
-
* - plan_create: Create a new plan with markdown content
|
|
34
|
-
* - plan_read: Read the current plan
|
|
35
|
-
* - plan_update: Update existing plan (shows diff preview)
|
|
36
|
-
* - plan_review: Request user review of the plan (shows plan with approval options)
|
|
37
|
-
*
|
|
38
|
-
* Plans are stored in .dexto/plans/{sessionId}/ with:
|
|
39
|
-
* - plan.md: Markdown content with checkboxes (- [ ] and - [x])
|
|
40
|
-
* - plan-meta.json: Metadata (status, title, timestamps)
|
|
41
|
-
*/
|
|
42
|
-
export declare const planToolsProvider: CustomToolProvider<'plan-tools', PlanToolsConfig>;
|
|
43
|
-
export {};
|
|
44
|
-
//# sourceMappingURL=tool-provider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tool-provider.d.ts","sourceRoot":"","sources":["../src/tool-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAqC,MAAM,aAAa,CAAC;AAazF;;GAEG;AACH,QAAA,MAAM,qBAAqB;;;;;;;;;;;;EAcd,CAAC;AAEd,KAAK,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,EAAE,kBAAkB,CAAC,YAAY,EAAE,eAAe,CAwC/E,CAAC"}
|
package/dist/tool-provider.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plan Tools Provider
|
|
3
|
-
*
|
|
4
|
-
* Provides implementation planning tools:
|
|
5
|
-
* - plan_create: Create a new plan for the session
|
|
6
|
-
* - plan_read: Read the current plan
|
|
7
|
-
* - plan_update: Update the existing plan
|
|
8
|
-
* - plan_review: Request user review of the plan (shows plan content with approval options)
|
|
9
|
-
*/
|
|
10
|
-
import * as path from 'node:path';
|
|
11
|
-
import { z } from 'zod';
|
|
12
|
-
import { PlanService } from './plan-service.js';
|
|
13
|
-
import { createPlanCreateTool } from './tools/plan-create-tool.js';
|
|
14
|
-
import { createPlanReadTool } from './tools/plan-read-tool.js';
|
|
15
|
-
import { createPlanUpdateTool } from './tools/plan-update-tool.js';
|
|
16
|
-
import { createPlanReviewTool } from './tools/plan-review-tool.js';
|
|
17
|
-
/**
|
|
18
|
-
* Available plan tool names for enabledTools configuration
|
|
19
|
-
*/
|
|
20
|
-
const PLAN_TOOL_NAMES = ['plan_create', 'plan_read', 'plan_update', 'plan_review'];
|
|
21
|
-
/**
|
|
22
|
-
* Configuration schema for Plan tools provider
|
|
23
|
-
*/
|
|
24
|
-
const PlanToolsConfigSchema = z
|
|
25
|
-
.object({
|
|
26
|
-
type: z.literal('plan-tools'),
|
|
27
|
-
basePath: z
|
|
28
|
-
.string()
|
|
29
|
-
.default('.dexto/plans')
|
|
30
|
-
.describe('Base directory for plan storage (relative to working directory)'),
|
|
31
|
-
enabledTools: z
|
|
32
|
-
.array(z.enum(PLAN_TOOL_NAMES))
|
|
33
|
-
.optional()
|
|
34
|
-
.describe(`Subset of tools to enable. If not specified, all tools are enabled. Available: ${PLAN_TOOL_NAMES.join(', ')}`),
|
|
35
|
-
})
|
|
36
|
-
.strict();
|
|
37
|
-
/**
|
|
38
|
-
* Plan tools provider
|
|
39
|
-
*
|
|
40
|
-
* Provides implementation planning tools:
|
|
41
|
-
* - plan_create: Create a new plan with markdown content
|
|
42
|
-
* - plan_read: Read the current plan
|
|
43
|
-
* - plan_update: Update existing plan (shows diff preview)
|
|
44
|
-
* - plan_review: Request user review of the plan (shows plan with approval options)
|
|
45
|
-
*
|
|
46
|
-
* Plans are stored in .dexto/plans/{sessionId}/ with:
|
|
47
|
-
* - plan.md: Markdown content with checkboxes (- [ ] and - [x])
|
|
48
|
-
* - plan-meta.json: Metadata (status, title, timestamps)
|
|
49
|
-
*/
|
|
50
|
-
export const planToolsProvider = {
|
|
51
|
-
type: 'plan-tools',
|
|
52
|
-
configSchema: PlanToolsConfigSchema,
|
|
53
|
-
create: (config, context) => {
|
|
54
|
-
const { logger } = context;
|
|
55
|
-
// Resolve base path (relative to cwd or absolute)
|
|
56
|
-
const basePath = path.isAbsolute(config.basePath)
|
|
57
|
-
? config.basePath
|
|
58
|
-
: path.join(process.cwd(), config.basePath);
|
|
59
|
-
logger.debug(`Creating PlanService with basePath: ${basePath}`);
|
|
60
|
-
const planService = new PlanService({ basePath }, logger);
|
|
61
|
-
// Build tool map for selective enabling
|
|
62
|
-
const toolCreators = {
|
|
63
|
-
plan_create: () => createPlanCreateTool(planService),
|
|
64
|
-
plan_read: () => createPlanReadTool(planService),
|
|
65
|
-
plan_update: () => createPlanUpdateTool(planService),
|
|
66
|
-
plan_review: () => createPlanReviewTool(planService),
|
|
67
|
-
};
|
|
68
|
-
// Determine which tools to create
|
|
69
|
-
const toolsToCreate = config.enabledTools ?? PLAN_TOOL_NAMES;
|
|
70
|
-
if (config.enabledTools) {
|
|
71
|
-
logger.debug(`Creating subset of plan tools: ${toolsToCreate.join(', ')}`);
|
|
72
|
-
}
|
|
73
|
-
// Create and return only the enabled tools
|
|
74
|
-
return toolsToCreate.map((toolName) => toolCreators[toolName]());
|
|
75
|
-
},
|
|
76
|
-
metadata: {
|
|
77
|
-
displayName: 'Plan Tools',
|
|
78
|
-
description: 'Create and manage implementation plans linked to sessions',
|
|
79
|
-
category: 'planning',
|
|
80
|
-
},
|
|
81
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tool-provider.test.d.ts","sourceRoot":"","sources":["../src/tool-provider.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plan Tools Provider Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for the planToolsProvider configuration and tool creation.
|
|
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 { planToolsProvider } from './tool-provider.js';
|
|
11
|
-
// Create mock logger
|
|
12
|
-
const createMockLogger = () => ({
|
|
13
|
-
debug: vi.fn(),
|
|
14
|
-
info: vi.fn(),
|
|
15
|
-
warn: vi.fn(),
|
|
16
|
-
error: vi.fn(),
|
|
17
|
-
createChild: vi.fn().mockReturnThis(),
|
|
18
|
-
});
|
|
19
|
-
// Create mock context with logger and minimal agent
|
|
20
|
-
const createMockContext = (logger) => ({
|
|
21
|
-
logger: logger,
|
|
22
|
-
agent: {}, // Minimal mock - provider only uses logger
|
|
23
|
-
});
|
|
24
|
-
describe('planToolsProvider', () => {
|
|
25
|
-
let mockLogger;
|
|
26
|
-
let tempDir;
|
|
27
|
-
let originalCwd;
|
|
28
|
-
beforeEach(async () => {
|
|
29
|
-
mockLogger = createMockLogger();
|
|
30
|
-
// Create temp directory for testing
|
|
31
|
-
const rawTempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dexto-provider-test-'));
|
|
32
|
-
tempDir = await fs.realpath(rawTempDir);
|
|
33
|
-
// Store original cwd and mock process.cwd to return temp dir
|
|
34
|
-
originalCwd = process.cwd();
|
|
35
|
-
vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
|
|
36
|
-
vi.clearAllMocks();
|
|
37
|
-
});
|
|
38
|
-
afterEach(async () => {
|
|
39
|
-
// Restore mocked process.cwd
|
|
40
|
-
vi.mocked(process.cwd).mockRestore();
|
|
41
|
-
try {
|
|
42
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Ignore cleanup errors
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
describe('provider metadata', () => {
|
|
49
|
-
it('should have correct type', () => {
|
|
50
|
-
expect(planToolsProvider.type).toBe('plan-tools');
|
|
51
|
-
});
|
|
52
|
-
it('should have metadata', () => {
|
|
53
|
-
expect(planToolsProvider.metadata).toBeDefined();
|
|
54
|
-
expect(planToolsProvider.metadata?.displayName).toBe('Plan Tools');
|
|
55
|
-
expect(planToolsProvider.metadata?.category).toBe('planning');
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
describe('config schema', () => {
|
|
59
|
-
it('should validate minimal config', () => {
|
|
60
|
-
const result = planToolsProvider.configSchema.safeParse({
|
|
61
|
-
type: 'plan-tools',
|
|
62
|
-
});
|
|
63
|
-
expect(result.success).toBe(true);
|
|
64
|
-
if (result.success) {
|
|
65
|
-
expect(result.data.basePath).toBe('.dexto/plans');
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
it('should validate config with custom basePath', () => {
|
|
69
|
-
const result = planToolsProvider.configSchema.safeParse({
|
|
70
|
-
type: 'plan-tools',
|
|
71
|
-
basePath: '/custom/path',
|
|
72
|
-
});
|
|
73
|
-
expect(result.success).toBe(true);
|
|
74
|
-
if (result.success) {
|
|
75
|
-
expect(result.data.basePath).toBe('/custom/path');
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
it('should validate config with enabledTools', () => {
|
|
79
|
-
const result = planToolsProvider.configSchema.safeParse({
|
|
80
|
-
type: 'plan-tools',
|
|
81
|
-
enabledTools: ['plan_create', 'plan_read'],
|
|
82
|
-
});
|
|
83
|
-
expect(result.success).toBe(true);
|
|
84
|
-
if (result.success) {
|
|
85
|
-
expect(result.data.enabledTools).toEqual(['plan_create', 'plan_read']);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
it('should reject invalid tool names', () => {
|
|
89
|
-
const result = planToolsProvider.configSchema.safeParse({
|
|
90
|
-
type: 'plan-tools',
|
|
91
|
-
enabledTools: ['invalid_tool'],
|
|
92
|
-
});
|
|
93
|
-
expect(result.success).toBe(false);
|
|
94
|
-
});
|
|
95
|
-
it('should reject unknown properties', () => {
|
|
96
|
-
const result = planToolsProvider.configSchema.safeParse({
|
|
97
|
-
type: 'plan-tools',
|
|
98
|
-
unknownProp: 'value',
|
|
99
|
-
});
|
|
100
|
-
expect(result.success).toBe(false);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
describe('create', () => {
|
|
104
|
-
it('should create all tools by default', () => {
|
|
105
|
-
const config = planToolsProvider.configSchema.parse({
|
|
106
|
-
type: 'plan-tools',
|
|
107
|
-
});
|
|
108
|
-
const tools = planToolsProvider.create(config, createMockContext(mockLogger));
|
|
109
|
-
expect(tools).toHaveLength(4);
|
|
110
|
-
const toolIds = tools.map((t) => t.id);
|
|
111
|
-
expect(toolIds).toContain('plan_create');
|
|
112
|
-
expect(toolIds).toContain('plan_read');
|
|
113
|
-
expect(toolIds).toContain('plan_update');
|
|
114
|
-
expect(toolIds).toContain('plan_review');
|
|
115
|
-
});
|
|
116
|
-
it('should create only enabled tools', () => {
|
|
117
|
-
const config = planToolsProvider.configSchema.parse({
|
|
118
|
-
type: 'plan-tools',
|
|
119
|
-
enabledTools: ['plan_create', 'plan_read'],
|
|
120
|
-
});
|
|
121
|
-
const tools = planToolsProvider.create(config, createMockContext(mockLogger));
|
|
122
|
-
expect(tools).toHaveLength(2);
|
|
123
|
-
const toolIds = tools.map((t) => t.id);
|
|
124
|
-
expect(toolIds).toContain('plan_create');
|
|
125
|
-
expect(toolIds).toContain('plan_read');
|
|
126
|
-
expect(toolIds).not.toContain('plan_update');
|
|
127
|
-
});
|
|
128
|
-
it('should create single tool', () => {
|
|
129
|
-
const config = planToolsProvider.configSchema.parse({
|
|
130
|
-
type: 'plan-tools',
|
|
131
|
-
enabledTools: ['plan_update'],
|
|
132
|
-
});
|
|
133
|
-
const tools = planToolsProvider.create(config, createMockContext(mockLogger));
|
|
134
|
-
expect(tools).toHaveLength(1);
|
|
135
|
-
expect(tools[0].id).toBe('plan_update');
|
|
136
|
-
});
|
|
137
|
-
it('should use relative basePath from cwd', () => {
|
|
138
|
-
const config = planToolsProvider.configSchema.parse({
|
|
139
|
-
type: 'plan-tools',
|
|
140
|
-
basePath: '.dexto/plans',
|
|
141
|
-
});
|
|
142
|
-
planToolsProvider.create(config, createMockContext(mockLogger));
|
|
143
|
-
// Verify debug log was called with resolved path
|
|
144
|
-
expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining(path.join(tempDir, '.dexto/plans')));
|
|
145
|
-
});
|
|
146
|
-
it('should use absolute basePath as-is', () => {
|
|
147
|
-
const absolutePath = '/absolute/path/to/plans';
|
|
148
|
-
const config = planToolsProvider.configSchema.parse({
|
|
149
|
-
type: 'plan-tools',
|
|
150
|
-
basePath: absolutePath,
|
|
151
|
-
});
|
|
152
|
-
planToolsProvider.create(config, createMockContext(mockLogger));
|
|
153
|
-
expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining(absolutePath));
|
|
154
|
-
});
|
|
155
|
-
it('should log when creating subset of tools', () => {
|
|
156
|
-
const config = planToolsProvider.configSchema.parse({
|
|
157
|
-
type: 'plan-tools',
|
|
158
|
-
enabledTools: ['plan_create'],
|
|
159
|
-
});
|
|
160
|
-
planToolsProvider.create(config, createMockContext(mockLogger));
|
|
161
|
-
expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining('Creating subset of plan tools'));
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
describe('tool descriptions', () => {
|
|
165
|
-
it('should have descriptions for all tools', () => {
|
|
166
|
-
const config = planToolsProvider.configSchema.parse({
|
|
167
|
-
type: 'plan-tools',
|
|
168
|
-
});
|
|
169
|
-
const tools = planToolsProvider.create(config, createMockContext(mockLogger));
|
|
170
|
-
for (const tool of tools) {
|
|
171
|
-
expect(tool.description).toBeDefined();
|
|
172
|
-
expect(tool.description.length).toBeGreaterThan(0);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
it('should have input schemas for all tools', () => {
|
|
176
|
-
const config = planToolsProvider.configSchema.parse({
|
|
177
|
-
type: 'plan-tools',
|
|
178
|
-
});
|
|
179
|
-
const tools = planToolsProvider.create(config, createMockContext(mockLogger));
|
|
180
|
-
for (const tool of tools) {
|
|
181
|
-
expect(tool.inputSchema).toBeDefined();
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
});
|
package/skills/plan/SKILL.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plan
|
|
3
|
-
description: Enter planning mode to create and manage implementation plans
|
|
4
|
-
user-invocable: true
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Planning Mode - PLAN FIRST, THEN IMPLEMENT
|
|
8
|
-
|
|
9
|
-
**CRITICAL**: You are in planning mode. You MUST create and get approval for a plan BEFORE writing any code or making any changes.
|
|
10
|
-
|
|
11
|
-
## MANDATORY WORKFLOW
|
|
12
|
-
|
|
13
|
-
**DO NOT skip these steps. DO NOT start implementing until the plan is approved.**
|
|
14
|
-
|
|
15
|
-
1. **Research first** (if needed): Use the explore agent or read relevant files to understand the codebase
|
|
16
|
-
2. **Check for existing plan**: Use `plan_read` to see if a plan exists
|
|
17
|
-
3. **Create/update plan**: Use `plan_create` or `plan_update` to define your approach
|
|
18
|
-
4. **Request review**: Use `plan_review` to get user approval
|
|
19
|
-
5. **WAIT for approval**: Only proceed to implementation after user approves
|
|
20
|
-
6. **Implement**: Execute the approved plan, updating checkboxes as you go
|
|
21
|
-
|
|
22
|
-
## Research Phase
|
|
23
|
-
|
|
24
|
-
Before creating your plan, you should understand the codebase:
|
|
25
|
-
|
|
26
|
-
- **Use the explore agent** (spawn_agent with subagent_type="Explore") to search for relevant code, patterns, and existing implementations
|
|
27
|
-
- **Read key files** to understand the current architecture
|
|
28
|
-
- **Identify dependencies** and files that will need changes
|
|
29
|
-
|
|
30
|
-
This research informs your plan and prevents wasted effort from incorrect assumptions.
|
|
31
|
-
|
|
32
|
-
## Available Tools
|
|
33
|
-
|
|
34
|
-
- **plan_create**: Create a new plan (REQUIRED before any implementation)
|
|
35
|
-
- **plan_read**: Read the current plan
|
|
36
|
-
- **plan_update**: Update the existing plan (shows diff preview)
|
|
37
|
-
- **plan_review**: Request user review - returns approve/iterate/reject with feedback
|
|
38
|
-
|
|
39
|
-
## WHAT YOU MUST DO NOW
|
|
40
|
-
|
|
41
|
-
1. **Research**: Use the explore agent or read files to understand the relevant parts of the codebase
|
|
42
|
-
2. **Check plan**: Use `plan_read` to check if a plan already exists
|
|
43
|
-
3. **Create plan**: Use `plan_create` to create a comprehensive plan based on your research
|
|
44
|
-
4. **Get approval**: Use `plan_review` to request user approval
|
|
45
|
-
5. **STOP and WAIT** - do not write any code until the user approves via plan_review
|
|
46
|
-
|
|
47
|
-
## Plan Structure
|
|
48
|
-
|
|
49
|
-
```markdown
|
|
50
|
-
# {Title}
|
|
51
|
-
|
|
52
|
-
## Objective
|
|
53
|
-
{Clear statement of what we're building/fixing}
|
|
54
|
-
|
|
55
|
-
## Steps
|
|
56
|
-
|
|
57
|
-
### 1. {Step Name}
|
|
58
|
-
- [ ] {Task description}
|
|
59
|
-
- [ ] {Task description}
|
|
60
|
-
Files: `path/to/file.ts`, `path/to/other.ts`
|
|
61
|
-
|
|
62
|
-
### 2. {Step Name}
|
|
63
|
-
- [ ] {Task description}
|
|
64
|
-
Files: `path/to/file.ts`
|
|
65
|
-
|
|
66
|
-
## Considerations
|
|
67
|
-
- {Edge cases to handle}
|
|
68
|
-
- {Error scenarios}
|
|
69
|
-
|
|
70
|
-
## Success Criteria
|
|
71
|
-
- {How we know we're done}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Guidelines
|
|
75
|
-
|
|
76
|
-
- **Break down complex tasks** into clear, sequential steps
|
|
77
|
-
- **Include specific file paths** that will be created or modified
|
|
78
|
-
- **Note dependencies** between steps
|
|
79
|
-
- **Keep plans concise** but complete
|
|
80
|
-
|
|
81
|
-
## Handling Review Responses
|
|
82
|
-
|
|
83
|
-
After calling `plan_review`, handle the response:
|
|
84
|
-
|
|
85
|
-
- **approve**: User approved - proceed with implementation
|
|
86
|
-
- **iterate**: User wants changes - update the plan based on feedback, then call `plan_review` again
|
|
87
|
-
- **reject**: User rejected - ask what they want instead
|
|
88
|
-
|
|
89
|
-
## DO NOT
|
|
90
|
-
|
|
91
|
-
- ❌ Start writing code before creating a plan
|
|
92
|
-
- ❌ Skip the plan_review step
|
|
93
|
-
- ❌ Assume approval - wait for explicit user response
|
|
94
|
-
- ❌ Make changes outside the approved plan without updating it first
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
**START NOW**:
|
|
99
|
-
1. Research the codebase using the explore agent if needed
|
|
100
|
-
2. Use `plan_read` to check for an existing plan
|
|
101
|
-
3. Use `plan_create` to create your plan
|
|
102
|
-
4. Use `plan_review` to get approval before any implementation
|