sauron-cli 1.4.2 → 1.4.3
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/README.md +16 -0
- package/dist/index.js +448 -199
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ Assistentes de código são incrivelmente poderosos, mas sofrem de amnésia vol
|
|
|
12
12
|
|
|
13
13
|
O Sauron resolve isso ejetando pastas estruturadas (`.sauron` e `.agents`) no seu repositório local. A partir desse momento, as IAs são condicionadas a documentar regras passivamente de acordo com os templates gerados, preservando o **Single Source of Truth** do seu produto.
|
|
14
14
|
|
|
15
|
+
|
|
15
16
|
## Instalação
|
|
16
17
|
|
|
17
18
|
Para inicializar o Sauron CLI no seu projeto, basta rodar o comando abaixo no terminal da raiz do seu projeto (não é necessário instalar nada globalmente):
|
|
@@ -29,6 +30,21 @@ O comando executa uma **varredura heurística não-bloqueante** ($O(1)$) na raiz
|
|
|
29
30
|
|
|
30
31
|
Durante o onboarding interativo, as perguntas são pré-populadas de forma inteligente com as informações detectadas. Ao concluir, o Sauron realiza a **injeção automática de receitas de Wiki** (Wiki Recipes) personalizadas para a sua stack tecnológica em `.sauron/wiki/standards/` (ex. TypeScript, Next.js, React, Tailwind CSS, PostgreSQL) de forma idempotente e não-destrutiva, gerando o manifesto dinâmico `AGENTS.md` e as regras de governança locais das IAs.
|
|
31
32
|
|
|
33
|
+
### Atualizações Inteligentes (Bypass & Reidratação)
|
|
34
|
+
O comando é totalmente **Idempotente**. Se executado em um repositório já inicializado:
|
|
35
|
+
- **Fast-Track (Bypass)**: O CLI detecta a configuração passada e oferece a opção de pular o onboarding, atualizando os agentes internos silenciosamente.
|
|
36
|
+
- **Reidratação Interativa**: Caso você queira alterar a configuração, os formulários do terminal são reidratados com o seu último estado configurado, poupando a necessidade de redigitar contextos complexos.
|
|
37
|
+
- **Wiki Protection**: Um filtro cirúrgico é ativado no motor de cópia da CLI, tornando a sua Base de Conhecimento em `.sauron/wiki/` 100% blindada contra *overwrites* acidentais durante a atualização.
|
|
38
|
+
|
|
39
|
+
### Como Atualizar um Projeto Existente
|
|
40
|
+
Para trazer as regras e inteligências mais recentes para o seu repositório sem perder o seu contexto salvo, basta rodar o `init` forçando a versão `@latest` do NPM:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx sauron-cli@latest init
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Comandos da CLI
|
|
47
|
+
|
|
32
48
|
```bash
|
|
33
49
|
# Inicialização interativa padrão
|
|
34
50
|
sauron init
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/features/init/init.command.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import fs13 from "fs-extra";
|
|
8
|
+
import path13 from "path";
|
|
9
9
|
import pc2 from "picocolors";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import * as p2 from "@clack/prompts";
|
|
@@ -36,8 +36,8 @@ async function saveManifest(targetDir, manifest) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// src/features/init/init.service.ts
|
|
39
|
-
import
|
|
40
|
-
import
|
|
39
|
+
import fs11 from "fs-extra";
|
|
40
|
+
import path11 from "path";
|
|
41
41
|
|
|
42
42
|
// src/core/merge.service.ts
|
|
43
43
|
function checkConflict(localContent, newContent, manifestHash) {
|
|
@@ -146,31 +146,59 @@ var RegistryService = class {
|
|
|
146
146
|
}
|
|
147
147
|
};
|
|
148
148
|
|
|
149
|
+
// src/core/registry/AdapterRegistry.ts
|
|
150
|
+
var AdapterRegistry = class {
|
|
151
|
+
static adapters = /* @__PURE__ */ new Map();
|
|
152
|
+
static register(adapter) {
|
|
153
|
+
if (this.adapters.has(adapter.id)) {
|
|
154
|
+
throw new Error(`Conflito: Adaptador com ID '${adapter.id}' j\xE1 foi registrado no ecossistema.`);
|
|
155
|
+
}
|
|
156
|
+
this.adapters.set(adapter.id, adapter);
|
|
157
|
+
}
|
|
158
|
+
static resolve(ids) {
|
|
159
|
+
const resolvedAdapters = [];
|
|
160
|
+
for (const id of ids) {
|
|
161
|
+
const normalizedId = id.toLowerCase().trim();
|
|
162
|
+
const adapterInstance = this.adapters.get(normalizedId);
|
|
163
|
+
if (!adapterInstance) {
|
|
164
|
+
throw new Error(`Falha Cr\xEDtica: Agente AI '${id}' n\xE3o \xE9 suportado pela vers\xE3o atual.`);
|
|
165
|
+
}
|
|
166
|
+
resolvedAdapters.push(adapterInstance);
|
|
167
|
+
}
|
|
168
|
+
return resolvedAdapters;
|
|
169
|
+
}
|
|
170
|
+
static getAll() {
|
|
171
|
+
return Array.from(this.adapters.values());
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
149
175
|
// src/core/adapters/cursor.adapter.ts
|
|
150
176
|
import fs3 from "fs-extra";
|
|
151
177
|
import path3 from "path";
|
|
152
178
|
var CursorAdapter = class {
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
id = "cursor";
|
|
180
|
+
displayName = "Cursor IDE Engine";
|
|
181
|
+
async detect(targetDir) {
|
|
182
|
+
return fs3.pathExists(path3.join(targetDir, ".cursor"));
|
|
155
183
|
}
|
|
156
|
-
async inject(
|
|
157
|
-
const rulesDir = path3.join(
|
|
184
|
+
async inject(payload, targetDir) {
|
|
185
|
+
const rulesDir = path3.join(targetDir, ".cursor", "rules");
|
|
158
186
|
await fs3.ensureDir(rulesDir);
|
|
159
187
|
const mdcPath = path3.join(rulesDir, "sauron-memory.mdc");
|
|
160
188
|
const mdcContent = `---
|
|
161
189
|
description: Diretrizes de Mem\xF3ria e Write Obligation para evitar Amn\xE9sia de Contexto
|
|
162
|
-
globs: *
|
|
190
|
+
globs: ${payload.ruleScope || "*"}
|
|
163
191
|
---
|
|
164
192
|
|
|
165
193
|
# SAURON START
|
|
166
|
-
${
|
|
194
|
+
${payload.globalRules}
|
|
167
195
|
# SAURON END
|
|
168
196
|
`;
|
|
169
197
|
await fs3.writeFile(mdcPath, mdcContent, "utf8");
|
|
170
198
|
return [".cursor/rules/sauron-memory.mdc"];
|
|
171
199
|
}
|
|
172
|
-
async clean(
|
|
173
|
-
const mdcPath = path3.join(
|
|
200
|
+
async clean(targetDir) {
|
|
201
|
+
const mdcPath = path3.join(targetDir, ".cursor", "rules", "sauron-memory.mdc");
|
|
174
202
|
if (await fs3.pathExists(mdcPath)) {
|
|
175
203
|
await fs3.remove(mdcPath);
|
|
176
204
|
return [".cursor/rules/sauron-memory.mdc"];
|
|
@@ -183,38 +211,60 @@ ${rulesContent}
|
|
|
183
211
|
import fs4 from "fs-extra";
|
|
184
212
|
import path4 from "path";
|
|
185
213
|
var WindsurfAdapter = class {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
214
|
+
id = "windsurf";
|
|
215
|
+
displayName = "Windsurf by Codeium";
|
|
216
|
+
async detect(targetDir) {
|
|
217
|
+
return fs4.pathExists(path4.join(targetDir, ".windsurf"));
|
|
218
|
+
}
|
|
219
|
+
async inject(payload, targetDir) {
|
|
220
|
+
const modifications = [];
|
|
221
|
+
const rulesDir = path4.join(targetDir, ".windsurf", "rules");
|
|
222
|
+
await fs4.ensureDir(rulesDir);
|
|
223
|
+
const ruleContent = `---
|
|
224
|
+
description: "Sauron Memory Override - Universal Guidelines and Base Types"
|
|
225
|
+
trigger: always_on
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
# Overarching Project Context
|
|
229
|
+
${payload.globalRules}
|
|
230
|
+
|
|
231
|
+
*Note: For dynamic capabilities, rely on native skill sets; for fundamental structures, observe these bounds.*
|
|
200
232
|
`;
|
|
201
|
-
const
|
|
202
|
-
await fs4.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
233
|
+
const mdPath = path4.join(rulesDir, "sauron-memory.md");
|
|
234
|
+
await fs4.outputFile(mdPath, ruleContent, "utf-8");
|
|
235
|
+
modifications.push(".windsurf/rules/sauron-memory.md");
|
|
236
|
+
const oldPath = path4.join(targetDir, ".windsurfrules");
|
|
237
|
+
if (await fs4.pathExists(oldPath)) {
|
|
238
|
+
const localContent = await fs4.readFile(oldPath, "utf8");
|
|
239
|
+
const cleanedContent = this.removeSauronBlock(localContent).trim();
|
|
240
|
+
if (cleanedContent === "") {
|
|
241
|
+
await fs4.remove(oldPath);
|
|
242
|
+
} else {
|
|
243
|
+
await fs4.writeFile(oldPath, cleanedContent + "\n", "utf8");
|
|
244
|
+
}
|
|
245
|
+
modifications.push(".windsurfrules");
|
|
209
246
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
247
|
+
return modifications;
|
|
248
|
+
}
|
|
249
|
+
async clean(targetDir) {
|
|
250
|
+
const modifications = [];
|
|
251
|
+
const mdPath = path4.join(targetDir, ".windsurf", "rules", "sauron-memory.md");
|
|
252
|
+
if (await fs4.pathExists(mdPath)) {
|
|
253
|
+
await fs4.remove(mdPath);
|
|
254
|
+
modifications.push(".windsurf/rules/sauron-memory.md");
|
|
255
|
+
}
|
|
256
|
+
const oldPath = path4.join(targetDir, ".windsurfrules");
|
|
257
|
+
if (await fs4.pathExists(oldPath)) {
|
|
258
|
+
const localContent = await fs4.readFile(oldPath, "utf8");
|
|
259
|
+
const cleanedContent = this.removeSauronBlock(localContent).trim();
|
|
260
|
+
if (cleanedContent === "") {
|
|
261
|
+
await fs4.remove(oldPath);
|
|
262
|
+
} else {
|
|
263
|
+
await fs4.writeFile(oldPath, cleanedContent + "\n", "utf8");
|
|
264
|
+
}
|
|
265
|
+
modifications.push(".windsurfrules");
|
|
216
266
|
}
|
|
217
|
-
return
|
|
267
|
+
return modifications;
|
|
218
268
|
}
|
|
219
269
|
removeSauronBlock(content) {
|
|
220
270
|
const regex = /# SAURON START[\s\S]*?# SAURON END/g;
|
|
@@ -225,26 +275,90 @@ ${rulesContent}
|
|
|
225
275
|
// src/core/adapters/aider.adapter.ts
|
|
226
276
|
import fs5 from "fs-extra";
|
|
227
277
|
import path5 from "path";
|
|
278
|
+
import * as yaml from "js-yaml";
|
|
279
|
+
function isAiderConfigValid(config) {
|
|
280
|
+
return typeof config === "object" && config !== null;
|
|
281
|
+
}
|
|
228
282
|
var AiderAdapter = class {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
await fs5.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
283
|
+
id = "aider";
|
|
284
|
+
displayName = "Aider Agent CLI";
|
|
285
|
+
async detect(targetDir) {
|
|
286
|
+
return fs5.pathExists(path5.join(targetDir, ".aider.conf.yml"));
|
|
287
|
+
}
|
|
288
|
+
async inject(payload, targetDir) {
|
|
289
|
+
const modifications = [];
|
|
290
|
+
const memoryFileName = ".sauron-aider-instructions.md";
|
|
291
|
+
const instructionsPath = path5.join(targetDir, memoryFileName);
|
|
292
|
+
await fs5.outputFile(instructionsPath, payload.globalRules, "utf-8");
|
|
293
|
+
modifications.push(memoryFileName);
|
|
294
|
+
const configPath = path5.join(targetDir, ".aider.conf.yml");
|
|
295
|
+
let configObj = {};
|
|
296
|
+
if (await fs5.pathExists(configPath)) {
|
|
297
|
+
try {
|
|
298
|
+
const configStr = await fs5.readFile(configPath, "utf8");
|
|
299
|
+
configObj = yaml.load(configStr) || {};
|
|
300
|
+
} catch (err) {
|
|
301
|
+
throw new Error(`O YAML subjacente em ${configPath} est\xE1 corrompido ou malformado.`);
|
|
302
|
+
}
|
|
246
303
|
}
|
|
247
|
-
|
|
304
|
+
if (isAiderConfigValid(configObj)) {
|
|
305
|
+
if (!configObj.read) {
|
|
306
|
+
configObj.read = [];
|
|
307
|
+
} else if (typeof configObj.read === "string") {
|
|
308
|
+
configObj.read = [configObj.read];
|
|
309
|
+
}
|
|
310
|
+
if (Array.isArray(configObj.read) && !configObj.read.includes(memoryFileName)) {
|
|
311
|
+
configObj.read.push(memoryFileName);
|
|
312
|
+
}
|
|
313
|
+
const newYamlDump = yaml.dump(configObj, { indent: 2, lineWidth: -1 });
|
|
314
|
+
await fs5.outputFile(configPath, newYamlDump, "utf-8");
|
|
315
|
+
modifications.push(".aider.conf.yml");
|
|
316
|
+
} else {
|
|
317
|
+
throw new Error("Falha no cast. A estrutura lida do '.aider.conf.yml' n\xE3o corresponde a um objeto mape\xE1vel.");
|
|
318
|
+
}
|
|
319
|
+
const oldPath = path5.join(targetDir, ".aider.instructions.md");
|
|
320
|
+
if (await fs5.pathExists(oldPath)) {
|
|
321
|
+
await fs5.remove(oldPath);
|
|
322
|
+
modifications.push(".aider.instructions.md");
|
|
323
|
+
}
|
|
324
|
+
return modifications;
|
|
325
|
+
}
|
|
326
|
+
async clean(targetDir) {
|
|
327
|
+
const modifications = [];
|
|
328
|
+
const memoryFileName = ".sauron-aider-instructions.md";
|
|
329
|
+
const instructionsPath = path5.join(targetDir, memoryFileName);
|
|
330
|
+
if (await fs5.pathExists(instructionsPath)) {
|
|
331
|
+
await fs5.remove(instructionsPath);
|
|
332
|
+
modifications.push(memoryFileName);
|
|
333
|
+
}
|
|
334
|
+
const configPath = path5.join(targetDir, ".aider.conf.yml");
|
|
335
|
+
if (await fs5.pathExists(configPath)) {
|
|
336
|
+
try {
|
|
337
|
+
const configStr = await fs5.readFile(configPath, "utf8");
|
|
338
|
+
const configObj = yaml.load(configStr);
|
|
339
|
+
if (isAiderConfigValid(configObj) && Array.isArray(configObj.read)) {
|
|
340
|
+
const newRead = configObj.read.filter((p5) => p5 !== memoryFileName);
|
|
341
|
+
if (newRead.length !== configObj.read.length) {
|
|
342
|
+
configObj.read = newRead;
|
|
343
|
+
if (configObj.read.length === 0) delete configObj.read;
|
|
344
|
+
const newYamlDump = yaml.dump(configObj, { indent: 2, lineWidth: -1 });
|
|
345
|
+
if (Object.keys(configObj).length === 0) {
|
|
346
|
+
await fs5.remove(configPath);
|
|
347
|
+
} else {
|
|
348
|
+
await fs5.outputFile(configPath, newYamlDump, "utf-8");
|
|
349
|
+
}
|
|
350
|
+
modifications.push(".aider.conf.yml");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} catch (err) {
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const oldPath = path5.join(targetDir, ".aider.instructions.md");
|
|
357
|
+
if (await fs5.pathExists(oldPath)) {
|
|
358
|
+
await fs5.remove(oldPath);
|
|
359
|
+
modifications.push(".aider.instructions.md");
|
|
360
|
+
}
|
|
361
|
+
return modifications;
|
|
248
362
|
}
|
|
249
363
|
};
|
|
250
364
|
|
|
@@ -252,25 +366,27 @@ ${rulesContent}
|
|
|
252
366
|
import fs6 from "fs-extra";
|
|
253
367
|
import path6 from "path";
|
|
254
368
|
var AntigravityAdapter = class {
|
|
255
|
-
|
|
256
|
-
|
|
369
|
+
id = "antigravity";
|
|
370
|
+
displayName = "Antigravity IDE Engine";
|
|
371
|
+
async detect(targetDir) {
|
|
372
|
+
return fs6.pathExists(path6.join(targetDir, ".agents"));
|
|
257
373
|
}
|
|
258
|
-
async inject(
|
|
259
|
-
const rulesPath = path6.join(
|
|
374
|
+
async inject(payload, targetDir) {
|
|
375
|
+
const rulesPath = path6.join(targetDir, ".agents", "rules", "memory.md");
|
|
260
376
|
await fs6.ensureDir(path6.dirname(rulesPath));
|
|
261
377
|
const content = `---
|
|
262
378
|
trigger: always_on
|
|
263
379
|
---
|
|
264
380
|
|
|
265
381
|
# SAURON START
|
|
266
|
-
${
|
|
382
|
+
${payload.globalRules}
|
|
267
383
|
# SAURON END
|
|
268
384
|
`;
|
|
269
385
|
await fs6.writeFile(rulesPath, content, "utf8");
|
|
270
386
|
return [".agents/rules/memory.md"];
|
|
271
387
|
}
|
|
272
|
-
async clean(
|
|
273
|
-
const agentsDir = path6.join(
|
|
388
|
+
async clean(targetDir) {
|
|
389
|
+
const agentsDir = path6.join(targetDir, ".agents");
|
|
274
390
|
if (await fs6.pathExists(agentsDir)) {
|
|
275
391
|
await fs6.remove(agentsDir);
|
|
276
392
|
return [".agents/rules/memory.md", ".agents/skills/wiki/SKILL.md"];
|
|
@@ -279,75 +395,193 @@ ${rulesContent}
|
|
|
279
395
|
}
|
|
280
396
|
};
|
|
281
397
|
|
|
282
|
-
// src/core/adapters/adapter.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
398
|
+
// src/core/adapters/opencode.adapter.ts
|
|
399
|
+
import fs7 from "fs-extra";
|
|
400
|
+
import path7 from "path";
|
|
401
|
+
var OpencodeAdapter = class {
|
|
402
|
+
id = "opencode";
|
|
403
|
+
displayName = "Opencode Multi-Model Interface";
|
|
404
|
+
async detect(targetDir) {
|
|
405
|
+
return fs7.pathExists(path7.join(targetDir, "opencode.json"));
|
|
406
|
+
}
|
|
407
|
+
async inject(payload, targetDir) {
|
|
408
|
+
const modifications = [];
|
|
409
|
+
const instructionDir = path7.join(targetDir, ".opencode", "instructions");
|
|
410
|
+
await fs7.ensureDir(instructionDir);
|
|
411
|
+
const rulesPath = path7.join(instructionDir, "sauron-memory.md");
|
|
412
|
+
await fs7.outputFile(rulesPath, payload.globalRules, "utf-8");
|
|
413
|
+
modifications.push(".opencode/instructions/sauron-memory.md");
|
|
414
|
+
const configPath = path7.join(targetDir, "opencode.json");
|
|
415
|
+
let configObj = {};
|
|
416
|
+
if (await fs7.pathExists(configPath)) {
|
|
417
|
+
try {
|
|
418
|
+
configObj = await fs7.readJson(configPath);
|
|
419
|
+
} catch (e) {
|
|
420
|
+
throw new Error("Falha no parse do arquivo base opencode.json. JSON inv\xE1lido detectado.");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (!Array.isArray(configObj.instructions)) {
|
|
424
|
+
configObj.instructions = configObj.instructions ? [configObj.instructions] : [];
|
|
296
425
|
}
|
|
426
|
+
const relativeRulesPath = ".opencode/instructions/sauron-memory.md";
|
|
427
|
+
if (!configObj.instructions.includes(relativeRulesPath)) {
|
|
428
|
+
configObj.instructions.push(relativeRulesPath);
|
|
429
|
+
await fs7.writeJson(configPath, configObj, { spaces: 2 });
|
|
430
|
+
modifications.push("opencode.json");
|
|
431
|
+
}
|
|
432
|
+
return modifications;
|
|
433
|
+
}
|
|
434
|
+
async clean(targetDir) {
|
|
435
|
+
const modifications = [];
|
|
436
|
+
const rulesPath = path7.join(targetDir, ".opencode", "instructions", "sauron-memory.md");
|
|
437
|
+
if (await fs7.pathExists(rulesPath)) {
|
|
438
|
+
await fs7.remove(rulesPath);
|
|
439
|
+
modifications.push(".opencode/instructions/sauron-memory.md");
|
|
440
|
+
}
|
|
441
|
+
const configPath = path7.join(targetDir, "opencode.json");
|
|
442
|
+
if (await fs7.pathExists(configPath)) {
|
|
443
|
+
try {
|
|
444
|
+
const configObj = await fs7.readJson(configPath);
|
|
445
|
+
if (Array.isArray(configObj.instructions)) {
|
|
446
|
+
const relativeRulesPath = ".opencode/instructions/sauron-memory.md";
|
|
447
|
+
const newInstructions = configObj.instructions.filter((i) => i !== relativeRulesPath);
|
|
448
|
+
if (newInstructions.length !== configObj.instructions.length) {
|
|
449
|
+
configObj.instructions = newInstructions;
|
|
450
|
+
if (configObj.instructions.length === 0) delete configObj.instructions;
|
|
451
|
+
if (Object.keys(configObj).length === 0) {
|
|
452
|
+
await fs7.remove(configPath);
|
|
453
|
+
} else {
|
|
454
|
+
await fs7.writeJson(configPath, configObj, { spaces: 2 });
|
|
455
|
+
}
|
|
456
|
+
modifications.push("opencode.json");
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
} catch (e) {
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return modifications;
|
|
297
463
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/core/adapters/codex.adapter.ts
|
|
467
|
+
import fs8 from "fs-extra";
|
|
468
|
+
import path8 from "path";
|
|
469
|
+
var CodexAdapter = class {
|
|
470
|
+
id = "codex";
|
|
471
|
+
displayName = "Codex Local Sandbox";
|
|
472
|
+
async detect(targetDir) {
|
|
473
|
+
return fs8.pathExists(path8.join(targetDir, ".codex"));
|
|
474
|
+
}
|
|
475
|
+
async inject(payload, targetDir) {
|
|
476
|
+
const rulesDir = path8.join(targetDir, ".codex", "rules");
|
|
477
|
+
await fs8.ensureDir(rulesDir);
|
|
478
|
+
const codexPolicy = `prompt: true
|
|
479
|
+
allow:
|
|
480
|
+
- read: .sauron/wiki/**
|
|
481
|
+
- read: AGENTS.md
|
|
482
|
+
|
|
483
|
+
# Sauron Central Governance Policy
|
|
484
|
+
${payload.globalRules}
|
|
485
|
+
`;
|
|
486
|
+
const outputPath = path8.join(rulesDir, "sauron-memory.rules");
|
|
487
|
+
await fs8.outputFile(outputPath, codexPolicy, "utf-8");
|
|
488
|
+
return [".codex/rules/sauron-memory.rules"];
|
|
489
|
+
}
|
|
490
|
+
async clean(targetDir) {
|
|
491
|
+
const rulesPath = path8.join(targetDir, ".codex", "rules", "sauron-memory.rules");
|
|
492
|
+
if (await fs8.pathExists(rulesPath)) {
|
|
493
|
+
await fs8.remove(rulesPath);
|
|
494
|
+
return [".codex/rules/sauron-memory.rules"];
|
|
495
|
+
}
|
|
496
|
+
return [];
|
|
305
497
|
}
|
|
306
498
|
};
|
|
307
499
|
|
|
500
|
+
// src/core/adapters/claude.adapter.ts
|
|
501
|
+
import fs9 from "fs-extra";
|
|
502
|
+
import path9 from "path";
|
|
503
|
+
var ClaudeAdapter = class {
|
|
504
|
+
id = "claude";
|
|
505
|
+
displayName = "Claude Code CLI";
|
|
506
|
+
async detect(targetDir) {
|
|
507
|
+
return fs9.pathExists(path9.join(targetDir, ".claude"));
|
|
508
|
+
}
|
|
509
|
+
async inject(payload, targetDir) {
|
|
510
|
+
const rulesDir = path9.join(targetDir, ".claude", "rules");
|
|
511
|
+
await fs9.ensureDir(rulesDir);
|
|
512
|
+
const claudeRuleContent = `# Sauron Governance Directives
|
|
513
|
+
|
|
514
|
+
## Context Inheritance
|
|
515
|
+
As per architectural guidelines, Claude must persistently obey instructions centralized in the global ${payload.fallbackReference || "AGENTS.md"} file within this project root.
|
|
516
|
+
|
|
517
|
+
## Local Addendum Context
|
|
518
|
+
${payload.globalRules}`;
|
|
519
|
+
const outputPath = path9.join(rulesDir, "sauron-base.md");
|
|
520
|
+
await fs9.outputFile(outputPath, claudeRuleContent, "utf-8");
|
|
521
|
+
return [".claude/rules/sauron-base.md"];
|
|
522
|
+
}
|
|
523
|
+
async clean(targetDir) {
|
|
524
|
+
const rulesPath = path9.join(targetDir, ".claude", "rules", "sauron-base.md");
|
|
525
|
+
if (await fs9.pathExists(rulesPath)) {
|
|
526
|
+
await fs9.remove(rulesPath);
|
|
527
|
+
return [".claude/rules/sauron-base.md"];
|
|
528
|
+
}
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/core/adapters/index.ts
|
|
534
|
+
AdapterRegistry.register(new CursorAdapter());
|
|
535
|
+
AdapterRegistry.register(new WindsurfAdapter());
|
|
536
|
+
AdapterRegistry.register(new AiderAdapter());
|
|
537
|
+
AdapterRegistry.register(new AntigravityAdapter());
|
|
538
|
+
AdapterRegistry.register(new OpencodeAdapter());
|
|
539
|
+
AdapterRegistry.register(new CodexAdapter());
|
|
540
|
+
AdapterRegistry.register(new ClaudeAdapter());
|
|
541
|
+
|
|
308
542
|
// src/core/wiki/wiki-bootstrapper.ts
|
|
309
|
-
import
|
|
310
|
-
import
|
|
543
|
+
import fs10 from "fs-extra";
|
|
544
|
+
import path10 from "path";
|
|
311
545
|
var WikiBootstrapper = class {
|
|
312
546
|
constructor(projectRoot) {
|
|
313
547
|
this.projectRoot = projectRoot;
|
|
314
|
-
this.wikiDir =
|
|
315
|
-
this.standardsDir =
|
|
548
|
+
this.wikiDir = path10.join(projectRoot, ".sauron", "wiki");
|
|
549
|
+
this.standardsDir = path10.join(this.wikiDir, "standards");
|
|
316
550
|
}
|
|
317
551
|
projectRoot;
|
|
318
552
|
standardsDir;
|
|
319
553
|
wikiDir;
|
|
320
554
|
async bootstrapFromTemplates(templatesDir, wikiTemplatesToInject) {
|
|
321
555
|
const injectedFiles = [];
|
|
322
|
-
const recipesSrcDir =
|
|
323
|
-
if (!await
|
|
556
|
+
const recipesSrcDir = path10.join(templatesDir, "wiki-recipes");
|
|
557
|
+
if (!await fs10.pathExists(recipesSrcDir)) {
|
|
324
558
|
return [];
|
|
325
559
|
}
|
|
326
|
-
await
|
|
327
|
-
const summaryPath =
|
|
560
|
+
await fs10.ensureDir(this.standardsDir);
|
|
561
|
+
const summaryPath = path10.join(this.wikiDir, "summary.json");
|
|
328
562
|
let summary = [];
|
|
329
|
-
if (await
|
|
563
|
+
if (await fs10.pathExists(summaryPath)) {
|
|
330
564
|
try {
|
|
331
|
-
summary = await
|
|
565
|
+
summary = await fs10.readJson(summaryPath);
|
|
332
566
|
} catch (err) {
|
|
333
567
|
summary = [];
|
|
334
568
|
}
|
|
335
569
|
}
|
|
336
570
|
for (const templateName of wikiTemplatesToInject) {
|
|
337
|
-
const srcTemplatePath =
|
|
338
|
-
if (!await
|
|
571
|
+
const srcTemplatePath = path10.join(recipesSrcDir, templateName);
|
|
572
|
+
if (!await fs10.pathExists(srcTemplatePath)) {
|
|
339
573
|
continue;
|
|
340
574
|
}
|
|
341
575
|
const destFilename = templateName;
|
|
342
|
-
const destFullPath =
|
|
576
|
+
const destFullPath = path10.join(this.standardsDir, destFilename);
|
|
343
577
|
const relWikiPath = `standards/${destFilename}`;
|
|
344
|
-
const content = await
|
|
578
|
+
const content = await fs10.readFile(srcTemplatePath, "utf8");
|
|
345
579
|
const hash = generateHash(content);
|
|
346
580
|
const len = Buffer.byteLength(content, "utf8");
|
|
347
|
-
const exists = await
|
|
581
|
+
const exists = await fs10.pathExists(destFullPath);
|
|
348
582
|
if (!exists) {
|
|
349
|
-
await
|
|
350
|
-
injectedFiles.push(
|
|
583
|
+
await fs10.writeFile(destFullPath, content, "utf8");
|
|
584
|
+
injectedFiles.push(path10.join(".sauron", "wiki", relWikiPath).replace(/\\/g, "/"));
|
|
351
585
|
}
|
|
352
586
|
const slug = destFilename.replace(".rules.md", "").replace(".rules.txt", "");
|
|
353
587
|
const docName = this.inferDocName(slug);
|
|
@@ -374,7 +608,7 @@ var WikiBootstrapper = class {
|
|
|
374
608
|
summary.push(newItem);
|
|
375
609
|
}
|
|
376
610
|
}
|
|
377
|
-
await
|
|
611
|
+
await fs10.writeJson(summaryPath, summary, { spaces: 2 });
|
|
378
612
|
return injectedFiles;
|
|
379
613
|
}
|
|
380
614
|
inferDocName(slug) {
|
|
@@ -403,25 +637,25 @@ var InitService = class {
|
|
|
403
637
|
const manifest = await getManifest(cwd) || { version: "1.0.0", files: {} };
|
|
404
638
|
const modifiedFiles = [];
|
|
405
639
|
const processDirectory = async (source, target) => {
|
|
406
|
-
if (!await
|
|
407
|
-
const files = await
|
|
640
|
+
if (!await fs11.pathExists(source)) return;
|
|
641
|
+
const files = await fs11.readdir(source);
|
|
408
642
|
for (const file of files) {
|
|
409
|
-
const sourcePath =
|
|
410
|
-
const targetPath =
|
|
411
|
-
const stat = await
|
|
643
|
+
const sourcePath = path11.join(source, file);
|
|
644
|
+
const targetPath = path11.join(target, file);
|
|
645
|
+
const stat = await fs11.stat(sourcePath);
|
|
412
646
|
if (stat.isDirectory()) {
|
|
413
|
-
const relativeToTarget =
|
|
414
|
-
if (relativeToTarget === ".sauron/wiki" && await
|
|
647
|
+
const relativeToTarget = path11.relative(cwd, targetPath).replace(/\\/g, "/");
|
|
648
|
+
if (relativeToTarget === ".sauron/wiki" && await fs11.pathExists(targetPath)) {
|
|
415
649
|
continue;
|
|
416
650
|
}
|
|
417
|
-
await
|
|
651
|
+
await fs11.ensureDir(targetPath);
|
|
418
652
|
await processDirectory(sourcePath, targetPath);
|
|
419
653
|
} else {
|
|
420
|
-
const content = await
|
|
421
|
-
const relPath =
|
|
654
|
+
const content = await fs11.readFile(sourcePath, "utf8");
|
|
655
|
+
const relPath = path11.relative(cwd, targetPath).replace(/\\/g, "/");
|
|
422
656
|
let shouldWrite = true;
|
|
423
|
-
if (await
|
|
424
|
-
const localContent = await
|
|
657
|
+
if (await fs11.pathExists(targetPath)) {
|
|
658
|
+
const localContent = await fs11.readFile(targetPath, "utf8");
|
|
425
659
|
const hasConflict = checkConflict(localContent, content, manifest.files[relPath]);
|
|
426
660
|
if (hasConflict) {
|
|
427
661
|
const decision = await driver.resolveConflict(relPath, localContent, content);
|
|
@@ -430,10 +664,10 @@ var InitService = class {
|
|
|
430
664
|
}
|
|
431
665
|
}
|
|
432
666
|
} else {
|
|
433
|
-
await
|
|
667
|
+
await fs11.ensureDir(path11.dirname(targetPath));
|
|
434
668
|
}
|
|
435
669
|
if (shouldWrite) {
|
|
436
|
-
await
|
|
670
|
+
await fs11.writeFile(targetPath, content, "utf8");
|
|
437
671
|
modifiedFiles.push(relPath);
|
|
438
672
|
}
|
|
439
673
|
const isMutable = relPath.startsWith(".sauron/wiki/") || relPath === ".agents/rules/memory.md";
|
|
@@ -443,9 +677,9 @@ var InitService = class {
|
|
|
443
677
|
}
|
|
444
678
|
}
|
|
445
679
|
};
|
|
446
|
-
await processDirectory(
|
|
447
|
-
await processDirectory(
|
|
448
|
-
const agentsMdPath =
|
|
680
|
+
await processDirectory(path11.join(templatesDir, ".sauron"), path11.join(cwd, ".sauron"));
|
|
681
|
+
await processDirectory(path11.join(templatesDir, ".agents"), path11.join(cwd, ".agents"));
|
|
682
|
+
const agentsMdPath = path11.join(cwd, "AGENTS.md");
|
|
449
683
|
const agentsMdContent = generateAgentsMarkdown(
|
|
450
684
|
options.aiTargets,
|
|
451
685
|
options.severity,
|
|
@@ -453,8 +687,8 @@ var InitService = class {
|
|
|
453
687
|
options.projectStack
|
|
454
688
|
);
|
|
455
689
|
let shouldWriteAgents = true;
|
|
456
|
-
if (await
|
|
457
|
-
const localAgents = await
|
|
690
|
+
if (await fs11.pathExists(agentsMdPath)) {
|
|
691
|
+
const localAgents = await fs11.readFile(agentsMdPath, "utf8");
|
|
458
692
|
const hasConflict = checkConflict(localAgents, agentsMdContent, manifest.files["AGENTS.md"]);
|
|
459
693
|
if (hasConflict) {
|
|
460
694
|
const decision = await driver.resolveConflict("AGENTS.md", localAgents, agentsMdContent);
|
|
@@ -464,7 +698,7 @@ var InitService = class {
|
|
|
464
698
|
}
|
|
465
699
|
}
|
|
466
700
|
if (shouldWriteAgents) {
|
|
467
|
-
await
|
|
701
|
+
await fs11.writeFile(agentsMdPath, agentsMdContent, "utf8");
|
|
468
702
|
modifiedFiles.push("AGENTS.md");
|
|
469
703
|
}
|
|
470
704
|
manifest.files["AGENTS.md"] = generateHash(agentsMdContent);
|
|
@@ -476,19 +710,28 @@ var InitService = class {
|
|
|
476
710
|
};
|
|
477
711
|
await saveManifest(cwd, manifest);
|
|
478
712
|
modifiedFiles.push(".sauron/.manifest.json");
|
|
479
|
-
const memoryFilePath =
|
|
713
|
+
const memoryFilePath = path11.join(cwd, ".agents", "rules", "memory.md");
|
|
480
714
|
let memoryRulesContent = "";
|
|
481
|
-
if (await
|
|
482
|
-
memoryRulesContent = await
|
|
715
|
+
if (await fs11.pathExists(memoryFilePath)) {
|
|
716
|
+
memoryRulesContent = await fs11.readFile(memoryFilePath, "utf8");
|
|
483
717
|
} else {
|
|
484
718
|
memoryRulesContent = agentsMdContent;
|
|
485
719
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
720
|
+
const projectName = path11.basename(cwd) || "Unnamed Project";
|
|
721
|
+
const memoryPayload = {
|
|
722
|
+
projectName,
|
|
723
|
+
globalRules: memoryRulesContent,
|
|
724
|
+
ruleScope: "**/*.{ts,js,tsx,jsx}",
|
|
725
|
+
fallbackReference: "AGENTS.md"
|
|
726
|
+
};
|
|
727
|
+
try {
|
|
728
|
+
const adapters = AdapterRegistry.resolve(options.aiTargets);
|
|
729
|
+
for (const adapter of adapters) {
|
|
730
|
+
const paths = await adapter.inject(memoryPayload, cwd);
|
|
490
731
|
modifiedFiles.push(...paths);
|
|
491
732
|
}
|
|
733
|
+
} catch (error) {
|
|
734
|
+
throw new Error(`Erro ao orquestrar adaptadores: ${error.message}`);
|
|
492
735
|
}
|
|
493
736
|
const bootstrapper = new WikiBootstrapper(cwd);
|
|
494
737
|
const injectedWikiFiles = await bootstrapper.bootstrapFromTemplates(
|
|
@@ -496,7 +739,6 @@ var InitService = class {
|
|
|
496
739
|
options.wikiTemplatesToInject
|
|
497
740
|
);
|
|
498
741
|
modifiedFiles.push(...injectedWikiFiles);
|
|
499
|
-
const projectName = path8.basename(cwd) || "Unnamed Project";
|
|
500
742
|
await this.registryService.registerWorkspace(
|
|
501
743
|
projectName,
|
|
502
744
|
cwd,
|
|
@@ -711,8 +953,8 @@ var PresentationRouter = class {
|
|
|
711
953
|
};
|
|
712
954
|
|
|
713
955
|
// src/core/scanner/project-scanner.ts
|
|
714
|
-
import
|
|
715
|
-
import
|
|
956
|
+
import fs12 from "fs-extra";
|
|
957
|
+
import path12 from "path";
|
|
716
958
|
|
|
717
959
|
// src/core/scanner/signatures.ts
|
|
718
960
|
var TECHNOLOGY_SIGNATURES = [
|
|
@@ -805,7 +1047,7 @@ var ProjectScanner = class {
|
|
|
805
1047
|
wikiTemplatesToInject: []
|
|
806
1048
|
};
|
|
807
1049
|
try {
|
|
808
|
-
const rootEntries = await
|
|
1050
|
+
const rootEntries = await fs12.readdir(this.cwd, { withFileTypes: true });
|
|
809
1051
|
const rootFiles = rootEntries.filter((e) => e.isFile()).map((e) => e.name);
|
|
810
1052
|
const rootDirs = rootEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
811
1053
|
if (rootFiles.includes("pnpm-lock.yaml") || rootFiles.includes("pnpm-workspace.yaml")) {
|
|
@@ -837,8 +1079,8 @@ var ProjectScanner = class {
|
|
|
837
1079
|
context.detectedIAs = ["Cursor", "Windsurf", "Aider", "Antigravity"];
|
|
838
1080
|
}
|
|
839
1081
|
if (rootFiles.includes("package.json")) {
|
|
840
|
-
const pkgPath =
|
|
841
|
-
const pkg = await
|
|
1082
|
+
const pkgPath = path12.join(this.cwd, "package.json");
|
|
1083
|
+
const pkg = await fs12.readJson(pkgPath);
|
|
842
1084
|
const allDeps = {
|
|
843
1085
|
...pkg.dependencies || {},
|
|
844
1086
|
...pkg.devDependencies || {}
|
|
@@ -859,8 +1101,8 @@ var ProjectScanner = class {
|
|
|
859
1101
|
const composeFiles = ["compose.yml", "docker-compose.yml"];
|
|
860
1102
|
for (const composeFile of composeFiles) {
|
|
861
1103
|
if (rootFiles.includes(composeFile)) {
|
|
862
|
-
const composePath =
|
|
863
|
-
const composeContent = await
|
|
1104
|
+
const composePath = path12.join(this.cwd, composeFile);
|
|
1105
|
+
const composeContent = await fs12.readFile(composePath, "utf8");
|
|
864
1106
|
for (const sig of TECHNOLOGY_SIGNATURES) {
|
|
865
1107
|
if (sig.regex && sig.regex.file === composeFile) {
|
|
866
1108
|
if (sig.regex.pattern.test(composeContent)) {
|
|
@@ -894,7 +1136,7 @@ var ProjectScanner = class {
|
|
|
894
1136
|
|
|
895
1137
|
// src/features/init/init.command.ts
|
|
896
1138
|
var __filename = fileURLToPath(import.meta.url);
|
|
897
|
-
var __dirname =
|
|
1139
|
+
var __dirname = path13.dirname(__filename);
|
|
898
1140
|
async function runInitCommand(options) {
|
|
899
1141
|
const cwd = process.cwd();
|
|
900
1142
|
const session = new SessionContext({
|
|
@@ -948,9 +1190,9 @@ async function runInitCommand(options) {
|
|
|
948
1190
|
projectContext = manifestData.config.projectContext;
|
|
949
1191
|
projectStack = manifestData.config.projectStack;
|
|
950
1192
|
} else {
|
|
951
|
-
const agentsMdPath =
|
|
952
|
-
if (await
|
|
953
|
-
const content = await
|
|
1193
|
+
const agentsMdPath = path13.join(cwd, "AGENTS.md");
|
|
1194
|
+
if (await fs13.pathExists(agentsMdPath)) {
|
|
1195
|
+
const content = await fs13.readFile(agentsMdPath, "utf8");
|
|
954
1196
|
const targetsMatch = content.match(/\*\*Targets:\*\* (.*)/);
|
|
955
1197
|
if (targetsMatch) aiTargets = targetsMatch[1].split(",").map((s) => s.trim());
|
|
956
1198
|
const severityMatch = content.match(/\*\*Severity:\*\* (.*)/);
|
|
@@ -982,6 +1224,9 @@ async function runInitCommand(options) {
|
|
|
982
1224
|
options: [
|
|
983
1225
|
{ value: "Cursor", label: "Cursor", hint: "Recomendado" },
|
|
984
1226
|
{ value: "Windsurf", label: "Windsurf" },
|
|
1227
|
+
{ value: "Claude", label: "Claude Code" },
|
|
1228
|
+
{ value: "Codex", label: "Codex" },
|
|
1229
|
+
{ value: "Opencode", label: "Opencode" },
|
|
985
1230
|
{ value: "Aider", label: "Aider" },
|
|
986
1231
|
{ value: "Antigravity", label: "Antigravity", hint: "Agente nativo" }
|
|
987
1232
|
],
|
|
@@ -1024,9 +1269,9 @@ async function runInitCommand(options) {
|
|
|
1024
1269
|
}
|
|
1025
1270
|
}
|
|
1026
1271
|
driver.startSpinner("Injetando o C\xE9rebro da IA no reposit\xF3rio...");
|
|
1027
|
-
let templatesDir =
|
|
1028
|
-
if (!
|
|
1029
|
-
templatesDir =
|
|
1272
|
+
let templatesDir = path13.join(__dirname, "..", "templates");
|
|
1273
|
+
if (!fs13.existsSync(templatesDir)) {
|
|
1274
|
+
templatesDir = path13.join(__dirname, "..", "..", "..", "templates");
|
|
1030
1275
|
}
|
|
1031
1276
|
const initService = new InitService();
|
|
1032
1277
|
try {
|
|
@@ -1048,7 +1293,7 @@ async function runInitCommand(options) {
|
|
|
1048
1293
|
success: true,
|
|
1049
1294
|
message,
|
|
1050
1295
|
payload: {
|
|
1051
|
-
projectName:
|
|
1296
|
+
projectName: path13.basename(cwd),
|
|
1052
1297
|
cwd,
|
|
1053
1298
|
aiTargets,
|
|
1054
1299
|
severity,
|
|
@@ -1066,16 +1311,16 @@ import pc3 from "picocolors";
|
|
|
1066
1311
|
import * as p3 from "@clack/prompts";
|
|
1067
1312
|
|
|
1068
1313
|
// src/features/doctor/doctor.service.ts
|
|
1069
|
-
import
|
|
1070
|
-
import
|
|
1314
|
+
import fs14 from "fs-extra";
|
|
1315
|
+
import path14 from "path";
|
|
1071
1316
|
var DoctorService = class {
|
|
1072
1317
|
registryService = new RegistryService();
|
|
1073
1318
|
async execute(cwd) {
|
|
1074
1319
|
const issues = [];
|
|
1075
1320
|
const workspaces = await this.registryService.listWorkspaces();
|
|
1076
|
-
const normalizedCwd =
|
|
1321
|
+
const normalizedCwd = path14.resolve(cwd).replace(/\\/g, "/");
|
|
1077
1322
|
const registered = workspaces.find(
|
|
1078
|
-
(w) =>
|
|
1323
|
+
(w) => path14.resolve(w.rootPath).replace(/\\/g, "/") === normalizedCwd
|
|
1079
1324
|
);
|
|
1080
1325
|
if (!registered) {
|
|
1081
1326
|
issues.push({
|
|
@@ -1093,8 +1338,8 @@ var DoctorService = class {
|
|
|
1093
1338
|
});
|
|
1094
1339
|
} else {
|
|
1095
1340
|
for (const [relPath, expectedHash] of Object.entries(manifest.files)) {
|
|
1096
|
-
const fullPath =
|
|
1097
|
-
if (!await
|
|
1341
|
+
const fullPath = path14.join(cwd, relPath);
|
|
1342
|
+
if (!await fs14.pathExists(fullPath)) {
|
|
1098
1343
|
issues.push({
|
|
1099
1344
|
severity: "error",
|
|
1100
1345
|
message: `Arquivo gerenciado pelo manifesto est\xE1 ausente: ${relPath}`,
|
|
@@ -1103,7 +1348,7 @@ var DoctorService = class {
|
|
|
1103
1348
|
});
|
|
1104
1349
|
continue;
|
|
1105
1350
|
}
|
|
1106
|
-
const fileContent = await
|
|
1351
|
+
const fileContent = await fs14.readFile(fullPath, "utf8");
|
|
1107
1352
|
const computedHash = generateHash(fileContent);
|
|
1108
1353
|
if (computedHash !== expectedHash) {
|
|
1109
1354
|
issues.push({
|
|
@@ -1115,8 +1360,8 @@ var DoctorService = class {
|
|
|
1115
1360
|
}
|
|
1116
1361
|
}
|
|
1117
1362
|
}
|
|
1118
|
-
const summaryPath =
|
|
1119
|
-
if (!await
|
|
1363
|
+
const summaryPath = path14.join(cwd, ".sauron", "wiki", "summary.json");
|
|
1364
|
+
if (!await fs14.pathExists(summaryPath)) {
|
|
1120
1365
|
issues.push({
|
|
1121
1366
|
severity: "error",
|
|
1122
1367
|
message: "Arquivo de sum\xE1rio da wiki (.sauron/wiki/summary.json) est\xE1 ausente.",
|
|
@@ -1124,13 +1369,13 @@ var DoctorService = class {
|
|
|
1124
1369
|
});
|
|
1125
1370
|
} else {
|
|
1126
1371
|
try {
|
|
1127
|
-
const summary = await
|
|
1372
|
+
const summary = await fs14.readJson(summaryPath);
|
|
1128
1373
|
if (Array.isArray(summary)) {
|
|
1129
1374
|
for (const item of summary) {
|
|
1130
1375
|
if (item.type === "file") {
|
|
1131
|
-
const fileRelPath =
|
|
1132
|
-
const fileFullPath =
|
|
1133
|
-
if (!await
|
|
1376
|
+
const fileRelPath = path14.join(".sauron", "wiki", item.path).replace(/\\/g, "/");
|
|
1377
|
+
const fileFullPath = path14.join(cwd, ".sauron", "wiki", item.path);
|
|
1378
|
+
if (!await fs14.pathExists(fileFullPath)) {
|
|
1134
1379
|
issues.push({
|
|
1135
1380
|
severity: "error",
|
|
1136
1381
|
message: `Documento catalogado no sum\xE1rio est\xE1 ausente: ${fileRelPath}`,
|
|
@@ -1139,7 +1384,7 @@ var DoctorService = class {
|
|
|
1139
1384
|
});
|
|
1140
1385
|
continue;
|
|
1141
1386
|
}
|
|
1142
|
-
const content = await
|
|
1387
|
+
const content = await fs14.readFile(fileFullPath, "utf8");
|
|
1143
1388
|
const computedHash = generateHash(content);
|
|
1144
1389
|
const computedLen = Buffer.byteLength(content, "utf8");
|
|
1145
1390
|
if (item.contentHash && computedHash !== item.contentHash) {
|
|
@@ -1177,37 +1422,41 @@ var DoctorService = class {
|
|
|
1177
1422
|
}
|
|
1178
1423
|
const targets = registered?.aiTargets || ["Cursor", "Windsurf", "Aider", "Antigravity"];
|
|
1179
1424
|
for (const target of targets) {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
});
|
|
1197
|
-
} else {
|
|
1198
|
-
const rulesContent = await fs11.readFile(fullPath, "utf8");
|
|
1199
|
-
const hasStart = rulesContent.includes("# SAURON START");
|
|
1200
|
-
const hasEnd = rulesContent.includes("# SAURON END");
|
|
1201
|
-
if (!hasStart || !hasEnd) {
|
|
1425
|
+
try {
|
|
1426
|
+
const adapters = AdapterRegistry.resolve([target]);
|
|
1427
|
+
if (adapters.length > 0) {
|
|
1428
|
+
const rulesFileMap = {
|
|
1429
|
+
"cursor": ".cursor/rules/sauron-memory.mdc",
|
|
1430
|
+
"windsurf": ".windsurf/rules/sauron-memory.md",
|
|
1431
|
+
"aider": ".sauron-aider-instructions.md",
|
|
1432
|
+
"antigravity": ".agents/rules/memory.md",
|
|
1433
|
+
"opencode": ".opencode/instructions/sauron-memory.md",
|
|
1434
|
+
"codex": ".codex/rules/sauron-memory.rules",
|
|
1435
|
+
"claude": ".claude/rules/sauron-base.md"
|
|
1436
|
+
};
|
|
1437
|
+
const relPath = rulesFileMap[target.toLowerCase().trim()];
|
|
1438
|
+
if (relPath) {
|
|
1439
|
+
const fullPath = path14.join(cwd, relPath);
|
|
1440
|
+
if (!await fs14.pathExists(fullPath)) {
|
|
1202
1441
|
issues.push({
|
|
1203
1442
|
severity: "error",
|
|
1204
|
-
message: `
|
|
1205
|
-
|
|
1206
|
-
fix: `Rode sauron init para restaurar as diretrizes de compliance no arquivo do agente.`
|
|
1443
|
+
message: `Configura\xE7\xE3o do agente ${target} est\xE1 ausente ou foi deletada: ${relPath}`,
|
|
1444
|
+
fix: `Rode sauron init para reinjetar a integra\xE7\xE3o com o ${target}.`
|
|
1207
1445
|
});
|
|
1446
|
+
} else {
|
|
1447
|
+
const rulesContent = await fs14.readFile(fullPath, "utf8");
|
|
1448
|
+
if (rulesContent.trim().length === 0) {
|
|
1449
|
+
issues.push({
|
|
1450
|
+
severity: "error",
|
|
1451
|
+
message: `Arquivo de governan\xE7a do Sauron est\xE1 vazio em: ${relPath}`,
|
|
1452
|
+
file: relPath,
|
|
1453
|
+
fix: `Rode sauron init para restaurar as diretrizes de compliance no arquivo do agente.`
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1208
1456
|
}
|
|
1209
1457
|
}
|
|
1210
1458
|
}
|
|
1459
|
+
} catch (e) {
|
|
1211
1460
|
}
|
|
1212
1461
|
}
|
|
1213
1462
|
const hasErrors = issues.some((issue) => issue.severity === "error");
|
|
@@ -1277,8 +1526,8 @@ import pc4 from "picocolors";
|
|
|
1277
1526
|
import * as p4 from "@clack/prompts";
|
|
1278
1527
|
|
|
1279
1528
|
// src/features/uninstall/uninstall.service.ts
|
|
1280
|
-
import
|
|
1281
|
-
import
|
|
1529
|
+
import fs15 from "fs-extra";
|
|
1530
|
+
import path15 from "path";
|
|
1282
1531
|
var UninstallService = class {
|
|
1283
1532
|
registryService = new RegistryService();
|
|
1284
1533
|
async execute(options) {
|
|
@@ -1286,25 +1535,25 @@ var UninstallService = class {
|
|
|
1286
1535
|
const removedPaths = [];
|
|
1287
1536
|
await this.registryService.unregisterWorkspace(cwd);
|
|
1288
1537
|
removedPaths.push("~/.sauron/registry.json (descadastrado)");
|
|
1289
|
-
const adapters =
|
|
1538
|
+
const adapters = AdapterRegistry.getAll();
|
|
1290
1539
|
for (const adapter of adapters) {
|
|
1291
1540
|
const paths = await adapter.clean(cwd);
|
|
1292
1541
|
removedPaths.push(...paths);
|
|
1293
1542
|
}
|
|
1294
|
-
const agentsMdPath =
|
|
1295
|
-
if (await
|
|
1296
|
-
await
|
|
1543
|
+
const agentsMdPath = path15.join(cwd, "AGENTS.md");
|
|
1544
|
+
if (await fs15.pathExists(agentsMdPath)) {
|
|
1545
|
+
await fs15.remove(agentsMdPath);
|
|
1297
1546
|
removedPaths.push("AGENTS.md");
|
|
1298
1547
|
}
|
|
1299
|
-
const sauronDir =
|
|
1300
|
-
if (await
|
|
1548
|
+
const sauronDir = path15.join(cwd, ".sauron");
|
|
1549
|
+
if (await fs15.pathExists(sauronDir)) {
|
|
1301
1550
|
if (purge) {
|
|
1302
|
-
await
|
|
1551
|
+
await fs15.remove(sauronDir);
|
|
1303
1552
|
removedPaths.push(".sauron/ (purgado por completo)");
|
|
1304
1553
|
} else {
|
|
1305
|
-
const manifestPath =
|
|
1306
|
-
if (await
|
|
1307
|
-
await
|
|
1554
|
+
const manifestPath = path15.join(sauronDir, ".manifest.json");
|
|
1555
|
+
if (await fs15.pathExists(manifestPath)) {
|
|
1556
|
+
await fs15.remove(manifestPath);
|
|
1308
1557
|
removedPaths.push(".sauron/.manifest.json");
|
|
1309
1558
|
}
|
|
1310
1559
|
removedPaths.push(".sauron/wiki/ (preservado como base est\xE1tica)");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sauron-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
"commander": "^15.0.0",
|
|
24
24
|
"diff": "^9.0.0",
|
|
25
25
|
"fs-extra": "^11.3.5",
|
|
26
|
+
"js-yaml": "^4.2.0",
|
|
26
27
|
"picocolors": "^1.1.1"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/diff": "^7.0.2",
|
|
30
31
|
"@types/fs-extra": "^11.0.4",
|
|
32
|
+
"@types/js-yaml": "^4.0.9",
|
|
31
33
|
"@types/node": "^25.9.2",
|
|
32
34
|
"tsup": "^8.5.1",
|
|
33
35
|
"typescript": "^6.0.3"
|