@umbral/cli 0.0.3 → 0.0.5
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/index.js +234 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -853,8 +853,218 @@ function getTemplate(detection) {
|
|
|
853
853
|
return TEMPLATES.find((t) => t.slugs.includes(detection.slug)) ?? null;
|
|
854
854
|
}
|
|
855
855
|
|
|
856
|
+
// src/claude-generate.ts
|
|
857
|
+
import { spawn, spawnSync } from "child_process";
|
|
858
|
+
function isClaudeAvailable() {
|
|
859
|
+
try {
|
|
860
|
+
const r = spawnSync("claude", ["--version"], {
|
|
861
|
+
timeout: 5e3,
|
|
862
|
+
shell: true,
|
|
863
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
864
|
+
});
|
|
865
|
+
return r.status === 0;
|
|
866
|
+
} catch {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
function buildPrompt(detections) {
|
|
871
|
+
const detectionsJson = JSON.stringify(
|
|
872
|
+
detections.map((d) => ({
|
|
873
|
+
name: d.name,
|
|
874
|
+
slug: d.slug,
|
|
875
|
+
category: d.category,
|
|
876
|
+
confidence: d.confidence,
|
|
877
|
+
evidence: d.evidence,
|
|
878
|
+
metadata: d.metadata,
|
|
879
|
+
subdir: d.subdir
|
|
880
|
+
})),
|
|
881
|
+
null,
|
|
882
|
+
2
|
|
883
|
+
);
|
|
884
|
+
return `Eres Umbral, un framework de gobernanza para proyectos de software. Bas\xE1ndote en las tecnolog\xEDas detectadas abajo, genera Estructuras de Decisi\xF3n Expl\xEDcitas (EDEs) \u2014 decisiones arquitect\xF3nicas formalizadas y espec\xEDficas para ESTE proyecto.
|
|
885
|
+
|
|
886
|
+
## Tecnolog\xEDas Detectadas
|
|
887
|
+
${detectionsJson}
|
|
888
|
+
|
|
889
|
+
## Esquema JSON de una EDE
|
|
890
|
+
Cada EDE debe seguir EXACTAMENTE esta estructura:
|
|
891
|
+
{
|
|
892
|
+
"id": "EDE-001-slug",
|
|
893
|
+
"title": "string",
|
|
894
|
+
"version": 1,
|
|
895
|
+
"status": "proposed",
|
|
896
|
+
"cognitiveLevel": "explorer" | "navigator" | "anchor",
|
|
897
|
+
"complexityTier": 1 | 2 | 3,
|
|
898
|
+
"whatAndHow": { "decision": "string", "mechanism": "string" },
|
|
899
|
+
"why": {
|
|
900
|
+
"rationale": "string",
|
|
901
|
+
"alternativesConsidered": [{ "option": "string", "rejectedBecause": "string" }],
|
|
902
|
+
"references": []
|
|
903
|
+
},
|
|
904
|
+
"whatNotToDo": { "antiPatterns": ["string"] },
|
|
905
|
+
"whatsNext": { "continuations": [], "openQuestions": [] },
|
|
906
|
+
"contracts": { "layerContracts": [], "verifiedBy": [] },
|
|
907
|
+
"tests": { "unitTests": [], "sadPaths": [], "coverageTarget": 0.8 },
|
|
908
|
+
"provenance": { "phase": "onboarding", "slice": null, "createdBy": "claude", "createdAt": null, "lastUpdated": null }
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
## Instrucciones
|
|
912
|
+
- Genera una EDE por cada decisi\xF3n arquitect\xF3nica significativa
|
|
913
|
+
- S\xE9 ESPEC\xCDFICO para este proyecto: usa la evidencia detectada para personalizar cada decisi\xF3n
|
|
914
|
+
- Incluye anti-patrones relevantes a la COMBINACI\xD3N de tecnolog\xEDas (no gen\xE9ricos)
|
|
915
|
+
- IDs: EDE-001-slug, EDE-002-slug, etc.
|
|
916
|
+
- cognitiveLevel: "explorer" para simples, "navigator" para intermedias, "anchor" para invariantes
|
|
917
|
+
- complexityTier: 1 simple, 2 medio, 3 complejo
|
|
918
|
+
- Todo el contenido en espa\xF1ol
|
|
919
|
+
- Genera entre 3 y 10 EDEs seg\xFAn la complejidad del proyecto
|
|
920
|
+
|
|
921
|
+
Retorna \xDANICAMENTE un array JSON v\xE1lido. Sin markdown, sin backticks, sin texto adicional.`;
|
|
922
|
+
}
|
|
923
|
+
function extractJson(text) {
|
|
924
|
+
const codeBlock = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
925
|
+
if (codeBlock) return codeBlock[1].trim();
|
|
926
|
+
const arrayMatch = text.match(/\[[\s\S]*\]/);
|
|
927
|
+
if (arrayMatch) return arrayMatch[0];
|
|
928
|
+
return text.trim();
|
|
929
|
+
}
|
|
930
|
+
function normalizeEde(raw, index) {
|
|
931
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
932
|
+
return {
|
|
933
|
+
id: raw.id ?? `EDE-${String(index + 1).padStart(3, "0")}-generated`,
|
|
934
|
+
title: raw.title ?? "EDE generada por Claude",
|
|
935
|
+
version: 1,
|
|
936
|
+
status: "proposed",
|
|
937
|
+
cognitiveLevel: raw.cognitiveLevel ?? "explorer",
|
|
938
|
+
complexityTier: raw.complexityTier ?? 1,
|
|
939
|
+
whatAndHow: {
|
|
940
|
+
decision: raw.whatAndHow?.decision ?? "",
|
|
941
|
+
mechanism: raw.whatAndHow?.mechanism ?? ""
|
|
942
|
+
},
|
|
943
|
+
why: {
|
|
944
|
+
rationale: raw.why?.rationale ?? "",
|
|
945
|
+
alternativesConsidered: Array.isArray(raw.why?.alternativesConsidered) ? raw.why.alternativesConsidered : [],
|
|
946
|
+
references: []
|
|
947
|
+
},
|
|
948
|
+
whatNotToDo: {
|
|
949
|
+
antiPatterns: Array.isArray(raw.whatNotToDo?.antiPatterns) ? raw.whatNotToDo.antiPatterns : []
|
|
950
|
+
},
|
|
951
|
+
whatsNext: {
|
|
952
|
+
continuations: [],
|
|
953
|
+
openQuestions: Array.isArray(raw.whatsNext?.openQuestions) ? raw.whatsNext.openQuestions : []
|
|
954
|
+
},
|
|
955
|
+
contracts: {
|
|
956
|
+
layerContracts: Array.isArray(raw.contracts?.layerContracts) ? raw.contracts.layerContracts : [],
|
|
957
|
+
verifiedBy: []
|
|
958
|
+
},
|
|
959
|
+
tests: {
|
|
960
|
+
unitTests: [],
|
|
961
|
+
sadPaths: [],
|
|
962
|
+
coverageTarget: raw.tests?.coverageTarget ?? 0.8
|
|
963
|
+
},
|
|
964
|
+
provenance: {
|
|
965
|
+
phase: "onboarding",
|
|
966
|
+
slice: null,
|
|
967
|
+
createdBy: "claude",
|
|
968
|
+
createdAt: now,
|
|
969
|
+
lastUpdated: now
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function parseEdes(stdout2) {
|
|
974
|
+
try {
|
|
975
|
+
const jsonStr = extractJson(stdout2);
|
|
976
|
+
const parsed = JSON.parse(jsonStr);
|
|
977
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
978
|
+
const edes = [];
|
|
979
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
980
|
+
try {
|
|
981
|
+
const ede = normalizeEde(parsed[i], i);
|
|
982
|
+
if (ede.whatAndHow.decision && ede.why.rationale) {
|
|
983
|
+
edes.push(ede);
|
|
984
|
+
}
|
|
985
|
+
} catch {
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return edes.length > 0 ? edes : null;
|
|
989
|
+
} catch {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
function generateWithClaude(detections) {
|
|
994
|
+
if (detections.length === 0) return Promise.resolve(null);
|
|
995
|
+
const prompt = buildPrompt(detections);
|
|
996
|
+
return new Promise((resolve) => {
|
|
997
|
+
const child = spawn("claude", ["--print"], {
|
|
998
|
+
shell: true,
|
|
999
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1000
|
+
});
|
|
1001
|
+
let stdout2 = "";
|
|
1002
|
+
let stderr = "";
|
|
1003
|
+
let timedOut = false;
|
|
1004
|
+
const timeout = setTimeout(() => {
|
|
1005
|
+
timedOut = true;
|
|
1006
|
+
child.kill();
|
|
1007
|
+
}, 12e4);
|
|
1008
|
+
child.stdout.on("data", (chunk) => {
|
|
1009
|
+
stdout2 += chunk.toString();
|
|
1010
|
+
});
|
|
1011
|
+
child.stderr.on("data", (chunk) => {
|
|
1012
|
+
stderr += chunk.toString();
|
|
1013
|
+
});
|
|
1014
|
+
child.on("close", (code) => {
|
|
1015
|
+
clearTimeout(timeout);
|
|
1016
|
+
if (timedOut || code !== 0 || !stdout2) {
|
|
1017
|
+
resolve(null);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
resolve(parseEdes(stdout2));
|
|
1021
|
+
});
|
|
1022
|
+
child.on("error", () => {
|
|
1023
|
+
clearTimeout(timeout);
|
|
1024
|
+
resolve(null);
|
|
1025
|
+
});
|
|
1026
|
+
child.stdin.write(prompt);
|
|
1027
|
+
child.stdin.end();
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// src/spinner.ts
|
|
1032
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1033
|
+
function createSpinner(text) {
|
|
1034
|
+
let i = 0;
|
|
1035
|
+
const start = Date.now();
|
|
1036
|
+
const w = process.stdout;
|
|
1037
|
+
function elapsed() {
|
|
1038
|
+
const s = Math.floor((Date.now() - start) / 1e3);
|
|
1039
|
+
return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
|
|
1040
|
+
}
|
|
1041
|
+
function render() {
|
|
1042
|
+
w.write(`\r ${FRAMES[i % FRAMES.length]} ${text} (${elapsed()})`);
|
|
1043
|
+
i++;
|
|
1044
|
+
}
|
|
1045
|
+
render();
|
|
1046
|
+
const interval = setInterval(render, 80);
|
|
1047
|
+
function clear(symbol, msg) {
|
|
1048
|
+
clearInterval(interval);
|
|
1049
|
+
const line = ` ${symbol} ${msg}`;
|
|
1050
|
+
w.write(`\r${line}${"".padEnd(40)}
|
|
1051
|
+
`);
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
update(newText) {
|
|
1055
|
+
text = newText;
|
|
1056
|
+
},
|
|
1057
|
+
succeed(msg) {
|
|
1058
|
+
clear("\u2713", msg);
|
|
1059
|
+
},
|
|
1060
|
+
fail(msg) {
|
|
1061
|
+
clear("\u2717", msg);
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
|
|
856
1066
|
// src/generate.ts
|
|
857
|
-
function
|
|
1067
|
+
function generateFromTemplates(detections) {
|
|
858
1068
|
const proposals = [];
|
|
859
1069
|
let counter = 1;
|
|
860
1070
|
for (const detection of detections) {
|
|
@@ -906,6 +1116,24 @@ function generateProposals(detections) {
|
|
|
906
1116
|
}
|
|
907
1117
|
return proposals;
|
|
908
1118
|
}
|
|
1119
|
+
async function generateProposals(detections) {
|
|
1120
|
+
if (isClaudeAvailable()) {
|
|
1121
|
+
const spinner = createSpinner("Generando EDEs con Claude Code...");
|
|
1122
|
+
const claudeEdes = await generateWithClaude(detections);
|
|
1123
|
+
if (claudeEdes && claudeEdes.length > 0) {
|
|
1124
|
+
spinner.succeed(
|
|
1125
|
+
`${claudeEdes.length} EDEs generadas con Claude Code (IA)`
|
|
1126
|
+
);
|
|
1127
|
+
return { proposals: claudeEdes, source: "claude" };
|
|
1128
|
+
}
|
|
1129
|
+
spinner.fail("Claude no pudo generar EDEs \u2014 usando templates");
|
|
1130
|
+
} else {
|
|
1131
|
+
process.stdout.write(
|
|
1132
|
+
" \u21B3 Claude Code no disponible \u2014 usando templates locales\n"
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
return { proposals: generateFromTemplates(detections), source: "templates" };
|
|
1136
|
+
}
|
|
909
1137
|
|
|
910
1138
|
// src/ui.ts
|
|
911
1139
|
import { createInterface } from "readline/promises";
|
|
@@ -1340,7 +1568,8 @@ async function initCommand(options) {
|
|
|
1340
1568
|
`);
|
|
1341
1569
|
}
|
|
1342
1570
|
}
|
|
1343
|
-
|
|
1571
|
+
w("\n");
|
|
1572
|
+
const { proposals, source } = await generateProposals(analysis.detections);
|
|
1344
1573
|
if (proposals.length === 0) {
|
|
1345
1574
|
w("\n No hay propuestas de EDEs para generar.\n");
|
|
1346
1575
|
w(" Configurando infraestructura base...\n\n");
|
|
@@ -1473,7 +1702,7 @@ async function mcpCommand() {
|
|
|
1473
1702
|
}
|
|
1474
1703
|
|
|
1475
1704
|
// src/commands/start.ts
|
|
1476
|
-
import { execSync, spawn } from "child_process";
|
|
1705
|
+
import { execSync, spawn as spawn2 } from "child_process";
|
|
1477
1706
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
1478
1707
|
import { join as join15 } from "path";
|
|
1479
1708
|
import { homedir as homedir3 } from "os";
|
|
@@ -1583,7 +1812,7 @@ async function startCommand(options) {
|
|
|
1583
1812
|
stdio: "inherit"
|
|
1584
1813
|
});
|
|
1585
1814
|
} else {
|
|
1586
|
-
const child =
|
|
1815
|
+
const child = spawn2("docker", ["compose", "-f", COMPOSE_PATH, "up"], {
|
|
1587
1816
|
stdio: "inherit"
|
|
1588
1817
|
});
|
|
1589
1818
|
child.on("close", (code) => process.exit(code ?? 0));
|
|
@@ -1636,7 +1865,7 @@ function stopCommand() {
|
|
|
1636
1865
|
|
|
1637
1866
|
// src/index.ts
|
|
1638
1867
|
var program = new Command();
|
|
1639
|
-
program.name("umbral").description("Umbral \u2014 Framework de gobernanza para proyectos con Claude Code").version("0.0.
|
|
1868
|
+
program.name("umbral").description("Umbral \u2014 Framework de gobernanza para proyectos con Claude Code").version("0.0.5");
|
|
1640
1869
|
program.command("init").description("Inicializar Umbral en el proyecto actual").option("--yes", "Aceptar todas las propuestas sin preguntar").option("--path <path>", "Ruta al proyecto (default: directorio actual)").action(initCommand);
|
|
1641
1870
|
program.command("start").description("Levantar la plataforma Umbral (Neo4j + Dashboard web)").option("--port <port>", "Puerto para el dashboard (default: auto)").option("--no-detach", "Correr en primer plano (sin -d)").action(startCommand);
|
|
1642
1871
|
program.command("stop").description("Detener la plataforma Umbral").action(stopCommand);
|