@skroyc/librarian 0.1.0 → 0.2.1

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 (76) hide show
  1. package/README.md +4 -16
  2. package/dist/agents/context-schema.d.ts +1 -1
  3. package/dist/agents/context-schema.d.ts.map +1 -1
  4. package/dist/agents/context-schema.js +5 -2
  5. package/dist/agents/context-schema.js.map +1 -1
  6. package/dist/agents/react-agent.d.ts.map +1 -1
  7. package/dist/agents/react-agent.js +63 -170
  8. package/dist/agents/react-agent.js.map +1 -1
  9. package/dist/agents/tool-runtime.d.ts.map +1 -1
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +53 -49
  13. package/dist/cli.js.map +1 -1
  14. package/dist/config.d.ts +1 -1
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +115 -69
  17. package/dist/config.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +246 -150
  21. package/dist/index.js.map +1 -1
  22. package/dist/tools/file-finding.tool.d.ts +1 -1
  23. package/dist/tools/file-finding.tool.d.ts.map +1 -1
  24. package/dist/tools/file-finding.tool.js +70 -130
  25. package/dist/tools/file-finding.tool.js.map +1 -1
  26. package/dist/tools/file-listing.tool.d.ts +7 -1
  27. package/dist/tools/file-listing.tool.d.ts.map +1 -1
  28. package/dist/tools/file-listing.tool.js +96 -80
  29. package/dist/tools/file-listing.tool.js.map +1 -1
  30. package/dist/tools/file-reading.tool.d.ts +4 -1
  31. package/dist/tools/file-reading.tool.d.ts.map +1 -1
  32. package/dist/tools/file-reading.tool.js +107 -45
  33. package/dist/tools/file-reading.tool.js.map +1 -1
  34. package/dist/tools/grep-content.tool.d.ts +13 -1
  35. package/dist/tools/grep-content.tool.d.ts.map +1 -1
  36. package/dist/tools/grep-content.tool.js +186 -144
  37. package/dist/tools/grep-content.tool.js.map +1 -1
  38. package/dist/utils/error-utils.d.ts +9 -0
  39. package/dist/utils/error-utils.d.ts.map +1 -0
  40. package/dist/utils/error-utils.js +61 -0
  41. package/dist/utils/error-utils.js.map +1 -0
  42. package/dist/utils/file-utils.d.ts +1 -0
  43. package/dist/utils/file-utils.d.ts.map +1 -1
  44. package/dist/utils/file-utils.js +81 -9
  45. package/dist/utils/file-utils.js.map +1 -1
  46. package/dist/utils/format-utils.d.ts +25 -0
  47. package/dist/utils/format-utils.d.ts.map +1 -0
  48. package/dist/utils/format-utils.js +111 -0
  49. package/dist/utils/format-utils.js.map +1 -0
  50. package/dist/utils/gitignore-service.d.ts +10 -0
  51. package/dist/utils/gitignore-service.d.ts.map +1 -0
  52. package/dist/utils/gitignore-service.js +91 -0
  53. package/dist/utils/gitignore-service.js.map +1 -0
  54. package/dist/utils/logger.d.ts +2 -2
  55. package/dist/utils/logger.d.ts.map +1 -1
  56. package/dist/utils/logger.js +35 -34
  57. package/dist/utils/logger.js.map +1 -1
  58. package/dist/utils/path-utils.js +3 -3
  59. package/dist/utils/path-utils.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/agents/context-schema.ts +5 -2
  62. package/src/agents/react-agent.ts +694 -784
  63. package/src/agents/tool-runtime.ts +4 -4
  64. package/src/cli.ts +95 -57
  65. package/src/config.ts +192 -90
  66. package/src/index.ts +402 -180
  67. package/src/tools/file-finding.tool.ts +198 -310
  68. package/src/tools/file-listing.tool.ts +245 -202
  69. package/src/tools/file-reading.tool.ts +225 -138
  70. package/src/tools/grep-content.tool.ts +387 -307
  71. package/src/utils/error-utils.ts +95 -0
  72. package/src/utils/file-utils.ts +104 -19
  73. package/src/utils/format-utils.ts +190 -0
  74. package/src/utils/gitignore-service.ts +123 -0
  75. package/src/utils/logger.ts +112 -77
  76. package/src/utils/path-utils.ts +3 -3
