legacy-squad 1.0.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.
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // apps/cli/src/index.ts
4
4
  import { Command } from "commander";
5
- import path8 from "node:path";
5
+ import path9 from "node:path";
6
6
  import { existsSync } from "node:fs";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
@@ -224,13 +224,89 @@ var ALL_AGENTS = [
224
224
  MODERNIZATION_AGENT
225
225
  ];
226
226
 
227
+ // packages/agents/src/findings-writer.ts
228
+ import path from "node:path";
229
+
230
+ // packages/core/src/paths.ts
231
+ function toPosix(p) {
232
+ return p.replace(/\\/g, "/");
233
+ }
234
+
235
+ // packages/agents/src/findings-writer.ts
236
+ var FINDINGS_DIR = "findings";
237
+ var INDEX_FILE = "index.json";
238
+ var FindingsWriter = class {
239
+ /**
240
+ * @param fs Porta de escrita injetada; implementação concreta na camada externa.
241
+ */
242
+ constructor(fs) {
243
+ this.fs = fs;
244
+ }
245
+ fs;
246
+ /**
247
+ * Particiona e grava os findings:
248
+ * - `findings/index.json`: lista slim (id, pillar, severity, title, priority);
249
+ * - `findings/<pilar>.json`: findings completos do pilar. Pilar sem achados
250
+ * não gera arquivo.
251
+ *
252
+ * O `index.json` é sempre gravado (mesmo vazio): é o ponto de entrada que
253
+ * sinaliza a nova estrutura para o `doctor.ts` e para os geradores.
254
+ *
255
+ * @param findings Findings consolidados pelo Compliance Engine.
256
+ * @param memoryDir Caminho de `.legacy-squad/memory` (nativo do SO; normalizado
257
+ * para POSIX na escrita — ver DA-006).
258
+ * @returns Promise resolvida quando todos os arquivos foram gravados.
259
+ */
260
+ async write(findings, memoryDir) {
261
+ const findingsDir = toPosix(path.join(memoryDir, FINDINGS_DIR));
262
+ await this.fs.mkdir(findingsDir);
263
+ const index = findings.map((finding) => ({
264
+ id: finding.id,
265
+ pillar: finding.pillar,
266
+ severity: finding.severity,
267
+ title: finding.title,
268
+ priority: finding.priority
269
+ }));
270
+ await this.fs.writeFile(
271
+ toPosix(path.join(findingsDir, INDEX_FILE)),
272
+ JSON.stringify(index, null, 2)
273
+ );
274
+ for (const [pillar, pillarFindings] of this.groupByPillar(findings)) {
275
+ await this.fs.writeFile(
276
+ toPosix(path.join(findingsDir, this.pillarToFileName(pillar))),
277
+ JSON.stringify(pillarFindings, null, 2)
278
+ );
279
+ }
280
+ }
281
+ /** Agrupa os findings por pilar, preservando a ordem de inserção. */
282
+ groupByPillar(findings) {
283
+ const byPillar = /* @__PURE__ */ new Map();
284
+ for (const finding of findings) {
285
+ const bucket = byPillar.get(finding.pillar);
286
+ if (bucket) {
287
+ bucket.push(finding);
288
+ } else {
289
+ byPillar.set(finding.pillar, [finding]);
290
+ }
291
+ }
292
+ return byPillar;
293
+ }
294
+ /**
295
+ * Converte o pilar (enum com `_`) no nome do arquivo (com `-`).
296
+ * Ex.: `legacy_code` → `legacy-code.json`.
297
+ */
298
+ pillarToFileName(pillar) {
299
+ return `${pillar.replace(/_/g, "-")}.json`;
300
+ }
301
+ };
302
+
227
303
  // packages/agents/src/installer.ts
228
- import { writeFile, mkdir, readFile as readFile2 } from "node:fs/promises";
229
- import path6 from "node:path";
304
+ import { writeFile as writeFile2, mkdir as mkdir2, readFile as readFile2 } from "node:fs/promises";
305
+ import path7 from "node:path";
230
306
 
231
307
  // packages/scanner/src/node-filesystem.ts
232
- import { readdir, readFile, stat } from "node:fs/promises";
233
- import path from "node:path";
308
+ import { readdir, readFile, stat, mkdir, writeFile } from "node:fs/promises";
309
+ import path2 from "node:path";
234
310
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
235
311
  "node_modules",
236
312
  ".git",
