@plures/praxis 1.2.12 → 1.2.41

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 (85) hide show
  1. package/README.md +63 -0
  2. package/dist/browser/{chunk-VOMLVI6V.js → chunk-BBP2F7TT.js} +70 -1
  3. package/dist/browser/{chunk-K377RW4V.js → chunk-FCEH7WMH.js} +1 -1
  4. package/dist/browser/{engine-YJZV4SLD.js → engine-65QDGCAN.js} +1 -1
  5. package/dist/browser/index.d.ts +104 -2
  6. package/dist/browser/index.js +181 -5
  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-Cqd8Mod2.d.ts} +56 -1
  10. package/dist/node/{chunk-PRPQO6R5.js → chunk-32YFEEML.js} +1 -1
  11. package/dist/node/{chunk-VOMLVI6V.js → chunk-BBP2F7TT.js} +70 -1
  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-7CXQV6RC.js} +1 -1
  21. package/dist/node/index.cjs +408 -3
  22. package/dist/node/index.d.cts +308 -7
  23. package/dist/node/index.d.ts +308 -7
  24. package/dist/node/index.js +336 -6
  25. package/dist/node/integrations/svelte.cjs +70 -1
  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-BocKczNv.d.cts} +1 -1
  30. package/dist/node/{protocol-Qek7ebBl.d.cts → protocol-BocKczNv.d.ts} +1 -1
  31. package/dist/node/{reactive-engine.svelte-CRNqHlbv.d.ts → reactive-engine.svelte-CGe8SpVE.d.cts} +57 -2
  32. package/dist/node/{reactive-engine.svelte-BFIZfawz.d.cts → reactive-engine.svelte-D-xTDxT5.d.ts} +57 -2
  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 +7 -2
  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/cli/commands/conversations.ts +252 -0
  60. package/src/cli/index.ts +73 -0
  61. package/src/conversations/README.md +230 -0
  62. package/src/conversations/candidate.schema.json +123 -0
  63. package/src/conversations/candidates.ts +114 -0
  64. package/src/conversations/capture.ts +56 -0
  65. package/src/conversations/classify.ts +110 -0
  66. package/src/conversations/conversation.schema.json +106 -0
  67. package/src/conversations/emitters/fs.ts +65 -0
  68. package/src/conversations/emitters/github.ts +115 -0
  69. package/src/conversations/gate.ts +102 -0
  70. package/src/conversations/index.ts +28 -0
  71. package/src/conversations/normalize.ts +51 -0
  72. package/src/conversations/redact.ts +57 -0
  73. package/src/conversations/types.ts +96 -0
  74. package/src/core/chronicle/chronicle.ts +227 -0
  75. package/src/core/chronicle/context.ts +80 -0
  76. package/src/core/chronicle/index.ts +53 -0
  77. package/src/core/chronicle/mcp.ts +135 -0
  78. package/src/core/chronicle/types.ts +61 -0
  79. package/src/core/engine.ts +99 -1
  80. package/src/core/pluresdb/index.ts +22 -0
  81. package/src/core/pluresdb/store.ts +162 -5
  82. package/src/core/rules.ts +12 -0
  83. package/src/dsl/index.ts +6 -0
  84. package/src/index.ts +18 -0
  85. package/src/integrations/pluresdb.ts +22 -0
