sauron-cli 1.4.0 → 1.4.1
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,20 @@
|
|
|
1
|
+
# Prompt: Inteligência de Atualização e Proteção de Memória no Sauron CLI
|
|
2
|
+
|
|
3
|
+
```markdown
|
|
4
|
+
Estamos desenvolvendo o Sauron CLI, uma ferramenta de CLI em Node.js (TypeScript) que injeta regras de governança e memória arquitetural em repositórios para otimizar o uso de IAs (Cursor, Windsurf, Aider).
|
|
5
|
+
|
|
6
|
+
Stack: Node.js, TypeScript, commander, @clack/prompts.
|
|
7
|
+
|
|
8
|
+
Atualmente, temos o comando `sauron init`, que executa um onboarding interativo com o usuário (perguntando as IAs que serão usadas, severidade das regras, contexto do projeto e stack tecnológica). Após coletar essas respostas, ele gera os arquivos de manifesto (`AGENTS.md`, `.sauron/.manifest.json` e a pasta `.agents/`).
|
|
9
|
+
|
|
10
|
+
O problema atual:
|
|
11
|
+
Quando o usuário roda `npx sauron-cli@latest init` para **atualizar** o Sauron em um projeto existente, a CLI refaz todas as perguntas do onboarding inicial do zero, ignorando as configurações que o usuário já respondeu na primeira instalação.
|
|
12
|
+
|
|
13
|
+
O que precisamos:
|
|
14
|
+
1. **Detecção de Instalação Existente**: Antes de iniciar o onboarding do `@clack/prompts`, a CLI deve verificar se o `.sauron/.manifest.json` (ou `AGENTS.md`) já existe.
|
|
15
|
+
2. **Reaproveitamento de Respostas**: Se existir, extrair os dados antigos (aiTargets, severity, projectContext, projectStack) e usá-los como `initialValue` nas perguntas do clack.
|
|
16
|
+
3. **Opção de Bypass (Fast-track)**: Melhor ainda seria perguntar "Detectamos uma instalação anterior. Deseja manter as configurações atuais e pular o onboarding? (y/n)". Se 'y', pular as perguntas e apenas re-injetar os arquivos principais atualizados.
|
|
17
|
+
4. **Proteção Rigorosa da Memória**: Garantir que a lógica de atualização **nunca** apague, sobrescreva ou modifique arquivos de documentação criados pelo usuário/IA dentro das subpastas de `.sauron/wiki/` (ex: `knowledge/`, `history/`, `modules/`). A atualização só pode atuar sobre a pasta `.agents/`, `AGENTS.md` e os arquivos base de configuração que vêm nos templates.
|
|
18
|
+
|
|
19
|
+
Como podemos implementar essa inteligência no nosso comando de init (provavelmente refatorando o `init.command.ts` e o fluxo do `init.service.ts`) de forma elegante, garantindo a idempotência e segurança absoluta dos dados da Wiki?
|
|
20
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -10,10 +10,6 @@ import pc2 from "picocolors";
|
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import * as p2 from "@clack/prompts";
|
|
12
12
|
|
|
13
|
-
// src/features/init/init.service.ts
|
|
14
|
-
import fs8 from "fs-extra";
|
|
15
|
-
import path8 from "path";
|
|
16
|
-
|
|
17
13
|
// src/core/manifest.service.ts
|
|
18
14
|
import crypto from "crypto";
|
|
19
15
|
import fs from "fs-extra";
|
|
@@ -39,6 +35,10 @@ async function saveManifest(targetDir, manifest) {
|
|
|
39
35
|
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
40
36
|
}
|
|
41
37
|
|
|
38
|
+
// src/features/init/init.service.ts
|
|
39
|
+
import fs8 from "fs-extra";
|
|
40
|
+
import path8 from "path";
|
|
41
|
+
|
|
42
42
|
// src/core/merge.service.ts
|
|
43
43
|
function checkConflict(localContent, newContent, manifestHash) {
|
|
44
44
|
const localHash = generateHash(localContent);
|
|
@@ -410,6 +410,10 @@ var InitService = class {
|
|
|
410
410
|
const targetPath = path8.join(target, file);
|
|
411
411
|
const stat = await fs8.stat(sourcePath);
|
|
412
412
|
if (stat.isDirectory()) {
|
|
413
|
+
const relativeToTarget = path8.relative(cwd, targetPath).replace(/\\/g, "/");
|
|
414
|
+
if (relativeToTarget === ".sauron/wiki" && await fs8.pathExists(targetPath)) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
413
417
|
await fs8.ensureDir(targetPath);
|
|
414
418
|
await processDirectory(sourcePath, targetPath);
|
|
415
419
|
} else {
|
|
@@ -464,6 +468,12 @@ var InitService = class {
|
|
|
464
468
|
modifiedFiles.push("AGENTS.md");
|
|
465
469
|
}
|
|
466
470
|
manifest.files["AGENTS.md"] = generateHash(agentsMdContent);
|
|
471
|
+
manifest.config = {
|
|
472
|
+
aiTargets: options.aiTargets,
|
|
473
|
+
severity: options.severity,
|
|
474
|
+
projectContext: options.projectContext,
|
|
475
|
+
projectStack: options.projectStack
|
|
476
|
+
};
|
|
467
477
|
await saveManifest(cwd, manifest);
|
|
468
478
|
modifiedFiles.push(".sauron/.manifest.json");
|
|
469
479
|
const memoryFilePath = path8.join(cwd, ".agents", "rules", "memory.md");
|
|
@@ -929,7 +939,41 @@ async function runInitCommand(options) {
|
|
|
929
939
|
...scannedContext.styling
|
|
930
940
|
].filter(Boolean).join(", ");
|
|
931
941
|
let projectStack = inferredStackString || "Node.js, TypeScript";
|
|
932
|
-
|
|
942
|
+
let bypassOnboarding = false;
|
|
943
|
+
const manifestData = await getManifest(cwd);
|
|
944
|
+
if (manifestData) {
|
|
945
|
+
if (manifestData.config) {
|
|
946
|
+
aiTargets = manifestData.config.aiTargets;
|
|
947
|
+
severity = manifestData.config.severity;
|
|
948
|
+
projectContext = manifestData.config.projectContext;
|
|
949
|
+
projectStack = manifestData.config.projectStack;
|
|
950
|
+
} else {
|
|
951
|
+
const agentsMdPath = path10.join(cwd, "AGENTS.md");
|
|
952
|
+
if (await fs10.pathExists(agentsMdPath)) {
|
|
953
|
+
const content = await fs10.readFile(agentsMdPath, "utf8");
|
|
954
|
+
const targetsMatch = content.match(/\*\*Targets:\*\* (.*)/);
|
|
955
|
+
if (targetsMatch) aiTargets = targetsMatch[1].split(",").map((s) => s.trim());
|
|
956
|
+
const severityMatch = content.match(/\*\*Severity:\*\* (.*)/);
|
|
957
|
+
if (severityMatch) severity = severityMatch[1].trim();
|
|
958
|
+
const contextMatch = content.match(/## Project Context\n([\s\S]*?)\n## Tech Stack/);
|
|
959
|
+
if (contextMatch) projectContext = contextMatch[1].trim();
|
|
960
|
+
const stackMatch = content.match(/## Tech Stack\n([\s\S]*?)\n## Golden Rule/);
|
|
961
|
+
if (stackMatch) projectStack = stackMatch[1].trim();
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (session.interactive) {
|
|
965
|
+
const bypass = await p2.confirm({
|
|
966
|
+
message: "Detectamos uma instala\xE7\xE3o anterior. Deseja manter as configura\xE7\xF5es atuais e pular o onboarding?",
|
|
967
|
+
initialValue: true
|
|
968
|
+
});
|
|
969
|
+
if (p2.isCancel(bypass)) {
|
|
970
|
+
p2.cancel("Inicializa\xE7\xE3o abortada.");
|
|
971
|
+
process.exit(0);
|
|
972
|
+
}
|
|
973
|
+
bypassOnboarding = bypass;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (session.interactive && !bypassOnboarding) {
|
|
933
977
|
try {
|
|
934
978
|
const config = await p2.group(
|
|
935
979
|
{
|
|
@@ -1282,10 +1326,10 @@ async function runUninstallCommand(options) {
|
|
|
1282
1326
|
p4.intro(pc4.bgRed(pc4.white(" Sauron Memory System - Desinstala\xE7\xE3o ")));
|
|
1283
1327
|
}
|
|
1284
1328
|
if (session.interactive) {
|
|
1285
|
-
const
|
|
1329
|
+
const confirm3 = await p4.confirm({
|
|
1286
1330
|
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
1331
|
});
|
|
1288
|
-
if (p4.isCancel(
|
|
1332
|
+
if (p4.isCancel(confirm3) || !confirm3) {
|
|
1289
1333
|
p4.cancel("Desinstala\xE7\xE3o abortada.");
|
|
1290
1334
|
process.exit(0);
|
|
1291
1335
|
}
|