@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.
Files changed (43) hide show
  1. package/dist/index.d.ts +1 -25
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -29
  4. package/dist/plan-service-getter.d.ts +4 -0
  5. package/dist/plan-service-getter.d.ts.map +1 -0
  6. package/dist/plan-service-getter.js +1 -0
  7. package/dist/plan-service.d.ts +2 -2
  8. package/dist/plan-service.d.ts.map +1 -1
  9. package/dist/plan-service.js +7 -7
  10. package/dist/plan-service.test.js +6 -0
  11. package/dist/tool-factory-config.d.ts +32 -0
  12. package/dist/tool-factory-config.d.ts.map +1 -0
  13. package/dist/tool-factory-config.js +30 -0
  14. package/dist/tool-factory.d.ts +4 -0
  15. package/dist/tool-factory.d.ts.map +1 -0
  16. package/dist/tool-factory.js +36 -0
  17. package/dist/tool-factory.test.d.ts +7 -0
  18. package/dist/tool-factory.test.d.ts.map +1 -0
  19. package/dist/tool-factory.test.js +100 -0
  20. package/dist/tools/plan-create-tool.d.ts +15 -3
  21. package/dist/tools/plan-create-tool.d.ts.map +1 -1
  22. package/dist/tools/plan-create-tool.js +29 -18
  23. package/dist/tools/plan-create-tool.test.js +47 -22
  24. package/dist/tools/plan-read-tool.d.ts +6 -3
  25. package/dist/tools/plan-read-tool.d.ts.map +1 -1
  26. package/dist/tools/plan-read-tool.js +10 -7
  27. package/dist/tools/plan-read-tool.test.js +31 -19
  28. package/dist/tools/plan-review-tool.d.ts +14 -5
  29. package/dist/tools/plan-review-tool.d.ts.map +1 -1
  30. package/dist/tools/plan-review-tool.js +16 -12
  31. package/dist/tools/plan-update-tool.d.ts +12 -3
  32. package/dist/tools/plan-update-tool.d.ts.map +1 -1
  33. package/dist/tools/plan-update-tool.js +14 -10
  34. package/dist/tools/plan-update-tool.test.js +40 -28
  35. package/package.json +4 -5
  36. package/.dexto-plugin/plugin.json +0 -7
  37. package/dist/tool-provider.d.ts +0 -44
  38. package/dist/tool-provider.d.ts.map +0 -1
  39. package/dist/tool-provider.js +0 -81
  40. package/dist/tool-provider.test.d.ts +0 -7
  41. package/dist/tool-provider.test.d.ts.map +0 -1
  42. package/dist/tool-provider.test.js +0 -185
  43. package/skills/plan/SKILL.md +0 -102
package/dist/index.d.ts CHANGED
@@ -3,32 +3,8 @@
3
3
  *
4
4
  * Implementation planning tools with session-linked plans.
5
5
  * Provides tools for creating, reading, updating, and tracking plans.
6
- *
7
- * This package is a Dexto plugin that automatically registers:
8
- * - Custom tool provider: plan-tools
9
- * - Skill: plan (planning mode instructions)
10
- *
11
- * Usage:
12
- * 1. Install the package
13
- * 2. The plugin discovery will find .dexto-plugin/plugin.json
14
- * 3. Tools and skill are automatically registered
15
- */
16
- /**
17
- * Path to the plugin directory containing .dexto-plugin manifest.
18
- * Use this in image definitions to declare bundled plugins.
19
- *
20
- * @example
21
- * ```typescript
22
- * import { PLUGIN_PATH } from '@dexto/tools-plan';
23
- *
24
- * export default defineImage({
25
- * bundledPlugins: [PLUGIN_PATH],
26
- * // ...
27
- * });
28
- * ```
29
6
  */
30
- export declare const PLUGIN_PATH: string;
31
- export { planToolsProvider } from './tool-provider.js';
7
+ export { planToolsFactory } from './tool-factory.js';
32
8
  export { PlanService } from './plan-service.js';
