@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/browser/index.js
CHANGED
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
PraxisRegistry,
|
|
3
3
|
ReactiveLogicEngine,
|
|
4
4
|
createReactiveEngine
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-N63K4KWS.js";
|
|
6
6
|
import {
|
|
7
7
|
LogicEngine,
|
|
8
8
|
PRAXIS_PROTOCOL_VERSION,
|
|
9
9
|
createPraxisEngine
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MJK3IYTJ.js";
|
|
11
11
|
import {
|
|
12
12
|
InMemoryPraxisDB,
|
|
13
13
|
PluresDBPraxisAdapter,
|
|
@@ -586,6 +586,7 @@ function defineRule(options) {
|
|
|
586
586
|
id: options.id,
|
|
587
587
|
description: options.description,
|
|
588
588
|
impl: options.impl,
|
|
589
|
+
eventTypes: options.eventTypes,
|
|
589
590
|
contract,
|
|
590
591
|
meta
|
|
591
592
|
};
|
|
@@ -884,6 +885,53 @@ function validateForGeneration(schema) {
|
|
|
884
885
|
};
|
|
885
886
|
}
|
|
886
887
|
|
|
888
|
+
// src/core/chronicle/context.ts
|
|
889
|
+
var ChronicleContext = class {
|
|
890
|
+
static _stack = [];
|
|
891
|
+
/**
|
|
892
|
+
* Get the current active span, if any.
|
|
893
|
+
*/
|
|
894
|
+
static get current() {
|
|
895
|
+
return this._stack[this._stack.length - 1];
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Run a synchronous function within a causal span.
|
|
899
|
+
* The span is automatically popped when the function returns.
|
|
900
|
+
*/
|
|
901
|
+
static run(span, fn) {
|
|
902
|
+
this._stack.push(span);
|
|
903
|
+
try {
|
|
904
|
+
return fn();
|
|
905
|
+
} finally {
|
|
906
|
+
this._stack.pop();
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Run an async function within a causal span.
|
|
911
|
+
* The span is popped after the promise settles.
|
|
912
|
+
*/
|
|
913
|
+
static async runAsync(span, fn) {
|
|
914
|
+
this._stack.push(span);
|
|
915
|
+
try {
|
|
916
|
+
return await fn();
|
|
917
|
+
} finally {
|
|
918
|
+
this._stack.pop();
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Create a child span that inherits the current contextId.
|
|
923
|
+
*
|
|
924
|
+
* @param spanId ID for the new span
|
|
925
|
+
* @returns A new ChronicleSpan with the current contextId
|
|
926
|
+
*/
|
|
927
|
+
static childSpan(spanId) {
|
|
928
|
+
return {
|
|
929
|
+
spanId,
|
|
930
|
+
contextId: this.current?.contextId
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
|
|
887
935
|
// src/core/pluresdb/store.ts
|
|
888
936
|
var PRAXIS_PATHS = {
|
|
889
937
|
/** Base path for all Praxis data */
|
|
@@ -919,12 +967,32 @@ var PraxisDBStore = class {
|
|
|
919
967
|
subscriptions = [];
|
|
920
968
|
factWatchers = /* @__PURE__ */ new Map();
|
|
921
969
|
onRuleError;
|
|
970
|
+
chronicle;
|
|
922
971
|
constructor(options) {
|
|
923
972
|
this.db = options.db;
|
|
924
973
|
this.registry = options.registry;
|
|
925
974
|
this.context = options.initialContext ?? {};
|
|
926
975
|
this.onRuleError = options.onRuleError ?? defaultErrorHandler;
|
|
927
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* Attach a Chronicle observer to this store.
|
|
979
|
+
*
|
|
980
|
+
* Every subsequent `storeFact` and `appendEvent` call will be recorded as a
|
|
981
|
+
* causal graph node in PluresDB, enabling full observability for free.
|
|
982
|
+
*
|
|
983
|
+
* @param chronicle Chronicle implementation to attach
|
|
984
|
+
* @returns `this` for fluent chaining
|
|
985
|
+
*
|
|
986
|
+
* @example
|
|
987
|
+
* ```typescript
|
|
988
|
+
* const store = createPraxisDBStore(db, registry)
|
|
989
|
+
* .withChronicle(createChronicle(db));
|
|
990
|
+
* ```
|
|
991
|
+
*/
|
|
992
|
+
withChronicle(chronicle) {
|
|
993
|
+
this.chronicle = chronicle;
|
|
994
|
+
return this;
|
|
995
|
+
}
|
|
928
996
|
/**
|
|
929
997
|
* Store a fact in PluresDB
|
|
930
998
|
*
|
|
@@ -939,7 +1007,31 @@ var PraxisDBStore = class {
|
|
|
939
1007
|
if (!constraintResult.valid) {
|
|
940
1008
|
throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
|
|
941
1009
|
}
|
|
1010
|
+
let before;
|
|
1011
|
+
if (this.chronicle) {
|
|
1012
|
+
const payload = fact.payload;
|
|
1013
|
+
const id = payload?.id;
|
|
1014
|
+
if (id) {
|
|
1015
|
+
before = await this.getFact(fact.tag, id);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
942
1018
|
await this.persistFact(fact);
|
|
1019
|
+
if (this.chronicle) {
|
|
1020
|
+
const payload = fact.payload;
|
|
1021
|
+
const id = payload?.id ?? "";
|
|
1022
|
+
const span = ChronicleContext.current;
|
|
1023
|
+
try {
|
|
1024
|
+
await this.chronicle.record({
|
|
1025
|
+
path: getFactPath(fact.tag, id),
|
|
1026
|
+
before,
|
|
1027
|
+
after: fact,
|
|
1028
|
+
cause: span?.spanId,
|
|
1029
|
+
context: span?.contextId,
|
|
1030
|
+
metadata: { factTag: fact.tag, operation: "storeFact" }
|
|
1031
|
+
});
|
|
1032
|
+
} catch {
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
943
1035
|
await this.triggerRules([fact]);
|
|
944
1036
|
}
|
|
945
1037
|
/**
|
|
@@ -953,7 +1045,31 @@ var PraxisDBStore = class {
|
|
|
953
1045
|
throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
|
|
954
1046
|
}
|
|
955
1047
|
for (const fact of facts) {
|
|
1048
|
+
let before;
|
|
1049
|
+
if (this.chronicle) {
|
|
1050
|
+
const payload = fact.payload;
|
|
1051
|
+
const id = payload?.id;
|
|
1052
|
+
if (id) {
|
|
1053
|
+
before = await this.getFact(fact.tag, id);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
956
1056
|
await this.persistFact(fact);
|
|
1057
|
+
if (this.chronicle) {
|
|
1058
|
+
const payload = fact.payload;
|
|
1059
|
+
const id = payload?.id ?? "";
|
|
1060
|
+
const span = ChronicleContext.current;
|
|
1061
|
+
try {
|
|
1062
|
+
await this.chronicle.record({
|
|
1063
|
+
path: getFactPath(fact.tag, id),
|
|
1064
|
+
before,
|
|
1065
|
+
after: fact,
|
|
1066
|
+
cause: span?.spanId,
|
|
1067
|
+
context: span?.contextId,
|
|
1068
|
+
metadata: { factTag: fact.tag, operation: "storeFacts" }
|
|
1069
|
+
});
|
|
1070
|
+
} catch {
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
957
1073
|
}
|
|
958
1074
|
await this.triggerRules(facts);
|
|
959
1075
|
}
|
|
@@ -995,7 +1111,29 @@ var PraxisDBStore = class {
|
|
|
995
1111
|
};
|
|
996
1112
|
const newEvents = [...existingEvents, entry];
|
|
997
1113
|
await this.db.set(path, newEvents);
|
|
998
|
-
|
|
1114
|
+
let eventNodeId;
|
|
1115
|
+
if (this.chronicle) {
|
|
1116
|
+
const span = ChronicleContext.current;
|
|
1117
|
+
try {
|
|
1118
|
+
const node = await this.chronicle.record({
|
|
1119
|
+
path,
|
|
1120
|
+
before: existingEvents.length > 0 ? existingEvents[existingEvents.length - 1] : void 0,
|
|
1121
|
+
after: entry,
|
|
1122
|
+
cause: span?.spanId,
|
|
1123
|
+
context: span?.contextId,
|
|
1124
|
+
metadata: { eventTag: event.tag, sequence: String(entry.sequence), operation: "appendEvent" }
|
|
1125
|
+
});
|
|
1126
|
+
eventNodeId = node.id;
|
|
1127
|
+
} catch {
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const outerSpan = ChronicleContext.current;
|
|
1131
|
+
const ruleSpan = eventNodeId ? { spanId: eventNodeId, contextId: outerSpan?.contextId } : outerSpan;
|
|
1132
|
+
if (ruleSpan && this.chronicle) {
|
|
1133
|
+
await ChronicleContext.runAsync(ruleSpan, () => this.triggerRulesForEvents([event]));
|
|
1134
|
+
} else {
|
|
1135
|
+
await this.triggerRulesForEvents([event]);
|
|
1136
|
+
}
|
|
999
1137
|
}
|
|
1000
1138
|
/**
|
|
1001
1139
|
* Append multiple events to their respective streams
|
|
@@ -1008,6 +1146,7 @@ var PraxisDBStore = class {
|
|
|
1008
1146
|
const existing = eventsByTag.get(event.tag) ?? [];
|
|
1009
1147
|
eventsByTag.set(event.tag, [...existing, event]);
|
|
1010
1148
|
}
|
|
1149
|
+
let lastEventNodeId;
|
|
1011
1150
|
for (const [tag, tagEvents] of eventsByTag) {
|
|
1012
1151
|
const path = getEventPath(tag);
|
|
1013
1152
|
const existingEvents = await this.db.get(path) ?? [];
|
|
@@ -1018,8 +1157,30 @@ var PraxisDBStore = class {
|
|
|
1018
1157
|
sequence: sequence++
|
|
1019
1158
|
}));
|
|
1020
1159
|
await this.db.set(path, [...existingEvents, ...newEntries]);
|
|
1160
|
+
if (this.chronicle) {
|
|
1161
|
+
const span = ChronicleContext.current;
|
|
1162
|
+
for (const entry of newEntries) {
|
|
1163
|
+
try {
|
|
1164
|
+
const node = await this.chronicle.record({
|
|
1165
|
+
path,
|
|
1166
|
+
after: entry,
|
|
1167
|
+
cause: span?.spanId,
|
|
1168
|
+
context: span?.contextId,
|
|
1169
|
+
metadata: { eventTag: tag, sequence: String(entry.sequence), operation: "appendEvents" }
|
|
1170
|
+
});
|
|
1171
|
+
lastEventNodeId = node.id;
|
|
1172
|
+
} catch {
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const outerSpan = ChronicleContext.current;
|
|
1178
|
+
const ruleSpan = lastEventNodeId ? { spanId: lastEventNodeId, contextId: outerSpan?.contextId } : outerSpan;
|
|
1179
|
+
if (ruleSpan && this.chronicle) {
|
|
1180
|
+
await ChronicleContext.runAsync(ruleSpan, () => this.triggerRulesForEvents(events));
|
|
1181
|
+
} else {
|
|
1182
|
+
await this.triggerRulesForEvents(events);
|
|
1021
1183
|
}
|
|
1022
|
-
await this.triggerRulesForEvents(events);
|
|
1023
1184
|
}
|
|
1024
1185
|
/**
|
|
1025
1186
|
* Get events from a stream
|
|
@@ -1116,13 +1277,18 @@ var PraxisDBStore = class {
|
|
|
1116
1277
|
const state = {
|
|
1117
1278
|
context: this.context,
|
|
1118
1279
|
facts: [],
|
|
1280
|
+
events,
|
|
1119
1281
|
meta: {}
|
|
1120
1282
|
};
|
|
1121
1283
|
const derivedFacts = [];
|
|
1122
1284
|
for (const rule of rules) {
|
|
1123
1285
|
try {
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1286
|
+
const result = rule.impl(state, events);
|
|
1287
|
+
if (Array.isArray(result)) {
|
|
1288
|
+
derivedFacts.push(...result);
|
|
1289
|
+
} else if (result && "kind" in result && result.kind === "emit") {
|
|
1290
|
+
derivedFacts.push(...result.facts);
|
|
1291
|
+
}
|
|
1126
1292
|
} catch (error) {
|
|
1127
1293
|
this.onRuleError(rule.id, error);
|
|
1128
1294
|
}
|
|
@@ -1132,6 +1298,21 @@ var PraxisDBStore = class {
|
|
|
1132
1298
|
if (constraintResult.valid) {
|
|
1133
1299
|
for (const fact of derivedFacts) {
|
|
1134
1300
|
await this.persistFact(fact);
|
|
1301
|
+
if (this.chronicle) {
|
|
1302
|
+
const payload = fact.payload;
|
|
1303
|
+
const id = payload?.id ?? "";
|
|
1304
|
+
const span = ChronicleContext.current;
|
|
1305
|
+
try {
|
|
1306
|
+
await this.chronicle.record({
|
|
1307
|
+
path: getFactPath(fact.tag, id),
|
|
1308
|
+
after: fact,
|
|
1309
|
+
cause: span?.spanId,
|
|
1310
|
+
context: span?.contextId,
|
|
1311
|
+
metadata: { factTag: fact.tag, operation: "derivedFact" }
|
|
1312
|
+
});
|
|
1313
|
+
} catch {
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1135
1316
|
}
|
|
1136
1317
|
}
|
|
1137
1318
|
}
|
|
@@ -2713,7 +2894,7 @@ function generateTauriConfig(config) {
|
|
|
2713
2894
|
|
|
2714
2895
|
// src/integrations/unified.ts
|
|
2715
2896
|
async function createUnifiedApp(config) {
|
|
2716
|
-
const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-
|
|
2897
|
+
const { createPraxisEngine: createPraxisEngine2 } = await import("./engine-YIEGSX7U.js");
|
|
2717
2898
|
const { createInMemoryDB: createInMemoryDB2 } = await import("./adapter-CIMBGDC7.js");
|
|
2718
2899
|
const db = config.db || createInMemoryDB2();
|
|
2719
2900
|
const pluresdb = createPluresDBAdapter({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { P as PraxisState, a as PraxisEvent, L as LogicEngine } from '../reactive-engine.svelte-DjynI82A.js';
|
|
2
|
+
export { o as ReactiveEngineOptions, p as ReactiveLogicEngine, s as createReactiveEngine } from '../reactive-engine.svelte-DjynI82A.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Svelte v5 Integration
|
package/dist/browser/{reactive-engine.svelte-9aS0kTa8.d.ts → reactive-engine.svelte-DjynI82A.d.ts}
RENAMED
|
@@ -71,6 +71,13 @@ interface PraxisState {
|
|
|
71
71
|
context: unknown;
|
|
72
72
|
/** Current facts about the domain */
|
|
73
73
|
facts: PraxisFact[];
|
|
74
|
+
/**
|
|
75
|
+
* Events currently being processed in this step.
|
|
76
|
+
* Available to rules during execution — guaranteed to contain the exact
|
|
77
|
+
* events passed to step()/stepWithContext().
|
|
78
|
+
* Empty outside of step execution.
|
|
79
|
+
*/
|
|
80
|
+
events?: PraxisEvent[];
|
|
74
81
|
/** Optional metadata (timestamps, version, etc.) */
|
|
75
82
|
meta?: Record<string, unknown>;
|
|
76
83
|
/** Protocol version (for cross-language compatibility) */
|
|
@@ -218,6 +225,71 @@ interface ContractGap {
|
|
|
218
225
|
message?: string;
|
|
219
226
|
}
|
|
220
227
|
|
|
228
|
+
/**
|
|
229
|
+
* The result of evaluating a rule. Every rule MUST return one of:
|
|
230
|
+
* - `RuleResult.emit(facts)` — rule produced facts
|
|
231
|
+
* - `RuleResult.noop(reason?)` — rule evaluated but had nothing to say
|
|
232
|
+
* - `RuleResult.skip(reason?)` — rule decided to skip (preconditions not met)
|
|
233
|
+
* - `RuleResult.retract(tags)` — rule retracts previously emitted facts
|
|
234
|
+
*/
|
|
235
|
+
declare class RuleResult {
|
|
236
|
+
/** The kind of result */
|
|
237
|
+
readonly kind: 'emit' | 'noop' | 'skip' | 'retract';
|
|
238
|
+
/** Facts produced (only for 'emit') */
|
|
239
|
+
readonly facts: PraxisFact[];
|
|
240
|
+
/** Fact tags to retract (only for 'retract') */
|
|
241
|
+
readonly retractTags: string[];
|
|
242
|
+
/** Optional reason (for noop/skip/retract — useful for debugging) */
|
|
243
|
+
readonly reason?: string;
|
|
244
|
+
/** The rule ID that produced this result (set by engine) */
|
|
245
|
+
ruleId?: string;
|
|
246
|
+
private constructor();
|
|
247
|
+
/**
|
|
248
|
+
* Rule produced facts.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* return RuleResult.emit([
|
|
252
|
+
* { tag: 'sprint.behind', payload: { deficit: 5 } }
|
|
253
|
+
* ]);
|
|
254
|
+
*/
|
|
255
|
+
static emit(facts: PraxisFact[]): RuleResult;
|
|
256
|
+
/**
|
|
257
|
+
* Rule evaluated but had nothing to report.
|
|
258
|
+
* Unlike returning [], this is explicit and traceable.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* if (ctx.completedHours >= expectedHours) {
|
|
262
|
+
* return RuleResult.noop('Sprint is on pace');
|
|
263
|
+
* }
|
|
264
|
+
*/
|
|
265
|
+
static noop(reason?: string): RuleResult;
|
|
266
|
+
/**
|
|
267
|
+
* Rule decided to skip because preconditions were not met.
|
|
268
|
+
* Distinct from noop: skip means "I can't evaluate", noop means "I evaluated and found nothing".
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* if (!ctx.sprintName) {
|
|
272
|
+
* return RuleResult.skip('No active sprint');
|
|
273
|
+
* }
|
|
274
|
+
*/
|
|
275
|
+
static skip(reason?: string): RuleResult;
|
|
276
|
+
/**
|
|
277
|
+
* Rule retracts previously emitted facts by tag.
|
|
278
|
+
* Used when a condition that previously produced facts is no longer true.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* // Sprint was behind, but caught up
|
|
282
|
+
* if (ctx.completedHours >= expectedHours) {
|
|
283
|
+
* return RuleResult.retract(['sprint.behind'], 'Sprint caught up');
|
|
284
|
+
* }
|
|
285
|
+
*/
|
|
286
|
+
static retract(tags: string[], reason?: string): RuleResult;
|
|
287
|
+
/** Whether this result produced facts */
|
|
288
|
+
get hasFacts(): boolean;
|
|
289
|
+
/** Whether this result retracts facts */
|
|
290
|
+
get hasRetractions(): boolean;
|
|
291
|
+
}
|
|
292
|
+
|
|
221
293
|
/**
|
|
222
294
|
* Rules and Constraints System
|
|
223
295
|
*
|
|
@@ -238,13 +310,20 @@ type ConstraintId = string;
|
|
|
238
310
|
* A rule function derives new facts or transitions from context + input facts/events.
|
|
239
311
|
* Rules must be pure - no side effects.
|
|
240
312
|
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
313
|
+
* Returns either:
|
|
314
|
+
* - `RuleResult` (new API — typed, traceable, supports retraction)
|
|
315
|
+
* - `PraxisFact[]` (legacy — backward compatible, will be deprecated)
|
|
316
|
+
*
|
|
317
|
+
* The state parameter includes `events` — the current batch being processed.
|
|
318
|
+
*
|
|
319
|
+
* @param state Current Praxis state (includes state.events for current batch)
|
|
320
|
+
* @param events Events to process (same as state.events, provided for convenience)
|
|
321
|
+
* @returns RuleResult or array of new facts
|
|
244
322
|
*/
|
|
245
323
|
type RuleFn<TContext = unknown> = (state: PraxisState & {
|
|
246
324
|
context: TContext;
|
|
247
|
-
|
|
325
|
+
events: PraxisEvent[];
|
|
326
|
+
}, events: PraxisEvent[]) => RuleResult | PraxisFact[];
|
|
248
327
|
/**
|
|
249
328
|
* A constraint function checks that an invariant holds.
|
|
250
329
|
* Constraints must be pure - no side effects.
|
|
@@ -265,6 +344,18 @@ interface RuleDescriptor<TContext = unknown> {
|
|
|
265
344
|
description: string;
|
|
266
345
|
/** Implementation function */
|
|
267
346
|
impl: RuleFn<TContext>;
|
|
347
|
+
/**
|
|
348
|
+
* Optional event type filter — only evaluate this rule when at least one
|
|
349
|
+
* event in the batch has a matching `tag`. When omitted, the rule runs on
|
|
350
|
+
* every step (catch-all).
|
|
351
|
+
*
|
|
352
|
+
* Accepts a single tag string or an array of tags.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* { id: 'sprint-behind', eventTypes: ['sprint.update'], impl: ... }
|
|
356
|
+
* { id: 'note-check', eventTypes: 'note.update', impl: ... }
|
|
357
|
+
*/
|
|
358
|
+
eventTypes?: string | string[];
|
|
268
359
|
/** Optional contract for rule behavior */
|
|
269
360
|
contract?: Contract;
|
|
270
361
|
/** Optional metadata */
|
|
@@ -393,6 +484,20 @@ interface PraxisEngineOptions<TContext = unknown> {
|
|
|
393
484
|
initialFacts?: PraxisFact[];
|
|
394
485
|
/** Initial metadata (optional) */
|
|
395
486
|
initialMeta?: Record<string, unknown>;
|
|
487
|
+
/**
|
|
488
|
+
* Fact deduplication strategy (default: 'last-write-wins').
|
|
489
|
+
*
|
|
490
|
+
* - 'none': facts accumulate without dedup (original behavior)
|
|
491
|
+
* - 'last-write-wins': only keep the latest fact per tag (most common)
|
|
492
|
+
* - 'append': keep all facts but cap at maxFacts
|
|
493
|
+
*/
|
|
494
|
+
factDedup?: 'none' | 'last-write-wins' | 'append';
|
|
495
|
+
/**
|
|
496
|
+
* Maximum number of facts to retain (default: 1000).
|
|
497
|
+
* When exceeded, oldest facts are evicted (FIFO).
|
|
498
|
+
* Set to 0 for unlimited (not recommended).
|
|
499
|
+
*/
|
|
500
|
+
maxFacts?: number;
|
|
396
501
|
}
|
|
397
502
|
/**
|
|
398
503
|
* The Praxis Logic Engine
|
|
@@ -403,6 +508,8 @@ interface PraxisEngineOptions<TContext = unknown> {
|
|
|
403
508
|
declare class LogicEngine<TContext = unknown> {
|
|
404
509
|
private state;
|
|
405
510
|
private readonly registry;
|
|
511
|
+
private readonly factDedup;
|
|
512
|
+
private readonly maxFacts;
|
|
406
513
|
constructor(options: PraxisEngineOptions<TContext>);
|
|
407
514
|
/**
|
|
408
515
|
* Get the current state (immutable copy)
|
|
@@ -441,6 +548,23 @@ declare class LogicEngine<TContext = unknown> {
|
|
|
441
548
|
* @param updater Function that produces new context from old context
|
|
442
549
|
*/
|
|
443
550
|
updateContext(updater: (context: TContext) => TContext): void;
|
|
551
|
+
/**
|
|
552
|
+
* Atomically update context AND process events in a single call.
|
|
553
|
+
*
|
|
554
|
+
* This avoids the fragile pattern of calling updateContext() then step()
|
|
555
|
+
* separately, where rules could see stale context if the ordering is wrong.
|
|
556
|
+
*
|
|
557
|
+
* @param updater Function that produces new context from old context
|
|
558
|
+
* @param events Events to process after context is updated
|
|
559
|
+
* @returns Result with new state and diagnostics
|
|
560
|
+
*
|
|
561
|
+
* @example
|
|
562
|
+
* engine.stepWithContext(
|
|
563
|
+
* ctx => ({ ...ctx, sprintName: sprint.name, items: sprint.items }),
|
|
564
|
+
* [{ tag: 'sprint.update', payload: { name: sprint.name } }]
|
|
565
|
+
* );
|
|
566
|
+
*/
|
|
567
|
+
stepWithContext(updater: (context: TContext) => TContext, events: PraxisEvent[]): PraxisStepResult;
|
|
444
568
|
/**
|
|
445
569
|
* Add facts directly (for exceptional cases).
|
|
446
570
|
* Generally, facts should be added through rules.
|
|
@@ -448,6 +572,16 @@ declare class LogicEngine<TContext = unknown> {
|
|
|
448
572
|
* @param facts Facts to add
|
|
449
573
|
*/
|
|
450
574
|
addFacts(facts: PraxisFact[]): void;
|
|
575
|
+
/**
|
|
576
|
+
* Check all constraints without processing any events.
|
|
577
|
+
*
|
|
578
|
+
* Useful for validation-only scenarios (e.g., form validation,
|
|
579
|
+
* pre-save checks) where you want constraint diagnostics without
|
|
580
|
+
* triggering any rules.
|
|
581
|
+
*
|
|
582
|
+
* @returns Array of constraint violation diagnostics (empty = all passing)
|
|
583
|
+
*/
|
|
584
|
+
checkConstraints(): PraxisDiagnostics[];
|
|
451
585
|
/**
|
|
452
586
|
* Clear all facts
|
|
453
587
|
*/
|
|
@@ -551,4 +685,4 @@ declare class ReactiveLogicEngine<TContext extends object> {
|
|
|
551
685
|
*/
|
|
552
686
|
declare function createReactiveEngine<TContext extends object>(options: ReactiveEngineOptions<TContext>): ReactiveLogicEngine<TContext>;
|
|
553
687
|
|
|
554
|
-
export { type ConstraintDescriptor as C, LogicEngine as L, type PraxisState as P, type RuleDescriptor as R, type PraxisEvent as a, PraxisRegistry as b, type
|
|
688
|
+
export { type ConstraintDescriptor as C, LogicEngine as L, type PraxisState as P, type RuleDescriptor as R, type PraxisEvent as a, PraxisRegistry as b, type ConstraintFn as c, type Contract as d, type RuleFn as e, type PraxisFact as f, type PraxisModule as g, type ConstraintId as h, PRAXIS_PROTOCOL_VERSION as i, type PraxisDiagnostics as j, type PraxisEngineOptions as k, type PraxisStepConfig as l, type PraxisStepFn as m, type PraxisStepResult as n, type ReactiveEngineOptions as o, ReactiveLogicEngine as p, type RuleId as q, createPraxisEngine as r, createReactiveEngine as s };
|