@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.
- package/README.md +44 -0
- package/dist/browser/chunk-MJK3IYTJ.js +384 -0
- package/dist/browser/{chunk-K377RW4V.js → chunk-N63K4KWS.js} +1 -1
- package/dist/browser/{engine-YJZV4SLD.js → engine-YIEGSX7U.js} +1 -1
- package/dist/browser/index.d.ts +104 -2
- package/dist/browser/index.js +188 -7
- package/dist/browser/integrations/svelte.d.ts +2 -2
- package/dist/browser/integrations/svelte.js +2 -2
- package/dist/browser/{reactive-engine.svelte-9aS0kTa8.d.ts → reactive-engine.svelte-DjynI82A.d.ts} +139 -5
- package/dist/node/{chunk-PRPQO6R5.js → chunk-5JQJZADT.js} +1 -1
- package/dist/node/chunk-KMJWAFZV.js +389 -0
- package/dist/node/{chunk-5RH7UAQC.js → chunk-PTH6MD6P.js} +1 -0
- package/dist/node/cli/index.cjs +1553 -839
- package/dist/node/cli/index.js +39 -2
- package/dist/node/cloud/index.d.cts +1 -1
- package/dist/node/cloud/index.d.ts +1 -1
- package/dist/node/components/index.d.cts +2 -2
- package/dist/node/components/index.d.ts +2 -2
- package/dist/node/conversations-KQBXTP3N.js +596 -0
- package/dist/node/{engine-2DQBKBJC.js → engine-FEN5IYZ5.js} +1 -1
- package/dist/node/index.cjs +911 -43
- package/dist/node/index.d.cts +574 -7
- package/dist/node/index.d.ts +574 -7
- package/dist/node/index.js +672 -26
- package/dist/node/integrations/svelte.cjs +190 -3
- package/dist/node/integrations/svelte.d.cts +3 -3
- package/dist/node/integrations/svelte.d.ts +3 -3
- package/dist/node/integrations/svelte.js +2 -2
- package/dist/node/{protocol-Qek7ebBl.d.ts → protocol-DcyGMmWY.d.cts} +8 -1
- package/dist/node/{protocol-Qek7ebBl.d.cts → protocol-DcyGMmWY.d.ts} +8 -1
- package/dist/node/{reactive-engine.svelte-CRNqHlbv.d.ts → reactive-engine.svelte-Cg0Yc2Hs.d.cts} +145 -6
- package/dist/node/{reactive-engine.svelte-BFIZfawz.d.cts → reactive-engine.svelte-DekxqFu0.d.ts} +145 -6
- package/dist/node/{terminal-adapter-B-UK_Vdz.d.ts → terminal-adapter-CvIvgTo4.d.ts} +1 -1
- package/dist/node/{terminal-adapter-BQSIF5bf.d.cts → terminal-adapter-Db-snPJ3.d.cts} +1 -1
- package/dist/node/{validate-CNHUULQE.js → validate-EN3M4FUR.js} +1 -1
- package/dist/node/{verify-KLJRXVJS.js → verify-7VZRP2WS.js} +2 -2
- package/docs/BOT_UPDATE_POLICY.md +125 -0
- package/docs/DOGFOODING_CHECKLIST.md +254 -0
- package/docs/DOGFOODING_INDEX.md +169 -0
- package/docs/DOGFOODING_QUICK_START.md +140 -0
- package/docs/KNO_ENG_EXTRACTION_PLAN.md +577 -0
- package/docs/PLURES_TOOLS_INVENTORY.md +170 -0
- package/docs/README.md +12 -0
- package/docs/TESTING_BOT_WORKFLOWS.md +154 -0
- package/docs/conversations/INTEGRATION_POINTS.md +719 -0
- package/docs/conversations/README.md +168 -0
- package/docs/core/extending-praxis-core.md +604 -0
- package/docs/core/praxis-core-api.md +385 -0
- package/docs/decision-ledger/contract-index.json +2 -2
- package/docs/decision-ledger/decisions/2026-02-01-monorepo-organization.md +130 -0
- package/docs/examples/DOGFOODING_WORKFLOW_EXAMPLE.md +295 -0
- package/docs/examples/README.md +41 -0
- package/docs/workflows/pr-overlap-guard.md +50 -0
- package/package.json +8 -3
- package/src/__tests__/chronicle.test.ts +512 -0
- package/src/__tests__/conversations.test.ts +312 -0
- package/src/__tests__/edge-cases.test.ts +1 -1
- package/src/__tests__/engine-dx.test.ts +355 -0
- package/src/__tests__/engine-v2.test.ts +532 -0
- package/src/cli/commands/conversations.ts +252 -0
- package/src/cli/index.ts +73 -0
- package/src/conversations/README.md +230 -0
- package/src/conversations/candidate.schema.json +123 -0
- package/src/conversations/candidates.ts +114 -0
- package/src/conversations/capture.ts +56 -0
- package/src/conversations/classify.ts +110 -0
- package/src/conversations/conversation.schema.json +106 -0
- package/src/conversations/emitters/fs.ts +65 -0
- package/src/conversations/emitters/github.ts +115 -0
- package/src/conversations/gate.ts +102 -0
- package/src/conversations/index.ts +28 -0
- package/src/conversations/normalize.ts +51 -0
- package/src/conversations/redact.ts +57 -0
- package/src/conversations/types.ts +96 -0
- package/src/core/chronicle/chronicle.ts +227 -0
- package/src/core/chronicle/context.ts +80 -0
- package/src/core/chronicle/index.ts +53 -0
- package/src/core/chronicle/mcp.ts +135 -0
- package/src/core/chronicle/types.ts +61 -0
- package/src/core/completeness.ts +274 -0
- package/src/core/engine.ts +143 -3
- package/src/core/pluresdb/index.ts +22 -0
- package/src/core/pluresdb/store.ts +171 -8
- package/src/core/protocol.ts +7 -0
- package/src/core/rule-result.ts +130 -0
- package/src/core/rules.ts +24 -5
- package/src/core/ui-rules.ts +340 -0
- package/src/dsl/index.ts +6 -0
- package/src/index.ts +45 -0
- package/src/integrations/pluresdb.ts +22 -0
- package/src/vite/completeness-plugin.ts +72 -0
- package/dist/browser/chunk-VOMLVI6V.js +0 -197
- package/dist/node/chunk-VOMLVI6V.js +0 -197
package/dist/node/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
formatValidationReportJSON,
|
|
22
22
|
formatValidationReportSARIF,
|
|
23
23
|
validateContracts
|
|
24
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
82
|
-
|
|
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(
|
|
676
|
-
const constraintResult = await this.checkConstraints([
|
|
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
|
-
|
|
681
|
-
|
|
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
|
|
694
|
-
|
|
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(
|
|
703
|
-
const payload =
|
|
819
|
+
async persistFact(fact2) {
|
|
820
|
+
const payload = fact2.payload;
|
|
704
821
|
const id = payload?.id ?? generateId();
|
|
705
|
-
const path = getFactPath(
|
|
706
|
-
await this.db.set(path,
|
|
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
|
-
|
|
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, (
|
|
799
|
-
callback([
|
|
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
|
|
863
|
-
|
|
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
|
|
872
|
-
await this.persistFact(
|
|
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,
|
|
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:
|
|
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-
|
|
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
|
};
|