@plures/praxis 1.2.41 → 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.
Files changed (36) hide show
  1. package/dist/browser/{chunk-BBP2F7TT.js → chunk-MJK3IYTJ.js} +123 -5
  2. package/dist/browser/{chunk-FCEH7WMH.js → chunk-N63K4KWS.js} +1 -1
  3. package/dist/browser/{engine-65QDGCAN.js → engine-YIEGSX7U.js} +1 -1
  4. package/dist/browser/index.d.ts +2 -2
  5. package/dist/browser/index.js +10 -5
  6. package/dist/browser/integrations/svelte.d.ts +2 -2
  7. package/dist/browser/integrations/svelte.js +2 -2
  8. package/dist/browser/{reactive-engine.svelte-Cqd8Mod2.d.ts → reactive-engine.svelte-DjynI82A.d.ts} +83 -4
  9. package/dist/node/{chunk-32YFEEML.js → chunk-5JQJZADT.js} +1 -1
  10. package/dist/node/{chunk-BBP2F7TT.js → chunk-KMJWAFZV.js} +128 -5
  11. package/dist/node/cloud/index.d.cts +1 -1
  12. package/dist/node/cloud/index.d.ts +1 -1
  13. package/dist/node/{engine-7CXQV6RC.js → engine-FEN5IYZ5.js} +1 -1
  14. package/dist/node/index.cjs +522 -59
  15. package/dist/node/index.d.cts +271 -5
  16. package/dist/node/index.d.ts +271 -5
  17. package/dist/node/index.js +355 -39
  18. package/dist/node/integrations/svelte.cjs +123 -5
  19. package/dist/node/integrations/svelte.d.cts +3 -3
  20. package/dist/node/integrations/svelte.d.ts +3 -3
  21. package/dist/node/integrations/svelte.js +2 -2
  22. package/dist/node/{protocol-BocKczNv.d.ts → protocol-DcyGMmWY.d.cts} +7 -0
  23. package/dist/node/{protocol-BocKczNv.d.cts → protocol-DcyGMmWY.d.ts} +7 -0
  24. package/dist/node/{reactive-engine.svelte-D-xTDxT5.d.ts → reactive-engine.svelte-Cg0Yc2Hs.d.cts} +90 -6
  25. package/dist/node/{reactive-engine.svelte-CGe8SpVE.d.cts → reactive-engine.svelte-DekxqFu0.d.ts} +90 -6
  26. package/package.json +2 -2
  27. package/src/__tests__/engine-v2.test.ts +532 -0
  28. package/src/core/completeness.ts +274 -0
  29. package/src/core/engine.ts +47 -5
  30. package/src/core/pluresdb/store.ts +9 -3
  31. package/src/core/protocol.ts +7 -0
  32. package/src/core/rule-result.ts +130 -0
  33. package/src/core/rules.ts +12 -5
  34. package/src/core/ui-rules.ts +340 -0
  35. package/src/index.ts +27 -0
  36. package/src/vite/completeness-plugin.ts +72 -0
@@ -50,7 +50,7 @@ import {
50
50
  import {
51
51
  ReactiveLogicEngine,
52
52
  createReactiveEngine
53
- } from "./chunk-32YFEEML.js";
53
+ } from "./chunk-5JQJZADT.js";
54
54
  import {
55
55
  PraxisRegistry
56
56
  } from "./chunk-R2PSBPKQ.js";
@@ -78,8 +78,10 @@ import {
78
78
  import {
79
79
  LogicEngine,
80
80
  PRAXIS_PROTOCOL_VERSION,
81
- createPraxisEngine
82
- } from "./chunk-BBP2F7TT.js";
81
+ RuleResult,
82
+ createPraxisEngine,
83
+ fact
84
+ } from "./chunk-KMJWAFZV.js";
83
85
  import "./chunk-QGM4M3NI.js";
84
86
 
85
87
  // src/core/reactive-engine.ts