33
9
  export type { Plan, PlanMeta, PlanStatus, PlanServiceOptions, PlanUpdateResult } from './types.js';
34
10
  export { PlanError, PlanErrorCode, type PlanErrorCodeType } from './errors.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,QAAgC,CAAC;AAGzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnG,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnG,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -3,36 +3,9 @@
3
3
  *
4
4
  * Implementation planning tools with session-linked plans.
5
5
  * Provides tools for creating, reading, updating, and tracking plans.
6
- *
7
- * This package is a Dexto plugin that automatically registers:
8
- * - Custom tool provider: plan-tools
9
- * - Skill: plan (planning mode instructions)
10
- *
11
- * Usage:
12
- * 1. Install the package
13
- * 2. The plugin discovery will find .dexto-plugin/plugin.json
14
- * 3. Tools and skill are automatically registered
15
- */
16
- import * as path from 'node:path';
17
- import { fileURLToPath } from 'node:url';
18
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
- /**
20
- * Path to the plugin directory containing .dexto-plugin manifest.
21
- * Use this in image definitions to declare bundled plugins.
22
- *
23
- * @example
24
- * ```typescript
25
- * import { PLUGIN_PATH } from '@dexto/tools-plan';
26
- *
27
- * export default defineImage({
28
- * bundledPlugins: [PLUGIN_PATH],
29
- * // ...
30
- * });
31
- * ```
32
6
  */
33
- export const PLUGIN_PATH = path.resolve(__dirname, '..');
34
- // Tool provider (for direct registration if needed)
35
- export { planToolsProvider } from './tool-provider.js';
7
+ // Tool factory (image-compatible)
8
+ export { planToolsFactory } from './tool-factory.js';
36
9
  // Service (for advanced use cases)
37
10
  export { PlanService } from './plan-service.js';
38
11
  // Error utilities
@@ -0,0 +1,4 @@
1
+ import type { ToolExecutionContext } from '@dexto/core';
2
+ import type { PlanService } from './plan-service.js';
3
+ export type PlanServiceGetter = (context: ToolExecutionContext) => Promise<PlanService>;
4
+ //# sourceMappingURL=plan-service-getter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-service-getter.d.ts","sourceRoot":"","sources":["../src/plan-service-getter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -6,7 +6,7 @@
6
6
  * - plan.md: The plan content
7
7
  * - plan-meta.json: Metadata (status, checkpoints, timestamps)
8
8
  */
9
- import type { IDextoLogger } from '@dexto/core';
9
+ import type { Logger } from '@dexto/core';
10
10
  import type { Plan, PlanMeta, PlanServiceOptions, PlanUpdateResult } from './types.js';