@@ -248,7 +324,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
248
324
  var NodeFileSystem = class {
249
325
  async readDir(dirPath) {
250
326
  const entries = await readdir(dirPath, { withFileTypes: true });
251
- return entries.filter((e) => !IGNORED_DIRS.has(e.name)).map((e) => path.join(dirPath, e.name));
327
+ return entries.filter((e) => !IGNORED_DIRS.has(e.name)).map((e) => path2.join(dirPath, e.name));
252
328
  }
253
329
  async readFile(filePath) {
254
330
  return readFile(filePath, "utf-8");
@@ -270,11 +346,25 @@ var NodeFileSystem = class {
270
346
  await this.walkForGlob(rootPath, pattern, results);
271
347
  return results;
272
348
  }
349
+ /**
350
+ * Garante a existência do diretório criando todos os intermediários.
351
+ * Idempotente — não lança se o diretório já existir.
352
+ */
353
+ async mkdir(dirPath) {
354
+ await mkdir(dirPath, { recursive: true });
355
+ }
356
+ /**
357
+ * Escreve (sobrescrevendo) o arquivo com `content` em UTF-8.
358
+ * Pré-condição: o diretório-pai deve existir — chame `mkdir` antes.
359
+ */
360
+ async writeFile(filePath, content) {
361
+ await writeFile(filePath, content, "utf-8");
362
+ }
273
363
  async walkForGlob(dir, pattern, results) {
274
364
  const entries = await readdir(dir, { withFileTypes: true });
275
365
  for (const entry of entries) {
276
366
  if (IGNORED_DIRS.has(entry.name)) continue;
277
- const fullPath = path.join(dir, entry.name);
367
+ const fullPath = path2.join(dir, entry.name);
278
368
  if (entry.isDirectory()) {
279
369
  await this.walkForGlob(fullPath, pattern, results);
280
370
  } else if (entry.name.match(pattern)) {
@@ -285,10 +375,10 @@ var NodeFileSystem = class {
285
375
  };
286
376
 
287
377
  // packages/scanner/src/repo-scanner.ts
288
- import path3 from "node:path";
378
+ import path4 from "node:path";
289
379
 
290
380
  // packages/scanner/src/stack-detector.ts
291
- import path2 from "node:path";
381
+ import path3 from "node:path";
292
382
  var packageJsonDetector = {
293
383
  filename: "package.json",
294
384
  detect(content, filePath) {
@@ -351,7 +441,7 @@ var packageJsonDetector = {
351
441
  stack,
352
442
  dependencies,
353
443
  projectType,
354
- projectName: pkg.name ?? path2.basename(path2.dirname(filePath))
444
+ projectName: pkg.name ?? path3.basename(path3.dirname(filePath))
355
445
  };
356
446
  }
357
447
  };
@@ -409,7 +499,7 @@ var csprojDetector = {
409
499
  stack.push({ name: "asp.net", type: "framework", version: match[2], source: filePath });
410
500
  }
411
501
  }
412
- return { stack, dependencies, projectType: "backend", projectName: path2.basename(filePath, ".csproj") };
502
+ return { stack, dependencies, projectType: "backend", projectName: path3.basename(filePath, ".csproj") };
413
503
  }
414
504
  };
415
505
  var pomXmlDetector = {
@@ -459,7 +549,7 @@ async function detectFromManifests(rootPath, fs) {
459
549
  }
460
550
  continue;
461
551
  }
462
- const manifestPath = path2.join(rootPath, detector.filename);
552
+ const manifestPath = path3.join(rootPath, detector.filename);
463
553
  if (await fs.exists(manifestPath)) {
464
554
  const content = await fs.readFile(manifestPath);
465
555
  return detector.detect(content, manifestPath);
@@ -488,7 +578,7 @@ async function detectFromExtensions(rootPath, fs) {
488
578
  }
489
579
 
490
580
  // packages/scanner/src/repo-scanner.ts
491
- function toPosix(p) {
581
+ function toPosix2(p) {
492
582
  return p.replace(/\\/g, "/");
493
583
  }
494
584
  var SOURCE_EXTENSIONS = /\.(tsx?|jsx?|php|cs|java|py|dart|vue|svelte)$/;
@@ -504,7 +594,7 @@ var RepoScanner = class {
504
594
  if (stack.length === 0) {
505
595
  stack = await detectFromExtensions(effectiveRoot, this.fs);
506
596
  }
507
- const projectName = manifestResult?.projectName ?? path3.basename(effectiveRoot);
597
+ const projectName = manifestResult?.projectName ?? path4.basename(effectiveRoot);
508
598
  const projectType = manifestResult?.projectType ?? "backend";
509
599
  const dependencies = manifestResult?.dependencies ?? [];
510
600
  const sourceFiles = await this.collectSourceFiles(effectiveRoot);
@@ -568,7 +658,7 @@ var RepoScanner = class {
568
658
  detectModules(rootPath, files) {
569
659
  const moduleMap = /* @__PURE__ */ new Map();
570
660
  for (const file of files) {
571
- const relative = toPosix(path3.relative(rootPath, file));
661
+ const relative = toPosix2(path4.relative(rootPath, file));
572
662
  const parts = relative.split("/");
573
663
  if (parts.length >= 2) {
574
664
  const moduleKey = parts.slice(0, 2).join("/");
@@ -578,7 +668,7 @@ var RepoScanner = class {
578
668
  }
579
669
  }
580
670
  return Array.from(moduleMap.entries()).map(([modulePath, moduleFiles]) => ({
581
- name: path3.basename(modulePath),
671
+ name: path4.basename(modulePath),
582
672
  path: modulePath,
583
673
  type: "module",
584
674
  filesCount: moduleFiles.length,
@@ -589,7 +679,7 @@ var RepoScanner = class {
589
679
  const entrypoints = [];
590
680
  if (projectType === "mobile") {
591
681
  for (const file of files) {
592
- const relative = toPosix(path3.relative(rootPath, file));
682
+ const relative = toPosix2(path4.relative(rootPath, file));
593
683
  if (relative.match(/src\/screens\/[^/]+\/index\.(tsx?|jsx?)$/)) {
594
684
  const screenName = relative.split("/").slice(-2, -1)[0];
595
685
  entrypoints.push({
@@ -602,11 +692,11 @@ var RepoScanner = class {
602
692
  }
603
693
  }
604
694
  for (const file of files) {
605
- const relative = toPosix(path3.relative(rootPath, file));
695
+ const relative = toPosix2(path4.relative(rootPath, file));
606
696
  if (relative.match(/^(index|main|App)\.(tsx?|jsx?|js)$/)) {
607
697
  entrypoints.push({
608
698
  type: "component",
609
- name: path3.basename(relative),
699
+ name: path4.basename(relative),
610
700
  path: relative,
611
701
  method: "main"
612
702
  });
@@ -620,7 +710,7 @@ var RepoScanner = class {
620
710
  for (const file of files) {
621
711
  try {
622
712
  const content = await this.fs.readFile(file);
623
- const relative = toPosix(path3.relative(rootPath, file));
713
+ const relative = toPosix2(path4.relative(rootPath, file));
624
714
  const urlMatches = content.matchAll(/https?:\/\/[^\s'"`,)]+/g);
625
715
  for (const match of urlMatches) {
626
716
  const url = match[0];
@@ -659,7 +749,7 @@ var RepoScanner = class {
659
749
  for (const file of files) {
660
750
  try {
661
751
  const s = await this.fs.stat(file);
662
- const relative = toPosix(path3.relative(rootPath, file));
752
+ const relative = toPosix2(path4.relative(rootPath, file));
663
753
  if (s.size > LARGE_FILE_THRESHOLD) {
664
754
  hotspots.push({
665
755
  path: relative,
@@ -675,8 +765,8 @@ var RepoScanner = class {
675
765
  };
676
766
 
677
767
  // packages/context/src/context-builder.ts
678
- import path4 from "node:path";
679
- function toPosix2(p) {
768
+ import path5 from "node:path";
769
+ function toPosix3(p) {
680
770
  return p.replace(/\\/g, "/");
681
771
  }
682
772
  var TOKENS_PER_CHAR = 0.25;
@@ -694,12 +784,12 @@ var ContextBuilder = class {
694
784
  return packs;
695
785
  }
696
786
  async buildModulePack(rootPath, moduleName, modulePath, repoIndex) {
697
- const fullModulePath = path4.join(rootPath, modulePath);
787
+ const fullModulePath = path5.join(rootPath, modulePath);
698
788
  let keyFiles = [];
699
789
  let totalSize = 0;
700
790
  try {
701
791
  const files = await this.fs.glob(fullModulePath, "\\.(tsx?|jsx?|php|cs|java)$");
702
- keyFiles = files.map((f) => toPosix2(path4.relative(rootPath, f))).slice(0, 10);
792
+ keyFiles = files.map((f) => toPosix3(path5.relative(rootPath, f))).slice(0, 10);
703
793
  for (const file of files.slice(0, 10)) {
704
794
  try {
705
795
  const stat3 = await this.fs.stat(file);
@@ -725,7 +815,7 @@ var ContextBuilder = class {
725
815
  };
726
816
 
727
817
  // packages/rules/src/compliance-engine.ts
728
- import path5 from "node:path";
818
+ import path6 from "node:path";
729
819
 
730
820
  // packages/rules/src/rule-catalog.ts
731
821
  var BACKEND_LANGUAGES = [
@@ -1029,9 +1119,6 @@ var CODE_QUALITY_RULES = [
1029
1119
  var ALL_RULES = [...SECURITY_RULES, ...CODE_QUALITY_RULES];
1030
1120
 
1031
1121
  // packages/rules/src/compliance-engine.ts
1032
- function toPosix3(p) {
1033
- return p.replace(/\\/g, "/");
1034
- }
1035
1122
  var SEVERITY_TO_PRIORITY = {
1036
1123
  critical: "P0",
1037
1124
  high: "P1",
@@ -1089,7 +1176,7 @@ var ComplianceEngine = class {
1089
1176
  try {
1090
1177
  const content = await this.fs.readFile(file);
1091
1178
  const lines = content.split("\n");
1092
- const relative = toPosix3(path5.relative(rootPath, file));
1179
+ const relative = toPosix(path6.relative(rootPath, file));
1093
1180
  for (const pattern of rule.detection.patterns) {
1094
1181
  const regex = new RegExp(pattern, "gi");
1095
1182
  for (let i = 0; i < lines.length; i++) {
@@ -1124,7 +1211,7 @@ var ComplianceEngine = class {
1124
1211
  for (const pattern of rule.detection.patterns) {
1125
1212
  const matchingFiles = await this.fs.glob(rootPath, pattern);
1126
1213
  for (const file of matchingFiles) {
1127
- const relative = toPosix3(path5.relative(rootPath, file));
1214
+ const relative = toPosix(path6.relative(rootPath, file));
1128
1215
  allEvidence.push({
1129
1216
  file: relative,
1130
1217
  line: 0,
@@ -1162,7 +1249,7 @@ var ComplianceEngine = class {
1162
1249
  pillar: rule.category,
1163
1250
  severity: rule.severity,
1164
1251
  evidence: jsFiles.slice(0, 5).map((f) => ({
1165
- file: toPosix3(path5.relative(rootPath, f)),
1252
+ file: toPosix(path6.relative(rootPath, f)),
1166
1253
  line: 0,
1167
1254
  snippet: "JavaScript file in a TypeScript project"
1168
1255
  })),
@@ -1201,16 +1288,16 @@ var Installer = class {
1201
1288
  const findings = await compliance.evaluate(effectiveRoot, repoIndex);
1202
1289
  const contextBuilder = new ContextBuilder(fs);
1203
1290
  const contextPacks = await contextBuilder.buildPacks(effectiveRoot, repoIndex);
1204
- const memoryDir = path6.join(effectiveRoot, ".legacy-squad", "memory");
1205
- await mkdir(memoryDir, { recursive: true });
1206
- const repoIndexPath = path6.join(memoryDir, "repo-index.json");
1207
- const findingsPath = path6.join(memoryDir, "findings.json");
1208
- const contextPacksPath = path6.join(memoryDir, "context-packs.json");
1209
- await writeFile(repoIndexPath, JSON.stringify(repoIndex, null, 2), "utf-8");
1210
- await writeFile(findingsPath, JSON.stringify(findings, null, 2), "utf-8");
1211
- await writeFile(contextPacksPath, JSON.stringify(contextPacks, null, 2), "utf-8");
1212
- const configDir = path6.join(effectiveRoot, ".legacy-squad", "config");
1213
- await mkdir(configDir, { recursive: true });
1291
+ const memoryDir = path7.join(effectiveRoot, ".legacy-squad", "memory");
1292
+ await mkdir2(memoryDir, { recursive: true });
1293
+ const repoIndexPath = path7.join(memoryDir, "repo-index.json");
1294
+ const findingsPath = path7.join(memoryDir, "findings", "index.json");
1295
+ const contextPacksPath = path7.join(memoryDir, "context-packs.json");
1296
+ await writeFile2(repoIndexPath, JSON.stringify(repoIndex, null, 2), "utf-8");
1297
+ await new FindingsWriter(fs).write(findings, memoryDir);
1298
+ await writeFile2(contextPacksPath, JSON.stringify(contextPacks, null, 2), "utf-8");
1299
+ const configDir = path7.join(effectiveRoot, ".legacy-squad", "config");
1300
+ await mkdir2(configDir, { recursive: true });
1214
1301
  const projectConfig = {
1215
1302
  project: {
1216
1303
  name: repoIndex.project.name,
@@ -1231,18 +1318,18 @@ var Installer = class {
1231
1318
  mode: { execution: "read_only" },
1232
1319
  ide: { primary: "claude-code" },
1233
1320
  installed_at: (/* @__PURE__ */ new Date()).toISOString(),
1234
- framework_version: "1.0.0"
1321
+ framework_version: "1.2.0"
1235
1322
  };
1236
- await writeFile(
1237
- path6.join(configDir, "project.yaml"),
1323
+ await writeFile2(
1324
+ path7.join(configDir, "project.yaml"),
1238
1325
  this.toYaml(projectConfig),
1239
1326
  "utf-8"
1240
1327
  );
1241
- await mkdir(path6.join(effectiveRoot, ".legacy-squad", "outputs", "reports"), { recursive: true });
1242
- await mkdir(path6.join(effectiveRoot, ".legacy-squad", "outputs", "assessments"), { recursive: true });
1243
- await mkdir(path6.join(effectiveRoot, ".legacy-squad", "logs"), { recursive: true });
1244
- const claudeCommandsDir = path6.join(effectiveRoot, ".claude", "commands", "legacy-squad");
1245
- await mkdir(claudeCommandsDir, { recursive: true });
1328
+ await mkdir2(path7.join(effectiveRoot, ".legacy-squad", "outputs", "reports"), { recursive: true });
1329
+ await mkdir2(path7.join(effectiveRoot, ".legacy-squad", "outputs", "assessments"), { recursive: true });
1330
+ await mkdir2(path7.join(effectiveRoot, ".legacy-squad", "logs"), { recursive: true });
1331
+ const claudeCommandsDir = path7.join(effectiveRoot, ".claude", "commands", "legacy-squad");
1332
+ await mkdir2(claudeCommandsDir, { recursive: true });
1246
1333
  await this.copySlashCommands(templateDir, claudeCommandsDir);
1247
1334
  await this.generateAgentsMd(effectiveRoot, repoIndex.project.name);
1248
1335
  const logContent = [
@@ -1259,8 +1346,8 @@ var Installer = class {
1259
1346
  `Requested root: ${projectRoot}`,
1260
1347
  `Effective root: ${effectiveRoot}`
1261
1348
  ].join("\n");
1262
- await writeFile(
1263
- path6.join(effectiveRoot, ".legacy-squad", "logs", "install.log"),
1349
+ await writeFile2(
1350
+ path7.join(effectiveRoot, ".legacy-squad", "logs", "install.log"),
1264
1351
  logContent,
1265
1352
  "utf-8"
1266
1353
  );
@@ -1294,11 +1381,11 @@ var Installer = class {
1294
1381
  "scan.md"
1295
1382
  ];
1296
1383
  for (const file of commandFiles) {
1297
- const sourcePath = path6.join(templateDir, file);
1298
- const targetPath = path6.join(targetDir, file);
1384
+ const sourcePath = path7.join(templateDir, file);
1385
+ const targetPath = path7.join(targetDir, file);
1299
1386
  try {
1300
1387
  const content = await readFile2(sourcePath, "utf-8");
1301
- await writeFile(targetPath, content, "utf-8");
1388
+ await writeFile2(targetPath, content, "utf-8");
1302
1389
  } catch {
1303
1390
  }
1304
1391
  }
@@ -1321,7 +1408,7 @@ var Installer = class {
1321
1408
  lines.push("Leia `.legacy-squad/memory/` para contexto e produza o assessment em `.legacy-squad/outputs/assessments/`.");
1322
1409
  lines.push("");
1323
1410
  }
1324
- await writeFile(path6.join(projectRoot, "AGENTS.md"), lines.join("\n"), "utf-8");
1411
+ await writeFile2(path7.join(projectRoot, "AGENTS.md"), lines.join("\n"), "utf-8");
1325
1412
  }
1326
1413
  toYaml(obj, indent = 0) {
1327
1414
  const lines = [];
@@ -1346,12 +1433,12 @@ var Installer = class {
1346
1433
 
1347
1434
  // packages/agents/src/doctor.ts
1348
1435
  import { stat as stat2 } from "node:fs/promises";
1349
- import path7 from "node:path";
1436
+ import path8 from "node:path";
1350
1437
  var Doctor = class {
1351
1438
  async check(projectRoot) {
1352
1439
  const checks = [];
1353
1440
  checks.push(await this.checkFile(projectRoot, ".legacy-squad/memory/repo-index.json", "Repo Index"));
1354
- checks.push(await this.checkFile(projectRoot, ".legacy-squad/memory/findings.json", "Findings"));
1441
+ checks.push(await this.checkFindingsStructure(projectRoot));
1355
1442
  checks.push(await this.checkFile(projectRoot, ".legacy-squad/memory/context-packs.json", "Context Packs"));
1356
1443
  checks.push(await this.checkFile(projectRoot, ".legacy-squad/config/project.yaml", "Project Config"));
1357
1444
  checks.push(await this.checkDir(projectRoot, ".claude/commands/legacy-squad", "Claude Code Agents"));
@@ -1360,9 +1447,43 @@ var Doctor = class {
1360
1447
  checks.push(await this.checkDir(projectRoot, ".legacy-squad/outputs/reports", "Reports Dir"));
1361
1448
  return checks;
1362
1449
  }
1450
+ /**
1451
+ * Verifica a estrutura de findings conforme DA-011:
1452
+ * - `findings/index.json` presente → ok (estrutura particionada)
1453
+ * - `findings.json` presente sem `findings/` → error com orientação de re-install
1454
+ * - Nenhum dos dois → error (não instalado)
1455
+ */
1456
+ async checkFindingsStructure(root) {
1457
+ const indexPath = path8.join(root, ".legacy-squad", "memory", "findings", "index.json");
1458
+ const legacyPath = path8.join(root, ".legacy-squad", "memory", "findings.json");
1459
+ if (await this.pathExists(indexPath)) {
1460
+ return { name: "Findings", status: "ok", message: ".legacy-squad/memory/findings/index.json \u2713" };
1461
+ }
1462
+ if (await this.pathExists(legacyPath)) {
1463
+ return {
1464
+ name: "Findings",
1465
+ status: "error",
1466
+ message: "Estrutura legada detectada (.legacy-squad/memory/findings.json). Execute npx legacy-squad install para regenerar na estrutura particionada (DA-011). A migra\xE7\xE3o n\xE3o \xE9 autom\xE1tica \u2014 nenhum dado foi alterado."
1467
+ };
1468
+ }
1469
+ return {
1470
+ name: "Findings",
1471
+ status: "error",
1472
+ message: ".legacy-squad/memory/findings/index.json not found"
1473
+ };
1474
+ }
1475
+ /** Retorna true se o path existir (arquivo ou diretório), false caso contrário. */
1476
+ async pathExists(fullPath) {
1477
+ try {
1478
+ await stat2(fullPath);
1479
+ return true;
1480
+ } catch {
1481
+ return false;
1482
+ }
1483
+ }
1363
1484
  async checkFile(root, relativePath, label) {
1364
1485
  try {
1365
- const fullPath = path7.join(root, relativePath);
1486
+ const fullPath = path8.join(root, relativePath);
1366
1487
  const s = await stat2(fullPath);
1367
1488
  if (s.size === 0) {
1368
1489
  return { name: label, status: "warning", message: `${relativePath} exists but is empty` };
@@ -1374,7 +1495,7 @@ var Doctor = class {
1374
1495
  }
1375
1496
  async checkDir(root, relativePath, label) {
1376
1497
  try {
1377
- const fullPath = path7.join(root, relativePath);
1498
+ const fullPath = path8.join(root, relativePath);
1378
1499
  const s = await stat2(fullPath);
1379
1500
  if (!s.isDirectory()) {
1380
1501
  return { name: label, status: "error", message: `${relativePath} is not a directory` };
@@ -1389,16 +1510,16 @@ var Doctor = class {
1389
1510
  // apps/cli/src/index.ts
1390
1511
  var program = new Command();
1391
1512
  function getTemplateDir() {
1392
- const cliDir = path8.dirname(fileURLToPath(import.meta.url));
1393
- const bundledPath = path8.resolve(cliDir, "templates", "claude-commands");
1513
+ const cliDir = path9.dirname(fileURLToPath(import.meta.url));
1514
+ const bundledPath = path9.resolve(cliDir, "templates", "claude-commands");
1394
1515
  if (existsSync(bundledPath)) return bundledPath;
1395
- const devPath = path8.resolve(cliDir, "..", "..", "..", "templates", "claude-commands");
1516
+ const devPath = path9.resolve(cliDir, "..", "..", "..", "templates", "claude-commands");
1396
1517
  if (existsSync(devPath)) return devPath;
1397
1518
  throw new Error("Templates not found. Run from the framework root or use the published package.");
1398
1519
  }
1399
- program.name("legacy-squad").description("AI-Powered Legacy Modernization Platform \u2014 Understand. Plan. Modernize.").version("1.0.0");
1520
+ program.name("legacy-squad").description("AI-Powered Legacy Modernization Platform \u2014 Understand. Plan. Modernize.").version("1.2.0");
1400
1521
  program.command("install").description("Install Legacy Squad Framework inside the current project").option("-p, --path <dir>", "Project root directory", ".").action(async (opts) => {
1401
- const projectRoot = path8.resolve(opts.path);
1522
+ const projectRoot = path9.resolve(opts.path);
1402
1523
  const templateDir = getTemplateDir();
1403
1524
  console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1404
1525
  console.log(" Legacy Squad Framework V1 \u2014 Install");
@@ -1454,7 +1575,7 @@ program.command("install").description("Install Legacy Squad Framework inside th
1454
1575
  console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
1455
1576
  });
1456
1577
  program.command("scan").description("Re-scan the project and update .legacy-squad/memory/").option("-p, --path <dir>", "Project root directory", ".").action(async (opts) => {
1457
- const projectRoot = path8.resolve(opts.path);
1578
+ const projectRoot = path9.resolve(opts.path);
1458
1579
  console.log(`
1459
1580
  \u{1F50D} Re-scanning: ${projectRoot}
1460
1581
  `);
@@ -1468,19 +1589,19 @@ program.command("scan").description("Re-scan the project and update .legacy-squa
1468
1589
  }
1469
1590
  const compliance = new ComplianceEngine(fs);
1470
1591
  const findings = await compliance.evaluate(effectiveRoot, repoIndex);
1471
- const { mkdir: mkdir2, writeFile: writeFile2 } = await import("node:fs/promises");
1472
- const memoryDir = path8.join(effectiveRoot, ".legacy-squad", "memory");
1473
- await mkdir2(memoryDir, { recursive: true });
1474
- await writeFile2(path8.join(memoryDir, "repo-index.json"), JSON.stringify(repoIndex, null, 2), "utf-8");
1475
- await writeFile2(path8.join(memoryDir, "findings.json"), JSON.stringify(findings, null, 2), "utf-8");
1592
+ const { mkdir: mkdir3, writeFile: writeFile3 } = await import("node:fs/promises");
1593
+ const memoryDir = path9.join(effectiveRoot, ".legacy-squad", "memory");
1594
+ await mkdir3(memoryDir, { recursive: true });
1595
+ await writeFile3(path9.join(memoryDir, "repo-index.json"), JSON.stringify(repoIndex, null, 2), "utf-8");
1596
+ await new FindingsWriter(fs).write(findings, memoryDir);
1476
1597
  console.log(`\u2705 Stack: ${repoIndex.stack.map((s) => s.name).join(", ")}`);
1477
1598
  console.log(`\u{1F4E6} Modules: ${repoIndex.modules.length}`);
1478
1599
  console.log(`\u{1F512} Findings: ${findings.length}`);
1479
- console.log(`\u{1F4C4} Updated: ${path8.join(effectiveRoot, ".legacy-squad", "memory")}
1600
+ console.log(`\u{1F4C4} Updated: ${path9.join(effectiveRoot, ".legacy-squad", "memory")}
1480
1601
  `);
1481
1602
  });
1482
1603
  program.command("doctor").description("Verify Legacy Squad installation health").option("-p, --path <dir>", "Project root directory", ".").action(async (opts) => {
1483
- const projectRoot = path8.resolve(opts.path);
1604
+ const projectRoot = path9.resolve(opts.path);
1484
1605
  console.log("\n\u{1FA7A} Legacy Squad Doctor\n");
1485
1606
  const doctor = new Doctor();
1486
1607
  const checks = await doctor.check(projectRoot);
@@ -3,7 +3,8 @@ Você é o **Architecture Agent** do Legacy Squad Framework.
3
3
  ## Contexto
4
4
  Leia estes arquivos para entender o projeto:
5
5
  - `.legacy-squad/memory/repo-index.json` — inventário do repositório
6
- - `.legacy-squad/memory/findings.json` — achados do compliance engine
6
+ - `.legacy-squad/memory/findings/index.json` — índice slim de todos os achados
7
+ - `.legacy-squad/memory/findings/architecture.json` — achados de arquitetura com evidência completa (se existir)
7
8
  - `.legacy-squad/memory/context-packs.json` — resumo dos módulos
8
9
 
9
10
  ## Sua Missão
@@ -1,52 +1,52 @@
1
- Você é o **Business Rules Agent** do Legacy Squad Framework.
2
-
3
- ## Contexto
4
- Leia estes arquivos para entender o projeto:
5
- - `.legacy-squad/memory/repo-index.json` — inventário do repositório
6
- - `.legacy-squad/memory/context-packs.json` — resumo dos módulos
7
-
8
- ## Sua Missão
9
- Extrair regras de negócio escondidas no código. Sistemas legados frequentemente têm lógica crítica enterrada em condicionais, validações e tratamento de erros que nunca foi documentada.
10
-
11
- 1. **Regras explícitas** — validações, permissões, fluxos visíveis
12
- 2. **Regras implícitas** — condicionais obscuros, magic numbers, comportamentos em catch blocks
13
- 3. **Modelo de domínio** — entidades principais e seus relacionamentos
14
- 4. **Fluxos de negócio** — jornadas do usuário codificadas no sistema
15
- 5. **Regras que devem ser preservadas** — lógica que não pode mudar na modernização
16
-
17
- ## Stack-aware analysis
18
-
19
- Antes de analisar, leia `repo-index.json` e identifique a stack. Adapte onde procurar:
20
-
21
- - **PHP / Laravel / Symfony**: olhe FormRequests/Validators (regras explícitas de input), Controllers (lógica de fluxo), Models/Eloquent (relacionamentos, scopes, accessors), Service classes, Middleware (regras de autorização), Policies/Gates.
22
- - **.NET / ASP.NET Core**: olhe DataAnnotations e FluentValidation (validações), Controllers/Minimal APIs, Services, EF Core entities (constraints, relationships), Authorization Policies, Filters (regras transversais).
23
- - **Java / Spring Boot**: olhe Bean Validation (`@NotNull`, `@Pattern`, `@Valid`), `@RestController` methods, `@Service` classes, JPA entities (`@Entity`, `@OneToMany`, lifecycle callbacks), `@PreAuthorize/@Secured`, Aspects.
24
- - **React Native / mobile**: olhe screens com lógica de submissão, hooks/services com validações, state stores (regras de transição de estado), middleware de API (autorização do cliente), formulários e suas validações inline.
25
- - **Node backend**: olhe middlewares de validação (Joi, Zod, class-validator), controllers/handlers, services, schemas de banco (Mongoose, Sequelize, Prisma — constraints e hooks).
26
-
27
- ## Padrões a procurar (independente da stack)
28
-
29
- - `switch/case` ou `if/else if/else` em cadeia com nomes de operações ou status — fluxos de máquina de estado escondidos
30
- - `ifs` aninhados com condições compostas — regras de negócio camufladas
31
- - Magic numbers e magic strings (e.g., `if (status == 3)`, `if (tipo == "PJ")`) — devem virar enums/constantes nomeadas
32
- - Lookups em arrays/maps hardcoded — tabelas de domínio que poderiam ser configuráveis
33
- - Try/catch que silencia erro mas faz uma ação alternativa — fluxo de negócio em fallback
34
- - Cálculos com fórmulas literais — regras tarifárias/de cálculo que ninguém entende mais
35
-
36
- ## Output
37
- Salve em: `.legacy-squad/outputs/assessments/business-rules-assessment.md`
38
-
39
- Estrutura:
40
- 1. **Business Domain Overview** (entidades principais, glossário de domínio extraído do código)
41
- 2. **Extracted Business Rules** (tabela: ID, regra, arquivo, linha, tipo explícito/implícito)
42
- 3. **Validation Rules Catalog** (consolidado por campo/entidade)
43
- 4. **Permission Model** (quem pode fazer o quê — extraído de middlewares, policies, guards)
44
- 5. **Implicit Rules** (regras escondidas em código — magic numbers, condicionais sem documentação)
45
- 6. **Rules Preservation Checklist for Modernization** (lista do que NÃO pode mudar)
46
-
47
- ## Regras
48
- - Toda regra extraída deve citar arquivo e linha
49
- - Distinga regras de negócio de detalhes técnicos de implementação
50
- - Sinalize regras que parecem acidentais vs intencionais
51
- - Use linguagem de domínio (a do negócio do projeto), não jargão técnico
52
- - Quando achar magic numbers/strings, sugira o nome da constante apropriado (e.g., `STATUS_APROVADO` em vez de `3`)
1
+ Você é o **Business Rules Agent** do Legacy Squad Framework.
2
+
3
+ ## Contexto
4
+ Leia estes arquivos para entender o projeto:
5
+ - `.legacy-squad/memory/repo-index.json` — inventário do repositório
6
+ - `.legacy-squad/memory/context-packs.json` — resumo dos módulos
7
+
8
+ ## Sua Missão
9
+ Extrair regras de negócio escondidas no código. Sistemas legados frequentemente têm lógica crítica enterrada em condicionais, validações e tratamento de erros que nunca foi documentada.
10
+
11
+ 1. **Regras explícitas** — validações, permissões, fluxos visíveis
12
+ 2. **Regras implícitas** — condicionais obscuros, magic numbers, comportamentos em catch blocks
13
+ 3. **Modelo de domínio** — entidades principais e seus relacionamentos
14
+ 4. **Fluxos de negócio** — jornadas do usuário codificadas no sistema
15
+ 5. **Regras que devem ser preservadas** — lógica que não pode mudar na modernização
16
+
17
+ ## Stack-aware analysis
18
+
19
+ Antes de analisar, leia `repo-index.json` e identifique a stack. Adapte onde procurar:
20
+
21
+ - **PHP / Laravel / Symfony**: olhe FormRequests/Validators (regras explícitas de input), Controllers (lógica de fluxo), Models/Eloquent (relacionamentos, scopes, accessors), Service classes, Middleware (regras de autorização), Policies/Gates.
22
+ - **.NET / ASP.NET Core**: olhe DataAnnotations e FluentValidation (validações), Controllers/Minimal APIs, Services, EF Core entities (constraints, relationships), Authorization Policies, Filters (regras transversais).
23
+ - **Java / Spring Boot**: olhe Bean Validation (`@NotNull`, `@Pattern`, `@Valid`), `@RestController` methods, `@Service` classes, JPA entities (`@Entity`, `@OneToMany`, lifecycle callbacks), `@PreAuthorize/@Secured`, Aspects.
24
+ - **React Native / mobile**: olhe screens com lógica de submissão, hooks/services com validações, state stores (regras de transição de estado), middleware de API (autorização do cliente), formulários e suas validações inline.
25
+ - **Node backend**: olhe middlewares de validação (Joi, Zod, class-validator), controllers/handlers, services, schemas de banco (Mongoose, Sequelize, Prisma — constraints e hooks).
26
+
27
+ ## Padrões a procurar (independente da stack)
28
+
29
+ - `switch/case` ou `if/else if/else` em cadeia com nomes de operações ou status — fluxos de máquina de estado escondidos
30
+ - `ifs` aninhados com condições compostas — regras de negócio camufladas
31
+ - Magic numbers e magic strings (e.g., `if (status == 3)`, `if (tipo == "PJ")`) — devem virar enums/constantes nomeadas
32
+ - Lookups em arrays/maps hardcoded — tabelas de domínio que poderiam ser configuráveis
33
+ - Try/catch que silencia erro mas faz uma ação alternativa — fluxo de negócio em fallback
34
+ - Cálculos com fórmulas literais — regras tarifárias/de cálculo que ninguém entende mais
35
+
36
+ ## Output
37
+ Salve em: `.legacy-squad/outputs/assessments/business-rules-assessment.md`
38
+
39
+ Estrutura:
40
+ 1. **Business Domain Overview** (entidades principais, glossário de domínio extraído do código)
41
+ 2. **Extracted Business Rules** (tabela: ID, regra, arquivo, linha, tipo explícito/implícito)
42
+ 3. **Validation Rules Catalog** (consolidado por campo/entidade)
43
+ 4. **Permission Model** (quem pode fazer o quê — extraído de middlewares, policies, guards)
44
+ 5. **Implicit Rules** (regras escondidas em código — magic numbers, condicionais sem documentação)
45
+ 6. **Rules Preservation Checklist for Modernization** (lista do que NÃO pode mudar)
46
+
47
+ ## Regras
48
+ - Toda regra extraída deve citar arquivo e linha
49
+ - Distinga regras de negócio de detalhes técnicos de implementação
50
+ - Sinalize regras que parecem acidentais vs intencionais
51
+ - Use linguagem de domínio (a do negócio do projeto), não jargão técnico
52
+ - Quando achar magic numbers/strings, sugira o nome da constante apropriado (e.g., `STATUS_APROVADO` em vez de `3`)