package/dist/index.js CHANGED
@@ -1,76 +1,86 @@
1
- import { clone, fetch, checkout } from 'isomorphic-git';
2
- import http from 'isomorphic-git/http/node';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { ReactAgent } from './agents/react-agent.js';
6
- import { logger } from './utils/logger.js';
7
- import os from 'node:os';
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { checkout, clone, fetch } from "isomorphic-git";
5
+ import http from "isomorphic-git/http/node";
6
+ import { ReactAgent } from "./agents/react-agent.js";
7
+ import { logger } from "./utils/logger.js";
8
8
  export class Librarian {
9
9
  config;
10
10
  constructor(config) {
11
11
  const validProviderTypes = [
12
- 'openai',
13
- 'anthropic',
14
- 'google',
15
- 'openai-compatible',
16
- 'anthropic-compatible',
17
- 'claude-code',
18
- 'gemini-cli',
12
+ "openai",
13
+ "anthropic",
14
+ "google",
15
+ "openai-compatible",
16
+ "anthropic-compatible",
17
+ "claude-code",
18
+ "gemini-cli",
19
19
  ];
20
20
  if (!validProviderTypes.includes(config.aiProvider.type)) {
21
21
  throw new Error(`Unsupported AI provider type: ${config.aiProvider.type}`);
22
22
  }
23
23
  this.config = config;
24
- logger.info('LIBRARIAN', 'Initializing librarian', {
24
+ logger.info("LIBRARIAN", "Initializing librarian", {
25
25
  aiProviderType: config.aiProvider.type,
26
26
  model: config.aiProvider.model,
27
- workingDir: config.workingDir.replace(os.homedir(), '~'),
28
- reposPath: config.repos_path ? config.repos_path.replace(os.homedir(), '~') : 'workingDir'
27
+ workingDir: config.workingDir.replace(os.homedir(), "~"),
28
+ reposPath: config.repos_path
29
+ ? config.repos_path.replace(os.homedir(), "~")
30
+ : "workingDir",
29
31
  });
30
32
  }
31
33
  async initialize() {
32
- if (this.config.aiProvider.type === 'claude-code') {
34
+ if (this.config.aiProvider.type === "claude-code") {
33
35
  try {
34
- const { execSync } = await import('node:child_process');
35
- execSync('claude --version', { stdio: 'ignore' });
36
- logger.info('LIBRARIAN', 'Claude CLI verified');
36
+ const { execSync } = await import("node:child_process");
37
+ execSync("claude --version", { stdio: "ignore" });
38
+ logger.info("LIBRARIAN", "Claude CLI verified");
37
39
  }
38
40
  catch {
39
- logger.error('LIBRARIAN', 'Claude CLI not found in PATH', undefined, { type: 'claude-code' });
41
+ logger.error("LIBRARIAN", "Claude CLI not found in PATH", undefined, {
42
+ type: "claude-code",
43
+ });
40
44
  console.error('Error: "claude" CLI not found. Please install it to use the "claude-code" provider.');
41
45
  process.exit(1);
42
46
  }
43
47
  }
44
- if (this.config.aiProvider.type === 'gemini-cli') {
48
+ if (this.config.aiProvider.type === "gemini-cli") {
45
49
  try {
46
- const { execSync } = await import('node:child_process');
47
- execSync('gemini --version', { stdio: 'ignore' });
48
- logger.info('LIBRARIAN', 'Gemini CLI verified');
50
+ const { execSync } = await import("node:child_process");
51
+ execSync("gemini --version", { stdio: "ignore" });
52
+ logger.info("LIBRARIAN", "Gemini CLI verified");
49
53
  }
50
54
  catch {
51
- logger.error('LIBRARIAN', 'Gemini CLI not found in PATH', undefined, { type: 'gemini-cli' });
55
+ logger.error("LIBRARIAN", "Gemini CLI not found in PATH", undefined, {
56
+ type: "gemini-cli",
57
+ });
52
58
  console.error('Error: "gemini" CLI not found. Please install it to use the "gemini-cli" provider.');
53
59
  process.exit(1);
54
60
  }
55
61
  }
56
62
  const workDir = this.config.repos_path || this.config.workingDir;
57
63
  if (fs.existsSync(workDir)) {
58
- logger.debug('LIBRARIAN', 'Working directory already exists', { path: workDir.replace(os.homedir(), '~') });
64
+ logger.debug("LIBRARIAN", "Working directory already exists", {
65
+ path: workDir.replace(os.homedir(), "~"),
66
+ });
59
67
  }
60
68
  else {
61
- logger.info('LIBRARIAN', 'Creating working directory', { path: workDir.replace(os.homedir(), '~') });
69
+ logger.info("LIBRARIAN", "Creating working directory", {
70
+ path: workDir.replace(os.homedir(), "~"),
71
+ });
62
72
  fs.mkdirSync(workDir, { recursive: true });
63
73
  }
64
- logger.info('LIBRARIAN', 'Initialization complete');
74
+ logger.info("LIBRARIAN", "Initialization complete");
65
75
  }
66
76
  resolveTechnology(qualifiedName) {
67
- logger.debug('LIBRARIAN', 'Resolving technology', { qualifiedName });
77
+ logger.debug("LIBRARIAN", "Resolving technology", { qualifiedName });
68
78
  let group;
69
79
  let name;
70
- if (qualifiedName.includes(':')) {
71
- const parts = qualifiedName.split(':');
80
+ if (qualifiedName.includes(":")) {
81
+ const parts = qualifiedName.split(":");
72
82
  group = parts[0];
73
- name = parts[1] || '';
83
+ name = parts[1] || "";
74
84
  }
75
85
  else {
76
86
  name = qualifiedName;
@@ -79,8 +89,17 @@ export class Librarian {
79
89
  const groupTechs = this.config.technologies[group];
80
90
  const tech = groupTechs ? groupTechs[name] : undefined;
81
91
  if (tech) {
82
- const result = { name, group, repo: tech.repo, branch: tech.branch || 'main' };
83
- logger.debug('LIBRARIAN', 'Technology resolved with explicit group', { name, group, repoHost: tech.repo.split('/')[2] || 'unknown' });
92
+ const result = {
93
+ name,
94
+ group,
95
+ repo: tech.repo,
96
+ branch: tech.branch || "main",
97
+ };
98
+ logger.debug("LIBRARIAN", "Technology resolved with explicit group", {
99
+ name,
100
+ group,
101
+ repoHost: tech.repo.split("/")[2] || "unknown",
102
+ });
84
103
  return result;
85
104
  }
86
105
  }
@@ -88,19 +107,32 @@ export class Librarian {
88
107
  for (const [groupName, techs] of Object.entries(this.config.technologies)) {
89
108
  const tech = techs[name];
90
109
  if (tech) {
91
- const result = { name, group: groupName, repo: tech.repo, branch: tech.branch || 'main' };
92
- logger.debug('LIBRARIAN', 'Technology resolved by search', { name, group: groupName, repoHost: tech.repo.split('/')[2] || 'unknown' });
110
+ const result = {
111
+ name,
112
+ group: groupName,
113
+ repo: tech.repo,
114
+ branch: tech.branch || "main",
115
+ };
116
+ logger.debug("LIBRARIAN", "Technology resolved by search", {
117
+ name,
118
+ group: groupName,
119
+ repoHost: tech.repo.split("/")[2] || "unknown",
120
+ });
93
121
  return result;
94
122
  }
95
123
  }
96
124
  }
97
- logger.debug('LIBRARIAN', 'Technology not found in configuration', { qualifiedName });
125
+ logger.debug("LIBRARIAN", "Technology not found in configuration", {
126
+ qualifiedName,
127
+ });
98
128
  return undefined;
99
129
  }
100
130
  getSecureGroupPath(groupName) {
101
- logger.debug('PATH', 'Validating group path', { groupName });
102
- if (groupName.includes('../') || groupName.includes('..\\') || groupName.startsWith('..')) {
103
- logger.error('PATH', 'Group name contains path traversal characters', undefined, { groupName });
131
+ logger.debug("PATH", "Validating group path", { groupName });
132
+ if (groupName.includes("../") ||
133
+ groupName.includes("..\\") ||
134
+ groupName.startsWith("..")) {
135
+ logger.error("PATH", "Group name contains path traversal characters", undefined, { groupName });
104
136
  throw new Error(`Group name "${groupName}" contains invalid path characters`);
105
137
  }
106
138
  const sanitizedGroupName = path.basename(groupName);
@@ -109,16 +141,21 @@ export class Librarian {
109
141
  const resolvedWorkingDir = path.resolve(workDir);
110
142
  const resolvedGroupPath = path.resolve(groupPath);
111
143
  if (!resolvedGroupPath.startsWith(resolvedWorkingDir)) {
112
- logger.error('PATH', 'Group path escapes working directory sandbox', undefined, { groupName });
144
+ logger.error("PATH", "Group path escapes working directory sandbox", undefined, { groupName });
113
145
  throw new Error(`Group name "${groupName}" attempts to escape the working directory sandbox`);
114
146
  }
115
- logger.debug('PATH', 'Group path validated', { groupName, path: groupPath.replace(os.homedir(), '~') });
147
+ logger.debug("PATH", "Group path validated", {
148
+ groupName,
149
+ path: groupPath.replace(os.homedir(), "~"),
150
+ });
116
151
  return groupPath;
117
152
  }
118
- getSecureRepoPath(repoName, groupName = 'default') {
119
- logger.debug('PATH', 'Validating repo path', { repoName, groupName });
120
- if (repoName.includes('../') || repoName.includes('..\\') || repoName.startsWith('..')) {
121
- logger.error('PATH', 'Repo name contains path traversal characters', undefined, { repoName, groupName });
153
+ getSecureRepoPath(repoName, groupName = "default") {
154
+ logger.debug("PATH", "Validating repo path", { repoName, groupName });
155
+ if (repoName.includes("../") ||
156
+ repoName.includes("..\\") ||
157
+ repoName.startsWith("..")) {
158
+ logger.error("PATH", "Repo name contains path traversal characters", undefined, { repoName, groupName });
122
159
  throw new Error(`Repository name "${repoName}" contains invalid path characters`);
123
160
  }
124
161
  const sanitizedRepoName = path.basename(repoName);
@@ -127,89 +164,116 @@ export class Librarian {
127
164
  const resolvedGroupDir = path.resolve(groupPath);
128
165
  const resolvedRepoPath = path.resolve(repoPath);
129
166
  if (!resolvedRepoPath.startsWith(resolvedGroupDir)) {
130
- logger.error('PATH', 'Repo path escapes group sandbox', undefined, { repoName, groupName });
167
+ logger.error("PATH", "Repo path escapes group sandbox", undefined, {
168
+ repoName,
169
+ groupName,
170
+ });
131
171
  throw new Error(`Repository name "${repoName}" attempts to escape the group sandbox`);
132
172
  }
133
- logger.debug('PATH', 'Repo path validated', { repoName, groupName, path: repoPath.replace(os.homedir(), '~') });
173
+ logger.debug("PATH", "Repo path validated", {
174
+ repoName,
175
+ groupName,
176
+ path: repoPath.replace(os.homedir(), "~"),
177
+ });
134
178
  return repoPath;
135
179
  }
136
- async updateRepository(repoName, groupName = 'default') {
137
- logger.info('GIT', 'Updating repository', { repoName, groupName });
138
- const timingId = logger.timingStart('updateRepository');
180
+ async updateRepository(repoName, groupName = "default") {
181
+ logger.info("GIT", "Updating repository", { repoName, groupName });
182
+ const timingId = logger.timingStart("updateRepository");
139
183
  const repoPath = this.getSecureRepoPath(repoName, groupName);
140
- const gitPath = path.join(repoPath, '.git');
184
+ const gitPath = path.join(repoPath, ".git");
141
185
  if (!fs.existsSync(repoPath)) {
142
- logger.error('GIT', 'Repository path does not exist', undefined, { repoName, repoPath: repoPath.replace(os.homedir(), '~') });
186
+ logger.error("GIT", "Repository path does not exist", undefined, {
187
+ repoName,
188
+ repoPath: repoPath.replace(os.homedir(), "~"),
189
+ });
143
190
  throw new Error(`Repository ${repoName} does not exist at ${repoPath}. Cannot update.`);
144
191
  }
145
192
  if (!fs.existsSync(gitPath)) {
146
- logger.error('GIT', 'Directory is not a git repository', undefined, { repoName, repoPath: repoPath.replace(os.homedir(), '~') });
193
+ logger.error("GIT", "Directory is not a git repository", undefined, {
194
+ repoName,
195
+ repoPath: repoPath.replace(os.homedir(), "~"),
196
+ });
147
197
  throw new Error(`Directory ${repoName} exists at ${repoPath} but is not a git repository. Cannot update.`);
148
198
  }
149
- logger.debug('GIT', 'Fetching updates from remote');
199
+ logger.debug("GIT", "Fetching updates from remote");
150
200
  await fetch({
151
201
  fs,
152
202
  http,
153
203
  dir: repoPath,
154
204
  singleBranch: true,
155
205
  });
156
- const tech = this.resolveTechnology(`${groupName}:${repoName}`) || this.resolveTechnology(repoName);
157
- const branch = tech?.branch || 'main';
158
- logger.debug('GIT', 'Checking out branch', { branch });
206
+ const tech = this.resolveTechnology(`${groupName}:${repoName}`) ||
207
+ this.resolveTechnology(repoName);
208
+ const branch = tech?.branch || "main";
209
+ logger.debug("GIT", "Checking out branch", { branch });
159
210
  await checkout({
160
211
  fs,
161
212
  dir: repoPath,
162
213
  ref: `origin/${branch}`,
163
214
  });
164
- logger.timingEnd(timingId, 'GIT', `Repository updated: ${repoName}`);
215
+ logger.timingEnd(timingId, "GIT", `Repository updated: ${repoName}`);
165
216
  }
166
217
  async syncRepository(repoName) {
167
- logger.info('GIT', 'Syncing repository', { repoName });
218
+ logger.info("GIT", "Syncing repository", { repoName });
168
219
  const tech = this.resolveTechnology(repoName);
169
220
  if (!tech) {
170
- logger.error('GIT', 'Technology not found in configuration', undefined, { repoName });
221
+ logger.error("GIT", "Technology not found in configuration", undefined, {
222
+ repoName,
223
+ });
171
224
  throw new Error(`Repository ${repoName} not found in configuration`);
172
225
  }
173
226
  const repoPath = this.getSecureRepoPath(tech.name, tech.group);
174
- const isLocalRepo = !(tech.repo.startsWith('http://') || tech.repo.startsWith('https://'));
227
+ const isLocalRepo = !(tech.repo.startsWith("http://") || tech.repo.startsWith("https://"));
175
228
  if (fs.existsSync(repoPath)) {
176
229
  if (isLocalRepo) {
177
- logger.debug('GIT', 'Local repository exists, skipping git operations');
230
+ logger.debug("GIT", "Local repository exists, skipping git operations");
178
231
  return repoPath;
179
232
  }
180
233
  if (!isLocalRepo) {
181
- logger.debug('GIT', 'Repository exists, performing update');
234
+ logger.debug("GIT", "Repository exists, performing update");
182
235
  await this.updateRepository(tech.name, tech.group);
183
236
  }
184
237
  }
185
238
  else {
186
239
  if (isLocalRepo) {
187
- logger.error('GIT', 'Local repository path does not exist', undefined, { repoName, repoPath });
240
+ logger.error("GIT", "Local repository path does not exist", undefined, {
241
+ repoName,
242
+ repoPath,
243
+ });
188
244
  throw new Error(`Local repository ${repoName} does not exist at ${repoPath}`);
189
245
  }
190
- logger.debug('GIT', 'Repository does not exist, performing clone');
246
+ logger.debug("GIT", "Repository does not exist, performing clone");
191
247
  return await this.cloneRepository(tech.name, tech.repo, tech.group, tech.branch);
192
248
  }
193
249
  return repoPath;
194
250
  }
195
- async cloneRepository(repoName, repoUrl, groupName = 'default', branch = 'main') {
196
- logger.info('GIT', 'Cloning repository', { repoName, repoHost: repoUrl.split('/')[2] || 'unknown', branch, groupName });
197
- const timingId = logger.timingStart('cloneRepository');
251
+ async cloneRepository(repoName, repoUrl, groupName = "default", branch = "main") {
252
+ logger.info("GIT", "Cloning repository", {
253
+ repoName,
254
+ repoHost: repoUrl.split("/")[2] || "unknown",
255
+ branch,
256
+ groupName,
257
+ });
258
+ const timingId = logger.timingStart("cloneRepository");
198
259
  const repoPath = this.getSecureRepoPath(repoName, groupName);
199
260
  if (fs.existsSync(repoPath)) {
200
- const gitPath = path.join(repoPath, '.git');
261
+ const gitPath = path.join(repoPath, ".git");
201
262
  if (fs.existsSync(gitPath)) {
202
- logger.debug('GIT', 'Repository already exists as git repo, updating instead');
263
+ logger.debug("GIT", "Repository already exists as git repo, updating instead");
203
264
  await this.updateRepository(repoName, groupName);
204
265
  return repoPath;
205
266
  }
206
267
  }
207
268
  const groupPath = this.getSecureGroupPath(groupName);
208
269
  if (!fs.existsSync(groupPath)) {
209
- logger.debug('GIT', 'Creating group directory', { groupName, path: groupPath.replace(os.homedir(), '~') });
270
+ logger.debug("GIT", "Creating group directory", {
271
+ groupName,
272
+ path: groupPath.replace(os.homedir(), "~"),
273
+ });
210
274
  fs.mkdirSync(groupPath, { recursive: true });
211
275
  }
212
- logger.debug('GIT', 'Starting shallow clone', { depth: 1 });
276
+ logger.debug("GIT", "Starting shallow clone", { depth: 1 });
213
277
  await clone({
214
278
  fs,
215
279
  http,
@@ -224,7 +288,7 @@ export class Librarian {
224
288
  try {
225
289
  const files = fs.readdirSync(dir, { withFileTypes: true });
226
290
  for (const file of files) {
227
- if (file.name === '.git') {
291
+ if (file.name === ".git") {
228
292
  continue;
229
293
  }
230
294
  if (file.isDirectory()) {
@@ -239,16 +303,19 @@ export class Librarian {
239
303
  }
240
304
  };
241
305
  countFiles(repoPath);
242
- logger.debug('GIT', 'Clone completed', { fileCount });
243
- logger.timingEnd(timingId, 'GIT', `Repository cloned: ${repoName}`);
306
+ logger.debug("GIT", "Clone completed", { fileCount });
307
+ logger.timingEnd(timingId, "GIT", `Repository cloned: ${repoName}`);
244
308
  return repoPath;
245
309
  }
246
310
  async queryRepository(repoName, query) {
247
- logger.info('LIBRARIAN', 'Querying repository', { repoName, queryLength: query.length });
248
- const timingId = logger.timingStart('queryRepository');
311
+ logger.info("LIBRARIAN", "Querying repository", {
312
+ repoName,
313
+ queryLength: query.length,
314
+ });
315
+ const timingId = logger.timingStart("queryRepository");
249
316
  const tech = this.resolveTechnology(repoName);
250
317
  if (!tech) {
251
- logger.error('LIBRARIAN', 'Technology not found in configuration', undefined, { repoName });
318
+ logger.error("LIBRARIAN", "Technology not found in configuration", undefined, { repoName });
252
319
  throw new Error(`Repository ${repoName} not found in configuration`);
253
320
  }
254
321
  const repoPath = await this.syncRepository(repoName);
@@ -257,10 +324,10 @@ export class Librarian {
257
324
  group: tech.group,
258
325
  technology: tech.name,
259
326
  };
260
- logger.debug('LIBRARIAN', 'Initializing agent for query with context', {
261
- workingDir: context.workingDir.replace(os.homedir(), '~'),
327
+ logger.debug("LIBRARIAN", "Initializing agent for query with context", {
328
+ workingDir: context.workingDir.replace(os.homedir(), "~"),
262
329
  group: context.group,
263
- technology: context.technology
330
+ technology: context.technology,
264
331
  });
265
332
  const agent = new ReactAgent({
266
333
  aiProvider: this.config.aiProvider,
@@ -268,35 +335,43 @@ export class Librarian {
268
335
  technology: {
269
336
  name: tech.name,
270
337
  repository: tech.repo,
271
- branch: tech.branch
272
- }
338
+ branch: tech.branch,
339
+ },
273
340
  });
274
341
  await agent.initialize();
275
342
  const result = await agent.queryRepository(repoPath, query, context);
276
- logger.timingEnd(timingId, 'LIBRARIAN', `Query completed: ${repoName}`);
277
- logger.info('LIBRARIAN', 'Query result received', { repoName, responseLength: result.length });
343
+ logger.timingEnd(timingId, "LIBRARIAN", `Query completed: ${repoName}`);
344
+ logger.info("LIBRARIAN", "Query result received", {
345
+ repoName,
346
+ responseLength: result.length,
347
+ });
278
348
  return result;
279
349
  }
280
350
  async *streamRepository(repoName, query) {
281
- logger.info('LIBRARIAN', 'Streaming repository query', { repoName, queryLength: query.length });
282
- const timingId = logger.timingStart('streamRepository');
351
+ logger.info("LIBRARIAN", "Streaming repository query", {
352
+ repoName,
353
+ queryLength: query.length,
354
+ });
355
+ const timingId = logger.timingStart("streamRepository");
283
356
  const tech = this.resolveTechnology(repoName);
284
357
  if (!tech) {
285
- logger.error('LIBRARIAN', 'Technology not found in configuration', undefined, { repoName });
358
+ logger.error("LIBRARIAN", "Technology not found in configuration", undefined, { repoName });
286
359
  throw new Error(`Repository ${repoName} not found in configuration`);
287
360
  }
288
361
  let isInterrupted = false;
289
362
  const cleanup = () => {
290
363
  isInterrupted = true;
291
364
  };
292
- logger.debug('LIBRARIAN', 'Setting up interruption handlers');
293
- process.on('SIGINT', cleanup);
294
- process.on('SIGTERM', cleanup);
365
+ logger.debug("LIBRARIAN", "Setting up interruption handlers");
366
+ process.on("SIGINT", cleanup);
367
+ process.on("SIGTERM", cleanup);
295
368
  try {
296
369
  const repoPath = await this.syncRepository(repoName);
297
370
  if (isInterrupted) {
298
- logger.warn('LIBRARIAN', 'Repository sync interrupted by user', { repoName });
299
- yield '[Repository sync interrupted by user]';
371
+ logger.warn("LIBRARIAN", "Repository sync interrupted by user", {
372
+ repoName,
373
+ });
374
+ yield "[Repository sync interrupted by user]";
300
375
  return;
301
376
  }
302
377
  const context = {
@@ -304,10 +379,10 @@ export class Librarian {
304
379
  group: tech.group,
305
380
  technology: tech.name,
306
381
  };
307
- logger.debug('LIBRARIAN', 'Initializing agent for streaming query with context', {
308
- workingDir: context.workingDir.replace(os.homedir(), '~'),
382
+ logger.debug("LIBRARIAN", "Initializing agent for streaming query with context", {
383
+ workingDir: context.workingDir.replace(os.homedir(), "~"),
309
384
  group: context.group,
310
- technology: context.technology
385
+ technology: context.technology,
311
386
  });
312
387
  const agent = new ReactAgent({
313
388
  aiProvider: this.config.aiProvider,
@@ -315,56 +390,67 @@ export class Librarian {
315
390
  technology: {
316
391
  name: tech.name,
317
392
  repository: tech.repo,
318
- branch: tech.branch
319
- }
393
+ branch: tech.branch,
394
+ },
320
395
  });
321
396
  await agent.initialize();
322
397
  if (isInterrupted) {
323
- logger.warn('LIBRARIAN', 'Agent initialization interrupted by user', { repoName });
324
- yield '[Agent initialization interrupted by user]';
398
+ logger.warn("LIBRARIAN", "Agent initialization interrupted by user", {
399
+ repoName,
400
+ });
401
+ yield "[Agent initialization interrupted by user]";
325
402
  return;
326
403
  }
327
- logger.debug('LIBRARIAN', 'Starting stream from agent');
404
+ logger.debug("LIBRARIAN", "Starting stream from agent");
328
405
  yield* agent.streamRepository(repoPath, query, context);
329
406
  }
330
407
  catch (error) {
331
- let errorMessage = 'Unknown error';
408
+ let errorMessage = "Unknown error";
332
409
  if (error instanceof Error) {
333
- if (error.message.includes('not found in configuration')) {
410
+ if (error.message.includes("not found in configuration")) {
334
411
  errorMessage = error.message;
335
412
  }
336
- else if (error.message.includes('git') || error.message.includes('clone')) {
413
+ else if (error.message.includes("git") ||
414
+ error.message.includes("clone")) {
337
415
  errorMessage = `Repository operation failed: ${error.message}`;
338
416
  }
339
- else if (error.message.includes('timeout')) {
340
- errorMessage = 'Repository operation timed out';
417
+ else if (error.message.includes("timeout")) {
418
+ errorMessage = "Repository operation timed out";
341
419
  }
342
420
  else {
343
421
  errorMessage = `Repository error: ${error.message}`;
344
422
  }
345
423
  }
346
- logger.error('LIBRARIAN', 'Stream error', error instanceof Error ? error : new Error(errorMessage), { repoName });
424
+ logger.error("LIBRARIAN", "Stream error", error instanceof Error ? error : new Error(errorMessage), { repoName });
347
425
  yield `\n[Error: ${errorMessage}]`;
348
426
  throw error;
349
427
  }
350
428
  finally {
351
- process.removeListener('SIGINT', cleanup);
352
- process.removeListener('SIGTERM', cleanup);
353
- logger.timingEnd(timingId, 'LIBRARIAN', `Stream completed: ${repoName}`);
429
+ process.removeListener("SIGINT", cleanup);
430
+ process.removeListener("SIGTERM", cleanup);
431
+ logger.timingEnd(timingId, "LIBRARIAN", `Stream completed: ${repoName}`);
354
432
  }
355
433
  }
356
434
  async queryGroup(groupName, query) {
357
- logger.info('LIBRARIAN', 'Querying group', { groupName, queryLength: query.length });
358
- const timingId = logger.timingStart('queryGroup');
435
+ logger.info("LIBRARIAN", "Querying group", {
436
+ groupName,
437
+ queryLength: query.length,
438
+ });
439
+ const timingId = logger.timingStart("queryGroup");
359
440
  if (!this.config.technologies[groupName]) {
360
- logger.error('LIBRARIAN', 'Group not found in configuration', undefined, { groupName });
441
+ logger.error("LIBRARIAN", "Group not found in configuration", undefined, {
442
+ groupName,
443
+ });
361
444
  throw new Error(`Group ${groupName} not found in configuration`);
362
445
  }
363
446
  const groupPath = this.getSecureGroupPath(groupName);
364
447
  const technologies = this.config.technologies[groupName];
365
448
  if (technologies) {
366
449
  const techNames = Object.keys(technologies);
367
- logger.info('LIBRARIAN', 'Syncing all technologies in group', { groupName, techCount: techNames.length });
450
+ logger.info("LIBRARIAN", "Syncing all technologies in group", {
451
+ groupName,
452
+ techCount: techNames.length,
453
+ });
368
454
  for (const techName of techNames) {
369
455
  await this.syncRepository(techName);
370
456
  }
@@ -372,27 +458,35 @@ export class Librarian {
372
458
  const context = {
373
459
  workingDir: groupPath,
374
460
  group: groupName,
375
- technology: '',
461
+ technology: "",
376
462
  };
377
- logger.debug('LIBRARIAN', 'Initializing agent for group query with context', {
378
- workingDir: context.workingDir.replace(os.homedir(), '~'),
379
- group: context.group
463
+ logger.debug("LIBRARIAN", "Initializing agent for group query with context", {
464
+ workingDir: context.workingDir.replace(os.homedir(), "~"),
465
+ group: context.group,
380
466
  });
381
467
  const agent = new ReactAgent({
382
468
  aiProvider: this.config.aiProvider,
383
- workingDir: groupPath
469
+ workingDir: groupPath,
384
470
  });
385
471
  await agent.initialize();
386
472
  const result = await agent.queryRepository(groupPath, query, context);
387
- logger.timingEnd(timingId, 'LIBRARIAN', `Group query completed: ${groupName}`);
388
- logger.info('LIBRARIAN', 'Group query result received', { groupName, responseLength: result.length });
473
+ logger.timingEnd(timingId, "LIBRARIAN", `Group query completed: ${groupName}`);
474
+ logger.info("LIBRARIAN", "Group query result received", {
475
+ groupName,
476
+ responseLength: result.length,
477
+ });
389
478
  return result;
390
479
  }
391
480
  async *streamGroup(groupName, query) {
392
- logger.info('LIBRARIAN', 'Streaming group query', { groupName, queryLength: query.length });
393
- const timingId = logger.timingStart('streamGroup');
481
+ logger.info("LIBRARIAN", "Streaming group query", {
482
+ groupName,
483
+ queryLength: query.length,
484
+ });
485
+ const timingId = logger.timingStart("streamGroup");
394
486
  if (!this.config.technologies[groupName]) {
395
- logger.error('LIBRARIAN', 'Group not found in configuration', undefined, { groupName });
487
+ logger.error("LIBRARIAN", "Group not found in configuration", undefined, {
488
+ groupName,
489
+ });
396
490
  throw new Error(`Group ${groupName} not found in configuration`);
397
491
  }
398
492
  const groupPath = this.getSecureGroupPath(groupName);
@@ -400,69 +494,71 @@ export class Librarian {
400
494
  const cleanup = () => {
401
495
  isInterrupted = true;
402
496
  };
403
- logger.debug('LIBRARIAN', 'Setting up interruption handlers for group');
404
- process.on('SIGINT', cleanup);
405
- process.on('SIGTERM', cleanup);
497
+ logger.debug("LIBRARIAN", "Setting up interruption handlers for group");
498
+ process.on("SIGINT", cleanup);
499
+ process.on("SIGTERM", cleanup);
406
500
  try {
407
501
  const technologies = this.config.technologies[groupName];
408
502
  if (technologies) {
409
503
  const techNames = Object.keys(technologies);
410
- logger.info('LIBRARIAN', 'Syncing all technologies in group for streaming', { groupName, techCount: techNames.length });
504
+ logger.info("LIBRARIAN", "Syncing all technologies in group for streaming", { groupName, techCount: techNames.length });
411
505
  for (const techName of techNames) {
412
506
  await this.syncRepository(techName);
413
507
  }
414
508
  }
415
509
  if (isInterrupted) {
416
- logger.warn('LIBRARIAN', 'Group sync interrupted by user', { groupName });
417
- yield '[Repository sync interrupted by user]';
510
+ logger.warn("LIBRARIAN", "Group sync interrupted by user", {
511
+ groupName,
512
+ });
513
+ yield "[Repository sync interrupted by user]";
418
514
  return;
419
515
  }
420
516
  const context = {
421
517
  workingDir: groupPath,
422
518
  group: groupName,
423
- technology: '',
519
+ technology: "",
424
520
  };
425
- logger.debug('LIBRARIAN', 'Initializing agent for group streaming with context', {
426
- workingDir: context.workingDir.replace(os.homedir(), '~'),
427
- group: context.group
521
+ logger.debug("LIBRARIAN", "Initializing agent for group streaming with context", {
522
+ workingDir: context.workingDir.replace(os.homedir(), "~"),
523
+ group: context.group,
428
524
  });
429
525
  const agent = new ReactAgent({
430
526
  aiProvider: this.config.aiProvider,
431
- workingDir: groupPath
527
+ workingDir: groupPath,
432
528
  });
433
529
  await agent.initialize();
434
530
  if (isInterrupted) {
435
- logger.warn('LIBRARIAN', 'Agent initialization interrupted by user for group', { groupName });
436
- yield '[Agent initialization interrupted by user]';
531
+ logger.warn("LIBRARIAN", "Agent initialization interrupted by user for group", { groupName });
532
+ yield "[Agent initialization interrupted by user]";
437
533
  return;
438
534
  }
439
- logger.debug('LIBRARIAN', 'Starting stream from agent for group');
535
+ logger.debug("LIBRARIAN", "Starting stream from agent for group");
440
536
  yield* agent.streamRepository(groupPath, query, context);
441
537
  }
442
538
  catch (error) {
443
539
  const errorMessage = this.getGroupStreamErrorMessage(error);
444
- logger.error('LIBRARIAN', 'Group stream error', error instanceof Error ? error : new Error(errorMessage), { groupName });
540
+ logger.error("LIBRARIAN", "Group stream error", error instanceof Error ? error : new Error(errorMessage), { groupName });
445
541
  yield `\n[Error: ${errorMessage}]`;
446
542
  throw error;
447
543
  }
448
544
  finally {
449
- process.removeListener('SIGINT', cleanup);
450
- process.removeListener('SIGTERM', cleanup);
451
- logger.timingEnd(timingId, 'LIBRARIAN', `Group stream completed: ${groupName}`);
545
+ process.removeListener("SIGINT", cleanup);
546
+ process.removeListener("SIGTERM", cleanup);
547
+ logger.timingEnd(timingId, "LIBRARIAN", `Group stream completed: ${groupName}`);
452
548
  }
453
549
  }
454
550
  getGroupStreamErrorMessage(error) {
455
551
  if (!(error instanceof Error)) {
456
- return 'Unknown error';
552
+ return "Unknown error";
457
553
  }
458
- if (error.message.includes('not found in configuration')) {
554
+ if (error.message.includes("not found in configuration")) {
459
555
  return error.message;
460
556
  }
461
- if (error.message.includes('git') || error.message.includes('clone')) {
557
+ if (error.message.includes("git") || error.message.includes("clone")) {
462
558
  return `Repository operation failed: ${error.message}`;
463
559
  }
464
- if (error.message.includes('timeout')) {
465
- return 'Repository operation timed out';
560
+ if (error.message.includes("timeout")) {
561
+ return "Repository operation timed out";
466
562
  }
467
563
  return `Group error: ${error.message}`;
468
564
  }