@stevenvo780/st-lang 3.1.3 → 3.2.1

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.
@@ -796,8 +796,18 @@ function isRelevantToGoal(f, goal) {
796
796
  }
797
797
  function addDerivedFormula(state, formula, justification, premises, source = 'rule') {
798
798
  const hash = formulaHash(formula);
799
- if (state.known.has(hash))
799
+ if (state.known.has(hash)) {
800
+ const variants = state.alternativeDerivations.get(hash) ?? [];
801
+ const isDuplicate = variants.some((variant) => variant.justification === justification &&
802
+ variant.source === source &&
803
+ variant.premises.length === premises.length &&
804
+ variant.premises.every((premise, index) => premise === premises[index]));
805
+ if (!isDuplicate) {
806
+ variants.push({ justification, premises: [...premises], source });
807
+ state.alternativeDerivations.set(hash, variants);
808
+ }
800
809
  return false;
810
+ }
801
811
  state.stepCount++;
802
812
  state.steps.push({
803
813
  stepNumber: state.stepCount,
@@ -807,6 +817,8 @@ function addDerivedFormula(state, formula, justification, premises, source = 'ru
807
817
  source,
808
818
  });
809
819
  state.known.set(hash, formula);
820
+ state.formulas.push(formula);
821
+ state.stepByHash.set(hash, state.stepCount);
810
822
  return true;
811
823
  }
812
824
  function buildPremiseRefs(theory, premiseNames) {
@@ -815,7 +827,12 @@ function buildPremiseRefs(theory, premiseNames) {
815
827
  location: (theory.axioms.get(name) || theory.theorems.get(name))?.source,
816
828
  }));
817
829
  }