@@ -739,37 +741,37 @@ var PraxisDBStore = class {
739
741
  * @param fact The fact to store
740
742
  * @returns Promise that resolves when the fact is stored
741
743
  */
742
- async storeFact(fact) {
743
- const constraintResult = await this.checkConstraints([fact]);
744
+ async storeFact(fact2) {
745
+ const constraintResult = await this.checkConstraints([fact2]);
744
746
  if (!constraintResult.valid) {
745
747
  throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
746
748
  }
747
749
  let before;
748
750
  if (this.chronicle) {
749
- const payload = fact.payload;
751
+ const payload = fact2.payload;
750
752
  const id = payload?.id;
751
753
  if (id) {
752
- before = await this.getFact(fact.tag, id);
754
+ before = await this.getFact(fact2.tag, id);
753
755
  }
754
756
  }
755
- await this.persistFact(fact);
757
+ await this.persistFact(fact2);
756
758
  if (this.chronicle) {
757
- const payload = fact.payload;
759
+ const payload = fact2.payload;
758
760
  const id = payload?.id ?? "";
759
761
  const span = ChronicleContext.current;
760
762
  try {
761
763
  await this.chronicle.record({
762
- path: getFactPath(fact.tag, id),
764
+ path: getFactPath(fact2.tag, id),
763
765
  before,
764
- after: fact,
766
+ after: fact2,
765
767
  cause: span?.spanId,
766
768
  context: span?.contextId,
767
- metadata: { factTag: fact.tag, operation: "storeFact" }
769
+ metadata: { factTag: fact2.tag, operation: "storeFact" }
768
770
  });
769
771
  } catch {
770
772
  }
771
773
  }
772
- await this.triggerRules([fact]);
774
+ await this.triggerRules([fact2]);
773
775
  }
774
776
  /**
775
777
  * Store multiple facts in PluresDB
@@ -781,28 +783,28 @@ var PraxisDBStore = class {
781
783
  if (!constraintResult.valid) {
782
784
  throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
783
785
  }
784
- for (const fact of facts) {
786
+ for (const fact2 of facts) {
785
787
  let before;
786
788
  if (this.chronicle) {
787
- const payload = fact.payload;
789
+ const payload = fact2.payload;
788
790
  const id = payload?.id;
789
791
  if (id) {
790
- before = await this.getFact(fact.tag, id);
792
+ before = await this.getFact(fact2.tag, id);
791
793
  }
792
794
  }
793
- await this.persistFact(fact);
795
+ await this.persistFact(fact2);
794
796
  if (this.chronicle) {
795
- const payload = fact.payload;
797
+ const payload = fact2.payload;
796
798
  const id = payload?.id ?? "";
797
799
  const span = ChronicleContext.current;
798
800
  try {
799
801
  await this.chronicle.record({
800
- path: getFactPath(fact.tag, id),
802
+ path: getFactPath(fact2.tag, id),
801
803
  before,
802
- after: fact,
804
+ after: fact2,
803
805
  cause: span?.spanId,
804
806
  context: span?.contextId,
805
- metadata: { factTag: fact.tag, operation: "storeFacts" }
807
+ metadata: { factTag: fact2.tag, operation: "storeFacts" }
806
808
  });
807
809
  } catch {
808
810
  }
@@ -814,11 +816,11 @@ var PraxisDBStore = class {
814
816
  * Internal method to persist a fact without constraint checking
815
817
  * Used by both storeFact and derived fact storage
816
818
  */
817
- async persistFact(fact) {
818
- const payload = fact.payload;
819
+ async persistFact(fact2) {
820
+ const payload = fact2.payload;
819
821
  const id = payload?.id ?? generateId();
820
- const path = getFactPath(fact.tag, id);
821
- await this.db.set(path, fact);
822
+ const path = getFactPath(fact2.tag, id);
823
+ await this.db.set(path, fact2);
822
824
  }
823
825
  /**
824
826
  * Get a fact by tag and id
@@ -955,8 +957,8 @@ var PraxisDBStore = class {
955
957
  if (watchers) {
956
958
  watchers.add(callback);
957
959
  }
958
- const unsubscribe = this.db.watch(path, (fact) => {
959
- callback([fact]);
960
+ const unsubscribe = this.db.watch(path, (fact2) => {
961
+ callback([fact2]);
960
962
  });
961
963
  this.subscriptions.push(unsubscribe);
962
964
  return () => {
@@ -1014,13 +1016,18 @@ var PraxisDBStore = class {
1014
1016
  const state = {
1015
1017
  context: this.context,
1016
1018
  facts: [],
1019
+ events,
1017
1020
  meta: {}
1018
1021
  };
1019
1022
  const derivedFacts = [];
1020
1023
  for (const rule of rules) {
1021
1024
  try {
1022
- const facts = rule.impl(state, events);
1023
- derivedFacts.push(...facts);
1025
+ const result = rule.impl(state, events);
1026
+ if (Array.isArray(result)) {
1027
+ derivedFacts.push(...result);
1028
+ } else if (result && "kind" in result && result.kind === "emit") {
1029
+ derivedFacts.push(...result.facts);
1030
+ }
1024
1031
  } catch (error) {
1025
1032
  this.onRuleError(rule.id, error);
1026
1033
  }
@@ -1028,19 +1035,19 @@ var PraxisDBStore = class {
1028
1035
  if (derivedFacts.length > 0) {
1029
1036
  const constraintResult = await this.checkConstraints(derivedFacts);
1030
1037
  if (constraintResult.valid) {
1031
- for (const fact of derivedFacts) {
1032
- await this.persistFact(fact);
1038
+ for (const fact2 of derivedFacts) {
1039
+ await this.persistFact(fact2);
1033
1040
  if (this.chronicle) {
1034
- const payload = fact.payload;
1041
+ const payload = fact2.payload;
1035
1042
  const id = payload?.id ?? "";
1036
1043
  const span = ChronicleContext.current;
1037
1044
  try {
1038
1045
  await this.chronicle.record({
1039
- path: getFactPath(fact.tag, id),
1040
- after: fact,
1046
+ path: getFactPath(fact2.tag, id),
1047
+ after: fact2,
1041
1048
  cause: span?.spanId,
1042
1049
  context: span?.contextId,
1043
- metadata: { factTag: fact.tag, operation: "derivedFact" }
1050
+ metadata: { factTag: fact2.tag, operation: "derivedFact" }
1044
1051
  });
1045
1052
  } catch {
1046
1053
  }
@@ -1479,7 +1486,7 @@ async function createUnumAdapter(config) {
1479
1486
  type: "event"
1480
1487
  });
1481
1488
  }
1482
- async function broadcastFact(channelId, fact) {
1489
+ async function broadcastFact(channelId, fact2) {
1483
1490
  const channel = channels.get(channelId);
1484
1491
  if (!channel) {
1485
1492
  throw new Error(`Not joined to channel ${channelId}`);
@@ -1487,7 +1494,7 @@ async function createUnumAdapter(config) {
1487
1494
  await channel.publish({
1488
1495
  id: generateId2(),
1489
1496
  sender: currentIdentity || { id: "anonymous", createdAt: Date.now() },
1490
- content: fact,
1497
+ content: fact2,
1491
1498
  type: "fact"
1492
1499
  });
1493
1500
  }
@@ -1768,7 +1775,7 @@ function generateTauriConfig(config) {
1768
1775
 
1769
1776
  // src/integrations/unified.ts
1770
1777
  async function createUnifiedApp(config) {
1771
- const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-7CXQV6RC.js");
1778
+ const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-FEN5IYZ5.js");
1772
1779
  const { createInMemoryDB: createInMemoryDB2 } = await import("./adapter-75ISSMWD.js");
1773
1780
  const db = config.db || createInMemoryDB2();
1774
1781
  const pluresdb = createPluresDBAdapter({
@@ -1897,6 +1904,298 @@ async function attachAllIntegrations(engine, registry, options = {}) {
1897
1904
  }
1898
1905
  };
1899
1906
  }
1907
+
1908
+ // src/core/ui-rules.ts
1909
+ var loadingGateRule = {
1910
+ id: "ui/loading-gate",
1911
+ description: "Signals when the app is in a loading state",
1912
+ eventTypes: ["ui.state-change", "app.init"],
1913
+ impl: (state) => {
1914
+ const ctx = state.context;
1915
+ if (ctx.loading) {
1916
+ return RuleResult.emit([fact("ui.loading-gate", { active: true })]);
1917
+ }
1918
+ return RuleResult.retract(["ui.loading-gate"], "Not loading");
1919
+ },
1920
+ contract: {
1921
+ ruleId: "RULE_ID_PLACEHOLDER",
1922
+ behavior: "Emits ui.loading-gate when context.loading is true, retracts when false",
1923
+ examples: [
1924
+ { given: "loading is true", when: "ui state changes", then: "ui.loading-gate emitted" },
1925
+ { given: "loading is false", when: "ui state changes", then: "ui.loading-gate retracted" }
1926
+ ],
1927
+ invariants: ["Loading gate must reflect context.loading exactly"]
1928
+ }
1929
+ };
1930
+ var errorDisplayRule = {
1931
+ id: "ui/error-display",
1932
+ description: "Signals when an error should be displayed to the user",
1933
+ eventTypes: ["ui.state-change", "app.error"],
1934
+ impl: (state) => {
1935
+ const ctx = state.context;
1936
+ if (ctx.error) {
1937
+ return RuleResult.emit([fact("ui.error-display", { message: ctx.error, severity: "error" })]);
1938
+ }
1939
+ return RuleResult.retract(["ui.error-display"], "Error cleared");
1940
+ },
1941
+ contract: {
1942
+ ruleId: "RULE_ID_PLACEHOLDER",
1943
+ behavior: "Emits ui.error-display when context.error is non-null, retracts when cleared",
1944
+ examples: [
1945
+ { given: "error is set", when: "ui state changes", then: "ui.error-display emitted with message" },
1946
+ { given: "error is null", when: "ui state changes", then: "ui.error-display retracted" }
1947
+ ],
1948
+ invariants: ["Error display must clear when error is null"]
1949
+ }
1950
+ };
1951
+ var offlineIndicatorRule = {
1952
+ id: "ui/offline-indicator",
1953
+ description: "Signals when the app is offline",
1954
+ eventTypes: ["ui.state-change", "network.change"],
1955
+ impl: (state) => {
1956
+ if (state.context.offline) {
1957
+ return RuleResult.emit([fact("ui.offline", { message: "You are offline. Changes will sync when reconnected." })]);
1958
+ }
1959
+ return RuleResult.retract(["ui.offline"], "Back online");
1960
+ },
1961
+ contract: {
1962
+ ruleId: "RULE_ID_PLACEHOLDER",
1963
+ behavior: "Emits ui.offline when context.offline is true, retracts when back online",
1964
+ examples: [
1965
+ { given: "offline is true", when: "network changes", then: "ui.offline emitted" },
1966
+ { given: "offline is false", when: "network changes", then: "ui.offline retracted" }
1967
+ ],
1968
+ invariants: ["Offline indicator must match actual connectivity"]
1969
+ }
1970
+ };
1971
+ var dirtyGuardRule = {
1972
+ id: "ui/dirty-guard",
1973
+ description: "Warns when there are unsaved changes",
1974
+ eventTypes: ["ui.state-change", "navigation.request"],
1975
+ impl: (state) => {
1976
+ if (state.context.dirty) {
1977
+ return RuleResult.emit([fact("ui.unsaved-warning", {
1978
+ message: "You have unsaved changes",
1979
+ blocking: true
1980
+ })]);
1981
+ }
1982
+ return RuleResult.retract(["ui.unsaved-warning"], "No unsaved changes");
1983
+ },
1984
+ contract: {
1985
+ ruleId: "RULE_ID_PLACEHOLDER",
1986
+ behavior: "Emits ui.unsaved-warning when context.dirty is true, retracts when saved",
1987
+ examples: [
1988
+ { given: "dirty is true", when: "ui state changes", then: "ui.unsaved-warning emitted with blocking=true" },
1989
+ { given: "dirty is false", when: "ui state changes", then: "ui.unsaved-warning retracted" }
1990
+ ],
1991
+ invariants: ["Dirty guard must clear after save"]
1992
+ }
1993
+ };
1994
+ var initGateRule = {
1995
+ id: "ui/init-gate",
1996
+ description: "Signals whether the app has completed initialization",
1997
+ eventTypes: ["ui.state-change", "app.init"],
1998
+ impl: (state) => {
1999
+ if (!state.context.initialized) {
2000
+ return RuleResult.emit([fact("ui.init-pending", {
2001
+ message: "App is initializing..."
2002
+ })]);
2003
+ }
2004
+ return RuleResult.retract(["ui.init-pending"], "App initialized");
2005
+ },
2006
+ contract: {
2007
+ ruleId: "RULE_ID_PLACEHOLDER",
2008
+ behavior: "Emits ui.init-pending until context.initialized is true",
2009
+ examples: [
2010
+ { given: "initialized is false", when: "app starts", then: "ui.init-pending emitted" },
2011
+ { given: "initialized is true", when: "init completes", then: "ui.init-pending retracted" }
2012
+ ],
2013
+ invariants: ["Init gate must clear exactly once, when initialization completes"]
2014
+ }
2015
+ };
2016
+ var viewportRule = {
2017
+ id: "ui/viewport-class",
2018
+ description: "Classifies viewport size for responsive layout decisions",
2019
+ eventTypes: ["ui.state-change", "ui.resize"],
2020
+ impl: (state) => {
2021
+ const vp = state.context.viewport;
2022
+ if (!vp) return RuleResult.skip("No viewport data");
2023
+ return RuleResult.emit([fact("ui.viewport-class", {
2024
+ viewport: vp,
2025
+ compact: vp === "mobile",
2026
+ showSidebar: vp !== "mobile"
2027
+ })]);
2028
+ },
2029
+ contract: {
2030
+ ruleId: "RULE_ID_PLACEHOLDER",
2031
+ behavior: "Classifies viewport into responsive layout hints",
2032
+ examples: [
2033
+ { given: "viewport is mobile", when: "resize event", then: "compact=true, showSidebar=false" },
2034
+ { given: "viewport is desktop", when: "resize event", then: "compact=false, showSidebar=true" }
2035
+ ],
2036
+ invariants: ["Viewport class must update on every resize event"]
2037
+ }
2038
+ };
2039
+ var noInteractionWhileLoadingConstraint = {
2040
+ id: "ui/no-interaction-while-loading",
2041
+ description: "Prevents data mutations while a load operation is in progress",
2042
+ impl: (state) => {
2043
+ if (state.context.loading) {
2044
+ return "Cannot perform action while data is loading";
2045
+ }
2046
+ return true;
2047
+ },
2048
+ contract: {
2049
+ ruleId: "RULE_ID_PLACEHOLDER",
2050
+ behavior: "Fails when context.loading is true",
2051
+ examples: [
2052
+ { given: "loading is true", when: "action attempted", then: "violation" },
2053
+ { given: "loading is false", when: "action attempted", then: "pass" }
2054
+ ],
2055
+ invariants: ["Must always fail during loading"]
2056
+ }
2057
+ };
2058
+ var mustBeInitializedConstraint = {
2059
+ id: "ui/must-be-initialized",
2060
+ description: "Requires app initialization before user interactions",
2061
+ impl: (state) => {
2062
+ if (!state.context.initialized) {
2063
+ return "App must be initialized before performing this action";
2064
+ }
2065
+ return true;
2066
+ },
2067
+ contract: {
2068
+ ruleId: "RULE_ID_PLACEHOLDER",
2069
+ behavior: "Fails when context.initialized is false",
2070
+ examples: [
2071
+ { given: "initialized is false", when: "action attempted", then: "violation" },
2072
+ { given: "initialized is true", when: "action attempted", then: "pass" }
2073
+ ],
2074
+ invariants: ["Must always fail before init completes"]
2075
+ }
2076
+ };
2077
+ var uiModule = {
2078
+ rules: [
2079
+ loadingGateRule,
2080
+ errorDisplayRule,
2081
+ offlineIndicatorRule,
2082
+ dirtyGuardRule,
2083
+ initGateRule,
2084
+ viewportRule
2085
+ ],
2086
+ constraints: [
2087
+ noInteractionWhileLoadingConstraint,
2088
+ mustBeInitializedConstraint
2089
+ ],
2090
+ meta: {
2091
+ name: "praxis-ui",
2092
+ version: "1.0.0",
2093
+ description: "Predefined UI rules and constraints \u2014 separate from business logic"
2094
+ }
2095
+ };
2096
+ function createUIModule(options) {
2097
+ const allRules = uiModule.rules;
2098
+ const allConstraints = uiModule.constraints;
2099
+ const selectedRules = options.rules ? allRules.filter((r) => options.rules.includes(r.id)) : allRules;
2100
+ const selectedConstraints = options.constraints ? allConstraints.filter((c) => options.constraints.includes(c.id)) : allConstraints;
2101
+ return {
2102
+ rules: [...selectedRules, ...options.extraRules ?? []],
2103
+ constraints: [...selectedConstraints, ...options.extraConstraints ?? []],
2104
+ meta: { ...uiModule.meta, customized: true }
2105
+ };
2106
+ }
2107
+ function uiStateChanged(changes) {
2108
+ return { tag: "ui.state-change", payload: changes ?? {} };
2109
+ }
2110
+ function navigationRequest(to) {
2111
+ return { tag: "navigation.request", payload: { to } };
2112
+ }
2113
+ function resizeEvent(width, height) {
2114
+ return { tag: "ui.resize", payload: { width, height } };
2115
+ }
2116
+
2117
+ // src/core/completeness.ts
2118
+ function auditCompleteness(manifest, registryRuleIds, registryConstraintIds, rulesWithContracts, config) {
2119
+ const threshold = config?.threshold ?? 90;
2120
+ const domainBranches = manifest.branches.filter((b) => b.kind === "domain");
2121
+ const coveredDomain = domainBranches.filter((b) => b.coveredBy && registryRuleIds.includes(b.coveredBy));
2122
+ const uncoveredDomain = domainBranches.filter((b) => !b.coveredBy || !registryRuleIds.includes(b.coveredBy));
2123
+ const invariantBranches = manifest.branches.filter((b) => b.kind === "invariant");
2124
+ const coveredInvariants = invariantBranches.filter((b) => b.coveredBy && registryConstraintIds.includes(b.coveredBy));
2125
+ const uncoveredInvariants = invariantBranches.filter((b) => !b.coveredBy || !registryConstraintIds.includes(b.coveredBy));
2126
+ const needContracts = manifest.rulesNeedingContracts;
2127
+ const haveContracts = needContracts.filter((id) => rulesWithContracts.includes(id));
2128
+ const missingContracts = needContracts.filter((id) => !rulesWithContracts.includes(id));
2129
+ const neededFields = manifest.stateFields.filter((f) => f.usedByRule);
2130
+ const coveredFields = neededFields.filter((f) => f.inContext);
2131
+ const missingFields = neededFields.filter((f) => !f.inContext);
2132
+ const coveredTransitions = manifest.transitions.filter((t) => t.eventTag);
2133
+ const missingTransitions = manifest.transitions.filter((t) => !t.eventTag);
2134
+ const ruleScore = domainBranches.length > 0 ? coveredDomain.length / domainBranches.length * 40 : 40;
2135
+ const constraintScore = invariantBranches.length > 0 ? coveredInvariants.length / invariantBranches.length * 20 : 20;
2136
+ const contractScore = needContracts.length > 0 ? haveContracts.length / needContracts.length * 15 : 15;
2137
+ const contextScore = neededFields.length > 0 ? coveredFields.length / neededFields.length * 15 : 15;
2138
+ const eventScore = manifest.transitions.length > 0 ? coveredTransitions.length / manifest.transitions.length * 10 : 10;
2139
+ const score = Math.round(ruleScore + constraintScore + contractScore + contextScore + eventScore);
2140
+ const rating = score >= 90 ? "complete" : score >= 70 ? "good" : score >= 50 ? "partial" : "incomplete";
2141
+ const report = {
2142
+ score,
2143
+ rating,
2144
+ rules: { total: domainBranches.length, covered: coveredDomain.length, uncovered: uncoveredDomain },
2145
+ constraints: { total: invariantBranches.length, covered: coveredInvariants.length, uncovered: uncoveredInvariants },
2146
+ contracts: { total: needContracts.length, withContracts: haveContracts.length, missing: missingContracts },
2147
+ context: { total: neededFields.length, covered: coveredFields.length, missing: missingFields },
2148
+ events: { total: manifest.transitions.length, covered: coveredTransitions.length, missing: missingTransitions }
2149
+ };
2150
+ if (config?.strict && score < threshold) {
2151
+ throw new Error(`Praxis completeness ${score}/100 (${rating}) \u2014 below threshold ${threshold}. ${uncoveredDomain.length} uncovered rules, ${uncoveredInvariants.length} uncovered invariants, ${missingContracts.length} missing contracts.`);
2152
+ }
2153
+ return report;
2154
+ }
2155
+ function formatReport(report) {
2156
+ const lines = [];
2157
+ const icon = report.rating === "complete" ? "\u2705" : report.rating === "good" ? "\u{1F7E2}" : report.rating === "partial" ? "\u{1F7E1}" : "\u{1F534}";
2158
+ lines.push(`${icon} Praxis Completeness: ${report.score}/100 (${report.rating})`);
2159
+ lines.push("");
2160
+ lines.push(`Rules: ${report.rules.covered}/${report.rules.total} domain branches covered (${pct(report.rules.covered, report.rules.total)})`);
2161
+ lines.push(`Constraints: ${report.constraints.covered}/${report.constraints.total} invariants covered (${pct(report.constraints.covered, report.constraints.total)})`);
2162
+ lines.push(`Contracts: ${report.contracts.withContracts}/${report.contracts.total} rules have contracts (${pct(report.contracts.withContracts, report.contracts.total)})`);
2163
+ lines.push(`Context: ${report.context.covered}/${report.context.total} state fields in context (${pct(report.context.covered, report.context.total)})`);
2164
+ lines.push(`Events: ${report.events.covered}/${report.events.total} transitions have events (${pct(report.events.covered, report.events.total)})`);
2165
+ if (report.rules.uncovered.length > 0) {
2166
+ lines.push("");
2167
+ lines.push("Uncovered domain logic:");
2168
+ for (const b of report.rules.uncovered) {
2169
+ lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
2170
+ }
2171
+ }
2172
+ if (report.constraints.uncovered.length > 0) {
2173
+ lines.push("");
2174
+ lines.push("Uncovered invariants:");
2175
+ for (const b of report.constraints.uncovered) {
2176
+ lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
2177
+ }
2178
+ }
2179
+ if (report.contracts.missing.length > 0) {
2180
+ lines.push("");
2181
+ lines.push("Rules missing contracts:");
2182
+ for (const id of report.contracts.missing) {
2183
+ lines.push(` \u{1F4DD} ${id}`);
2184
+ }
2185
+ }
2186
+ if (report.events.missing.length > 0) {
2187
+ lines.push("");
2188
+ lines.push("State transitions without events:");
2189
+ for (const t of report.events.missing) {
2190
+ lines.push(` \u26A1 ${t.location}: ${t.description}`);
2191
+ }
2192
+ }
2193
+ return lines.join("\n");
2194
+ }
2195
+ function pct(a, b) {
2196
+ if (b === 0) return "100%";
2197
+ return Math.round(a / b * 100) + "%";
2198
+ }
1900
2199
  export {
1901
2200
  AcknowledgeContractGap,
1902
2201
  ActorManager,
@@ -1921,6 +2220,7 @@ export {
1921
2220
  PraxisSchemaRegistry,
1922
2221
  ReactiveLogicEngine,
1923
2222
  RegistryIntrospector,
2223
+ RuleResult,
1924
2224
  StateDocsGenerator,
1925
2225
  TerminalAdapter,
1926
2226
  ValidateContracts,
@@ -1928,6 +2228,7 @@ export {
1928
2228
  attachTauriToEngine,
1929
2229
  attachToEngine,
1930
2230
  attachUnumToEngine,
2231
+ auditCompleteness,
1931
2232
  canvasToMermaid,
1932
2233
  canvasToSchema,
1933
2234
  canvasToYaml,
@@ -1953,6 +2254,7 @@ export {
1953
2254
  createTauriPraxisAdapter,
1954
2255
  createTerminalAdapter,
1955
2256
  createTimerActor,
2257
+ createUIModule,
1956
2258
  createUnifiedApp,
1957
2259
  createUnumAdapter,
1958
2260
  defineConstraint,
@@ -1961,10 +2263,14 @@ export {
1961
2263
  defineFact,
1962
2264
  defineModule,
1963
2265
  defineRule,
2266
+ dirtyGuardRule,
2267
+ errorDisplayRule,
2268
+ fact,
1964
2269
  filterEvents,
1965
2270
  filterFacts,
1966
2271
  findEvent,
1967
2272
  findFact,
2273
+ formatReport,
1968
2274
  formatValidationReport,
1969
2275
  formatValidationReportJSON,
1970
2276
  formatValidationReportSARIF,
@@ -1975,16 +2281,26 @@ export {
1975
2281
  getEventPath,
1976
2282
  getFactPath,
1977
2283
  getSchemaPath,
2284
+ initGateRule,
1978
2285
  isContract,
1979
2286
  loadSchema,
1980
2287
  loadSchemaFromFile,
1981
2288
  loadSchemaFromJson,
1982
2289
  loadSchemaFromYaml,
2290
+ loadingGateRule,
2291
+ mustBeInitializedConstraint,
2292
+ navigationRequest,
2293
+ noInteractionWhileLoadingConstraint,
2294
+ offlineIndicatorRule,
1983
2295
  registerSchema,
2296
+ resizeEvent,
1984
2297
  runTerminalCommand,
1985
2298
  schemaToCanvas,
2299
+ uiModule,
2300
+ uiStateChanged,
1986
2301
  validateContracts,
1987
2302
  validateForGeneration,
1988
2303
  validateSchema,
1989
- validateWithGuardian
2304
+ validateWithGuardian,
2305
+ viewportRule
1990
2306
  };