repowise 0.1.86 → 0.1.88

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 (2) hide show
  1. package/dist/bin/repowise.js +798 -329
  2. package/package.json +1 -1
@@ -3,12 +3,12 @@
3
3
  // bin/repowise.ts
4
4
  import { readFileSync as readFileSync2 } from "fs";
5
5
  import { fileURLToPath as fileURLToPath3 } from "url";
6
- import { dirname as dirname9, join as join22 } from "path";
6
+ import { dirname as dirname9, join as join23 } from "path";
7
7
  import { Command } from "commander";
8
8
 
9
9
  // ../listener/dist/main.js
10
- import { readFile as readFile5, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
11
- import { join as join12, dirname as dirname4 } from "path";
10
+ import { readFile as readFile6, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
11
+ import { join as join13, dirname as dirname5 } from "path";
12
12
  import { fileURLToPath as fileURLToPath2 } from "url";
13
13
  import lockfile2 from "proper-lockfile";
14
14
 
@@ -20,17 +20,318 @@ function getConfigDir() {
20
20
  return join(homedir(), isStaging ? ".repowise-staging" : ".repowise");
21
21
  }
22
22
 
23
+ // ../../packages/shared/dist/lib/ai-tools.js
24
+ import { readFile, writeFile, mkdir, readdir, stat, unlink } from "fs/promises";
25
+ import { join as join2, dirname } from "path";
26
+ var AI_TOOL_CONFIG = {
27
+ cursor: {
28
+ label: "Cursor",
29
+ fileName: "repowise.mdc",
30
+ filePath: ".cursor/rules/repowise.mdc",
31
+ legacyFilePath: ".cursorrules",
32
+ markerStart: "<!-- repowise-start -->",
33
+ markerEnd: "<!-- repowise-end -->",
34
+ format: "markdown",
35
+ frontmatter: "---\ndescription: RepoWise project context\nglobs: \nalwaysApply: true\n---",
36
+ owned: true
37
+ },
38
+ "claude-code": {
39
+ label: "Claude Code",
40
+ fileName: "CLAUDE.md",
41
+ filePath: "CLAUDE.md",
42
+ markerStart: "<!-- repowise-start -->",
43
+ markerEnd: "<!-- repowise-end -->",
44
+ format: "markdown",
45
+ owned: false
46
+ },
47
+ copilot: {
48
+ label: "GitHub Copilot",
49
+ fileName: "copilot-instructions.md",
50
+ filePath: ".github/copilot-instructions.md",
51
+ markerStart: "<!-- repowise-start -->",
52
+ markerEnd: "<!-- repowise-end -->",
53
+ format: "markdown",
54
+ owned: false
55
+ },
56
+ windsurf: {
57
+ label: "Windsurf",
58
+ fileName: "repowise.md",
59
+ filePath: ".windsurf/rules/repowise.md",
60
+ legacyFilePath: ".windsurfrules",
61
+ markerStart: "<!-- repowise-start -->",
62
+ markerEnd: "<!-- repowise-end -->",
63
+ format: "markdown",
64
+ frontmatter: "---\ntrigger: always_on\ndescription: RepoWise project context\n---",
65
+ owned: true
66
+ },
67
+ cline: {
68
+ label: "Cline",
69
+ fileName: "repowise.md",
70
+ filePath: ".clinerules/repowise.md",
71
+ legacyFilePath: ".clinerules",
72
+ markerStart: "<!-- repowise-start -->",
73
+ markerEnd: "<!-- repowise-end -->",
74
+ format: "markdown",
75
+ owned: true
76
+ },
77
+ codex: {
78
+ label: "Codex",
79
+ fileName: "AGENTS.md",
80
+ filePath: "AGENTS.md",
81
+ markerStart: "<!-- repowise-start -->",
82
+ markerEnd: "<!-- repowise-end -->",
83
+ format: "markdown",
84
+ owned: false
85
+ },
86
+ "roo-code": {
87
+ label: "Roo Code",
88
+ fileName: "repowise.md",
89
+ filePath: ".roo/rules/repowise.md",
90
+ legacyFilePath: ".roo/rules.md",
91
+ markerStart: "<!-- repowise-start -->",
92
+ markerEnd: "<!-- repowise-end -->",
93
+ format: "markdown",
94
+ owned: true
95
+ },
96
+ gemini: {
97
+ label: "Gemini CLI",
98
+ fileName: "GEMINI.md",
99
+ filePath: "GEMINI.md",
100
+ markerStart: "<!-- repowise-start -->",
101
+ markerEnd: "<!-- repowise-end -->",
102
+ format: "markdown",
103
+ owned: false
104
+ },
105
+ junie: {
106
+ label: "JetBrains Junie",
107
+ fileName: "AGENTS.md",
108
+ filePath: ".junie/AGENTS.md",
109
+ markerStart: "<!-- repowise-start -->",
110
+ markerEnd: "<!-- repowise-end -->",
111
+ format: "markdown",
112
+ owned: true
113
+ },
114
+ warp: {
115
+ label: "Warp",
116
+ fileName: "AGENTS.md",
117
+ filePath: "AGENTS.md",
118
+ markerStart: "<!-- repowise-start -->",
119
+ markerEnd: "<!-- repowise-end -->",
120
+ format: "markdown",
121
+ owned: false
122
+ },
123
+ jules: {
124
+ label: "Google Jules",
125
+ fileName: "AGENTS.md",
126
+ filePath: "AGENTS.md",
127
+ markerStart: "<!-- repowise-start -->",
128
+ markerEnd: "<!-- repowise-end -->",
129
+ format: "markdown",
130
+ owned: false
131
+ },
132
+ amp: {
133
+ label: "Amp",
134
+ fileName: "AGENTS.md",
135
+ filePath: "AGENTS.md",
136
+ markerStart: "<!-- repowise-start -->",
137
+ markerEnd: "<!-- repowise-end -->",
138
+ format: "markdown",
139
+ owned: false
140
+ },
141
+ devin: {
142
+ label: "Devin",
143
+ fileName: "AGENTS.md",
144
+ filePath: "AGENTS.md",
145
+ markerStart: "<!-- repowise-start -->",
146
+ markerEnd: "<!-- repowise-end -->",
147
+ format: "markdown",
148
+ owned: false
149
+ }
150
+ };
151
+ var SUPPORTED_TOOLS = Object.keys(AI_TOOL_CONFIG);
152
+ function sanitizeRepoName(name) {
153
+ return name.replace(/[<>[\]`()|\\]/g, "");
154
+ }
155
+ function fileDescriptionFromName(fileName) {
156
+ return fileName.replace(/\.md$/, "").split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
157
+ }
158
+ function generateReference(tool, repoName, contextFolder, contextFiles) {
159
+ const config2 = AI_TOOL_CONFIG[tool];
160
+ const safeName = sanitizeRepoName(repoName);
161
+ const fileLines = contextFiles.map((f) => {
162
+ const baseName = f.fileName.split("/").pop() ?? f.fileName;
163
+ const desc = fileDescriptionFromName(baseName);
164
+ const isOverview = baseName === "project-overview.md";
165
+ return { path: f.relativePath, desc: isOverview ? `${desc} (full index of all files)` : desc };
166
+ });
167
+ const hasFiles = fileLines.length > 0;
168
+ const contentLines = [
169
+ `## Project Context \u2014 ${safeName}`,
170
+ "",
171
+ `This repository has AI-optimized context files generated by RepoWise.`,
172
+ `**IMPORTANT: Before answering questions about the codebase or making any changes, ALWAYS check the \`${contextFolder}/\` folder first.** These files contain pre-analyzed architecture, patterns, API contracts, and domain knowledge that will answer most questions without needing to search the codebase.`,
173
+ "",
174
+ `**Start here:** \`${contextFolder}/project-overview.md\` \u2014 the routing document that maps every context file to its domain. Read it first to find which context file has the answer you need.`,
175
+ ""
176
+ ];
177
+ if (hasFiles) {
178
+ contentLines.push(`**Core context files:**`, "", ...fileLines.map((f) => `- \`${f.path}\` \u2014 ${f.desc}`), "", `> Additional context files may exist beyond this list. Check \`project-overview.md\` for the complete index.`, "");
179
+ }
180
+ contentLines.push(`**Subagents:** When delegating tasks to sub-agents, always include this instruction: "Read \`${contextFolder}/project-overview.md\` before performing any work."`);
181
+ if (config2.owned) {
182
+ const parts = [];
183
+ if (config2.frontmatter) {
184
+ parts.push(config2.frontmatter);
185
+ }
186
+ parts.push("", ...contentLines, "");
187
+ return parts.join("\n");
188
+ }
189
+ return [config2.markerStart, "", ...contentLines, "", config2.markerEnd].join("\n");
190
+ }
191
+ async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
192
+ const config2 = AI_TOOL_CONFIG[tool];
193
+ const fullPath = join2(repoRoot, config2.filePath);
194
+ const dir = dirname(fullPath);
195
+ if (dir !== repoRoot) {
196
+ await mkdir(dir, { recursive: true });
197
+ }
198
+ const referenceBlock = generateReference(tool, repoName, contextFolder, contextFiles);
199
+ if (config2.owned) {
200
+ let created2 = true;
201
+ try {
202
+ await stat(fullPath);
203
+ created2 = false;
204
+ } catch {
205
+ }
206
+ await writeFile(fullPath, referenceBlock, "utf-8");
207
+ return { created: created2 };
208
+ }
209
+ let existing = "";
210
+ let created = true;
211
+ try {
212
+ existing = await readFile(fullPath, "utf-8");
213
+ created = false;
214
+ } catch (err) {
215
+ if (err.code !== "ENOENT")
216
+ throw err;
217
+ }
218
+ const startIdx = existing.indexOf(config2.markerStart);
219
+ const endIdx = existing.indexOf(config2.markerEnd);
220
+ let content;
221
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
222
+ const before = existing.slice(0, startIdx);
223
+ const after = existing.slice(endIdx + config2.markerEnd.length);
224
+ content = before + referenceBlock + after;
225
+ } else {
226
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
227
+ content = existing + separator + referenceBlock + "\n";
228
+ }
229
+ await writeFile(fullPath, content, "utf-8");
230
+ return { created };
231
+ }
232
+ async function migrateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
233
+ const config2 = AI_TOOL_CONFIG[tool];
234
+ if (!config2.legacyFilePath)
235
+ return { migrated: false, legacyRemoved: false };
236
+ const legacyPath = join2(repoRoot, config2.legacyFilePath);
237
+ let legacyIsFile = false;
238
+ try {
239
+ const s = await stat(legacyPath);
240
+ legacyIsFile = s.isFile();
241
+ } catch {
242
+ await updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles);
243
+ return { migrated: false, legacyRemoved: false };
244
+ }
245
+ if (!legacyIsFile) {
246
+ await updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles);
247
+ return { migrated: false, legacyRemoved: false };
248
+ }
249
+ const legacyContent = await readFile(legacyPath, "utf-8");
250
+ let cleaned = legacyContent;
251
+ const oldMarkers = [
252
+ { start: "# --- repowise-start ---", end: "# --- repowise-end ---" },
253
+ { start: "<!-- repowise-start -->", end: "<!-- repowise-end -->" }
254
+ ];
255
+ for (const m of oldMarkers) {
256
+ const si = cleaned.indexOf(m.start);
257
+ const ei = cleaned.indexOf(m.end);
258
+ if (si !== -1 && ei !== -1 && ei > si) {
259
+ cleaned = cleaned.slice(0, si) + cleaned.slice(ei + m.end.length);
260
+ }
261
+ }
262
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
263
+ if (tool === "cline") {
264
+ if (cleaned.length > 0) {
265
+ await unlink(legacyPath);
266
+ await mkdir(join2(repoRoot, ".clinerules"), { recursive: true });
267
+ await writeFile(join2(repoRoot, ".clinerules/user-rules.md"), cleaned + "\n", "utf-8");
268
+ } else {
269
+ await unlink(legacyPath);
270
+ }
271
+ } else if (cleaned.length > 0) {
272
+ await writeFile(legacyPath, cleaned + "\n", "utf-8");
273
+ } else {
274
+ await unlink(legacyPath);
275
+ }
276
+ await updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles);
277
+ return { migrated: true, legacyRemoved: cleaned.length === 0 };
278
+ }
279
+ async function fileExists(path) {
280
+ try {
281
+ await stat(path);
282
+ return true;
283
+ } catch {
284
+ return false;
285
+ }
286
+ }
287
+ async function detectInstalledTools(repoRoot) {
288
+ const detected = [];
289
+ for (const [tool, config2] of Object.entries(AI_TOOL_CONFIG)) {
290
+ if (tool !== "codex" && config2.filePath === "AGENTS.md")
291
+ continue;
292
+ if (await fileExists(join2(repoRoot, config2.filePath))) {
293
+ detected.push(tool);
294
+ } else if (config2.legacyFilePath && await fileExists(join2(repoRoot, config2.legacyFilePath))) {
295
+ detected.push(tool);
296
+ }
297
+ }
298
+ return detected;
299
+ }
300
+ async function scanLocalContextFiles(repoRoot, contextFolder) {
301
+ const folderPath = join2(repoRoot, contextFolder);
302
+ try {
303
+ const entries = await readdir(folderPath, { withFileTypes: true, recursive: true });
304
+ const results = [];
305
+ for (const entry of entries) {
306
+ if (!entry.isFile() || !entry.name.endsWith(".md"))
307
+ continue;
308
+ const parentDir = entry.parentPath ?? folderPath;
309
+ const fullPath = join2(parentDir, entry.name);
310
+ const relFromContext = fullPath.slice(folderPath.length + 1);
311
+ results.push({
312
+ fileName: relFromContext,
313
+ relativePath: `${contextFolder}/${relFromContext}`
314
+ });
315
+ }
316
+ return results.sort((a, b) => a.fileName.localeCompare(b.fileName));
317
+ } catch (err) {
318
+ if (err.code === "ENOENT")
319
+ return [];
320
+ throw err;
321
+ }
322
+ }
323
+
23
324
  // ../listener/dist/lib/config.js
