@hubblecommerce/overmind-core 0.1.7 → 0.1.9

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.
@@ -49,5 +49,9 @@ export declare const config: {
49
49
  suggestionsSpaceId: string;
50
50
  suggestionsParentId: string;
51
51
  };
52
+ intake: {
53
+ url: string;
54
+ internalToken: string;
55
+ };
52
56
  };
53
57
  //# sourceMappingURL=app.config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.config.d.ts","sourceRoot":"","sources":["../../../src/config/app.config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDlB,CAAC"}
1
+ {"version":3,"file":"app.config.d.ts","sourceRoot":"","sources":["../../../src/config/app.config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDlB,CAAC"}
@@ -49,4 +49,8 @@ export const config = {
49
49
  suggestionsSpaceId: process.env.CONFLUENCE_SUGGESTIONS_SPACE_ID || '',
50
50
  suggestionsParentId: process.env.CONFLUENCE_SUGGESTIONS_PARENT_ID || '',
51
51
  },
52
+ intake: {
53
+ url: process.env.INTAKE_URL || 'http://localhost:3002',
54
+ internalToken: process.env.INTAKE_INTERNAL_TOKEN || '',
55
+ },
52
56
  };
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ export declare function createSearchRepositoryTool(): import("@langchain/core/tools").DynamicStructuredTool<z.ZodObject<{
3
+ projectId: z.ZodString;
4
+ pattern: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ projectId: string;
7
+ pattern: string;
8
+ }, {
9
+ projectId: string;
10
+ pattern: string;
11
+ }>, {
12
+ projectId: string;
13
+ pattern: string;
14
+ }, {
15
+ projectId: string;
16
+ pattern: string;
17
+ }, string, unknown, "search_repository_code">;
18
+ export declare function createGetRepositoryFileTool(): import("@langchain/core/tools").DynamicStructuredTool<z.ZodObject<{
19
+ projectId: z.ZodString;
20
+ filePath: z.ZodString;
21
+ }, "strip", z.ZodTypeAny, {
22
+ projectId: string;
23
+ filePath: string;
24
+ }, {
25
+ projectId: string;
26
+ filePath: string;
27
+ }>, {
28
+ projectId: string;
29
+ filePath: string;
30
+ }, {
31
+ projectId: string;
32
+ filePath: string;
33
+ }, string, unknown, "get_repository_file">;
34
+ export declare function createGetRepositoryStructureTool(): import("@langchain/core/tools").DynamicStructuredTool<z.ZodObject<{
35
+ projectId: z.ZodString;
36
+ path: z.ZodOptional<z.ZodString>;
37
+ depth: z.ZodOptional<z.ZodNumber>;
38
+ }, "strip", z.ZodTypeAny, {
39
+ projectId: string;
40
+ path?: string | undefined;
41
+ depth?: number | undefined;
42
+ }, {
43
+ projectId: string;
44
+ path?: string | undefined;
45
+ depth?: number | undefined;
46
+ }>, {
47
+ projectId: string;
48
+ path?: string | undefined;
49
+ depth?: number | undefined;
50
+ }, {
51
+ projectId: string;
52
+ path?: string | undefined;
53
+ depth?: number | undefined;
54
+ }, string, unknown, "get_repository_structure">;
55
+ //# sourceMappingURL=repository.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/repository.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAsBxB,wBAAgB,0BAA0B;;;;;;;;;;;;;;;8CAyBzC;AAED,wBAAgB,2BAA2B;;;;;;;;;;;;;;;2CAyB1C;AAED,wBAAgB,gCAAgC;;;;;;;;;;;;;;;;;;;;gDA6B/C"}
@@ -0,0 +1,94 @@
1
+ import { tool } from '@langchain/core/tools';
2
+ import { z } from 'zod';
3
+ import { config } from '../config/app.config.js';
4
+ async function intakeRequest(endpoint, body) {
5
+ const response = await fetch(`${config.intake.url}/repository${endpoint}`, {
6
+ method: 'POST',
7
+ headers: {
8
+ 'Content-Type': 'application/json',
9
+ 'Authorization': `Bearer ${config.intake.internalToken}`,
10
+ },
11
+ body: JSON.stringify(body),
12
+ });
13
+ if (!response.ok) {
14
+ const error = await response.text();
15
+ throw new Error(`Intake API error: ${response.status} ${error}`);
16
+ }
17
+ return response.json();
18
+ }
19
+ export function createSearchRepositoryTool() {
20
+ return tool(async ({ projectId, pattern }) => {
21
+ try {
22
+ const result = await intakeRequest('/search', { projectId, pattern });
23
+ return JSON.stringify({
24
+ output: result.output,
25
+ totalMatches: result.totalMatches,
26
+ truncated: result.truncated,
27
+ });
28
+ }
29
+ catch (error) {
30
+ return JSON.stringify({ error: true, message: String(error) });
31
+ }
32
+ }, {
33
+ name: 'search_repository_code',
34
+ description: `Search a GitLab repository's code for a pattern. Returns matching lines grouped by file path with surrounding context.
35
+ Use specific patterns to get focused results — broad patterns may be truncated.
36
+ The 'truncated' flag indicates the query was too broad; narrow the pattern and retry.`,
37
+ schema: z.object({
38
+ projectId: z.string().describe('GitLab project path (e.g. "group/project")'),
39
+ pattern: z.string().describe('Search pattern (JavaScript regex syntax)'),
40
+ }),
41
+ });
42
+ }
43
+ export function createGetRepositoryFileTool() {
44
+ return tool(async ({ projectId, filePath }) => {
45
+ try {
46
+ const result = await intakeRequest('/file', { projectId, filePath });
47
+ return JSON.stringify({
48
+ content: result.content,
49
+ totalChars: result.totalChars,
50
+ truncated: result.truncated,
51
+ });
52
+ }
53
+ catch (error) {
54
+ return JSON.stringify({ error: true, message: String(error) });
55
+ }
56
+ }, {
57
+ name: 'get_repository_file',
58
+ description: `Get the full content of a specific file from a GitLab repository.
59
+ Use after finding relevant files via search_repository_code or get_repository_structure.
60
+ Large files may be truncated.`,
61
+ schema: z.object({
62
+ projectId: z.string().describe('GitLab project path (e.g. "group/project")'),
63
+ filePath: z.string().describe('Path to the file within the repository (e.g. "src/index.ts")'),
64
+ }),
65
+ });
66
+ }
67
+ export function createGetRepositoryStructureTool() {
68
+ return tool(async ({ projectId, path, depth }) => {
69
+ try {
70
+ const result = await intakeRequest('/structure', {
71
+ projectId,
72
+ path,
73
+ depth,
74
+ });
75
+ return JSON.stringify({
76
+ structure: result.structure,
77
+ truncated: result.truncated,
78
+ });
79
+ }
80
+ catch (error) {
81
+ return JSON.stringify({ error: true, message: String(error) });
82
+ }
83
+ }, {
84
+ name: 'get_repository_structure',
85
+ description: `Get the directory structure of a GitLab repository as an indented file tree.
86
+ By default shows top-level only (depth 1). Use 'path' to drill into a subdirectory and 'depth' to control levels.
87
+ Start with depth 1 to orient, then drill deeper into relevant directories.`,
88
+ schema: z.object({
89
+ projectId: z.string().describe('GitLab project path (e.g. "group/project")'),
90
+ path: z.string().optional().describe('Subdirectory to start from (e.g. "src/components")'),
91
+ depth: z.number().optional().describe('How many levels deep to show (default: 1)'),
92
+ }),
93
+ });
94
+ }
@@ -0,0 +1,22 @@
1
+ export interface RepositorySearchResponse {
2
+ projectId: string;
3
+ pattern: string;
4
+ output: string;
5
+ totalMatches: number;
6
+ truncated: boolean;
7
+ }
8
+ export interface RepositoryFileResponse {
9
+ projectId: string;
10
+ filePath: string;
11
+ content: string;
12
+ totalChars: number;
13
+ truncated: boolean;
14
+ }
15
+ export interface RepositoryStructureResponse {
16
+ projectId: string;
17
+ path: string;
18
+ depth: number;
19
+ structure: string;
20
+ truncated: boolean;
21
+ }
22
+ //# sourceMappingURL=repository.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.types.d.ts","sourceRoot":"","sources":["../../../src/types/repository.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACtB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -17,6 +17,12 @@ interface SearchOptions {
17
17
  ignoreCase?: boolean;
