gaunt-sloth-assistant 0.7.2 → 0.7.3

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 (46) hide show
  1. package/.gsloth.config_.json +2 -2
  2. package/.gsloth.config_claude.mjs +18 -0
  3. package/.gsloth.config_vertex.json +6 -0
  4. package/README.md +1 -1
  5. package/assets/release-notes/v0_7_3.md +64 -0
  6. package/dist/builtInToolsConfig.d.ts +6 -0
  7. package/dist/builtInToolsConfig.js +73 -0
  8. package/dist/builtInToolsConfig.js.map +1 -0
  9. package/dist/commands/prCommand.js +14 -1
  10. package/dist/commands/prCommand.js.map +1 -1
  11. package/dist/config.d.ts +18 -7
  12. package/dist/config.js +1 -41
  13. package/dist/config.js.map +1 -1
  14. package/dist/core/Invocation.js +5 -2
  15. package/dist/core/Invocation.js.map +1 -1
  16. package/dist/helpers/jira/jiraClient.d.ts +4 -0
  17. package/dist/helpers/jira/jiraClient.js +75 -0
  18. package/dist/helpers/jira/jiraClient.js.map +1 -0
  19. package/dist/helpers/jira/jiraLogWork.d.ts +2 -0
  20. package/dist/helpers/jira/jiraLogWork.js +53 -0
  21. package/dist/helpers/jira/jiraLogWork.js.map +1 -0
  22. package/dist/providers/jiraIssueProvider.js +9 -52
  23. package/dist/providers/jiraIssueProvider.js.map +1 -1
  24. package/dist/tools/gthJiraLogWorkTool.d.ts +3 -0
  25. package/dist/tools/gthJiraLogWorkTool.js +37 -0
  26. package/dist/tools/gthJiraLogWorkTool.js.map +1 -0
  27. package/dist/tools/gthStatusUpdateTool.d.ts +2 -0
  28. package/dist/tools/gthStatusUpdateTool.js +16 -0
  29. package/dist/tools/gthStatusUpdateTool.js.map +1 -0
  30. package/docs/DEVELOPMENT.md +26 -0
  31. package/package.json +1 -1
  32. package/src/builtInToolsConfig.ts +99 -0
  33. package/src/commands/prCommand.ts +28 -3
  34. package/src/config.ts +20 -62
  35. package/src/core/Invocation.ts +7 -2
  36. package/src/helpers/jira/jiraClient.ts +99 -0
  37. package/src/helpers/jira/jiraLogWork.ts +81 -0
  38. package/src/providers/jiraIssueProvider.ts +13 -70
  39. package/src/tools/gthJiraLogWorkTool.ts +51 -0
  40. package/src/tools/gthStatusUpdateTool.ts +18 -0
  41. package/vitest-it.config.ts +1 -1
  42. package/vitest.config.ts +1 -0
  43. package/dist/tools/statusUpdate.d.ts +0 -1
  44. package/dist/tools/statusUpdate.js +0 -12
  45. package/dist/tools/statusUpdate.js.map +0 -1
  46. package/src/tools/statusUpdate.ts +0 -15
