@compilr-dev/sdk 0.1.7 → 0.1.8

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.
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Plan Tools — CRUD operations for plans.
3
+ *
4
+ * 5 tools: plan_create, plan_update, plan_get, plan_list, plan_delete
5
+ *
6
+ * Ported from CLI's src/tools/plan-tools.ts.
7
+ */
8
+ import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
9
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
10
+ export function createPlanTools(config) {
11
+ const { context: ctx } = config;
12
+ // ---------------------------------------------------------------------------
13
+ // plan_create
14
+ // ---------------------------------------------------------------------------
15
+ const planCreateTool = defineTool({
16
+ name: 'plan_create',
17
+ description: 'Create a new plan for the current project. Plans are used to document implementation approaches before coding. The plan is created with "draft" status.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ name: {
22
+ type: 'string',
23
+ description: 'Plan name (kebab-case, 3-5 words). Example: "auth-jwt-migration", "add-dark-mode-toggle"',
24
+ },
25
+ content: {
26
+ type: 'string',
27
+ description: 'Plan content in markdown format. Should include: Context, Goals, Steps, Verification sections.',
28
+ },
29
+ work_item_id: {
30
+ type: 'number',
31
+ description: 'Optional: ID of the work item this plan is for. Links the plan to a backlog item.',
32
+ },
33
+ project_id: {
34
+ type: 'number',
35
+ description: 'Project ID (uses active project if not provided)',
36
+ },
37
+ },
38
+ required: ['name', 'content'],
39
+ },
40
+ execute: async (input) => {
41
+ try {
42
+ const projectId = input.project_id ?? ctx.currentProjectId;
43
+ if (!projectId) {
44
+ return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
45
+ }
46
+ // Validate name format (kebab-case)
47
+ if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(input.name) && input.name.length > 2) {
48
+ if (input.name.length > 2 && !/^[a-z0-9-]+$/.test(input.name)) {
49
+ return createErrorResult('Plan name should be kebab-case (lowercase letters, numbers, hyphens). Example: "auth-jwt-migration"');
50
+ }
51
+ }
52
+ const createInput = {
53
+ project_id: projectId,
54
+ name: input.name,
55
+ content: input.content,
56
+ work_item_id: input.work_item_id,
57
+ };
58
+ const plan = await ctx.plans.create(createInput);
59
+ return createSuccessResult({
60
+ success: true,
61
+ message: `Plan "${plan.name}" created`,
62
+ plan: {
63
+ id: plan.id,
64
+ name: plan.name,
65
+ status: plan.status,
66
+ workItemId: plan.workItemId,
67
+ createdAt: plan.createdAt.toISOString(),
68
+ },
69
+ });
70
+ }
71
+ catch (error) {
72
+ return createErrorResult(`Failed to create plan: ${error instanceof Error ? error.message : String(error)}`);
73
+ }
74
+ },
75
+ });
76
+ // ---------------------------------------------------------------------------
77
+ // plan_update
78
+ // ---------------------------------------------------------------------------
79
+ const planUpdateTool = defineTool({
80
+ name: 'plan_update',
81
+ description: 'Update an existing plan. Can update content, status, or work item link. Status transitions: draft→approved→in_progress→completed. Any status can transition to "abandoned".',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ plan_id: {
86
+ type: 'number',
87
+ description: 'ID of the plan to update',
88
+ },
89
+ content: {
90
+ type: 'string',
91
+ description: 'New plan content (optional)',
92
+ },
93
+ status: {
94
+ type: 'string',
95
+ enum: ['draft', 'approved', 'in_progress', 'completed', 'abandoned'],
96
+ description: 'New status (optional). Valid transitions: draft→approved, approved→in_progress, in_progress→completed. Any→abandoned. abandoned→draft.',
97
+ },
98
+ work_item_id: {
99
+ type: ['number', 'null'],
100
+ description: 'Work item ID to link (optional). Use null to unlink.',
101
+ },
102
+ },
103
+ required: ['plan_id'],
104
+ },
105
+ execute: async (input) => {
106
+ try {
107
+ const updateInput = {};
108
+ if (input.content !== undefined) {
109
+ updateInput.content = input.content;
110
+ }
111
+ if (input.status !== undefined) {
112
+ updateInput.status = input.status;
113
+ }
114
+ if (input.work_item_id !== undefined) {
115
+ updateInput.work_item_id = input.work_item_id;
116
+ }
117
+ const plan = await ctx.plans.update(input.plan_id, updateInput);
118
+ if (!plan) {
119
+ return createErrorResult(`Plan with ID ${String(input.plan_id)} not found`);
120
+ }
121
+ return createSuccessResult({
122
+ success: true,
123
+ message: `Plan "${plan.name}" updated`,
124
+ plan: {
125
+ id: plan.id,
126
+ name: plan.name,
127
+ status: plan.status,
128
+ workItemId: plan.workItemId,
129
+ updatedAt: plan.updatedAt.toISOString(),
130
+ },
131
+ });
132
+ }
133
+ catch (error) {
134
+ return createErrorResult(`Failed to update plan: ${error instanceof Error ? error.message : String(error)}`);
135
+ }
136
+ },
137
+ });
138
+ // ---------------------------------------------------------------------------
139
+ // plan_get
140
+ // ---------------------------------------------------------------------------
141
+ const planGetTool = defineTool({
142
+ name: 'plan_get',
143
+ description: 'Get a plan by ID or name. Returns the full plan content and linked work item details.',
144
+ inputSchema: {
145
+ type: 'object',
146
+ properties: {
147
+ plan_id: {
148
+ type: 'number',
149
+ description: 'Plan ID (preferred)',
150
+ },
151
+ name: {
152
+ type: 'string',
153
+ description: 'Plan name (alternative to ID)',
154
+ },
155
+ project_id: {
156
+ type: 'number',
157
+ description: 'Project ID (required if using name, uses active project if not provided)',
158
+ },
159
+ },
160
+ required: [],
161
+ },
162
+ execute: async (input) => {
163
+ try {
164
+ let plan;
165
+ if (input.plan_id) {
166
+ plan = await ctx.plans.getWithWorkItem(input.plan_id);
167
+ }
168
+ else if (input.name) {
169
+ const projectId = input.project_id ?? ctx.currentProjectId;
170
+ if (!projectId) {
171
+ return createErrorResult('No project specified and no active project. Provide plan_id or project_id.');
172
+ }
173
+ const basePlan = await ctx.plans.getByName(projectId, input.name);
174
+ if (basePlan) {
175
+ plan = await ctx.plans.getWithWorkItem(basePlan.id);
176
+ }
177
+ }
178
+ else {
179
+ return createErrorResult('Either plan_id or name is required');
180
+ }
181
+ if (!plan) {
182
+ return createSuccessResult({
183
+ success: true,
184
+ plan: null,
185
+ message: 'Plan not found',
186
+ });
187
+ }
188
+ return createSuccessResult({
189
+ success: true,
190
+ plan: {
191
+ id: plan.id,
192
+ name: plan.name,
193
+ content: plan.content,
194
+ status: plan.status,
195
+ workItemId: plan.workItemId,
196
+ workItem: plan.workItem
197
+ ? {
198
+ id: plan.workItem.id,
199
+ itemId: plan.workItem.itemId,
200
+ title: plan.workItem.title,
201
+ status: plan.workItem.status,
202
+ }
203
+ : null,
204
+ createdAt: plan.createdAt.toISOString(),
205
+ updatedAt: plan.updatedAt.toISOString(),
206
+ },
207
+ });
208
+ }
209
+ catch (error) {
210
+ return createErrorResult(`Failed to get plan: ${error instanceof Error ? error.message : String(error)}`);
211
+ }
212
+ },
213
+ });
214
+ // ---------------------------------------------------------------------------
215
+ // plan_list
216
+ // ---------------------------------------------------------------------------
217
+ const planListTool = defineTool({
218
+ name: 'plan_list',
219
+ description: 'List plans for a project. Supports filtering by status and work item. Returns plan summaries without full content.',
220
+ inputSchema: {
221
+ type: 'object',
222
+ properties: {
223
+ project_id: {
224
+ type: 'number',
225
+ description: 'Project ID (uses active project if not provided)',
226
+ },
227
+ status: {
228
+ type: 'string',
229
+ enum: ['draft', 'approved', 'in_progress', 'completed', 'abandoned', 'all'],
230
+ description: 'Filter by status (default: all)',
231
+ },
232
+ work_item_id: {
233
+ type: 'number',
234
+ description: 'Filter by linked work item',
235
+ },
236
+ limit: {
237
+ type: 'number',
238
+ description: 'Maximum plans to return (default: 20)',
239
+ },
240
+ order_by: {
241
+ type: 'string',
242
+ enum: ['updated_desc', 'updated_asc', 'created_desc', 'name_asc'],
243
+ description: 'Sort order (default: updated_desc)',
244
+ },
245
+ },
246
+ required: [],
247
+ },
248
+ execute: async (input) => {
249
+ try {
250
+ const projectId = input.project_id ?? ctx.currentProjectId;
251
+ if (!projectId) {
252
+ return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
253
+ }
254
+ const options = {
255
+ limit: input.limit ?? 20,
256
+ order_by: input.order_by ?? 'updated_desc',
257
+ };
258
+ if (input.status && input.status !== 'all') {
259
+ options.status = input.status;
260
+ }
261
+ if (input.work_item_id !== undefined) {
262
+ options.work_item_id = input.work_item_id;
263
+ }
264
+ const plans = await ctx.plans.list(projectId, options);
265
+ const counts = await ctx.plans.countByStatus(projectId);
266
+ const planSummaries = plans.map((plan) => ({
267
+ id: plan.id,
268
+ name: plan.name,
269
+ status: plan.status,
270
+ workItemId: plan.workItemId,
271
+ workItemTitle: plan.workItemTitle,
272
+ workItemItemId: plan.workItemItemId,
273
+ updatedAt: plan.updatedAt.toISOString(),
274
+ }));
275
+ return createSuccessResult({
276
+ success: true,
277
+ plans: planSummaries,
278
+ count: planSummaries.length,
279
+ statusCounts: counts,
280
+ projectId,
281
+ });
282
+ }
283
+ catch (error) {
284
+ return createErrorResult(`Failed to list plans: ${error instanceof Error ? error.message : String(error)}`);
285
+ }
286
+ },
287
+ });
288
+ // ---------------------------------------------------------------------------
289
+ // plan_delete
290
+ // ---------------------------------------------------------------------------
291
+ const planDeleteTool = defineTool({
292
+ name: 'plan_delete',
293
+ description: 'Delete a plan by ID. This action cannot be undone.',
294
+ inputSchema: {
295
+ type: 'object',
296
+ properties: {
297
+ plan_id: {
298
+ type: 'number',
299
+ description: 'ID of the plan to delete',
300
+ },
301
+ },
302
+ required: ['plan_id'],
303
+ },
304
+ execute: async (input) => {
305
+ try {
306
+ const plan = await ctx.plans.getById(input.plan_id);
307
+ if (!plan) {
308
+ return createErrorResult(`Plan with ID ${String(input.plan_id)} not found`);
309
+ }
310
+ const deleted = await ctx.plans.delete(input.plan_id);
311
+ if (!deleted) {
312
+ return createErrorResult(`Failed to delete plan "${plan.name}"`);
313
+ }
314
+ return createSuccessResult({
315
+ success: true,
316
+ message: `Plan "${plan.name}" deleted`,
317
+ });
318
+ }
319
+ catch (error) {
320
+ return createErrorResult(`Failed to delete plan: ${error instanceof Error ? error.message : String(error)}`);
321
+ }
322
+ },
323
+ });
324
+ return [planCreateTool, planUpdateTool, planGetTool, planListTool, planDeleteTool];
325
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Project Tools — CRUD operations for projects.
3
+ *
4
+ * 4 tools: project_get, project_create, project_update, project_list
5
+ *
6
+ * Ported from CLI's src/tools/project-db.ts. Key adaptations:
7
+ * - getActiveProject()?.id → ctx.currentProjectId
8
+ * - setCurrentProject() → ctx.currentProjectId = id + hooks?.onProjectResolved()
9
+ * - awardFirstProject() → hooks?.onFirstProject()
10
+ * - process.cwd() → config.cwd ?? process.cwd()
11
+ * - All repo calls are properly await-ed
12
+ */
13
+ import type { PlatformToolsConfig } from '../context.js';
14
+ export declare function createProjectTools(config: PlatformToolsConfig): (import("@compilr-dev/agents").Tool<{
15
+ project_id?: number;
16
+ name?: string;
17
+ path?: string;
18
+ }> | import("@compilr-dev/agents").Tool<{
19
+ name: string;
20
+ display_name: string;
21
+ description?: string;
22
+ path?: string;
23
+ type?: string;
24
+ workflow_mode?: string;
25
+ language?: string;
26
+ framework?: string;
27
+ package_manager?: string;
28
+ }> | import("@compilr-dev/agents").Tool<{
29
+ project_id?: number;
30
+ status?: string;
31
+ description?: string;
32
+ workflow_mode?: string;
33
+ lifecycle_state?: string;
34
+ current_item_id?: string;
35
+ git_remote?: string;
36
+ git_branch?: string;
37
+ last_context?: Record<string, unknown>;
38
+ }> | import("@compilr-dev/agents").Tool<{
39
+ status?: string;
40
+ limit?: number;
41
+ }>)[];