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.
- package/dist/bin/repowise.js +798 -329
- package/package.json +1 -1
package/dist/bin/repowise.js
CHANGED
|
@@ -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
|
|
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
|
|
11
|
-
import { join as
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
49
|
-
await
|
|
349
|
+
const configPath = join3(configDir, "config.json");
|
|
350
|
+
await mkdir2(configDir, { recursive: true, mode: 448 });
|
|
50
351
|
try {
|
|
51
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
92
|
-
import { join as
|
|
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 =
|
|
398
|
+
const statePath = join4(getConfigDir(), "listener-state.json");
|
|
98
399
|
try {
|
|
99
|
-
const data = await
|
|
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 =
|
|
111
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
286
|
-
import { join as
|
|
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 =
|
|
326
|
-
const data = await
|
|
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 =
|
|
338
|
-
await
|
|
339
|
-
await
|
|
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
|
|
491
|
-
import { dirname as
|
|
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
|
|
797
|
+
import { join as join7 } from "path";
|
|
497
798
|
async function verifyContextFolder(localPath) {
|
|
498
799
|
try {
|
|
499
|
-
await access(
|
|
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 =
|
|
588
|
-
await
|
|
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 =
|
|
613
|
-
await
|
|
614
|
-
await
|
|
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
|
|
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
|
|
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
|
|
939
|
+
return join9(repowiseDir(), "listener.pid");
|
|
639
940
|
}
|
|
640
941
|
function logDirPath() {
|
|
641
|
-
return
|
|
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
|
|
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
|
|
980
|
+
await mkdir6(logDir2, { recursive: true });
|
|
680
981
|
const cmd = resolveListenerCommand();
|
|
681
|
-
const stdoutFd = openSync(
|
|
682
|
-
const stderrFd = openSync(
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
825
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
1147
|
+
return join11(homedir3(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
847
1148
|
}
|
|
848
1149
|
function logDir() {
|
|
849
|
-
return
|
|
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>${
|
|
1172
|
+
<string>${join11(logs, "listener-stdout.log")}</string>
|
|
872
1173
|
<key>StandardErrorPath</key>
|
|
873
|
-
<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
|
|
881
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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:${
|
|
926
|
-
StandardError=append:${
|
|
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
|
|
933
|
-
await
|
|
934
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
1323
|
-
const lockPath =
|
|
1324
|
-
await
|
|
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 ${
|
|
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 =
|
|
1434
|
-
const pkgJsonPath =
|
|
1435
|
-
const pkgJson = JSON.parse(await
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
1731
|
-
import { join as
|
|
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
|
|
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 =
|
|
1744
|
-
await
|
|
2101
|
+
const path = join15(dir, "config.json");
|
|
2102
|
+
await mkdir9(dir, { recursive: true });
|
|
1745
2103
|
const tmpPath = path + ".tmp";
|
|
1746
2104
|
try {
|
|
1747
|
-
await
|
|
2105
|
+
await writeFile9(tmpPath, JSON.stringify(config2, null, 2));
|
|
1748
2106
|
await rename3(tmpPath, path);
|
|
1749
2107
|
} catch (err) {
|
|
1750
2108
|
try {
|
|
1751
|
-
await
|
|
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 =
|
|
1760
|
-
await
|
|
2117
|
+
const path = join15(dir, "config.json");
|
|
2118
|
+
await mkdir9(dir, { recursive: true });
|
|
1761
2119
|
try {
|
|
1762
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
2005
|
-
const data = await
|
|
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 =
|
|
2017
|
-
await
|
|
2018
|
-
await
|
|
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
|
|
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 {
|
|
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
|
|
2215
|
-
import { join as
|
|
2216
|
-
var
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
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
|
-
|
|
2354
|
-
|
|
2355
|
-
} catch
|
|
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
|
-
|
|
2359
|
-
|
|
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
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
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
|
|
2727
|
+
import { join as join18 } from "path";
|
|
2397
2728
|
function ensureGitignore(repoRoot, entry) {
|
|
2398
|
-
const gitignorePath =
|
|
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
|
-
|
|
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
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
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
|
-
|
|
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.
|
|
2940
|
-
`
|
|
2941
|
-
|
|
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.\
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
3515
|
-
import { basename as basename2, join as
|
|
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 =
|
|
3519
|
-
const CONFIG_PATH =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
3917
|
-
const newBranch = await
|
|
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(
|
|
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);
|