@@ -0,0 +1,81 @@
1
+ import type { JiraConfig } from '#src/providers/types.js';
2
+ import { getJiraCredentials, jiraRequest } from './jiraClient.js';
3
+ import { displayError, displaySuccess } from '#src/consoleUtils.js';
4
+
5
+ interface WorklogRequestBody {
6
+ comment: {
7
+ content: Array<{
8
+ content: Array<{
9
+ text: string;
10
+ type: 'text';
11
+ }>;
12
+ type: 'paragraph';
13
+ }>;
14
+ type: 'doc';
15
+ version: 1;
16
+ };
17
+ started: string;
18
+ timeSpentSeconds: number;
19
+ }
20
+
21
+ export default async function jiraLogWork(
22
+ config: Partial<JiraConfig> | null,
23
+ jiraId: string,
24
+ timeInSeconds: number,
25
+ comment: string = 'Work logged',
26
+ startedAt: Date = new Date()
27
+ ): Promise<string> {
28
+ try {
29
+ // Use provided config or empty config (will use environment variables)
30
+ const credentials = getJiraCredentials(config);
31
+
32
+ const bodyData: WorklogRequestBody = {
33
+ comment: {
34
+ content: [
35
+ {
36
+ content: [
37
+ {
38
+ text: comment,
39
+ type: 'text',
40
+ },
41
+ ],
42
+ type: 'paragraph',
43
+ },
44
+ ],
45
+ type: 'doc',
46
+ version: 1,
47
+ },
48
+ started: startedAt.toISOString().replace('Z', '+0000'),
49
+ timeSpentSeconds: timeInSeconds,
50
+ };
51
+
52
+ /**
53
+ * https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-worklogs/#api-rest-api-3-issue-issueidorkey-worklog-post
54
+ * Needs:
55
+ * Classic RECOMMENDED:write:jira-work
56
+ *
57
+ * OR
58
+ *
59
+ * write:issue-worklog:jira, write:issue-worklog.property:jira, read:avatar:jira, read:group:jira,
60
+ * read:issue-worklog:jira, read:project-role:jira, read:user:jira, read:issue-worklog.property:jira
61
+ */
62
+ await jiraRequest(credentials, `/rest/api/3/issue/${jiraId}/worklog`, {
63
+ method: 'POST',
64
+ body: JSON.stringify(bodyData),
65
+ });
66
+
67
+ const hours = Math.floor(timeInSeconds / 3600);
68
+ const minutes = Math.floor((timeInSeconds % 3600) / 60);
69
+ const timeStr = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
70
+
71
+ const successMessage = `✅ Logged ${timeStr} to ${jiraId}`;
72
+ displaySuccess(successMessage);
73
+ return successMessage;
74
+ } catch (error) {
75
+ const errorMessage = `Failed to log work to Jira: ${
76
+ error instanceof Error ? error.message : String(error)
77
+ }`;
78
+ displayError(errorMessage);
79
+ return errorMessage;
80
+ }
81
+ }
@@ -1,7 +1,6 @@
1
1
  import { display, displayError, displayWarning } from '#src/consoleUtils.js';
2
- import { env } from '#src/systemUtils.js';
3
2
  import type { JiraConfig } from './types.js';
4
- import { ProgressIndicator } from '#src/utils.js';
3
+ import { getJiraCredentials, jiraRequest } from '#src/helpers/jira/jiraClient.js';
5
4
 
