@plures/praxis 1.2.13 → 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 (93) hide show
  1. package/README.md +44 -0
  2. package/dist/browser/chunk-MJK3IYTJ.js +384 -0
  3. package/dist/browser/{chunk-K377RW4V.js → chunk-N63K4KWS.js} +1 -1
  4. package/dist/browser/{engine-YJZV4SLD.js → engine-YIEGSX7U.js} +1 -1
  5. package/dist/browser/index.d.ts +104 -2
  6. package/dist/browser/index.js +188 -7
  7. package/dist/browser/integrations/svelte.d.ts +2 -2
  8. package/dist/browser/integrations/svelte.js +2 -2
  9. package/dist/browser/{reactive-engine.svelte-9aS0kTa8.d.ts → reactive-engine.svelte-DjynI82A.d.ts} +139 -5
  10. package/dist/node/{chunk-PRPQO6R5.js → chunk-5JQJZADT.js} +1 -1
  11. package/dist/node/chunk-KMJWAFZV.js +389 -0
  12. package/dist/node/{chunk-5RH7UAQC.js → chunk-PTH6MD6P.js} +1 -0
  13. package/dist/node/cli/index.cjs +1553 -839
  14. package/dist/node/cli/index.js +39 -2
  15. package/dist/node/cloud/index.d.cts +1 -1
  16. package/dist/node/cloud/index.d.ts +1 -1
  17. package/dist/node/components/index.d.cts +2 -2
  18. package/dist/node/components/index.d.ts +2 -2
  19. package/dist/node/conversations-KQBXTP3N.js +596 -0
  20. package/dist/node/{engine-2DQBKBJC.js → engine-FEN5IYZ5.js} +1 -1
  21. package/dist/node/index.cjs +911 -43
  22. package/dist/node/index.d.cts +574 -7
  23. package/dist/node/index.d.ts +574 -7
  24. package/dist/node/index.js +672 -26
  25. package/dist/node/integrations/svelte.cjs +190 -3
  26. package/dist/node/integrations/svelte.d.cts +3 -3
  27. package/dist/node/integrations/svelte.d.ts +3 -3
  28. package/dist/node/integrations/svelte.js +2 -2
  29. package/dist/node/{protocol-Qek7ebBl.d.ts → protocol-DcyGMmWY.d.cts} +8 -1
  30. package/dist/node/{protocol-Qek7ebBl.d.cts → protocol-DcyGMmWY.d.ts} +8 -1
  31. package/dist/node/{reactive-engine.svelte-CRNqHlbv.d.ts → reactive-engine.svelte-Cg0Yc2Hs.d.cts} +145 -6
  32. package/dist/node/{reactive-engine.svelte-BFIZfawz.d.cts → reactive-engine.svelte-DekxqFu0.d.ts} +145 -6
  33. package/dist/node/{terminal-adapter-B-UK_Vdz.d.ts → terminal-adapter-CvIvgTo4.d.ts} +1 -1
  34. package/dist/node/{terminal-adapter-BQSIF5bf.d.cts → terminal-adapter-Db-snPJ3.d.cts} +1 -1
  35. package/dist/node/{validate-CNHUULQE.js → validate-EN3M4FUR.js} +1 -1
  36. package/dist/node/{verify-KLJRXVJS.js → verify-7VZRP2WS.js} +2 -2
  37. package/docs/BOT_UPDATE_POLICY.md +125 -0
  38. package/docs/DOGFOODING_CHECKLIST.md +254 -0
  39. package/docs/DOGFOODING_INDEX.md +169 -0
  40. package/docs/DOGFOODING_QUICK_START.md +140 -0
  41. package/docs/KNO_ENG_EXTRACTION_PLAN.md +577 -0
  42. package/docs/PLURES_TOOLS_INVENTORY.md +170 -0
  43. package/docs/README.md +12 -0
  44. package/docs/TESTING_BOT_WORKFLOWS.md +154 -0
  45. package/docs/conversations/INTEGRATION_POINTS.md +719 -0
  46. package/docs/conversations/README.md +168 -0
  47. package/docs/core/extending-praxis-core.md +604 -0
  48. package/docs/core/praxis-core-api.md +385 -0
  49. package/docs/decision-ledger/contract-index.json +2 -2
  50. package/docs/decision-ledger/decisions/2026-02-01-monorepo-organization.md +130 -0
  51. package/docs/examples/DOGFOODING_WORKFLOW_EXAMPLE.md +295 -0
  52. package/docs/examples/README.md +41 -0
  53. package/docs/workflows/pr-overlap-guard.md +50 -0
  54. package/package.json +8 -3
  55. package/src/__tests__/chronicle.test.ts +512 -0
  56. package/src/__tests__/conversations.test.ts +312 -0
  57. package/src/__tests__/edge-cases.test.ts +1 -1
  58. package/src/__tests__/engine-dx.test.ts +355 -0
  59. package/src/__tests__/engine-v2.test.ts +532 -0
  60. package/src/cli/commands/conversations.ts +252 -0
  61. package/src/cli/index.ts +73 -0
  62. package/src/conversations/README.md +230 -0
  63. package/src/conversations/candidate.schema.json +123 -0
  64. package/src/conversations/candidates.ts +114 -0
  65. package/src/conversations/capture.ts +56 -0
  66. package/src/conversations/classify.ts +110 -0
  67. package/src/conversations/conversation.schema.json +106 -0
  68. package/src/conversations/emitters/fs.ts +65 -0
  69. package/src/conversations/emitters/github.ts +115 -0
  70. package/src/conversations/gate.ts +102 -0
  71. package/src/conversations/index.ts +28 -0
  72. package/src/conversations/normalize.ts +51 -0
  73. package/src/conversations/redact.ts +57 -0
  74. package/src/conversations/types.ts +96 -0
  75. package/src/core/chronicle/chronicle.ts +227 -0
  76. package/src/core/chronicle/context.ts +80 -0
  77. package/src/core/chronicle/index.ts +53 -0
  78. package/src/core/chronicle/mcp.ts +135 -0
  79. package/src/core/chronicle/types.ts +61 -0
  80. package/src/core/completeness.ts +274 -0
  81. package/src/core/engine.ts +143 -3
  82. package/src/core/pluresdb/index.ts +22 -0
  83. package/src/core/pluresdb/store.ts +171 -8
  84. package/src/core/protocol.ts +7 -0
  85. package/src/core/rule-result.ts +130 -0
  86. package/src/core/rules.ts +24 -5
  87. package/src/core/ui-rules.ts +340 -0
  88. package/src/dsl/index.ts +6 -0
  89. package/src/index.ts +45 -0
  90. package/src/integrations/pluresdb.ts +22 -0
  91. package/src/vite/completeness-plugin.ts +72 -0
  92. package/dist/browser/chunk-VOMLVI6V.js +0 -197
  93. package/dist/node/chunk-VOMLVI6V.js +0 -197
