@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/README.md
CHANGED
|
@@ -318,6 +318,8 @@ See [src/decision-ledger/README.md](./src/decision-ledger/README.md) for complet
|
|
|
318
318
|
## Documentation
|
|
319
319
|
- [Getting Started](./GETTING_STARTED.md)
|
|
320
320
|
- [Framework Guide](./FRAMEWORK.md)
|
|
321
|
+
- [Praxis-Core API](./docs/core/praxis-core-api.md) - Stable API surface & guarantees
|
|
322
|
+
- [Extending Praxis-Core](./docs/core/extending-praxis-core.md) - Extension guidelines
|
|
321
323
|
- [Decision Ledger Guide](./src/decision-ledger/README.md)
|
|
322
324
|
- [Examples](./examples/)
|
|
323
325
|
|
|
@@ -458,6 +460,44 @@ This protocol is:
|
|
|
458
460
|
|
|
459
461
|
## Framework Architecture
|
|
460
462
|
|
|
463
|
+
Praxis is organized as a **monorepo** with clearly separated packages. See [MONOREPO.md](./MONOREPO.md) for the complete organization plan.
|
|
464
|
+
|
|
465
|
+
### Target Monorepo Structure
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
praxis/
|
|
469
|
+
├── packages/ # Published npm packages
|
|
470
|
+
│ ├── praxis-core/ # Core logic library (zero dependencies)
|
|
471
|
+
│ │ └── src/
|
|
472
|
+
│ │ ├── logic/ # Facts, events, rules, constraints, engine
|
|
473
|
+
│ │ ├── schema/ # Schema definitions and validation
|
|
474
|
+
│ │ ├── decision-ledger/ # Contracts and behavior specifications
|
|
475
|
+
│ │ └── protocol/ # Core protocol types
|
|
476
|
+
│ ├── praxis-cli/ # Command-line interface
|
|
477
|
+
│ │ └── src/
|
|
478
|
+
│ │ ├── commands/ # CLI commands
|
|
479
|
+
│ │ └── generators/ # Code generators
|
|
480
|
+
│ ├── praxis-svelte/ # Svelte 5 integration
|
|
481
|
+
│ │ └── src/
|
|
482
|
+
│ │ ├── components/ # Reactive Svelte components
|
|
483
|
+
│ │ ├── generators/ # Component generators
|
|
484
|
+
│ │ └── runtime/ # Svelte runtime integration
|
|
485
|
+
│ ├── praxis-cloud/ # Cloud sync and relay
|
|
486
|
+
│ │ └── src/
|
|
487
|
+
│ │ ├── relay/ # Cloud relay server
|
|
488
|
+
│ │ └── sync/ # Sync protocol
|
|
489
|
+
│ └── praxis/ # Main package (re-exports all)
|
|
490
|
+
├── apps/ # Example applications
|
|
491
|
+
├── tools/ # Development tools
|
|
492
|
+
├── ui/ # UI components and tools
|
|
493
|
+
├── docs/ # Documentation
|
|
494
|
+
└── examples/ # Simple examples and demos
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Current Structure (In Transition)
|
|
498
|
+
|
|
499
|
+
The existing code is currently located in:
|
|
500
|
+
|
|
461
501
|
```
|
|
462
502
|
/praxis
|
|
463
503
|
├── core/ # Core framework
|
|
@@ -1116,6 +1156,10 @@ MIT License - see [LICENSE](./LICENSE) for details.
|
|
|
1116
1156
|
|
|
1117
1157
|
Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) to get started.
|
|
1118
1158
|
|
|
1159
|
+
**Automated Updates**: This repository uses batched bot updates to reduce commit churn. Dependency updates are grouped weekly and include audit trails. See the [Bot Update Policy](./docs/BOT_UPDATE_POLICY.md) for details.
|
|
1160
|
+
|
|
1161
|
+
**Dogfooding Plures Tools**: We actively dogfood all Plures tools during development. If you encounter friction while using any tool, please file a [Dogfooding Friction Report](https://github.com/plures/praxis/issues/new/choose). See the [Dogfooding Quick Start](./docs/DOGFOODING_QUICK_START.md) for details.
|
|
1162
|
+
|
|
1119
1163
|
- 🐛 [Report a bug](https://github.com/plures/praxis/issues/new?template=bug_report.yml)
|
|
1120
1164
|
- 💡 [Request a feature](https://github.com/plures/praxis/issues/new?template=enhancement.yml)
|
|
1121
1165
|
- 📖 [Improve documentation](https://github.com/plures/praxis/issues/new?template=bug_report.yml)
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// src/core/protocol.ts
|
|
2
|
+
var PRAXIS_PROTOCOL_VERSION = "1.0.0";
|
|
3
|
+
|
|
4
|
+
// src/core/rule-result.ts
|
|
5
|
+
var RuleResult = class _RuleResult {
|
|
6
|
+
/** The kind of result */
|
|
7
|
+
kind;
|
|
8
|
+
/** Facts produced (only for 'emit') */
|
|
9
|
+
facts;
|
|
10
|
+
/** Fact tags to retract (only for 'retract') */
|
|
11
|
+
retractTags;
|
|
12
|
+
/** Optional reason (for noop/skip/retract — useful for debugging) */
|
|
13
|
+
reason;
|
|
14
|
+
/** The rule ID that produced this result (set by engine) */
|
|
15
|
+
ruleId;
|
|
16
|
+
constructor(kind, facts, retractTags, reason) {
|
|
17
|
+
this.kind = kind;
|
|
18
|
+
this.facts = facts;
|
|
19
|
+
this.retractTags = retractTags;
|
|
20
|
+
this.reason = reason;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Rule produced facts.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* return RuleResult.emit([
|
|
27
|
+
* { tag: 'sprint.behind', payload: { deficit: 5 } }
|
|
28
|
+
* ]);
|
|
29
|
+
*/
|
|
30
|
+
static emit(facts) {
|
|
31
|
+
if (facts.length === 0) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"RuleResult.emit() requires at least one fact. Use RuleResult.noop() or RuleResult.skip() when a rule has nothing to say."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return new _RuleResult("emit", facts, []);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Rule evaluated but had nothing to report.
|
|
40
|
+
* Unlike returning [], this is explicit and traceable.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* if (ctx.completedHours >= expectedHours) {
|
|
44
|
+
* return RuleResult.noop('Sprint is on pace');
|
|
45
|
+
* }
|
|
46
|
+
*/
|
|
47
|
+
static noop(reason) {
|
|
48
|
+
return new _RuleResult("noop", [], [], reason);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Rule decided to skip because preconditions were not met.
|
|
52
|
+
* Distinct from noop: skip means "I can't evaluate", noop means "I evaluated and found nothing".
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* if (!ctx.sprintName) {
|
|
56
|
+
* return RuleResult.skip('No active sprint');
|
|
57
|
+
* }
|
|
58
|
+
*/
|
|
59
|
+
static skip(reason) {
|
|
60
|
+
return new _RuleResult("skip", [], [], reason);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Rule retracts previously emitted facts by tag.
|
|
64
|
+
* Used when a condition that previously produced facts is no longer true.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // Sprint was behind, but caught up
|
|
68
|
+
* if (ctx.completedHours >= expectedHours) {
|
|
69
|
+
* return RuleResult.retract(['sprint.behind'], 'Sprint caught up');
|
|
70
|
+
* }
|
|
71
|
+
*/
|
|
72
|
+
static retract(tags, reason) {
|
|
73
|
+
if (tags.length === 0) {
|
|
74
|
+
throw new Error("RuleResult.retract() requires at least one tag.");
|
|
75
|
+
}
|
|
76
|
+
return new _RuleResult("retract", [], tags, reason);
|
|
77
|
+
}
|
|
78
|
+
/** Whether this result produced facts */
|
|
79
|
+
get hasFacts() {
|
|
80
|
+
return this.facts.length > 0;
|
|
81
|
+
}
|
|
82
|
+
/** Whether this result retracts facts */
|
|
83
|
+
get hasRetractions() {
|
|
84
|
+
return this.retractTags.length > 0;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/core/engine.ts
|
|
89
|
+
function safeClone(value) {
|
|
90
|
+
if (value === null || typeof value !== "object") {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
if (typeof globalThis.structuredClone === "function") {
|
|
94
|
+
try {
|
|
95
|
+
return globalThis.structuredClone(value);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
return [...value];
|
|
101
|
+
}
|
|
102
|
+
return { ...value };
|
|
103
|
+
}
|
|
104
|
+
var LogicEngine = class {
|
|
105
|
+
state;
|
|
106
|
+
registry;
|
|
107
|
+
factDedup;
|
|
108
|
+
maxFacts;
|
|
109
|
+
constructor(options) {
|
|
110
|
+
this.registry = options.registry;
|
|
111
|
+
this.factDedup = options.factDedup ?? "last-write-wins";
|
|
112
|
+
this.maxFacts = options.maxFacts ?? 1e3;
|
|
113
|
+
this.state = {
|
|
114
|
+
context: options.initialContext,
|
|
115
|
+
facts: options.initialFacts ?? [],
|
|
116
|
+
meta: options.initialMeta ?? {},
|
|
117
|
+
protocolVersion: PRAXIS_PROTOCOL_VERSION
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get the current state (immutable copy)
|
|
122
|
+
*/
|
|
123
|
+
getState() {
|
|
124
|
+
return {
|
|
125
|
+
context: safeClone(this.state.context),
|
|
126
|
+
facts: [...this.state.facts],
|
|
127
|
+
meta: this.state.meta ? safeClone(this.state.meta) : void 0,
|
|
128
|
+
protocolVersion: this.state.protocolVersion
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get the current context
|
|
133
|
+
*/
|
|
134
|
+
getContext() {
|
|
135
|
+
return safeClone(this.state.context);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get current facts
|
|
139
|
+
*/
|
|
140
|
+
getFacts() {
|
|
141
|
+
return [...this.state.facts];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Process events through the engine.
|
|
145
|
+
* Applies all registered rules and checks all registered constraints.
|
|
146
|
+
*
|
|
147
|
+
* @param events Events to process
|
|
148
|
+
* @returns Result with new state and diagnostics
|
|
149
|
+
*/
|
|
150
|
+
step(events) {
|
|
151
|
+
const config = {
|
|
152
|
+
ruleIds: this.registry.getRuleIds(),
|
|
153
|
+
constraintIds: this.registry.getConstraintIds()
|
|
154
|
+
};
|
|
155
|
+
return this.stepWithConfig(events, config);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Process events with specific rule and constraint configuration.
|
|
159
|
+
*
|
|
160
|
+
* @param events Events to process
|
|
161
|
+
* @param config Step configuration
|
|
162
|
+
* @returns Result with new state and diagnostics
|
|
163
|
+
*/
|
|
164
|
+
stepWithConfig(events, config) {
|
|
165
|
+
const diagnostics = [];
|
|
166
|
+
let newState = { ...this.state };
|
|
167
|
+
const stateWithEvents = {
|
|
168
|
+
...newState,
|
|
169
|
+
events
|
|
170
|
+
// current batch — rules can read state.events
|
|
171
|
+
};
|
|
172
|
+
const newFacts = [];
|
|
173
|
+
const retractions = [];
|
|
174
|
+
const eventTags = new Set(events.map((e) => e.tag));
|
|
175
|
+
for (const ruleId of config.ruleIds) {
|
|
176
|
+
const rule = this.registry.getRule(ruleId);
|
|
177
|
+
if (!rule) {
|
|
178
|
+
diagnostics.push({
|
|
179
|
+
kind: "rule-error",
|
|
180
|
+
message: `Rule "${ruleId}" not found in registry`,
|
|
181
|
+
data: { ruleId }
|
|
182
|
+
});
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (rule.eventTypes) {
|
|
186
|
+
const filterTags = Array.isArray(rule.eventTypes) ? rule.eventTypes : [rule.eventTypes];
|
|
187
|
+
if (!filterTags.some((t) => eventTags.has(t))) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const rawResult = rule.impl(stateWithEvents, events);
|
|
193
|
+
if (rawResult instanceof RuleResult) {
|
|
194
|
+
rawResult.ruleId = ruleId;
|
|
195
|
+
switch (rawResult.kind) {
|
|
196
|
+
case "emit":
|
|
197
|
+
newFacts.push(...rawResult.facts);
|
|
198
|
+
break;
|
|
199
|
+
case "retract":
|
|
200
|
+
retractions.push(...rawResult.retractTags);
|
|
201
|
+
break;
|
|
202
|
+
case "noop":
|
|
203
|
+
case "skip":
|
|
204
|
+
if (rawResult.reason) {
|
|
205
|
+
diagnostics.push({
|
|
206
|
+
kind: "rule-error",
|
|
207
|
+
// reused kind — could add 'rule-trace' in protocol v2
|
|
208
|
+
message: `[${rawResult.kind}] ${ruleId}: ${rawResult.reason}`,
|
|
209
|
+
data: { ruleId, resultKind: rawResult.kind, reason: rawResult.reason }
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
} else if (Array.isArray(rawResult)) {
|
|
215
|
+
newFacts.push(...rawResult);
|
|
216
|
+
}
|
|
217
|
+
} catch (error) {
|
|
218
|
+
diagnostics.push({
|
|
219
|
+
kind: "rule-error",
|
|
220
|
+
message: `Error executing rule "${ruleId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
221
|
+
data: { ruleId, error }
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
let existingFacts = newState.facts;
|
|
226
|
+
if (retractions.length > 0) {
|
|
227
|
+
const retractSet = new Set(retractions);
|
|
228
|
+
existingFacts = existingFacts.filter((f) => !retractSet.has(f.tag));
|
|
229
|
+
}
|
|
230
|
+
let mergedFacts;
|
|
231
|
+
switch (this.factDedup) {
|
|
232
|
+
case "last-write-wins": {
|
|
233
|
+
const factMap = /* @__PURE__ */ new Map();
|
|
234
|
+
for (const f of existingFacts) factMap.set(f.tag, f);
|
|
235
|
+
for (const f of newFacts) factMap.set(f.tag, f);
|
|
236
|
+
mergedFacts = Array.from(factMap.values());
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case "append":
|
|
240
|
+
mergedFacts = [...existingFacts, ...newFacts];
|
|
241
|
+
break;
|
|
242
|
+
case "none":
|
|
243
|
+
default:
|
|
244
|
+
mergedFacts = [...existingFacts, ...newFacts];
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
if (this.maxFacts > 0 && mergedFacts.length > this.maxFacts) {
|
|
248
|
+
mergedFacts = mergedFacts.slice(mergedFacts.length - this.maxFacts);
|
|
249
|
+
}
|
|
250
|
+
newState = {
|
|
251
|
+
...newState,
|
|
252
|
+
facts: mergedFacts
|
|
253
|
+
};
|
|
254
|
+
for (const constraintId of config.constraintIds) {
|
|
255
|
+
const constraint = this.registry.getConstraint(constraintId);
|
|
256
|
+
if (!constraint) {
|
|
257
|
+
diagnostics.push({
|
|
258
|
+
kind: "constraint-violation",
|
|
259
|
+
message: `Constraint "${constraintId}" not found in registry`,
|
|
260
|
+
data: { constraintId }
|
|
261
|
+
});
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const result = constraint.impl(newState);
|
|
266
|
+
if (result === false) {
|
|
267
|
+
diagnostics.push({
|
|
268
|
+
kind: "constraint-violation",
|
|
269
|
+
message: `Constraint "${constraintId}" violated`,
|
|
270
|
+
data: { constraintId, description: constraint.description }
|
|
271
|
+
});
|
|
272
|
+
} else if (typeof result === "string") {
|
|
273
|
+
diagnostics.push({
|
|
274
|
+
kind: "constraint-violation",
|
|
275
|
+
message: result,
|
|
276
|
+
data: { constraintId, description: constraint.description }
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
diagnostics.push({
|
|
281
|
+
kind: "constraint-violation",
|
|
282
|
+
message: `Error checking constraint "${constraintId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
283
|
+
data: { constraintId, error }
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
this.state = newState;
|
|
288
|
+
return {
|
|
289
|
+
state: newState,
|
|
290
|
+
diagnostics
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Update the context directly (for exceptional cases).
|
|
295
|
+
* Generally, context should be updated through rules.
|
|
296
|
+
*
|
|
297
|
+
* @param updater Function that produces new context from old context
|
|
298
|
+
*/
|
|
299
|
+
updateContext(updater) {
|
|
300
|
+
this.state = {
|
|
301
|
+
...this.state,
|
|
302
|
+
context: updater(this.state.context)
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Atomically update context AND process events in a single call.
|
|
307
|
+
*
|
|
308
|
+
* This avoids the fragile pattern of calling updateContext() then step()
|
|
309
|
+
* separately, where rules could see stale context if the ordering is wrong.
|
|
310
|
+
*
|
|
311
|
+
* @param updater Function that produces new context from old context
|
|
312
|
+
* @param events Events to process after context is updated
|
|
313
|
+
* @returns Result with new state and diagnostics
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* engine.stepWithContext(
|
|
317
|
+
* ctx => ({ ...ctx, sprintName: sprint.name, items: sprint.items }),
|
|
318
|
+
* [{ tag: 'sprint.update', payload: { name: sprint.name } }]
|
|
319
|
+
* );
|
|
320
|
+
*/
|
|
321
|
+
stepWithContext(updater, events) {
|
|
322
|
+
this.state = {
|
|
323
|
+
...this.state,
|
|
324
|
+
context: updater(this.state.context)
|
|
325
|
+
};
|
|
326
|
+
return this.step(events);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Add facts directly (for exceptional cases).
|
|
330
|
+
* Generally, facts should be added through rules.
|
|
331
|
+
*
|
|
332
|
+
* @param facts Facts to add
|
|
333
|
+
*/
|
|
334
|
+
addFacts(facts) {
|
|
335
|
+
this.state = {
|
|
336
|
+
...this.state,
|
|
337
|
+
facts: [...this.state.facts, ...facts]
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Check all constraints without processing any events.
|
|
342
|
+
*
|
|
343
|
+
* Useful for validation-only scenarios (e.g., form validation,
|
|
344
|
+
* pre-save checks) where you want constraint diagnostics without
|
|
345
|
+
* triggering any rules.
|
|
346
|
+
*
|
|
347
|
+
* @returns Array of constraint violation diagnostics (empty = all passing)
|
|
348
|
+
*/
|
|
349
|
+
checkConstraints() {
|
|
350
|
+
return this.stepWithConfig([], {
|
|
351
|
+
ruleIds: [],
|
|
352
|
+
constraintIds: this.registry.getConstraintIds()
|
|
353
|
+
}).diagnostics;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Clear all facts
|
|
357
|
+
*/
|
|
358
|
+
clearFacts() {
|
|
359
|
+
this.state = {
|
|
360
|
+
...this.state,
|
|
361
|
+
facts: []
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Reset the engine to initial state
|
|
366
|
+
*/
|
|
367
|
+
reset(options) {
|
|
368
|
+
this.state = {
|
|
369
|
+
context: options.initialContext,
|
|
370
|
+
facts: options.initialFacts ?? [],
|
|
371
|
+
meta: options.initialMeta ?? {},
|
|
372
|
+
protocolVersion: PRAXIS_PROTOCOL_VERSION
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
function createPraxisEngine(options) {
|
|
377
|
+
return new LogicEngine(options);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export {
|
|
381
|
+
PRAXIS_PROTOCOL_VERSION,
|
|
382
|
+
LogicEngine,
|
|
383
|
+
createPraxisEngine
|
|
384
|
+
};
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LogicEngine, P as PraxisState, a as PraxisEvent, b as PraxisRegistry, R as RuleDescriptor, C as ConstraintDescriptor, c as
|
|
2
|
-
export {
|
|
1
|
+
import { L as LogicEngine, P as PraxisState, a as PraxisEvent, b as PraxisRegistry, R as RuleDescriptor, C as ConstraintDescriptor, c as ConstraintFn, d as Contract, e as RuleFn, f as PraxisFact, g as PraxisModule } from './reactive-engine.svelte-DjynI82A.js';
|
|
2
|
+
export { h as ConstraintId, i as PRAXIS_PROTOCOL_VERSION, j as PraxisDiagnostics, k as PraxisEngineOptions, l as PraxisStepConfig, m as PraxisStepFn, n as PraxisStepResult, o as ReactiveEngineOptions, p as ReactiveLogicEngine, q as RuleId, r as createPraxisEngine, s as createReactiveEngine } from './reactive-engine.svelte-DjynI82A.js';
|
|
3
3
|
import { LocalFirstOptions } from '@plures/pluresdb/local-first';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -435,6 +435,11 @@ interface DefineRuleOptions<TContext = unknown> {
|
|
|
435
435
|
id: string;
|
|
436
436
|
description: string;
|
|
437
437
|
impl: RuleFn<TContext>;
|
|
438
|
+
/**
|
|
439
|
+
* Optional event type filter — only evaluate this rule when at least one
|
|
440
|
+
* event in the batch has a matching `tag`. Accepts a single tag or array.
|
|
441
|
+
*/
|
|
442
|
+
eventTypes?: string | string[];
|
|
438
443
|
contract?: Contract;
|
|
439
444
|
meta?: Record<string, unknown>;
|
|
440
445
|
}
|
|
@@ -1103,6 +1108,86 @@ interface PraxisLocalFirstOptions extends LocalFirstOptions {
|
|
|
1103
1108
|
*/
|
|
1104
1109
|
declare function createPraxisLocalFirst(options?: PraxisLocalFirstOptions): Promise<PluresDBPraxisAdapter>;
|
|
1105
1110
|
|
|
1111
|
+
/**
|
|
1112
|
+
* Chronicle Types
|
|
1113
|
+
*
|
|
1114
|
+
* Core types for the Chronicle causal tracking system.
|
|
1115
|
+
* Records state transitions as a causal graph stored in PluresDB.
|
|
1116
|
+
*/
|
|
1117
|
+
/**
|
|
1118
|
+
* Direction for traversing causal chains.
|
|
1119
|
+
*/
|
|
1120
|
+
type TraceDirection = 'backward' | 'forward' | 'both';
|
|
1121
|
+
/**
|
|
1122
|
+
* A recorded state transition event passed to Chronicle.
|
|
1123
|
+
*/
|
|
1124
|
+
interface ChronicleEvent {
|
|
1125
|
+
/** Path to the changed value (fact or event stream path) */
|
|
1126
|
+
path: string;
|
|
1127
|
+
/** Value before the change (undefined for creates) */
|
|
1128
|
+
before?: unknown;
|
|
1129
|
+
/** Value after the change */
|
|
1130
|
+
after?: unknown;
|
|
1131
|
+
/** Parent span/node ID that caused this change */
|
|
1132
|
+
cause?: string;
|
|
1133
|
+
/** Session or request ID grouping related changes */
|
|
1134
|
+
context?: string;
|
|
1135
|
+
/** Additional metadata key-value pairs */
|
|
1136
|
+
metadata: Record<string, string>;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* A Chronicle node representing a single recorded state transition.
|
|
1140
|
+
*/
|
|
1141
|
+
interface ChronicleNode {
|
|
1142
|
+
/** Unique node ID: `chronos:{timestamp}-{counter}` */
|
|
1143
|
+
id: string;
|
|
1144
|
+
/** Timestamp (ms since epoch) when this node was recorded */
|
|
1145
|
+
timestamp: number;
|
|
1146
|
+
/** The recorded state transition */
|
|
1147
|
+
event: ChronicleEvent;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Chronicle Interface and PluresDbChronicle Implementation
|
|
1152
|
+
*
|
|
1153
|
+
* Records state transitions as a causal graph in PluresDB.
|
|
1154
|
+
* Attached to PraxisDBStore via `.withChronicle()` for zero-effort observability.
|
|
1155
|
+
*/
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Chronicle interface — records state transitions as a causal graph.
|
|
1159
|
+
*
|
|
1160
|
+
* Automatically attached to any PraxisDBStore at runtime via `.withChronicle()`.
|
|
1161
|
+
* Records state diffs as graph nodes with causal edges.
|
|
1162
|
+
*/
|
|
1163
|
+
interface Chronicle {
|
|
1164
|
+
/**
|
|
1165
|
+
* Record a state transition and return the created node.
|
|
1166
|
+
*/
|
|
1167
|
+
record(event: ChronicleEvent): Promise<ChronicleNode>;
|
|
1168
|
+
/**
|
|
1169
|
+
* Trace causality backward or forward from a node.
|
|
1170
|
+
*
|
|
1171
|
+
* @param nodeId Starting node ID
|
|
1172
|
+
* @param direction `'backward'` follows incoming edges, `'forward'` follows outgoing edges
|
|
1173
|
+
* @param maxDepth Maximum traversal depth (prevents cycles / infinite loops)
|
|
1174
|
+
*/
|
|
1175
|
+
trace(nodeId: string, direction: TraceDirection, maxDepth: number): Promise<ChronicleNode[]>;
|
|
1176
|
+
/**
|
|
1177
|
+
* Return all Chronicle nodes recorded within a timestamp range.
|
|
1178
|
+
*
|
|
1179
|
+
* @param start Inclusive start timestamp (ms)
|
|
1180
|
+
* @param end Inclusive end timestamp (ms)
|
|
1181
|
+
*/
|
|
1182
|
+
range(start: number, end: number): Promise<ChronicleNode[]>;
|
|
1183
|
+
/**
|
|
1184
|
+
* Return all Chronicle nodes belonging to a context (session/request).
|
|
1185
|
+
*
|
|
1186
|
+
* @param contextId The context identifier
|
|
1187
|
+
*/
|
|
1188
|
+
subgraph(contextId: string): Promise<ChronicleNode[]>;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1106
1191
|
/**
|
|
1107
1192
|
* PraxisDB Store
|
|
1108
1193
|
*
|
|
@@ -1182,9 +1267,26 @@ declare class PraxisDBStore<TContext = unknown> {
|
|
|
1182
1267
|
private subscriptions;
|
|
1183
1268
|
private factWatchers;
|
|
1184
1269
|
private onRuleError;
|
|
1270
|
+
private chronicle?;
|
|
1185
1271
|
constructor(options: PraxisDBStoreOptions<TContext> & {
|
|
1186
1272
|
onRuleError?: RuleErrorHandler;
|
|
1187
1273
|
});
|
|
1274
|
+
/**
|
|
1275
|
+
* Attach a Chronicle observer to this store.
|
|
1276
|
+
*
|
|
1277
|
+
* Every subsequent `storeFact` and `appendEvent` call will be recorded as a
|
|
1278
|
+
* causal graph node in PluresDB, enabling full observability for free.
|
|
1279
|
+
*
|
|
1280
|
+
* @param chronicle Chronicle implementation to attach
|
|
1281
|
+
* @returns `this` for fluent chaining
|
|
1282
|
+
*
|
|
1283
|
+
* @example
|
|
1284
|
+
* ```typescript
|
|
1285
|
+
* const store = createPraxisDBStore(db, registry)
|
|
1286
|
+
* .withChronicle(createChronicle(db));
|
|
1287
|
+
* ```
|
|
1288
|
+
*/
|
|
1289
|
+
withChronicle(chronicle: Chronicle): this;
|
|
1188
1290
|
/**
|
|
1189
1291
|
* Store a fact in PluresDB
|
|
1190
1292
|
*
|