@plures/praxis 1.2.13 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +44 -0
  2. package/dist/browser/chunk-MJK3IYTJ.js +384 -0
  3. package/dist/browser/{chunk-K377RW4V.js → chunk-N63K4KWS.js} +1 -1
  4. package/dist/browser/{engine-YJZV4SLD.js → engine-YIEGSX7U.js} +1 -1
  5. package/dist/browser/index.d.ts +104 -2
  6. package/dist/browser/index.js +188 -7
  7. package/dist/browser/integrations/svelte.d.ts +2 -2
  8. package/dist/browser/integrations/svelte.js +2 -2
  9. package/dist/browser/{reactive-engine.svelte-9aS0kTa8.d.ts → reactive-engine.svelte-DjynI82A.d.ts} +139 -5
  10. package/dist/node/{chunk-PRPQO6R5.js → chunk-5JQJZADT.js} +1 -1
  11. package/dist/node/chunk-KMJWAFZV.js +389 -0
  12. package/dist/node/{chunk-5RH7UAQC.js → chunk-PTH6MD6P.js} +1 -0
  13. package/dist/node/cli/index.cjs +1553 -839
  14. package/dist/node/cli/index.js +39 -2
  15. package/dist/node/cloud/index.d.cts +1 -1
  16. package/dist/node/cloud/index.d.ts +1 -1
  17. package/dist/node/components/index.d.cts +2 -2
  18. package/dist/node/components/index.d.ts +2 -2
  19. package/dist/node/conversations-KQBXTP3N.js +596 -0
  20. package/dist/node/{engine-2DQBKBJC.js → engine-FEN5IYZ5.js} +1 -1
  21. package/dist/node/index.cjs +911 -43
  22. package/dist/node/index.d.cts +574 -7
  23. package/dist/node/index.d.ts +574 -7
  24. package/dist/node/index.js +672 -26
  25. package/dist/node/integrations/svelte.cjs +190 -3
  26. package/dist/node/integrations/svelte.d.cts +3 -3
  27. package/dist/node/integrations/svelte.d.ts +3 -3
  28. package/dist/node/integrations/svelte.js +2 -2
  29. package/dist/node/{protocol-Qek7ebBl.d.ts → protocol-DcyGMmWY.d.cts} +8 -1
  30. package/dist/node/{protocol-Qek7ebBl.d.cts → protocol-DcyGMmWY.d.ts} +8 -1
  31. package/dist/node/{reactive-engine.svelte-CRNqHlbv.d.ts → reactive-engine.svelte-Cg0Yc2Hs.d.cts} +145 -6
  32. package/dist/node/{reactive-engine.svelte-BFIZfawz.d.cts → reactive-engine.svelte-DekxqFu0.d.ts} +145 -6
  33. package/dist/node/{terminal-adapter-B-UK_Vdz.d.ts → terminal-adapter-CvIvgTo4.d.ts} +1 -1
  34. package/dist/node/{terminal-adapter-BQSIF5bf.d.cts → terminal-adapter-Db-snPJ3.d.cts} +1 -1
  35. package/dist/node/{validate-CNHUULQE.js → validate-EN3M4FUR.js} +1 -1
  36. package/dist/node/{verify-KLJRXVJS.js → verify-7VZRP2WS.js} +2 -2
  37. package/docs/BOT_UPDATE_POLICY.md +125 -0
  38. package/docs/DOGFOODING_CHECKLIST.md +254 -0
  39. package/docs/DOGFOODING_INDEX.md +169 -0
  40. package/docs/DOGFOODING_QUICK_START.md +140 -0
  41. package/docs/KNO_ENG_EXTRACTION_PLAN.md +577 -0
  42. package/docs/PLURES_TOOLS_INVENTORY.md +170 -0
  43. package/docs/README.md +12 -0
  44. package/docs/TESTING_BOT_WORKFLOWS.md +154 -0
  45. package/docs/conversations/INTEGRATION_POINTS.md +719 -0
  46. package/docs/conversations/README.md +168 -0
  47. package/docs/core/extending-praxis-core.md +604 -0
  48. package/docs/core/praxis-core-api.md +385 -0
  49. package/docs/decision-ledger/contract-index.json +2 -2
  50. package/docs/decision-ledger/decisions/2026-02-01-monorepo-organization.md +130 -0
  51. package/docs/examples/DOGFOODING_WORKFLOW_EXAMPLE.md +295 -0
  52. package/docs/examples/README.md +41 -0
  53. package/docs/workflows/pr-overlap-guard.md +50 -0
  54. package/package.json +8 -3
  55. package/src/__tests__/chronicle.test.ts +512 -0
  56. package/src/__tests__/conversations.test.ts +312 -0
  57. package/src/__tests__/edge-cases.test.ts +1 -1
  58. package/src/__tests__/engine-dx.test.ts +355 -0
  59. package/src/__tests__/engine-v2.test.ts +532 -0
  60. package/src/cli/commands/conversations.ts +252 -0
  61. package/src/cli/index.ts +73 -0
  62. package/src/conversations/README.md +230 -0
  63. package/src/conversations/candidate.schema.json +123 -0
  64. package/src/conversations/candidates.ts +114 -0
  65. package/src/conversations/capture.ts +56 -0
  66. package/src/conversations/classify.ts +110 -0
  67. package/src/conversations/conversation.schema.json +106 -0
  68. package/src/conversations/emitters/fs.ts +65 -0
  69. package/src/conversations/emitters/github.ts +115 -0
  70. package/src/conversations/gate.ts +102 -0
  71. package/src/conversations/index.ts +28 -0
  72. package/src/conversations/normalize.ts +51 -0
  73. package/src/conversations/redact.ts +57 -0
  74. package/src/conversations/types.ts +96 -0
  75. package/src/core/chronicle/chronicle.ts +227 -0
  76. package/src/core/chronicle/context.ts +80 -0
  77. package/src/core/chronicle/index.ts +53 -0
  78. package/src/core/chronicle/mcp.ts +135 -0
  79. package/src/core/chronicle/types.ts +61 -0
  80. package/src/core/completeness.ts +274 -0
  81. package/src/core/engine.ts +143 -3
  82. package/src/core/pluresdb/index.ts +22 -0
  83. package/src/core/pluresdb/store.ts +171 -8
  84. package/src/core/protocol.ts +7 -0
  85. package/src/core/rule-result.ts +130 -0
  86. package/src/core/rules.ts +24 -5
  87. package/src/core/ui-rules.ts +340 -0
  88. package/src/dsl/index.ts +6 -0
  89. package/src/index.ts +45 -0
  90. package/src/integrations/pluresdb.ts +22 -0
  91. package/src/vite/completeness-plugin.ts +72 -0
  92. package/dist/browser/chunk-VOMLVI6V.js +0 -197
  93. package/dist/node/chunk-VOMLVI6V.js +0 -197