24
- import { readFile, writeFile, rename, unlink, mkdir, chmod } from "fs/promises";
25
- import { join as join2 } from "path";
325
+ import { readFile as readFile2, writeFile as writeFile2, rename, unlink as unlink2, mkdir as mkdir2, chmod } from "fs/promises";
326
+ import { join as join3 } from "path";
26
327
  import lockfile from "proper-lockfile";
27
328
  var DEFAULT_API_URL = false ? "https://staging-api.repowise.ai" : "https://api.repowise.ai";
28
329
  async function getListenerConfig() {
29
330
  const configDir = getConfigDir();
30
- const configPath = join2(configDir, "config.json");
331
+ const configPath = join3(configDir, "config.json");
31
332
  const apiUrl = process.env["REPOWISE_API_URL"] ?? DEFAULT_API_URL;
32
333
  try {
33
- const data = await readFile(configPath, "utf-8");
334
+ const data = await readFile2(configPath, "utf-8");
34
335
  const raw = JSON.parse(data);
35
336
  const validRepos = (raw.repos ?? []).filter((r) => typeof r === "object" && r !== null && typeof r.repoId === "string" && typeof r.localPath === "string");
36
337
  return {
@@ -45,10 +346,10 @@ async function getListenerConfig() {
45
346
  }
46
347
  async function saveListenerConfig(config2) {
47
348
  const configDir = getConfigDir();
48
- const configPath = join2(configDir, "config.json");
49
- await mkdir(configDir, { recursive: true, mode: 448 });
349
+ const configPath = join3(configDir, "config.json");
350
+ await mkdir2(configDir, { recursive: true, mode: 448 });
50
351
  try {
51
- await writeFile(configPath, "", { flag: "a" });
352
+ await writeFile2(configPath, "", { flag: "a" });
52
353
  } catch {
53
354
  }
54
355
  let release = null;
@@ -56,7 +357,7 @@ async function saveListenerConfig(config2) {
56
357
  release = await lockfile.lock(configPath, { stale: 1e4, retries: 3, realpath: false });
57
358
  let raw = {};
58
359
  try {
59
- const data = await readFile(configPath, "utf-8");
360
+ const data = await readFile2(configPath, "utf-8");
60
361
  raw = JSON.parse(data);
61
362
  } catch {
62
363
  }
@@ -67,12 +368,12 @@ async function saveListenerConfig(config2) {
67
368
  };
68
369
  const tmpPath = configPath + ".tmp";
69
370
  try {
70
- await writeFile(tmpPath, JSON.stringify(output, null, 2));
371
+ await writeFile2(tmpPath, JSON.stringify(output, null, 2));
71
372
  await chmod(tmpPath, 384);
72
373
  await rename(tmpPath, configPath);
73
374
  } catch (err) {
74
375
  try {
75
- await unlink(tmpPath);
376
+ await unlink2(tmpPath);
76
377
  } catch {
77
378
  }
78
379
  throw err;
@@ -88,15 +389,15 @@ async function saveListenerConfig(config2) {
88
389
  }
89
390
 
90
391
  // ../listener/dist/lib/state.js
91
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2, rename as rename2, unlink as unlink2 } from "fs/promises";
92
- import { join as join3 } from "path";
392
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, chmod as chmod2, rename as rename2, unlink as unlink3 } from "fs/promises";
393
+ import { join as join4 } from "path";
93
394
  function emptyState() {
94
395
  return { repos: {} };
95
396
  }
96
397
  async function loadState() {
97
- const statePath = join3(getConfigDir(), "listener-state.json");
398
+ const statePath = join4(getConfigDir(), "listener-state.json");
98
399
  try {
99
- const data = await readFile2(statePath, "utf-8");
400
+ const data = await readFile3(statePath, "utf-8");
100
401
  return JSON.parse(data);
101
402
  } catch (err) {
102
403
  if (err.code === "ENOENT" || err instanceof SyntaxError) {
@@ -107,16 +408,16 @@ async function loadState() {
107
408
  }
108
409
  async function saveState(state) {
109
410
  const configDir = getConfigDir();
110
- const statePath = join3(configDir, "listener-state.json");
111
- await mkdir2(configDir, { recursive: true, mode: 448 });
411
+ const statePath = join4(configDir, "listener-state.json");
412
+ await mkdir3(configDir, { recursive: true, mode: 448 });
112
413
  const tmpPath = statePath + ".tmp";
113
414
  try {
114
- await writeFile2(tmpPath, JSON.stringify(state, null, 2));
415
+ await writeFile3(tmpPath, JSON.stringify(state, null, 2));
115
416
  await chmod2(tmpPath, 384);
116
417
  await rename2(tmpPath, statePath);
117
418
  } catch (err) {
118
419
  try {
119
- await unlink2(tmpPath);
420
+ await unlink3(tmpPath);
120
421
  } catch {
121
422
  }
122
423
  throw err;
@@ -125,7 +426,7 @@ async function saveState(state) {
125
426
 
126
427
  // ../listener/dist/lib/reconcile.js
127
428
  import { statSync, readdirSync } from "fs";
128
- import { basename, dirname, join as join4 } from "path";
429
+ import { basename, dirname as dirname2, join as join5 } from "path";
129
430
  function reconcileRepos(configRepos, activeRepos, state, apiUrl, options) {
130
431
  if (activeRepos.length === 0) {
131
432
  return { updated: false, repos: configRepos, changes: [], addedRepos: [] };
@@ -169,7 +470,7 @@ function reconcileRepos(configRepos, activeRepos, state, apiUrl, options) {
169
470
  }
170
471
  }
171
472
  const dirName = basename(repo.localPath);
172
- const parentDir = basename(dirname(repo.localPath));
473
+ const parentDir = basename(dirname2(repo.localPath));
173
474
  const fullPathName = `${parentDir}/${dirName}`;
174
475
  let matches = activeRepos.filter((ar) => ar.fullName === fullPathName);
175
476
  if (matches.length === 0) {
@@ -209,7 +510,7 @@ function reconcileRepos(configRepos, activeRepos, state, apiUrl, options) {
209
510
  if (unmatchedActiveRepos.length > 0) {
210
511
  const parentDirs = /* @__PURE__ */ new Set();
211
512
  for (const repo of updatedRepos) {
212
- parentDirs.add(dirname(repo.localPath));
513
+ parentDirs.add(dirname2(repo.localPath));
213
514
  }
214
515
  for (const activeRepo of unmatchedActiveRepos) {
215
516
  const found = findLocalRepo(activeRepo, parentDirs, usedPaths);
@@ -243,7 +544,7 @@ function findLocalRepo(activeRepo, parentDirs, usedPaths) {
243
544
  const nameParts = activeRepo.fullName.split("/");
244
545
  const repoName = nameParts[nameParts.length - 1];
245
546
  for (const parentDir of parentDirs) {
246
- const candidate = join4(parentDir, repoName);
547
+ const candidate = join5(parentDir, repoName);
247
548
  if (!usedPaths.has(candidate) && hasContextFolder(candidate)) {
248
549
  return candidate;
249
550
  }
@@ -252,7 +553,7 @@ function findLocalRepo(activeRepo, parentDirs, usedPaths) {
252
553
  }
253
554
  function hasContextFolder(dirPath) {
254
555
  try {
255
- const contextPath = join4(dirPath, "repowise-context");
556
+ const contextPath = join5(dirPath, "repowise-context");
256
557
  const s = statSync(contextPath);
257
558
  if (!s.isDirectory())
258
559
  return false;
@@ -282,8 +583,8 @@ function migrateState(state, oldId, newId) {
282
583
  }
283
584
 
284
585
  // ../listener/dist/lib/auth.js
285
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, chmod as chmod3 } from "fs/promises";
286
- import { join as join5 } from "path";
586
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, chmod as chmod3 } from "fs/promises";
587
+ import { join as join6 } from "path";
287
588
  function getTokenUrl(creds) {
288
589
  const cognito = creds?.cognito;
289
590
  const domain = process.env["REPOWISE_COGNITO_DOMAIN"] ?? cognito?.domain ?? "auth-repowise-dev";
@@ -322,8 +623,8 @@ async function refreshTokens(refreshToken, creds) {
322
623
  }
323
624
  async function getStoredCredentials() {
324
625
  try {
325
- const credPath = join5(getConfigDir(), "credentials.json");
326
- const data = await readFile3(credPath, "utf-8");
626
+ const credPath = join6(getConfigDir(), "credentials.json");
627
+ const data = await readFile4(credPath, "utf-8");
327
628
  return JSON.parse(data);
328
629
  } catch (err) {
329
630
  if (err.code === "ENOENT" || err instanceof SyntaxError) {
@@ -334,9 +635,9 @@ async function getStoredCredentials() {
334
635
  }
335
636
  async function storeCredentials(credentials) {
336
637
  const dir = getConfigDir();
337
- const credPath = join5(dir, "credentials.json");
338
- await mkdir3(dir, { recursive: true, mode: 448 });
339
- await writeFile3(credPath, JSON.stringify(credentials, null, 2));
638
+ const credPath = join6(dir, "credentials.json");
639
+ await mkdir4(dir, { recursive: true, mode: 448 });
640
+ await writeFile4(credPath, JSON.stringify(credentials, null, 2));
340
641
  await chmod3(credPath, 384);
341
642
  }
342
643
  async function getValidCredentials(options) {
@@ -487,16 +788,16 @@ function notifyContextUpdated(repoId, fileCount) {
487
788
 
488
789
  // ../listener/dist/context-fetcher.js
489
790
  import { execFile } from "child_process";
490
- import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
491
- import { dirname as dirname2, join as join7 } from "path";
791
+ import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
792
+ import { dirname as dirname3, join as join8 } from "path";
492
793
  import { promisify } from "util";
493
794
 
494
795
  // ../listener/dist/file-writer.js
495
796
  import { access } from "fs/promises";
496
- import { join as join6 } from "path";
797
+ import { join as join7 } from "path";
497
798
  async function verifyContextFolder(localPath) {
498
799
  try {
499
- await access(join6(localPath, "repowise-context"));
800
+ await access(join7(localPath, "repowise-context"));
500
801
  return true;
501
802
  } catch {
502
803
  return false;
@@ -584,8 +885,8 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
584
885
  if (files.length === 0) {
585
886
  return { success: true, updatedFiles: [] };
586
887
  }
587
- const contextDir = join7(localPath, "repowise-context");
588
- await mkdir4(contextDir, { recursive: true });
888
+ const contextDir = join8(localPath, "repowise-context");
889
+ await mkdir5(contextDir, { recursive: true });
589
890
  const updatedFiles = [];
590
891
  for (const file of files) {
591
892
  if (file.fileName.includes(".."))
@@ -609,9 +910,9 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
609
910
  continue;
610
911
  }
611
912
  const content = await contentRes.text();
612
- const filePath = join7(contextDir, file.fileName);
613
- await mkdir4(dirname2(filePath), { recursive: true });
614
- await writeFile4(filePath, content, "utf-8");
913
+ const filePath = join8(contextDir, file.fileName);
914
+ await mkdir5(dirname3(filePath), { recursive: true });
915
+ await writeFile5(filePath, content, "utf-8");
615
916
  updatedFiles.push(file.fileName);
616
917
  }
617
918
  console.log(`Context fetch for ${repoId}: downloaded ${updatedFiles.length}/${files.length} file(s)`);
@@ -626,19 +927,19 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
626
927
  // ../listener/dist/process-manager.js
627
928
  import { spawn } from "child_process";
628
929
  import { openSync, closeSync } from "fs";
629
- import { readFile as readFile4, writeFile as writeFile5, mkdir as mkdir5, unlink as unlink3 } from "fs/promises";
930
+ import { readFile as readFile5, writeFile as writeFile6, mkdir as mkdir6, unlink as unlink4 } from "fs/promises";
630
931
  import { homedir as homedir2 } from "os";
631
- import { join as join8 } from "path";
932
+ import { join as join9 } from "path";
632
933
  import { createRequire } from "module";
633
934
  import { fileURLToPath } from "url";
634
935
  function repowiseDir() {
635
936
  return getConfigDir();
636
937
  }
637
938
  function pidPath() {
638
- return join8(repowiseDir(), "listener.pid");
939
+ return join9(repowiseDir(), "listener.pid");
639
940
  }
640
941
  function logDirPath() {
641
- return join8(repowiseDir(), "logs");
942
+ return join9(repowiseDir(), "logs");
642
943
  }
643
944
  function resolveListenerCommand() {
644
945
  try {
@@ -652,7 +953,7 @@ function resolveListenerCommand() {
652
953
  }
653
954
  async function readPid() {
654
955
  try {
655
- const content = await readFile4(pidPath(), "utf-8");
956
+ const content = await readFile5(pidPath(), "utf-8");
656
957
  const pid = parseInt(content.trim(), 10);
657
958
  return Number.isNaN(pid) ? null : pid;
658
959
  } catch (err) {
@@ -676,10 +977,10 @@ async function startBackground() {
676
977
  return pid2;
677
978
  }
678
979
  const logDir2 = logDirPath();
679
- await mkdir5(logDir2, { recursive: true });
980
+ await mkdir6(logDir2, { recursive: true });
680
981
  const cmd = resolveListenerCommand();
681
- const stdoutFd = openSync(join8(logDir2, "listener-stdout.log"), "a");
682
- const stderrFd = openSync(join8(logDir2, "listener-stderr.log"), "a");
982
+ const stdoutFd = openSync(join9(logDir2, "listener-stdout.log"), "a");
983
+ const stderrFd = openSync(join9(logDir2, "listener-stderr.log"), "a");
683
984
  const child = spawn(process.execPath, [cmd.script, ...cmd.args], {
684
985
  detached: true,
685
986
  stdio: ["ignore", stdoutFd, stderrFd],
@@ -692,7 +993,7 @@ async function startBackground() {
692
993
  const pid = child.pid;
693
994
  if (!pid)
694
995
  throw new Error("Failed to spawn listener process");
695
- await writeFile5(pidPath(), String(pid));
996
+ await writeFile6(pidPath(), String(pid));
696
997
  return pid;
697
998
  }
698
999
  async function stopProcess() {
@@ -727,7 +1028,7 @@ async function isRunning() {
727
1028
  }
728
1029
  async function removePidFile() {
729
1030
  try {
730
- await unlink3(pidPath());
1031
+ await unlink4(pidPath());
731
1032
  } catch {
732
1033
  }
733
1034
  }
@@ -735,7 +1036,7 @@ async function removePidFile() {
735
1036
  // ../listener/dist/lib/auto-updater.js
736
1037
  import { execFile as execFile2 } from "child_process";
737
1038
  import { access as access2, constants, realpath } from "fs/promises";
738
- import { dirname as dirname3, join as join9 } from "path";
1039
+ import { dirname as dirname4, join as join10 } from "path";
739
1040
  import { promisify as promisify2 } from "util";
740
1041
  var execFileAsync2 = promisify2(execFile2);
741
1042
  async function installUpdate(currentVersion, packageName, targetVersion) {
@@ -747,12 +1048,12 @@ async function installUpdate(currentVersion, packageName, targetVersion) {
747
1048
  console.log(`[auto-update] ${targetVersion} is not newer than ${currentVersion} \u2014 skipping`);
748
1049
  return { updated: false };
749
1050
  }
750
- const npmWrapper = join9(dirname3(process.execPath), "npm");
1051
+ const npmWrapper = join10(dirname4(process.execPath), "npm");
751
1052
  const npmScript = await realpath(npmWrapper);
752
1053
  const runNpm = (args) => execFileAsync2(process.execPath, [npmScript, ...args], { timeout: 6e4 });
753
1054
  try {
754
1055
  const { stdout: prefix } = await runNpm(["prefix", "-g"]);
755
- const npmDir = join9(prefix.trim(), "lib", "node_modules");
1056
+ const npmDir = join10(prefix.trim(), "lib", "node_modules");
756
1057
  const checkDir = process.platform === "win32" ? prefix.trim() : npmDir;
757
1058
  await access2(checkDir, constants.W_OK);
758
1059
  } catch (err) {
@@ -821,14 +1122,14 @@ function comparePrerelease(a, b) {
821
1122
  }
822
1123
 
823
1124
  // ../listener/dist/lifecycle.js
824
- import { unlink as unlink5 } from "fs/promises";
825
- import { join as join11 } from "path";
1125
+ import { unlink as unlink6 } from "fs/promises";
1126
+ import { join as join12 } from "path";
826
1127
 
827
1128
  // ../listener/dist/service-installer.js
828
1129
  import { execFile as execFile3 } from "child_process";
829
- import { writeFile as writeFile6, mkdir as mkdir6, unlink as unlink4 } from "fs/promises";
1130
+ import { writeFile as writeFile7, mkdir as mkdir7, unlink as unlink5 } from "fs/promises";
830
1131
  import { homedir as homedir3 } from "os";
831
- import { join as join10 } from "path";
1132
+ import { join as join11 } from "path";
832
1133
  var IS_STAGING = true ? false : false;
833
1134
  function exec(cmd, args) {
834
1135
  return new Promise((resolve, reject) => {
@@ -843,10 +1144,10 @@ function exec(cmd, args) {
843
1144
  }
844
1145
  var PLIST_LABEL = IS_STAGING ? "com.repowise-staging.listener" : "com.repowise.listener";
845
1146
  function plistPath() {
846
- return join10(homedir3(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
1147
+ return join11(homedir3(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
847
1148
  }
848
1149
  function logDir() {
849
- return join10(getConfigDir(), "logs");
1150
+ return join11(getConfigDir(), "logs");
850
1151
  }
851
1152
  function buildPlist() {
852
1153
  const cmd = resolveListenerCommand();
@@ -868,22 +1169,22 @@ ${programArgs}
868
1169
  <key>KeepAlive</key>
869
1170
  <true/>
870
1171
  <key>StandardOutPath</key>
871
- <string>${join10(logs, "listener-stdout.log")}</string>
1172
+ <string>${join11(logs, "listener-stdout.log")}</string>
872
1173
  <key>StandardErrorPath</key>
873
- <string>${join10(logs, "listener-stderr.log")}</string>
1174
+ <string>${join11(logs, "listener-stderr.log")}</string>
874
1175
  <key>ProcessType</key>
875
1176
  <string>Background</string>
876
1177
  </dict>
877
1178
  </plist>`;
878
1179
  }
879
1180
  async function darwinInstall() {
880
- await mkdir6(logDir(), { recursive: true });
881
- await mkdir6(join10(homedir3(), "Library", "LaunchAgents"), { recursive: true });
1181
+ await mkdir7(logDir(), { recursive: true });
1182
+ await mkdir7(join11(homedir3(), "Library", "LaunchAgents"), { recursive: true });
882
1183
  try {
883
1184
  await exec("launchctl", ["unload", plistPath()]);
884
1185
  } catch {
885
1186
  }
886
- await writeFile6(plistPath(), buildPlist());
1187
+ await writeFile7(plistPath(), buildPlist());
887
1188
  await exec("launchctl", ["load", plistPath()]);
888
1189
  }
889
1190
  async function darwinUninstall() {
@@ -892,7 +1193,7 @@ async function darwinUninstall() {
892
1193
  } catch {
893
1194
  }
894
1195
  try {
895
- await unlink4(plistPath());
1196
+ await unlink5(plistPath());
896
1197
  } catch {
897
1198
  }
898
1199
  }
@@ -906,7 +1207,7 @@ async function darwinIsInstalled() {
906
1207
  }
907
1208
  var SYSTEMD_SERVICE = IS_STAGING ? "repowise-staging-listener" : "repowise-listener";
908
1209
  function unitPath() {
909
- return join10(homedir3(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
1210
+ return join11(homedir3(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
910
1211
  }
911
1212
  function buildUnit() {
912
1213
  const cmd = resolveListenerCommand();
@@ -922,16 +1223,16 @@ Type=simple
922
1223
  ExecStart=${execStart}
923
1224
  Restart=always
924
1225
  RestartSec=10
925
- StandardOutput=append:${join10(logs, "listener-stdout.log")}
926
- StandardError=append:${join10(logs, "listener-stderr.log")}
1226
+ StandardOutput=append:${join11(logs, "listener-stdout.log")}
1227
+ StandardError=append:${join11(logs, "listener-stderr.log")}
927
1228
 
928
1229
  [Install]
929
1230
  WantedBy=default.target`;
930
1231
  }
931
1232
  async function linuxInstall() {
932
- await mkdir6(logDir(), { recursive: true });
933
- await mkdir6(join10(homedir3(), ".config", "systemd", "user"), { recursive: true });
934
- await writeFile6(unitPath(), buildUnit());
1233
+ await mkdir7(logDir(), { recursive: true });
1234
+ await mkdir7(join11(homedir3(), ".config", "systemd", "user"), { recursive: true });
1235
+ await writeFile7(unitPath(), buildUnit());
935
1236
  await exec("systemctl", ["--user", "daemon-reload"]);
936
1237
  await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
937
1238
  await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
@@ -946,7 +1247,7 @@ async function linuxUninstall() {
946
1247
  } catch {
947
1248
  }
948
1249
  try {
949
- await unlink4(unitPath());
1250
+ await unlink5(unitPath());
950
1251
  } catch {
951
1252
  }
952
1253
  try {
@@ -964,7 +1265,7 @@ async function linuxIsInstalled() {
964
1265
  }
965
1266
  var TASK_NAME = IS_STAGING ? "RepoWise Staging Listener" : "RepoWise Listener";
966
1267
  async function win32Install() {
967
- await mkdir6(logDir(), { recursive: true });
1268
+ await mkdir7(logDir(), { recursive: true });
968
1269
  const cmd = resolveListenerCommand();
969
1270
  const taskCmd = [process.execPath, cmd.script, ...cmd.args].map((a) => `"${a}"`).join(" ");
970
1271
  await exec("schtasks", [
@@ -1115,7 +1416,7 @@ async function getListenerStatus() {
1115
1416
  return { running: true, method: "pid", pid, serviceInstalled: serviceInstalled2 };
1116
1417
  }
1117
1418
  try {
1118
- await unlink5(join11(getConfigDir(), "listener.pid"));
1419
+ await unlink6(join12(getConfigDir(), "listener.pid"));
1119
1420
  } catch {
1120
1421
  }
1121
1422
  }
@@ -1163,6 +1464,57 @@ var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
1163
1464
  var STALE_CHECK_COOLDOWN_MS = 60 * 60 * 1e3;
1164
1465
  var CRASH_LOOP_WINDOW_MS = 3e4;
1165
1466
  var CRASH_LOOP_THRESHOLD = 3;
1467
+ async function readRawToolConfig() {
1468
+ try {
1469
+ const configPath = join13(getConfigDir(), "config.json");
1470
+ const data = await readFile6(configPath, "utf-8");
1471
+ const raw = JSON.parse(data);
1472
+ return {
1473
+ aiTools: Array.isArray(raw["aiTools"]) ? raw["aiTools"] : void 0,
1474
+ contextFolder: typeof raw["contextFolder"] === "string" ? raw["contextFolder"] : void 0
1475
+ };
1476
+ } catch {
1477
+ return {};
1478
+ }
1479
+ }
1480
+ async function updateToolConfigsForRepo(localPath, config2, state, repoId) {
1481
+ const contextFolder = config2.contextFolder ?? "repowise-context";
1482
+ const repoName = localPath.split("/").pop() ?? "unknown";
1483
+ let tools = config2.aiTools ?? [];
1484
+ if (tools.length === 0) {
1485
+ tools = await detectInstalledTools(localPath);
1486
+ }
1487
+ if (tools.length === 0)
1488
+ return;
1489
+ const contextFiles = await scanLocalContextFiles(localPath, contextFolder);
1490
+ if (contextFiles.length === 0)
1491
+ return;
1492
+ const hash = JSON.stringify({ tools, files: contextFiles.map((f) => f.fileName) });
1493
+ if (state.repos[repoId]?.lastToolConfigHash === hash)
1494
+ return;
1495
+ const written = /* @__PURE__ */ new Set();
1496
+ for (const tool of tools) {
1497
+ const toolConfig = AI_TOOL_CONFIG[tool];
1498
+ if (!toolConfig)
1499
+ continue;
1500
+ if (written.has(toolConfig.filePath))
1501
+ continue;
1502
+ written.add(toolConfig.filePath);
1503
+ if (toolConfig.legacyFilePath) {
1504
+ await migrateToolConfig(localPath, tool, repoName, contextFolder, contextFiles);
1505
+ } else {
1506
+ await updateToolConfig(localPath, tool, repoName, contextFolder, contextFiles);
1507
+ }
1508
+ }
1509
+ if (!written.has("AGENTS.md")) {
1510
+ await updateToolConfig(localPath, "codex", repoName, contextFolder, contextFiles);
1511
+ }
1512
+ if (!state.repos[repoId]) {
1513
+ state.repos[repoId] = { lastSyncTimestamp: "", lastSyncCommitSha: null };
1514
+ }
1515
+ state.repos[repoId].lastToolConfigHash = hash;
1516
+ console.log(`[ai-tools] Updated tool configs for ${repoId}`);
1517
+ }
1166
1518
  var running = false;
1167
1519
  var sleepResolve = null;
1168
1520
  var releaseLock = null;
@@ -1212,6 +1564,12 @@ async function processNotifications(notifications, state, repoLocalPaths, apiUrl
1212
1564
  if (result.success) {
1213
1565
  updateCount++;
1214
1566
  notifyContextUpdated(notif.repoId, result.updatedFiles.length);
1567
+ try {
1568
+ const toolConfig = await readRawToolConfig();
1569
+ await updateToolConfigsForRepo(localPath, toolConfig, state, notif.repoId);
1570
+ } catch (toolErr) {
1571
+ console.warn(`[ai-tools] Tool config update failed for ${notif.repoId}:`, toolErr instanceof Error ? toolErr.message : toolErr);
1572
+ }
1215
1573
  state.repos[notif.repoId] = {
1216
1574
  ...state.repos[notif.repoId],
1217
1575
  lastSyncTimestamp: notif.createdAt,
@@ -1287,7 +1645,7 @@ async function checkStaleContext(repos, state, groups) {
1287
1645
  if (group?.offline.isOffline)
1288
1646
  continue;
1289
1647
  const { statSync: statSync2, readdirSync: readdirSync2 } = await import("fs");
1290
- const contextPath = join12(repo.localPath, "repowise-context");
1648
+ const contextPath = join13(repo.localPath, "repowise-context");
1291
1649
  let isMissingOrEmpty = false;
1292
1650
  try {
1293
1651
  const s = statSync2(contextPath);
@@ -1319,9 +1677,9 @@ async function checkStaleContext(repos, state, groups) {
1319
1677
  async function startListener() {
1320
1678
  running = true;
1321
1679
  const configDir = getConfigDir();
1322
- await mkdir7(configDir, { recursive: true });
1323
- const lockPath = join12(configDir, "listener.lock");
1324
- await writeFile7(lockPath, "", { flag: "a" });
1680
+ await mkdir8(configDir, { recursive: true });
1681
+ const lockPath = join13(configDir, "listener.lock");
1682
+ await writeFile8(lockPath, "", { flag: "a" });
1325
1683
  let lockIsHeld = false;
1326
1684
  try {
1327
1685
  lockIsHeld = await lockfile2.check(lockPath, { stale: 3e4, realpath: false });
@@ -1363,7 +1721,7 @@ async function startListener() {
1363
1721
  return;
1364
1722
  }
1365
1723
  if (config2.repos.length === 0 && !config2.autoDiscoverRepos) {
1366
- console.error(`No repos configured. Add repos to ${join12(configDir, "config.json")}`);
1724
+ console.error(`No repos configured. Add repos to ${join13(configDir, "config.json")}`);
1367
1725
  await releaseLockAndExit();
1368
1726
  process.exitCode = 1;
1369
1727
  return;
@@ -1430,9 +1788,9 @@ async function startListener() {
1430
1788
  const packageName = true ? "repowise" : "repowise";
1431
1789
  let currentVersion = "";
1432
1790
  try {
1433
- const selfDir = dirname4(fileURLToPath2(import.meta.url));
1434
- const pkgJsonPath = join12(selfDir, "..", "..", "package.json");
1435
- const pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf-8"));
1791
+ const selfDir = dirname5(fileURLToPath2(import.meta.url));
1792
+ const pkgJsonPath = join13(selfDir, "..", "..", "package.json");
1793
+ const pkgJson = JSON.parse(await readFile6(pkgJsonPath, "utf-8"));
1436
1794
  currentVersion = pkgJson.version;
1437
1795
  } catch (err) {
1438
1796
  console.log(`[auto-update] Version detection failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -1651,7 +2009,7 @@ async function startListener() {
1651
2009
  } catch {
1652
2010
  }
1653
2011
  }
1654
- const credentialsPath = join12(getConfigDir(), "credentials.json");
2012
+ const credentialsPath = join13(getConfigDir(), "credentials.json");
1655
2013
  let credentialsChanged = false;
1656
2014
  let watcher = null;
1657
2015
  try {
@@ -1697,7 +2055,7 @@ if (isDirectRun) {
1697
2055
 
1698
2056
  // src/lib/env.ts
1699
2057
  import { homedir as homedir4 } from "os";
1700
- import { join as join13 } from "path";
2058
+ import { join as join14 } from "path";
1701
2059
  var IS_STAGING2 = true ? false : false;
1702
2060
  var PRODUCTION = {
1703
2061
  apiUrl: "https://api.repowise.ai",
@@ -1717,7 +2075,7 @@ function getEnvConfig() {
1717
2075
  return IS_STAGING2 ? STAGING : PRODUCTION;
1718
2076
  }
1719
2077
  function getConfigDir2() {
1720
- return join13(homedir4(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
2078
+ return join14(homedir4(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
1721
2079
  }
1722
2080
  function getPackageName() {
1723
2081
  return true ? "repowise" : "repowise";
@@ -1727,12 +2085,12 @@ function getPackageName() {
1727
2085
  import chalk from "chalk";
1728
2086
 
1729
2087
  // src/lib/config.ts
1730
- import { readFile as readFile6, writeFile as writeFile8, mkdir as mkdir8, rename as rename3, unlink as unlink6 } from "fs/promises";
1731
- import { join as join14 } from "path";
2088
+ import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, rename as rename3, unlink as unlink7 } from "fs/promises";
2089
+ import { join as join15 } from "path";
1732
2090
  import lockfile3 from "proper-lockfile";
1733
2091
  async function getConfig() {
1734
2092
  try {
1735
- const data = await readFile6(join14(getConfigDir2(), "config.json"), "utf-8");
2093
+ const data = await readFile7(join15(getConfigDir2(), "config.json"), "utf-8");
1736
2094
  return JSON.parse(data);
1737
2095
  } catch {
1738
2096
  return {};
@@ -1740,15 +2098,15 @@ async function getConfig() {
1740
2098
  }
1741
2099
  async function saveConfig(config2) {
1742
2100
  const dir = getConfigDir2();
1743
- const path = join14(dir, "config.json");
1744
- await mkdir8(dir, { recursive: true });
2101
+ const path = join15(dir, "config.json");
2102
+ await mkdir9(dir, { recursive: true });
1745
2103
  const tmpPath = path + ".tmp";
1746
2104
  try {
1747
- await writeFile8(tmpPath, JSON.stringify(config2, null, 2));
2105
+ await writeFile9(tmpPath, JSON.stringify(config2, null, 2));
1748
2106
  await rename3(tmpPath, path);
1749
2107
  } catch (err) {
1750
2108
  try {
1751
- await unlink6(tmpPath);
2109
+ await unlink7(tmpPath);
1752
2110
  } catch {
1753
2111
  }
1754
2112
  throw err;
@@ -1756,10 +2114,10 @@ async function saveConfig(config2) {
1756
2114
  }
1757
2115
  async function mergeAndSaveConfig(updates) {
1758
2116
  const dir = getConfigDir2();
1759
- const path = join14(dir, "config.json");
1760
- await mkdir8(dir, { recursive: true });
2117
+ const path = join15(dir, "config.json");
2118
+ await mkdir9(dir, { recursive: true });
1761
2119
  try {
1762
- await writeFile8(path, "", { flag: "a" });
2120
+ await writeFile9(path, "", { flag: "a" });
1763
2121
  } catch {
1764
2122
  }
1765
2123
  let release = null;
@@ -1767,7 +2125,7 @@ async function mergeAndSaveConfig(updates) {
1767
2125
  release = await lockfile3.lock(path, { stale: 1e4, retries: 3, realpath: false });
1768
2126
  let raw = {};
1769
2127
  try {
1770
- raw = JSON.parse(await readFile6(path, "utf-8"));
2128
+ raw = JSON.parse(await readFile7(path, "utf-8"));
1771
2129
  } catch {
1772
2130
  }
1773
2131
  const merged = { ...raw, ...updates };
@@ -1827,15 +2185,110 @@ async function showWelcome(currentVersion) {
1827
2185
 
1828
2186
  // src/commands/create.ts
1829
2187
  import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
1830
- import { dirname as dirname6, join as join18 } from "path";
2188
+ import { dirname as dirname6, join as join19 } from "path";
1831
2189
  import chalk5 from "chalk";
1832
2190
  import ora from "ora";
1833
2191
 
2192
+ // ../../packages/shared/src/constants/tiers.ts
2193
+ var SUBSCRIPTION_TIERS = {
2194
+ STARTER: "starter",
2195
+ PRO: "pro",
2196
+ TEAM: "team"
2197
+ };
2198
+ var TIER_LIMITS = {
2199
+ [SUBSCRIPTION_TIERS.STARTER]: {
2200
+ maxRepos: 1,
2201
+ maxSeats: 1,
2202
+ maxSyncsPerMonth: 10,
2203
+ maxConcurrentSyncs: 1,
2204
+ bedrockEndpoint: "public"
2205
+ },
2206
+ [SUBSCRIPTION_TIERS.PRO]: {
2207
+ maxRepos: 2,
2208
+ maxSeats: 1,
2209
+ maxSyncsPerMonth: 30,
2210
+ maxConcurrentSyncs: 2,
2211
+ bedrockEndpoint: "public"
2212
+ },
2213
+ [SUBSCRIPTION_TIERS.TEAM]: {
2214
+ maxRepos: 3,
2215
+ maxSeats: 999999,
2216
+ maxSyncsPerMonth: 30,
2217
+ maxConcurrentSyncs: 10,
2218
+ bedrockEndpoint: "public"
2219
+ }
2220
+ };
2221
+
2222
+ // ../../packages/shared/src/constants/roles.ts
2223
+ var ROLES = {
2224
+ OWNER: "owner",
2225
+ ADMIN: "admin",
2226
+ REPO_MANAGER: "repo_manager",
2227
+ MEMBER: "member"
2228
+ };
2229
+ var PERMISSIONS = {
2230
+ [ROLES.OWNER]: {
2231
+ connectRepos: true,
2232
+ viewSync: true,
2233
+ triggerRetry: true,
2234
+ configWatcher: true,
2235
+ inviteMembers: true,
2236
+ manageBilling: true,
2237
+ configAiTools: true,
2238
+ manageTeam: true
2239
+ },
2240
+ [ROLES.ADMIN]: {
2241
+ connectRepos: true,
2242
+ viewSync: true,
2243
+ triggerRetry: true,
2244
+ configWatcher: true,
2245
+ inviteMembers: true,
2246
+ manageBilling: false,
2247
+ configAiTools: true,
2248
+ manageTeam: true
2249
+ },
2250
+ [ROLES.REPO_MANAGER]: {
2251
+ connectRepos: true,
2252
+ viewSync: true,
2253
+ triggerRetry: true,
2254
+ configWatcher: true,
2255
+ inviteMembers: false,
2256
+ manageBilling: false,
2257
+ configAiTools: true,
2258
+ manageTeam: false
2259
+ },
2260
+ [ROLES.MEMBER]: {
2261
+ connectRepos: false,
2262
+ viewSync: true,
2263
+ triggerRetry: true,
2264
+ configWatcher: false,
2265
+ inviteMembers: false,
2266
+ manageBilling: false,
2267
+ configAiTools: true,
2268
+ manageTeam: false
2269
+ }
2270
+ };
2271
+ function hasPermission(role, permission) {
2272
+ return PERMISSIONS[role][permission];
2273
+ }
2274
+ var ROLE_PRIORITY = [
2275
+ ROLES.OWNER,
2276
+ ROLES.ADMIN,
2277
+ ROLES.REPO_MANAGER,
2278
+ ROLES.MEMBER
2279
+ ];
2280
+ function resolveRole(groups) {
2281
+ for (const role of ROLE_PRIORITY) {
2282
+ if (groups.includes(role)) return role;
2283
+ }
2284
+ return ROLES.MEMBER;
2285
+ }
2286
+
1834
2287
  // src/lib/auth.ts
1835
2288
  import { createHash, randomBytes } from "crypto";
1836
- import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as unlink7 } from "fs/promises";
2289
+ import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir10, chmod as chmod4, unlink as unlink8 } from "fs/promises";
1837
2290
  import http from "http";
1838
- import { join as join15 } from "path";
2291
+ import { join as join16 } from "path";
1839
2292
  var CLI_CALLBACK_PORT = 19876;
1840
2293
  var CALLBACK_TIMEOUT_MS = 12e4;
1841
2294
  function getCognitoConfigForStorage() {
@@ -2001,8 +2454,8 @@ async function refreshTokens2(refreshToken) {
2001
2454
  }
2002
2455
  async function getStoredCredentials2() {
2003
2456
  try {
2004
- const credPath = join15(getConfigDir2(), "credentials.json");
2005
- const data = await readFile7(credPath, "utf-8");
2457
+ const credPath = join16(getConfigDir2(), "credentials.json");
2458
+ const data = await readFile8(credPath, "utf-8");
2006
2459
  return JSON.parse(data);
2007
2460
  } catch (err) {
2008
2461
  if (err.code === "ENOENT" || err instanceof SyntaxError) {
@@ -2013,14 +2466,14 @@ async function getStoredCredentials2() {
2013
2466
  }
2014
2467
  async function storeCredentials2(credentials) {
2015
2468
  const dir = getConfigDir2();
2016
- const credPath = join15(dir, "credentials.json");
2017
- await mkdir9(dir, { recursive: true, mode: 448 });
2018
- await writeFile9(credPath, JSON.stringify(credentials, null, 2));
2469
+ const credPath = join16(dir, "credentials.json");
2470
+ await mkdir10(dir, { recursive: true, mode: 448 });
2471
+ await writeFile10(credPath, JSON.stringify(credentials, null, 2));
2019
2472
  await chmod4(credPath, 384);
2020
2473
  }
2021
2474
  async function clearCredentials() {
2022
2475
  try {
2023
- await unlink7(join15(getConfigDir2(), "credentials.json"));
2476
+ await unlink8(join16(getConfigDir2(), "credentials.json"));
2024
2477
  } catch (err) {
2025
2478
  if (err.code !== "ENOENT") throw err;
2026
2479
  }
@@ -2110,7 +2563,11 @@ function decodeIdToken(idToken) {
2110
2563
  const parts = idToken.split(".");
2111
2564
  if (parts.length < 2) return { email: "unknown" };
2112
2565
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
2113
- return { email: payload.email ?? "unknown", tenantId: payload["custom:tenant_id"] };
2566
+ return {
2567
+ email: payload.email ?? "unknown",
2568
+ tenantId: payload["custom:tenant_id"],
2569
+ groups: payload["cognito:groups"]
2570
+ };
2114
2571
  } catch {
2115
2572
  return { email: "unknown" };
2116
2573
  }
@@ -2179,23 +2636,34 @@ async function apiRequest(path, options) {
2179
2636
  }
2180
2637
 
2181
2638
  // src/lib/prompts.ts
2182
- import { checkbox, confirm } from "@inquirer/prompts";
2639
+ import { checkbox, confirm, Separator } from "@inquirer/prompts";
2183
2640
  import chalk2 from "chalk";
2184
2641
  async function selectAiTools() {
2185
2642
  const choices = [
2643
+ new Separator(chalk2.dim("\u2500\u2500 Popular \u2500\u2500")),
2186
2644
  { name: "Cursor", value: "cursor" },
2187
2645
  { name: "Claude Code", value: "claude-code" },
2188
2646
  { name: "GitHub Copilot", value: "copilot" },
2189
2647
  { name: "Windsurf", value: "windsurf" },
2648
+ new Separator(chalk2.dim("\u2500\u2500 More Tools \u2500\u2500")),
2190
2649
  { name: "Cline", value: "cline" },
2191
2650
  { name: "Codex", value: "codex" },
2192
2651
  { name: "Roo Code", value: "roo-code" },
2652
+ { name: "Gemini CLI", value: "gemini" },
2653
+ new Separator(chalk2.dim("\u2500\u2500 Cloud Agents \u2500\u2500")),
2654
+ { name: "Warp", value: "warp" },
2655
+ { name: "JetBrains Junie", value: "junie" },
2656
+ { name: "Google Jules", value: "jules" },
2657
+ { name: "Amp", value: "amp" },
2658
+ { name: "Devin", value: "devin" },
2659
+ new Separator(chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),
2193
2660
  { name: "Other (manual setup)", value: "other" }
2194
2661
  ];
2195
2662
  while (true) {
2196
2663
  const selected = await checkbox({
2197
2664
  message: chalk2.bold("Which AI tools do you use?") + chalk2.dim(" (Space to select, Enter to continue)"),
2198
- choices
2665
+ choices,
2666
+ pageSize: 22
2199
2667
  });
2200
2668
  if (selected.length === 0) {
2201
2669
  const goBack = await confirm({
@@ -2211,191 +2679,54 @@ async function selectAiTools() {
2211
2679
  }
2212
2680
 
2213
2681
  // src/lib/ai-tools.ts
2214
- import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir10, readdir } from "fs/promises";
2215
- import { join as join16, dirname as dirname5 } from "path";
2216
- var AI_TOOL_CONFIG = {
2217
- cursor: {
2218
- label: "Cursor",
2219
- fileName: ".cursorrules",
2220
- filePath: ".cursorrules",
2221
- markerStart: "# --- repowise-start ---",
2222
- markerEnd: "# --- repowise-end ---",
2223
- format: "plain-text"
2224
- },
2225
- "claude-code": {
2226
- label: "Claude Code",
2227
- fileName: "CLAUDE.md",
2228
- filePath: "CLAUDE.md",
2229
- markerStart: "<!-- repowise-start -->",
2230
- markerEnd: "<!-- repowise-end -->",
2231
- format: "markdown"
2232
- },
2233
- copilot: {
2234
- label: "GitHub Copilot",
2235
- fileName: "copilot-instructions.md",
2236
- filePath: ".github/copilot-instructions.md",
2237
- markerStart: "<!-- repowise-start -->",
2238
- markerEnd: "<!-- repowise-end -->",
2239
- format: "markdown"
2240
- },
2241
- windsurf: {
2242
- label: "Windsurf",
2243
- fileName: ".windsurfrules",
2244
- filePath: ".windsurfrules",
2245
- markerStart: "# --- repowise-start ---",
2246
- markerEnd: "# --- repowise-end ---",
2247
- format: "plain-text"
2248
- },
2249
- cline: {
2250
- label: "Cline",
2251
- fileName: ".clinerules",
2252
- filePath: ".clinerules",
2253
- markerStart: "# --- repowise-start ---",
2254
- markerEnd: "# --- repowise-end ---",
2255
- format: "plain-text"
2256
- },
2257
- codex: {
2258
- label: "Codex",
2259
- fileName: "AGENTS.md",
2260
- filePath: "AGENTS.md",
2261
- markerStart: "<!-- repowise-start -->",
2262
- markerEnd: "<!-- repowise-end -->",
2263
- format: "markdown"
2264
- },
2265
- "roo-code": {
2266
- label: "Roo Code",
2267
- fileName: "rules.md",
2268
- filePath: ".roo/rules.md",
2269
- markerStart: "<!-- repowise-start -->",
2270
- markerEnd: "<!-- repowise-end -->",
2271
- format: "markdown"
2272
- }
2273
- };
2274
- var SUPPORTED_TOOLS = Object.keys(AI_TOOL_CONFIG);
2275
- function sanitizeRepoName(name) {
2276
- return name.replace(/[<>[\]`()|\\]/g, "");
2277
- }
2278
- function fileDescriptionFromName(fileName) {
2279
- return fileName.replace(/\.md$/, "").split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2280
- }
2281
- function generateReference(tool, repoName, contextFolder, contextFiles) {
2282
- const config2 = AI_TOOL_CONFIG[tool];
2283
- const safeName = sanitizeRepoName(repoName);
2284
- const fileLines = contextFiles.map((f) => {
2285
- const baseName = f.fileName.split("/").pop() ?? f.fileName;
2286
- const desc = fileDescriptionFromName(baseName);
2287
- const isOverview = baseName === "project-overview.md";
2288
- return { path: f.relativePath, desc: isOverview ? `${desc} (full index of all files)` : desc };
2289
- });
2290
- const hasFiles = fileLines.length > 0;
2291
- if (config2.format === "markdown") {
2292
- const lines2 = [
2293
- config2.markerStart,
2294
- "",
2295
- `## Project Context \u2014 ${safeName}`,
2296
- "",
2297
- `This repository has AI-optimized context files generated by RepoWise.`,
2298
- `**IMPORTANT: Before answering questions about the codebase or making any changes, ALWAYS check the \`${contextFolder}/\` folder first.** These files contain pre-analyzed architecture, patterns, API contracts, and domain knowledge that will answer most questions without needing to search the codebase.`,
2299
- "",
2300
- `**Start here:** \`${contextFolder}/project-overview.md\` \u2014 the routing document that maps every context file to its domain. Read it first to find which context file has the answer you need.`,
2301
- ""
2302
- ];
2303
- if (hasFiles) {
2304
- lines2.push(
2305
- `**Core context files:**`,
2306
- "",
2307
- ...fileLines.map((f) => `- \`${f.path}\` \u2014 ${f.desc}`),
2308
- "",
2309
- `> Additional context files may exist beyond this list. Check \`project-overview.md\` for the complete index.`
2310
- );
2311
- }
2312
- lines2.push("", config2.markerEnd);
2313
- return lines2.join("\n");
2314
- }
2315
- const lines = [
2316
- config2.markerStart,
2317
- `# Project Context \u2014 ${safeName}`,
2318
- "#",
2319
- `# This repository has AI-optimized context files generated by RepoWise.`,
2320
- `# IMPORTANT: Before answering questions about the codebase or making any changes,`,
2321
- `# ALWAYS check the ${contextFolder}/ folder first. These files contain pre-analyzed`,
2322
- `# architecture, patterns, API contracts, and domain knowledge that will answer`,
2323
- `# most questions without needing to search the codebase.`,
2324
- "#",
2325
- `# Start here: ${contextFolder}/project-overview.md`,
2326
- `# The routing document that maps every context file to its domain.`,
2327
- `# Read it first to find which context file has the answer you need.`
2328
- ];
2329
- if (hasFiles) {
2330
- lines.push(
2331
- "#",
2332
- `# Core context files:`,
2333
- ...fileLines.map((f) => `# ${f.path} \u2014 ${f.desc}`),
2334
- "#",
2335
- "# Additional context files may exist beyond this list.",
2336
- "# Check project-overview.md for the complete index."
2337
- );
2338
- }
2339
- lines.push(config2.markerEnd);
2340
- return lines.join("\n");
2341
- }
2342
- async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
2343
- const config2 = AI_TOOL_CONFIG[tool];
2344
- const fullPath = join16(repoRoot, config2.filePath);
2345
- const dir = dirname5(fullPath);
2346
- if (dir !== repoRoot) {
2347
- await mkdir10(dir, { recursive: true });
2348
- }
2349
- const referenceBlock = generateReference(tool, repoName, contextFolder, contextFiles);
2350
- let existing = "";
2351
- let created = true;
2682
+ import { readFile as readFile9, writeFile as writeFile11, mkdir as mkdir11 } from "fs/promises";
2683
+ import { join as join17 } from "path";
2684
+ var REPOWISE_HOOK_MARKER = "repowise-context";
2685
+ async function writeClaudeSubagentHook(repoRoot, contextFolder) {
2686
+ const settingsPath = join17(repoRoot, ".claude", "settings.json");
2687
+ let settings = {};
2352
2688
  try {
2353
- existing = await readFile8(fullPath, "utf-8");
2354
- created = false;
2355
- } catch (err) {
2356
- if (err.code !== "ENOENT") throw err;
2689
+ const raw = await readFile9(settingsPath, "utf-8");
2690
+ settings = JSON.parse(raw);
2691
+ } catch {
2357
2692
  }
2358
- const startIdx = existing.indexOf(config2.markerStart);
2359
- const endIdx = existing.indexOf(config2.markerEnd);
2360
- let content;
2361
- if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
2362
- const before = existing.slice(0, startIdx);
2363
- const after = existing.slice(endIdx + config2.markerEnd.length);
2364
- content = before + referenceBlock + after;
2365
- } else {
2366
- const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
2367
- content = existing + separator + referenceBlock + "\n";
2693
+ if (!settings["hooks"] || typeof settings["hooks"] !== "object") {
2694
+ settings["hooks"] = {};
2368
2695
  }
2369
- await writeFile10(fullPath, content, "utf-8");
2370
- return { created };
2371
- }
2372
- async function scanLocalContextFiles(repoRoot, contextFolder) {
2373
- const folderPath = join16(repoRoot, contextFolder);
2374
- try {
2375
- const entries = await readdir(folderPath, { withFileTypes: true, recursive: true });
2376
- const results = [];
2377
- for (const entry of entries) {
2378
- if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2379
- const parentDir = entry.parentPath ?? folderPath;
2380
- const fullPath = join16(parentDir, entry.name);
2381
- const relFromContext = fullPath.slice(folderPath.length + 1);
2382
- results.push({
2383
- fileName: relFromContext,
2384
- relativePath: `${contextFolder}/${relFromContext}`
2385
- });
2386
- }
2387
- return results.sort((a, b) => a.fileName.localeCompare(b.fileName));
2388
- } catch (err) {
2389
- if (err.code === "ENOENT") return [];
2390
- throw err;
2696
+ const hooks = settings["hooks"];
2697
+ const repoWiseHook = {
2698
+ matcher: "",
2699
+ hooks: [
2700
+ {
2701
+ type: "command",
2702
+ command: `echo '{"additionalContext":"IMPORTANT: Read ${contextFolder}/project-overview.md before performing any work. This file maps every context file to its domain."}'`
2703
+ }
2704
+ ]
2705
+ };
2706
+ const subagentStart = Array.isArray(hooks["SubagentStart"]) ? hooks["SubagentStart"] : [];
2707
+ const existingIdx = subagentStart.findIndex((entry) => {
2708
+ const entryHooks = entry["hooks"];
2709
+ return entryHooks?.some((h) => {
2710
+ const cmd = h["command"];
2711
+ return typeof cmd === "string" && cmd.includes(REPOWISE_HOOK_MARKER);
2712
+ });
2713
+ });
2714
+ if (existingIdx >= 0) {
2715
+ subagentStart[existingIdx] = repoWiseHook;
2716
+ } else {
2717
+ subagentStart.push(repoWiseHook);
2391
2718
  }
2719
+ hooks["SubagentStart"] = subagentStart;
2720
+ settings["hooks"] = hooks;
2721
+ await mkdir11(join17(repoRoot, ".claude"), { recursive: true });
2722
+ await writeFile11(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2392
2723
  }
2393
2724
 
2394
2725
  // src/lib/gitignore.ts
2395
2726
  import { readFileSync, writeFileSync, existsSync } from "fs";
2396
- import { join as join17 } from "path";
2727
+ import { join as join18 } from "path";
2397
2728
  function ensureGitignore(repoRoot, entry) {
2398
- const gitignorePath = join17(repoRoot, ".gitignore");
2729
+ const gitignorePath = join18(repoRoot, ".gitignore");
2399
2730
  if (existsSync(gitignorePath)) {
2400
2731
  const content = readFileSync(gitignorePath, "utf-8");
2401
2732
  const lines = content.split("\n").map((l) => l.trim());
@@ -2428,10 +2759,57 @@ function detectRepoName(repoRoot) {
2428
2759
  }
2429
2760
 
2430
2761
  // src/lib/interview-handler.ts
2762
+ import { createInterface } from "readline";
2431
2763
  import chalk3 from "chalk";
2432
- import { input } from "@inquirer/prompts";
2433
2764
  var INTERVIEW_TIMEOUT_MS = 5 * 60 * 1e3;
2434
2765
  var MAX_QUESTIONS = 10;
2766
+ function multilineInput() {
2767
+ return new Promise((resolve, reject) => {
2768
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2769
+ const lines = [];
2770
+ let lastLineTime = 0;
2771
+ let resolved = false;
2772
+ const done = (value) => {
2773
+ if (resolved) return;
2774
+ resolved = true;
2775
+ rl.close();
2776
+ resolve(value);
2777
+ };
2778
+ rl.setPrompt(" > ");
2779
+ rl.prompt();
2780
+ rl.on("line", (line) => {
2781
+ const now = Date.now();
2782
+ const elapsed = now - lastLineTime;
2783
+ lastLineTime = now;
2784
+ const lower = line.trim().toLowerCase();
2785
+ if (lower === "done" || lower === "skip") {
2786
+ done(lower);
2787
+ return;
2788
+ }
2789
+ if (lines.length === 0) {
2790
+ if (line === "") {
2791
+ done("");
2792
+ return;
2793
+ }
2794
+ lines.push(line);
2795
+ rl.setPrompt(" > ");
2796
+ rl.prompt();
2797
+ return;
2798
+ }
2799
+ if (elapsed > 50 && line === "") {
2800
+ done(lines.join("\n").trim());
2801
+ return;
2802
+ }
2803
+ lines.push(line);
2804
+ rl.setPrompt(" > ");
2805
+ rl.prompt();
2806
+ });
2807
+ rl.on("close", () => {
2808
+ done(lines.length > 0 ? lines.join("\n").trim() : "");
2809
+ });
2810
+ rl.on("error", reject);
2811
+ });
2812
+ }
2435
2813
  var questionCounter = 0;
2436
2814
  async function handleInterview(syncId, questionId, questionText, questionContext, estimatedQuestions) {
2437
2815
  questionCounter++;
@@ -2452,14 +2830,11 @@ async function handleInterview(syncId, questionId, questionText, questionContext
2452
2830
  console.log(chalk3.dim(` ${questionContext}`));
2453
2831
  }
2454
2832
  console.log(` ${questionText}`);
2455
- console.log(chalk3.dim(' (Enter to skip \xB7 "done" to finish early)'));
2833
+ console.log(chalk3.dim(' (Enter to submit \xB7 Enter to skip \xB7 "done" to finish early)'));
2456
2834
  let answer;
2457
2835
  try {
2458
2836
  answer = await Promise.race([
2459
- input({
2460
- message: chalk3.cyan(">"),
2461
- theme: { prefix: " " }
2462
- }),
2837
+ multilineInput(),
2463
2838
  new Promise(
2464
2839
  (_, reject) => setTimeout(() => reject(new Error("INTERVIEW_TIMEOUT")), INTERVIEW_TIMEOUT_MS)
2465
2840
  )
@@ -2650,6 +3025,11 @@ var ProgressRenderer = class {
2650
3025
  spinner.stop();
2651
3026
  console.log("");
2652
3027
  console.log(chalk4.cyan.bold(" \u2500\u2500 Code Analysis \u2500\u2500"));
3028
+ console.log(
3029
+ chalk4.cyan(
3030
+ " \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
3031
+ )
3032
+ );
2653
3033
  console.log(chalk4.dim(" Analyzing your codebase structure, functions, and relationships."));
2654
3034
  spinner.start();
2655
3035
  }
@@ -2669,11 +3049,6 @@ var ProgressRenderer = class {
2669
3049
  this.generationHeaderShown = true;
2670
3050
  spinner.stop();
2671
3051
  console.log(chalk4.cyan.bold(" \u2500\u2500 Context Generation \u2500\u2500"));
2672
- console.log(
2673
- chalk4.cyan(
2674
- " \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
2675
- )
2676
- );
2677
3052
  console.log("");
2678
3053
  spinner.start();
2679
3054
  }
@@ -2776,7 +3151,7 @@ var ProgressRenderer = class {
2776
3151
  getSpinnerText(syncResult) {
2777
3152
  const overallPct = computeOverallProgress(syncResult);
2778
3153
  const stepLabel = syncResult.stepLabel ?? syncResult.currentStep ?? "Processing";
2779
- if (syncResult.scanProgress && !syncResult.scanProgress.summary) {
3154
+ if (syncResult.scanProgress && !syncResult.scanProgress.summary && syncResult.currentStep === "scan-and-generate") {
2780
3155
  const sp = syncResult.scanProgress;
2781
3156
  const pct = sp.totalBatches > 0 ? Math.round(sp.currentBatch / sp.totalBatches * 100) : 0;
2782
3157
  return `Scanning batch ${sp.currentBatch}/${sp.totalBatches} ${chalk4.dim(`(${pct}%)`)}`;
@@ -2922,26 +3297,51 @@ async function create() {
2922
3297
  try {
2923
3298
  const pricing = await apiRequest(`/v1/repos/${repoId}/rescan-pricing`);
2924
3299
  if (pricing.lastFullScanAt) {
2925
- if (pricing.allowed && pricing.isFree) {
2926
- spinner.succeed(chalk5.cyan("Free rescan available. Will trigger a full rescan."));
2927
- useFreeRescan = true;
2928
- } else {
3300
+ const { groups } = decodeIdToken(credentials.idToken);
3301
+ const role = resolveRole(groups ?? []);
3302
+ const canRescan = hasPermission(role, "connectRepos");
3303
+ if (!canRescan) {
2929
3304
  spinner.fail(chalk5.red("This repository already has context generated."));
2930
3305
  console.log(
2931
3306
  chalk5.cyan(
2932
3307
  `
2933
- If you're a team member, run: ${chalk5.bold("repowise member")}
3308
+ As a team member, run: ${chalk5.bold("repowise member")}
2934
3309
  This will download context and configure your AI tools.
2935
3310
  `
2936
3311
  )
2937
3312
  );
3313
+ process.exitCode = 1;
3314
+ return;
3315
+ }
3316
+ if (pricing.allowed && pricing.isFree) {
3317
+ spinner.succeed(chalk5.cyan("Free rescan available. Will trigger a full rescan."));
3318
+ useFreeRescan = true;
3319
+ } else if (pricing.allowed) {
3320
+ spinner.stop();
3321
+ const costText = pricing.estimatedCost && pricing.estimatedCost > 0 ? `$${pricing.estimatedCost.toFixed(2)}` : "pay-as-you-go (based on actual processing cost)";
2938
3322
  console.log(
2939
- chalk5.dim(
2940
- ` To trigger a new full scan, visit: https://app.repowise.ai/repos/${repoId}
2941
- To sync recent changes, use: repowise sync
2942
- `
3323
+ chalk5.yellow(
3324
+ `
3325
+ This repository already has context generated.
3326
+ A full rescan will cost: ${chalk5.bold(costText)}`
2943
3327
  )
2944
3328
  );
3329
+ if (pricing.costWarning) {
3330
+ console.log(chalk5.dim(` ${pricing.costWarning}`));
3331
+ }
3332
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
3333
+ const proceed = await confirm2({
3334
+ message: "Proceed with paid full rescan?",
3335
+ default: false
3336
+ });
3337
+ if (!proceed) {
3338
+ console.log(chalk5.dim("\n To sync recent changes, use: repowise sync\n"));
3339
+ process.exitCode = 0;
3340
+ return;
3341
+ }
3342
+ useFreeRescan = true;
3343
+ } else {
3344
+ spinner.fail(chalk5.red(pricing.reason ?? "Cannot rescan at this time."));
2945
3345
  process.exitCode = 1;
2946
3346
  return;
2947
3347
  }
@@ -2952,7 +3352,7 @@ async function create() {
2952
3352
  if (hasOther) {
2953
3353
  console.log(
2954
3354
  chalk5.cyan(
2955
- "\nFor AI tools not listed, context files still work with any tool that reads the filesystem.\nRequest support for your tool at: https://dashboard.repowise.ai/support/ai-tools"
3355
+ "\nFor AI tools not listed, context files still work with any tool that reads the filesystem.\nTo request full support for a new AI tool, email support@repowise.ai"
2956
3356
  )
2957
3357
  );
2958
3358
  }
@@ -3069,7 +3469,7 @@ async function create() {
3069
3469
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
3070
3470
  const files = listResult.data?.files ?? listResult.files ?? [];
3071
3471
  if (files.length > 0) {
3072
- const contextDir = join18(repoRoot, DEFAULT_CONTEXT_FOLDER);
3472
+ const contextDir = join19(repoRoot, DEFAULT_CONTEXT_FOLDER);
3073
3473
  mkdirSync(contextDir, { recursive: true });
3074
3474
  let downloadedCount = 0;
3075
3475
  let failedCount = 0;
@@ -3083,7 +3483,7 @@ async function create() {
3083
3483
  const response = await fetch(presignedUrl);
3084
3484
  if (response.ok) {
3085
3485
  const content = await response.text();
3086
- const filePath = join18(contextDir, file.fileName);
3486
+ const filePath = join19(contextDir, file.fileName);
3087
3487
  mkdirSync(dirname6(filePath), { recursive: true });
3088
3488
  writeFileSync2(filePath, content, "utf-8");
3089
3489
  downloadedCount++;
@@ -3127,10 +3527,17 @@ Files are stored on our servers (not in git). Retry when online.`
3127
3527
  )
3128
3528
  );
3129
3529
  }
3130
- if (tools.length > 0 && repoRoot) {
3530
+ if (repoRoot) {
3131
3531
  spinner.start("Configuring AI tools...");
3132
3532
  const results = [];
3533
+ const written = /* @__PURE__ */ new Set();
3133
3534
  for (const tool of tools) {
3535
+ const config2 = AI_TOOL_CONFIG[tool];
3536
+ if (written.has(config2.filePath)) continue;
3537
+ written.add(config2.filePath);
3538
+ if (config2.legacyFilePath) {
3539
+ await migrateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles);
3540
+ }
3134
3541
  const { created: wasCreated } = await updateToolConfig(
3135
3542
  repoRoot,
3136
3543
  tool,
@@ -3138,10 +3545,23 @@ Files are stored on our servers (not in git). Retry when online.`
3138
3545
  contextFolder,
3139
3546
  contextFiles
3140
3547
  );
3141
- const config2 = AI_TOOL_CONFIG[tool];
3142
3548
  const action = wasCreated ? "Created" : "Updated";
3143
3549
  results.push(` ${action} ${config2.filePath}`);
3144
3550
  }
3551
+ if (!written.has("AGENTS.md")) {
3552
+ const { created: wasCreated } = await updateToolConfig(
3553
+ repoRoot,
3554
+ "codex",
3555
+ repoName,
3556
+ contextFolder,
3557
+ contextFiles
3558
+ );
3559
+ results.push(` ${wasCreated ? "Created" : "Updated"} AGENTS.md`);
3560
+ }
3561
+ if (tools.includes("claude-code")) {
3562
+ await writeClaudeSubagentHook(repoRoot, contextFolder);
3563
+ results.push(" Configured .claude/settings.json (SubagentStart hook)");
3564
+ }
3145
3565
  spinner.succeed("AI tools configured");
3146
3566
  console.log(chalk5.dim(results.join("\n")));
3147
3567
  }
@@ -3204,7 +3624,7 @@ Files are stored on our servers (not in git). Retry when online.`
3204
3624
 
3205
3625
  // src/commands/member.ts
3206
3626
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
3207
- import { dirname as dirname7, join as join19 } from "path";
3627
+ import { dirname as dirname7, join as join20 } from "path";
3208
3628
  import chalk6 from "chalk";
3209
3629
  import ora2 from "ora";
3210
3630
  var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
@@ -3328,7 +3748,7 @@ async function member() {
3328
3748
  spinner.succeed(`Found ${chalk6.bold(files.length)} context files on server`);
3329
3749
  const { tools } = await selectAiTools();
3330
3750
  spinner.start("Downloading context files...");
3331
- const contextDir = join19(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3751
+ const contextDir = join20(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3332
3752
  mkdirSync2(contextDir, { recursive: true });
3333
3753
  let downloadedCount = 0;
3334
3754
  let failedCount = 0;
@@ -3343,7 +3763,7 @@ async function member() {
3343
3763
  const response = await fetch(presignedUrl);
3344
3764
  if (response.ok) {
3345
3765
  const content = await response.text();
3346
- const filePath = join19(contextDir, file.fileName);
3766
+ const filePath = join20(contextDir, file.fileName);
3347
3767
  mkdirSync2(dirname7(filePath), { recursive: true });
3348
3768
  writeFileSync3(filePath, content, "utf-8");
3349
3769
  downloadedCount++;
@@ -3365,11 +3785,15 @@ async function member() {
3365
3785
  ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3366
3786
  } catch {
3367
3787
  }
3368
- if (tools.length > 0) {
3788
+ {
3369
3789
  spinner.start("Configuring AI tools...");
3370
3790
  const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3371
3791
  const configured = [];
3792
+ const written = /* @__PURE__ */ new Set();
3372
3793
  for (const tool of tools) {
3794
+ const config2 = AI_TOOL_CONFIG[tool];
3795
+ if (written.has(config2.filePath)) continue;
3796
+ written.add(config2.filePath);
3373
3797
  const { created } = await updateToolConfig(
3374
3798
  repoRoot,
3375
3799
  tool,
@@ -3377,9 +3801,22 @@ async function member() {
3377
3801
  DEFAULT_CONTEXT_FOLDER2,
3378
3802
  contextFiles
3379
3803
  );
3380
- const config2 = AI_TOOL_CONFIG[tool];
3381
3804
  configured.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
3382
3805
  }
3806
+ if (!written.has("AGENTS.md")) {
3807
+ const { created } = await updateToolConfig(
3808
+ repoRoot,
3809
+ "codex",
3810
+ repoName,
3811
+ DEFAULT_CONTEXT_FOLDER2,
3812
+ contextFiles
3813
+ );
3814
+ configured.push(`${created ? "Created" : "Updated"} AGENTS.md`);
3815
+ }
3816
+ if (tools.includes("claude-code")) {
3817
+ await writeClaudeSubagentHook(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3818
+ configured.push("Configured .claude/settings.json (SubagentStart hook)");
3819
+ }
3383
3820
  spinner.succeed("AI tools configured");
3384
3821
  for (const msg of configured) {
3385
3822
  console.log(chalk6.dim(` ${msg}`));
@@ -3511,15 +3948,15 @@ async function logout() {
3511
3948
  }
3512
3949
 
3513
3950
  // src/commands/status.ts
3514
- import { readFile as readFile9 } from "fs/promises";
3515
- import { basename as basename2, join as join20 } from "path";
3951
+ import { readFile as readFile10 } from "fs/promises";
3952
+ import { basename as basename2, join as join21 } from "path";
3516
3953
  async function status() {
3517
3954
  const configDir = getConfigDir2();
3518
- const STATE_PATH = join20(configDir, "listener-state.json");
3519
- const CONFIG_PATH = join20(configDir, "config.json");
3955
+ const STATE_PATH = join21(configDir, "listener-state.json");
3956
+ const CONFIG_PATH = join21(configDir, "config.json");
3520
3957
  let state = null;
3521
3958
  try {
3522
- const data = await readFile9(STATE_PATH, "utf-8");
3959
+ const data = await readFile10(STATE_PATH, "utf-8");
3523
3960
  state = JSON.parse(data);
3524
3961
  } catch {
3525
3962
  }
@@ -3545,7 +3982,7 @@ async function status() {
3545
3982
  }
3546
3983
  const repoNames = /* @__PURE__ */ new Map();
3547
3984
  try {
3548
- const configData = await readFile9(CONFIG_PATH, "utf-8");
3985
+ const configData = await readFile10(CONFIG_PATH, "utf-8");
3549
3986
  const config2 = JSON.parse(configData);
3550
3987
  for (const repo of config2.repos ?? []) {
3551
3988
  repoNames.set(repo.repoId, basename2(repo.localPath));
@@ -3564,7 +4001,7 @@ async function status() {
3564
4001
 
3565
4002
  // src/commands/sync.ts
3566
4003
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
3567
- import { dirname as dirname8, join as join21 } from "path";
4004
+ import { dirname as dirname8, join as join22 } from "path";
3568
4005
  import chalk9 from "chalk";
3569
4006
  import ora4 from "ora";
3570
4007
  var POLL_INTERVAL_MS2 = 3e3;
@@ -3707,7 +4144,7 @@ async function sync() {
3707
4144
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
3708
4145
  const files = listResult.data?.files ?? listResult.files ?? [];
3709
4146
  if (files.length > 0) {
3710
- const contextDir = join21(repoRoot, DEFAULT_CONTEXT_FOLDER3);
4147
+ const contextDir = join22(repoRoot, DEFAULT_CONTEXT_FOLDER3);
3711
4148
  mkdirSync3(contextDir, { recursive: true });
3712
4149
  let downloadedCount = 0;
3713
4150
  let failedCount = 0;
@@ -3721,7 +4158,7 @@ async function sync() {
3721
4158
  const response = await fetch(presignedUrl);
3722
4159
  if (response.ok) {
3723
4160
  const content = await response.text();
3724
- const filePath = join21(contextDir, file.fileName);
4161
+ const filePath = join22(contextDir, file.fileName);
3725
4162
  mkdirSync3(dirname8(filePath), { recursive: true });
3726
4163
  writeFileSync4(filePath, content, "utf-8");
3727
4164
  downloadedCount++;
@@ -3740,6 +4177,38 @@ async function sync() {
3740
4177
  ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER3);
3741
4178
  } catch {
3742
4179
  }
4180
+ try {
4181
+ const existingConfig = await getConfig();
4182
+ const aiTools = existingConfig.aiTools ?? [];
4183
+ if (aiTools.length > 0) {
4184
+ const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER3);
4185
+ if (contextFiles.length > 0) {
4186
+ const written = /* @__PURE__ */ new Set();
4187
+ for (const tool of aiTools) {
4188
+ const config2 = AI_TOOL_CONFIG[tool];
4189
+ if (!config2 || written.has(config2.filePath)) continue;
4190
+ written.add(config2.filePath);
4191
+ await updateToolConfig(
4192
+ repoRoot,
4193
+ tool,
4194
+ repoName,
4195
+ DEFAULT_CONTEXT_FOLDER3,
4196
+ contextFiles
4197
+ );
4198
+ }
4199
+ if (!written.has("AGENTS.md")) {
4200
+ await updateToolConfig(
4201
+ repoRoot,
4202
+ "codex",
4203
+ repoName,
4204
+ DEFAULT_CONTEXT_FOLDER3,
4205
+ contextFiles
4206
+ );
4207
+ }
4208
+ }
4209
+ }
4210
+ } catch {
4211
+ }
3743
4212
  } else {
3744
4213
  spinner.info("No context files found on server");
3745
4214
  }
@@ -3913,8 +4382,8 @@ async function config() {
3913
4382
  }
3914
4383
  patch.deliveryMode = newMode;
3915
4384
  } else if (setting === "monitoredBranch") {
3916
- const { input: input2 } = await import("@inquirer/prompts");
3917
- const newBranch = await input2({
4385
+ const { input } = await import("@inquirer/prompts");
4386
+ const newBranch = await input({
3918
4387
  message: "Monitored branch",
3919
4388
  default: currentBranch
3920
4389
  });
@@ -3941,7 +4410,7 @@ async function config() {
3941
4410
  // bin/repowise.ts
3942
4411
  var __filename = fileURLToPath3(import.meta.url);
3943
4412
  var __dirname = dirname9(__filename);
3944
- var pkg = JSON.parse(readFileSync2(join22(__dirname, "..", "..", "package.json"), "utf-8"));
4413
+ var pkg = JSON.parse(readFileSync2(join23(__dirname, "..", "..", "package.json"), "utf-8"));
3945
4414
  var program = new Command();
3946
4415
  program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
3947
4416
  await showWelcome(pkg.version);