6
5
  interface JiraIssueResponse {
7
6
  fields: {
@@ -35,40 +34,10 @@ export async function get(
35
34
  return null;
36
35
  }
37
36
 
38
- // Get username from environment variable or config
39
- const username = env.JIRA_USERNAME || config.username;
40
- if (!username) {
41
- throw new Error(
42
- 'Missing JIRA username. The username can be defined as JIRA_USERNAME environment variable or as "username" in config.'
43
- );
44
- }
45
-
46
- // Get token from environment variable or config
47
- const token = env.JIRA_API_PAT_TOKEN || config.token;
48
- if (!token) {
49
- throw new Error(
50
- 'Missing JIRA PAT token. The token can be defined as JIRA_API_PAT_TOKEN environment variable or as "token" in config.'
51
- );
52
- }
53
-
54
- // Get cloud ID from environment variable or config
55
- const cloudId = env.JIRA_CLOUD_ID || config.cloudId;
56
- if (!cloudId) {
57
- throw new Error(
58
- 'Missing JIRA Cloud ID. The Cloud ID can be defined as JIRA_CLOUD_ID environment variable or as "cloudId" in config.'
59
- );
60
- }
37
+ const credentials = getJiraCredentials(config);
61
38
 
62
39
  try {
63
- const issue = await getJiraIssue(
64
- {
65
- ...config,
66
- username,
67
- token,
68
- cloudId,
69
- },
70
- issueId
71
- );
40
+ const issue = await getJiraIssue(credentials, issueId);
72
41
  if (!issue) {
73
42
  return null;
74
43
  }
@@ -95,52 +64,26 @@ export async function get(
95
64
  * @param jiraKey Jira issue ID
96
65
  * @returns Jira issue response
97
66
  */
98
- async function getJiraIssue(config: JiraConfig, jiraKey: string): Promise<JiraIssueResponse> {
67
+ async function getJiraIssue(
68
+ credentials: { username: string; token: string; cloudId: string; displayUrl?: string },
69
+ jiraKey: string
70
+ ): Promise<JiraIssueResponse> {
99
71
  // Jira Cloud ID can be found by authenticated user at https://company.atlassian.net/_edge/tenant_info
100
72
 
101
73
  // According to doc https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-get permissions to read this resource:
102
74
  // https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-issueidorkey-get
103
75
  // either Classic (RECOMMENDED) read:jira-work
104
- // or Granular read:issue-meta:jira, read:issue-security-level:jira, read:issue.vote:jira, read:issue.changelog:jira, read:avatar:jira, read:issue:jira, read:status:jira, read:user:jira, read:field-configuration:jira
105
- const apiUrl = `https://api.atlassian.com/ex/jira/${config.cloudId}/rest/api/2/issue/${jiraKey}`;
106
- if (config.displayUrl) {
107
- display(`Loading Jira issue ${config.displayUrl}${jiraKey}`);
76
+ // or Granular read:issue-meta:jira, read:issue-security-level:jira, read:issue.vote:jira, read:issue.changelog:jira,
77
+ // read:avatar:jira, read:issue:jira, read:status:jira, read:user:jira, read:field-configuration:jira
78
+
79
+ if (credentials.displayUrl) {
80
+ display(`Loading Jira issue ${credentials.displayUrl}${jiraKey}`);
108
81
  }
109
82
 
110
83
  // This filter will be necessary for V3: `&expand=renderedFields` to convert ADF to HTML
111
84
  const filters = '?fields=summary,description'; // Limit JSON to summary and description
112
85
 
113
- // Encode credentials for Basic Authentication header
114
- const credentials = `${config.username}:${config.token}`;
115
- const encodedCredentials = Buffer.from(credentials).toString('base64');
116
- const authHeader = `Basic ${encodedCredentials}`;
117
-
118
- // Define request headers
119
- const headers = {
120
- Authorization: authHeader,
121
- Accept: 'application/json; charset=utf-8',
122
- 'Accept-Language': 'en-US,en;q=0.9', // Prevents errors in other languages
123
- };
124
-
125
- const progressIndicator = new ProgressIndicator(
126
- `Retrieving jira from api ${apiUrl.replace(/^https?:\/\//, '')}`
127
- );
128
- const response = await fetch(apiUrl + filters, {
86
+ return jiraRequest<JiraIssueResponse>(credentials, `/rest/api/2/issue/${jiraKey}${filters}`, {
129
87
  method: 'GET',
130
- headers: headers,
131
88
  });
132
- progressIndicator.stop();
133
-
134
- if (!response?.ok) {
135
- try {
136
- const errorData = await response.json();
137
- throw new Error(
138
- `Failed to fetch Jira issue: ${response.statusText} - ${JSON.stringify(errorData)}`
139
- );
140
- } catch (_e) {
141
- throw new Error(`Failed to fetch Jira issue: ${response?.statusText}`);
142
- }
143
- }
144
-
145
- return response.json();
146
89
  }
@@ -0,0 +1,51 @@
1
+ import { type StructuredToolInterface, tool } from '@langchain/core/tools';
2
+ import { z } from 'zod';
3
+ import { displayWarning } from '#src/consoleUtils.js';
4
+ import jiraLogWork from '#src/helpers/jira/jiraLogWork.js';
5
+ import type { JiraConfig } from '#src/providers/types.js';
6
+ import { SlothConfig } from '#src/config.js';
7
+
8
+ // Define the input schema for the tool
9
+ const gthJiraLogWorkSchema = z.object({
10
+ jiraId: z.string().describe('The Jira issue ID (e.g., "PROJ-123")'),
11
+ timeInSeconds: z.number().describe('Time spent in seconds'),
12
+ comment: z.string().optional().describe('Work log comment'),
13
+ startedAt: z.string().optional().describe('ISO 8601 date string for when work started'),
14
+ });
15
+
16
+ const toolDefinition = {
17
+ name: 'gth_jira_log_work',
18
+ description: `Gaunt Sloth Jira Log Work Tool. Log work time to a Jira issue. Requires Jira configuration with credentials.
19
+ Example: gth_jira_log_work({ jiraId: "PROJ-123", timeInSeconds: 3600, comment: "Implemented feature X" })`,
20
+ schema: gthJiraLogWorkSchema,
21
+ };
22
+
23
+ function getToolImpl(config?: Partial<JiraConfig>): StructuredToolInterface {
24
+ let toolImpl = async ({
25
+ jiraId,
26
+ timeInSeconds,
27
+ comment = 'Work logged',
28
+ startedAt,
29
+ }: z.infer<typeof gthJiraLogWorkSchema>): Promise<string> => {
30
+ const jiraConfig = config || {};
31
+
32
+ const startDate = startedAt ? new Date(startedAt) : new Date();
33
+
34
+ return await jiraLogWork(jiraConfig, jiraId, timeInSeconds, comment, startDate);
35
+ };
36
+ return tool(toolImpl, toolDefinition);
37
+ }
38
+
39
+ // Export a default instance that uses environment variables
40
+ export function get(config: SlothConfig) {
41
+ if (!config.prebuiltToolsConfig?.jira && config.requirementsProviderConfig?.jira) {
42
+ displayWarning(
43
+ 'config.prebuiltToolsConfig.jira is not defined. Using config.requirementsProviderConfig.jira.'
44
+ );
45
+ }
46
+ let jiraConfig = config.prebuiltToolsConfig?.jira || config.requirementsProviderConfig?.jira;
47
+ if (!jiraConfig) {
48
+ throw new Error('gth_jira_log_work is added to preBuiltTools, but no Jira config is provided.');
49
+ }
50
+ return getToolImpl(jiraConfig);
51
+ }
@@ -0,0 +1,18 @@
1
+ import { tool } from '@langchain/core/tools';
2
+ import { z } from 'zod';
3
+ import chalk from 'chalk';
4
+ import { display } from '#src/consoleUtils.js';
5
+ import { SlothConfig } from '#src/config.js';
6
+
7
+ const toolDefinition = {
8
+ name: 'gth_status_update',
9
+ description: `Gaunt Sloth Status Update Tool. Use this tool to update status in the console. Example: gth_status_update "Working on something important", be brief, feel free to use emojis. Update after using tools.`,
10
+ schema: z.string(),
11
+ };
12
+ const toolImpl = (s: string): void => {
13
+ display(chalk.grey(s));
14
+ };
15
+
16
+ export function get(_: SlothConfig) {
17
+ return tool(toolImpl, toolDefinition);
18
+ }
@@ -5,7 +5,7 @@ export default defineConfig({
5
5
  include: ['integration-tests/**/*.it.ts'],
6
6
  environment: 'node',
7
7
  globals: true,
8
- testTimeout: 1000 * 60 * 3,
8
+ testTimeout: 1000 * 60 * 5,
9
9
  maxWorkers: 1,
10
10
  fileParallelism: false,
11
11
  },
package/vitest.config.ts CHANGED
@@ -9,5 +9,6 @@ export default defineConfig({
9
9
  reporter: ['text', 'json', 'html'],
10
10
  },
11
11
  globals: true,
12
+ testTimeout: 10000,
12
13
  },
13
14
  });
@@ -1 +0,0 @@
1
- export declare const statusUpdate: import("@langchain/core/tools").DynamicTool<void>;
@@ -1,12 +0,0 @@
1
- import { tool } from '@langchain/core/tools';
2
- import { z } from 'zod';
3
- import chalk from 'chalk';
4
- import { display } from '#src/consoleUtils.js';
5
- export const statusUpdate = tool((s) => {
6
- display(chalk.grey(s));
7
- }, {
8
- name: 'status_update',
9
- description: `Status Update Tool. Use this tool to update status in the console. Example: status_update "Working on something important", be brief, feel free to use emojis. Update after using tools.`,
10
- schema: z.string(),
11
- });
12
- //# sourceMappingURL=statusUpdate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"statusUpdate.js","sourceRoot":"","sources":["../../src/tools/statusUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAC9B,CAAC,CAAS,EAAQ,EAAE;IAClB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACzB,CAAC,EACD;IACE,IAAI,EAAE,eAAe;IACrB,WAAW,EAAE,0LAA0L;IACvM,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;CACnB,CACF,CAAC"}
@@ -1,15 +0,0 @@
1
- import { tool } from '@langchain/core/tools';
2
- import { z } from 'zod';
3
- import chalk from 'chalk';
4
- import { display } from '#src/consoleUtils.js';
5
-
6
- export const statusUpdate = tool(
7
- (s: string): void => {
8
- display(chalk.grey(s));
9
- },
10
- {
11
- name: 'status_update',
12
- description: `Status Update Tool. Use this tool to update status in the console. Example: status_update "Working on something important", be brief, feel free to use emojis. Update after using tools.`,
13
- schema: z.string(),
14
- }
15
- );