@@ -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 ruleFacts = rule.impl(newState, events);
143
- newFacts.push(...ruleFacts);
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: [...newState.facts, ...newFacts]
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(fact) {
1266
- return fact.tag === tag;
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((fact, factIndex) => {
2019
- if (!fact.tag) {
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(fact.tag)) {
2244
+ } else if (!isValidIdentifier(fact2.tag)) {
2025
2245
  errors.push({
2026
2246
  path: `logic[${logicIndex}].facts[${factIndex}].tag`,
2027
- message: `Fact tag "${fact.tag}" is not a valid JavaScript identifier. Use only letters, numbers, underscores, and dollar signs, and do not start with a number.`
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(fact) {
2346
- const constraintResult = await this.checkConstraints([fact]);
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
- await this.persistFact(fact);
2351
- await this.triggerRules([fact]);
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 fact of facts) {
2364
- await this.persistFact(fact);
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(fact) {
2373
- const payload = fact.payload;
2707
+ async persistFact(fact2) {
2708
+ const payload = fact2.payload;
2374
2709
  const id = payload?.id ?? generateId();
2375
- const path3 = getFactPath(fact.tag, id);
2376
- await this.db.set(path3, fact);
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
- await this.triggerRulesForEvents([event]);
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, (fact) => {
2469
- callback([fact]);
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 facts = rule.impl(state2, events);
2533
- derivedFacts.push(...facts);
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 fact of derivedFacts) {
2542
- await this.persistFact(fact);
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, fact) {
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: fact,
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((fact, index) => {
3153
- const pos = fact.position && (fact.position.x !== 0 || fact.position.y !== 0) ? fact.position : { x: 50 + xSpacing * 3, y: yOffset + index * ySpacing };
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: fact.id || `fact-${nodeId++}`,
3705
+ id: fact2.id || `fact-${nodeId++}`,
3156
3706
  type: "fact",
3157
- label: fact.tag,
3707
+ label: fact2.tag,
3158
3708
  x: pos.x,
3159
3709
  y: pos.y,
3160
3710
  width: 150,
3161
3711
  height: 50,
3162
- data: fact,
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 fact of logic.facts) {
3618
- lines.push(`- \`${fact.tag}\`: ${fact.description || ""}`);
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 fact of logic.facts) {
3751
- const payload = fact.payload ? Object.entries(fact.payload).map(([k, v]) => `${k}: ${v}`).join(", ") : "-";
3752
- lines.push(`| \`${fact.tag}\` | ${fact.description || "-"} | ${payload} |`);
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 fact of logic.facts) {
3805
- lines.push(` ${fact.tag.replace(/[^a-zA-Z0-9]/g, "")} --> [*]`);
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
  });