818
- function buildProof(goal, steps, premiseNames, theory, method = 'natural_deduction', subproofs) {
830
+ function buildProof(goal, steps, premiseNames, theory, method = 'natural_deduction', subproofs, metadataExtras = {}) {
831
+ const metadata = {
832
+ createdAt: new Date().toISOString(),
833
+ profile: theory.profile,
834
+ ...metadataExtras,
835
+ };
819
836
  return {
820
837
  goal,
821
838
  steps,
@@ -823,11 +840,56 @@ function buildProof(goal, steps, premiseNames, theory, method = 'natural_deducti
823
840
  derivedFrom: premiseNames,
824
841
  premiseRefs: buildPremiseRefs(theory, premiseNames),
825
842
  method,
826
- subproofs,
827
- metadata: {
828
- createdAt: new Date().toISOString(),
829
- profile: theory.profile,
830
- },
843
+ subproofs: subproofs ? [...subproofs] : undefined,
844
+ metadata,
845
+ };
846
+ }
847
+ function buildDerivationMetadata(state, retainedSteps, semanticFallback = false) {
848
+ const sampledAlternatives = Array.from(state.alternativeDerivations.entries())
849
+ .filter(([, variants]) => variants.length > 0)
850
+ .sort((left, right) => right[1].length - left[1].length)
851
+ .slice(0, 12)
852
+ .map(([hash, variants]) => ({
853
+ formula: hash,
854
+ primaryStep: state.stepByHash.get(hash) ?? 0,
855
+ variants: variants.slice(0, 6).map((variant) => ({
856
+ justification: variant.justification,
857
+ premises: [...variant.premises],
858
+ source: variant.source,
859
+ })),
860
+ }));
861
+ const alternativeDerivationCount = Array.from(state.alternativeDerivations.values()).reduce((count, variants) => count + variants.length, 0);
862
+ return {
863
+ exploredStepCount: state.steps.length,
864
+ retainedStepCount: retainedSteps.length,
865
+ uniqueFormulaCount: state.formulas.length,
866
+ alternativeDerivationCount,
867
+ alternativeDerivationSamples: sampledAlternatives,
868
+ semanticFallback,
869
+ };
870
+ }
871
+ function buildCompositeDerivationMetadata(state, retainedSteps, subProofs = [], semanticFallback = false) {
872
+ const baseMetadata = buildDerivationMetadata(state, retainedSteps, semanticFallback);
873
+ const baseExploredStepCount = Number(baseMetadata.exploredStepCount ?? 0);
874
+ const baseAlternativeDerivationCount = Number(baseMetadata.alternativeDerivationCount ?? 0);
875
+ const subExploredStepCount = subProofs.reduce((count, subProof) => {
876
+ const subMetadata = subProof.metadata ?? {};
877
+ const explored = Number(subMetadata.exploredStepCount ?? subProof.steps.length);
878
+ return count + explored;
879
+ }, 0);
880
+ const subAlternativeDerivationCount = subProofs.reduce((count, subProof) => {
881
+ const subMetadata = subProof.metadata ?? {};
882
+ const alternatives = Number(subMetadata.alternativeDerivationCount ?? 0);
883
+ return count + alternatives;
884
+ }, 0);
885
+ const exploredStepCount = baseExploredStepCount + subExploredStepCount;
886
+ const alternativeDerivationCount = baseAlternativeDerivationCount + subAlternativeDerivationCount;
887
+ return {
888
+ ...baseMetadata,
889
+ exploredStepCount,
890
+ retainedStepCount: retainedSteps.length,
891
+ alternativeDerivationCount,
892
+ alternativeDerivationSamples: [],
831
893
  };
832
894
  }
833
895
  function isNegationOf(a, b) {
@@ -838,6 +900,116 @@ function isExcludedMiddleFormula(formula) {
838
900
  return false;
839
901
  return (isNegationOf(formula.args[0], formula.args[1]) || isNegationOf(formula.args[1], formula.args[0]));
840
902
  }
903
+ function buildSingleAssumptionProof(premiseSteps, assumption, assumptionJustification, subProof, goal, finalJustification) {
904
+ const mainSteps = [];
905
+ let stepNum = 0;
906
+ for (const s of premiseSteps) {
907
+ if (s.source === 'premise') {
908
+ stepNum++;
909
+ mainSteps.push({ ...s, stepNumber: stepNum, premises: [] });
910
+ }
911
+ }
912
+ stepNum++;
913
+ const assumptionStepNum = stepNum;
914
+ mainSteps.push({
915
+ stepNumber: stepNum,
916
+ formula: assumption,
917
+ justification: assumptionJustification,
918
+ premises: [],
919
+ source: 'assumption',
920
+ });
921
+ const subStepMap = new Map();
922
+ for (const s of subProof.steps) {
923
+ if (s.source === 'premise' && formulasEqual(s.formula, assumption)) {
924
+ subStepMap.set(s.stepNumber, assumptionStepNum);
925
+ continue;
926
+ }
927
+ if (s.source === 'premise') {
928
+ const existing = mainSteps.find((ms) => ms.source === 'premise' && formulasEqual(ms.formula, s.formula));
929
+ if (existing) {
930
+ subStepMap.set(s.stepNumber, existing.stepNumber);
931
+ continue;
932
+ }
933
+ }
934
+ stepNum++;
935
+ subStepMap.set(s.stepNumber, stepNum);
936
+ mainSteps.push({
937
+ stepNumber: stepNum,
938
+ formula: s.formula,
939
+ justification: s.justification,
940
+ premises: s.premises.map((p) => subStepMap.get(p) || p),
941
+ source: s.source,
942
+ });
943
+ }
944
+ stepNum++;
945
+ const subGoalStepNum = subStepMap.get(subProof.steps[subProof.steps.length - 1]?.stepNumber ?? 0) ?? stepNum - 1;
946
+ mainSteps.push({
947
+ stepNumber: stepNum,
948
+ formula: goal,
949
+ justification: finalJustification,
950
+ premises: [assumptionStepNum, subGoalStepNum],
951
+ subproofs: [subProof],
952
+ source: 'rule',
953
+ });
954
+ return mainSteps;
955
+ }
956
+ function buildMergedGoalProof(premiseSteps, subProofs, goal, finalJustification) {
957
+ const mainSteps = [];
958
+ let stepNum = 0;
959
+ const baseStepByHash = new Map();
960
+ for (const s of premiseSteps) {
961
+ if (s.source === 'premise') {
962
+ stepNum++;
963
+ mainSteps.push({ ...s, stepNumber: stepNum, premises: [] });
964
+ baseStepByHash.set(formulaHash(s.formula), stepNum);
965
+ }
966
+ }
967
+ const finalPremises = [];
968
+ for (const subProof of subProofs) {
969
+ const stepMap = new Map();
970
+ for (const s of subProof.steps) {
971
+ const hash = formulaHash(s.formula);
972
+ if (s.source === 'premise' && baseStepByHash.has(hash)) {
973
+ stepMap.set(s.stepNumber, baseStepByHash.get(hash) ?? 0);
974
+ continue;
975
+ }
976
+ if (s.source !== 'assumption' && baseStepByHash.has(hash)) {
977
+ stepMap.set(s.stepNumber, baseStepByHash.get(hash) ?? 0);
978
+ continue;
979
+ }
980
+ stepNum++;
981
+ stepMap.set(s.stepNumber, stepNum);
982
+ mainSteps.push({
983
+ stepNumber: stepNum,
984
+ formula: s.formula,
985
+ justification: s.justification,
986
+ premises: s.premises.map((p) => stepMap.get(p) || p),
987
+ subproofs: s.subproofs ? [...s.subproofs] : undefined,
988
+ source: s.source,
989
+ });
990
+ if (s.source !== 'assumption') {
991
+ baseStepByHash.set(hash, stepNum);
992
+ }
993
+ }
994
+ const mappedFinal = stepMap.get(subProof.steps[subProof.steps.length - 1]?.stepNumber ?? 0);
995
+ if (mappedFinal)
996
+ finalPremises.push(mappedFinal);
997
+ }
998
+ stepNum++;
999
+ mainSteps.push({
1000
+ stepNumber: stepNum,
1001
+ formula: goal,
1002
+ justification: finalJustification,
1003
+ premises: finalPremises,
1004
+ subproofs: subProofs,
1005
+ source: 'rule',
1006
+ });
1007
+ return mainSteps;
1008
+ }
1009
+ function buildKnownDerivationProof(state, formula, premiseNames, theory) {
1010
+ const relevantSteps = traceBack(state.steps, formula);
1011
+ return buildProof(formula, relevantSteps, premiseNames, theory, 'natural_deduction', undefined, buildDerivationMetadata(state, relevantSteps));
1012
+ }
841
1013
  function getCommutativeVariant(formula) {
842
1014
  if ((formula.kind === 'and' || formula.kind === 'or') && formula.args?.[0] && formula.args?.[1]) {
843
1015
  return { kind: formula.kind, args: [formula.args[1], formula.args[0]] };
@@ -883,8 +1055,11 @@ function getAbsorptionResult(formula) {
883
1055
  function tryDerive(goal, theory, premiseNames, depth = 0) {
884
1056
  const state = {
885
1057
  known: new Map(),
1058
+ formulas: [],
886
1059
  steps: [],
887
1060
  stepCount: 0,
1061
+ stepByHash: new Map(),
1062
+ alternativeDerivations: new Map(),
888
1063
  };
889
1064
  // Cargar premisas
890
1065
  for (const name of premiseNames) {
@@ -917,6 +1092,8 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
917
1092
  source: 'premise',
918
1093
  });
919
1094
  state.known.set(formulaHash(f), f);
1095
+ state.formulas.push(f);
1096
+ state.stepByHash.set(formulaHash(f), state.stepCount);
920
1097
  }
921
1098
  }
922
1099
  if (isExcludedMiddleFormula(goal)) {
@@ -930,7 +1107,7 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
930
1107
  while (changed && iterations < maxIterations && state.known.size < MAX_KNOWN) {
931
1108
  changed = false;
932
1109
  iterations++;
933
- const currentFormulas = Array.from(state.known.values());
1110
+ const currentFormulas = state.formulas;
934
1111
  const prevProcessedIndex = lastProcessedIndex;
935
1112
  lastProcessedIndex = currentFormulas.length;
936
1113
  for (let i = 0; i < currentFormulas.length; i++) {
@@ -944,6 +1121,15 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
944
1121
  const f2 = currentFormulas[j];
945
1122
  if (state.known.has(formulaHash(goal)))
946
1123
  break;
1124
+ // Contradicción explícita: de A y !A, derivar false (⊥)
1125
+ if ((f1.kind === 'not' && f1.args?.[0] && formulasEqual(f1.args[0], f2)) ||
1126
+ (f2.kind === 'not' && f2.args?.[0] && formulasEqual(f2.args[0], f1))) {
1127
+ changed =
1128
+ addDerivedFormula(state, { kind: 'false' }, 'Contradiccion', [
1129
+ findStep(state.steps, f1),
1130
+ findStep(state.steps, f2),
1131
+ ]) || changed;
1132
+ }
947
1133
  // Modus Ponens: de A y (A -> B), derivar B
948
1134
  if (f2.kind === 'implies' &&
949
1135
  f2.args?.[0] &&
@@ -978,6 +1164,19 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
978
1164
  findStep(state.steps, f2),
979
1165
  ]) || changed;
980
1166
  }
1167
+ // Modus Tollens con consecuente negado: de B y (A -> !B), derivar !A
1168
+ if (f2.kind === 'implies' &&
1169
+ f2.args?.[0] &&
1170
+ f2.args?.[1]?.kind === 'not' &&
1171
+ f2.args[1].args?.[0] &&
1172
+ formulasEqual(f1, f2.args[1].args[0])) {
1173
+ const conclusion = { kind: 'not', args: [f2.args[0]] };
1174
+ changed =
1175
+ addDerivedFormula(state, conclusion, 'Modus Tollens', [
1176
+ findStep(state.steps, f1),
1177
+ findStep(state.steps, f2),
1178
+ ]) || changed;
1179
+ }
981
1180
  // Conjunction Introduction: de A y B, derivar A & B
982
1181
  // Only produce conjunctions that are relevant to the goal to avoid O(n²) explosion
983
1182
  if (f1 !== f2) {
@@ -1166,6 +1365,11 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
1166
1365
  ]) || changed;
1167
1366
  }
1168
1367
  }
1368
+ // Eliminación de contradicción: de false (⊥), derivar cualquier meta
1369
+ if (f1.kind === 'false' && !formulasEqual(f1, goal)) {
1370
+ changed =
1371
+ addDerivedFormula(state, goal, 'Explosion', [findStep(state.steps, f1)]) || changed;
1372
+ }
1169
1373
  // Conjunction Elimination: de A & B, derivar A y B
1170
1374
  if (f1.kind === 'and' && f1.args) {
1171
1375
  for (const sub of f1.args) {
@@ -1506,10 +1710,10 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
1506
1710
  if (state.known.has(formulaHash(goal))) {
1507
1711
  // Filtrar solo pasos relevantes para la derivación
1508
1712
  const relevantSteps = traceBack(state.steps, goal);
1509
- return buildProof(goal, relevantSteps, premiseNames, theory);
1713
+ return buildProof(goal, relevantSteps, premiseNames, theory, 'natural_deduction', undefined, buildDerivationMetadata(state, relevantSteps));
1510
1714
  }
1511
1715
  // --- Sub-derivaciones recursivas (antes del fallback semántico) ---
1512
- const MAX_SUB_DEPTH = 2;
1716
+ const MAX_SUB_DEPTH = 3;
1513
1717
  // Prueba Condicional real (→-Introducción / Deduction Theorem):
1514
1718
  // Para derivar A→B, asumimos A como premisa temporal y derivamos B.
1515
1719
  if (depth < MAX_SUB_DEPTH && goal.kind === 'implies' && goal.args?.[0] && goal.args?.[1]) {
@@ -1587,14 +1791,14 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
1587
1791
  subproofs: [subProof],
1588
1792
  source: 'rule',
1589
1793
  });
1590
- return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [subProof]);
1794
+ return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [subProof], buildCompositeDerivationMetadata(state, mainSteps, [subProof]));
1591
1795
  }
