legacy-squad 1.0.1 → 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/LICENSE +21 -21
- package/README.md +132 -84
- package/README.pt-br.md +187 -134
- package/dist/cli.mjs +194 -73
- package/dist/templates/claude-commands/architecture.md +2 -1
- package/dist/templates/claude-commands/business-rules.md +52 -52
- package/dist/templates/claude-commands/generate-mmp.md +3 -1
- package/dist/templates/claude-commands/generate-prs.md +2 -1
- package/dist/templates/claude-commands/generate-sdd.md +3 -1
- package/dist/templates/claude-commands/generate-specs.md +1 -1
- package/dist/templates/claude-commands/legacy-code.md +2 -1
- package/dist/templates/claude-commands/modernization.md +2 -1
- package/dist/templates/claude-commands/scan.md +1 -1
- package/dist/templates/claude-commands/security.md +2 -1
- package/package.json +59 -58
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
|
|
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
|
|
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
|
|
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) =>
|
|
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 =
|
|
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
|
|
378
|
+
import path4 from "node:path";
|
|
289
379
|
|
|
290
380
|
// packages/scanner/src/stack-detector.ts
|
|
291
|
-
import
|
|
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 ??
|
|
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:
|
|
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 =
|
|
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
|
|
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 ??
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
679
|
-
function
|
|
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 =
|
|
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) =>
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
1205
|
-
await
|
|
1206
|
-
const repoIndexPath =
|
|
1207
|
-
const findingsPath =
|
|
1208
|
-
const contextPacksPath =
|
|
1209
|
-
await
|
|
1210
|
-
await
|
|
1211
|
-
await
|
|
1212
|
-
const configDir =
|
|
1213
|
-
await
|
|
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.
|
|
1321
|
+
framework_version: "1.2.0"
|
|
1235
1322
|
};
|
|
1236
|
-
await
|
|
1237
|
-
|
|
1323
|
+
await writeFile2(
|
|
1324
|
+
path7.join(configDir, "project.yaml"),
|
|
1238
1325
|
this.toYaml(projectConfig),
|
|
1239
1326
|
"utf-8"
|
|
1240
1327
|
);
|
|
1241
|
-
await
|
|
1242
|
-
await
|
|
1243
|
-
await
|
|
1244
|
-
const claudeCommandsDir =
|
|
1245
|
-
await
|
|
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
|
|
1263
|
-
|
|
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 =
|
|
1298
|
-
const targetPath =
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1393
|
-
const bundledPath =
|
|
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 =
|
|
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.
|
|
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 =
|
|
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 =
|
|
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:
|
|
1472
|
-
const memoryDir =
|
|
1473
|
-
await
|
|
1474
|
-
await
|
|
1475
|
-
await
|
|
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: ${
|
|
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 =
|
|
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` —
|
|
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`)
|