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/.agents/rules/memory.md +156 -0
- package/.agents/skills/wiki/SKILL.md +172 -0
- package/.aider.instructions.md +152 -0
- package/.cursor/rules/sauron-memory.mdc +157 -0
- package/.genesis/prompts/prompt-onboarding-inteligente.md +45 -0
- package/.genesis/prompts/prompt-sauron-vs-openspec-arquitetura.md +106 -0
- package/.github/workflows/ci.yml +27 -0
- package/.sauron/.manifest.json +2 -1
- package/.sauron/wiki/history/implementacao-scanner-inteligente.md +29 -0
- package/.sauron/wiki/history/reestruturacao-arquitetural-core.md +29 -0
- package/.sauron/wiki/history/resiliencia-caminho-templates.md +29 -0
- package/.sauron/wiki/standards/typescript.rules.md +21 -0
- package/.sauron/wiki/summary.json +81 -0
- package/.windsurfrules +160 -0
- package/Evolu/303/247/303/243o Arquitetural do Sauron CLI.md" +97 -0
- package/README.md +40 -5
- package/dist/index.js +1186 -147
- package/package.json +1 -1
- package/templates/.agents/rules/memory.md +68 -68
- package/templates/.agents/skills/wiki/SKILL.md +14 -14
- package/templates/wiki-recipes/nextjs.rules.md +21 -0
- package/templates/wiki-recipes/postgresql.rules.md +20 -0
- package/templates/wiki-recipes/react.rules.md +18 -0
- package/templates/wiki-recipes/tailwindcss.rules.md +18 -0
- package/templates/wiki-recipes/typescript.rules.md +21 -0
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
|
|
8
|
-
import
|
|
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
|
|
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
|
|
15
|
-
import
|
|
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 `#
|
|
56
|
+
return `# Global Agent Guidelines (Sauron CLI)
|
|
57
57
|
|
|
58
|
-
**
|
|
59
|
-
**
|
|
58
|
+
**Targets:** ${aiTargets.join(", ")}
|
|
59
|
+
**Severity:** ${severity}
|
|
60
60
|
|
|
61
|
-
##
|
|
61
|
+
## Project Context
|
|
62
62
|
${projectContext}
|
|
63
63
|
|
|
64
|
-
## Stack
|
|
64
|
+
## Tech Stack
|
|
65
65
|
${projectStack}
|
|
66
66
|
|
|
67
|
-
##
|
|
68
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 =
|
|
84
|
-
const targetPath =
|
|
85
|
-
const stat = await
|
|
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
|
|
413
|
+
await fs8.ensureDir(targetPath);
|
|
88
414
|
await processDirectory(sourcePath, targetPath);
|
|
89
415
|
} else {
|
|
90
|
-
const content = await
|
|
91
|
-
const relPath =
|
|
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
|
|
94
|
-
const localContent = await
|
|
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
|
|
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
|
|
429
|
+
await fs8.ensureDir(path8.dirname(targetPath));
|
|
104
430
|
}
|
|
105
431
|
if (shouldWrite) {
|
|
106
|
-
await
|
|
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(
|
|
113
|
-
await processDirectory(
|
|
114
|
-
const agentsMdPath =
|
|
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
|
|
123
|
-
const localAgents = await
|
|
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
|
|
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
|
|
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 =
|
|
887
|
+
var __dirname = path10.dirname(__filename);
|
|
143
888
|
async function runInitCommand(options) {
|
|
144
889
|
const cwd = process.cwd();
|
|
145
|
-
const
|
|
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
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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();
|