sauron-cli 1.1.3 → 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.
Files changed (2) hide show
  1. package/dist/index.js +186 -138
  2. package/package.json +1 -1
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,70 +39,108 @@ 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
145
  const logo = `
104
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
@@ -107,17 +149,17 @@ async function runInit(options) {
107
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
108
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
109
151
  `;
110
- console.log(pc2.bold(logo));
111
- console.log(pc2.dim(" CLI de Idempot\xEAncia e Mem\xF3ria para IAs\n"));
112
- p2.intro(pc2.bgRed(pc2.white(" Sauron Memory System - Inicializa\xE7\xE3o ")));
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 ")));
113
155
  let aiTargets = ["Cursor", "Windsurf", "Aider", "Antigravity"];
114
156
  let severity = "Observacional";
115
157
  let projectContext = "Projeto Gen\xE9rico";
116
158
  let projectStack = "Node.js, TypeScript";
117
159
  if (!options.yes) {
118
- const config = await p2.group(
160
+ const config = await p.group(
119
161
  {
120
- targets: () => p2.multiselect({
162
+ targets: () => p.multiselect({
121
163
  message: "Qual(is) IA(s) voc\xEA usar\xE1 neste projeto?",
122
164
  options: [
123
165
  { value: "Cursor", label: "Cursor", hint: "Recomendado" },
@@ -127,19 +169,19 @@ async function runInit(options) {
127
169
  ],
128
170
  required: false
129
171
  }),
130
- severity: () => p2.select({
172
+ severity: () => p.select({
131
173
  message: "Qual o n\xEDvel de severidade das regras da IA?",
132
174
  options: [
133
175
  { value: "Observacional", label: "Observacional (A IA documenta quando julgar necess\xE1rio)" },
134
176
  { value: "Estrito", label: "Estrito (Bloqueia altera\xE7\xF5es sem documenta\xE7\xE3o expl\xEDcita)" }
135
177
  ]
136
178
  }),
137
- context: () => p2.text({
179
+ context: () => p.text({
138
180
  message: "Descreva brevemente o contexto do seu projeto:",
139
181
  placeholder: "Ex: App de agendamento de pilates",
140
182
  defaultValue: "Projeto Gen\xE9rico"
141
183
  }),
142
- stack: () => p2.text({
184
+ stack: () => p.text({
143
185
  message: "Qual a stack tecnol\xF3gica principal do seu projeto?",
144
186
  placeholder: "Ex: Next.js 15, Tailwind, Firebase",
145
187
  defaultValue: "Node.js, TypeScript"
@@ -147,7 +189,7 @@ async function runInit(options) {
147
189
  },
148
190
  {
149
191
  onCancel: () => {
150
- p2.cancel("Inicializa\xE7\xE3o abortada.");
192
+ p.cancel("Inicializa\xE7\xE3o abortada.");
151
193
  process.exit(0);
152
194
  }
153
195
  }
@@ -157,83 +199,89 @@ async function runInit(options) {
157
199
  projectContext = config.context;
158
200
  projectStack = config.stack;
159
201
  }
160
- const s = p2.spinner();
202
+ const s = p.spinner();
161
203
  s.start("Injetando o C\xE9rebro da IA no reposit\xF3rio...");
162
- const packageRoot = path2.join(__dirname, "..", "..");
163
- const templatesDir = path2.join(packageRoot, "templates");
164
- const manifest = await getManifest(cwd) || { version: "1.0.0", files: {} };
165
- async function processDirectory(source, target) {
166
- if (!await fs2.pathExists(source)) return;
167
- const files = await fs2.readdir(source);
168
- for (const file of files) {
169
- const sourcePath = path2.join(source, file);
170
- const targetPath = path2.join(target, file);
171
- const stat = await fs2.stat(sourcePath);
172
- if (stat.isDirectory()) {
173
- await fs2.ensureDir(targetPath);
174
- await processDirectory(sourcePath, targetPath);
175
- } else {
176
- const content = await fs2.readFile(sourcePath, "utf8");
177
- const relPath = path2.relative(cwd, targetPath).replace(/\\/g, "/");
178
- let shouldWrite = true;
179
- if (await fs2.pathExists(targetPath)) {
180
- const localContent = await fs2.readFile(targetPath, "utf8");
181
- s.stop(`Conflito ou atualiza\xE7\xE3o em ${relPath}`);
182
- const decision = await resolveConflict(relPath, localContent, content, manifest.files[relPath]);
183
- s.start("Continuando inje\xE7\xE3o...");
184
- if (decision === "ours") {
185
- shouldWrite = false;
186
- }
187
- } else {
188
- await fs2.ensureDir(path2.dirname(targetPath));
189
- }
190
- if (shouldWrite) {
191
- await fs2.writeFile(targetPath, content, "utf8");
192
- }
193
- 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;
194
222
  }
195
- }
196
- }
197
- await processDirectory(path2.join(templatesDir, ".sauron"), path2.join(cwd, ".sauron"));
198
- await processDirectory(path2.join(templatesDir, ".agents"), path2.join(cwd, ".agents"));
199
- const agentsMdPath = path2.join(cwd, "AGENTS.md");
200
- const agentsMdContent = `# Diretrizes Globais de Agentes (Sauron CLI)
201
-
202
- **Alvos:** ${aiTargets.join(", ")}
203
- **Severidade:** ${severity}
204
-
205
- ## Contexto do Projeto
206
- ${projectContext}
207
-
208
- ## Stack Tecnol\xF3gica
209
- ${projectStack}
210
-
211
- ## Regra de Ouro (Write Obligation)
212
- 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.
213
-
214
- *Gerado automaticamente pelo Sauron CLI.*
215
- `;
216
- let shouldWriteAgents = true;
217
- if (await fs2.pathExists(agentsMdPath)) {
218
- const localAgents = await fs2.readFile(agentsMdPath, "utf8");
219
- s.stop(`Conflito em AGENTS.md`);
220
- const decision = await resolveConflict("AGENTS.md", localAgents, agentsMdContent, manifest.files["AGENTS.md"]);
221
- s.start("Finalizando...");
222
- 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);
223
232
  }
224
- if (shouldWriteAgents) {
225
- 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
+ }
226
257
  }
227
- manifest.files["AGENTS.md"] = generateHash(agentsMdContent);
228
- await saveManifest(cwd, manifest);
229
- s.stop("Inje\xE7\xE3o finalizada.");
230
- p2.outro(
231
- pc2.green(pc2.bold("Sauron Memory System instalado com sucesso!\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."')
232
- );
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;
233
281
  }
234
282
 
235
283
  // src/index.ts
236
284
  var program = new Command();
237
285
  program.name("sauron").description("Sauron CLI - Framework para resolu\xE7\xE3o de Amn\xE9sia de Contexto em IAs").version("1.0.0");
238
- 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));
239
287
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sauron-cli",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "bin": {