@@ -0,0 +1,604 @@
1
+ # Extending Praxis-Core
2
+
3
+ **Audience:** Library authors, plugin developers, and advanced users
4
+ **Status:** Stable guidance
5
+ **Last Updated:** 2026-02-01
6
+
7
+ ## Overview
8
+
9
+ This guide explains how to extend Praxis-Core safely and effectively without breaking existing applications. It covers best practices for creating custom rules, constraints, modules, and integrations.
10
+
11
+ ## Table of Contents
12
+
13
+ - [Principles of Extension](#principles-of-extension)
14
+ - [Creating Custom Rules](#creating-custom-rules)
15
+ - [Creating Custom Constraints](#creating-custom-constraints)
16
+ - [Building Modules](#building-modules)
17
+ - [Contract Requirements](#contract-requirements)
18
+ - [Testing Extensions](#testing-extensions)
19
+ - [Publishing Extensions](#publishing-extensions)
20
+ - [Breaking Change Policy](#breaking-change-policy)
21
+
22
+ ## Principles of Extension
23
+
24
+ ### 1. Purity First
25
+
26
+ All rules and constraints must be pure functions:
27
+
28
+ ```typescript
29
+ // ✅ GOOD: Pure function
30
+ const goodRule = defineRule({
31
+ id: 'counter.increment',
32
+ description: 'Increment counter',
33
+ impl: (state, events) => {
34
+ const incrementEvent = events.find(Increment.is);
35
+ if (!incrementEvent) return [];
36
+ return [CountUpdated.create({ value: state.context.count + 1 })];
37
+ }
38
+ });
39
+
40
+ // ❌ BAD: Side effects
41
+ const badRule = defineRule({
42
+ id: 'counter.increment',
43
+ description: 'Increment counter',
44
+ impl: (state, events) => {
45
+ console.log('Incrementing!'); // Side effect: logging
46
+ fetch('/api/increment'); // Side effect: network
47
+ return [];
48
+ }
49
+ });
50
+ ```
51
+
52
+ **Why?** Pure functions are:
53
+ - Testable
54
+ - Predictable
55
+ - Serializable
56
+ - Cross-language compatible
57
+
58
+ ### 2. Stable Identifiers
59
+
60
+ Rule and constraint IDs are permanent. Once published, they should never change.
61
+
62
+ ```typescript
63
+ // ✅ GOOD: Stable, namespaced ID
64
+ const rule = defineRule({
65
+ id: 'myapp.auth.login', // namespace.domain.action
66
+ description: 'Handle user login',
67
+ impl: loginImpl
68
+ });
69
+
70
+ // ❌ BAD: Generic, likely to conflict
71
+ const rule = defineRule({
72
+ id: 'login', // Too generic, no namespace
73
+ description: 'Handle user login',
74
+ impl: loginImpl
75
+ });
76
+ ```
77
+
78
+ **ID Conventions:**
79
+ - Format: `{namespace}.{domain}.{action}`
80
+ - Namespace: Your package/org name
81
+ - Domain: Feature area (auth, cart, etc.)
82
+ - Action: What the rule does
83
+
84
+ ### 3. Type Safety
85
+
86
+ Always provide explicit types for context and payloads:
87
+
88
+ ```typescript
89
+ // ✅ GOOD: Explicit types
90
+ interface AppContext {
91
+ userId: string | null;
92
+ sessionToken: string | null;
93
+ }
94
+
95
+ const LoginSuccess = defineFact<'LoginSuccess', { userId: string; token: string }>('LoginSuccess');
96
+
97
+ const loginRule = defineRule<AppContext>({
98
+ id: 'myapp.auth.login',
99
+ description: 'Process successful login',
100
+ impl: (state, events) => {
101
+ // state.context is typed as AppContext
102
+ // Full type safety throughout
103
+ }
104
+ });
105
+
106
+ // ❌ BAD: No types, relies on 'any'
107
+ const loginRule = defineRule({
108
+ id: 'myapp.auth.login',
109
+ description: 'Process successful login',
110
+ impl: (state, events) => {
111
+ // state.context is unknown
112
+ // No type safety
113
+ }
114
+ });
115
+ ```
116
+
117
+ ### 4. Immutability
118
+
119
+ Never mutate state directly. Always return new values:
120
+
121
+ ```typescript
122
+ // ✅ GOOD: Immutable updates
123
+ const rule = defineRule<AppContext>({
124
+ id: 'myapp.user.update',
125
+ description: 'Update user data',
126
+ impl: (state, events) => {
127
+ const updateEvent = events.find(UpdateUser.is);
128
+ if (!updateEvent) return [];
129
+
130
+ // Create new context, don't mutate
131
+ const newContext = {
132
+ ...state.context,
133
+ userId: updateEvent.payload.userId
134
+ };
135
+
136
+ return [UserUpdated.create(newContext)];
137
+ }
138
+ });
139
+
140
+ // ❌ BAD: Mutates state
141
+ const rule = defineRule<AppContext>({
142
+ id: 'myapp.user.update',
143
+ description: 'Update user data',
144
+ impl: (state, events) => {
145
+ const updateEvent = events.find(UpdateUser.is);
146
+ if (!updateEvent) return [];
147
+
148
+ // WRONG: Mutates existing state
149
+ state.context.userId = updateEvent.payload.userId;
150
+
151
+ return [];
152
+ }
153
+ });
154
+ ```
155
+
156
+ ## Creating Custom Rules
157
+
158
+ ### Basic Rule
159
+
160
+ ```typescript
161
+ import { defineRule, defineFact, defineEvent } from '@plures/praxis';
162
+
163
+ // 1. Define your events
164
+ const AddItem = defineEvent<'AddItem', { itemId: string; quantity: number }>('AddItem');
165
+
166
+ // 2. Define your facts
167
+ const ItemAdded = defineFact<'ItemAdded', { itemId: string; quantity: number }>('ItemAdded');
168
+
169
+ // 3. Define your context type
170
+ interface CartContext {
171
+ items: Array<{ itemId: string; quantity: number }>;
172
+ }
173
+
174
+ // 4. Define the rule
175
+ const addItemRule = defineRule<CartContext>({
176
+ id: 'myapp.cart.addItem',
177
+ description: 'Add item to shopping cart',
178
+ impl: (state, events) => {
179
+ const addEvent = events.find(AddItem.is);
180
+ if (!addEvent) return [];
181
+
182
+ return [ItemAdded.create({
183
+ itemId: addEvent.payload.itemId,
184
+ quantity: addEvent.payload.quantity
185
+ })];
186
+ }
187
+ });
188
+ ```
189
+
190
+ ### Rule with Guards
191
+
192
+ Use guard clauses for clean, readable rules:
193
+
194
+ ```typescript
195
+ const processOrderRule = defineRule<OrderContext>({
196
+ id: 'myapp.order.process',
197
+ description: 'Process order if valid',
198
+ impl: (state, events) => {
199
+ const orderEvent = events.find(PlaceOrder.is);
200
+
201
+ // Guard: No event
202
+ if (!orderEvent) return [];
203
+
204
+ // Guard: Empty cart
205
+ if (state.context.items.length === 0) {
206
+ return [OrderRejected.create({ reason: 'Cart is empty' })];
207
+ }
208
+
209
+ // Guard: Insufficient funds
210
+ if (state.context.balance < state.context.cartTotal) {
211
+ return [OrderRejected.create({ reason: 'Insufficient funds' })];
212
+ }
213
+
214
+ // Happy path
215
+ return [OrderProcessed.create({
216
+ orderId: generateId(),
217
+ items: state.context.items,
218
+ total: state.context.cartTotal
219
+ })];
220
+ }
221
+ });
222
+ ```
223
+
224
+ ### Multi-Event Rules
225
+
226
+ Rules can respond to multiple event types:
227
+
228
+ ```typescript
229
+ const sessionRule = defineRule<SessionContext>({
230
+ id: 'myapp.session.manage',
231
+ description: 'Manage user session lifecycle',
232
+ impl: (state, events) => {
233
+ const loginEvent = events.find(Login.is);
234
+ const logoutEvent = events.find(Logout.is);
235
+ const renewEvent = events.find(RenewSession.is);
236
+
237
+ if (loginEvent) {
238
+ return [SessionStarted.create({
239
+ userId: loginEvent.payload.userId,
240
+ expiresAt: Date.now() + 3600000 // 1 hour
241
+ })];
242
+ }
243
+
244
+ if (logoutEvent) {
245
+ return [SessionEnded.create({
246
+ userId: state.context.userId
247
+ })];
248
+ }
249
+
250
+ if (renewEvent && state.context.sessionActive) {
251
+ return [SessionRenewed.create({
252
+ expiresAt: Date.now() + 3600000
253
+ })];
254
+ }
255
+
256
+ return [];
257
+ }
258
+ });
259
+ ```
260
+
261
+ ## Creating Custom Constraints
262
+
263
+ ### Basic Constraint
264
+
265
+ ```typescript
266
+ import { defineConstraint } from '@plures/praxis';
267
+
268
+ interface CartContext {
269
+ items: Array<{ itemId: string; quantity: number }>;
270
+ maxItems: number;
271
+ }
272
+
273
+ const maxCartItemsConstraint = defineConstraint<CartContext>({
274
+ id: 'myapp.cart.maxItems',
275
+ description: 'Cart cannot exceed maximum item limit',
276
+ impl: (state) => {
277
+ const itemCount = state.context.items.length;
278
+ const maxItems = state.context.maxItems;
279
+
280
+ if (itemCount > maxItems) {
281
+ return `Cart has ${itemCount} items, maximum is ${maxItems}`;
282
+ }
283
+
284
+ return true;
285
+ }
286
+ });
287
+ ```
288
+
289
+ ### Constraint with Context
290
+
291
+ Constraints can validate complex business rules:
292
+
293
+ ```typescript
294
+ const orderValidConstraint = defineConstraint<OrderContext>({
295
+ id: 'myapp.order.valid',
296
+ description: 'Order must have valid items and payment',
297
+ impl: (state) => {
298
+ // Check items
299
+ if (state.context.items.length === 0) {
300
+ return 'Order must contain at least one item';
301
+ }
302
+
303
+ // Check quantities
304
+ const invalidQuantity = state.context.items.some(item => item.quantity <= 0);
305
+ if (invalidQuantity) {
306
+ return 'All items must have positive quantity';
307
+ }
308
+
309
+ // Check payment
310
+ if (!state.context.paymentMethod) {
311
+ return 'Payment method required';
312
+ }
313
+
314
+ // Check total
315
+ if (state.context.total <= 0) {
316
+ return 'Order total must be positive';
317
+ }
318
+
319
+ return true;
320
+ }
321
+ });
322
+ ```
323
+
324
+ ## Building Modules
325
+
326
+ Group related rules and constraints into modules:
327
+
328
+ ```typescript
329
+ import { defineModule } from '@plures/praxis';
330
+
331
+ interface CartContext {
332
+ items: Array<{ itemId: string; quantity: number }>;
333
+ maxItems: number;
334
+ total: number;
335
+ }
336
+
337
+ export const cartModule = defineModule<CartContext>({
338
+ id: 'myapp.cart',
339
+ description: 'Shopping cart logic',
340
+ rules: [
341
+ addItemRule,
342
+ removeItemRule,
343
+ updateQuantityRule,
344
+ clearCartRule
345
+ ],
346
+ constraints: [
347
+ maxCartItemsConstraint,
348
+ validQuantitiesConstraint,
349
+ validTotalConstraint
350
+ ]
351
+ });
352
+
353
+ // Usage
354
+ const registry = new PraxisRegistry<CartContext>();
355
+ registry.registerModule(cartModule);
356
+ ```
357
+
358
+ ## Contract Requirements
359
+
360
+ **Important:** All rules and constraints in praxis-core must have contracts when used in the Praxis repository itself (dogfooding requirement).
361
+
362
+ ### Defining Contracts
363
+
364
+ ```typescript
365
+ import { defineContract } from '@plures/praxis';
366
+
367
+ const addItemContract = defineContract({
368
+ ruleId: 'myapp.cart.addItem',
369
+ behavior: 'When an AddItem event is received, create an ItemAdded fact with the same itemId and quantity',
370
+ examples: [
371
+ {
372
+ given: 'Empty cart',
373
+ when: 'AddItem event with itemId="abc" and quantity=2',
374
+ then: 'ItemAdded fact emitted with itemId="abc" and quantity=2'
375
+ },
376
+ {
377
+ given: 'Cart with existing items',
378
+ when: 'AddItem event with itemId="xyz" and quantity=1',
379
+ then: 'ItemAdded fact emitted with itemId="xyz" and quantity=1'
380
+ }
381
+ ],
382
+ invariants: [
383
+ 'ItemAdded.itemId === AddItem.itemId',
384
+ 'ItemAdded.quantity === AddItem.quantity',
385
+ 'Exactly one ItemAdded fact per AddItem event'
386
+ ],
387
+ references: [
388
+ { type: 'doc', url: 'https://example.com/cart-spec', description: 'Cart specification' }
389
+ ]
390
+ });
391
+
392
+ const addItemRule = defineRule<CartContext>({
393
+ id: 'myapp.cart.addItem',
394
+ description: 'Add item to shopping cart',
395
+ impl: addItemImpl,
396
+ contract: addItemContract // Attach contract
397
+ });
398
+ ```
399
+
400
+ ### Contract Testing
401
+
402
+ All contract examples should have corresponding tests:
403
+
404
+ ```typescript
405
+ import { describe, it, expect } from 'vitest';
406
+
407
+ describe('myapp.cart.addItem', () => {
408
+ it('Example 1: Empty cart + AddItem -> ItemAdded', () => {
409
+ // Given: Empty cart
410
+ const registry = new PraxisRegistry<CartContext>();
411
+ registry.registerRule(addItemRule);
412
+ const engine = createPraxisEngine({
413
+ initialContext: { items: [], maxItems: 100, total: 0 },
414
+ registry
415
+ });
416
+
417
+ // When: AddItem event with itemId="abc" and quantity=2
418
+ const result = engine.step([
419
+ AddItem.create({ itemId: 'abc', quantity: 2 })
420
+ ]);
421
+
422
+ // Then: ItemAdded fact emitted
423
+ const itemAddedFacts = result.state.facts.filter(ItemAdded.is);
424
+ expect(itemAddedFacts).toHaveLength(1);
425
+ expect(itemAddedFacts[0].payload.itemId).toBe('abc');
426
+ expect(itemAddedFacts[0].payload.quantity).toBe(2);
427
+ });
428
+
429
+ // More tests for other examples...
430
+ });
431
+ ```
432
+
433
+ ## Testing Extensions
434
+
435
+ ### Unit Tests
436
+
437
+ Test rules and constraints in isolation:
438
+
439
+ ```typescript
440
+ describe('addItemRule', () => {
441
+ it('should emit ItemAdded fact when AddItem event received', () => {
442
+ const state: PraxisState & { context: CartContext } = {
443
+ context: { items: [], maxItems: 100, total: 0 },
444
+ facts: [],
445
+ protocolVersion: '1.0.0'
446
+ };
447
+
448
+ const events = [AddItem.create({ itemId: 'test', quantity: 1 })];
449
+
450
+ const result = addItemRule.impl(state, events);
451
+
452
+ expect(result).toHaveLength(1);
453
+ expect(ItemAdded.is(result[0])).toBe(true);
454
+ });
455
+ });
456
+ ```
457
+
458
+ ### Integration Tests
459
+
460
+ Test modules with the full engine:
461
+
462
+ ```typescript
463
+ describe('cartModule integration', () => {
464
+ it('should handle complete cart workflow', () => {
465
+ const registry = new PraxisRegistry<CartContext>();
466
+ registry.registerModule(cartModule);
467
+
468
+ const engine = createPraxisEngine({
469
+ initialContext: { items: [], maxItems: 10, total: 0 },
470
+ registry
471
+ });
472
+
473
+ // Add item
474
+ engine.step([AddItem.create({ itemId: 'item1', quantity: 2 })]);
475
+
476
+ // Update quantity
477
+ engine.step([UpdateQuantity.create({ itemId: 'item1', quantity: 3 })]);
478
+
479
+ // Verify final state
480
+ const context = engine.getContext();
481
+ expect(context.items).toHaveLength(1);
482
+ expect(context.items[0].quantity).toBe(3);
483
+ });
484
+ });
485
+ ```
486
+
487
+ ## Publishing Extensions
488
+
489
+ ### Package Structure
490
+
491
+ ```
492
+ my-praxis-extension/
493
+ ├── src/
494
+ │ ├── index.ts # Main exports
495
+ │ ├── rules.ts # Rule definitions
496
+ │ ├── constraints.ts # Constraint definitions
497
+ │ ├── contracts.ts # Contract definitions
498
+ │ └── types.ts # Type definitions
499
+ ├── dist/ # Compiled output
500
+ ├── package.json
501
+ ├── tsconfig.json
502
+ └── README.md
503
+ ```
504
+
505
+ ### Package.json
506
+
507
+ ```json
508
+ {
509
+ "name": "@yourorg/praxis-extension",
510
+ "version": "1.0.0",
511
+ "type": "module",
512
+ "main": "./dist/index.js",
513
+ "types": "./dist/index.d.ts",
514
+ "exports": {
515
+ ".": {
516
+ "types": "./dist/index.d.ts",
517
+ "import": "./dist/index.js"
518
+ }
519
+ },
520
+ "peerDependencies": {
521
+ "@plures/praxis": "^1.0.0"
522
+ },
523
+ "keywords": ["praxis", "praxis-extension"]
524
+ }
525
+ ```
526
+
527
+ ### Version Compatibility
528
+
529
+ Specify which praxis-core versions your extension supports:
530
+
531
+ ```typescript
532
+ export const EXTENSION_METADATA = {
533
+ name: '@yourorg/praxis-extension',
534
+ version: '1.0.0',
535
+ praxisVersions: {
536
+ min: '1.0.0',
537
+ max: '1.x.x' // Supports all 1.x versions
538
+ }
539
+ };
540
+ ```
541
+
542
+ ## Breaking Change Policy
543
+
544
+ ### For Extension Authors
545
+
546
+ When publishing extensions:
547
+
548
+ 1. **Follow SemVer**: Major for breaking, minor for features, patch for fixes
549
+ 2. **Document Changes**: Maintain a CHANGELOG.md
550
+ 3. **Deprecation Path**: Deprecate before removing (one minor version notice)
551
+ 4. **Migration Guide**: Provide upgrade instructions for breaking changes
552
+
553
+ ### Avoiding Breaking Changes
554
+
555
+ Safe additions (non-breaking):
556
+ - ✅ New rules or constraints
557
+ - ✅ New optional fields in context types
558
+ - ✅ New examples in contracts
559
+ - ✅ Performance improvements
560
+ - ✅ Bug fixes
561
+
562
+ Breaking changes:
563
+ - ❌ Changing rule IDs
564
+ - ❌ Changing rule behavior without deprecation
565
+ - ❌ Removing rules or constraints
566
+ - ❌ Changing context type requirements
567
+ - ❌ Changing event/fact payload structures
568
+
569
+ ## Best Practices Summary
570
+
571
+ 1. **Pure Functions**: Rules and constraints must be pure
572
+ 2. **Stable IDs**: Never change rule/constraint IDs
573
+ 3. **Type Safety**: Use explicit types for all contexts and payloads
574
+ 4. **Immutability**: Never mutate state
575
+ 5. **Contracts**: Document behavior with contracts
576
+ 6. **Tests**: Test all contract examples
577
+ 7. **Namespacing**: Use namespaced IDs to avoid conflicts
578
+ 8. **Documentation**: Document your extension thoroughly
579
+ 9. **Versioning**: Follow semantic versioning
580
+ 10. **Compatibility**: Test against supported praxis-core versions
581
+
582
+ ## Examples
583
+
584
+ See these examples for reference:
585
+
586
+ - [Cart Module](../../examples/todo/) - Simple module example
587
+ - [Auth Module](../../examples/hero-shop/) - Complex module with multiple rules
588
+ - [Form Builder](../../examples/form-builder/) - Dynamic constraint validation
589
+
590
+ ## Support
591
+
592
+ - **Questions**: [GitHub Discussions](https://github.com/plures/praxis/discussions)
593
+ - **Issues**: [GitHub Issues](https://github.com/plures/praxis/issues)
594
+ - **Documentation**: [Core API Docs](./praxis-core-api.md)
595
+
596
+ ## References
597
+
598
+ - [Praxis-Core API](./praxis-core-api.md)
599
+ - [Decision Ledger Dogfooding](../decision-ledger/DOGFOODING.md)
600
+ - [Contributing Guide](../../CONTRIBUTING.md)
601
+
602
+ ---
603
+
604
+ **Next:** [Decision Ledger](../decision-ledger/LATEST.md)