sauron-cli 1.1.0 → 1.2.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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "files": {
4
+ "AGENTS.md": "16636ca1f503c84b91daa406a3cfc581563580f48443e6c082e32c7ea0037920"
5
+ }
6
+ }
package/dist/index.js CHANGED
@@ -3,14 +3,18 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
- // src/commands/init.ts
6
+ // src/features/init/init.command.ts
7
+ import path3 from "path";
8
+ import pc from "picocolors";
9
+ import { fileURLToPath } from "url";
10
+ import * as p from "@clack/prompts";
11
+ import * as Diff from "diff";
12
+
13
+ // src/features/init/init.service.ts
7
14
  import fs2 from "fs-extra";
8
15
  import path2 from "path";
9
- import pc2 from "picocolors";
10
- import { fileURLToPath } from "url";
11
- import * as p2 from "@clack/prompts";
12
16
 
13
- // src/engine/manifest.ts
17
+ // src/core/manifest.service.ts
14
18
  import crypto from "crypto";
15
19
  import fs from "fs-extra";
16
20
  import path from "path";
@@ -35,80 +39,127 @@ async function saveManifest(targetDir, manifest) {
35
39
  await fs.writeJson(manifestPath, manifest, { spaces: 2 });
36
40
  }
37
41
 
38
- // src/engine/merge.ts
39
- import * as p from "@clack/prompts";
40
- import pc from "picocolors";
41
- import * as Diff from "diff";
42
- async function resolveConflict(filePath, localContent, newContent, manifestHash) {
42
+ // src/core/merge.service.ts
43
+ function checkConflict(localContent, newContent, manifestHash) {
43
44
  const localHash = generateHash(localContent);
44
45
  if (manifestHash && localHash === manifestHash) {
45
- return "theirs";
46
+ return false;
46
47
  }
47
48
  if (localContent === newContent) {
48
- return "ours";
49
+ return false;
49
50
  }
50
- p.note(`Foi detectada uma muta\xE7\xE3o no arquivo: ${pc.cyan(filePath)}
51
- O seu agente de IA ou voc\xEA modificou este arquivo desde a \xFAltima instala\xE7\xE3o.`, "\u26A0\uFE0F CONFLITO DETECTADO");
52
- let resolved = null;
53
- while (!resolved) {
54
- const action = await p.select({
55
- message: `Como deseja proceder com ${pc.cyan(filePath)}?`,
56
- options: [
57
- { value: "ours", label: "Preservar Local (Ours)", hint: "Mant\xE9m sua vers\xE3o e ignora o novo template" },
58
- { value: "theirs", label: "Sobrescrever (Theirs)", hint: "Destr\xF3i a sua vers\xE3o e aplica o template novo" },
59
- { value: "diff", label: "Auditar Diferen\xE7as (Diff Mode)", hint: "Mostra o que vai mudar visualmente" }
60
- ]
61
- });
62
- if (p.isCancel(action)) {
63
- p.cancel("Opera\xE7\xE3o cancelada pelo usu\xE1rio durante a resolu\xE7\xE3o de conflito.");
64
- process.exit(0);
65
- }
66
- if (action === "diff") {
67
- const diffStr = renderDiff(localContent, newContent, filePath);
68
- console.log("\n" + diffStr + "\n");
69
- } else {
70
- resolved = action;
71
- }
72
- }
73
- return resolved;
51
+ return true;
74
52
  }
75
- function renderDiff(oldStr, newStr, fileName) {
76
- const diffs = Diff.diffLines(oldStr, newStr);
77
- let output = pc.bold(`--- a/${fileName} (Local)
78
- +++ b/${fileName} (Novo Template)
79
- `);
80
- diffs.forEach((part) => {
81
- const lines = part.value.replace(/\n$/, "").split("\n");
82
- for (const line of lines) {
83
- if (part.added) {
84
- output += pc.green(`+ ${line}
85
- `);
86
- } else if (part.removed) {
87
- output += pc.red(`- ${line}
88
- `);
89
- } else {
90
- output += pc.dim(` ${line}
91
- `);
53
+
54
+ // src/features/init/templates.ts
55
+ function generateAgentsMarkdown(aiTargets, severity, projectContext, projectStack) {
56
+ return `# Diretrizes Globais de Agentes (Sauron CLI)
57
+
58
+ **Alvos:** ${aiTargets.join(", ")}
59
+ **Severidade:** ${severity}
60
+
61
+ ## Contexto do Projeto
62
+ ${projectContext}
63
+
64
+ ## Stack Tecnol\xF3gica
65
+ ${projectStack}
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.
69
+
70
+ *Gerado automaticamente pelo Sauron CLI.*
71
+ `;
72
+ }
73
+
74
+ // src/features/init/init.service.ts
75
+ var InitService = class {
76
+ async execute(options, onConflict) {
77
+ const { cwd, templatesDir } = options;
78
+ 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);
82
+ 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);
86
+ if (stat.isDirectory()) {
87
+ await fs2.ensureDir(targetPath);
88
+ await processDirectory(sourcePath, targetPath);
89
+ } else {
90
+ const content = await fs2.readFile(sourcePath, "utf8");
91
+ const relPath = path2.relative(cwd, targetPath).replace(/\\/g, "/");
92
+ let shouldWrite = true;
93
+ if (await fs2.pathExists(targetPath)) {
94
+ const localContent = await fs2.readFile(targetPath, "utf8");
95
+ const hasConflict = checkConflict(localContent, content, manifest.files[relPath]);
96
+ if (hasConflict) {
97
+ const decision = await onConflict(relPath, localContent, content);
98
+ if (decision === "ours") {
99
+ shouldWrite = false;
100
+ }
101
+ }
102
+ } else {
103
+ await fs2.ensureDir(path2.dirname(targetPath));
104
+ }
105
+ if (shouldWrite) {
106
+ await fs2.writeFile(targetPath, content, "utf8");
107
+ }
108
+ manifest.files[relPath] = generateHash(content);
109
+ }
92
110
  }
93
111
  }
94
- });
95
- return output;
96
- }
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");
115
+ const agentsMdContent = generateAgentsMarkdown(
116
+ options.aiTargets,
117
+ options.severity,
118
+ options.projectContext,
119
+ options.projectStack
120
+ );
121
+ let shouldWriteAgents = true;
122
+ if (await fs2.pathExists(agentsMdPath)) {
123
+ const localAgents = await fs2.readFile(agentsMdPath, "utf8");
124
+ const hasConflict = checkConflict(localAgents, agentsMdContent, manifest.files["AGENTS.md"]);
125
+ if (hasConflict) {
126
+ const decision = await onConflict("AGENTS.md", localAgents, agentsMdContent);
127
+ if (decision === "ours") {
128
+ shouldWriteAgents = false;
129
+ }
130
+ }
131
+ }
132
+ if (shouldWriteAgents) {
133
+ await fs2.writeFile(agentsMdPath, agentsMdContent, "utf8");
134
+ }
135
+ manifest.files["AGENTS.md"] = generateHash(agentsMdContent);
136
+ await saveManifest(cwd, manifest);
137
+ }
138
+ };
97
139
 