11
11
  /**
12
12
  * Service for managing implementation plans.
@@ -14,7 +14,7 @@ import type { Plan, PlanMeta, PlanServiceOptions, PlanUpdateResult } from './typ
14
14
  export declare class PlanService {
15
15
  private basePath;
16
16
  private logger;
17
- constructor(options: PlanServiceOptions, logger?: IDextoLogger);
17
+ constructor(options: PlanServiceOptions, logger: Logger);
18
18
  /**
19
19
  * Resolves and validates a session directory path.
20
20
  * Prevents path traversal attacks by ensuring the resolved path stays within basePath.
@@ -1 +1 @@
1
- {"version":3,"file":"plan-service.d.ts","sourceRoot":"","sources":["../src/plan-service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMvF;;GAEG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAA2B;gBAE7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,CAAC,EAAE,YAAY;IAK9D;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;OAEG;IACH,OAAO,CAAC,UAAU;IAIlB;;;OAGG;IACI,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAI7C;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKjD;;;;;OAKG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC7F;;;;OAIG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAmDnD;;;;;OAKG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqC3E;;;;;OAKG;IACG,UAAU,CACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GACrD,OAAO,CAAC,QAAQ,CAAC;IA2BpB;;;;;OAKG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAYjD"}
1
+ {"version":3,"file":"plan-service.d.ts","sourceRoot":"","sources":["../src/plan-service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMvF;;GAEG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM;IAKvD;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;OAEG;IACH,OAAO,CAAC,UAAU;IAIlB;;;OAGG;IACI,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAI7C;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKjD;;;;;OAKG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC7F;;;;OAIG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAiDnD;;;;;OAKG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqC3E;;;;;OAKG;IACG,UAAU,CACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GACrD,OAAO,CAAC,QAAQ,CAAC;IA2BpB;;;;;OAKG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAYjD"}
@@ -92,7 +92,7 @@ export class PlanService {
92
92
  fs.writeFile(this.getPlanPath(sessionId), content, 'utf-8'),
93
93
  fs.writeFile(this.getMetaPath(sessionId), JSON.stringify(meta, null, 2), 'utf-8'),
94
94
  ]);
95
- this.logger?.debug(`Created plan for session ${sessionId}`);
95
+ this.logger.debug(`Created plan for session ${sessionId}`);
96
96
  return { content, meta };
97
97
  }
98
98
  catch (error) {
@@ -116,7 +116,7 @@ export class PlanService {
116
116
  const metaParsed = JSON.parse(metaContent);
117
117
  const metaResult = PlanMetaSchema.safeParse(metaParsed);
118
118
  if (!metaResult.success) {
119
- this.logger?.warn(`Invalid plan metadata for session ${sessionId}, using defaults`);
119
+ this.logger.warn(`Invalid plan metadata for session ${sessionId}, using defaults`);
120
120
  // Return with minimal metadata if parsing fails
121
121
  return {
122
122
  content,
@@ -139,11 +139,11 @@ export class PlanService {
139
139
  // JSON parse errors (SyntaxError) mean corrupted data - treat as not found
140
140
  // but log for debugging
141
141
  if (error instanceof SyntaxError) {
142
- this.logger?.error(`Failed to read plan for session ${sessionId}: ${error.message}`);
142
+ this.logger.error(`Failed to read plan for session ${sessionId}: ${error.message}`);
143
143
  return null;
144
144
  }
145
145
  // For real I/O errors (permission denied, disk issues), throw to surface the issue
146
- this.logger?.error(`Failed to read plan for session ${sessionId}: ${err.message ?? String(err)}`);
146
+ this.logger.error(`Failed to read plan for session ${sessionId}: ${err.message ?? String(err)}`);
147
147
  throw PlanError.storageError('read', sessionId, err);
148
148
  }
149
149
  }
@@ -170,7 +170,7 @@ export class PlanService {
170
170
  fs.writeFile(this.getPlanPath(sessionId), content, 'utf-8'),
171
171
  fs.writeFile(this.getMetaPath(sessionId), JSON.stringify(updatedMeta, null, 2), 'utf-8'),
172
172
  ]);
173
- this.logger?.debug(`Updated plan for session ${sessionId}`);
173
+ this.logger.debug(`Updated plan for session ${sessionId}`);
174
174
  return {
175
175
  oldContent,
176
176
  newContent: content,
@@ -199,7 +199,7 @@ export class PlanService {
199
199
  };
200
200
  try {
201
201
  await fs.writeFile(this.getMetaPath(sessionId), JSON.stringify(updatedMeta, null, 2), 'utf-8');
202
- this.logger?.debug(`Updated plan metadata for session ${sessionId}`);
202
+ this.logger.debug(`Updated plan metadata for session ${sessionId}`);
203
203
  return updatedMeta;
204
204
  }
205
205
  catch (error) {
@@ -218,7 +218,7 @@ export class PlanService {
218
218
  }
219
219
  try {
220
220
  await fs.rm(this.getPlanDir(sessionId), { recursive: true, force: true });
221
- this.logger?.debug(`Deleted plan for session ${sessionId}`);
221
+ this.logger.debug(`Deleted plan for session ${sessionId}`);
222
222
  }
223
223
  catch (error) {
224
224
  throw PlanError.storageError('delete', sessionId, error);
@@ -13,10 +13,16 @@ import { DextoRuntimeError } from '@dexto/core';
13
13
  // Create mock logger
14
14
  const createMockLogger = () => ({
15
15
  debug: vi.fn(),
16
+ silly: vi.fn(),
16
17
  info: vi.fn(),
17
18
  warn: vi.fn(),
18
19
  error: vi.fn(),
20
+ trackException: vi.fn(),
19
21
  createChild: vi.fn().mockReturnThis(),
22
+ setLevel: vi.fn(),
23
+ getLevel: vi.fn(() => 'debug'),
24
+ getLogFilePath: vi.fn(() => null),
25
+ destroy: vi.fn(async () => undefined),
20
26
  });
21
27
  describe('PlanService', () => {
22
28
  let mockLogger;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Plan Tools Factory
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
+ /**
12
+ * Available plan tool names for enabledTools configuration
13
+ */
14
+ export declare const PLAN_TOOL_NAMES: readonly ["plan_create", "plan_read", "plan_update", "plan_review"];
15
+ /**
16
+ * Configuration schema for Plan tools factory
17
+ */
18
+ export declare const PlanToolsConfigSchema: z.ZodObject<{
19
+ type: z.ZodLiteral<"plan-tools">;
20
+ basePath: z.ZodDefault<z.ZodString>;
21
+ enabledTools: z.ZodOptional<z.ZodArray<z.ZodEnum<["plan_create", "plan_read", "plan_update", "plan_review"]>, "many">>;
22
+ }, "strict", z.ZodTypeAny, {
23
+ type: "plan-tools";
24
+ basePath: string;
25
+ enabledTools?: ("plan_create" | "plan_read" | "plan_update" | "plan_review")[] | undefined;
26
+ }, {
27
+ type: "plan-tools";
28
+ basePath?: string | undefined;
29
+ enabledTools?: ("plan_create" | "plan_read" | "plan_update" | "plan_review")[] | undefined;
30
+ }>;
31
+ export type PlanToolsConfig = z.output<typeof PlanToolsConfigSchema>;
32
+ //# sourceMappingURL=tool-factory-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-factory-config.d.ts","sourceRoot":"","sources":["../src/tool-factory-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,eAAe,qEAAsE,CAAC;AAEnG;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;EAcrB,CAAC;AAEd,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Plan Tools Factory
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
+ /**
12
+ * Available plan tool names for enabledTools configuration
13
+ */
14
+ export const PLAN_TOOL_NAMES = ['plan_create', 'plan_read', 'plan_update', 'plan_review'];
15
+ /**
16
+ * Configuration schema for Plan tools factory
17
+ */
18
+ export const PlanToolsConfigSchema = z
19
+ .object({
20
+ type: z.literal('plan-tools'),
21
+ basePath: z
22
+ .string()
23
+ .default('.dexto/plans')
24
+ .describe('Base directory for plan storage (relative to working directory)'),
25
+ enabledTools: z
26
+ .array(z.enum(PLAN_TOOL_NAMES))
27
+ .optional()
28
+ .describe(`Subset of tools to enable. If not specified, all tools are enabled. Available: ${PLAN_TOOL_NAMES.join(', ')}`),
29
+ })
30
+ .strict();
@@ -0,0 +1,4 @@
1
+ import type { ToolFactory } from '@dexto/agent-config';
2
+ import { type PlanToolsConfig } from './tool-factory-config.js';
3
+ export declare const planToolsFactory: ToolFactory<PlanToolsConfig>;
4
+ //# sourceMappingURL=tool-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-factory.d.ts","sourceRoot":"","sources":["../src/tool-factory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAQvD,OAAO,EAGH,KAAK,eAAe,EACvB,MAAM,0BAA0B,CAAC;AAKlC,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,eAAe,CAiCzD,CAAC"}
@@ -0,0 +1,36 @@
1
+ import * as path from 'node:path';
2
+ import { PlanService } from './plan-service.js';
3
+ import { createPlanCreateTool } from './tools/plan-create-tool.js';
4
+ import { createPlanReadTool } from './tools/plan-read-tool.js';
5
+ import { createPlanUpdateTool } from './tools/plan-update-tool.js';
6
+ import { createPlanReviewTool } from './tools/plan-review-tool.js';
7
+ import { PLAN_TOOL_NAMES, PlanToolsConfigSchema, } from './tool-factory-config.js';
8
+ export const planToolsFactory = {
9
+ configSchema: PlanToolsConfigSchema,
10
+ metadata: {
11
+ displayName: 'Plan Tools',
12
+ description: 'Create and manage implementation plans linked to sessions',
13
+ category: 'planning',
14
+ },
15
+ create: (config) => {
16
+ const basePath = path.isAbsolute(config.basePath)
17
+ ? config.basePath
18
+ : path.join(process.cwd(), config.basePath);
19
+ let planService;
20
+ const getPlanService = async (context) => {
21
+ if (planService) {
22
+ return planService;
23
+ }
24
+ planService = new PlanService({ basePath }, context.logger);
25
+ return planService;
26
+ };
27
+ const toolCreators = {
28
+ plan_create: () => createPlanCreateTool(getPlanService),
29
+ plan_read: () => createPlanReadTool(getPlanService),
30
+ plan_update: () => createPlanUpdateTool(getPlanService),
31
+ plan_review: () => createPlanReviewTool(getPlanService),
32
+ };
33
+ const toolsToCreate = config.enabledTools ?? PLAN_TOOL_NAMES;
34
+ return toolsToCreate.map((toolName) => toolCreators[toolName]());
35
+ },
36
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Plan Tools Factory Tests
3
+ *
4
+ * Validates the plan tools factory schema and tool creation.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=tool-factory.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-factory.test.d.ts","sourceRoot":"","sources":["../src/tool-factory.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Plan Tools Factory Tests
3
+ *
4
+ * Validates the plan tools factory schema and tool creation.
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { planToolsFactory } from './tool-factory.js';
8
+ describe('planToolsFactory', () => {
9
+ describe('factory metadata', () => {
10
+ it('should have metadata', () => {
11
+ expect(planToolsFactory.metadata).toBeDefined();
12
+ expect(planToolsFactory.metadata?.displayName).toBe('Plan Tools');
13
+ expect(planToolsFactory.metadata?.category).toBe('planning');
14
+ });
15
+ });
16
+ describe('config schema', () => {
17
+ it('should validate minimal config', () => {
18
+ const result = planToolsFactory.configSchema.safeParse({
19
+ type: 'plan-tools',
20
+ });
21
+ expect(result.success).toBe(true);
22
+ if (result.success) {
23
+ expect(result.data.basePath).toBe('.dexto/plans');
24
+ }
25
+ });
26
+ it('should validate config with custom basePath', () => {
27
+ const result = planToolsFactory.configSchema.safeParse({
28
+ type: 'plan-tools',
29
+ basePath: '/custom/path',
30
+ });
31
+ expect(result.success).toBe(true);
32
+ if (result.success) {
33
+ expect(result.data.basePath).toBe('/custom/path');
34
+ }
35
+ });
36
+ it('should validate config with enabledTools', () => {
37
+ const result = planToolsFactory.configSchema.safeParse({
38
+ type: 'plan-tools',
39
+ enabledTools: ['plan_create', 'plan_read'],
40
+ });
41
+ expect(result.success).toBe(true);
42
+ if (result.success) {
43
+ expect(result.data.enabledTools).toEqual(['plan_create', 'plan_read']);
44
+ }
45
+ });
46
+ it('should reject invalid tool names', () => {
47
+ const result = planToolsFactory.configSchema.safeParse({
48
+ type: 'plan-tools',
49
+ enabledTools: ['invalid_tool'],
50
+ });
51
+ expect(result.success).toBe(false);
52
+ });
53
+ it('should reject unknown properties', () => {
54
+ const result = planToolsFactory.configSchema.safeParse({
55
+ type: 'plan-tools',
56
+ unknownProp: 'value',
57
+ });
58
+ expect(result.success).toBe(false);
59
+ });
60
+ });
61
+ describe('create', () => {
62
+ it('should create all tools by default', () => {
63
+ const config = planToolsFactory.configSchema.parse({
64
+ type: 'plan-tools',
65
+ });
66
+ const tools = planToolsFactory.create(config);
67
+ expect(tools).toHaveLength(4);
68
+ const toolIds = tools.map((t) => t.id);
69
+ expect(toolIds).toContain('plan_create');
70
+ expect(toolIds).toContain('plan_read');
71
+ expect(toolIds).toContain('plan_update');
72
+ expect(toolIds).toContain('plan_review');
73
+ });
74
+ it('should create only enabled tools', () => {
75
+ const config = planToolsFactory.configSchema.parse({
76
+ type: 'plan-tools',
77
+ enabledTools: ['plan_create', 'plan_read'],
78
+ });
79
+ const tools = planToolsFactory.create(config);
80
+ expect(tools).toHaveLength(2);
81
+ const toolIds = tools.map((t) => t.id);
82
+ expect(toolIds).toContain('plan_create');
83
+ expect(toolIds).toContain('plan_read');
84
+ expect(toolIds).not.toContain('plan_update');
85
+ });
86
+ });
87
+ describe('tool definitions', () => {
88
+ it('should have descriptions and input schemas for all tools', () => {
89
+ const config = planToolsFactory.configSchema.parse({
90
+ type: 'plan-tools',
91
+ });
92
+ const tools = planToolsFactory.create(config);
93
+ for (const tool of tools) {
94
+ expect(tool.description).toBeDefined();
95
+ expect(tool.description.length).toBeGreaterThan(0);
96
+ expect(tool.inputSchema).toBeDefined();
97
+ }
98
+ });
99
+ });
100
+ });
@@ -4,10 +4,22 @@
4
4
  * Creates a new implementation plan for the current session.
5
5
  * Shows a preview for approval before saving.
6
6
  */
7
- import type { InternalTool } from '@dexto/core';
8
- import type { PlanService } from '../plan-service.js';
7
+ import { z } from 'zod';
8
+ import type { Tool } from '@dexto/core';
9
+ import type { PlanServiceGetter } from '../plan-service-getter.js';
10
+ declare const PlanCreateInputSchema: z.ZodObject<{
11
+ title: z.ZodString;
12
+ content: z.ZodString;
13
+ }, "strict", z.ZodTypeAny, {
14
+ title: string;
15
+ content: string;
16
+ }, {
17
+ title: string;
18
+ content: string;
19
+ }>;
9
20
  /**
10
21
  * Creates the plan_create tool
11
22
  */
12
- export declare function createPlanCreateTool(planService: PlanService): InternalTool;
23
+ export declare function createPlanCreateTool(getPlanService: PlanServiceGetter): Tool<typeof PlanCreateInputSchema>;
24
+ export {};
13
25
  //# sourceMappingURL=plan-create-tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan-create-tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-create-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAyC,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAgBtD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,WAAW,GAAG,YAAY,CAgE3E"}
1
+ {"version":3,"file":"plan-create-tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-create-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,IAAI,EAAyC,MAAM,aAAa,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAGnE,QAAA,MAAM,qBAAqB;;;;;;;;;EASd,CAAC;AAEd;;GAEG;AACH,wBAAgB,oBAAoB,CAChC,cAAc,EAAE,iBAAiB,GAClC,IAAI,CAAC,OAAO,qBAAqB,CAAC,CA0EpC"}
@@ -5,6 +5,7 @@
5
5
  * Shows a preview for approval before saving.
6
6
  */
7
7
  import { z } from 'zod';
8
+ import { defineTool } from '@dexto/core';
8
9
  import { PlanError } from '../errors.js';
9
10
  const PlanCreateInputSchema = z
10
11
  .object({
@@ -17,9 +18,10 @@ const PlanCreateInputSchema = z
17
18
  /**
18
19
  * Creates the plan_create tool
19
20
  */
20
- export function createPlanCreateTool(planService) {
21
- return {
21
+ export function createPlanCreateTool(getPlanService) {
22
+ return defineTool({
22
23
  id: 'plan_create',
24
+ displayName: 'Plan',
23
25
  description: 'Create a new implementation plan for the current session. Shows the plan for approval before saving. Use markdown format for the plan content with clear steps and file references.',
24
26
  inputSchema: PlanCreateInputSchema,
25
27
  /**
@@ -27,46 +29,55 @@ export function createPlanCreateTool(planService) {
27
29
  */
28
30
  generatePreview: async (input, context) => {
29
31
  const { content } = input;
30
- if (!context?.sessionId) {
32
+ if (!context.sessionId) {
31
33
  throw PlanError.sessionIdRequired();
32
34
  }
35
+ const resolvedPlanService = await getPlanService(context);
33
36
  // Check if plan already exists
34
- const exists = await planService.exists(context.sessionId);
37
+ const exists = await resolvedPlanService.exists(context.sessionId);
35
38
  if (exists) {
36
39
  throw PlanError.planAlreadyExists(context.sessionId);
37
40
  }
38
41
  // Return preview for approval UI
39
42
  const lineCount = content.split('\n').length;
40
- const planPath = planService.getPlanPath(context.sessionId);
43
+ const planPath = resolvedPlanService.getPlanPath(context.sessionId);
41
44
  return {
42
45
  type: 'file',
43
46
  path: planPath,
44
47
  operation: 'create',
45
48
  content,
46
- size: content.length,
49
+ size: Buffer.byteLength(content, 'utf8'),
47
50
  lineCount,
48
51
  };
49
52
  },
50
- execute: async (input, context) => {
53
+ async execute(input, context) {
51
54
  const { title, content } = input;
52
- if (!context?.sessionId) {
55
+ if (!context.sessionId) {
53
56
  throw PlanError.sessionIdRequired();
54
57
  }
55
- const plan = await planService.create(context.sessionId, content, { title });
56
- const planPath = planService.getPlanPath(context.sessionId);
58
+ const resolvedPlanService = await getPlanService(context);
59
+ // Keep consistent with generatePreview: fail early if plan already exists.
60
+ // (PlanService.create also guards this, but this keeps the control flow obvious.)
61
+ const exists = await resolvedPlanService.exists(context.sessionId);
62
+ if (exists) {
63
+ throw PlanError.planAlreadyExists(context.sessionId);
64
+ }
65
+ const plan = await resolvedPlanService.create(context.sessionId, content, { title });
66
+ const planPath = resolvedPlanService.getPlanPath(context.sessionId);
67
+ const _display = {
68
+ type: 'file',
69
+ path: planPath,
70
+ operation: 'create',
71
+ size: Buffer.byteLength(content, 'utf8'),
72
+ lineCount: content.split('\n').length,
73
+ };
57
74
  return {
58
75
  success: true,
59
76
  path: planPath,
60
77
  status: plan.meta.status,
61
78
  title: plan.meta.title,
62
- _display: {
63
- type: 'file',
64
- path: planPath,
65
- operation: 'create',
66
- size: content.length,
67
- lineCount: content.split('\n').length,
68
- },
79
+ _display,
69
80
  };
70
81
  },
71
- };
82
+ });
72
83
  }