18
18
  contextLines?: number;
19
19
  }
20
+ interface DirectoryStructureOptions {
21
+ /** Subtree root path to start from (e.g. "app/components"). Default: root */
22
+ path?: string;
23
+ /** How many levels deep to return. Default: 1 (top-level only) */
24
+ depth?: number;
25
+ }
20
26
  /**
21
27
  * Search a repomix XML string for lines matching a pattern.
22
28
  * Returns matching lines with optional context.
@@ -28,5 +34,10 @@ export declare function searchRepomix(xml: string, pattern: string, options?: Se
28
34
  * Returns null if the file is not found.
29
35
  */
30
36
  export declare function getRepomixFile(xml: string, filePath: string): string | null;
37
+ /**
38
+ * Extract the directory structure from repomix XML.
39
+ * Returns an indented tree filtered by optional path and depth.
40
+ */
41
+ export declare function getRepomixDirectoryStructure(xml: string, options?: DirectoryStructureOptions): string | null;
31
42
  export {};
32
43
  //# sourceMappingURL=repomix-search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repomix-search.d.ts","sourceRoot":"","sources":["../../../src/utils/repomix-search.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,UAAU,WAAW;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,YAAY;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,UAAU,aAAa;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAmGD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,YAAY,CASjG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ3E"}
1
+ {"version":3,"file":"repomix-search.d.ts","sourceRoot":"","sources":["../../../src/utils/repomix-search.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,UAAU,WAAW;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,YAAY;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,UAAU,aAAa;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,yBAAyB;IAC/B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAmGD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,YAAY,CASjG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ3E;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,yBAAyB,GAAG,MAAM,GAAG,IAAI,CA0D5G"}
@@ -105,3 +105,57 @@ export function getRepomixFile(xml, filePath) {
105
105
  return null;
106
106
  return match[1];
107
107
  }
108
+ /**
109
+ * Extract the directory structure from repomix XML.
110
+ * Returns an indented tree filtered by optional path and depth.
111
+ */
112
+ export function getRepomixDirectoryStructure(xml, options) {
113
+ const match = xml.match(/<directory_structure>\n?([\s\S]*?)\n?<\/directory_structure>/);
114
+ if (!match?.[1])
115
+ return null;
116
+ const fullTree = match[1];
117
+ const lines = fullTree.split('\n');
118
+ const targetPath = options?.path?.replace(/\/$/, '') ?? '';
119
+ const maxDepth = options?.depth ?? 1;
120
+ const getDepth = (line) => {
121
+ const leadingSpaces = line.match(/^( *)/)?.[1].length ?? 0;
122
+ return leadingSpaces / 2;
123
+ };
124
+ // Build full paths from indented tree using a stack
125
+ const pathStack = [];
126
+ if (!targetPath) {
127
+ return lines
128
+ .filter(line => line.trim() && getDepth(line) < maxDepth)
129
+ .join('\n');
130
+ }
131
+ // Find the subtree starting at targetPath
132
+ let inSubtree = false;
133
+ let subtreeBaseDepth = 0;
134
+ const result = [];
135
+ for (const line of lines) {
136
+ const trimmed = line.trim();
137
+ if (!trimmed)
138
+ continue;
139
+ const depth = getDepth(line);
140
+ // Maintain path stack: trim to current depth, then push
141
+ pathStack.length = depth;
142
+ pathStack.push(trimmed.replace(/\/$/, ''));
143
+ const fullPath = pathStack.join('/');
144
+ if (!inSubtree) {
145
+ if (fullPath === targetPath) {
146
+ inSubtree = true;
147
+ subtreeBaseDepth = depth + 1;
148
+ result.push(trimmed);
149
+ }
150
+ continue;
151
+ }
152
+ if (depth < subtreeBaseDepth) {
153
+ break;
154
+ }
155
+ const relativeDepth = depth - subtreeBaseDepth;
156
+ if (relativeDepth < maxDepth) {
157
+ result.push(' '.repeat(relativeDepth) + trimmed);
158
+ }
159
+ }
160
+ return result.length > 0 ? result.join('\n') : null;
161
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { searchRepomix, getRepomixFile } from './repomix-search.js';
2
+ import { searchRepomix, getRepomixFile, getRepomixDirectoryStructure } from './repomix-search.js';
3
3
  const SAMPLE_XML = `<file path="src/index.ts">
4
4
  import { App } from './app';
5
5
 
@@ -25,6 +25,27 @@ export function login(username: string, password: string) {
25
25
  });
26
26
  }
27
27
  </file>`;
28
+ const SAMPLE_XML_WITH_STRUCTURE = `<directory_structure>
29
+ app/
30
+ components/
31
+ Header.vue
32
+ Footer.vue
33
+ pages/
34
+ index.vue
35
+ about.vue
36
+ middleware/
37
+ auth.ts
38
+ config/
39
+ app.config.ts
40
+ package.json
41
+ README.md
42
+ </directory_structure>
43
+
44
+ <files>
45
+ <file path="app/components/Header.vue">
46
+ <template><header></header></template>
47
+ </file>
48
+ </files>`;
28
49
  describe('searchRepomix', () => {
29
50
  it('returns matching lines with file context', () => {
30
51
  const results = searchRepomix(SAMPLE_XML, 'auth');
@@ -86,3 +107,36 @@ describe('getRepomixFile', () => {
86
107
  expect(content).toContain("import { App }");
87
108
  });
88
109
  });
110
+ describe('getRepomixDirectoryStructure', () => {
111
+ it('returns top-level entries by default (depth 1)', () => {
112
+ const result = getRepomixDirectoryStructure(SAMPLE_XML_WITH_STRUCTURE);
113
+ expect(result).toContain('app/');
114
+ expect(result).toContain('config/');
115
+ expect(result).toContain('package.json');
116
+ expect(result).not.toContain('Header.vue');
117
+ expect(result).not.toContain('pages/');
118
+ });
119
+ it('returns deeper entries when depth is increased', () => {
120
+ const result = getRepomixDirectoryStructure(SAMPLE_XML_WITH_STRUCTURE, { depth: 2 });
121
+ expect(result).toContain('app/');
122
+ expect(result).toContain('components/');
123
+ expect(result).toContain('pages/');
124
+ expect(result).not.toContain('Header.vue');
125
+ });
126
+ it('returns a subtree when path is specified', () => {
127
+ const result = getRepomixDirectoryStructure(SAMPLE_XML_WITH_STRUCTURE, { path: 'app/components', depth: 1 });
128
+ expect(result).toContain('components/');
129
+ expect(result).toContain('Header.vue');
130
+ expect(result).toContain('Footer.vue');
131
+ expect(result).not.toContain('pages/');
132
+ expect(result).not.toContain('config/');
133
+ });
134
+ it('returns null when directory_structure section is missing', () => {
135
+ const result = getRepomixDirectoryStructure('<file path="test.ts">code</file>');
136
+ expect(result).toBeNull();
137
+ });
138
+ it('returns null when path does not exist in tree', () => {
139
+ const result = getRepomixDirectoryStructure(SAMPLE_XML_WITH_STRUCTURE, { path: 'nonexistent' });
140
+ expect(result).toBeNull();
141
+ });
142
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubblecommerce/overmind-core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "description": "Shared infrastructure package for the Overmind AI agent system",
6
6
  "main": "./dist/src/index.js",
@@ -53,6 +53,14 @@
53
53
  "./utils/repomix-search": {
54
54
  "types": "./dist/src/utils/repomix-search.d.ts",
55
55
  "default": "./dist/src/utils/repomix-search.js"
56
+ },
57
+ "./tools/repository": {
58
+ "types": "./dist/src/tools/repository.tool.d.ts",
59
+ "default": "./dist/src/tools/repository.tool.js"
60
+ },
61
+ "./types/repository": {
62
+ "types": "./dist/src/types/repository.types.d.ts",
63
+ "default": "./dist/src/types/repository.types.js"
56
64
  }
57
65
  },
58
66
  "files": [