@@ -21,7 +21,7 @@ import {
21
21
  formatValidationReportJSON,
22
22
  formatValidationReportSARIF,
23
23
  validateContracts
24
- } from "./chunk-5RH7UAQC.js";
24
+ } from "./chunk-PTH6MD6P.js";
25
25
  import {
26
26
  defineContract,
27
27
  getContract,
@@ -50,7 +50,7 @@ import {
50
50
  import {
51
51
  ReactiveLogicEngine,
52
52
  createReactiveEngine
53
- } from "./chunk-PRPQO6R5.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-VOMLVI6V.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
@@ -622,6 +624,53 @@ function createIntrospector(registry) {
622
624
  return new RegistryIntrospector(registry);
623
625
  }
624
626
 
627
+ // src/core/chronicle/context.ts
628
+ var ChronicleContext = class {
629
+ static _stack = [];
630
+ /**
631
+ * Get the current active span, if any.
632
+ */
633
+ static get current() {
634
+ return this._stack[this._stack.length - 1];
635
+ }
636
+ /**
637
+ * Run a synchronous function within a causal span.
638
+ * The span is automatically popped when the function returns.
639
+ */
640
+ static run(span, fn) {
641
+ this._stack.push(span);
642
+ try {
643
+ return fn();
644
+ } finally {
645
+ this._stack.pop();
646
+ }
647
+ }
648
+ /**
649
+ * Run an async function within a causal span.
650
+ * The span is popped after the promise settles.
651
+ */
652
+ static async runAsync(span, fn) {
653
+ this._stack.push(span);
654
+ try {
655
+ return await fn();
656
+ } finally {
657
+ this._stack.pop();
658
+ }
659
+ }
660
+ /**
661
+ * Create a child span that inherits the current contextId.
662
+ *
663
+ * @param spanId ID for the new span
664
+ * @returns A new ChronicleSpan with the current contextId
665
+ */
666
+ static childSpan(spanId) {
667
+ return {
668
+ spanId,
669
+ contextId: this.current?.contextId
670
+ };
671
+ }
672
+ };
673
+
625
674
  // src/core/pluresdb/store.ts
626
675
  var PRAXIS_PATHS = {
627
676
  /** Base path for all Praxis data */
@@ -657,12 +706,32 @@ var PraxisDBStore = class {
657
706
  subscriptions = [];
658
707
  factWatchers = /* @__PURE__ */ new Map();
659
708
  onRuleError;
709
+ chronicle;
660
710
  constructor(options) {
661
711
  this.db = options.db;
662
712
  this.registry = options.registry;
663
713
  this.context = options.initialContext ?? {};
664
714
  this.onRuleError = options.onRuleError ?? defaultErrorHandler;
665
715
  }
716
+ /**
717
+ * Attach a Chronicle observer to this store.
718
+ *
719
+ * Every subsequent `storeFact` and `appendEvent` call will be recorded as a
720
+ * causal graph node in PluresDB, enabling full observability for free.
721
+ *
722
+ * @param chronicle Chronicle implementation to attach
723
+ * @returns `this` for fluent chaining
724
+ *
725
+ * @example
726
+ * ```typescript
727
+ * const store = createPraxisDBStore(db, registry)
728
+ * .withChronicle(createChronicle(db));
729
+ * ```
730
+ */
731
+ withChronicle(chronicle) {
732
+ this.chronicle = chronicle;
733
+ return this;
734
+ }
666
735
  /**
667
736
  * Store a fact in PluresDB
668
737
  *
@@ -672,13 +741,37 @@ var PraxisDBStore = class {
672
741
  * @param fact The fact to store
673
742
  * @returns Promise that resolves when the fact is stored
674
743
  */
675
- async storeFact(fact) {
676
- const constraintResult = await this.checkConstraints([fact]);
744
+ async storeFact(fact2) {
745
+ const constraintResult = await this.checkConstraints([fact2]);
677
746
  if (!constraintResult.valid) {
678
747
  throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
679
748
  }
680
- await this.persistFact(fact);
681
- await this.triggerRules([fact]);
749
+ let before;
750
+ if (this.chronicle) {
751
+ const payload = fact2.payload;
752
+ const id = payload?.id;
753
+ if (id) {
754
+ before = await this.getFact(fact2.tag, id);
755
+ }
756
+ }
757
+ await this.persistFact(fact2);
758
+ if (this.chronicle) {
759
+ const payload = fact2.payload;
760
+ const id = payload?.id ?? "";
761
+ const span = ChronicleContext.current;
762
+ try {
763
+ await this.chronicle.record({
764
+ path: getFactPath(fact2.tag, id),
765
+ before,
766
+ after: fact2,
767
+ cause: span?.spanId,
768
+ context: span?.contextId,
769
+ metadata: { factTag: fact2.tag, operation: "storeFact" }
770
+ });
771
+ } catch {
772
+ }
773
+ }
774
+ await this.triggerRules([fact2]);
682
775
  }
683
776
  /**
684
777
  * Store multiple facts in PluresDB
@@ -690,8 +783,32 @@ var PraxisDBStore = class {
690
783
  if (!constraintResult.valid) {
691
784
  throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
692
785
  }
693
- for (const fact of facts) {
694
- await this.persistFact(fact);
786
+ for (const fact2 of facts) {
787
+ let before;
788
+ if (this.chronicle) {
789
+ const payload = fact2.payload;
790
+ const id = payload?.id;
791
+ if (id) {
792
+ before = await this.getFact(fact2.tag, id);
793
+ }
794
+ }
795
+ await this.persistFact(fact2);
796
+ if (this.chronicle) {
797
+ const payload = fact2.payload;
798
+ const id = payload?.id ?? "";
799
+ const span = ChronicleContext.current;
800
+ try {
801
+ await this.chronicle.record({
802
+ path: getFactPath(fact2.tag, id),
803
+ before,
804
+ after: fact2,
805
+ cause: span?.spanId,
806
+ context: span?.contextId,
807
+ metadata: { factTag: fact2.tag, operation: "storeFacts" }
808
+ });
809
+ } catch {
810
+ }
811
+ }
695
812
  }
696
813
  await this.triggerRules(facts);
697
814
  }
@@ -699,11 +816,11 @@ var PraxisDBStore = class {
699
816
  * Internal method to persist a fact without constraint checking
700
817
  * Used by both storeFact and derived fact storage
701
818
  */
702
- async persistFact(fact) {
703
- const payload = fact.payload;
819
+ async persistFact(fact2) {
820
+ const payload = fact2.payload;
704
821
  const id = payload?.id ?? generateId();
705
- const path = getFactPath(fact.tag, id);
706
- await this.db.set(path, fact);
822
+ const path = getFactPath(fact2.tag, id);
823
+ await this.db.set(path, fact2);
707
824
  }
708
825
  /**
709
826
  * Get a fact by tag and id
@@ -733,7 +850,29 @@ var PraxisDBStore = class {
733
850
  };
734
851
  const newEvents = [...existingEvents, entry];
735
852
  await this.db.set(path, newEvents);
736
- await this.triggerRulesForEvents([event]);
853
+ let eventNodeId;
854
+ if (this.chronicle) {
855
+ const span = ChronicleContext.current;
856
+ try {
857
+ const node = await this.chronicle.record({
858
+ path,
859
+ before: existingEvents.length > 0 ? existingEvents[existingEvents.length - 1] : void 0,
860
+ after: entry,
861
+ cause: span?.spanId,
862
+ context: span?.contextId,
863
+ metadata: { eventTag: event.tag, sequence: String(entry.sequence), operation: "appendEvent" }
864
+ });
865
+ eventNodeId = node.id;
866
+ } catch {
867
+ }
868
+ }
869
+ const outerSpan = ChronicleContext.current;
870
+ const ruleSpan = eventNodeId ? { spanId: eventNodeId, contextId: outerSpan?.contextId } : outerSpan;
871
+ if (ruleSpan && this.chronicle) {
872
+ await ChronicleContext.runAsync(ruleSpan, () => this.triggerRulesForEvents([event]));
873
+ } else {
874
+ await this.triggerRulesForEvents([event]);
875
+ }
737
876
  }
738
877
  /**
739
878
  * Append multiple events to their respective streams
@@ -746,6 +885,7 @@ var PraxisDBStore = class {
746
885
  const existing = eventsByTag.get(event.tag) ?? [];
747
886
  eventsByTag.set(event.tag, [...existing, event]);
748
887
  }
888
+ let lastEventNodeId;
749
889
  for (const [tag, tagEvents] of eventsByTag) {
750
890
  const path = getEventPath(tag);
751
891
  const existingEvents = await this.db.get(path) ?? [];
@@ -756,8 +896,30 @@ var PraxisDBStore = class {
756
896
  sequence: sequence++
757
897
  }));
758
898
  await this.db.set(path, [...existingEvents, ...newEntries]);
899
+ if (this.chronicle) {
900
+ const span = ChronicleContext.current;
901
+ for (const entry of newEntries) {
902
+ try {
903
+ const node = await this.chronicle.record({
904
+ path,
905
+ after: entry,
906
+ cause: span?.spanId,
907
+ context: span?.contextId,
908
+ metadata: { eventTag: tag, sequence: String(entry.sequence), operation: "appendEvents" }
909
+ });
910
+ lastEventNodeId = node.id;
911
+ } catch {
912
+ }
913
+ }
914
+ }
915
+ }
916
+ const outerSpan = ChronicleContext.current;
917
+ const ruleSpan = lastEventNodeId ? { spanId: lastEventNodeId, contextId: outerSpan?.contextId } : outerSpan;
918
+ if (ruleSpan && this.chronicle) {
919
+ await ChronicleContext.runAsync(ruleSpan, () => this.triggerRulesForEvents(events));
920
+ } else {
921
+ await this.triggerRulesForEvents(events);
759
922
  }
760
- await this.triggerRulesForEvents(events);
761
923
  }
762
924
  /**
763
925
  * Get events from a stream
@@ -795,8 +957,8 @@ var PraxisDBStore = class {
795
957
  if (watchers) {
796
958
  watchers.add(callback);
797
959
  }
798
- const unsubscribe = this.db.watch(path, (fact) => {
799
- callback([fact]);
960
+ const unsubscribe = this.db.watch(path, (fact2) => {
961
+ callback([fact2]);
800
962
  });
801
963
  this.subscriptions.push(unsubscribe);
802
964
  return () => {
@@ -854,13 +1016,18 @@ var PraxisDBStore = class {
854
1016
  const state = {
855
1017
  context: this.context,
856
1018
  facts: [],
1019
+ events,
857
1020
  meta: {}
858
1021
  };
859
1022
  const derivedFacts = [];
860
1023
  for (const rule of rules) {
861
1024
  try {
862
- const facts = rule.impl(state, events);
863
- 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
+ }
864
1031
  } catch (error) {
865
1032
  this.onRuleError(rule.id, error);
866
1033
  }
@@ -868,8 +1035,23 @@ var PraxisDBStore = class {
868
1035
  if (derivedFacts.length > 0) {
869
1036
  const constraintResult = await this.checkConstraints(derivedFacts);
870
1037
  if (constraintResult.valid) {
871
- for (const fact of derivedFacts) {
872
- await this.persistFact(fact);
1038
+ for (const fact2 of derivedFacts) {
1039
+ await this.persistFact(fact2);
1040
+ if (this.chronicle) {
1041
+ const payload = fact2.payload;
1042
+ const id = payload?.id ?? "";
1043
+ const span = ChronicleContext.current;
1044
+ try {
1045
+ await this.chronicle.record({
1046
+ path: getFactPath(fact2.tag, id),
1047
+ after: fact2,
1048
+ cause: span?.spanId,
1049
+ context: span?.contextId,
1050
+ metadata: { factTag: fact2.tag, operation: "derivedFact" }
1051
+ });
1052
+ } catch {
1053
+ }
1054
+ }
873
1055
  }
874
1056
  }
875
1057
  }
@@ -989,6 +1171,156 @@ function createSchemaRegistry(db) {
989
1171
  return new PraxisSchemaRegistry(db);
990
1172
  }
991
1173
 
1174
+ // src/core/chronicle/chronicle.ts
1175
+ var CHRONICLE_PATHS = {
1176
+ BASE: "/_praxis/chronos",
1177
+ NODES: "/_praxis/chronos/nodes",
1178
+ EDGES_OUT: "/_praxis/chronos/edges/out",
1179
+ EDGES_IN: "/_praxis/chronos/edges/in",
1180
+ CONTEXT: "/_praxis/chronos/context",
1181
+ INDEX: "/_praxis/chronos/index"
1182
+ };
1183
+ var _nodeCounter = 0;
1184
+ var PluresDbChronicle = class {
1185
+ db;
1186
+ constructor(db) {
1187
+ this.db = db;
1188
+ }
1189
+ async record(event) {
1190
+ const timestamp = Date.now();
1191
+ const id = `chronos:${timestamp}-${++_nodeCounter}`;
1192
+ const node = { id, timestamp, event };
1193
+ await this.db.set(`${CHRONICLE_PATHS.NODES}/${id}`, node);
1194
+ const index = await this.db.get(CHRONICLE_PATHS.INDEX) ?? [];
1195
+ await this.db.set(CHRONICLE_PATHS.INDEX, [...index, id]);
1196
+ if (event.cause) {
1197
+ await this.addEdge(event.cause, id, "causes");
1198
+ }
1199
+ if (event.context) {
1200
+ const contextPath = `${CHRONICLE_PATHS.CONTEXT}/${event.context}`;
1201
+ const contextNodes = await this.db.get(contextPath) ?? [];
1202
+ if (contextNodes.length > 0) {
1203
+ const prevId = contextNodes[contextNodes.length - 1];
1204
+ await this.addEdge(prevId, id, "follows");
1205
+ }
1206
+ await this.db.set(contextPath, [...contextNodes, id]);
1207
+ }
1208
+ return node;
1209
+ }
1210
+ async trace(nodeId, direction, maxDepth) {
1211
+ const visited = /* @__PURE__ */ new Set();
1212
+ const result = [];
1213
+ await this._traceRecursive(nodeId, direction, maxDepth, 0, visited, result);
1214
+ return result;
1215
+ }
1216
+ async range(start, end) {
1217
+ const index = await this.db.get(CHRONICLE_PATHS.INDEX) ?? [];
1218
+ const result = [];
1219
+ for (const id of index) {
1220
+ const node = await this.db.get(`${CHRONICLE_PATHS.NODES}/${id}`);
1221
+ if (node && node.timestamp >= start && node.timestamp <= end) {
1222
+ result.push(node);
1223
+ }
1224
+ }
1225
+ return result;
1226
+ }
1227
+ async subgraph(contextId) {
1228
+ const contextPath = `${CHRONICLE_PATHS.CONTEXT}/${contextId}`;
1229
+ const nodeIds = await this.db.get(contextPath) ?? [];
1230
+ const result = [];
1231
+ for (const id of nodeIds) {
1232
+ const node = await this.db.get(`${CHRONICLE_PATHS.NODES}/${id}`);
1233
+ if (node) {
1234
+ result.push(node);
1235
+ }
1236
+ }
1237
+ return result;
1238
+ }
1239
+ // ── Internal helpers ──────────────────────────────────────────────────────
1240
+ async addEdge(from, to, type) {
1241
+ const edge = { from, to, type };
1242
+ const outPath = `${CHRONICLE_PATHS.EDGES_OUT}/${from}`;
1243
+ const outEdges = await this.db.get(outPath) ?? [];
1244
+ await this.db.set(outPath, [...outEdges, edge]);
1245
+ const inPath = `${CHRONICLE_PATHS.EDGES_IN}/${to}`;
1246
+ const inEdges = await this.db.get(inPath) ?? [];
1247
+ await this.db.set(inPath, [...inEdges, edge]);
1248
+ }
1249
+ async _traceRecursive(nodeId, direction, maxDepth, depth, visited, result) {
1250
+ if (depth > maxDepth || visited.has(nodeId)) {
1251
+ return;
1252
+ }
1253
+ visited.add(nodeId);
1254
+ const node = await this.db.get(`${CHRONICLE_PATHS.NODES}/${nodeId}`);
1255
+ if (node) {
1256
+ result.push(node);
1257
+ }
1258
+ if (direction === "backward" || direction === "both") {
1259
+ const inEdges = await this.db.get(`${CHRONICLE_PATHS.EDGES_IN}/${nodeId}`) ?? [];
1260
+ for (const edge of inEdges) {
1261
+ await this._traceRecursive(edge.from, direction, maxDepth, depth + 1, visited, result);
1262
+ }
1263
+ }
1264
+ if (direction === "forward" || direction === "both") {
1265
+ const outEdges = await this.db.get(`${CHRONICLE_PATHS.EDGES_OUT}/${nodeId}`) ?? [];
1266
+ for (const edge of outEdges) {
1267
+ await this._traceRecursive(edge.to, direction, maxDepth, depth + 1, visited, result);
1268
+ }
1269
+ }
1270
+ }
1271
+ };
1272
+ function createChronicle(db) {
1273
+ return new PluresDbChronicle(db);
1274
+ }
1275
+
1276
+ // src/core/chronicle/mcp.ts
1277
+ function createChronosMcpTools(chronicle) {
1278
+ return {
1279
+ async trace(params) {
1280
+ try {
1281
+ const nodes = await chronicle.trace(
1282
+ params.nodeId,
1283
+ params.direction ?? "backward",
1284
+ params.maxDepth ?? 10
1285
+ );
1286
+ return { success: true, data: nodes };
1287
+ } catch (error) {
1288
+ return {
1289
+ success: false,
1290
+ error: error instanceof Error ? error.message : String(error)
1291
+ };
1292
+ }
1293
+ },
1294
+ async search(params) {
1295
+ try {
1296
+ const query = params.query.toLowerCase();
1297
+ let candidates;
1298
+ if (params.contextId) {
1299
+ candidates = await chronicle.subgraph(params.contextId);
1300
+ } else {
1301
+ candidates = await chronicle.range(params.since ?? 0, params.until ?? Date.now());
1302
+ }
1303
+ const filtered = candidates.filter((node) => {
1304
+ const inPath = node.event.path.toLowerCase().includes(query);
1305
+ const inMeta = Object.values(node.event.metadata).some(
1306
+ (v) => v.toLowerCase().includes(query)
1307
+ );
1308
+ const inAfter = JSON.stringify(node.event.after ?? "").toLowerCase().includes(query);
1309
+ const inBefore = JSON.stringify(node.event.before ?? "").toLowerCase().includes(query);
1310
+ return inPath || inMeta || inAfter || inBefore;
1311
+ });
1312
+ const limited = params.limit !== void 0 ? filtered.slice(0, params.limit) : filtered;
1313
+ return { success: true, data: limited };
1314
+ } catch (error) {
1315
+ return {
1316
+ success: false,
1317
+ error: error instanceof Error ? error.message : String(error)
1318
+ };
1319
+ }
1320
+ }
1321
+ };
1322
+ }
1323
+
992
1324
  // src/integrations/pluresdb.ts
993
1325
  function createPluresDBAdapter(options) {
994
1326
  const store = createPraxisDBStore(options.db, options.registry, options.initialContext);
@@ -1154,7 +1486,7 @@ async function createUnumAdapter(config) {
1154
1486
  type: "event"
1155
1487
  });
1156
1488
  }
1157
- async function broadcastFact(channelId, fact) {
1489
+ async function broadcastFact(channelId, fact2) {
1158
1490
  const channel = channels.get(channelId);
1159
1491
  if (!channel) {
1160
1492
  throw new Error(`Not joined to channel ${channelId}`);
@@ -1162,7 +1494,7 @@ async function createUnumAdapter(config) {
1162
1494
  await channel.publish({
1163
1495
  id: generateId2(),
1164
1496
  sender: currentIdentity || { id: "anonymous", createdAt: Date.now() },
1165
- content: fact,
1497
+ content: fact2,
1166
1498
  type: "fact"
1167
1499
  });
1168
1500
  }
@@ -1443,7 +1775,7 @@ function generateTauriConfig(config) {
1443
1775
 
1444
1776
  // src/integrations/unified.ts
1445
1777
  async function createUnifiedApp(config) {
1446
- const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-2DQBKBJC.js");
1778
+ const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-FEN5IYZ5.js");
1447
1779
  const { createInMemoryDB: createInMemoryDB2 } = await import("./adapter-75ISSMWD.js");
1448
1780
  const db = config.db || createInMemoryDB2();
1449
1781
  const pluresdb = createPluresDBAdapter({
@@ -1572,10 +1904,304 @@ async function attachAllIntegrations(engine, registry, options = {}) {
1572
1904
  }
1573
1905
  };
1574
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
+ }
1575
2199
  export {
1576
2200
  AcknowledgeContractGap,
1577
2201
  ActorManager,
1578
2202
  BehaviorLedger,
2203
+ CHRONICLE_PATHS,
2204
+ ChronicleContext,
1579
2205
  ContractAdded,
1580
2206
  ContractGapAcknowledged,
1581
2207
  ContractMissing,
@@ -1588,11 +2214,13 @@ export {
1588
2214
  PRAXIS_PROTOCOL_VERSION,
1589
2215
  PluresDBGenerator,
1590
2216
  PluresDBPraxisAdapter,
2217
+ PluresDbChronicle,
1591
2218
  PraxisDBStore,
1592
2219
  PraxisRegistry,
1593
2220
  PraxisSchemaRegistry,
1594
2221
  ReactiveLogicEngine,
1595
2222
  RegistryIntrospector,
2223
+ RuleResult,
1596
2224
  StateDocsGenerator,
1597
2225
  TerminalAdapter,
1598
2226
  ValidateContracts,
@@ -1600,11 +2228,14 @@ export {
1600
2228
  attachTauriToEngine,
1601
2229
  attachToEngine,
1602
2230
  attachUnumToEngine,
2231
+ auditCompleteness,
1603
2232
  canvasToMermaid,
1604
2233
  canvasToSchema,
1605
2234
  canvasToYaml,
1606
2235
  createBehaviorLedger,
1607
2236
  createCanvasEditor,
2237
+ createChronicle,
2238
+ createChronosMcpTools,
1608
2239
  createReactiveEngine2 as createFrameworkAgnosticReactiveEngine,
1609
2240
  createInMemoryDB,
1610
2241
  createIntrospector,
@@ -1623,6 +2254,7 @@ export {
1623
2254
  createTauriPraxisAdapter,
1624
2255
  createTerminalAdapter,
1625
2256
  createTimerActor,
2257
+ createUIModule,
1626
2258
  createUnifiedApp,
1627
2259
  createUnumAdapter,
1628
2260
  defineConstraint,
@@ -1631,10 +2263,14 @@ export {
1631
2263
  defineFact,
1632
2264
  defineModule,
1633
2265
  defineRule,
2266
+ dirtyGuardRule,
2267
+ errorDisplayRule,
2268
+ fact,
1634
2269
  filterEvents,
1635
2270
  filterFacts,
1636
2271
  findEvent,
1637
2272
  findFact,
2273
+ formatReport,
1638
2274
  formatValidationReport,
1639
2275
  formatValidationReportJSON,
1640
2276
  formatValidationReportSARIF,
@@ -1645,16 +2281,26 @@ export {
1645
2281
  getEventPath,
1646
2282
  getFactPath,
1647
2283
  getSchemaPath,
2284
+ initGateRule,
1648
2285
  isContract,
1649
2286
  loadSchema,
1650
2287
  loadSchemaFromFile,
1651
2288
  loadSchemaFromJson,
1652
2289
  loadSchemaFromYaml,
2290
+ loadingGateRule,
2291
+ mustBeInitializedConstraint,
2292
+ navigationRequest,
2293
+ noInteractionWhileLoadingConstraint,
2294
+ offlineIndicatorRule,
1653
2295
  registerSchema,
2296
+ resizeEvent,
1654
2297
  runTerminalCommand,
1655
2298
  schemaToCanvas,
2299
+ uiModule,
2300
+ uiStateChanged,
1656
2301
  validateContracts,
1657
2302
  validateForGeneration,
1658
2303
  validateSchema,
1659
- validateWithGuardian
2304
+ validateWithGuardian,
2305
+ viewportRule
1660
2306
  };