repowise 0.1.87 → 0.1.89
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 +630 -317
- 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,7 +2185,7 @@ 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
|
|
|
@@ -1928,9 +2286,9 @@ function resolveRole(groups) {
|
|
|
1928
2286
|
|
|
1929
2287
|
// src/lib/auth.ts
|
|
1930
2288
|
import { createHash, randomBytes } from "crypto";
|
|
1931
|
-
import { readFile as
|
|
2289
|
+
import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir10, chmod as chmod4, unlink as unlink8 } from "fs/promises";
|
|
1932
2290
|
import http from "http";
|
|
1933
|
-
import { join as
|
|
2291
|
+
import { join as join16 } from "path";
|
|
1934
2292
|
var CLI_CALLBACK_PORT = 19876;
|
|
1935
2293
|
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
1936
2294
|
function getCognitoConfigForStorage() {
|
|
@@ -2096,8 +2454,8 @@ async function refreshTokens2(refreshToken) {
|
|
|
2096
2454
|
}
|
|
2097
2455
|
async function getStoredCredentials2() {
|
|
2098
2456
|
try {
|
|
2099
|
-
const credPath =
|
|
2100
|
-
const data = await
|
|
2457
|
+
const credPath = join16(getConfigDir2(), "credentials.json");
|
|
2458
|
+
const data = await readFile8(credPath, "utf-8");
|
|
2101
2459
|
return JSON.parse(data);
|
|
2102
2460
|
} catch (err) {
|
|
2103
2461
|
if (err.code === "ENOENT" || err instanceof SyntaxError) {
|
|
@@ -2108,14 +2466,14 @@ async function getStoredCredentials2() {
|
|
|
2108
2466
|
}
|
|
2109
2467
|
async function storeCredentials2(credentials) {
|
|
2110
2468
|
const dir = getConfigDir2();
|
|
2111
|
-
const credPath =
|
|
2112
|
-
await
|
|
2113
|
-
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));
|
|
2114
2472
|
await chmod4(credPath, 384);
|
|
2115
2473
|
}
|
|
2116
2474
|
async function clearCredentials() {
|
|
2117
2475
|
try {
|
|
2118
|
-
await
|
|
2476
|
+
await unlink8(join16(getConfigDir2(), "credentials.json"));
|
|
2119
2477
|
} catch (err) {
|
|
2120
2478
|
if (err.code !== "ENOENT") throw err;
|
|
2121
2479
|
}
|
|
@@ -2278,23 +2636,34 @@ async function apiRequest(path, options) {
|
|
|
2278
2636
|
}
|
|
2279
2637
|
|
|
2280
2638
|
// src/lib/prompts.ts
|
|
2281
|
-
import { checkbox, confirm } from "@inquirer/prompts";
|
|
2639
|
+
import { checkbox, confirm, Separator } from "@inquirer/prompts";
|
|
2282
2640
|
import chalk2 from "chalk";
|
|
2283
2641
|
async function selectAiTools() {
|
|
2284
2642
|
const choices = [
|
|
2643
|
+
new Separator(chalk2.dim("\u2500\u2500 Popular \u2500\u2500")),
|
|
2285
2644
|
{ name: "Cursor", value: "cursor" },
|
|
2286
2645
|
{ name: "Claude Code", value: "claude-code" },
|
|
2287
2646
|
{ name: "GitHub Copilot", value: "copilot" },
|
|
2288
2647
|
{ name: "Windsurf", value: "windsurf" },
|
|
2648
|
+
new Separator(chalk2.dim("\u2500\u2500 More Tools \u2500\u2500")),
|
|
2289
2649
|
{ name: "Cline", value: "cline" },
|
|
2290
2650
|
{ name: "Codex", value: "codex" },
|
|
2291
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")),
|
|
2292
2660
|
{ name: "Other (manual setup)", value: "other" }
|
|
2293
2661
|
];
|
|
2294
2662
|
while (true) {
|
|
2295
2663
|
const selected = await checkbox({
|
|
2296
2664
|
message: chalk2.bold("Which AI tools do you use?") + chalk2.dim(" (Space to select, Enter to continue)"),
|
|
2297
|
-
choices
|
|
2665
|
+
choices,
|
|
2666
|
+
pageSize: 22
|
|
2298
2667
|
});
|
|
2299
2668
|
if (selected.length === 0) {
|
|
2300
2669
|
const goBack = await confirm({
|
|
@@ -2310,191 +2679,54 @@ async function selectAiTools() {
|
|
|
2310
2679
|
}
|
|
2311
2680
|
|
|
2312
2681
|
// src/lib/ai-tools.ts
|
|
2313
|
-
import { readFile as
|
|
2314
|
-
import { join as
|
|
2315
|
-
var
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
filePath: ".cursorrules",
|
|
2320
|
-
markerStart: "# --- repowise-start ---",
|
|
2321
|
-
markerEnd: "# --- repowise-end ---",
|
|
2322
|
-
format: "plain-text"
|
|
2323
|
-
},
|
|
2324
|
-
"claude-code": {
|
|
2325
|
-
label: "Claude Code",
|
|
2326
|
-
fileName: "CLAUDE.md",
|
|
2327
|
-
filePath: "CLAUDE.md",
|
|
2328
|
-
markerStart: "<!-- repowise-start -->",
|
|
2329
|
-
markerEnd: "<!-- repowise-end -->",
|
|
2330
|
-
format: "markdown"
|
|
2331
|
-
},
|
|
2332
|
-
copilot: {
|
|
2333
|
-
label: "GitHub Copilot",
|
|
2334
|
-
fileName: "copilot-instructions.md",
|
|
2335
|
-
filePath: ".github/copilot-instructions.md",
|
|
2336
|
-
markerStart: "<!-- repowise-start -->",
|
|
2337
|
-
markerEnd: "<!-- repowise-end -->",
|
|
2338
|
-
format: "markdown"
|
|
2339
|
-
},
|
|
2340
|
-
windsurf: {
|
|
2341
|
-
label: "Windsurf",
|
|
2342
|
-
fileName: ".windsurfrules",
|
|
2343
|
-
filePath: ".windsurfrules",
|
|
2344
|
-
markerStart: "# --- repowise-start ---",
|
|
2345
|
-
markerEnd: "# --- repowise-end ---",
|
|
2346
|
-
format: "plain-text"
|
|
2347
|
-
},
|
|
2348
|
-
cline: {
|
|
2349
|
-
label: "Cline",
|
|
2350
|
-
fileName: ".clinerules",
|
|
2351
|
-
filePath: ".clinerules",
|
|
2352
|
-
markerStart: "# --- repowise-start ---",
|
|
2353
|
-
markerEnd: "# --- repowise-end ---",
|
|
2354
|
-
format: "plain-text"
|
|
2355
|
-
},
|
|
2356
|
-
codex: {
|
|
2357
|
-
label: "Codex",
|
|
2358
|
-
fileName: "AGENTS.md",
|
|
2359
|
-
filePath: "AGENTS.md",
|
|
2360
|
-
markerStart: "<!-- repowise-start -->",
|
|
2361
|
-
markerEnd: "<!-- repowise-end -->",
|
|
2362
|
-
format: "markdown"
|
|
2363
|
-
},
|
|
2364
|
-
"roo-code": {
|
|
2365
|
-
label: "Roo Code",
|
|
2366
|
-
fileName: "rules.md",
|
|
2367
|
-
filePath: ".roo/rules.md",
|
|
2368
|
-
markerStart: "<!-- repowise-start -->",
|
|
2369
|
-
markerEnd: "<!-- repowise-end -->",
|
|
2370
|
-
format: "markdown"
|
|
2371
|
-
}
|
|
2372
|
-
};
|
|
2373
|
-
var SUPPORTED_TOOLS = Object.keys(AI_TOOL_CONFIG);
|
|
2374
|
-
function sanitizeRepoName(name) {
|
|
2375
|
-
return name.replace(/[<>[\]`()|\\]/g, "");
|
|
2376
|
-
}
|
|
2377
|
-
function fileDescriptionFromName(fileName) {
|
|
2378
|
-
return fileName.replace(/\.md$/, "").split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2379
|
-
}
|
|
2380
|
-
function generateReference(tool, repoName, contextFolder, contextFiles) {
|
|
2381
|
-
const config2 = AI_TOOL_CONFIG[tool];
|
|
2382
|
-
const safeName = sanitizeRepoName(repoName);
|
|
2383
|
-
const fileLines = contextFiles.map((f) => {
|
|
2384
|
-
const baseName = f.fileName.split("/").pop() ?? f.fileName;
|
|
2385
|
-
const desc = fileDescriptionFromName(baseName);
|
|
2386
|
-
const isOverview = baseName === "project-overview.md";
|
|
2387
|
-
return { path: f.relativePath, desc: isOverview ? `${desc} (full index of all files)` : desc };
|
|
2388
|
-
});
|
|
2389
|
-
const hasFiles = fileLines.length > 0;
|
|
2390
|
-
if (config2.format === "markdown") {
|
|
2391
|
-
const lines2 = [
|
|
2392
|
-
config2.markerStart,
|
|
2393
|
-
"",
|
|
2394
|
-
`## Project Context \u2014 ${safeName}`,
|
|
2395
|
-
"",
|
|
2396
|
-
`This repository has AI-optimized context files generated by RepoWise.`,
|
|
2397
|
-
`**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.`,
|
|
2398
|
-
"",
|
|
2399
|
-
`**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.`,
|
|
2400
|
-
""
|
|
2401
|
-
];
|
|
2402
|
-
if (hasFiles) {
|
|
2403
|
-
lines2.push(
|
|
2404
|
-
`**Core context files:**`,
|
|
2405
|
-
"",
|
|
2406
|
-
...fileLines.map((f) => `- \`${f.path}\` \u2014 ${f.desc}`),
|
|
2407
|
-
"",
|
|
2408
|
-
`> Additional context files may exist beyond this list. Check \`project-overview.md\` for the complete index.`
|
|
2409
|
-
);
|
|
2410
|
-
}
|
|
2411
|
-
lines2.push("", config2.markerEnd);
|
|
2412
|
-
return lines2.join("\n");
|
|
2413
|
-
}
|
|
2414
|
-
const lines = [
|
|
2415
|
-
config2.markerStart,
|
|
2416
|
-
`# Project Context \u2014 ${safeName}`,
|
|
2417
|
-
"#",
|
|
2418
|
-
`# This repository has AI-optimized context files generated by RepoWise.`,
|
|
2419
|
-
`# IMPORTANT: Before answering questions about the codebase or making any changes,`,
|
|
2420
|
-
`# ALWAYS check the ${contextFolder}/ folder first. These files contain pre-analyzed`,
|
|
2421
|
-
`# architecture, patterns, API contracts, and domain knowledge that will answer`,
|
|
2422
|
-
`# most questions without needing to search the codebase.`,
|
|
2423
|
-
"#",
|
|
2424
|
-
`# Start here: ${contextFolder}/project-overview.md`,
|
|
2425
|
-
`# The routing document that maps every context file to its domain.`,
|
|
2426
|
-
`# Read it first to find which context file has the answer you need.`
|
|
2427
|
-
];
|
|
2428
|
-
if (hasFiles) {
|
|
2429
|
-
lines.push(
|
|
2430
|
-
"#",
|
|
2431
|
-
`# Core context files:`,
|
|
2432
|
-
...fileLines.map((f) => `# ${f.path} \u2014 ${f.desc}`),
|
|
2433
|
-
"#",
|
|
2434
|
-
"# Additional context files may exist beyond this list.",
|
|
2435
|
-
"# Check project-overview.md for the complete index."
|
|
2436
|
-
);
|
|
2437
|
-
}
|
|
2438
|
-
lines.push(config2.markerEnd);
|
|
2439
|
-
return lines.join("\n");
|
|
2440
|
-
}
|
|
2441
|
-
async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
|
|
2442
|
-
const config2 = AI_TOOL_CONFIG[tool];
|
|
2443
|
-
const fullPath = join16(repoRoot, config2.filePath);
|
|
2444
|
-
const dir = dirname5(fullPath);
|
|
2445
|
-
if (dir !== repoRoot) {
|
|
2446
|
-
await mkdir10(dir, { recursive: true });
|
|
2447
|
-
}
|
|
2448
|
-
const referenceBlock = generateReference(tool, repoName, contextFolder, contextFiles);
|
|
2449
|
-
let existing = "";
|
|
2450
|
-
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 = {};
|
|
2451
2688
|
try {
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
} catch
|
|
2455
|
-
if (err.code !== "ENOENT") throw err;
|
|
2689
|
+
const raw = await readFile9(settingsPath, "utf-8");
|
|
2690
|
+
settings = JSON.parse(raw);
|
|
2691
|
+
} catch {
|
|
2456
2692
|
}
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
let content;
|
|
2460
|
-
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
2461
|
-
const before = existing.slice(0, startIdx);
|
|
2462
|
-
const after = existing.slice(endIdx + config2.markerEnd.length);
|
|
2463
|
-
content = before + referenceBlock + after;
|
|
2464
|
-
} else {
|
|
2465
|
-
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
2466
|
-
content = existing + separator + referenceBlock + "\n";
|
|
2693
|
+
if (!settings["hooks"] || typeof settings["hooks"] !== "object") {
|
|
2694
|
+
settings["hooks"] = {};
|
|
2467
2695
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
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);
|
|
2490
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");
|
|
2491
2723
|
}
|
|
2492
2724
|
|
|
2493
2725
|
// src/lib/gitignore.ts
|
|
2494
2726
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
2495
|
-
import { join as
|
|
2727
|
+
import { join as join18 } from "path";
|
|
2496
2728
|
function ensureGitignore(repoRoot, entry) {
|
|
2497
|
-
const gitignorePath =
|
|
2729
|
+
const gitignorePath = join18(repoRoot, ".gitignore");
|
|
2498
2730
|
if (existsSync(gitignorePath)) {
|
|
2499
2731
|
const content = readFileSync(gitignorePath, "utf-8");
|
|
2500
2732
|
const lines = content.split("\n").map((l) => l.trim());
|
|
@@ -3009,6 +3241,7 @@ async function create() {
|
|
|
3009
3241
|
let repoRoot;
|
|
3010
3242
|
let repoPlatform;
|
|
3011
3243
|
let repoExternalId;
|
|
3244
|
+
let repoLookupError;
|
|
3012
3245
|
spinner.start("Checking for pending repository...");
|
|
3013
3246
|
try {
|
|
3014
3247
|
const pending = await apiRequest("/v1/onboarding/pending");
|
|
@@ -3044,7 +3277,8 @@ async function create() {
|
|
|
3044
3277
|
repoPlatform = match.platform;
|
|
3045
3278
|
repoExternalId = match.externalId;
|
|
3046
3279
|
}
|
|
3047
|
-
} catch {
|
|
3280
|
+
} catch (err) {
|
|
3281
|
+
repoLookupError = err instanceof Error ? err.message : String(err);
|
|
3048
3282
|
}
|
|
3049
3283
|
} else {
|
|
3050
3284
|
try {
|
|
@@ -3053,11 +3287,15 @@ async function create() {
|
|
|
3053
3287
|
}
|
|
3054
3288
|
}
|
|
3055
3289
|
if (!repoId) {
|
|
3056
|
-
|
|
3057
|
-
chalk5.red(
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3290
|
+
if (repoLookupError) {
|
|
3291
|
+
spinner.fail(chalk5.red(`Failed to look up repositories: ${repoLookupError}`));
|
|
3292
|
+
} else {
|
|
3293
|
+
spinner.fail(
|
|
3294
|
+
chalk5.red(
|
|
3295
|
+
"Could not find this repository in your RepoWise account. Connect it on the dashboard first."
|
|
3296
|
+
)
|
|
3297
|
+
);
|
|
3298
|
+
}
|
|
3061
3299
|
process.exitCode = 1;
|
|
3062
3300
|
return;
|
|
3063
3301
|
}
|
|
@@ -3120,7 +3358,7 @@ async function create() {
|
|
|
3120
3358
|
if (hasOther) {
|
|
3121
3359
|
console.log(
|
|
3122
3360
|
chalk5.cyan(
|
|
3123
|
-
"\nFor AI tools not listed, context files still work with any tool that reads the filesystem.\
|
|
3361
|
+
"\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"
|
|
3124
3362
|
)
|
|
3125
3363
|
);
|
|
3126
3364
|
}
|
|
@@ -3237,7 +3475,7 @@ async function create() {
|
|
|
3237
3475
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
3238
3476
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
3239
3477
|
if (files.length > 0) {
|
|
3240
|
-
const contextDir =
|
|
3478
|
+
const contextDir = join19(repoRoot, DEFAULT_CONTEXT_FOLDER);
|
|
3241
3479
|
mkdirSync(contextDir, { recursive: true });
|
|
3242
3480
|
let downloadedCount = 0;
|
|
3243
3481
|
let failedCount = 0;
|
|
@@ -3251,7 +3489,7 @@ async function create() {
|
|
|
3251
3489
|
const response = await fetch(presignedUrl);
|
|
3252
3490
|
if (response.ok) {
|
|
3253
3491
|
const content = await response.text();
|
|
3254
|
-
const filePath =
|
|
3492
|
+
const filePath = join19(contextDir, file.fileName);
|
|
3255
3493
|
mkdirSync(dirname6(filePath), { recursive: true });
|
|
3256
3494
|
writeFileSync2(filePath, content, "utf-8");
|
|
3257
3495
|
downloadedCount++;
|
|
@@ -3295,10 +3533,17 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
3295
3533
|
)
|
|
3296
3534
|
);
|
|
3297
3535
|
}
|
|
3298
|
-
if (
|
|
3536
|
+
if (repoRoot) {
|
|
3299
3537
|
spinner.start("Configuring AI tools...");
|
|
3300
3538
|
const results = [];
|
|
3539
|
+
const written = /* @__PURE__ */ new Set();
|
|
3301
3540
|
for (const tool of tools) {
|
|
3541
|
+
const config2 = AI_TOOL_CONFIG[tool];
|
|
3542
|
+
if (written.has(config2.filePath)) continue;
|
|
3543
|
+
written.add(config2.filePath);
|
|
3544
|
+
if (config2.legacyFilePath) {
|
|
3545
|
+
await migrateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles);
|
|
3546
|
+
}
|
|
3302
3547
|
const { created: wasCreated } = await updateToolConfig(
|
|
3303
3548
|
repoRoot,
|
|
3304
3549
|
tool,
|
|
@@ -3306,10 +3551,23 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
3306
3551
|
contextFolder,
|
|
3307
3552
|
contextFiles
|
|
3308
3553
|
);
|
|
3309
|
-
const config2 = AI_TOOL_CONFIG[tool];
|
|
3310
3554
|
const action = wasCreated ? "Created" : "Updated";
|
|
3311
3555
|
results.push(` ${action} ${config2.filePath}`);
|
|
3312
3556
|
}
|
|
3557
|
+
if (!written.has("AGENTS.md")) {
|
|
3558
|
+
const { created: wasCreated } = await updateToolConfig(
|
|
3559
|
+
repoRoot,
|
|
3560
|
+
"codex",
|
|
3561
|
+
repoName,
|
|
3562
|
+
contextFolder,
|
|
3563
|
+
contextFiles
|
|
3564
|
+
);
|
|
3565
|
+
results.push(` ${wasCreated ? "Created" : "Updated"} AGENTS.md`);
|
|
3566
|
+
}
|
|
3567
|
+
if (tools.includes("claude-code")) {
|
|
3568
|
+
await writeClaudeSubagentHook(repoRoot, contextFolder);
|
|
3569
|
+
results.push(" Configured .claude/settings.json (SubagentStart hook)");
|
|
3570
|
+
}
|
|
3313
3571
|
spinner.succeed("AI tools configured");
|
|
3314
3572
|
console.log(chalk5.dim(results.join("\n")));
|
|
3315
3573
|
}
|
|
@@ -3372,7 +3630,7 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
3372
3630
|
|
|
3373
3631
|
// src/commands/member.ts
|
|
3374
3632
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3375
|
-
import { dirname as dirname7, join as
|
|
3633
|
+
import { dirname as dirname7, join as join20 } from "path";
|
|
3376
3634
|
import chalk6 from "chalk";
|
|
3377
3635
|
import ora2 from "ora";
|
|
3378
3636
|
var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
|
|
@@ -3496,7 +3754,7 @@ async function member() {
|
|
|
3496
3754
|
spinner.succeed(`Found ${chalk6.bold(files.length)} context files on server`);
|
|
3497
3755
|
const { tools } = await selectAiTools();
|
|
3498
3756
|
spinner.start("Downloading context files...");
|
|
3499
|
-
const contextDir =
|
|
3757
|
+
const contextDir = join20(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3500
3758
|
mkdirSync2(contextDir, { recursive: true });
|
|
3501
3759
|
let downloadedCount = 0;
|
|
3502
3760
|
let failedCount = 0;
|
|
@@ -3511,7 +3769,7 @@ async function member() {
|
|
|
3511
3769
|
const response = await fetch(presignedUrl);
|
|
3512
3770
|
if (response.ok) {
|
|
3513
3771
|
const content = await response.text();
|
|
3514
|
-
const filePath =
|
|
3772
|
+
const filePath = join20(contextDir, file.fileName);
|
|
3515
3773
|
mkdirSync2(dirname7(filePath), { recursive: true });
|
|
3516
3774
|
writeFileSync3(filePath, content, "utf-8");
|
|
3517
3775
|
downloadedCount++;
|
|
@@ -3533,11 +3791,15 @@ async function member() {
|
|
|
3533
3791
|
ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3534
3792
|
} catch {
|
|
3535
3793
|
}
|
|
3536
|
-
|
|
3794
|
+
{
|
|
3537
3795
|
spinner.start("Configuring AI tools...");
|
|
3538
3796
|
const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3539
3797
|
const configured = [];
|
|
3798
|
+
const written = /* @__PURE__ */ new Set();
|
|
3540
3799
|
for (const tool of tools) {
|
|
3800
|
+
const config2 = AI_TOOL_CONFIG[tool];
|
|
3801
|
+
if (written.has(config2.filePath)) continue;
|
|
3802
|
+
written.add(config2.filePath);
|
|
3541
3803
|
const { created } = await updateToolConfig(
|
|
3542
3804
|
repoRoot,
|
|
3543
3805
|
tool,
|
|
@@ -3545,9 +3807,22 @@ async function member() {
|
|
|
3545
3807
|
DEFAULT_CONTEXT_FOLDER2,
|
|
3546
3808
|
contextFiles
|
|
3547
3809
|
);
|
|
3548
|
-
const config2 = AI_TOOL_CONFIG[tool];
|
|
3549
3810
|
configured.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
|
|
3550
3811
|
}
|
|
3812
|
+
if (!written.has("AGENTS.md")) {
|
|
3813
|
+
const { created } = await updateToolConfig(
|
|
3814
|
+
repoRoot,
|
|
3815
|
+
"codex",
|
|
3816
|
+
repoName,
|
|
3817
|
+
DEFAULT_CONTEXT_FOLDER2,
|
|
3818
|
+
contextFiles
|
|
3819
|
+
);
|
|
3820
|
+
configured.push(`${created ? "Created" : "Updated"} AGENTS.md`);
|
|
3821
|
+
}
|
|
3822
|
+
if (tools.includes("claude-code")) {
|
|
3823
|
+
await writeClaudeSubagentHook(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
3824
|
+
configured.push("Configured .claude/settings.json (SubagentStart hook)");
|
|
3825
|
+
}
|
|
3551
3826
|
spinner.succeed("AI tools configured");
|
|
3552
3827
|
for (const msg of configured) {
|
|
3553
3828
|
console.log(chalk6.dim(` ${msg}`));
|
|
@@ -3679,15 +3954,15 @@ async function logout() {
|
|
|
3679
3954
|
}
|
|
3680
3955
|
|
|
3681
3956
|
// src/commands/status.ts
|
|
3682
|
-
import { readFile as
|
|
3683
|
-
import { basename as basename2, join as
|
|
3957
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
3958
|
+
import { basename as basename2, join as join21 } from "path";
|
|
3684
3959
|
async function status() {
|
|
3685
3960
|
const configDir = getConfigDir2();
|
|
3686
|
-
const STATE_PATH =
|
|
3687
|
-
const CONFIG_PATH =
|
|
3961
|
+
const STATE_PATH = join21(configDir, "listener-state.json");
|
|
3962
|
+
const CONFIG_PATH = join21(configDir, "config.json");
|
|
3688
3963
|
let state = null;
|
|
3689
3964
|
try {
|
|
3690
|
-
const data = await
|
|
3965
|
+
const data = await readFile10(STATE_PATH, "utf-8");
|
|
3691
3966
|
state = JSON.parse(data);
|
|
3692
3967
|
} catch {
|
|
3693
3968
|
}
|
|
@@ -3713,7 +3988,7 @@ async function status() {
|
|
|
3713
3988
|
}
|
|
3714
3989
|
const repoNames = /* @__PURE__ */ new Map();
|
|
3715
3990
|
try {
|
|
3716
|
-
const configData = await
|
|
3991
|
+
const configData = await readFile10(CONFIG_PATH, "utf-8");
|
|
3717
3992
|
const config2 = JSON.parse(configData);
|
|
3718
3993
|
for (const repo of config2.repos ?? []) {
|
|
3719
3994
|
repoNames.set(repo.repoId, basename2(repo.localPath));
|
|
@@ -3732,7 +4007,7 @@ async function status() {
|
|
|
3732
4007
|
|
|
3733
4008
|
// src/commands/sync.ts
|
|
3734
4009
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3735
|
-
import { dirname as dirname8, join as
|
|
4010
|
+
import { dirname as dirname8, join as join22 } from "path";
|
|
3736
4011
|
import chalk9 from "chalk";
|
|
3737
4012
|
import ora4 from "ora";
|
|
3738
4013
|
var POLL_INTERVAL_MS2 = 3e3;
|
|
@@ -3775,6 +4050,7 @@ async function sync() {
|
|
|
3775
4050
|
let repoId;
|
|
3776
4051
|
let repoPlatform;
|
|
3777
4052
|
let repoExternalId;
|
|
4053
|
+
let repoLookupError;
|
|
3778
4054
|
spinner.start("Resolving repository...");
|
|
3779
4055
|
try {
|
|
3780
4056
|
const repos = await apiRequest("/v1/repos");
|
|
@@ -3784,14 +4060,19 @@ async function sync() {
|
|
|
3784
4060
|
repoPlatform = match.platform;
|
|
3785
4061
|
repoExternalId = match.externalId;
|
|
3786
4062
|
}
|
|
3787
|
-
} catch {
|
|
4063
|
+
} catch (err) {
|
|
4064
|
+
repoLookupError = err instanceof Error ? err.message : String(err);
|
|
3788
4065
|
}
|
|
3789
4066
|
if (!repoId) {
|
|
3790
|
-
|
|
3791
|
-
chalk9.red(
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
4067
|
+
if (repoLookupError) {
|
|
4068
|
+
spinner.fail(chalk9.red(`Failed to look up repositories: ${repoLookupError}`));
|
|
4069
|
+
} else {
|
|
4070
|
+
spinner.fail(
|
|
4071
|
+
chalk9.red(
|
|
4072
|
+
"Could not find this repository in your RepoWise account. Run `repowise create` first."
|
|
4073
|
+
)
|
|
4074
|
+
);
|
|
4075
|
+
}
|
|
3795
4076
|
process.exitCode = 1;
|
|
3796
4077
|
return;
|
|
3797
4078
|
}
|
|
@@ -3875,7 +4156,7 @@ async function sync() {
|
|
|
3875
4156
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
3876
4157
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
3877
4158
|
if (files.length > 0) {
|
|
3878
|
-
const contextDir =
|
|
4159
|
+
const contextDir = join22(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
3879
4160
|
mkdirSync3(contextDir, { recursive: true });
|
|
3880
4161
|
let downloadedCount = 0;
|
|
3881
4162
|
let failedCount = 0;
|
|
@@ -3889,7 +4170,7 @@ async function sync() {
|
|
|
3889
4170
|
const response = await fetch(presignedUrl);
|
|
3890
4171
|
if (response.ok) {
|
|
3891
4172
|
const content = await response.text();
|
|
3892
|
-
const filePath =
|
|
4173
|
+
const filePath = join22(contextDir, file.fileName);
|
|
3893
4174
|
mkdirSync3(dirname8(filePath), { recursive: true });
|
|
3894
4175
|
writeFileSync4(filePath, content, "utf-8");
|
|
3895
4176
|
downloadedCount++;
|
|
@@ -3908,6 +4189,38 @@ async function sync() {
|
|
|
3908
4189
|
ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
3909
4190
|
} catch {
|
|
3910
4191
|
}
|
|
4192
|
+
try {
|
|
4193
|
+
const existingConfig = await getConfig();
|
|
4194
|
+
const aiTools = existingConfig.aiTools ?? [];
|
|
4195
|
+
if (aiTools.length > 0) {
|
|
4196
|
+
const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
4197
|
+
if (contextFiles.length > 0) {
|
|
4198
|
+
const written = /* @__PURE__ */ new Set();
|
|
4199
|
+
for (const tool of aiTools) {
|
|
4200
|
+
const config2 = AI_TOOL_CONFIG[tool];
|
|
4201
|
+
if (!config2 || written.has(config2.filePath)) continue;
|
|
4202
|
+
written.add(config2.filePath);
|
|
4203
|
+
await updateToolConfig(
|
|
4204
|
+
repoRoot,
|
|
4205
|
+
tool,
|
|
4206
|
+
repoName,
|
|
4207
|
+
DEFAULT_CONTEXT_FOLDER3,
|
|
4208
|
+
contextFiles
|
|
4209
|
+
);
|
|
4210
|
+
}
|
|
4211
|
+
if (!written.has("AGENTS.md")) {
|
|
4212
|
+
await updateToolConfig(
|
|
4213
|
+
repoRoot,
|
|
4214
|
+
"codex",
|
|
4215
|
+
repoName,
|
|
4216
|
+
DEFAULT_CONTEXT_FOLDER3,
|
|
4217
|
+
contextFiles
|
|
4218
|
+
);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
} catch {
|
|
4223
|
+
}
|
|
3911
4224
|
} else {
|
|
3912
4225
|
spinner.info("No context files found on server");
|
|
3913
4226
|
}
|
|
@@ -4109,7 +4422,7 @@ async function config() {
|
|
|
4109
4422
|
// bin/repowise.ts
|
|
4110
4423
|
var __filename = fileURLToPath3(import.meta.url);
|
|
4111
4424
|
var __dirname = dirname9(__filename);
|
|
4112
|
-
var pkg = JSON.parse(readFileSync2(
|
|
4425
|
+
var pkg = JSON.parse(readFileSync2(join23(__dirname, "..", "..", "package.json"), "utf-8"));
|
|
4113
4426
|
var program = new Command();
|
|
4114
4427
|
program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
|
|
4115
4428
|
await showWelcome(pkg.version);
|