legacy-squad 1.0.1 → 1.3.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 +495 -82
- 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/legacy-squad.md +38 -0
- 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 path10 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,19 +1318,21 @@ 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.3.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
|
|
1245
|
-
|
|
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 claudeCommandsRoot = path7.join(effectiveRoot, ".claude", "commands");
|
|
1332
|
+
const claudeCommandsDir = path7.join(claudeCommandsRoot, "legacy-squad");
|
|
1333
|
+
await mkdir2(claudeCommandsDir, { recursive: true });
|
|
1246
1334
|
await this.copySlashCommands(templateDir, claudeCommandsDir);
|
|
1335
|
+
await this.copyOrchestrator(templateDir, claudeCommandsRoot);
|
|
1247
1336
|
await this.generateAgentsMd(effectiveRoot, repoIndex.project.name);
|
|
1248
1337
|
const logContent = [
|
|
1249
1338
|
`Legacy Squad Framework \u2014 Install Log`,
|
|
@@ -1259,8 +1348,8 @@ var Installer = class {
|
|
|
1259
1348
|
`Requested root: ${projectRoot}`,
|
|
1260
1349
|
`Effective root: ${effectiveRoot}`
|
|
1261
1350
|
].join("\n");
|
|
1262
|
-
await
|
|
1263
|
-
|
|
1351
|
+
await writeFile2(
|
|
1352
|
+
path7.join(effectiveRoot, ".legacy-squad", "logs", "install.log"),
|
|
1264
1353
|
logContent,
|
|
1265
1354
|
"utf-8"
|
|
1266
1355
|
);
|
|
@@ -1294,15 +1383,30 @@ var Installer = class {
|
|
|
1294
1383
|
"scan.md"
|
|
1295
1384
|
];
|
|
1296
1385
|
for (const file of commandFiles) {
|
|
1297
|
-
const sourcePath =
|
|
1298
|
-
const targetPath =
|
|
1386
|
+
const sourcePath = path7.join(templateDir, file);
|
|
1387
|
+
const targetPath = path7.join(targetDir, file);
|
|
1299
1388
|
try {
|
|
1300
1389
|
const content = await readFile2(sourcePath, "utf-8");
|
|
1301
|
-
await
|
|
1390
|
+
await writeFile2(targetPath, content, "utf-8");
|
|
1302
1391
|
} catch {
|
|
1303
1392
|
}
|
|
1304
1393
|
}
|
|
1305
1394
|
}
|
|
1395
|
+
/**
|
|
1396
|
+
* DA-012: copia o template do orchestrator para `.claude/commands/legacy-squad.md`
|
|
1397
|
+
* (raiz de commands, não subdir). No Claude Code, arquivos na raiz de commands viram
|
|
1398
|
+
* o comando puro `/<filename>` — então este caminho expressa `/legacy-squad` (validado
|
|
1399
|
+
* emp.). Falha silenciosamente se o template não existir, no mesmo estilo de copySlashCommands.
|
|
1400
|
+
*/
|
|
1401
|
+
async copyOrchestrator(templateDir, commandsRoot) {
|
|
1402
|
+
const sourcePath = path7.join(templateDir, "legacy-squad.md");
|
|
1403
|
+
const targetPath = path7.join(commandsRoot, "legacy-squad.md");
|
|
1404
|
+
try {
|
|
1405
|
+
const content = await readFile2(sourcePath, "utf-8");
|
|
1406
|
+
await writeFile2(targetPath, content, "utf-8");
|
|
1407
|
+
} catch {
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1306
1410
|
async generateAgentsMd(projectRoot, projectName) {
|
|
1307
1411
|
const lines = [
|
|
1308
1412
|
`# Legacy Squad Agents \u2014 ${projectName}`,
|
|
@@ -1321,7 +1425,7 @@ var Installer = class {
|
|
|
1321
1425
|
lines.push("Leia `.legacy-squad/memory/` para contexto e produza o assessment em `.legacy-squad/outputs/assessments/`.");
|
|
1322
1426
|
lines.push("");
|
|
1323
1427
|
}
|
|
1324
|
-
await
|
|
1428
|
+
await writeFile2(path7.join(projectRoot, "AGENTS.md"), lines.join("\n"), "utf-8");
|
|
1325
1429
|
}
|
|
1326
1430
|
toYaml(obj, indent = 0) {
|
|
1327
1431
|
const lines = [];
|
|
@@ -1346,23 +1450,58 @@ var Installer = class {
|
|
|
1346
1450
|
|
|
1347
1451
|
// packages/agents/src/doctor.ts
|
|
1348
1452
|
import { stat as stat2 } from "node:fs/promises";
|
|
1349
|
-
import
|
|
1453
|
+
import path8 from "node:path";
|
|
1350
1454
|
var Doctor = class {
|
|
1351
1455
|
async check(projectRoot) {
|
|
1352
1456
|
const checks = [];
|
|
1353
1457
|
checks.push(await this.checkFile(projectRoot, ".legacy-squad/memory/repo-index.json", "Repo Index"));
|
|
1354
|
-
checks.push(await this.
|
|
1458
|
+
checks.push(await this.checkFindingsStructure(projectRoot));
|
|
1355
1459
|
checks.push(await this.checkFile(projectRoot, ".legacy-squad/memory/context-packs.json", "Context Packs"));
|
|
1356
1460
|
checks.push(await this.checkFile(projectRoot, ".legacy-squad/config/project.yaml", "Project Config"));
|
|
1357
1461
|
checks.push(await this.checkDir(projectRoot, ".claude/commands/legacy-squad", "Claude Code Agents"));
|
|
1462
|
+
checks.push(await this.checkFile(projectRoot, ".claude/commands/legacy-squad.md", "Orchestrator /legacy-squad"));
|
|
1358
1463
|
checks.push(await this.checkFile(projectRoot, "AGENTS.md", "Codex AGENTS.md"));
|
|
1359
1464
|
checks.push(await this.checkDir(projectRoot, ".legacy-squad/outputs/assessments", "Assessments Dir"));
|
|
1360
1465
|
checks.push(await this.checkDir(projectRoot, ".legacy-squad/outputs/reports", "Reports Dir"));
|
|
1361
1466
|
return checks;
|
|
1362
1467
|
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Verifica a estrutura de findings conforme DA-011:
|
|
1470
|
+
* - `findings/index.json` presente → ok (estrutura particionada)
|
|
1471
|
+
* - `findings.json` presente sem `findings/` → error com orientação de re-install
|
|
1472
|
+
* - Nenhum dos dois → error (não instalado)
|
|
1473
|
+
*/
|
|
1474
|
+
async checkFindingsStructure(root) {
|
|
1475
|
+
const indexPath = path8.join(root, ".legacy-squad", "memory", "findings", "index.json");
|
|
1476
|
+
const legacyPath = path8.join(root, ".legacy-squad", "memory", "findings.json");
|
|
1477
|
+
if (await this.pathExists(indexPath)) {
|
|
1478
|
+
return { name: "Findings", status: "ok", message: ".legacy-squad/memory/findings/index.json \u2713" };
|
|
1479
|
+
}
|
|
1480
|
+
if (await this.pathExists(legacyPath)) {
|
|
1481
|
+
return {
|
|
1482
|
+
name: "Findings",
|
|
1483
|
+
status: "error",
|
|
1484
|
+
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."
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
return {
|
|
1488
|
+
name: "Findings",
|
|
1489
|
+
status: "error",
|
|
1490
|
+
message: ".legacy-squad/memory/findings/index.json not found"
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
/** Retorna true se o path existir (arquivo ou diretório), false caso contrário. */
|
|
1494
|
+
async pathExists(fullPath) {
|
|
1495
|
+
try {
|
|
1496
|
+
await stat2(fullPath);
|
|
1497
|
+
return true;
|
|
1498
|
+
} catch {
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1363
1502
|
async checkFile(root, relativePath, label) {
|
|
1364
1503
|
try {
|
|
1365
|
-
const fullPath =
|
|
1504
|
+
const fullPath = path8.join(root, relativePath);
|
|
1366
1505
|
const s = await stat2(fullPath);
|
|
1367
1506
|
if (s.size === 0) {
|
|
1368
1507
|
return { name: label, status: "warning", message: `${relativePath} exists but is empty` };
|
|
@@ -1374,7 +1513,7 @@ var Doctor = class {
|
|
|
1374
1513
|
}
|
|
1375
1514
|
async checkDir(root, relativePath, label) {
|
|
1376
1515
|
try {
|
|
1377
|
-
const fullPath =
|
|
1516
|
+
const fullPath = path8.join(root, relativePath);
|
|
1378
1517
|
const s = await stat2(fullPath);
|
|
1379
1518
|
if (!s.isDirectory()) {
|
|
1380
1519
|
return { name: label, status: "error", message: `${relativePath} is not a directory` };
|
|
@@ -1386,19 +1525,281 @@ var Doctor = class {
|
|
|
1386
1525
|
}
|
|
1387
1526
|
};
|
|
1388
1527
|
|
|
1528
|
+
// packages/agents/src/lifecycle-detector.ts
|
|
1529
|
+
import path9 from "node:path";
|
|
1530
|
+
var REPO_INDEX_PATH = ".legacy-squad/memory/repo-index.json";
|
|
1531
|
+
var FINDINGS_INDEX_PATH = ".legacy-squad/memory/findings/index.json";
|
|
1532
|
+
var assessmentPath = (slug) => `.legacy-squad/outputs/assessments/${slug}-assessment.md`;
|
|
1533
|
+
var PRS_PATH = ".legacy-squad/outputs/reports/PRS.md";
|
|
1534
|
+
var SDD_PATH = ".legacy-squad/outputs/sdd/SDD.md";
|
|
1535
|
+
var MMP_PATH = ".legacy-squad/outputs/mmp/MMP.md";
|
|
1536
|
+
var SPECS_PATH = ".legacy-squad/outputs/specs/INDEX.md";
|
|
1537
|
+
var MATURITY_LABELS = {
|
|
1538
|
+
1: "Unknown",
|
|
1539
|
+
2: "Understood",
|
|
1540
|
+
3: "Assessed",
|
|
1541
|
+
4: "Planned",
|
|
1542
|
+
5: "Modernization Ready",
|
|
1543
|
+
6: "Continuously Modernized"
|
|
1544
|
+
};
|
|
1545
|
+
var STEPS = [
|
|
1546
|
+
{
|
|
1547
|
+
id: "scan",
|
|
1548
|
+
label: "Scan",
|
|
1549
|
+
command: null,
|
|
1550
|
+
phase: "discovery",
|
|
1551
|
+
target: REPO_INDEX_PATH,
|
|
1552
|
+
reason: "Rode `npx legacy-squad install` para escanear o projeto e gerar o repo-index."
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
id: "security",
|
|
1556
|
+
label: "Security assessment",
|
|
1557
|
+
command: "/legacy-squad:security",
|
|
1558
|
+
phase: "assessment",
|
|
1559
|
+
target: assessmentPath("security"),
|
|
1560
|
+
reason: "Inicie os assessments rodando `/legacy-squad:security`."
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
id: "architecture",
|
|
1564
|
+
label: "Architecture assessment",
|
|
1565
|
+
command: "/legacy-squad:architecture",
|
|
1566
|
+
phase: "assessment",
|
|
1567
|
+
target: assessmentPath("architecture"),
|
|
1568
|
+
reason: "Rode `/legacy-squad:architecture`."
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
id: "legacy-code",
|
|
1572
|
+
label: "Legacy code assessment",
|
|
1573
|
+
command: "/legacy-squad:legacy-code",
|
|
1574
|
+
phase: "assessment",
|
|
1575
|
+
target: assessmentPath("legacy-code"),
|
|
1576
|
+
reason: "Rode `/legacy-squad:legacy-code`."
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
id: "business-rules",
|
|
1580
|
+
label: "Business rules assessment",
|
|
1581
|
+
command: "/legacy-squad:business-rules",
|
|
1582
|
+
phase: "assessment",
|
|
1583
|
+
target: assessmentPath("business-rules"),
|
|
1584
|
+
reason: "Rode `/legacy-squad:business-rules`."
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
id: "modernization",
|
|
1588
|
+
label: "Modernization assessment",
|
|
1589
|
+
command: "/legacy-squad:modernization",
|
|
1590
|
+
phase: "assessment",
|
|
1591
|
+
target: assessmentPath("modernization"),
|
|
1592
|
+
reason: "Rode `/legacy-squad:modernization`."
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
id: "generate-prs",
|
|
1596
|
+
label: "PRS",
|
|
1597
|
+
command: "/legacy-squad:generate-prs",
|
|
1598
|
+
phase: "assessment",
|
|
1599
|
+
target: PRS_PATH,
|
|
1600
|
+
reason: "Consolide o diagn\xF3stico com `/legacy-squad:generate-prs`."
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
id: "generate-sdd",
|
|
1604
|
+
label: "SDD",
|
|
1605
|
+
command: "/legacy-squad:generate-sdd",
|
|
1606
|
+
phase: "design",
|
|
1607
|
+
target: SDD_PATH,
|
|
1608
|
+
reason: "Gere o desenho t\xE9cnico com `/legacy-squad:generate-sdd`."
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
id: "generate-mmp",
|
|
1612
|
+
label: "MMP",
|
|
1613
|
+
command: "/legacy-squad:generate-mmp",
|
|
1614
|
+
phase: "planning",
|
|
1615
|
+
target: MMP_PATH,
|
|
1616
|
+
reason: "Gere o plano mestre com `/legacy-squad:generate-mmp`."
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
id: "generate-specs",
|
|
1620
|
+
label: "Execution Specs",
|
|
1621
|
+
command: "/legacy-squad:generate-specs",
|
|
1622
|
+
phase: "execution",
|
|
1623
|
+
target: SPECS_PATH,
|
|
1624
|
+
reason: "Decomponha em specs com `/legacy-squad:generate-specs`."
|
|
1625
|
+
}
|
|
1626
|
+
];
|
|
1627
|
+
var PHASE_ORDER = [
|
|
1628
|
+
{ id: "discovery", label: "Discovery" },
|
|
1629
|
+
{ id: "assessment", label: "Assessment" },
|
|
1630
|
+
{ id: "design", label: "Design" },
|
|
1631
|
+
{ id: "planning", label: "Planning" },
|
|
1632
|
+
{ id: "execution", label: "Execution" }
|
|
1633
|
+
];
|
|
1634
|
+
var ASSESSMENT_STEP_IDS = [
|
|
1635
|
+
"security",
|
|
1636
|
+
"architecture",
|
|
1637
|
+
"legacy-code",
|
|
1638
|
+
"business-rules",
|
|
1639
|
+
"modernization"
|
|
1640
|
+
];
|
|
1641
|
+
var LifecycleDetector = class {
|
|
1642
|
+
constructor(fs) {
|
|
1643
|
+
this.fs = fs;
|
|
1644
|
+
}
|
|
1645
|
+
fs;
|
|
1646
|
+
/** Detecta e retorna o snapshot determinístico do lifecycle em `projectRoot`. */
|
|
1647
|
+
async detect(projectRoot) {
|
|
1648
|
+
const done = /* @__PURE__ */ new Map();
|
|
1649
|
+
for (const step of STEPS) {
|
|
1650
|
+
done.set(step.id, await this.fs.exists(path9.join(projectRoot, step.target)));
|
|
1651
|
+
}
|
|
1652
|
+
const installed = done.get("scan") === true;
|
|
1653
|
+
const project = installed ? await this.readProject(projectRoot) : null;
|
|
1654
|
+
const findingCount = installed ? await this.readFindingCount(projectRoot) : 0;
|
|
1655
|
+
const maturityLevel = this.computeMaturity(done);
|
|
1656
|
+
return {
|
|
1657
|
+
project,
|
|
1658
|
+
installed,
|
|
1659
|
+
findingCount,
|
|
1660
|
+
maturityLevel,
|
|
1661
|
+
maturityLabel: MATURITY_LABELS[maturityLevel],
|
|
1662
|
+
phases: this.buildPhases(done),
|
|
1663
|
+
nextStep: this.computeNextStep(done),
|
|
1664
|
+
complete: done.get("generate-specs") === true
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
/** Agrupa os passos por fase, calculando a contagem de concluídos. */
|
|
1668
|
+
buildPhases(done) {
|
|
1669
|
+
return PHASE_ORDER.map(({ id, label }) => {
|
|
1670
|
+
const steps = STEPS.filter((s) => s.phase === id).map((s) => ({
|
|
1671
|
+
id: s.id,
|
|
1672
|
+
label: s.label,
|
|
1673
|
+
command: s.command,
|
|
1674
|
+
done: done.get(s.id) === true
|
|
1675
|
+
}));
|
|
1676
|
+
return {
|
|
1677
|
+
id,
|
|
1678
|
+
label,
|
|
1679
|
+
steps,
|
|
1680
|
+
doneCount: steps.filter((s) => s.done).length,
|
|
1681
|
+
totalCount: steps.length
|
|
1682
|
+
};
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
/** Deriva o Maturity Level (§10) a partir dos artefatos presentes. */
|
|
1686
|
+
computeMaturity(done) {
|
|
1687
|
+
if (done.get("generate-specs")) return 5;
|
|
1688
|
+
if (done.get("generate-mmp")) return 4;
|
|
1689
|
+
if (ASSESSMENT_STEP_IDS.every((id) => done.get(id))) return 3;
|
|
1690
|
+
if (done.get("scan")) return 2;
|
|
1691
|
+
return 1;
|
|
1692
|
+
}
|
|
1693
|
+
/** Próximo passo = primeiro `target` ausente na ordem canônica; null se tudo concluído. */
|
|
1694
|
+
computeNextStep(done) {
|
|
1695
|
+
const [scanStep] = STEPS;
|
|
1696
|
+
if (!done.get("scan")) {
|
|
1697
|
+
return { id: "install", command: null, reason: scanStep.reason };
|
|
1698
|
+
}
|
|
1699
|
+
for (const step of STEPS) {
|
|
1700
|
+
if (!done.get(step.id)) {
|
|
1701
|
+
return { id: step.id, command: step.command, reason: step.reason };
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return null;
|
|
1705
|
+
}
|
|
1706
|
+
/** Lê nome, tipo e stack do repo-index; null se ausente ou ilegível. */
|
|
1707
|
+
async readProject(root) {
|
|
1708
|
+
try {
|
|
1709
|
+
const raw = await this.fs.readFile(path9.join(root, REPO_INDEX_PATH));
|
|
1710
|
+
const idx = JSON.parse(raw);
|
|
1711
|
+
return {
|
|
1712
|
+
name: idx.project?.name ?? "unknown",
|
|
1713
|
+
type: idx.project?.type ?? "unknown",
|
|
1714
|
+
stack: (idx.stack ?? []).map((s) => s.name ?? "").filter((n) => n.length > 0)
|
|
1715
|
+
};
|
|
1716
|
+
} catch {
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
/** Conta os achados no index slim (DA-011); 0 se ausente ou ilegível. */
|
|
1721
|
+
async readFindingCount(root) {
|
|
1722
|
+
try {
|
|
1723
|
+
const raw = await this.fs.readFile(path9.join(root, FINDINGS_INDEX_PATH));
|
|
1724
|
+
const parsed = JSON.parse(raw);
|
|
1725
|
+
return Array.isArray(parsed) ? parsed.length : 0;
|
|
1726
|
+
} catch {
|
|
1727
|
+
return 0;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1732
|
+
// packages/output/src/dashboard-renderer.ts
|
|
1733
|
+
var RULE = "\u2501".repeat(40);
|
|
1734
|
+
var STEP_DISPLAY = {
|
|
1735
|
+
scan: "scan",
|
|
1736
|
+
security: "security",
|
|
1737
|
+
architecture: "architecture",
|
|
1738
|
+
"legacy-code": "legacy-code",
|
|
1739
|
+
"business-rules": "business-rules",
|
|
1740
|
+
modernization: "modernization",
|
|
1741
|
+
"generate-prs": "PRS",
|
|
1742
|
+
"generate-sdd": "SDD",
|
|
1743
|
+
"generate-mmp": "MMP",
|
|
1744
|
+
"generate-specs": "Specs"
|
|
1745
|
+
};
|
|
1746
|
+
var display = (id) => STEP_DISPLAY[id] ?? id;
|
|
1747
|
+
function phaseIcon(phase) {
|
|
1748
|
+
if (phase.totalCount > 0 && phase.doneCount === phase.totalCount) return "\u2713";
|
|
1749
|
+
if (phase.doneCount > 0) return "\u25D0";
|
|
1750
|
+
return "\xB7";
|
|
1751
|
+
}
|
|
1752
|
+
function phaseDetail(phase) {
|
|
1753
|
+
if (phase.steps.length === 1) {
|
|
1754
|
+
return display(phase.steps[0].id);
|
|
1755
|
+
}
|
|
1756
|
+
const names = phase.steps.map((s) => display(s.id)).join(" \xB7 ");
|
|
1757
|
+
return `${phase.doneCount}/${phase.totalCount} ${names}`;
|
|
1758
|
+
}
|
|
1759
|
+
var DashboardRenderer = class {
|
|
1760
|
+
render(snapshot) {
|
|
1761
|
+
const lines = [RULE];
|
|
1762
|
+
if (snapshot.project) {
|
|
1763
|
+
const stack = snapshot.project.stack.join(", ");
|
|
1764
|
+
lines.push(` Legacy Squad \u2014 ${snapshot.project.name}`);
|
|
1765
|
+
lines.push(` ${snapshot.project.type}${stack ? ` \xB7 ${stack}` : ""}`);
|
|
1766
|
+
} else {
|
|
1767
|
+
lines.push(" Legacy Squad");
|
|
1768
|
+
lines.push(" (framework n\xE3o instalado neste diret\xF3rio)");
|
|
1769
|
+
}
|
|
1770
|
+
lines.push(RULE, "");
|
|
1771
|
+
lines.push(` Maturity Level ${snapshot.maturityLevel} \u2014 ${snapshot.maturityLabel}`);
|
|
1772
|
+
lines.push(` Findings ${snapshot.findingCount}`, "");
|
|
1773
|
+
const labelWidth = Math.max(...snapshot.phases.map((p) => p.label.length));
|
|
1774
|
+
for (const phase of snapshot.phases) {
|
|
1775
|
+
lines.push(` ${phaseIcon(phase)} ${phase.label.padEnd(labelWidth)} ${phaseDetail(phase)}`);
|
|
1776
|
+
}
|
|
1777
|
+
lines.push("");
|
|
1778
|
+
if (snapshot.nextStep) {
|
|
1779
|
+
const target = snapshot.nextStep.command ?? "npx legacy-squad install";
|
|
1780
|
+
lines.push(` \u2192 Pr\xF3ximo passo: ${target}`);
|
|
1781
|
+
lines.push(` ${snapshot.nextStep.reason}`);
|
|
1782
|
+
} else {
|
|
1783
|
+
lines.push(" \u2713 Lifecycle V1 completo \u2014 specs prontas para execu\xE7\xE3o.");
|
|
1784
|
+
}
|
|
1785
|
+
lines.push(RULE);
|
|
1786
|
+
return lines.join("\n");
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1389
1790
|
// apps/cli/src/index.ts
|
|
1390
1791
|
var program = new Command();
|
|
1391
1792
|
function getTemplateDir() {
|
|
1392
|
-
const cliDir =
|
|
1393
|
-
const bundledPath =
|
|
1793
|
+
const cliDir = path10.dirname(fileURLToPath(import.meta.url));
|
|
1794
|
+
const bundledPath = path10.resolve(cliDir, "templates", "claude-commands");
|
|
1394
1795
|
if (existsSync(bundledPath)) return bundledPath;
|
|
1395
|
-
const devPath =
|
|
1796
|
+
const devPath = path10.resolve(cliDir, "..", "..", "..", "templates", "claude-commands");
|
|
1396
1797
|
if (existsSync(devPath)) return devPath;
|
|
1397
1798
|
throw new Error("Templates not found. Run from the framework root or use the published package.");
|
|
1398
1799
|
}
|
|
1399
|
-
program.name("legacy-squad").description("AI-Powered Legacy Modernization Platform \u2014 Understand. Plan. Modernize.").version("1.
|
|
1800
|
+
program.name("legacy-squad").description("AI-Powered Legacy Modernization Platform \u2014 Understand. Plan. Modernize.").version("1.3.0");
|
|
1400
1801
|
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 =
|
|
1802
|
+
const projectRoot = path10.resolve(opts.path);
|
|
1402
1803
|
const templateDir = getTemplateDir();
|
|
1403
1804
|
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
1805
|
console.log(" Legacy Squad Framework V1 \u2014 Install");
|
|
@@ -1440,21 +1841,21 @@ program.command("install").description("Install Legacy Squad Framework inside th
|
|
|
1440
1841
|
console.log(" 1. Open Claude Code: claude");
|
|
1441
1842
|
console.log("");
|
|
1442
1843
|
console.log(" Analysis (run in order):");
|
|
1443
|
-
console.log(" /legacy-squad
|
|
1444
|
-
console.log(" /legacy-squad
|
|
1445
|
-
console.log(" /legacy-squad
|
|
1446
|
-
console.log(" /legacy-squad
|
|
1447
|
-
console.log(" /legacy-squad
|
|
1844
|
+
console.log(" /legacy-squad:security");
|
|
1845
|
+
console.log(" /legacy-squad:architecture");
|
|
1846
|
+
console.log(" /legacy-squad:legacy-code");
|
|
1847
|
+
console.log(" /legacy-squad:business-rules");
|
|
1848
|
+
console.log(" /legacy-squad:modernization");
|
|
1448
1849
|
console.log("");
|
|
1449
1850
|
console.log(" Consolidated artifacts (run after analysis):");
|
|
1450
|
-
console.log(" /legacy-squad
|
|
1451
|
-
console.log(" /legacy-squad
|
|
1452
|
-
console.log(" /legacy-squad
|
|
1453
|
-
console.log(" /legacy-squad
|
|
1851
|
+
console.log(" /legacy-squad:generate-prs (Product Refactor Specification)");
|
|
1852
|
+
console.log(" /legacy-squad:generate-sdd (Software Design Document)");
|
|
1853
|
+
console.log(" /legacy-squad:generate-mmp (Modernization Master Plan)");
|
|
1854
|
+
console.log(" /legacy-squad:generate-specs (Execution Specs for V2)");
|
|
1454
1855
|
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
1856
|
});
|
|
1456
1857
|
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 =
|
|
1858
|
+
const projectRoot = path10.resolve(opts.path);
|
|
1458
1859
|
console.log(`
|
|
1459
1860
|
\u{1F50D} Re-scanning: ${projectRoot}
|
|
1460
1861
|
`);
|
|
@@ -1468,19 +1869,19 @@ program.command("scan").description("Re-scan the project and update .legacy-squa
|
|
|
1468
1869
|
}
|
|
1469
1870
|
const compliance = new ComplianceEngine(fs);
|
|
1470
1871
|
const findings = await compliance.evaluate(effectiveRoot, repoIndex);
|
|
1471
|
-
const { mkdir:
|
|
1472
|
-
const memoryDir =
|
|
1473
|
-
await
|
|
1474
|
-
await
|
|
1475
|
-
await
|
|
1872
|
+
const { mkdir: mkdir3, writeFile: writeFile3 } = await import("node:fs/promises");
|
|
1873
|
+
const memoryDir = path10.join(effectiveRoot, ".legacy-squad", "memory");
|
|
1874
|
+
await mkdir3(memoryDir, { recursive: true });
|
|
1875
|
+
await writeFile3(path10.join(memoryDir, "repo-index.json"), JSON.stringify(repoIndex, null, 2), "utf-8");
|
|
1876
|
+
await new FindingsWriter(fs).write(findings, memoryDir);
|
|
1476
1877
|
console.log(`\u2705 Stack: ${repoIndex.stack.map((s) => s.name).join(", ")}`);
|
|
1477
1878
|
console.log(`\u{1F4E6} Modules: ${repoIndex.modules.length}`);
|
|
1478
1879
|
console.log(`\u{1F512} Findings: ${findings.length}`);
|
|
1479
|
-
console.log(`\u{1F4C4} Updated: ${
|
|
1880
|
+
console.log(`\u{1F4C4} Updated: ${path10.join(effectiveRoot, ".legacy-squad", "memory")}
|
|
1480
1881
|
`);
|
|
1481
1882
|
});
|
|
1482
1883
|
program.command("doctor").description("Verify Legacy Squad installation health").option("-p, --path <dir>", "Project root directory", ".").action(async (opts) => {
|
|
1483
|
-
const projectRoot =
|
|
1884
|
+
const projectRoot = path10.resolve(opts.path);
|
|
1484
1885
|
console.log("\n\u{1FA7A} Legacy Squad Doctor\n");
|
|
1485
1886
|
const doctor = new Doctor();
|
|
1486
1887
|
const checks = await doctor.check(projectRoot);
|
|
@@ -1497,4 +1898,16 @@ program.command("doctor").description("Verify Legacy Squad installation health")
|
|
|
1497
1898
|
console.log("\n\u2705 All checks passed.\n");
|
|
1498
1899
|
}
|
|
1499
1900
|
});
|
|
1901
|
+
program.command("status").description("Show the project lifecycle dashboard and the recommended next step").option("-p, --path <dir>", "Project root directory", ".").option("--json", "Output the raw lifecycle snapshot as JSON").action(async (opts) => {
|
|
1902
|
+
const projectRoot = path10.resolve(opts.path);
|
|
1903
|
+
const fs = new NodeFileSystem();
|
|
1904
|
+
const snapshot = await new LifecycleDetector(fs).detect(projectRoot);
|
|
1905
|
+
if (opts.json) {
|
|
1906
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
console.log("");
|
|
1910
|
+
console.log(new DashboardRenderer().render(snapshot));
|
|
1911
|
+
console.log("");
|
|
1912
|
+
});
|
|
1500
1913
|
program.parse();
|