@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.
Files changed (2) hide show
  1. package/dist/index.js +234 -5
  2. 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 generateProposals(detections) {
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
- const proposals = generateProposals(analysis.detections);
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 = spawn("docker", ["compose", "-f", COMPOSE_PATH, "up"], {
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.3");
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umbral/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {