@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.cjs
CHANGED
|
@@ -39,6 +39,99 @@ var init_protocol = __esm({
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
// src/core/rule-result.ts
|
|
43
|
+
function fact(tag, payload) {
|
|
44
|
+
return { tag, payload };
|
|
45
|
+
}
|
|
46
|
+
var RuleResult;
|
|
47
|
+
var init_rule_result = __esm({
|
|
48
|
+
"src/core/rule-result.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
RuleResult = class _RuleResult {
|
|
51
|
+
/** The kind of result */
|
|
52
|
+
kind;
|
|
53
|
+
/** Facts produced (only for 'emit') */
|
|
54
|
+
facts;
|
|
55
|
+
/** Fact tags to retract (only for 'retract') */
|
|
56
|
+
retractTags;
|
|
57
|
+
/** Optional reason (for noop/skip/retract — useful for debugging) */
|
|
58
|
+
reason;
|
|
59
|
+
/** The rule ID that produced this result (set by engine) */
|
|
60
|
+
ruleId;
|
|
61
|
+
constructor(kind, facts, retractTags, reason) {
|
|
62
|
+
this.kind = kind;
|
|
63
|
+
this.facts = facts;
|
|
64
|
+
this.retractTags = retractTags;
|
|
65
|
+
this.reason = reason;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Rule produced facts.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* return RuleResult.emit([
|
|
72
|
+
* { tag: 'sprint.behind', payload: { deficit: 5 } }
|
|
73
|
+
* ]);
|
|
74
|
+
*/
|
|
75
|
+
static emit(facts) {
|
|
76
|
+
if (facts.length === 0) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"RuleResult.emit() requires at least one fact. Use RuleResult.noop() or RuleResult.skip() when a rule has nothing to say."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return new _RuleResult("emit", facts, []);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Rule evaluated but had nothing to report.
|
|
85
|
+
* Unlike returning [], this is explicit and traceable.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* if (ctx.completedHours >= expectedHours) {
|
|
89
|
+
* return RuleResult.noop('Sprint is on pace');
|
|
90
|
+
* }
|
|
91
|
+
*/
|
|
92
|
+
static noop(reason) {
|
|
93
|
+
return new _RuleResult("noop", [], [], reason);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Rule decided to skip because preconditions were not met.
|
|
97
|
+
* Distinct from noop: skip means "I can't evaluate", noop means "I evaluated and found nothing".
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* if (!ctx.sprintName) {
|
|
101
|
+
* return RuleResult.skip('No active sprint');
|
|
102
|
+
* }
|
|
103
|
+
*/
|
|
104
|
+
static skip(reason) {
|
|
105
|
+
return new _RuleResult("skip", [], [], reason);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Rule retracts previously emitted facts by tag.
|
|
109
|
+
* Used when a condition that previously produced facts is no longer true.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* // Sprint was behind, but caught up
|
|
113
|
+
* if (ctx.completedHours >= expectedHours) {
|
|
114
|
+
* return RuleResult.retract(['sprint.behind'], 'Sprint caught up');
|
|
115
|
+
* }
|
|
116
|
+
*/
|
|
117
|
+
static retract(tags, reason) {
|
|
118
|
+
if (tags.length === 0) {
|
|
119
|
+
throw new Error("RuleResult.retract() requires at least one tag.");
|
|
120
|
+
}
|
|
121
|
+
return new _RuleResult("retract", [], tags, reason);
|
|
122
|
+
}
|
|
123
|
+
/** Whether this result produced facts */
|
|
124
|
+
get hasFacts() {
|
|
125
|
+
return this.facts.length > 0;
|
|
126
|
+
}
|
|
127
|
+
/** Whether this result retracts facts */
|
|
128
|
+
get hasRetractions() {
|
|
129
|
+
return this.retractTags.length > 0;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
42
135
|
// src/core/engine.ts
|
|
43
136
|
var engine_exports = {};
|
|
44
137
|
__export(engine_exports, {
|
|
@@ -68,11 +161,16 @@ var init_engine = __esm({
|
|
|
68
161
|
"src/core/engine.ts"() {
|
|
69
162
|
"use strict";
|
|
70
163
|
init_protocol();
|
|
164
|
+
init_rule_result();
|
|
71
165
|
LogicEngine = class {
|
|
72
166
|
state;
|
|
73
167
|
registry;
|
|
168
|
+
factDedup;
|
|
169
|
+
maxFacts;
|
|
74
170
|
constructor(options) {
|
|
75
171
|
this.registry = options.registry;
|
|
172
|
+
this.factDedup = options.factDedup ?? "last-write-wins";
|
|
173
|
+
this.maxFacts = options.maxFacts ?? 1e3;
|
|
76
174
|
this.state = {
|
|
77
175
|
context: options.initialContext,
|
|
78
176
|
facts: options.initialFacts ?? [],
|
|
@@ -127,7 +225,14 @@ var init_engine = __esm({
|
|
|
127
225
|
stepWithConfig(events, config) {
|
|
128
226
|
const diagnostics = [];
|
|
129
227
|
let newState = { ...this.state };
|
|
228
|
+
const stateWithEvents = {
|
|
229
|
+
...newState,
|
|
230
|
+
events
|
|
231
|
+
// current batch — rules can read state.events
|
|
232
|
+
};
|
|
130
233
|
const newFacts = [];
|
|
234
|
+
const retractions = [];
|
|
235
|
+
const eventTags = new Set(events.map((e) => e.tag));
|
|
131
236
|
for (const ruleId of config.ruleIds) {
|
|
132
237
|
const rule = this.registry.getRule(ruleId);
|
|
133
238
|
if (!rule) {
|
|
@@ -138,9 +243,38 @@ var init_engine = __esm({
|
|
|
138
243
|
});
|
|
139
244
|
continue;
|
|
140
245
|
}
|
|
246
|
+
if (rule.eventTypes) {
|
|
247
|
+
const filterTags = Array.isArray(rule.eventTypes) ? rule.eventTypes : [rule.eventTypes];
|
|
248
|
+
if (!filterTags.some((t) => eventTags.has(t))) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
141
252
|
try {
|
|
142
|
-
const
|
|
143
|
-
|
|
253
|
+
const rawResult = rule.impl(stateWithEvents, events);
|
|
254
|
+
if (rawResult instanceof RuleResult) {
|
|
255
|
+
rawResult.ruleId = ruleId;
|
|
256
|
+
switch (rawResult.kind) {
|
|
257
|
+
case "emit":
|
|
258
|
+
newFacts.push(...rawResult.facts);
|
|
259
|
+
break;
|
|
260
|
+
case "retract":
|
|
261
|
+
retractions.push(...rawResult.retractTags);
|
|
262
|
+
break;
|
|
263
|
+
case "noop":
|
|
264
|
+
case "skip":
|
|
265
|
+
if (rawResult.reason) {
|
|
266
|
+
diagnostics.push({
|
|
267
|
+
kind: "rule-error",
|
|
268
|
+
// reused kind — could add 'rule-trace' in protocol v2
|
|
269
|
+
message: `[${rawResult.kind}] ${ruleId}: ${rawResult.reason}`,
|
|
270
|
+
data: { ruleId, resultKind: rawResult.kind, reason: rawResult.reason }
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
} else if (Array.isArray(rawResult)) {
|
|
276
|
+
newFacts.push(...rawResult);
|
|
277
|
+
}
|
|
144
278
|
} catch (error) {
|
|
145
279
|
diagnostics.push({
|
|
146
280
|
kind: "rule-error",
|
|
@@ -149,9 +283,34 @@ var init_engine = __esm({
|
|
|
149
283
|
});
|
|
150
284
|
}
|
|
151
285
|
}
|
|
286
|
+
let existingFacts = newState.facts;
|
|
287
|
+
if (retractions.length > 0) {
|
|
288
|
+
const retractSet = new Set(retractions);
|
|
289
|
+
existingFacts = existingFacts.filter((f) => !retractSet.has(f.tag));
|
|
290
|
+
}
|
|
291
|
+
let mergedFacts;
|
|
292
|
+
switch (this.factDedup) {
|
|
293
|
+
case "last-write-wins": {
|
|
294
|
+
const factMap = /* @__PURE__ */ new Map();
|
|
295
|
+
for (const f of existingFacts) factMap.set(f.tag, f);
|
|
296
|
+
for (const f of newFacts) factMap.set(f.tag, f);
|
|
297
|
+
mergedFacts = Array.from(factMap.values());
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case "append":
|
|
301
|
+
mergedFacts = [...existingFacts, ...newFacts];
|
|
302
|
+
break;
|
|
303
|
+
case "none":
|
|
304
|
+
default:
|
|
305
|
+
mergedFacts = [...existingFacts, ...newFacts];
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
if (this.maxFacts > 0 && mergedFacts.length > this.maxFacts) {
|
|
309
|
+
mergedFacts = mergedFacts.slice(mergedFacts.length - this.maxFacts);
|
|
310
|
+
}
|
|
152
311
|
newState = {
|
|
153
312
|
...newState,
|
|
154
|
-
facts:
|
|
313
|
+
facts: mergedFacts
|
|
155
314
|
};
|
|
156
315
|
for (const constraintId of config.constraintIds) {
|
|
157
316
|
const constraint = this.registry.getConstraint(constraintId);
|
|
@@ -204,6 +363,29 @@ var init_engine = __esm({
|
|
|
204
363
|
context: updater(this.state.context)
|
|
205
364
|
};
|
|
206
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Atomically update context AND process events in a single call.
|
|
368
|
+
*
|
|
369
|
+
* This avoids the fragile pattern of calling updateContext() then step()
|
|
370
|
+
* separately, where rules could see stale context if the ordering is wrong.
|
|
371
|
+
*
|
|
372
|
+
* @param updater Function that produces new context from old context
|
|
373
|
+
* @param events Events to process after context is updated
|
|
374
|
+
* @returns Result with new state and diagnostics
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* engine.stepWithContext(
|
|
378
|
+
* ctx => ({ ...ctx, sprintName: sprint.name, items: sprint.items }),
|
|
379
|
+
* [{ tag: 'sprint.update', payload: { name: sprint.name } }]
|
|
380
|
+
* );
|
|
381
|
+
*/
|
|
382
|
+
stepWithContext(updater, events) {
|
|
383
|
+
this.state = {
|
|
384
|
+
...this.state,
|
|
385
|
+
context: updater(this.state.context)
|
|
386
|
+
};
|
|
387
|
+
return this.step(events);
|
|
388
|
+
}
|
|
207
389
|
/**
|
|
208
390
|
* Add facts directly (for exceptional cases).
|
|
209
391
|
* Generally, facts should be added through rules.
|
|
@@ -216,6 +398,21 @@ var init_engine = __esm({
|
|
|
216
398
|
facts: [...this.state.facts, ...facts]
|
|
217
399
|
};
|
|
218
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Check all constraints without processing any events.
|
|
403
|
+
*
|
|
404
|
+
* Useful for validation-only scenarios (e.g., form validation,
|
|
405
|
+
* pre-save checks) where you want constraint diagnostics without
|
|
406
|
+
* triggering any rules.
|
|
407
|
+
*
|
|
408
|
+
* @returns Array of constraint violation diagnostics (empty = all passing)
|
|
409
|
+
*/
|
|
410
|
+
checkConstraints() {
|
|
411
|
+
return this.stepWithConfig([], {
|
|
412
|
+
ruleIds: [],
|
|
413
|
+
constraintIds: this.registry.getConstraintIds()
|
|
414
|
+
}).diagnostics;
|
|
415
|
+
}
|
|
219
416
|
/**
|
|
220
417
|
* Clear all facts
|
|
221
418
|
*/
|
|
@@ -405,6 +602,8 @@ __export(src_exports, {
|
|
|
405
602
|
AcknowledgeContractGap: () => AcknowledgeContractGap,
|
|
406
603
|
ActorManager: () => ActorManager,
|
|
407
604
|
BehaviorLedger: () => BehaviorLedger,
|
|
605
|
+
CHRONICLE_PATHS: () => CHRONICLE_PATHS,
|
|
606
|
+
ChronicleContext: () => ChronicleContext,
|
|
408
607
|
ContractAdded: () => ContractAdded,
|
|
409
608
|
ContractGapAcknowledged: () => ContractGapAcknowledged,
|
|
410
609
|
ContractMissing: () => ContractMissing,
|
|
@@ -417,11 +616,13 @@ __export(src_exports, {
|
|
|
417
616
|
PRAXIS_PROTOCOL_VERSION: () => PRAXIS_PROTOCOL_VERSION,
|
|
418
617
|
PluresDBGenerator: () => PluresDBGenerator,
|
|
419
618
|
PluresDBPraxisAdapter: () => PluresDBPraxisAdapter,
|
|
619
|
+
PluresDbChronicle: () => PluresDbChronicle,
|
|
420
620
|
PraxisDBStore: () => PraxisDBStore,
|
|
421
621
|
PraxisRegistry: () => PraxisRegistry,
|
|
422
622
|
PraxisSchemaRegistry: () => PraxisSchemaRegistry,
|
|
423
623
|
ReactiveLogicEngine: () => ReactiveLogicEngine,
|
|
424
624
|
RegistryIntrospector: () => RegistryIntrospector,
|
|
625
|
+
RuleResult: () => RuleResult,
|
|
425
626
|
StateDocsGenerator: () => StateDocsGenerator,
|
|
426
627
|
TerminalAdapter: () => TerminalAdapter,
|
|
427
628
|
ValidateContracts: () => ValidateContracts,
|
|
@@ -429,11 +630,14 @@ __export(src_exports, {
|
|
|
429
630
|
attachTauriToEngine: () => attachTauriToEngine,
|
|
430
631
|
attachToEngine: () => attachToEngine,
|
|
431
632
|
attachUnumToEngine: () => attachUnumToEngine,
|
|
633
|
+
auditCompleteness: () => auditCompleteness,
|
|
432
634
|
canvasToMermaid: () => canvasToMermaid,
|
|
433
635
|
canvasToSchema: () => canvasToSchema,
|
|
434
636
|
canvasToYaml: () => canvasToYaml,
|
|
435
637
|
createBehaviorLedger: () => createBehaviorLedger,
|
|
436
638
|
createCanvasEditor: () => createCanvasEditor,
|
|
639
|
+
createChronicle: () => createChronicle,
|
|
640
|
+
createChronosMcpTools: () => createChronosMcpTools,
|
|
437
641
|
createFrameworkAgnosticReactiveEngine: () => createReactiveEngine2,
|
|
438
642
|
createInMemoryDB: () => createInMemoryDB,
|
|
439
643
|
createIntrospector: () => createIntrospector,
|
|
@@ -452,6 +656,7 @@ __export(src_exports, {
|
|
|
452
656
|
createTauriPraxisAdapter: () => createTauriPraxisAdapter,
|
|
453
657
|
createTerminalAdapter: () => createTerminalAdapter,
|
|
454
658
|
createTimerActor: () => createTimerActor,
|
|
659
|
+
createUIModule: () => createUIModule,
|
|
455
660
|
createUnifiedApp: () => createUnifiedApp,
|
|
456
661
|
createUnumAdapter: () => createUnumAdapter,
|
|
457
662
|
defineConstraint: () => defineConstraint,
|
|
@@ -460,10 +665,14 @@ __export(src_exports, {
|
|
|
460
665
|
defineFact: () => defineFact,
|
|
461
666
|
defineModule: () => defineModule,
|
|
462
667
|
defineRule: () => defineRule,
|
|
668
|
+
dirtyGuardRule: () => dirtyGuardRule,
|
|
669
|
+
errorDisplayRule: () => errorDisplayRule,
|
|
670
|
+
fact: () => fact,
|
|
463
671
|
filterEvents: () => filterEvents,
|
|
464
672
|
filterFacts: () => filterFacts,
|
|
465
673
|
findEvent: () => findEvent,
|
|
466
674
|
findFact: () => findFact,
|
|
675
|
+
formatReport: () => formatReport,
|
|
467
676
|
formatValidationReport: () => formatValidationReport,
|
|
468
677
|
formatValidationReportJSON: () => formatValidationReportJSON,
|
|
469
678
|
formatValidationReportSARIF: () => formatValidationReportSARIF,
|
|
@@ -474,18 +683,28 @@ __export(src_exports, {
|
|
|
474
683
|
getEventPath: () => getEventPath,
|
|
475
684
|
getFactPath: () => getFactPath,
|
|
476
685
|
getSchemaPath: () => getSchemaPath,
|
|
686
|
+
initGateRule: () => initGateRule,
|
|
477
687
|
isContract: () => isContract,
|
|
478
688
|
loadSchema: () => loadSchema,
|
|
479
689
|
loadSchemaFromFile: () => loadSchemaFromFile,
|
|
480
690
|
loadSchemaFromJson: () => loadSchemaFromJson,
|
|
481
691
|
loadSchemaFromYaml: () => loadSchemaFromYaml,
|
|
692
|
+
loadingGateRule: () => loadingGateRule,
|
|
693
|
+
mustBeInitializedConstraint: () => mustBeInitializedConstraint,
|
|
694
|
+
navigationRequest: () => navigationRequest,
|
|
695
|
+
noInteractionWhileLoadingConstraint: () => noInteractionWhileLoadingConstraint,
|
|
696
|
+
offlineIndicatorRule: () => offlineIndicatorRule,
|
|
482
697
|
registerSchema: () => registerSchema,
|
|
698
|
+
resizeEvent: () => resizeEvent,
|
|
483
699
|
runTerminalCommand: () => runTerminalCommand,
|
|
484
700
|
schemaToCanvas: () => schemaToCanvas,
|
|
701
|
+
uiModule: () => uiModule,
|
|
702
|
+
uiStateChanged: () => uiStateChanged,
|
|
485
703
|
validateContracts: () => validateContracts,
|
|
486
704
|
validateForGeneration: () => validateForGeneration,
|
|
487
705
|
validateSchema: () => validateSchema,
|
|
488
|
-
validateWithGuardian: () => validateWithGuardian
|
|
706
|
+
validateWithGuardian: () => validateWithGuardian,
|
|
707
|
+
viewportRule: () => viewportRule
|
|
489
708
|
});
|
|
490
709
|
module.exports = __toCommonJS(src_exports);
|
|
491
710
|
init_protocol();
|
|
@@ -1262,8 +1481,8 @@ function defineFact(tag) {
|
|
|
1262
1481
|
create(payload) {
|
|
1263
1482
|
return { tag, payload };
|
|
1264
1483
|
},
|
|
1265
|
-
is(
|
|
1266
|
-
return
|
|
1484
|
+
is(fact2) {
|
|
1485
|
+
return fact2.tag === tag;
|
|
1267
1486
|
}
|
|
1268
1487
|
};
|
|
1269
1488
|
}
|
|
@@ -1285,6 +1504,7 @@ function defineRule(options) {
|
|
|
1285
1504
|
id: options.id,
|
|
1286
1505
|
description: options.description,
|
|
1287
1506
|
impl: options.impl,
|
|
1507
|
+
eventTypes: options.eventTypes,
|
|
1288
1508
|
contract,
|
|
1289
1509
|
meta
|
|
1290
1510
|
};
|
|
@@ -2015,16 +2235,16 @@ function validateSchema(schema) {
|
|
|
2015
2235
|
if (schema.logic) {
|
|
2016
2236
|
schema.logic.forEach((logic, logicIndex) => {
|
|
2017
2237
|
if (logic.facts) {
|
|
2018
|
-
logic.facts.forEach((
|
|
2019
|
-
if (!
|
|
2238
|
+
logic.facts.forEach((fact2, factIndex) => {
|
|
2239
|
+
if (!fact2.tag) {
|
|
2020
2240
|
errors.push({
|
|
2021
2241
|
path: `logic[${logicIndex}].facts[${factIndex}].tag`,
|
|
2022
2242
|
message: "Fact tag is required"
|
|
2023
2243
|
});
|
|
2024
|
-
} else if (!isValidIdentifier(
|
|
2244
|
+
} else if (!isValidIdentifier(fact2.tag)) {
|
|
2025
2245
|
errors.push({
|
|
2026
2246
|
path: `logic[${logicIndex}].facts[${factIndex}].tag`,
|
|
2027
|
-
message: `Fact tag "${
|
|
2247
|
+
message: `Fact tag "${fact2.tag}" is not a valid JavaScript identifier. Use only letters, numbers, underscores, and dollar signs, and do not start with a number.`
|
|
2028
2248
|
});
|
|
2029
2249
|
}
|
|
2030
2250
|
});
|
|
@@ -2292,6 +2512,53 @@ async function loadSchemaFromFile(filePath, options = {}) {
|
|
|
2292
2512
|
}
|
|
2293
2513
|
}
|
|
2294
2514
|
|
|
2515
|
+
// src/core/chronicle/context.ts
|
|
2516
|
+
var ChronicleContext = class {
|
|
2517
|
+
static _stack = [];
|
|
2518
|
+
/**
|
|
2519
|
+
* Get the current active span, if any.
|
|
2520
|
+
*/
|
|
2521
|
+
static get current() {
|
|
2522
|
+
return this._stack[this._stack.length - 1];
|
|
2523
|
+
}
|
|
2524
|
+
/**
|
|
2525
|
+
* Run a synchronous function within a causal span.
|
|
2526
|
+
* The span is automatically popped when the function returns.
|
|
2527
|
+
*/
|
|
2528
|
+
static run(span, fn) {
|
|
2529
|
+
this._stack.push(span);
|
|
2530
|
+
try {
|
|
2531
|
+
return fn();
|
|
2532
|
+
} finally {
|
|
2533
|
+
this._stack.pop();
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
/**
|
|
2537
|
+
* Run an async function within a causal span.
|
|
2538
|
+
* The span is popped after the promise settles.
|
|
2539
|
+
*/
|
|
2540
|
+
static async runAsync(span, fn) {
|
|
2541
|
+
this._stack.push(span);
|
|
2542
|
+
try {
|
|
2543
|
+
return await fn();
|
|
2544
|
+
} finally {
|
|
2545
|
+
this._stack.pop();
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* Create a child span that inherits the current contextId.
|
|
2550
|
+
*
|
|
2551
|
+
* @param spanId ID for the new span
|
|
2552
|
+
* @returns A new ChronicleSpan with the current contextId
|
|
2553
|
+
*/
|
|
2554
|
+
static childSpan(spanId) {
|
|
2555
|
+
return {
|
|
2556
|
+
spanId,
|
|
2557
|
+
contextId: this.current?.contextId
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
|
|
2295
2562
|
// src/core/pluresdb/store.ts
|
|
2296
2563
|
var PRAXIS_PATHS = {
|
|
2297
2564
|
/** Base path for all Praxis data */
|
|
@@ -2327,12 +2594,32 @@ var PraxisDBStore = class {
|
|
|
2327
2594
|
subscriptions = [];
|
|
2328
2595
|
factWatchers = /* @__PURE__ */ new Map();
|
|
2329
2596
|
onRuleError;
|
|
2597
|
+
chronicle;
|
|
2330
2598
|
constructor(options) {
|
|
2331
2599
|
this.db = options.db;
|
|
2332
2600
|
this.registry = options.registry;
|
|
2333
2601
|
this.context = options.initialContext ?? {};
|
|
2334
2602
|
this.onRuleError = options.onRuleError ?? defaultErrorHandler;
|
|
2335
2603
|
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Attach a Chronicle observer to this store.
|
|
2606
|
+
*
|
|
2607
|
+
* Every subsequent `storeFact` and `appendEvent` call will be recorded as a
|
|
2608
|
+
* causal graph node in PluresDB, enabling full observability for free.
|
|
2609
|
+
*
|
|
2610
|
+
* @param chronicle Chronicle implementation to attach
|
|
2611
|
+
* @returns `this` for fluent chaining
|
|
2612
|
+
*
|
|
2613
|
+
* @example
|
|
2614
|
+
* ```typescript
|
|
2615
|
+
* const store = createPraxisDBStore(db, registry)
|
|
2616
|
+
* .withChronicle(createChronicle(db));
|
|
2617
|
+
* ```
|
|
2618
|
+
*/
|
|
2619
|
+
withChronicle(chronicle) {
|
|
2620
|
+
this.chronicle = chronicle;
|
|
2621
|
+
return this;
|
|
2622
|
+
}
|
|
2336
2623
|
/**
|
|
2337
2624
|
* Store a fact in PluresDB
|
|
2338
2625
|
*
|
|
@@ -2342,13 +2629,37 @@ var PraxisDBStore = class {
|
|
|
2342
2629
|
* @param fact The fact to store
|
|
2343
2630
|
* @returns Promise that resolves when the fact is stored
|
|
2344
2631
|
*/
|
|
2345
|
-
async storeFact(
|
|
2346
|
-
const constraintResult = await this.checkConstraints([
|
|
2632
|
+
async storeFact(fact2) {
|
|
2633
|
+
const constraintResult = await this.checkConstraints([fact2]);
|
|
2347
2634
|
if (!constraintResult.valid) {
|
|
2348
2635
|
throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
|
|
2349
2636
|
}
|
|
2350
|
-
|
|
2351
|
-
|
|
2637
|
+
let before;
|
|
2638
|
+
if (this.chronicle) {
|
|
2639
|
+
const payload = fact2.payload;
|
|
2640
|
+
const id = payload?.id;
|
|
2641
|
+
if (id) {
|
|
2642
|
+
before = await this.getFact(fact2.tag, id);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
await this.persistFact(fact2);
|
|
2646
|
+
if (this.chronicle) {
|
|
2647
|
+
const payload = fact2.payload;
|
|
2648
|
+
const id = payload?.id ?? "";
|
|
2649
|
+
const span = ChronicleContext.current;
|
|
2650
|
+
try {
|
|
2651
|
+
await this.chronicle.record({
|
|
2652
|
+
path: getFactPath(fact2.tag, id),
|
|
2653
|
+
before,
|
|
2654
|
+
after: fact2,
|
|
2655
|
+
cause: span?.spanId,
|
|
2656
|
+
context: span?.contextId,
|
|
2657
|
+
metadata: { factTag: fact2.tag, operation: "storeFact" }
|
|
2658
|
+
});
|
|
2659
|
+
} catch {
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
await this.triggerRules([fact2]);
|
|
2352
2663
|
}
|
|
2353
2664
|
/**
|
|
2354
2665
|
* Store multiple facts in PluresDB
|
|
@@ -2360,8 +2671,32 @@ var PraxisDBStore = class {
|
|
|
2360
2671
|
if (!constraintResult.valid) {
|
|
2361
2672
|
throw new Error(`Constraint violation: ${constraintResult.errors.join(", ")}`);
|
|
2362
2673
|
}
|
|
2363
|
-
for (const
|
|
2364
|
-
|
|
2674
|
+
for (const fact2 of facts) {
|
|
2675
|
+
let before;
|
|
2676
|
+
if (this.chronicle) {
|
|
2677
|
+
const payload = fact2.payload;
|
|
2678
|
+
const id = payload?.id;
|
|
2679
|
+
if (id) {
|
|
2680
|
+
before = await this.getFact(fact2.tag, id);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
await this.persistFact(fact2);
|
|
2684
|
+
if (this.chronicle) {
|
|
2685
|
+
const payload = fact2.payload;
|
|
2686
|
+
const id = payload?.id ?? "";
|
|
2687
|
+
const span = ChronicleContext.current;
|
|
2688
|
+
try {
|
|
2689
|
+
await this.chronicle.record({
|
|
2690
|
+
path: getFactPath(fact2.tag, id),
|
|
2691
|
+
before,
|
|
2692
|
+
after: fact2,
|
|
2693
|
+
cause: span?.spanId,
|
|
2694
|
+
context: span?.contextId,
|
|
2695
|
+
metadata: { factTag: fact2.tag, operation: "storeFacts" }
|
|
2696
|
+
});
|
|
2697
|
+
} catch {
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2365
2700
|
}
|
|
2366
2701
|
await this.triggerRules(facts);
|
|
2367
2702
|
}
|
|
@@ -2369,11 +2704,11 @@ var PraxisDBStore = class {
|
|
|
2369
2704
|
* Internal method to persist a fact without constraint checking
|
|
2370
2705
|
* Used by both storeFact and derived fact storage
|
|
2371
2706
|
*/
|
|
2372
|
-
async persistFact(
|
|
2373
|
-
const payload =
|
|
2707
|
+
async persistFact(fact2) {
|
|
2708
|
+
const payload = fact2.payload;
|
|
2374
2709
|
const id = payload?.id ?? generateId();
|
|
2375
|
-
const path3 = getFactPath(
|
|
2376
|
-
await this.db.set(path3,
|
|
2710
|
+
const path3 = getFactPath(fact2.tag, id);
|
|
2711
|
+
await this.db.set(path3, fact2);
|
|
2377
2712
|
}
|
|
2378
2713
|
/**
|
|
2379
2714
|
* Get a fact by tag and id
|
|
@@ -2403,7 +2738,29 @@ var PraxisDBStore = class {
|
|
|
2403
2738
|
};
|
|
2404
2739
|
const newEvents = [...existingEvents, entry];
|
|
2405
2740
|
await this.db.set(path3, newEvents);
|
|
2406
|
-
|
|
2741
|
+
let eventNodeId;
|
|
2742
|
+
if (this.chronicle) {
|
|
2743
|
+
const span = ChronicleContext.current;
|
|
2744
|
+
try {
|
|
2745
|
+
const node = await this.chronicle.record({
|
|
2746
|
+
path: path3,
|
|
2747
|
+
before: existingEvents.length > 0 ? existingEvents[existingEvents.length - 1] : void 0,
|
|
2748
|
+
after: entry,
|
|
2749
|
+
cause: span?.spanId,
|
|
2750
|
+
context: span?.contextId,
|
|
2751
|
+
metadata: { eventTag: event.tag, sequence: String(entry.sequence), operation: "appendEvent" }
|
|
2752
|
+
});
|
|
2753
|
+
eventNodeId = node.id;
|
|
2754
|
+
} catch {
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
const outerSpan = ChronicleContext.current;
|
|
2758
|
+
const ruleSpan = eventNodeId ? { spanId: eventNodeId, contextId: outerSpan?.contextId } : outerSpan;
|
|
2759
|
+
if (ruleSpan && this.chronicle) {
|
|
2760
|
+
await ChronicleContext.runAsync(ruleSpan, () => this.triggerRulesForEvents([event]));
|
|
2761
|
+
} else {
|
|
2762
|
+
await this.triggerRulesForEvents([event]);
|
|
2763
|
+
}
|
|
2407
2764
|
}
|
|
2408
2765
|
/**
|
|
2409
2766
|
* Append multiple events to their respective streams
|
|
@@ -2416,6 +2773,7 @@ var PraxisDBStore = class {
|
|
|
2416
2773
|
const existing = eventsByTag.get(event.tag) ?? [];
|
|
2417
2774
|
eventsByTag.set(event.tag, [...existing, event]);
|
|
2418
2775
|
}
|
|
2776
|
+
let lastEventNodeId;
|
|
2419
2777
|
for (const [tag, tagEvents] of eventsByTag) {
|
|
2420
2778
|
const path3 = getEventPath(tag);
|
|
2421
2779
|
const existingEvents = await this.db.get(path3) ?? [];
|
|
@@ -2426,8 +2784,30 @@ var PraxisDBStore = class {
|
|
|
2426
2784
|
sequence: sequence++
|
|
2427
2785
|
}));
|
|
2428
2786
|
await this.db.set(path3, [...existingEvents, ...newEntries]);
|
|
2787
|
+
if (this.chronicle) {
|
|
2788
|
+
const span = ChronicleContext.current;
|
|
2789
|
+
for (const entry of newEntries) {
|
|
2790
|
+
try {
|
|
2791
|
+
const node = await this.chronicle.record({
|
|
2792
|
+
path: path3,
|
|
2793
|
+
after: entry,
|
|
2794
|
+
cause: span?.spanId,
|
|
2795
|
+
context: span?.contextId,
|
|
2796
|
+
metadata: { eventTag: tag, sequence: String(entry.sequence), operation: "appendEvents" }
|
|
2797
|
+
});
|
|
2798
|
+
lastEventNodeId = node.id;
|
|
2799
|
+
} catch {
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
const outerSpan = ChronicleContext.current;
|
|
2805
|
+
const ruleSpan = lastEventNodeId ? { spanId: lastEventNodeId, contextId: outerSpan?.contextId } : outerSpan;
|
|
2806
|
+
if (ruleSpan && this.chronicle) {
|
|
2807
|
+
await ChronicleContext.runAsync(ruleSpan, () => this.triggerRulesForEvents(events));
|
|
2808
|
+
} else {
|
|
2809
|
+
await this.triggerRulesForEvents(events);
|
|
2429
2810
|
}
|
|
2430
|
-
await this.triggerRulesForEvents(events);
|
|
2431
2811
|
}
|
|
2432
2812
|
/**
|
|
2433
2813
|
* Get events from a stream
|
|
@@ -2465,8 +2845,8 @@ var PraxisDBStore = class {
|
|
|
2465
2845
|
if (watchers) {
|
|
2466
2846
|
watchers.add(callback);
|
|
2467
2847
|
}
|
|
2468
|
-
const unsubscribe = this.db.watch(path3, (
|
|
2469
|
-
callback([
|
|
2848
|
+
const unsubscribe = this.db.watch(path3, (fact2) => {
|
|
2849
|
+
callback([fact2]);
|
|
2470
2850
|
});
|
|
2471
2851
|
this.subscriptions.push(unsubscribe);
|
|
2472
2852
|
return () => {
|
|
@@ -2524,13 +2904,18 @@ var PraxisDBStore = class {
|
|
|
2524
2904
|
const state2 = {
|
|
2525
2905
|
context: this.context,
|
|
2526
2906
|
facts: [],
|
|
2907
|
+
events,
|
|
2527
2908
|
meta: {}
|
|
2528
2909
|
};
|
|
2529
2910
|
const derivedFacts = [];
|
|
2530
2911
|
for (const rule of rules) {
|
|
2531
2912
|
try {
|
|
2532
|
-
const
|
|
2533
|
-
|
|
2913
|
+
const result = rule.impl(state2, events);
|
|
2914
|
+
if (Array.isArray(result)) {
|
|
2915
|
+
derivedFacts.push(...result);
|
|
2916
|
+
} else if (result && "kind" in result && result.kind === "emit") {
|
|
2917
|
+
derivedFacts.push(...result.facts);
|
|
2918
|
+
}
|
|
2534
2919
|
} catch (error) {
|
|
2535
2920
|
this.onRuleError(rule.id, error);
|
|
2536
2921
|
}
|
|
@@ -2538,8 +2923,23 @@ var PraxisDBStore = class {
|
|
|
2538
2923
|
if (derivedFacts.length > 0) {
|
|
2539
2924
|
const constraintResult = await this.checkConstraints(derivedFacts);
|
|
2540
2925
|
if (constraintResult.valid) {
|
|
2541
|
-
for (const
|
|
2542
|
-
await this.persistFact(
|
|
2926
|
+
for (const fact2 of derivedFacts) {
|
|
2927
|
+
await this.persistFact(fact2);
|
|
2928
|
+
if (this.chronicle) {
|
|
2929
|
+
const payload = fact2.payload;
|
|
2930
|
+
const id = payload?.id ?? "";
|
|
2931
|
+
const span = ChronicleContext.current;
|
|
2932
|
+
try {
|
|
2933
|
+
await this.chronicle.record({
|
|
2934
|
+
path: getFactPath(fact2.tag, id),
|
|
2935
|
+
after: fact2,
|
|
2936
|
+
cause: span?.spanId,
|
|
2937
|
+
context: span?.contextId,
|
|
2938
|
+
metadata: { factTag: fact2.tag, operation: "derivedFact" }
|
|
2939
|
+
});
|
|
2940
|
+
} catch {
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2543
2943
|
}
|
|
2544
2944
|
}
|
|
2545
2945
|
}
|
|
@@ -2833,6 +3233,156 @@ function createPluresDBGenerator(outputDir, options) {
|
|
|
2833
3233
|
});
|
|
2834
3234
|
}
|
|
2835
3235
|
|
|
3236
|
+
// src/core/chronicle/chronicle.ts
|
|
3237
|
+
var CHRONICLE_PATHS = {
|
|
3238
|
+
BASE: "/_praxis/chronos",
|
|
3239
|
+
NODES: "/_praxis/chronos/nodes",
|
|
3240
|
+
EDGES_OUT: "/_praxis/chronos/edges/out",
|
|
3241
|
+
EDGES_IN: "/_praxis/chronos/edges/in",
|
|
3242
|
+
CONTEXT: "/_praxis/chronos/context",
|
|
3243
|
+
INDEX: "/_praxis/chronos/index"
|
|
3244
|
+
};
|
|
3245
|
+
var _nodeCounter = 0;
|
|
3246
|
+
var PluresDbChronicle = class {
|
|
3247
|
+
db;
|
|
3248
|
+
constructor(db) {
|
|
3249
|
+
this.db = db;
|
|
3250
|
+
}
|
|
3251
|
+
async record(event) {
|
|
3252
|
+
const timestamp = Date.now();
|
|
3253
|
+
const id = `chronos:${timestamp}-${++_nodeCounter}`;
|
|
3254
|
+
const node = { id, timestamp, event };
|
|
3255
|
+
await this.db.set(`${CHRONICLE_PATHS.NODES}/${id}`, node);
|
|
3256
|
+
const index = await this.db.get(CHRONICLE_PATHS.INDEX) ?? [];
|
|
3257
|
+
await this.db.set(CHRONICLE_PATHS.INDEX, [...index, id]);
|
|
3258
|
+
if (event.cause) {
|
|
3259
|
+
await this.addEdge(event.cause, id, "causes");
|
|
3260
|
+
}
|
|
3261
|
+
if (event.context) {
|
|
3262
|
+
const contextPath = `${CHRONICLE_PATHS.CONTEXT}/${event.context}`;
|
|
3263
|
+
const contextNodes = await this.db.get(contextPath) ?? [];
|
|
3264
|
+
if (contextNodes.length > 0) {
|
|
3265
|
+
const prevId = contextNodes[contextNodes.length - 1];
|
|
3266
|
+
await this.addEdge(prevId, id, "follows");
|
|
3267
|
+
}
|
|
3268
|
+
await this.db.set(contextPath, [...contextNodes, id]);
|
|
3269
|
+
}
|
|
3270
|
+
return node;
|
|
3271
|
+
}
|
|
3272
|
+
async trace(nodeId, direction, maxDepth) {
|
|
3273
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3274
|
+
const result = [];
|
|
3275
|
+
await this._traceRecursive(nodeId, direction, maxDepth, 0, visited, result);
|
|
3276
|
+
return result;
|
|
3277
|
+
}
|
|
3278
|
+
async range(start, end) {
|
|
3279
|
+
const index = await this.db.get(CHRONICLE_PATHS.INDEX) ?? [];
|
|
3280
|
+
const result = [];
|
|
3281
|
+
for (const id of index) {
|
|
3282
|
+
const node = await this.db.get(`${CHRONICLE_PATHS.NODES}/${id}`);
|
|
3283
|
+
if (node && node.timestamp >= start && node.timestamp <= end) {
|
|
3284
|
+
result.push(node);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
return result;
|
|
3288
|
+
}
|
|
3289
|
+
async subgraph(contextId) {
|
|
3290
|
+
const contextPath = `${CHRONICLE_PATHS.CONTEXT}/${contextId}`;
|
|
3291
|
+
const nodeIds = await this.db.get(contextPath) ?? [];
|
|
3292
|
+
const result = [];
|
|
3293
|
+
for (const id of nodeIds) {
|
|
3294
|
+
const node = await this.db.get(`${CHRONICLE_PATHS.NODES}/${id}`);
|
|
3295
|
+
if (node) {
|
|
3296
|
+
result.push(node);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
return result;
|
|
3300
|
+
}
|
|
3301
|
+
// ── Internal helpers ──────────────────────────────────────────────────────
|
|
3302
|
+
async addEdge(from, to, type) {
|
|
3303
|
+
const edge = { from, to, type };
|
|
3304
|
+
const outPath = `${CHRONICLE_PATHS.EDGES_OUT}/${from}`;
|
|
3305
|
+
const outEdges = await this.db.get(outPath) ?? [];
|
|
3306
|
+
await this.db.set(outPath, [...outEdges, edge]);
|
|
3307
|
+
const inPath = `${CHRONICLE_PATHS.EDGES_IN}/${to}`;
|
|
3308
|
+
const inEdges = await this.db.get(inPath) ?? [];
|
|
3309
|
+
await this.db.set(inPath, [...inEdges, edge]);
|
|
3310
|
+
}
|
|
3311
|
+
async _traceRecursive(nodeId, direction, maxDepth, depth, visited, result) {
|
|
3312
|
+
if (depth > maxDepth || visited.has(nodeId)) {
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
visited.add(nodeId);
|
|
3316
|
+
const node = await this.db.get(`${CHRONICLE_PATHS.NODES}/${nodeId}`);
|
|
3317
|
+
if (node) {
|
|
3318
|
+
result.push(node);
|
|
3319
|
+
}
|
|
3320
|
+
if (direction === "backward" || direction === "both") {
|
|
3321
|
+
const inEdges = await this.db.get(`${CHRONICLE_PATHS.EDGES_IN}/${nodeId}`) ?? [];
|
|
3322
|
+
for (const edge of inEdges) {
|
|
3323
|
+
await this._traceRecursive(edge.from, direction, maxDepth, depth + 1, visited, result);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
if (direction === "forward" || direction === "both") {
|
|
3327
|
+
const outEdges = await this.db.get(`${CHRONICLE_PATHS.EDGES_OUT}/${nodeId}`) ?? [];
|
|
3328
|
+
for (const edge of outEdges) {
|
|
3329
|
+
await this._traceRecursive(edge.to, direction, maxDepth, depth + 1, visited, result);
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
};
|
|
3334
|
+
function createChronicle(db) {
|
|
3335
|
+
return new PluresDbChronicle(db);
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
// src/core/chronicle/mcp.ts
|
|
3339
|
+
function createChronosMcpTools(chronicle) {
|
|
3340
|
+
return {
|
|
3341
|
+
async trace(params) {
|
|
3342
|
+
try {
|
|
3343
|
+
const nodes = await chronicle.trace(
|
|
3344
|
+
params.nodeId,
|
|
3345
|
+
params.direction ?? "backward",
|
|
3346
|
+
params.maxDepth ?? 10
|
|
3347
|
+
);
|
|
3348
|
+
return { success: true, data: nodes };
|
|
3349
|
+
} catch (error) {
|
|
3350
|
+
return {
|
|
3351
|
+
success: false,
|
|
3352
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
},
|
|
3356
|
+
async search(params) {
|
|
3357
|
+
try {
|
|
3358
|
+
const query = params.query.toLowerCase();
|
|
3359
|
+
let candidates;
|
|
3360
|
+
if (params.contextId) {
|
|
3361
|
+
candidates = await chronicle.subgraph(params.contextId);
|
|
3362
|
+
} else {
|
|
3363
|
+
candidates = await chronicle.range(params.since ?? 0, params.until ?? Date.now());
|
|
3364
|
+
}
|
|
3365
|
+
const filtered = candidates.filter((node) => {
|
|
3366
|
+
const inPath = node.event.path.toLowerCase().includes(query);
|
|
3367
|
+
const inMeta = Object.values(node.event.metadata).some(
|
|
3368
|
+
(v) => v.toLowerCase().includes(query)
|
|
3369
|
+
);
|
|
3370
|
+
const inAfter = JSON.stringify(node.event.after ?? "").toLowerCase().includes(query);
|
|
3371
|
+
const inBefore = JSON.stringify(node.event.before ?? "").toLowerCase().includes(query);
|
|
3372
|
+
return inPath || inMeta || inAfter || inBefore;
|
|
3373
|
+
});
|
|
3374
|
+
const limited = params.limit !== void 0 ? filtered.slice(0, params.limit) : filtered;
|
|
3375
|
+
return { success: true, data: limited };
|
|
3376
|
+
} catch (error) {
|
|
3377
|
+
return {
|
|
3378
|
+
success: false,
|
|
3379
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
}
|
|
3385
|
+
|
|
2836
3386
|
// src/integrations/pluresdb.ts
|
|
2837
3387
|
function createPluresDBAdapter(options) {
|
|
2838
3388
|
const store = createPraxisDBStore(options.db, options.registry, options.initialContext);
|
|
@@ -2998,7 +3548,7 @@ async function createUnumAdapter(config) {
|
|
|
2998
3548
|
type: "event"
|
|
2999
3549
|
});
|
|
3000
3550
|
}
|
|
3001
|
-
async function broadcastFact(channelId,
|
|
3551
|
+
async function broadcastFact(channelId, fact2) {
|
|
3002
3552
|
const channel = channels.get(channelId);
|
|
3003
3553
|
if (!channel) {
|
|
3004
3554
|
throw new Error(`Not joined to channel ${channelId}`);
|
|
@@ -3006,7 +3556,7 @@ async function createUnumAdapter(config) {
|
|
|
3006
3556
|
await channel.publish({
|
|
3007
3557
|
id: generateId2(),
|
|
3008
3558
|
sender: currentIdentity || { id: "anonymous", createdAt: Date.now() },
|
|
3009
|
-
content:
|
|
3559
|
+
content: fact2,
|
|
3010
3560
|
type: "fact"
|
|
3011
3561
|
});
|
|
3012
3562
|
}
|
|
@@ -3149,17 +3699,17 @@ function schemaToCanvas(schema, _options = {}) {
|
|
|
3149
3699
|
yOffset += schema.events.length * ySpacing + 30;
|
|
3150
3700
|
}
|
|
3151
3701
|
if (schema.facts) {
|
|
3152
|
-
schema.facts.forEach((
|
|
3153
|
-
const pos =
|
|
3702
|
+
schema.facts.forEach((fact2, index) => {
|
|
3703
|
+
const pos = fact2.position && (fact2.position.x !== 0 || fact2.position.y !== 0) ? fact2.position : { x: 50 + xSpacing * 3, y: yOffset + index * ySpacing };
|
|
3154
3704
|
const node = {
|
|
3155
|
-
id:
|
|
3705
|
+
id: fact2.id || `fact-${nodeId++}`,
|
|
3156
3706
|
type: "fact",
|
|
3157
|
-
label:
|
|
3707
|
+
label: fact2.tag,
|
|
3158
3708
|
x: pos.x,
|
|
3159
3709
|
y: pos.y,
|
|
3160
3710
|
width: 150,
|
|
3161
3711
|
height: 50,
|
|
3162
|
-
data:
|
|
3712
|
+
data: fact2,
|
|
3163
3713
|
style: {
|
|
3164
3714
|
backgroundColor: "#fce4ec",
|
|
3165
3715
|
borderColor: "#c2185b"
|
|
@@ -3614,8 +4164,8 @@ var StateDocsGenerator = class {
|
|
|
3614
4164
|
if (logic.facts && logic.facts.length > 0) {
|
|
3615
4165
|
lines.push("**Facts:**");
|
|
3616
4166
|
lines.push("");
|
|
3617
|
-
for (const
|
|
3618
|
-
lines.push(`- \`${
|
|
4167
|
+
for (const fact2 of logic.facts) {
|
|
4168
|
+
lines.push(`- \`${fact2.tag}\`: ${fact2.description || ""}`);
|
|
3619
4169
|
}
|
|
3620
4170
|
lines.push("");
|
|
3621
4171
|
}
|
|
@@ -3747,9 +4297,9 @@ var StateDocsGenerator = class {
|
|
|
3747
4297
|
lines.push("");
|
|
3748
4298
|
lines.push("| Fact | Description | Payload |");
|
|
3749
4299
|
lines.push("|------|-------------|---------|");
|
|
3750
|
-
for (const
|
|
3751
|
-
const payload =
|
|
3752
|
-
lines.push(`| \`${
|
|
4300
|
+
for (const fact2 of logic.facts) {
|
|
4301
|
+
const payload = fact2.payload ? Object.entries(fact2.payload).map(([k, v]) => `${k}: ${v}`).join(", ") : "-";
|
|
4302
|
+
lines.push(`| \`${fact2.tag}\` | ${fact2.description || "-"} | ${payload} |`);
|
|
3753
4303
|
}
|
|
3754
4304
|
lines.push("");
|
|
3755
4305
|
}
|
|
@@ -3801,8 +4351,8 @@ var StateDocsGenerator = class {
|
|
|
3801
4351
|
for (const event of logic.events) {
|
|
3802
4352
|
lines.push(` Processing --> ${event.tag.replace(/[^a-zA-Z0-9]/g, "")}: ${event.tag}`);
|
|
3803
4353
|
}
|
|
3804
|
-
for (const
|
|
3805
|
-
lines.push(` ${
|
|
4354
|
+
for (const fact2 of logic.facts) {
|
|
4355
|
+
lines.push(` ${fact2.tag.replace(/[^a-zA-Z0-9]/g, "")} --> [*]`);
|
|
3806
4356
|
}
|
|
3807
4357
|
}
|
|
3808
4358
|
return {
|
|
@@ -4253,11 +4803,309 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4253
4803
|
}
|
|
4254
4804
|
};
|
|
4255
4805
|
}
|
|
4806
|
+
|
|
4807
|
+
// src/index.ts
|
|
4808
|
+
init_rule_result();
|
|
4809
|
+
|
|
4810
|
+
// src/core/ui-rules.ts
|
|
4811
|
+
init_rule_result();
|
|
4812
|
+
var loadingGateRule = {
|
|
4813
|
+
id: "ui/loading-gate",
|
|
4814
|
+
description: "Signals when the app is in a loading state",
|
|
4815
|
+
eventTypes: ["ui.state-change", "app.init"],
|
|
4816
|
+
impl: (state2) => {
|
|
4817
|
+
const ctx = state2.context;
|
|
4818
|
+
if (ctx.loading) {
|
|
4819
|
+
return RuleResult.emit([fact("ui.loading-gate", { active: true })]);
|
|
4820
|
+
}
|
|
4821
|
+
return RuleResult.retract(["ui.loading-gate"], "Not loading");
|
|
4822
|
+
},
|
|
4823
|
+
contract: {
|
|
4824
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4825
|
+
behavior: "Emits ui.loading-gate when context.loading is true, retracts when false",
|
|
4826
|
+
examples: [
|
|
4827
|
+
{ given: "loading is true", when: "ui state changes", then: "ui.loading-gate emitted" },
|
|
4828
|
+
{ given: "loading is false", when: "ui state changes", then: "ui.loading-gate retracted" }
|
|
4829
|
+
],
|
|
4830
|
+
invariants: ["Loading gate must reflect context.loading exactly"]
|
|
4831
|
+
}
|
|
4832
|
+
};
|
|
4833
|
+
var errorDisplayRule = {
|
|
4834
|
+
id: "ui/error-display",
|
|
4835
|
+
description: "Signals when an error should be displayed to the user",
|
|
4836
|
+
eventTypes: ["ui.state-change", "app.error"],
|
|
4837
|
+
impl: (state2) => {
|
|
4838
|
+
const ctx = state2.context;
|
|
4839
|
+
if (ctx.error) {
|
|
4840
|
+
return RuleResult.emit([fact("ui.error-display", { message: ctx.error, severity: "error" })]);
|
|
4841
|
+
}
|
|
4842
|
+
return RuleResult.retract(["ui.error-display"], "Error cleared");
|
|
4843
|
+
},
|
|
4844
|
+
contract: {
|
|
4845
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4846
|
+
behavior: "Emits ui.error-display when context.error is non-null, retracts when cleared",
|
|
4847
|
+
examples: [
|
|
4848
|
+
{ given: "error is set", when: "ui state changes", then: "ui.error-display emitted with message" },
|
|
4849
|
+
{ given: "error is null", when: "ui state changes", then: "ui.error-display retracted" }
|
|
4850
|
+
],
|
|
4851
|
+
invariants: ["Error display must clear when error is null"]
|
|
4852
|
+
}
|
|
4853
|
+
};
|
|
4854
|
+
var offlineIndicatorRule = {
|
|
4855
|
+
id: "ui/offline-indicator",
|
|
4856
|
+
description: "Signals when the app is offline",
|
|
4857
|
+
eventTypes: ["ui.state-change", "network.change"],
|
|
4858
|
+
impl: (state2) => {
|
|
4859
|
+
if (state2.context.offline) {
|
|
4860
|
+
return RuleResult.emit([fact("ui.offline", { message: "You are offline. Changes will sync when reconnected." })]);
|
|
4861
|
+
}
|
|
4862
|
+
return RuleResult.retract(["ui.offline"], "Back online");
|
|
4863
|
+
},
|
|
4864
|
+
contract: {
|
|
4865
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4866
|
+
behavior: "Emits ui.offline when context.offline is true, retracts when back online",
|
|
4867
|
+
examples: [
|
|
4868
|
+
{ given: "offline is true", when: "network changes", then: "ui.offline emitted" },
|
|
4869
|
+
{ given: "offline is false", when: "network changes", then: "ui.offline retracted" }
|
|
4870
|
+
],
|
|
4871
|
+
invariants: ["Offline indicator must match actual connectivity"]
|
|
4872
|
+
}
|
|
4873
|
+
};
|
|
4874
|
+
var dirtyGuardRule = {
|
|
4875
|
+
id: "ui/dirty-guard",
|
|
4876
|
+
description: "Warns when there are unsaved changes",
|
|
4877
|
+
eventTypes: ["ui.state-change", "navigation.request"],
|
|
4878
|
+
impl: (state2) => {
|
|
4879
|
+
if (state2.context.dirty) {
|
|
4880
|
+
return RuleResult.emit([fact("ui.unsaved-warning", {
|
|
4881
|
+
message: "You have unsaved changes",
|
|
4882
|
+
blocking: true
|
|
4883
|
+
})]);
|
|
4884
|
+
}
|
|
4885
|
+
return RuleResult.retract(["ui.unsaved-warning"], "No unsaved changes");
|
|
4886
|
+
},
|
|
4887
|
+
contract: {
|
|
4888
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4889
|
+
behavior: "Emits ui.unsaved-warning when context.dirty is true, retracts when saved",
|
|
4890
|
+
examples: [
|
|
4891
|
+
{ given: "dirty is true", when: "ui state changes", then: "ui.unsaved-warning emitted with blocking=true" },
|
|
4892
|
+
{ given: "dirty is false", when: "ui state changes", then: "ui.unsaved-warning retracted" }
|
|
4893
|
+
],
|
|
4894
|
+
invariants: ["Dirty guard must clear after save"]
|
|
4895
|
+
}
|
|
4896
|
+
};
|
|
4897
|
+
var initGateRule = {
|
|
4898
|
+
id: "ui/init-gate",
|
|
4899
|
+
description: "Signals whether the app has completed initialization",
|
|
4900
|
+
eventTypes: ["ui.state-change", "app.init"],
|
|
4901
|
+
impl: (state2) => {
|
|
4902
|
+
if (!state2.context.initialized) {
|
|
4903
|
+
return RuleResult.emit([fact("ui.init-pending", {
|
|
4904
|
+
message: "App is initializing..."
|
|
4905
|
+
})]);
|
|
4906
|
+
}
|
|
4907
|
+
return RuleResult.retract(["ui.init-pending"], "App initialized");
|
|
4908
|
+
},
|
|
4909
|
+
contract: {
|
|
4910
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4911
|
+
behavior: "Emits ui.init-pending until context.initialized is true",
|
|
4912
|
+
examples: [
|
|
4913
|
+
{ given: "initialized is false", when: "app starts", then: "ui.init-pending emitted" },
|
|
4914
|
+
{ given: "initialized is true", when: "init completes", then: "ui.init-pending retracted" }
|
|
4915
|
+
],
|
|
4916
|
+
invariants: ["Init gate must clear exactly once, when initialization completes"]
|
|
4917
|
+
}
|
|
4918
|
+
};
|
|
4919
|
+
var viewportRule = {
|
|
4920
|
+
id: "ui/viewport-class",
|
|
4921
|
+
description: "Classifies viewport size for responsive layout decisions",
|
|
4922
|
+
eventTypes: ["ui.state-change", "ui.resize"],
|
|
4923
|
+
impl: (state2) => {
|
|
4924
|
+
const vp = state2.context.viewport;
|
|
4925
|
+
if (!vp) return RuleResult.skip("No viewport data");
|
|
4926
|
+
return RuleResult.emit([fact("ui.viewport-class", {
|
|
4927
|
+
viewport: vp,
|
|
4928
|
+
compact: vp === "mobile",
|
|
4929
|
+
showSidebar: vp !== "mobile"
|
|
4930
|
+
})]);
|
|
4931
|
+
},
|
|
4932
|
+
contract: {
|
|
4933
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4934
|
+
behavior: "Classifies viewport into responsive layout hints",
|
|
4935
|
+
examples: [
|
|
4936
|
+
{ given: "viewport is mobile", when: "resize event", then: "compact=true, showSidebar=false" },
|
|
4937
|
+
{ given: "viewport is desktop", when: "resize event", then: "compact=false, showSidebar=true" }
|
|
4938
|
+
],
|
|
4939
|
+
invariants: ["Viewport class must update on every resize event"]
|
|
4940
|
+
}
|
|
4941
|
+
};
|
|
4942
|
+
var noInteractionWhileLoadingConstraint = {
|
|
4943
|
+
id: "ui/no-interaction-while-loading",
|
|
4944
|
+
description: "Prevents data mutations while a load operation is in progress",
|
|
4945
|
+
impl: (state2) => {
|
|
4946
|
+
if (state2.context.loading) {
|
|
4947
|
+
return "Cannot perform action while data is loading";
|
|
4948
|
+
}
|
|
4949
|
+
return true;
|
|
4950
|
+
},
|
|
4951
|
+
contract: {
|
|
4952
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4953
|
+
behavior: "Fails when context.loading is true",
|
|
4954
|
+
examples: [
|
|
4955
|
+
{ given: "loading is true", when: "action attempted", then: "violation" },
|
|
4956
|
+
{ given: "loading is false", when: "action attempted", then: "pass" }
|
|
4957
|
+
],
|
|
4958
|
+
invariants: ["Must always fail during loading"]
|
|
4959
|
+
}
|
|
4960
|
+
};
|
|
4961
|
+
var mustBeInitializedConstraint = {
|
|
4962
|
+
id: "ui/must-be-initialized",
|
|
4963
|
+
description: "Requires app initialization before user interactions",
|
|
4964
|
+
impl: (state2) => {
|
|
4965
|
+
if (!state2.context.initialized) {
|
|
4966
|
+
return "App must be initialized before performing this action";
|
|
4967
|
+
}
|
|
4968
|
+
return true;
|
|
4969
|
+
},
|
|
4970
|
+
contract: {
|
|
4971
|
+
ruleId: "RULE_ID_PLACEHOLDER",
|
|
4972
|
+
behavior: "Fails when context.initialized is false",
|
|
4973
|
+
examples: [
|
|
4974
|
+
{ given: "initialized is false", when: "action attempted", then: "violation" },
|
|
4975
|
+
{ given: "initialized is true", when: "action attempted", then: "pass" }
|
|
4976
|
+
],
|
|
4977
|
+
invariants: ["Must always fail before init completes"]
|
|
4978
|
+
}
|
|
4979
|
+
};
|
|
4980
|
+
var uiModule = {
|
|
4981
|
+
rules: [
|
|
4982
|
+
loadingGateRule,
|
|
4983
|
+
errorDisplayRule,
|
|
4984
|
+
offlineIndicatorRule,
|
|
4985
|
+
dirtyGuardRule,
|
|
4986
|
+
initGateRule,
|
|
4987
|
+
viewportRule
|
|
4988
|
+
],
|
|
4989
|
+
constraints: [
|
|
4990
|
+
noInteractionWhileLoadingConstraint,
|
|
4991
|
+
mustBeInitializedConstraint
|
|
4992
|
+
],
|
|
4993
|
+
meta: {
|
|
4994
|
+
name: "praxis-ui",
|
|
4995
|
+
version: "1.0.0",
|
|
4996
|
+
description: "Predefined UI rules and constraints \u2014 separate from business logic"
|
|
4997
|
+
}
|
|
4998
|
+
};
|
|
4999
|
+
function createUIModule(options) {
|
|
5000
|
+
const allRules = uiModule.rules;
|
|
5001
|
+
const allConstraints = uiModule.constraints;
|
|
5002
|
+
const selectedRules = options.rules ? allRules.filter((r) => options.rules.includes(r.id)) : allRules;
|
|
5003
|
+
const selectedConstraints = options.constraints ? allConstraints.filter((c) => options.constraints.includes(c.id)) : allConstraints;
|
|
5004
|
+
return {
|
|
5005
|
+
rules: [...selectedRules, ...options.extraRules ?? []],
|
|
5006
|
+
constraints: [...selectedConstraints, ...options.extraConstraints ?? []],
|
|
5007
|
+
meta: { ...uiModule.meta, customized: true }
|
|
5008
|
+
};
|
|
5009
|
+
}
|
|
5010
|
+
function uiStateChanged(changes) {
|
|
5011
|
+
return { tag: "ui.state-change", payload: changes ?? {} };
|
|
5012
|
+
}
|
|
5013
|
+
function navigationRequest(to) {
|
|
5014
|
+
return { tag: "navigation.request", payload: { to } };
|
|
5015
|
+
}
|
|
5016
|
+
function resizeEvent(width, height) {
|
|
5017
|
+
return { tag: "ui.resize", payload: { width, height } };
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
// src/core/completeness.ts
|
|
5021
|
+
function auditCompleteness(manifest, registryRuleIds, registryConstraintIds, rulesWithContracts, config) {
|
|
5022
|
+
const threshold = config?.threshold ?? 90;
|
|
5023
|
+
const domainBranches = manifest.branches.filter((b) => b.kind === "domain");
|
|
5024
|
+
const coveredDomain = domainBranches.filter((b) => b.coveredBy && registryRuleIds.includes(b.coveredBy));
|
|
5025
|
+
const uncoveredDomain = domainBranches.filter((b) => !b.coveredBy || !registryRuleIds.includes(b.coveredBy));
|
|
5026
|
+
const invariantBranches = manifest.branches.filter((b) => b.kind === "invariant");
|
|
5027
|
+
const coveredInvariants = invariantBranches.filter((b) => b.coveredBy && registryConstraintIds.includes(b.coveredBy));
|
|
5028
|
+
const uncoveredInvariants = invariantBranches.filter((b) => !b.coveredBy || !registryConstraintIds.includes(b.coveredBy));
|
|
5029
|
+
const needContracts = manifest.rulesNeedingContracts;
|
|
5030
|
+
const haveContracts = needContracts.filter((id) => rulesWithContracts.includes(id));
|
|
5031
|
+
const missingContracts = needContracts.filter((id) => !rulesWithContracts.includes(id));
|
|
5032
|
+
const neededFields = manifest.stateFields.filter((f) => f.usedByRule);
|
|
5033
|
+
const coveredFields = neededFields.filter((f) => f.inContext);
|
|
5034
|
+
const missingFields = neededFields.filter((f) => !f.inContext);
|
|
5035
|
+
const coveredTransitions = manifest.transitions.filter((t) => t.eventTag);
|
|
5036
|
+
const missingTransitions = manifest.transitions.filter((t) => !t.eventTag);
|
|
5037
|
+
const ruleScore = domainBranches.length > 0 ? coveredDomain.length / domainBranches.length * 40 : 40;
|
|
5038
|
+
const constraintScore = invariantBranches.length > 0 ? coveredInvariants.length / invariantBranches.length * 20 : 20;
|
|
5039
|
+
const contractScore = needContracts.length > 0 ? haveContracts.length / needContracts.length * 15 : 15;
|
|
5040
|
+
const contextScore = neededFields.length > 0 ? coveredFields.length / neededFields.length * 15 : 15;
|
|
5041
|
+
const eventScore = manifest.transitions.length > 0 ? coveredTransitions.length / manifest.transitions.length * 10 : 10;
|
|
5042
|
+
const score = Math.round(ruleScore + constraintScore + contractScore + contextScore + eventScore);
|
|
5043
|
+
const rating = score >= 90 ? "complete" : score >= 70 ? "good" : score >= 50 ? "partial" : "incomplete";
|
|
5044
|
+
const report = {
|
|
5045
|
+
score,
|
|
5046
|
+
rating,
|
|
5047
|
+
rules: { total: domainBranches.length, covered: coveredDomain.length, uncovered: uncoveredDomain },
|
|
5048
|
+
constraints: { total: invariantBranches.length, covered: coveredInvariants.length, uncovered: uncoveredInvariants },
|
|
5049
|
+
contracts: { total: needContracts.length, withContracts: haveContracts.length, missing: missingContracts },
|
|
5050
|
+
context: { total: neededFields.length, covered: coveredFields.length, missing: missingFields },
|
|
5051
|
+
events: { total: manifest.transitions.length, covered: coveredTransitions.length, missing: missingTransitions }
|
|
5052
|
+
};
|
|
5053
|
+
if (config?.strict && score < threshold) {
|
|
5054
|
+
throw new Error(`Praxis completeness ${score}/100 (${rating}) \u2014 below threshold ${threshold}. ${uncoveredDomain.length} uncovered rules, ${uncoveredInvariants.length} uncovered invariants, ${missingContracts.length} missing contracts.`);
|
|
5055
|
+
}
|
|
5056
|
+
return report;
|
|
5057
|
+
}
|
|
5058
|
+
function formatReport(report) {
|
|
5059
|
+
const lines = [];
|
|
5060
|
+
const icon = report.rating === "complete" ? "\u2705" : report.rating === "good" ? "\u{1F7E2}" : report.rating === "partial" ? "\u{1F7E1}" : "\u{1F534}";
|
|
5061
|
+
lines.push(`${icon} Praxis Completeness: ${report.score}/100 (${report.rating})`);
|
|
5062
|
+
lines.push("");
|
|
5063
|
+
lines.push(`Rules: ${report.rules.covered}/${report.rules.total} domain branches covered (${pct(report.rules.covered, report.rules.total)})`);
|
|
5064
|
+
lines.push(`Constraints: ${report.constraints.covered}/${report.constraints.total} invariants covered (${pct(report.constraints.covered, report.constraints.total)})`);
|
|
5065
|
+
lines.push(`Contracts: ${report.contracts.withContracts}/${report.contracts.total} rules have contracts (${pct(report.contracts.withContracts, report.contracts.total)})`);
|
|
5066
|
+
lines.push(`Context: ${report.context.covered}/${report.context.total} state fields in context (${pct(report.context.covered, report.context.total)})`);
|
|
5067
|
+
lines.push(`Events: ${report.events.covered}/${report.events.total} transitions have events (${pct(report.events.covered, report.events.total)})`);
|
|
5068
|
+
if (report.rules.uncovered.length > 0) {
|
|
5069
|
+
lines.push("");
|
|
5070
|
+
lines.push("Uncovered domain logic:");
|
|
5071
|
+
for (const b of report.rules.uncovered) {
|
|
5072
|
+
lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
if (report.constraints.uncovered.length > 0) {
|
|
5076
|
+
lines.push("");
|
|
5077
|
+
lines.push("Uncovered invariants:");
|
|
5078
|
+
for (const b of report.constraints.uncovered) {
|
|
5079
|
+
lines.push(` \u274C ${b.location}: ${b.condition}${b.note ? ` \u2014 ${b.note}` : ""}`);
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
if (report.contracts.missing.length > 0) {
|
|
5083
|
+
lines.push("");
|
|
5084
|
+
lines.push("Rules missing contracts:");
|
|
5085
|
+
for (const id of report.contracts.missing) {
|
|
5086
|
+
lines.push(` \u{1F4DD} ${id}`);
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
if (report.events.missing.length > 0) {
|
|
5090
|
+
lines.push("");
|
|
5091
|
+
lines.push("State transitions without events:");
|
|
5092
|
+
for (const t of report.events.missing) {
|
|
5093
|
+
lines.push(` \u26A1 ${t.location}: ${t.description}`);
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
return lines.join("\n");
|
|
5097
|
+
}
|
|
5098
|
+
function pct(a, b) {
|
|
5099
|
+
if (b === 0) return "100%";
|
|
5100
|
+
return Math.round(a / b * 100) + "%";
|
|
5101
|
+
}
|
|
4256
5102
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4257
5103
|
0 && (module.exports = {
|
|
4258
5104
|
AcknowledgeContractGap,
|
|
4259
5105
|
ActorManager,
|
|
4260
5106
|
BehaviorLedger,
|
|
5107
|
+
CHRONICLE_PATHS,
|
|
5108
|
+
ChronicleContext,
|
|
4261
5109
|
ContractAdded,
|
|
4262
5110
|
ContractGapAcknowledged,
|
|
4263
5111
|
ContractMissing,
|
|
@@ -4270,11 +5118,13 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4270
5118
|
PRAXIS_PROTOCOL_VERSION,
|
|
4271
5119
|
PluresDBGenerator,
|
|
4272
5120
|
PluresDBPraxisAdapter,
|
|
5121
|
+
PluresDbChronicle,
|
|
4273
5122
|
PraxisDBStore,
|
|
4274
5123
|
PraxisRegistry,
|
|
4275
5124
|
PraxisSchemaRegistry,
|
|
4276
5125
|
ReactiveLogicEngine,
|
|
4277
5126
|
RegistryIntrospector,
|
|
5127
|
+
RuleResult,
|
|
4278
5128
|
StateDocsGenerator,
|
|
4279
5129
|
TerminalAdapter,
|
|
4280
5130
|
ValidateContracts,
|
|
@@ -4282,11 +5132,14 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4282
5132
|
attachTauriToEngine,
|
|
4283
5133
|
attachToEngine,
|
|
4284
5134
|
attachUnumToEngine,
|
|
5135
|
+
auditCompleteness,
|
|
4285
5136
|
canvasToMermaid,
|
|
4286
5137
|
canvasToSchema,
|
|
4287
5138
|
canvasToYaml,
|
|
4288
5139
|
createBehaviorLedger,
|
|
4289
5140
|
createCanvasEditor,
|
|
5141
|
+
createChronicle,
|
|
5142
|
+
createChronosMcpTools,
|
|
4290
5143
|
createFrameworkAgnosticReactiveEngine,
|
|
4291
5144
|
createInMemoryDB,
|
|
4292
5145
|
createIntrospector,
|
|
@@ -4305,6 +5158,7 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4305
5158
|
createTauriPraxisAdapter,
|
|
4306
5159
|
createTerminalAdapter,
|
|
4307
5160
|
createTimerActor,
|
|
5161
|
+
createUIModule,
|
|
4308
5162
|
createUnifiedApp,
|
|
4309
5163
|
createUnumAdapter,
|
|
4310
5164
|
defineConstraint,
|
|
@@ -4313,10 +5167,14 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4313
5167
|
defineFact,
|
|
4314
5168
|
defineModule,
|
|
4315
5169
|
defineRule,
|
|
5170
|
+
dirtyGuardRule,
|
|
5171
|
+
errorDisplayRule,
|
|
5172
|
+
fact,
|
|
4316
5173
|
filterEvents,
|
|
4317
5174
|
filterFacts,
|
|
4318
5175
|
findEvent,
|
|
4319
5176
|
findFact,
|
|
5177
|
+
formatReport,
|
|
4320
5178
|
formatValidationReport,
|
|
4321
5179
|
formatValidationReportJSON,
|
|
4322
5180
|
formatValidationReportSARIF,
|
|
@@ -4327,16 +5185,26 @@ async function attachAllIntegrations(engine, registry, options = {}) {
|
|
|
4327
5185
|
getEventPath,
|
|
4328
5186
|
getFactPath,
|
|
4329
5187
|
getSchemaPath,
|
|
5188
|
+
initGateRule,
|
|
4330
5189
|
isContract,
|
|
4331
5190
|
loadSchema,
|
|
4332
5191
|
loadSchemaFromFile,
|
|
4333
5192
|
loadSchemaFromJson,
|
|
4334
5193
|
loadSchemaFromYaml,
|
|
5194
|
+
loadingGateRule,
|
|
5195
|
+
mustBeInitializedConstraint,
|
|
5196
|
+
navigationRequest,
|
|
5197
|
+
noInteractionWhileLoadingConstraint,
|
|
5198
|
+
offlineIndicatorRule,
|
|
4335
5199
|
registerSchema,
|
|
5200
|
+
resizeEvent,
|
|
4336
5201
|
runTerminalCommand,
|
|
4337
5202
|
schemaToCanvas,
|
|
5203
|
+
uiModule,
|
|
5204
|
+
uiStateChanged,
|
|
4338
5205
|
validateContracts,
|
|
4339
5206
|
validateForGeneration,
|
|
4340
5207
|
validateSchema,
|
|
4341
|
-
validateWithGuardian
|
|
5208
|
+
validateWithGuardian,
|
|
5209
|
+
viewportRule
|
|
4342
5210
|
});
|