98
- // src/commands/init.ts
140
+ // src/features/init/init.command.ts
99
141
  var __filename = fileURLToPath(import.meta.url);
100
- var __dirname = path2.dirname(__filename);
101
- async function runInit(options) {
142
+ var __dirname = path3.dirname(__filename);
143
+ async function runInitCommand(options) {
102
144
  const cwd = process.cwd();
103
- p2.intro(pc2.bgBlue(pc2.white(" \u{1F441}\uFE0F Sauron Memory System - Inicializa\xE7\xE3o ")));
145
+ const logo = `
146
+ \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
+ \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
+ \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
150
+ \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
+ `;
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 ")));
104
155
  let aiTargets = ["Cursor", "Windsurf", "Aider", "Antigravity"];
105
156
  let severity = "Observacional";
106
157
  let projectContext = "Projeto Gen\xE9rico";
107
158
  let projectStack = "Node.js, TypeScript";
108
159
  if (!options.yes) {
109
- const config = await p2.group(
160
+ const config = await p.group(
110
161
  {
111
- targets: () => p2.multiselect({
162
+ targets: () => p.multiselect({
112
163
  message: "Qual(is) IA(s) voc\xEA usar\xE1 neste projeto?",
113
164
  options: [
114
165
  { value: "Cursor", label: "Cursor", hint: "Recomendado" },
@@ -118,19 +169,19 @@ async function runInit(options) {
118
169
  ],
119
170
  required: false
120
171
  }),
121
- severity: () => p2.select({
172
+ severity: () => p.select({
122
173
  message: "Qual o n\xEDvel de severidade das regras da IA?",
123
174
  options: [
124
175
  { value: "Observacional", label: "Observacional (A IA documenta quando julgar necess\xE1rio)" },
125
176
  { value: "Estrito", label: "Estrito (Bloqueia altera\xE7\xF5es sem documenta\xE7\xE3o expl\xEDcita)" }
126
177
  ]
127
178
  }),
128
- context: () => p2.text({
179
+ context: () => p.text({
129
180
  message: "Descreva brevemente o contexto do seu projeto:",
130
181
  placeholder: "Ex: App de agendamento de pilates",
131
182
  defaultValue: "Projeto Gen\xE9rico"
132
183
  }),
133
- stack: () => p2.text({
184
+ stack: () => p.text({
134
185
  message: "Qual a stack tecnol\xF3gica principal do seu projeto?",
135
186
  placeholder: "Ex: Next.js 15, Tailwind, Firebase",
136
187
  defaultValue: "Node.js, TypeScript"
@@ -138,7 +189,7 @@ async function runInit(options) {
138
189
  },
139
190
  {
140
191
  onCancel: () => {
141
- p2.cancel("Inicializa\xE7\xE3o abortada.");
192
+ p.cancel("Inicializa\xE7\xE3o abortada.");
142
193
  process.exit(0);
143
194
  }
144
195
  }
@@ -148,83 +199,89 @@ async function runInit(options) {
148
199
  projectContext = config.context;
149
200
  projectStack = config.stack;
150
201
  }
151
- const s = p2.spinner();
202
+ const s = p.spinner();
152
203
  s.start("Injetando o C\xE9rebro da IA no reposit\xF3rio...");
153
- const packageRoot = path2.join(__dirname, "..", "..");
154
- const templatesDir = path2.join(packageRoot, "templates");
155
- const manifest = await getManifest(cwd) || { version: "1.0.0", files: {} };
156
- async function processDirectory(source, target) {
157
- if (!await fs2.pathExists(source)) return;
158
- const files = await fs2.readdir(source);
159
- for (const file of files) {
160
- const sourcePath = path2.join(source, file);
161
- const targetPath = path2.join(target, file);
162
- const stat = await fs2.stat(sourcePath);
163
- if (stat.isDirectory()) {
164
- await fs2.ensureDir(targetPath);
165
- await processDirectory(sourcePath, targetPath);
166
- } else {
167
- const content = await fs2.readFile(sourcePath, "utf8");
168
- const relPath = path2.relative(cwd, targetPath).replace(/\\/g, "/");
169
- let shouldWrite = true;
170
- if (await fs2.pathExists(targetPath)) {
171
- const localContent = await fs2.readFile(targetPath, "utf8");
172
- s.stop(`Conflito ou atualiza\xE7\xE3o em ${relPath}`);
173
- const decision = await resolveConflict(relPath, localContent, content, manifest.files[relPath]);
174
- s.start("Continuando inje\xE7\xE3o...");
175
- if (decision === "ours") {
176
- shouldWrite = false;
177
- }
178
- } else {
179
- await fs2.ensureDir(path2.dirname(targetPath));
180
- }
181
- if (shouldWrite) {
182
- await fs2.writeFile(targetPath, content, "utf8");
183
- }
184
- manifest.files[relPath] = generateHash(content);
204
+ const packageRoot = path3.join(__dirname, "..", "..", "..");
205
+ const templatesDir = path3.join(packageRoot, "templates");
206
+ const initService = new InitService();
207
+ try {
208
+ await initService.execute(
209
+ {
210
+ aiTargets,
211
+ severity,
212
+ projectContext,
213
+ projectStack,
214
+ cwd,
215
+ templatesDir
216
+ },
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;
185
222
  }
186
- }
187
- }
188
- await processDirectory(path2.join(templatesDir, ".sauron"), path2.join(cwd, ".sauron"));
189
- await processDirectory(path2.join(templatesDir, ".agents"), path2.join(cwd, ".agents"));
190
- const agentsMdPath = path2.join(cwd, "AGENTS.md");
191
- const agentsMdContent = `# Diretrizes Globais de Agentes (Sauron CLI)
192
-
193
- **Alvos:** ${aiTargets.join(", ")}
194
- **Severidade:** ${severity}
195
-
196
- ## Contexto do Projeto
197
- ${projectContext}
198
-
199
- ## Stack Tecnol\xF3gica
200
- ${projectStack}
201
-
202
- ## Regra de Ouro (Write Obligation)
203
- 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.
204
-
205
- *Gerado automaticamente pelo Sauron CLI.*
206
- `;
207
- let shouldWriteAgents = true;
208
- if (await fs2.pathExists(agentsMdPath)) {
209
- const localAgents = await fs2.readFile(agentsMdPath, "utf8");
210
- s.stop(`Conflito em AGENTS.md`);
211
- const decision = await resolveConflict("AGENTS.md", localAgents, agentsMdContent, manifest.files["AGENTS.md"]);
212
- s.start("Finalizando...");
213
- if (decision === "ours") shouldWriteAgents = false;
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."')
227
+ );
228
+ } catch (error) {
229
+ s.stop("Falha na instala\xE7\xE3o.");
230
+ p.cancel(error.message);
231
+ process.exit(1);
214
232
  }
215
- if (shouldWriteAgents) {
216
- await fs2.writeFile(agentsMdPath, agentsMdContent, "utf8");
233
+ }
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);
250
+ }
251
+ if (action === "diff") {
252
+ const diffStr = renderDiff(localContent, newContent, filePath);
253
+ console.log("\n" + diffStr + "\n");
254
+ } else {
255
+ resolved = action;
256
+ }
217
257
  }
218
- manifest.files["AGENTS.md"] = generateHash(agentsMdContent);
219
- await saveManifest(cwd, manifest);
220
- s.stop("Inje\xE7\xE3o finalizada.");
221
- p2.outro(
222
- pc2.green(pc2.bold("Sauron Memory System instalado com sucesso! \u{1F441}\uFE0F\n\n")) + pc2.white("O C\xE9rebro da IA foi injetado e protegido pelo motor de integridade.\n") + pc2.cyan("A\xE7\xF5es Recomendadas:\n") + pc2.dim("Copie o comando abaixo e envie para a sua IA testar a nova arquitetura:\n") + pc2.yellow('"Analise a estrutura .sauron/ e .agents/ rec\xE9m injetada e sugira quais regras cr\xEDticas eu devo documentar agora para nosso projeto."')
223
- );
258
+ return resolved;
259
+ }
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
+ `);
274
+ } else {
275
+ output += pc.dim(` ${line}
276
+ `);
277
+ }
278
+ }
279
+ });
280
+ return output;
224
281
  }
225
282
 
226
283
  // src/index.ts
227
284
  var program = new Command();
228
285
  program.name("sauron").description("Sauron CLI - Framework para resolu\xE7\xE3o de Amn\xE9sia de Contexto em IAs").version("1.0.0");
229
- 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) => runInit(options));
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));
230
287
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sauron-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "bin": {