1592
1796
  }
1593
1797
  }
1594
1798
  // Prueba por Casos (∨-Eliminación / Disjunction Elimination):
1595
1799
  // Si tenemos A|B y queremos derivar C, asumimos A→C y B→C por separado.
1596
1800
  if (depth < MAX_SUB_DEPTH) {
1597
- const disjunctions = Array.from(state.known.values()).filter((f) => f.kind === 'or' && f.args?.[0] && f.args?.[1]);
1801
+ const disjunctions = state.formulas.filter((f) => f.kind === 'or' && f.args?.[0] && f.args?.[1]);
1598
1802
  for (const disj of disjunctions) {
1599
1803
  const left = disj.args?.[0];
1600
1804
  const right = disj.args?.[1];
@@ -1723,10 +1927,113 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
1723
1927
  subproofs: [subProofL, subProofR],
1724
1928
  source: 'rule',
1725
1929
  });
1726
- return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [
1727
- subProofL,
1728
- subProofR,
1729
- ]);
1930
+ return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [subProofL, subProofR], buildCompositeDerivationMetadata(state, mainSteps, [subProofL, subProofR]));
1931
+ }
1932
+ }
1933
+ // Backchaining dirigido por meta: si tenemos A → objetivo,
1934
+ // intentamos derivar A para cerrar con Modus Ponens sin esperar a que aparezca espontáneamente.
1935
+ if (depth < MAX_SUB_DEPTH) {
1936
+ const candidateImplications = state.formulas.filter((formula) => formula.kind === 'implies' &&
1937
+ !!formula.args?.[0] &&
1938
+ !!formula.args?.[1] &&
1939
+ formulasEqual(formula.args[1], goal));
1940
+ for (const implication of candidateImplications) {
1941
+ const antecedent = implication.args?.[0];
1942
+ if (!antecedent || formulasEqual(antecedent, goal))
1943
+ continue;
1944
+ const antecedentProof = tryDerive(antecedent, theory, premiseNames, depth + 1);
1945
+ if (!antecedentProof || antecedentProof.status !== 'complete')
1946
+ continue;
1947
+ const antecedentIsSyntactic = antecedentProof.steps.every((step) => step.source !== 'semantic');
1948
+ if (!antecedentIsSyntactic)
1949
+ continue;
1950
+ const implicationProof = buildKnownDerivationProof(state, implication, premiseNames, theory);
1951
+ const mainSteps = buildMergedGoalProof(state.steps, [implicationProof, antecedentProof], goal, 'Modus Ponens');
1952
+ return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [implicationProof, antecedentProof], buildCompositeDerivationMetadata(state, mainSteps, [implicationProof, antecedentProof]));
1953
+ }
1954
+ }
1955
+ // Meta conjuntiva (∧-Introducción dirigida por meta): derivar ambos componentes por separado.
1956
+ if (depth < MAX_SUB_DEPTH && goal.kind === 'and' && goal.args?.[0] && goal.args?.[1]) {
1957
+ const leftGoal = goal.args[0];
1958
+ const rightGoal = goal.args[1];
1959
+ const leftProof = tryDerive(leftGoal, theory, premiseNames, depth + 1);
1960
+ const rightProof = tryDerive(rightGoal, theory, premiseNames, depth + 1);
1961
+ if (leftProof &&
1962
+ rightProof &&
1963
+ leftProof.status === 'complete' &&
1964
+ rightProof.status === 'complete') {
1965
+ const leftSyntactic = leftProof.steps.every((s) => s.source !== 'semantic');
1966
+ const rightSyntactic = rightProof.steps.every((s) => s.source !== 'semantic');
1967
+ if (leftSyntactic && rightSyntactic) {
1968
+ const mainSteps = buildMergedGoalProof(state.steps, [leftProof, rightProof], goal, 'Introduccion de conjuncion');
1969
+ return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [leftProof, rightProof], buildCompositeDerivationMetadata(state, mainSteps, [leftProof, rightProof]));
1970
+ }
1971
+ }
1972
+ }
1973
+ // Meta bicondicional (↔-Introducción dirigida por meta): demostrar ambos sentidos.
1974
+ if (depth < MAX_SUB_DEPTH && goal.kind === 'biconditional' && goal.args?.[0] && goal.args?.[1]) {
1975
+ const leftToRight = { kind: 'implies', args: [goal.args[0], goal.args[1]] };
1976
+ const rightToLeft = { kind: 'implies', args: [goal.args[1], goal.args[0]] };
1977
+ const leftProof = tryDerive(leftToRight, theory, premiseNames, depth + 1);
1978
+ const rightProof = tryDerive(rightToLeft, theory, premiseNames, depth + 1);
1979
+ if (leftProof &&
1980
+ rightProof &&
1981
+ leftProof.status === 'complete' &&
1982
+ rightProof.status === 'complete') {
1983
+ const leftSyntactic = leftProof.steps.every((s) => s.source !== 'semantic');
1984
+ const rightSyntactic = rightProof.steps.every((s) => s.source !== 'semantic');
1985
+ if (leftSyntactic && rightSyntactic) {
1986
+ const mainSteps = buildMergedGoalProof(state.steps, [leftProof, rightProof], goal, 'Introduccion de bicondicional');
1987
+ return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [leftProof, rightProof], buildCompositeDerivationMetadata(state, mainSteps, [leftProof, rightProof]));
1988
+ }
1989
+ }
1990
+ }
1991
+ // Introducción de negación (¬-Introducción): para derivar ¬A,
1992
+ // asumimos A y derivamos contradicción explícita (false / ⊥).
1993
+ if (depth < MAX_SUB_DEPTH && goal.kind === 'not' && goal.args?.[0]) {
1994
+ const assumption = goal.args[0];
1995
+ const contradiction = { kind: 'false' };
1996
+ const tempTheory = {
1997
+ profile: theory.profile,
1998
+ axioms: new Map(theory.axioms),
1999
+ theorems: new Map(theory.theorems),
2000
+ claims: theory.claims,
2001
+ judgments: theory.judgments,
2002
+ };
2003
+ const assumptionName = `__neg_assumption_${depth}_${formulaHash(assumption)}`;
2004
+ tempTheory.axioms.set(assumptionName, assumption);
2005
+ const subPremises = [...premiseNames, assumptionName];
2006
+ const subProof = tryDerive(contradiction, tempTheory, subPremises, depth + 1);
2007
+ if (subProof && subProof.status === 'complete') {
2008
+ const isSyntactic = subProof.steps.every((s) => s.source !== 'semantic');
2009
+ if (isSyntactic) {
2010
+ const mainSteps = buildSingleAssumptionProof(state.steps, assumption, 'Supuesto (para negacion)', subProof, goal, 'Introduccion de negacion');
2011
+ return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [subProof], buildCompositeDerivationMetadata(state, mainSteps, [subProof]));
2012
+ }
2013
+ }
2014
+ }
2015
+ // RAA genérica: para derivar A en lógica clásica,
2016
+ // asumimos ¬A y buscamos contradicción explícita.
2017
+ if (depth < MAX_SUB_DEPTH && goal.kind !== 'not' && goal.kind !== 'false') {
2018
+ const assumption = { kind: 'not', args: [goal] };
2019
+ const contradiction = { kind: 'false' };
2020
+ const tempTheory = {
2021
+ profile: theory.profile,
2022
+ axioms: new Map(theory.axioms),
2023
+ theorems: new Map(theory.theorems),
2024
+ claims: theory.claims,
2025
+ judgments: theory.judgments,
2026
+ };
2027
+ const assumptionName = `__raa_assumption_${depth}_${formulaHash(assumption)}`;
2028
+ tempTheory.axioms.set(assumptionName, assumption);
2029
+ const subPremises = [...premiseNames, assumptionName];
2030
+ const subProof = tryDerive(contradiction, tempTheory, subPremises, depth + 1);
2031
+ if (subProof && subProof.status === 'complete') {
2032
+ const isSyntactic = subProof.steps.every((s) => s.source !== 'semantic');
2033
+ if (isSyntactic) {
2034
+ const mainSteps = buildSingleAssumptionProof(state.steps, assumption, 'Supuesto (para RAA)', subProof, goal, 'RAA (Reduccion al Absurdo)');
2035
+ return buildProof(goal, mainSteps, premiseNames, theory, 'natural_deduction', [subProof], buildCompositeDerivationMetadata(state, mainSteps, [subProof]));
2036
+ }
1730
2037
  }
1731
2038
  }
