sauron-cli 1.2.0 → 1.4.0

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/index.js CHANGED
@@ -4,15 +4,15 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/features/init/init.command.ts
7
- import path3 from "path";
8
- import pc from "picocolors";
7
+ import fs10 from "fs-extra";
8
+ import path10 from "path";
9
+ import pc2 from "picocolors";
9
10
  import { fileURLToPath } from "url";
10
- import * as p from "@clack/prompts";
11
- import * as Diff from "diff";
11
+ import * as p2 from "@clack/prompts";
12
12
 
13
13
  // src/features/init/init.service.ts
14
- import fs2 from "fs-extra";
15
- import path2 from "path";
14
+ import fs8 from "fs-extra";
15
+ import path8 from "path";
16
16
 
17
17
  // src/core/manifest.service.ts
18
18
  import crypto from "crypto";
@@ -53,65 +53,395 @@ function checkConflict(localContent, newContent, manifestHash) {
53
53
 
54
54
  // src/features/init/templates.ts
55
55
  function generateAgentsMarkdown(aiTargets, severity, projectContext, projectStack) {
56
- return `# Diretrizes Globais de Agentes (Sauron CLI)
56
+ return `# Global Agent Guidelines (Sauron CLI)
57
57
 
58
- **Alvos:** ${aiTargets.join(", ")}
59
- **Severidade:** ${severity}
58
+ **Targets:** ${aiTargets.join(", ")}
59
+ **Severity:** ${severity}
60
60
 
61
- ## Contexto do Projeto
61
+ ## Project Context
62
62
  ${projectContext}
63
63
 
64
- ## Stack Tecnol\xF3gica
64
+ ## Tech Stack
65
65
  ${projectStack}
66
66
 
67
- ## Regra de Ouro (Write Obligation)
68
- Todos os agentes operando neste reposit\xF3rio est\xE3o estritamente obrigados a documentar qualquer altera\xE7\xE3o arquitetural, de regra de neg\xF3cio ou muta\xE7\xE3o de estado nas pastas \`.sauron\` e \`.agents\` correspondentes. A leitura passiva sem documenta\xE7\xE3o \xE9 considerada quebra de compliance.
67
+ ## Golden Rule (Write Obligation)
68
+ All agents operating in this repository are strictly OBLIGATED to document any architectural changes, business rules, or state mutations in the corresponding \`.sauron\` and \`.agents\` folders. Passive reading without documentation is considered a compliance violation.
69
69
 
70
- *Gerado automaticamente pelo Sauron CLI.*
70
+ *Automatically generated by Sauron CLI.*
71
71
  `;
72
72
  }
73
73
 
74
+ // src/core/registry/registry.service.ts
75
+ import fs2 from "fs-extra";
76
+ import path2 from "path";
77
+ import os from "os";
78
+ var RegistryService = class {
79
+ getRegistryPath() {
80
+ const homeDir = os.homedir();
81
+ return path2.join(homeDir, ".sauron", "registry.json");
82
+ }
83
+ async registerWorkspace(name, rootPath, aiTargets, severity) {
84
+ const registryPath = this.getRegistryPath();
85
+ await fs2.ensureDir(path2.dirname(registryPath));
86
+ let workspaces = [];
87
+ if (await fs2.pathExists(registryPath)) {
88
+ try {
89
+ workspaces = await fs2.readJson(registryPath);
90
+ } catch (err) {
91
+ workspaces = [];
92
+ }
93
+ }
94
+ const normalizedPath = path2.resolve(rootPath).replace(/\\/g, "/");
95
+ const existingIndex = workspaces.findIndex(
96
+ (w) => path2.resolve(w.rootPath).replace(/\\/g, "/") === normalizedPath
97
+ );
98
+ const now = (/* @__PURE__ */ new Date()).toISOString();
99
+ if (existingIndex >= 0) {
100
+ workspaces[existingIndex] = {
101
+ ...workspaces[existingIndex],
102
+ name,
103
+ aiTargets,
104
+ severity,
105
+ lastCheckedAt: now
106
+ };
107
+ } else {
108
+ workspaces.push({
109
+ name,
110
+ rootPath: normalizedPath,
111
+ initializedAt: now,
112
+ lastCheckedAt: now,
113
+ aiTargets,
114
+ severity
115
+ });
116
+ }
117
+ await fs2.writeJson(registryPath, workspaces, { spaces: 2 });
118
+ }
119
+ async unregisterWorkspace(rootPath) {
120
+ const registryPath = this.getRegistryPath();
121
+ if (!await fs2.pathExists(registryPath)) {
122
+ return;
123
+ }
124
+ let workspaces = [];
125
+ try {
126
+ workspaces = await fs2.readJson(registryPath);
127
+ } catch (err) {
128
+ return;
129
+ }
130
+ const normalizedPath = path2.resolve(rootPath).replace(/\\/g, "/");
131
+ const filtered = workspaces.filter(
132
+ (w) => path2.resolve(w.rootPath).replace(/\\/g, "/") !== normalizedPath
133
+ );
134
+ await fs2.writeJson(registryPath, filtered, { spaces: 2 });
135
+ }
136
+ async listWorkspaces() {
137
+ const registryPath = this.getRegistryPath();
138
+ if (!await fs2.pathExists(registryPath)) {
139
+ return [];
140
+ }
141
+ try {
142
+ return await fs2.readJson(registryPath);
143
+ } catch (err) {
144
+ return [];
145
+ }
146
+ }
147
+ };
148
+
149
+ // src/core/adapters/cursor.adapter.ts
150
+ import fs3 from "fs-extra";
151
+ import path3 from "path";
152
+ var CursorAdapter = class {
153
+ getName() {
154
+ return "Cursor";
155
+ }
156
+ async inject(cwd, rulesContent) {
157
+ const rulesDir = path3.join(cwd, ".cursor", "rules");
158
+ await fs3.ensureDir(rulesDir);
159
+ const mdcPath = path3.join(rulesDir, "sauron-memory.mdc");
160
+ const mdcContent = `---
161
+ description: Diretrizes de Mem\xF3ria e Write Obligation para evitar Amn\xE9sia de Contexto
162
+ globs: *
163
+ ---
164
+
165
+ # SAURON START
166
+ ${rulesContent}
167
+ # SAURON END
168
+ `;
169
+ await fs3.writeFile(mdcPath, mdcContent, "utf8");
170
+ return [".cursor/rules/sauron-memory.mdc"];
171
+ }
172
+ async clean(cwd) {
173
+ const mdcPath = path3.join(cwd, ".cursor", "rules", "sauron-memory.mdc");
174
+ if (await fs3.pathExists(mdcPath)) {
175
+ await fs3.remove(mdcPath);
176
+ return [".cursor/rules/sauron-memory.mdc"];
177
+ }
178
+ return [];
179
+ }
180
+ };
181
+
182
+ // src/core/adapters/windsurf.adapter.ts
183
+ import fs4 from "fs-extra";
184
+ import path4 from "path";
185
+ var WindsurfAdapter = class {
186
+ getName() {
187
+ return "Windsurf";
188
+ }
189
+ async inject(cwd, rulesContent) {
190
+ const rulesPath = path4.join(cwd, ".windsurfrules");
191
+ let localContent = "";
192
+ if (await fs4.pathExists(rulesPath)) {
193
+ localContent = await fs4.readFile(rulesPath, "utf8");
194
+ }
195
+ const cleanedContent = this.removeSauronBlock(localContent);
196
+ const sauronBlock = `
197
+ # SAURON START
198
+ ${rulesContent}
199
+ # SAURON END
200
+ `;
201
+ const finalContent = (cleanedContent.trim() + "\n" + sauronBlock).trim() + "\n";
202
+ await fs4.writeFile(rulesPath, finalContent, "utf8");
203
+ return [".windsurfrules"];
204
+ }
205
+ async clean(cwd) {
206
+ const rulesPath = path4.join(cwd, ".windsurfrules");
207
+ if (!await fs4.pathExists(rulesPath)) {
208
+ return [];
209
+ }
210
+ const localContent = await fs4.readFile(rulesPath, "utf8");
211
+ const cleanedContent = this.removeSauronBlock(localContent).trim();
212
+ if (cleanedContent === "") {
213
+ await fs4.remove(rulesPath);
214
+ } else {
215
+ await fs4.writeFile(rulesPath, cleanedContent + "\n", "utf8");
216
+ }
217
+ return [".windsurfrules"];
218
+ }
219
+ removeSauronBlock(content) {
220
+ const regex = /# SAURON START[\s\S]*?# SAURON END/g;
221
+ return content.replace(regex, "").trim();
222
+ }
223
+ };
224
+
225
+ // src/core/adapters/aider.adapter.ts
226
+ import fs5 from "fs-extra";
227
+ import path5 from "path";
228
+ var AiderAdapter = class {
229
+ getName() {
230
+ return "Aider";
231
+ }
232
+ async inject(cwd, rulesContent) {
233
+ const rulesPath = path5.join(cwd, ".aider.instructions.md");
234
+ const content = `# SAURON START
235
+ ${rulesContent}
236
+ # SAURON END
237
+ `;
238
+ await fs5.writeFile(rulesPath, content, "utf8");
239
+ return [".aider.instructions.md"];
240
+ }
241
+ async clean(cwd) {
242
+ const rulesPath = path5.join(cwd, ".aider.instructions.md");
243
+ if (await fs5.pathExists(rulesPath)) {
244
+ await fs5.remove(rulesPath);
245
+ return [".aider.instructions.md"];
246
+ }
247
+ return [];
248
+ }
249
+ };
250
+
251
+ // src/core/adapters/antigravity.adapter.ts
252
+ import fs6 from "fs-extra";
253
+ import path6 from "path";
254
+ var AntigravityAdapter = class {
255
+ getName() {
256
+ return "Antigravity";
257
+ }
258
+ async inject(cwd, rulesContent) {
259
+ const rulesPath = path6.join(cwd, ".agents", "rules", "memory.md");
260
+ await fs6.ensureDir(path6.dirname(rulesPath));
261
+ const content = `---
262
+ trigger: always_on
263
+ ---
264
+
265
+ # SAURON START
266
+ ${rulesContent}
267
+ # SAURON END
268
+ `;
269
+ await fs6.writeFile(rulesPath, content, "utf8");
270
+ return [".agents/rules/memory.md"];
271
+ }
272
+ async clean(cwd) {
273
+ const agentsDir = path6.join(cwd, ".agents");
274
+ if (await fs6.pathExists(agentsDir)) {
275
+ await fs6.remove(agentsDir);
276
+ return [".agents/rules/memory.md", ".agents/skills/wiki/SKILL.md"];
277
+ }
278
+ return [];
279
+ }
280
+ };
281
+
282
+ // src/core/adapters/adapter.factory.ts
283
+ var AdapterFactory = class {
284
+ static getAdapter(name) {
285
+ switch (name.trim().toLowerCase()) {
286
+ case "cursor":
287
+ return new CursorAdapter();
288
+ case "windsurf":
289
+ return new WindsurfAdapter();
290
+ case "aider":
291
+ return new AiderAdapter();
292
+ case "antigravity":
293
+ return new AntigravityAdapter();
294
+ default:
295
+ return null;
296
+ }
297
+ }
298
+ static getAllAdapters() {
299
+ return [
300
+ new CursorAdapter(),
301
+ new WindsurfAdapter(),
302
+ new AiderAdapter(),
303
+ new AntigravityAdapter()
304
+ ];
305
+ }
306
+ };
307
+
308
+ // src/core/wiki/wiki-bootstrapper.ts
309
+ import fs7 from "fs-extra";
310
+ import path7 from "path";
311
+ var WikiBootstrapper = class {
312
+ constructor(projectRoot) {
313
+ this.projectRoot = projectRoot;
314
+ this.wikiDir = path7.join(projectRoot, ".sauron", "wiki");
315
+ this.standardsDir = path7.join(this.wikiDir, "standards");
316
+ }
317
+ projectRoot;
318
+ standardsDir;
319
+ wikiDir;
320
+ async bootstrapFromTemplates(templatesDir, wikiTemplatesToInject) {
321
+ const injectedFiles = [];
322
+ const recipesSrcDir = path7.join(templatesDir, "wiki-recipes");
323
+ if (!await fs7.pathExists(recipesSrcDir)) {
324
+ return [];
325
+ }
326
+ await fs7.ensureDir(this.standardsDir);
327
+ const summaryPath = path7.join(this.wikiDir, "summary.json");
328
+ let summary = [];
329
+ if (await fs7.pathExists(summaryPath)) {
330
+ try {
331
+ summary = await fs7.readJson(summaryPath);
332
+ } catch (err) {
333
+ summary = [];
334
+ }
335
+ }
336
+ for (const templateName of wikiTemplatesToInject) {
337
+ const srcTemplatePath = path7.join(recipesSrcDir, templateName);
338
+ if (!await fs7.pathExists(srcTemplatePath)) {
339
+ continue;
340
+ }
341
+ const destFilename = templateName;
342
+ const destFullPath = path7.join(this.standardsDir, destFilename);
343
+ const relWikiPath = `standards/${destFilename}`;
344
+ const content = await fs7.readFile(srcTemplatePath, "utf8");
345
+ const hash = generateHash(content);
346
+ const len = Buffer.byteLength(content, "utf8");
347
+ const exists = await fs7.pathExists(destFullPath);
348
+ if (!exists) {
349
+ await fs7.writeFile(destFullPath, content, "utf8");
350
+ injectedFiles.push(path7.join(".sauron", "wiki", relWikiPath).replace(/\\/g, "/"));
351
+ }
352
+ const slug = destFilename.replace(".rules.md", "").replace(".rules.txt", "");
353
+ const docName = this.inferDocName(slug);
354
+ const kbId = `kb-standards-${slug}`;
355
+ const existingIndex = summary.findIndex((item) => item.path === relWikiPath);
356
+ const newItem = {
357
+ type: "file",
358
+ name: docName,
359
+ slug: `${slug}-rules`,
360
+ path: relWikiPath,
361
+ id: kbId,
362
+ domainId: "domain-standards",
363
+ orgId: "org-sauron-cli",
364
+ contentLength: len,
365
+ contentHash: hash
366
+ };
367
+ if (existingIndex >= 0) {
368
+ summary[existingIndex] = {
369
+ ...summary[existingIndex],
370
+ contentLength: len,
371
+ contentHash: hash
372
+ };
373
+ } else {
374
+ summary.push(newItem);
375
+ }
376
+ }
377
+ await fs7.writeJson(summaryPath, summary, { spaces: 2 });
378
+ return injectedFiles;
379
+ }
380
+ inferDocName(slug) {
381
+ switch (slug.toLowerCase()) {
382
+ case "typescript":
383
+ return "TypeScript Guidelines";
384
+ case "nextjs":
385
+ return "Next.js App Router Rules";
386
+ case "react":
387
+ return "React Components & Hooks";
388
+ case "postgresql":
389
+ return "PostgreSQL SQL Standards";
390
+ case "tailwindcss":
391
+ return "Tailwind CSS Styles Manual";
392
+ default:
393
+ return `${slug.charAt(0).toUpperCase() + slug.slice(1)} Standards`;
394
+ }
395
+ }
396
+ };
397
+
74
398
  // src/features/init/init.service.ts
75
399
  var InitService = class {
76
- async execute(options, onConflict) {
400
+ registryService = new RegistryService();
401
+ async execute(options, driver) {
77
402
  const { cwd, templatesDir } = options;
78
403
  const manifest = await getManifest(cwd) || { version: "1.0.0", files: {} };
79
- async function processDirectory(source, target) {
80
- if (!await fs2.pathExists(source)) return;
81
- const files = await fs2.readdir(source);
404
+ const modifiedFiles = [];
405
+ const processDirectory = async (source, target) => {
406
+ if (!await fs8.pathExists(source)) return;
407
+ const files = await fs8.readdir(source);
82
408
  for (const file of files) {
83
- const sourcePath = path2.join(source, file);
84
- const targetPath = path2.join(target, file);
85
- const stat = await fs2.stat(sourcePath);
409
+ const sourcePath = path8.join(source, file);
410
+ const targetPath = path8.join(target, file);
411
+ const stat = await fs8.stat(sourcePath);
86
412
  if (stat.isDirectory()) {
87
- await fs2.ensureDir(targetPath);
413
+ await fs8.ensureDir(targetPath);
88
414
  await processDirectory(sourcePath, targetPath);
89
415
  } else {
90
- const content = await fs2.readFile(sourcePath, "utf8");
91
- const relPath = path2.relative(cwd, targetPath).replace(/\\/g, "/");
416
+ const content = await fs8.readFile(sourcePath, "utf8");
417
+ const relPath = path8.relative(cwd, targetPath).replace(/\\/g, "/");
92
418
  let shouldWrite = true;
93
- if (await fs2.pathExists(targetPath)) {
94
- const localContent = await fs2.readFile(targetPath, "utf8");
419
+ if (await fs8.pathExists(targetPath)) {
420
+ const localContent = await fs8.readFile(targetPath, "utf8");
95
421
  const hasConflict = checkConflict(localContent, content, manifest.files[relPath]);
96
422
  if (hasConflict) {
97
- const decision = await onConflict(relPath, localContent, content);
423
+ const decision = await driver.resolveConflict(relPath, localContent, content);
98
424
  if (decision === "ours") {
99
425
  shouldWrite = false;
100
426
  }
101
427
  }
102
428
  } else {
103
- await fs2.ensureDir(path2.dirname(targetPath));
429
+ await fs8.ensureDir(path8.dirname(targetPath));
104
430
  }
105
431
  if (shouldWrite) {
106
- await fs2.writeFile(targetPath, content, "utf8");
432
+ await fs8.writeFile(targetPath, content, "utf8");
433
+ modifiedFiles.push(relPath);
434
+ }
435
+ const isMutable = relPath.startsWith(".sauron/wiki/") || relPath === ".agents/rules/memory.md";
436
+ if (!isMutable) {
437
+ manifest.files[relPath] = generateHash(content);
107
438
  }
108
- manifest.files[relPath] = generateHash(content);
109
439
  }
110
440
  }
111
- }
112
- await processDirectory(path2.join(templatesDir, ".sauron"), path2.join(cwd, ".sauron"));
113
- await processDirectory(path2.join(templatesDir, ".agents"), path2.join(cwd, ".agents"));
114
- const agentsMdPath = path2.join(cwd, "AGENTS.md");
441
+ };
442
+ await processDirectory(path8.join(templatesDir, ".sauron"), path8.join(cwd, ".sauron"));
443
+ await processDirectory(path8.join(templatesDir, ".agents"), path8.join(cwd, ".agents"));
444
+ const agentsMdPath = path8.join(cwd, "AGENTS.md");
115
445
  const agentsMdContent = generateAgentsMarkdown(
116
446
  options.aiTargets,
117
447
  options.severity,
@@ -119,169 +449,878 @@ var InitService = class {
119
449
  options.projectStack
120
450
  );
121
451
  let shouldWriteAgents = true;
122
- if (await fs2.pathExists(agentsMdPath)) {
123
- const localAgents = await fs2.readFile(agentsMdPath, "utf8");
452
+ if (await fs8.pathExists(agentsMdPath)) {
453
+ const localAgents = await fs8.readFile(agentsMdPath, "utf8");
124
454
  const hasConflict = checkConflict(localAgents, agentsMdContent, manifest.files["AGENTS.md"]);
125
455
  if (hasConflict) {
126
- const decision = await onConflict("AGENTS.md", localAgents, agentsMdContent);
456
+ const decision = await driver.resolveConflict("AGENTS.md", localAgents, agentsMdContent);
127
457
  if (decision === "ours") {
128
458
  shouldWriteAgents = false;
129
459
  }
130
460
  }
131
461
  }
132
462
  if (shouldWriteAgents) {
133
- await fs2.writeFile(agentsMdPath, agentsMdContent, "utf8");
463
+ await fs8.writeFile(agentsMdPath, agentsMdContent, "utf8");
464
+ modifiedFiles.push("AGENTS.md");
134
465
  }
135
466
  manifest.files["AGENTS.md"] = generateHash(agentsMdContent);
136
467
  await saveManifest(cwd, manifest);
468
+ modifiedFiles.push(".sauron/.manifest.json");
469
+ const memoryFilePath = path8.join(cwd, ".agents", "rules", "memory.md");
470
+ let memoryRulesContent = "";
471
+ if (await fs8.pathExists(memoryFilePath)) {
472
+ memoryRulesContent = await fs8.readFile(memoryFilePath, "utf8");
473
+ } else {
474
+ memoryRulesContent = agentsMdContent;
475
+ }
476
+ for (const target of options.aiTargets) {
477
+ const adapter = AdapterFactory.getAdapter(target);
478
+ if (adapter) {
479
+ const paths = await adapter.inject(cwd, memoryRulesContent);
480
+ modifiedFiles.push(...paths);
481
+ }
482
+ }
483
+ const bootstrapper = new WikiBootstrapper(cwd);
484
+ const injectedWikiFiles = await bootstrapper.bootstrapFromTemplates(
485
+ templatesDir,
486
+ options.wikiTemplatesToInject
487
+ );
488
+ modifiedFiles.push(...injectedWikiFiles);
489
+ const projectName = path8.basename(cwd) || "Unnamed Project";
490
+ await this.registryService.registerWorkspace(
491
+ projectName,
492
+ cwd,
493
+ options.aiTargets,
494
+ options.severity
495
+ );
496
+ return modifiedFiles;
497
+ }
498
+ };
499
+
500
+ // src/domain/session/session-context.ts
501
+ var SessionContext = class {
502
+ json;
503
+ interactive;
504
+ conflictResolution;
505
+ yes;
506
+ constructor(options) {
507
+ this.json = options.json;
508
+ this.yes = options.yes || false;
509
+ this.interactive = options.json ? false : options.interactive ?? true;
510
+ this.conflictResolution = options.conflictResolution;
511
+ }
512
+ };
513
+
514
+ // src/presentation/drivers/terminal.driver.ts
515
+ import * as p from "@clack/prompts";
516
+ import pc from "picocolors";
517
+ import * as Diff from "diff";
518
+ var TerminalDriver = class {
519
+ constructor(context) {
520
+ this.context = context;
521
+ }
522
+ context;
523
+ spinnerInstance = p.spinner();
524
+ logInfo(msg) {
525
+ console.log(pc.blue("\u2139 ") + pc.white(msg));
526
+ }
527
+ logWarning(msg) {
528
+ console.log(pc.yellow("\u26A0\uFE0F ") + pc.yellow(msg));
529
+ }
530
+ logSuccess(msg) {
531
+ console.log(pc.green("\u2714 ") + pc.green(msg));
532
+ }
533
+ logError(msg) {
534
+ console.log(pc.red("\u2716 ") + pc.red(msg));
535
+ }
536
+ startSpinner(msg) {
537
+ this.spinnerInstance.start(msg);
538
+ }
539
+ stopSpinner(msg, success = true) {
540
+ if (success) {
541
+ this.spinnerInstance.stop(pc.green("\u2714 ") + msg);
542
+ } else {
543
+ this.spinnerInstance.stop(pc.red("\u2716 ") + msg);
544
+ }
545
+ }
546
+ async resolveConflict(filePath, localContent, newContent) {
547
+ this.stopSpinner(`Conflito em ${filePath}`, false);
548
+ if (!this.context.interactive) {
549
+ if (this.context.conflictResolution) {
550
+ this.logInfo(
551
+ `Conflito em ${filePath} resolvido automaticamente como '${this.context.conflictResolution}' em execu\xE7\xE3o n\xE3o-interativa.`
552
+ );
553
+ this.startSpinner("Continuando processamento...");
554
+ return this.context.conflictResolution;
555
+ }
556
+ this.logError(
557
+ `Conflito detectado em ${filePath} em execu\xE7\xE3o n\xE3o-interativa sem flag de resolu\xE7\xE3o configurada (--conflict).`
558
+ );
559
+ this.finish({
560
+ success: false,
561
+ message: `Conflito n\xE3o resolvido no arquivo ${filePath}. Defina --conflict para automatizar a resolu\xE7\xE3o.`
562
+ });
563
+ process.exit(1);
564
+ }
565
+ p.note(
566
+ `Foi detectada uma muta\xE7\xE3o no arquivo: ${pc.cyan(filePath)}
567
+ O seu agente de IA ou voc\xEA modificou este arquivo desde a \xFAltima instala\xE7\xE3o.`,
568
+ "\u26A0\uFE0F CONFLITO DETECTADO"
569
+ );
570
+ let resolved = null;
571
+ while (!resolved) {
572
+ const action = await p.select({
573
+ message: `Como deseja proceder com ${pc.cyan(filePath)}?`,
574
+ options: [
575
+ { value: "ours", label: "Preservar Local (Ours)", hint: "Mant\xE9m sua vers\xE3o e ignora o novo template" },
576
+ { value: "theirs", label: "Sobrescrever (Theirs)", hint: "Destr\xF3i a sua vers\xE3o e aplica o template novo" },
577
+ { value: "diff", label: "Auditar Diferen\xE7as (Diff Mode)", hint: "Mostra o que vai mudar visualmente" }
578
+ ]
579
+ });
580
+ if (p.isCancel(action)) {
581
+ p.cancel("Opera\xE7\xE3o cancelada pelo usu\xE1rio durante a resolu\xE7\xE3o de conflito.");
582
+ process.exit(0);
583
+ }
584
+ if (action === "diff") {
585
+ const diffStr = this.renderDiff(localContent, newContent, filePath);
586
+ console.log("\n" + diffStr + "\n");
587
+ } else {
588
+ resolved = action;
589
+ }
590
+ }
591
+ this.startSpinner("Continuando processamento...");
592
+ return resolved;
593
+ }
594
+ finish(data) {
595
+ if (data.success) {
596
+ p.outro(pc.green(pc.bold(data.message || "Opera\xE7\xE3o finalizada com sucesso!")));
597
+ } else {
598
+ p.cancel(pc.red(pc.bold(data.message || "Opera\xE7\xE3o falhou.")));
599
+ process.exit(1);
600
+ }
601
+ }
602
+ renderDiff(oldStr, newStr, fileName) {
603
+ const diffs = Diff.diffLines(oldStr, newStr);
604
+ let output = pc.bold(`--- a/${fileName} (Local)
605
+ +++ b/${fileName} (Novo Template)
606
+ `);
607
+ diffs.forEach((part) => {
608
+ const lines = part.value.replace(/\n$/, "").split("\n");
609
+ for (const line of lines) {
610
+ if (part.added) {
611
+ output += pc.green(`+ ${line}
612
+ `);
613
+ } else if (part.removed) {
614
+ output += pc.red(`- ${line}
615
+ `);
616
+ } else {
617
+ output += pc.dim(` ${line}
618
+ `);
619
+ }
620
+ }
621
+ });
622
+ return output;
623
+ }
624
+ };
625
+
626
+ // src/presentation/drivers/json.driver.ts
627
+ var JsonDriver = class {
628
+ constructor(context) {
629
+ this.context = context;
630
+ }
631
+ context;
632
+ logs = [];
633
+ payload = null;
634
+ logInfo(msg) {
635
+ this.addLog("info", msg);
636
+ }
637
+ logWarning(msg) {
638
+ this.addLog("warning", msg);
639
+ }
640
+ logSuccess(msg) {
641
+ this.addLog("success", msg);
642
+ }
643
+ logError(msg) {
644
+ this.addLog("error", msg);
645
+ }
646
+ startSpinner(msg) {
647
+ this.addLog("info", `Come\xE7ou: ${msg}`);
648
+ }
649
+ stopSpinner(msg, success = true) {
650
+ this.addLog(success ? "success" : "error", `Terminou: ${msg}`);
651
+ }
652
+ async resolveConflict(filePath, localContent, newContent) {
653
+ this.addLog("warning", `Conflito detectado no arquivo: ${filePath}`);
654
+ if (this.context.conflictResolution) {
655
+ this.addLog(
656
+ "info",
657
+ `Conflito em ${filePath} resolvido automaticamente como '${this.context.conflictResolution}' via configura\xE7\xE3o de sess\xE3o.`
658
+ );
659
+ return this.context.conflictResolution;
660
+ }
661
+ this.addLog(
662
+ "error",
663
+ `Bloqueio de execu\xE7\xE3o: Conflito detectado em ${filePath} em execu\xE7\xE3o program\xE1tica/n\xE3o-interativa sem flag de resolu\xE7\xE3o configurada (--conflict).`
664
+ );
665
+ this.finish({
666
+ success: false,
667
+ message: `Conflito n\xE3o resolvido no arquivo ${filePath}. Defina --conflict para automatizar a resolu\xE7\xE3o.`
668
+ });
669
+ process.exit(1);
670
+ }
671
+ finish(data) {
672
+ const output = {
673
+ success: data.success,
674
+ message: data.message || (data.success ? "Opera\xE7\xE3o conclu\xEDda" : "Opera\xE7\xE3o falhou"),
675
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
676
+ logs: this.logs,
677
+ payload: data.payload || this.payload
678
+ };
679
+ console.log(JSON.stringify(output, null, 2));
680
+ if (!data.success) {
681
+ process.exit(1);
682
+ }
683
+ }
684
+ addLog(level, message) {
685
+ this.logs.push({
686
+ level,
687
+ message,
688
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
689
+ });
690
+ }
691
+ };
692
+
693
+ // src/presentation/router.ts
694
+ var PresentationRouter = class {
695
+ static createDriver(context) {
696
+ if (context.json) {
697
+ return new JsonDriver(context);
698
+ }
699
+ return new TerminalDriver(context);
700
+ }
701
+ };
702
+
703
+ // src/core/scanner/project-scanner.ts
704
+ import fs9 from "fs-extra";
705
+ import path9 from "path";
706
+
707
+ // src/core/scanner/signatures.ts
708
+ var TECHNOLOGY_SIGNATURES = [
709
+ {
710
+ name: "TypeScript",
711
+ deps: ["typescript", "@types/node"],
712
+ files: ["tsconfig.json"],
713
+ wikiTemplate: "typescript.rules.md"
714
+ },
715
+ {
716
+ name: "Next.js",
717
+ deps: ["next"],
718
+ files: ["next.config.js", "next.config.mjs"],
719
+ wikiTemplate: "nextjs.rules.md"
720
+ },
721
+ {
722
+ name: "React",
723
+ deps: ["react", "react-dom"],
724
+ wikiTemplate: "react.rules.md"
725
+ },
726
+ {
727
+ name: "NestJS",
728
+ deps: ["@nestjs/core", "@nestjs/common"],
729
+ files: ["nest-cli.json"]
730
+ },
731
+ {
732
+ name: "Express",
733
+ deps: ["express", "@types/express"]
734
+ },
735
+ {
736
+ name: "Prisma ORM",
737
+ deps: ["prisma", "@prisma/client"],
738
+ files: ["prisma/schema.prisma"]
739
+ },
740
+ {
741
+ name: "PostgreSQL",
742
+ deps: ["pg"],
743
+ regex: {
744
+ file: "compose.yml",
745
+ pattern: /image:\s*postgres/i
746
+ },
747
+ wikiTemplate: "postgresql.rules.md"
748
+ },
749
+ {
750
+ name: "PostgreSQL (Docker-Compose)",
751
+ regex: {
752
+ file: "docker-compose.yml",
753
+ pattern: /image:\s*postgres/i
754
+ },
755
+ wikiTemplate: "postgresql.rules.md"
756
+ },
757
+ {
758
+ name: "Tailwind CSS",
759
+ deps: ["tailwindcss"],
760
+ files: ["tailwind.config.js", "tailwind.config.ts", "postcss.config.js"],
761
+ wikiTemplate: "tailwindcss.rules.md"
762
+ },
763
+ {
764
+ name: "Redis",
765
+ regex: {
766
+ file: "compose.yml",
767
+ pattern: /image:\s*redis/i
768
+ }
769
+ },
770
+ {
771
+ name: "Redis (Docker-Compose)",
772
+ regex: {
773
+ file: "docker-compose.yml",
774
+ pattern: /image:\s*redis/i
775
+ }
776
+ }
777
+ ];
778
+
779
+ // src/core/scanner/project-scanner.ts
780
+ var ProjectScanner = class {
781
+ cwd;
782
+ constructor(cwd = process.cwd()) {
783
+ this.cwd = cwd;
784
+ }
785
+ async scan() {
786
+ const context = {
787
+ primaryLanguage: "JavaScript",
788
+ // Fallback padrão
789
+ frameworks: [],
790
+ database: [],
791
+ styling: [],
792
+ packageManager: "npm",
793
+ isMonorepo: false,
794
+ detectedIAs: [],
795
+ wikiTemplatesToInject: []
796
+ };
797
+ try {
798
+ const rootEntries = await fs9.readdir(this.cwd, { withFileTypes: true });
799
+ const rootFiles = rootEntries.filter((e) => e.isFile()).map((e) => e.name);
800
+ const rootDirs = rootEntries.filter((e) => e.isDirectory()).map((e) => e.name);
801
+ if (rootFiles.includes("pnpm-lock.yaml") || rootFiles.includes("pnpm-workspace.yaml")) {
802
+ context.packageManager = "pnpm";
803
+ if (rootFiles.includes("pnpm-workspace.yaml")) {
804
+ context.isMonorepo = true;
805
+ }
806
+ } else if (rootFiles.includes("yarn.lock")) {
807
+ context.packageManager = "yarn";
808
+ } else if (rootFiles.includes("package-lock.json")) {
809
+ context.packageManager = "npm";
810
+ }
811
+ if (rootFiles.includes("tsconfig.json")) {
812
+ context.primaryLanguage = "TypeScript";
813
+ }
814
+ if (rootDirs.includes(".cursor") || rootFiles.includes(".cursorrules")) {
815
+ context.detectedIAs.push("Cursor");
816
+ }
817
+ if (rootFiles.includes(".windsurfrules")) {
818
+ context.detectedIAs.push("Windsurf");
819
+ }
820
+ if (rootFiles.includes(".aider.instructions.md") || rootFiles.includes(".aider.conf.yml")) {
821
+ context.detectedIAs.push("Aider");
822
+ }
823
+ if (rootDirs.includes(".agents")) {
824
+ context.detectedIAs.push("Antigravity");
825
+ }
826
+ if (context.detectedIAs.length === 0) {
827
+ context.detectedIAs = ["Cursor", "Windsurf", "Aider", "Antigravity"];
828
+ }
829
+ if (rootFiles.includes("package.json")) {
830
+ const pkgPath = path9.join(this.cwd, "package.json");
831
+ const pkg = await fs9.readJson(pkgPath);
832
+ const allDeps = {
833
+ ...pkg.dependencies || {},
834
+ ...pkg.devDependencies || {}
835
+ };
836
+ for (const sig of TECHNOLOGY_SIGNATURES) {
837
+ let matched = false;
838
+ if (sig.deps && sig.deps.some((dep) => allDeps[dep])) {
839
+ matched = true;
840
+ }
841
+ if (sig.files && sig.files.some((f) => rootFiles.includes(f) || rootDirs.includes(f))) {
842
+ matched = true;
843
+ }
844
+ if (matched) {
845
+ this.categorizeAndAdd(sig, context);
846
+ }
847
+ }
848
+ }
849
+ const composeFiles = ["compose.yml", "docker-compose.yml"];
850
+ for (const composeFile of composeFiles) {
851
+ if (rootFiles.includes(composeFile)) {
852
+ const composePath = path9.join(this.cwd, composeFile);
853
+ const composeContent = await fs9.readFile(composePath, "utf8");
854
+ for (const sig of TECHNOLOGY_SIGNATURES) {
855
+ if (sig.regex && sig.regex.file === composeFile) {
856
+ if (sig.regex.pattern.test(composeContent)) {
857
+ this.categorizeAndAdd(sig, context);
858
+ }
859
+ }
860
+ }
861
+ }
862
+ }
863
+ } catch (error) {
864
+ console.warn("O Scanner de Arquitetura do Sauron encontrou um atrito de I/O n\xE3o-fatal: ", error);
865
+ }
866
+ return context;
867
+ }
868
+ categorizeAndAdd(sig, context) {
869
+ const name = sig.name;
870
+ if (name.toLowerCase().includes("tailwind") || name.toLowerCase().includes("styled")) {
871
+ if (!context.styling.includes(name)) context.styling.push(name);
872
+ } else if (name.toLowerCase().includes("postgres") || name.toLowerCase().includes("prisma") || name.toLowerCase().includes("redis") || name.toLowerCase().includes("mongo")) {
873
+ if (!context.database.includes(name)) context.database.push(name);
874
+ } else {
875
+ if (name !== "TypeScript" && !context.frameworks.includes(name)) {
876
+ context.frameworks.push(name);
877
+ }
878
+ }
879
+ if (sig.wikiTemplate && !context.wikiTemplatesToInject.includes(sig.wikiTemplate)) {
880
+ context.wikiTemplatesToInject.push(sig.wikiTemplate);
881
+ }
137
882
  }
138
883
  };
139
884
 
140
885
  // src/features/init/init.command.ts
141
886
  var __filename = fileURLToPath(import.meta.url);
142
- var __dirname = path3.dirname(__filename);
887
+ var __dirname = path10.dirname(__filename);
143
888
  async function runInitCommand(options) {
144
889
  const cwd = process.cwd();
145
- const logo = `
890
+ const session = new SessionContext({
891
+ json: !!options.json,
892
+ interactive: !options.yes && !options.json,
893
+ conflictResolution: options.conflict,
894
+ yes: options.yes
895
+ });
896
+ const driver = PresentationRouter.createDriver(session);
897
+ const scanner = new ProjectScanner(cwd);
898
+ let scanSpinner = null;
899
+ if (!session.json) {
900
+ scanSpinner = p2.spinner();
901
+ scanSpinner.start("O Olho de Sauron est\xE1 varrendo o reposit\xF3rio em busca da stack tecnol\xF3gica e depend\xEAncias...");
902
+ }
903
+ const scannedContext = await scanner.scan();
904
+ if (session.interactive) {
905
+ await new Promise((resolve) => setTimeout(resolve, 800));
906
+ }
907
+ if (scanSpinner) {
908
+ scanSpinner.stop("Mapeamento neural conclu\xEDdo com sucesso. Contexto do reposit\xF3rio foi inferido.");
909
+ }
910
+ if (!session.json) {
911
+ const logo = `
146
912
  \x1B[38;2;255;106;0m \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\x1B[0m
147
913
  \x1B[38;2;255;179;0m\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\x1B[0m
148
914
  \x1B[38;2;255;215;0m\u255A\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\x1B[0m
149
- \x1B[38;2;255;140;0m \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\x1B[0m
915
+ \x1B[38;2;255;140;0m \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588
150
916
  \x1B[38;2;204;51;0m\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\x1B[0m
151
917
  `;
152
- console.log(pc.bold(logo));
153
- console.log(pc.dim(" CLI de Idempot\xEAncia e Mem\xF3ria para IAs\n"));
154
- p.intro(pc.bgRed(pc.white(" Sauron Memory System - Inicializa\xE7\xE3o ")));
155
- let aiTargets = ["Cursor", "Windsurf", "Aider", "Antigravity"];
918
+ console.log(pc2.bold(logo));
919
+ console.log(pc2.dim(" CLI de Idempot\xEAncia e Mem\xF3ria para IAs\n"));
920
+ p2.intro(pc2.bgRed(pc2.white(" Sauron Memory System - Inicializa\xE7\xE3o ")));
921
+ }
922
+ let aiTargets = scannedContext.detectedIAs;
156
923
  let severity = "Observacional";
157
924
  let projectContext = "Projeto Gen\xE9rico";
158
- let projectStack = "Node.js, TypeScript";
159
- if (!options.yes) {
160
- const config = await p.group(
161
- {
162
- targets: () => p.multiselect({
163
- message: "Qual(is) IA(s) voc\xEA usar\xE1 neste projeto?",
164
- options: [
165
- { value: "Cursor", label: "Cursor", hint: "Recomendado" },
166
- { value: "Windsurf", label: "Windsurf" },
167
- { value: "Aider", label: "Aider" },
168
- { value: "Antigravity", label: "Antigravity", hint: "Agente nativo" }
169
- ],
170
- required: false
171
- }),
172
- severity: () => p.select({
173
- message: "Qual o n\xEDvel de severidade das regras da IA?",
174
- options: [
175
- { value: "Observacional", label: "Observacional (A IA documenta quando julgar necess\xE1rio)" },
176
- { value: "Estrito", label: "Estrito (Bloqueia altera\xE7\xF5es sem documenta\xE7\xE3o expl\xEDcita)" }
177
- ]
178
- }),
179
- context: () => p.text({
180
- message: "Descreva brevemente o contexto do seu projeto:",
181
- placeholder: "Ex: App de agendamento de pilates",
182
- defaultValue: "Projeto Gen\xE9rico"
183
- }),
184
- stack: () => p.text({
185
- message: "Qual a stack tecnol\xF3gica principal do seu projeto?",
186
- placeholder: "Ex: Next.js 15, Tailwind, Firebase",
187
- defaultValue: "Node.js, TypeScript"
188
- })
189
- },
190
- {
191
- onCancel: () => {
192
- p.cancel("Inicializa\xE7\xE3o abortada.");
193
- process.exit(0);
925
+ const inferredStackString = [
926
+ scannedContext.primaryLanguage,
927
+ ...scannedContext.frameworks,
928
+ ...scannedContext.database,
929
+ ...scannedContext.styling
930
+ ].filter(Boolean).join(", ");
931
+ let projectStack = inferredStackString || "Node.js, TypeScript";
932
+ if (session.interactive) {
933
+ try {
934
+ const config = await p2.group(
935
+ {
936
+ targets: () => p2.multiselect({
937
+ message: "Confirmar a(s) IA(s) operativas que consumir\xE3o ativamente o contexto estruturado da Wiki:",
938
+ options: [
939
+ { value: "Cursor", label: "Cursor", hint: "Recomendado" },
940
+ { value: "Windsurf", label: "Windsurf" },
941
+ { value: "Aider", label: "Aider" },
942
+ { value: "Antigravity", label: "Antigravity", hint: "Agente nativo" }
943
+ ],
944
+ initialValues: aiTargets,
945
+ required: false
946
+ }),
947
+ severity: () => p2.select({
948
+ message: "Defina a severidade rigorosa de implementa\xE7\xE3o das regras contextuais da IA:",
949
+ options: [
950
+ { value: "Observacional", label: "Observacional (As regras servem apenas de sugest\xF5es org\xE2nicas)" },
951
+ { value: "Estrito", label: "Estrito (Enforcement ativo e recusa de l\xF3gicas em desacordo)" }
952
+ ],
953
+ initialValue: severity
954
+ }),
955
+ context: () => p2.text({
956
+ message: "Descreva brevemente o contexto sist\xEAmico do projeto (ou ratifique o contexto preditivo):",
957
+ placeholder: "Ex: App de agendamento de pilates",
958
+ initialValue: projectContext
959
+ }),
960
+ stack: () => p2.text({
961
+ message: "Qual a stack tecnol\xF3gica mapeada em definitivo para este projeto?",
962
+ placeholder: "Ex: Next.js 15, Tailwind, Firebase",
963
+ initialValue: projectStack
964
+ })
965
+ },
966
+ {
967
+ onCancel: () => {
968
+ p2.cancel("Inicializa\xE7\xE3o abortada.");
969
+ process.exit(0);
970
+ }
194
971
  }
195
- }
196
- );
197
- aiTargets = config.targets;
198
- severity = config.severity;
199
- projectContext = config.context;
200
- projectStack = config.stack;
201
- }
202
- const s = p.spinner();
203
- s.start("Injetando o C\xE9rebro da IA no reposit\xF3rio...");
204
- const packageRoot = path3.join(__dirname, "..", "..", "..");
205
- const templatesDir = path3.join(packageRoot, "templates");
972
+ );
973
+ aiTargets = config.targets;
974
+ severity = config.severity;
975
+ projectContext = config.context;
976
+ projectStack = config.stack;
977
+ } catch (error) {
978
+ driver.finish({ success: false, message: `Erro ao interagir com console: ${error.message}` });
979
+ return;
980
+ }
981
+ }
982
+ driver.startSpinner("Injetando o C\xE9rebro da IA no reposit\xF3rio...");
983
+ let templatesDir = path10.join(__dirname, "..", "templates");
984
+ if (!fs10.existsSync(templatesDir)) {
985
+ templatesDir = path10.join(__dirname, "..", "..", "..", "templates");
986
+ }
206
987
  const initService = new InitService();
207
988
  try {
208
- await initService.execute(
989
+ const modifiedFiles = await initService.execute(
209
990
  {
210
991
  aiTargets,
211
992
  severity,
212
993
  projectContext,
213
994
  projectStack,
214
995
  cwd,
215
- templatesDir
996
+ templatesDir,
997
+ wikiTemplatesToInject: scannedContext.wikiTemplatesToInject
216
998
  },
217
- async (filePath, localContent, newContent) => {
218
- s.stop(`Conflito em ${filePath}`);
219
- const decision = await showConflictUI(filePath, localContent, newContent);
220
- s.start("Continuando inje\xE7\xE3o...");
221
- return decision;
222
- }
223
- );
224
- s.stop("Inje\xE7\xE3o finalizada.");
225
- p.outro(
226
- pc.green(pc.bold("Sauron Memory System instalado com sucesso!\n\n")) + pc.white("O C\xE9rebro da IA foi injetado e protegido pelo motor de integridade.\n") + pc.cyan("A\xE7\xF5es Recomendadas:\n") + pc.dim("Copie o comando abaixo e envie para a sua IA testar a nova arquitetura:\n") + pc.yellow('"Analise a estrutura .sauron/ e .agents/ rec\xE9m injetada e sugira quais regras cr\xEDticas eu devo documentar agora para nosso projeto."')
999
+ driver
227
1000
  );
1001
+ driver.stopSpinner("Inje\xE7\xE3o finalizada.", true);
1002
+ const message = 'Sauron Memory System instalado com sucesso!\n\nO C\xE9rebro da IA foi injetado e protegido pelo motor de integridade.\nA\xE7\xF5es Recomendadas:\nCopie o comando abaixo e envie para a sua IA testar a nova arquitetura:\n"Analise a estrutura .sauron/ e .agents/ rec\xE9m injetada e sugira quais regras cr\xEDticas eu devo documentar agora para nosso projeto."';
1003
+ driver.finish({
1004
+ success: true,
1005
+ message,
1006
+ payload: {
1007
+ projectName: path10.basename(cwd),
1008
+ cwd,
1009
+ aiTargets,
1010
+ severity,
1011
+ modifiedFiles
1012
+ }
1013
+ });
228
1014
  } catch (error) {
229
- s.stop("Falha na instala\xE7\xE3o.");
230
- p.cancel(error.message);
231
- process.exit(1);
1015
+ driver.stopSpinner("Falha na instala\xE7\xE3o.", false);
1016
+ driver.finish({ success: false, message: error.message });
232
1017
  }
233
1018
  }
234
- async function showConflictUI(filePath, localContent, newContent) {
235
- p.note(`Foi detectada uma muta\xE7\xE3o no arquivo: ${pc.cyan(filePath)}
236
- O seu agente de IA ou voc\xEA modificou este arquivo desde a \xFAltima instala\xE7\xE3o.`, "\u26A0\uFE0F CONFLITO DETECTADO");
237
- let resolved = null;
238
- while (!resolved) {
239
- const action = await p.select({
240
- message: `Como deseja proceder com ${pc.cyan(filePath)}?`,
241
- options: [
242
- { value: "ours", label: "Preservar Local (Ours)", hint: "Mant\xE9m sua vers\xE3o e ignora o novo template" },
243
- { value: "theirs", label: "Sobrescrever (Theirs)", hint: "Destr\xF3i a sua vers\xE3o e aplica o template novo" },
244
- { value: "diff", label: "Auditar Diferen\xE7as (Diff Mode)", hint: "Mostra o que vai mudar visualmente" }
245
- ]
246
- });
247
- if (p.isCancel(action)) {
248
- p.cancel("Opera\xE7\xE3o cancelada pelo usu\xE1rio durante a resolu\xE7\xE3o de conflito.");
249
- process.exit(0);
1019
+
1020
+ // src/features/doctor/doctor.command.ts
1021
+ import pc3 from "picocolors";
1022
+ import * as p3 from "@clack/prompts";
1023
+
1024
+ // src/features/doctor/doctor.service.ts
1025
+ import fs11 from "fs-extra";
1026
+ import path11 from "path";
1027
+ var DoctorService = class {
1028
+ registryService = new RegistryService();
1029
+ async execute(cwd) {
1030
+ const issues = [];
1031
+ const workspaces = await this.registryService.listWorkspaces();
1032
+ const normalizedCwd = path11.resolve(cwd).replace(/\\/g, "/");
1033
+ const registered = workspaces.find(
1034
+ (w) => path11.resolve(w.rootPath).replace(/\\/g, "/") === normalizedCwd
1035
+ );
1036
+ if (!registered) {
1037
+ issues.push({
1038
+ severity: "warning",
1039
+ message: "Este projeto n\xE3o est\xE1 cadastrado no registro central do Sauron na m\xE1quina.",
1040
+ fix: 'Execute "sauron init" para registrar o projeto globalmente.'
1041
+ });
1042
+ }
1043
+ const manifest = await getManifest(cwd);
1044
+ if (!manifest) {
1045
+ issues.push({
1046
+ severity: "error",
1047
+ message: "Manifesto do Sauron (.sauron/.manifest.json) n\xE3o foi encontrado.",
1048
+ fix: 'Execute "sauron init -y" para reinjetar a estrutura de manifesto.'
1049
+ });
1050
+ } else {
1051
+ for (const [relPath, expectedHash] of Object.entries(manifest.files)) {
1052
+ const fullPath = path11.join(cwd, relPath);
1053
+ if (!await fs11.pathExists(fullPath)) {
1054
+ issues.push({
1055
+ severity: "error",
1056
+ message: `Arquivo gerenciado pelo manifesto est\xE1 ausente: ${relPath}`,
1057
+ file: relPath,
1058
+ fix: 'Execute "sauron init" para restaurar os arquivos originais.'
1059
+ });
1060
+ continue;
1061
+ }
1062
+ const fileContent = await fs11.readFile(fullPath, "utf8");
1063
+ const computedHash = generateHash(fileContent);
1064
+ if (computedHash !== expectedHash) {
1065
+ issues.push({
1066
+ severity: "warning",
1067
+ message: `Modifica\xE7\xE3o manual detectada no arquivo do sistema: ${relPath}`,
1068
+ file: relPath,
1069
+ fix: "Valide se a modifica\xE7\xE3o foi intencional ou execute sauron init para restaurar o padr\xE3o."
1070
+ });
1071
+ }
1072
+ }
250
1073
  }
251
- if (action === "diff") {
252
- const diffStr = renderDiff(localContent, newContent, filePath);
253
- console.log("\n" + diffStr + "\n");
1074
+ const summaryPath = path11.join(cwd, ".sauron", "wiki", "summary.json");
1075
+ if (!await fs11.pathExists(summaryPath)) {
1076
+ issues.push({
1077
+ severity: "error",
1078
+ message: "Arquivo de sum\xE1rio da wiki (.sauron/wiki/summary.json) est\xE1 ausente.",
1079
+ fix: 'Execute "sauron init" para restabelecer os diret\xF3rios base da wiki.'
1080
+ });
254
1081
  } else {
255
- resolved = action;
1082
+ try {
1083
+ const summary = await fs11.readJson(summaryPath);
1084
+ if (Array.isArray(summary)) {
1085
+ for (const item of summary) {
1086
+ if (item.type === "file") {
1087
+ const fileRelPath = path11.join(".sauron", "wiki", item.path).replace(/\\/g, "/");
1088
+ const fileFullPath = path11.join(cwd, ".sauron", "wiki", item.path);
1089
+ if (!await fs11.pathExists(fileFullPath)) {
1090
+ issues.push({
1091
+ severity: "error",
1092
+ message: `Documento catalogado no sum\xE1rio est\xE1 ausente: ${fileRelPath}`,
1093
+ file: fileRelPath,
1094
+ fix: `Recrie o arquivo correspondente ou remova-o de summary.json.`
1095
+ });
1096
+ continue;
1097
+ }
1098
+ const content = await fs11.readFile(fileFullPath, "utf8");
1099
+ const computedHash = generateHash(content);
1100
+ const computedLen = Buffer.byteLength(content, "utf8");
1101
+ if (item.contentHash && computedHash !== item.contentHash) {
1102
+ issues.push({
1103
+ severity: "warning",
1104
+ message: `Diverg\xEAncia de assinatura criptogr\xE1fica no documento da wiki: ${fileRelPath}`,
1105
+ file: fileRelPath,
1106
+ fix: `Atualize o summary.json com o novo hash: "${computedHash}" e tamanho: ${computedLen}.`
1107
+ });
1108
+ }
1109
+ if (item.contentLength !== void 0 && computedLen !== item.contentLength) {
1110
+ issues.push({
1111
+ severity: "warning",
1112
+ message: `Diverg\xEAncia de tamanho de conte\xFAdo (bytes) no documento: ${fileRelPath}`,
1113
+ file: fileRelPath,
1114
+ fix: `Atualize o summary.json com o novo tamanho em bytes: ${computedLen}.`
1115
+ });
1116
+ }
1117
+ }
1118
+ }
1119
+ } else {
1120
+ issues.push({
1121
+ severity: "error",
1122
+ message: "summary.json possui formato estrutural inv\xE1lido (deve ser um array de objetos).",
1123
+ fix: "Corrija a formata\xE7\xE3o do JSON para seguir o padr\xE3o do Sauron CLI."
1124
+ });
1125
+ }
1126
+ } catch (err) {
1127
+ issues.push({
1128
+ severity: "error",
1129
+ message: `Falha ao ler o arquivo de sum\xE1rio (JSON corrompido): ${err.message}`,
1130
+ fix: "Valide a integridade do JSON de summary.json."
1131
+ });
1132
+ }
1133
+ }
1134
+ const targets = registered?.aiTargets || ["Cursor", "Windsurf", "Aider", "Antigravity"];
1135
+ for (const target of targets) {
1136
+ const adapter = AdapterFactory.getAdapter(target);
1137
+ if (adapter) {
1138
+ const rulesFileMap = {
1139
+ "Cursor": ".cursor/rules/sauron-memory.mdc",
1140
+ "Windsurf": ".windsurfrules",
1141
+ "Aider": ".aider.instructions.md",
1142
+ "Antigravity": ".agents/rules/memory.md"
1143
+ };
1144
+ const relPath = rulesFileMap[target];
1145
+ if (relPath) {
1146
+ const fullPath = path11.join(cwd, relPath);
1147
+ if (!await fs11.pathExists(fullPath)) {
1148
+ issues.push({
1149
+ severity: "error",
1150
+ message: `Configura\xE7\xE3o do agente ${target} est\xE1 ausente ou foi deletada: ${relPath}`,
1151
+ fix: `Rode sauron init para reinjetar a integra\xE7\xE3o com o ${target}.`
1152
+ });
1153
+ } else {
1154
+ const rulesContent = await fs11.readFile(fullPath, "utf8");
1155
+ const hasStart = rulesContent.includes("# SAURON START");
1156
+ const hasEnd = rulesContent.includes("# SAURON END");
1157
+ if (!hasStart || !hasEnd) {
1158
+ issues.push({
1159
+ severity: "error",
1160
+ message: `Bloco de governan\xE7a do Sauron foi violado ou removido em: ${relPath}`,
1161
+ file: relPath,
1162
+ fix: `Rode sauron init para restaurar as diretrizes de compliance no arquivo do agente.`
1163
+ });
1164
+ }
1165
+ }
1166
+ }
1167
+ }
1168
+ }
1169
+ const hasErrors = issues.some((issue) => issue.severity === "error");
1170
+ return {
1171
+ success: !hasErrors,
1172
+ issues
1173
+ };
1174
+ }
1175
+ };
1176
+
1177
+ // src/features/doctor/doctor.command.ts
1178
+ async function runDoctorCommand(options) {
1179
+ const cwd = process.cwd();
1180
+ const session = new SessionContext({
1181
+ json: !!options.json,
1182
+ interactive: !options.json
1183
+ });
1184
+ const driver = PresentationRouter.createDriver(session);
1185
+ if (!session.json) {
1186
+ p3.intro(pc3.bgBlue(pc3.white(" Sauron Memory System - Diagn\xF3stico (Doctor) ")));
1187
+ }
1188
+ driver.startSpinner("Escaneando integridade do c\xE9rebro da IA...");
1189
+ const doctorService = new DoctorService();
1190
+ try {
1191
+ const report = await doctorService.execute(cwd);
1192
+ driver.stopSpinner("Auditoria conclu\xEDda.", report.success);
1193
+ if (!session.json) {
1194
+ if (report.issues.length === 0) {
1195
+ driver.logSuccess("Nenhum problema encontrado. O Sauron est\xE1 \xEDntegro e em compliance!");
1196
+ } else {
1197
+ console.log("\nAnomalias Encontradas:");
1198
+ for (const issue of report.issues) {
1199
+ const badge = issue.severity === "error" ? pc3.red(pc3.bold(" ERROR ")) : pc3.yellow(pc3.bold(" WARNING "));
1200
+ console.log(`
1201
+ [${badge}] ${issue.message}`);
1202
+ if (issue.file) {
1203
+ console.log(` Arquivo: ${pc3.cyan(issue.file)}`);
1204
+ }
1205
+ if (issue.fix) {
1206
+ console.log(` Corre\xE7\xE3o recomendada: ${pc3.green(issue.fix)}`);
1207
+ }
1208
+ }
1209
+ console.log("");
1210
+ }
256
1211
  }
1212
+ const message = report.success ? "Auditoria conclu\xEDda com sucesso. Zero erros graves detectados!" : `Auditoria conclu\xEDda com problemas. Foram encontrados ${report.issues.filter((i) => i.severity === "error").length} erros graves.`;
1213
+ driver.finish({
1214
+ success: report.success,
1215
+ message,
1216
+ payload: {
1217
+ cwd,
1218
+ success: report.success,
1219
+ issues: report.issues
1220
+ }
1221
+ });
1222
+ } catch (error) {
1223
+ driver.stopSpinner("Erro na auditoria.", false);
1224
+ driver.finish({
1225
+ success: false,
1226
+ message: `Falha cr\xEDtica durante a execu\xE7\xE3o do doctor: ${error.message}`
1227
+ });
257
1228
  }
258
- return resolved;
259
1229
  }
260
- function renderDiff(oldStr, newStr, fileName) {
261
- const diffs = Diff.diffLines(oldStr, newStr);
262
- let output = pc.bold(`--- a/${fileName} (Local)
263
- +++ b/${fileName} (Novo Template)
264
- `);
265
- diffs.forEach((part) => {
266
- const lines = part.value.replace(/\n$/, "").split("\n");
267
- for (const line of lines) {
268
- if (part.added) {
269
- output += pc.green(`+ ${line}
270
- `);
271
- } else if (part.removed) {
272
- output += pc.red(`- ${line}
273
- `);
1230
+
1231
+ // src/features/uninstall/uninstall.command.ts
1232
+ import pc4 from "picocolors";
1233
+ import * as p4 from "@clack/prompts";
1234
+
1235
+ // src/features/uninstall/uninstall.service.ts
1236
+ import fs12 from "fs-extra";
1237
+ import path12 from "path";
1238
+ var UninstallService = class {
1239
+ registryService = new RegistryService();
1240
+ async execute(options) {
1241
+ const { cwd, purge } = options;
1242
+ const removedPaths = [];
1243
+ await this.registryService.unregisterWorkspace(cwd);
1244
+ removedPaths.push("~/.sauron/registry.json (descadastrado)");
1245
+ const adapters = AdapterFactory.getAllAdapters();
1246
+ for (const adapter of adapters) {
1247
+ const paths = await adapter.clean(cwd);
1248
+ removedPaths.push(...paths);
1249
+ }
1250
+ const agentsMdPath = path12.join(cwd, "AGENTS.md");
1251
+ if (await fs12.pathExists(agentsMdPath)) {
1252
+ await fs12.remove(agentsMdPath);
1253
+ removedPaths.push("AGENTS.md");
1254
+ }
1255
+ const sauronDir = path12.join(cwd, ".sauron");
1256
+ if (await fs12.pathExists(sauronDir)) {
1257
+ if (purge) {
1258
+ await fs12.remove(sauronDir);
1259
+ removedPaths.push(".sauron/ (purgado por completo)");
274
1260
  } else {
275
- output += pc.dim(` ${line}
276
- `);
1261
+ const manifestPath = path12.join(sauronDir, ".manifest.json");
1262
+ if (await fs12.pathExists(manifestPath)) {
1263
+ await fs12.remove(manifestPath);
1264
+ removedPaths.push(".sauron/.manifest.json");
1265
+ }
1266
+ removedPaths.push(".sauron/wiki/ (preservado como base est\xE1tica)");
277
1267
  }
278
1268
  }
1269
+ return removedPaths;
1270
+ }
1271
+ };
1272
+
1273
+ // src/features/uninstall/uninstall.command.ts
1274
+ async function runUninstallCommand(options) {
1275
+ const cwd = process.cwd();
1276
+ const session = new SessionContext({
1277
+ json: !!options.json,
1278
+ interactive: !options.yes && !options.json
279
1279
  });
280
- return output;
1280
+ const driver = PresentationRouter.createDriver(session);
1281
+ if (!session.json) {
1282
+ p4.intro(pc4.bgRed(pc4.white(" Sauron Memory System - Desinstala\xE7\xE3o ")));
1283
+ }
1284
+ if (session.interactive) {
1285
+ const confirm2 = await p4.confirm({
1286
+ message: options.purge ? pc4.red("Tem certeza que deseja desinstalar o Sauron e PURGAR toda a wiki de documenta\xE7\xE3o f\xEDsica?") : "Tem certeza que deseja desinstalar o Sauron deste projeto (a wiki/ de documenta\xE7\xE3o ser\xE1 preservada)?"
1287
+ });
1288
+ if (p4.isCancel(confirm2) || !confirm2) {
1289
+ p4.cancel("Desinstala\xE7\xE3o abortada.");
1290
+ process.exit(0);
1291
+ }
1292
+ }
1293
+ driver.startSpinner("Desinstalando Sauron e limpando vincula\xE7\xF5es de agentes...");
1294
+ const uninstallService = new UninstallService();
1295
+ try {
1296
+ const removedPaths = await uninstallService.execute({
1297
+ cwd,
1298
+ purge: !!options.purge
1299
+ });
1300
+ driver.stopSpinner("Desinstala\xE7\xE3o conclu\xEDda.", true);
1301
+ const message = options.purge ? "Sauron CLI desinstalado por completo e pasta wiki/ purgada com sucesso!" : "Sauron CLI desinstalado com sucesso! A base wiki/ foi preservada como hist\xF3rico de contexto.";
1302
+ driver.finish({
1303
+ success: true,
1304
+ message,
1305
+ payload: {
1306
+ cwd,
1307
+ purge: !!options.purge,
1308
+ removedPaths
1309
+ }
1310
+ });
1311
+ } catch (error) {
1312
+ driver.stopSpinner("Erro ao desinstalar.", false);
1313
+ driver.finish({
1314
+ success: false,
1315
+ message: `Falha cr\xEDtica durante a desinstala\xE7\xE3o: ${error.message}`
1316
+ });
1317
+ }
281
1318
  }
282
1319
 
283
1320
  // src/index.ts
284
1321
  var program = new Command();
285
- program.name("sauron").description("Sauron CLI - Framework para resolu\xE7\xE3o de Amn\xE9sia de Contexto em IAs").version("1.0.0");
286
- program.command("init").description("Inicializa o Sauron Memory System no projeto atual").option("-y, --yes", "Pula os prompts interativos e usa os valores padr\xE3o (N\xE3o-interativo)").action((options) => runInitCommand(options));
1322
+ program.name("sauron").description("Sauron CLI - Framework para resolu\xE7\xE3o de Amn\xE9sia de Contexto em IAs").version("1.2.0");
1323
+ program.command("init").description("Inicializa o Sauron Memory System no projeto atual").option("-y, --yes", "Pula os prompts interativos e usa os valores padr\xE3o (N\xE3o-interativo)").option("--json", "Sa\xEDda do resultado em formato JSON estruturado").option("--conflict <resolution>", "Estrat\xE9gia de resolu\xE7\xE3o de conflitos autom\xE1tica (ours | theirs)").action((options) => runInitCommand(options));
1324
+ program.command("doctor").description("Executa uma auditoria de integridade e conformidade estrutural das regras e wiki").option("--json", "Sa\xEDda do relat\xF3rio em formato JSON estruturado").action((options) => runDoctorCommand(options));
1325
+ program.command("uninstall").description("Remove as vincula\xE7\xF5es do Sauron CLI e regras de assistentes locais").option("--purge", "Remove fisicamente a pasta .sauron/ incluindo a wiki de documenta\xE7\xE3o").option("-y, --yes", "Pula os prompts de confirma\xE7\xE3o de desinstala\xE7\xE3o").option("--json", "Sa\xEDda do resultado em formato JSON estruturado").action((options) => runUninstallCommand(options));
287
1326
  program.parse();