1732
2039
  // Fallback: verificar semánticamente
@@ -1799,13 +2106,19 @@ function tryDerive(goal, theory, premiseNames, depth = 0) {
1799
2106
  state.known.set(goalHash, goal);
1800
2107
  }
1801
2108
  const relevantSteps = traceBack(state.steps, goal);
1802
- return buildProof(goal, relevantSteps, premiseNames, theory, 'semantic');
2109
+ return buildProof(goal, relevantSteps, premiseNames, theory, 'semantic', undefined, buildDerivationMetadata(state, relevantSteps, true));
1803
2110
  }
1804
2111
  }
1805
2112
  return null;
1806
2113
  }
1807
- function findStep(steps, formula) {
2114
+ function findStep(stateOrSteps, formula) {
1808
2115
  const hash = formulaHash(formula);
2116
+ if (!Array.isArray(stateOrSteps)) {
2117
+ const cached = stateOrSteps.stepByHash.get(hash);
2118
+ if (cached !== undefined)
2119
+ return cached;
2120
+ }
2121
+ const steps = Array.isArray(stateOrSteps) ? stateOrSteps : stateOrSteps.steps;
1809
2122
  for (const s of steps) {
1810
2123
  if (formulaHash(s.formula) === hash)
1811
2124
  return s.stepNumber;
@@ -2191,13 +2504,23 @@ class ClassicalPropositional {
2191
2504
  : `${formulaToString(goal)} derivado exitosamente`,
2192
2505
  proof,
2193
2506
  reasoningType,
2194
- reasoningSchema: rulesUsed.has('Modus Ponens')
2195
- ? 'φ ψ, φ ψ'
2196
- : rulesUsed.has('Modus Tollens')
2197
- ? 'φ ψ, ¬ψ ¬φ'
2198
- : rulesUsed.has('Silogismo Hipotetico')
2199
- ? 'φ ψ, ψ → χ φ → χ'
2200
- : undefined,
2507
+ reasoningSchema: rulesUsed.has('Introduccion de negacion')
2508
+ ? '[φ] ⊥, por lo tanto ¬φ'
2509
+ : rulesUsed.has('RAA (Reduccion al Absurdo)')
2510
+ ? '[¬φ] ⊥, por lo tanto φ'
2511
+ : rulesUsed.has('Contradiccion')
2512
+ ? 'φ, ¬φ'
2513
+ : rulesUsed.has('Explosion')
2514
+ ? '⊥ ⊢ ψ'
2515
+ : rulesUsed.has('Prueba Condicional (Teorema de Deduccion)')
2516
+ ? '[φ] ⊢ ψ, por lo tanto φ → ψ'
2517
+ : rulesUsed.has('Modus Ponens')
2518
+ ? 'φ → ψ, φ ⊢ ψ'
2519
+ : rulesUsed.has('Modus Tollens')
2520
+ ? 'φ → ψ, ¬ψ ⊢ ¬φ'
2521
+ : rulesUsed.has('Silogismo Hipotetico')
2522
+ ? 'φ → ψ, ψ → χ ⊢ φ → χ'
2523
+ : undefined,
2201
2524
  educationalNote: (0, educational_notes_1.pickEducationalNote)({
2202
2525
  op: 'derive',
2203
2526
  ok: true,