@plures/praxis 1.1.3 → 1.2.10

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 (74) hide show
  1. package/FRAMEWORK.md +106 -15
  2. package/README.md +194 -119
  3. package/dist/browser/adapter-CIMBGDC7.js +14 -0
  4. package/dist/browser/chunk-K377RW4V.js +230 -0
  5. package/dist/browser/chunk-MBVHLOU2.js +152 -0
  6. package/dist/browser/{chunk-R45WXWKH.js → chunk-VOMLVI6V.js} +1 -149
  7. package/dist/browser/engine-YJZV4SLD.js +8 -0
  8. package/dist/browser/index.d.ts +161 -5
  9. package/dist/browser/index.js +156 -141
  10. package/dist/browser/integrations/svelte.d.ts +2 -2
  11. package/dist/browser/integrations/svelte.js +2 -1
  12. package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts} +136 -1
  13. package/dist/node/adapter-75ISSMWD.js +15 -0
  14. package/dist/node/chunk-5RH7UAQC.js +486 -0
  15. package/dist/node/chunk-MBVHLOU2.js +152 -0
  16. package/dist/node/chunk-PRPQO6R5.js +85 -0
  17. package/dist/node/chunk-R2PSBPKQ.js +150 -0
  18. package/dist/node/chunk-S54337I5.js +446 -0
  19. package/dist/node/{chunk-R45WXWKH.js → chunk-VOMLVI6V.js} +1 -149
  20. package/dist/node/chunk-WZ6B3LZ6.js +638 -0
  21. package/dist/node/cli/index.cjs +2936 -897
  22. package/dist/node/cli/index.js +27 -0
  23. package/dist/node/components/index.d.cts +3 -2
  24. package/dist/node/components/index.d.ts +3 -2
  25. package/dist/node/docs-JFNYTOJA.js +102 -0
  26. package/dist/node/engine-2DQBKBJC.js +9 -0
  27. package/dist/node/index.cjs +1114 -354
  28. package/dist/node/index.d.cts +388 -5
  29. package/dist/node/index.d.ts +388 -5
  30. package/dist/node/index.js +201 -640
  31. package/dist/node/integrations/svelte.cjs +76 -0
  32. package/dist/node/integrations/svelte.d.cts +2 -2
  33. package/dist/node/integrations/svelte.d.ts +2 -2
  34. package/dist/node/integrations/svelte.js +3 -1
  35. package/dist/node/{reactive-engine.svelte-1M4m_C_v.d.cts → reactive-engine.svelte-BFIZfawz.d.cts} +199 -1
  36. package/dist/node/{reactive-engine.svelte-ChNFn4Hj.d.ts → reactive-engine.svelte-CRNqHlbv.d.ts} +199 -1
  37. package/dist/node/reverse-W7THPV45.js +193 -0
  38. package/dist/node/{terminal-adapter-CWka-yL8.d.ts → terminal-adapter-B-UK_Vdz.d.ts} +28 -3
  39. package/dist/node/{terminal-adapter-CDzxoLKR.d.cts → terminal-adapter-BQSIF5bf.d.cts} +28 -3
  40. package/dist/node/validate-CNHUULQE.js +180 -0
  41. package/docs/core/pluresdb-integration.md +15 -15
  42. package/docs/decision-ledger/BEHAVIOR_LEDGER.md +225 -0
  43. package/docs/decision-ledger/DecisionLedger.tla +180 -0
  44. package/docs/decision-ledger/IMPLEMENTATION_SUMMARY.md +217 -0
  45. package/docs/decision-ledger/LATEST.md +166 -0
  46. package/docs/guides/cicd-pipeline.md +142 -0
  47. package/package.json +2 -2
  48. package/src/__tests__/cli-validate.test.ts +197 -0
  49. package/src/__tests__/decision-ledger.test.ts +485 -0
  50. package/src/__tests__/reverse-generator.test.ts +189 -0
  51. package/src/__tests__/scanner.test.ts +215 -0
  52. package/src/cli/commands/docs.ts +147 -0
  53. package/src/cli/commands/reverse.ts +289 -0
  54. package/src/cli/commands/validate.ts +264 -0
  55. package/src/cli/index.ts +68 -0
  56. package/src/core/pluresdb/adapter.ts +46 -3
  57. package/src/core/reactive-engine.svelte.ts +6 -1
  58. package/src/core/reactive-engine.ts +1 -1
  59. package/src/core/rules.ts +133 -0
  60. package/src/decision-ledger/README.md +400 -0
  61. package/src/decision-ledger/REVERSE_ENGINEERING.md +484 -0
  62. package/src/decision-ledger/facts-events.ts +121 -0
  63. package/src/decision-ledger/index.ts +70 -0
  64. package/src/decision-ledger/ledger.ts +246 -0
  65. package/src/decision-ledger/logic-ledger.ts +158 -0
  66. package/src/decision-ledger/reverse-generator.ts +426 -0
  67. package/src/decision-ledger/scanner.ts +506 -0
  68. package/src/decision-ledger/types.ts +247 -0
  69. package/src/decision-ledger/validation.ts +336 -0
  70. package/src/dsl/index.ts +13 -2
  71. package/src/index.browser.ts +6 -0
  72. package/src/index.ts +40 -0
  73. package/src/integrations/pluresdb.ts +14 -2
  74. package/src/integrations/unified.ts +350 -0
@@ -0,0 +1,485 @@
1
+ /**
2
+ * Decision Ledger - Tests
3
+ *
4
+ * Tests for contract definition, validation, and ledger operations.
5
+ *
6
+ * These tests are derived from the behavior ledger examples and assumptions.
7
+ */
8
+
9
+ import { describe, it, expect } from 'vitest';
10
+ import { PraxisRegistry } from '../core/rules.js';
11
+ import { defineRule } from '../dsl/index.js';
12
+ import {
13
+ defineContract,
14
+ getContract,
15
+ isContract,
16
+ validateContracts,
17
+ formatValidationReport,
18
+ ContractMissing,
19
+ AcknowledgeContractGap,
20
+ BehaviorLedger,
21
+ createBehaviorLedger,
22
+ } from '../decision-ledger/index.js';
23
+
24
+ describe('Decision Ledger', () => {
25
+ describe('Contract Definition', () => {
26
+ // Example 1 from behavior ledger: Defining a Contract for a Rule
27
+ it('should define a contract with behavior, examples, and invariants', () => {
28
+ const loginContract = defineContract({
29
+ ruleId: 'auth.login',
30
+ behavior: 'Process login events and create user session facts',
31
+ examples: [
32
+ {
33
+ given: 'User provides valid credentials',
34
+ when: 'LOGIN event is received',
35
+ then: 'UserSessionCreated fact is emitted',
36
+ },
37
+ ],
38
+ invariants: ['Session must have unique ID', 'Session must have timestamp'],
39
+ assumptions: [
40
+ {
41
+ id: 'assume-unique-username',
42
+ statement: 'Usernames are unique across the system',
43
+ confidence: 0.9,
44
+ justification: 'Standard practice in authentication systems',
45
+ impacts: ['spec', 'tests'],
46
+ status: 'active',
47
+ },
48
+ ],
49
+ references: [{ type: 'doc', url: 'https://docs.example.com/auth' }],
50
+ });
51
+
52
+ expect(loginContract.ruleId).toBe('auth.login');
53
+ expect(loginContract.behavior).toBe('Process login events and create user session facts');
54
+ expect(loginContract.examples).toHaveLength(1);
55
+ expect(loginContract.examples[0].given).toBe('User provides valid credentials');
56
+ expect(loginContract.invariants).toHaveLength(2);
57
+ expect(loginContract.assumptions).toHaveLength(1);
58
+ expect(loginContract.assumptions![0].id).toBe('assume-unique-username');
59
+ expect(loginContract.references).toHaveLength(1);
60
+ });
61
+
62
+ it('should throw error if contract has no examples', () => {
63
+ expect(() => {
64
+ defineContract({
65
+ ruleId: 'test.rule',
66
+ behavior: 'Test behavior',
67
+ examples: [],
68
+ invariants: [],
69
+ });
70
+ }).toThrow('Contract must have at least one example');
71
+ });
72
+
73
+ it('should validate contract structure with type guard', () => {
74
+ const validContract = {
75
+ ruleId: 'test.rule',
76
+ behavior: 'Test behavior',
77
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
78
+ invariants: ['test'],
79
+ };
80
+
81
+ expect(isContract(validContract)).toBe(true);
82
+
83
+ const invalidContract = {
84
+ ruleId: 'test.rule',
85
+ behavior: 'Test behavior',
86
+ examples: [],
87
+ invariants: [],
88
+ };
89
+
90
+ expect(isContract(invalidContract)).toBe(false);
91
+ });
92
+
93
+ it('should extract contract from rule metadata', () => {
94
+ const contract = defineContract({
95
+ ruleId: 'test.rule',
96
+ behavior: 'Test behavior',
97
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
98
+ invariants: [],
99
+ });
100
+
101
+ const rule = defineRule({
102
+ id: 'test.rule',
103
+ description: 'Test rule',
104
+ impl: () => [],
105
+ meta: { contract },
106
+ });
107
+
108
+ const extracted = getContract(rule.meta);
109
+ expect(extracted).toBeDefined();
110
+ expect(extracted?.ruleId).toBe('test.rule');
111
+ });
112
+ });
113
+
114
+ describe('Contract Validation', () => {
115
+ // Example 2 from behavior ledger: Build-time Validation
116
+ it('should validate registry and produce complete/incomplete report', () => {
117
+ const registry = new PraxisRegistry();
118
+
119
+ // Rule with complete contract
120
+ const completeContract = defineContract({
121
+ ruleId: 'auth.login',
122
+ behavior: 'Process login events',
123
+ examples: [{ given: 'valid creds', when: 'LOGIN', then: 'session created' }],
124
+ invariants: ['unique session ID'],
125
+ });
126
+
127
+ registry.registerRule(
128
+ defineRule({
129
+ id: 'auth.login',
130
+ description: 'Login rule',
131
+ impl: () => [],
132
+ meta: { contract: completeContract },
133
+ })
134
+ );
135
+
136
+ // Rule without contract
137
+ registry.registerRule(
138
+ defineRule({
139
+ id: 'cart.addItem',
140
+ description: 'Add item to cart',
141
+ impl: () => [],
142
+ })
143
+ );
144
+
145
+ const report = validateContracts(registry);
146
+
147
+ expect(report.total).toBe(2);
148
+ expect(report.complete).toHaveLength(1);
149
+ expect(report.complete[0].ruleId).toBe('auth.login');
150
+ expect(report.missing).toHaveLength(1);
151
+ expect(report.missing).toContain('cart.addItem');
152
+ // Rules without contracts only appear in missing array, not incomplete
153
+ expect(report.incomplete).toHaveLength(0);
154
+ });
155
+
156
+ it('should validate contract completeness', () => {
157
+ const registry = new PraxisRegistry();
158
+
159
+ // Contract missing behavior
160
+ const incompleteContract = defineContract({
161
+ ruleId: 'test.rule',
162
+ behavior: '', // Empty behavior
163
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
164
+ invariants: [],
165
+ });
166
+
167
+ registry.registerRule(
168
+ defineRule({
169
+ id: 'test.rule',
170
+ description: 'Test',
171
+ impl: () => [],
172
+ meta: { contract: incompleteContract },
173
+ })
174
+ );
175
+
176
+ const report = validateContracts(registry, {
177
+ requiredFields: ['behavior', 'examples'],
178
+ });
179
+
180
+ expect(report.incomplete).toHaveLength(1);
181
+ expect(report.incomplete[0].missing).toContain('behavior');
182
+ });
183
+
184
+ it('should format validation report as text', () => {
185
+ const registry = new PraxisRegistry();
186
+
187
+ registry.registerRule(
188
+ defineRule({
189
+ id: 'test.rule',
190
+ description: 'Test',
191
+ impl: () => [],
192
+ meta: {
193
+ contract: defineContract({
194
+ ruleId: 'test.rule',
195
+ behavior: 'Test',
196
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
197
+ invariants: [],
198
+ }),
199
+ },
200
+ })
201
+ );
202
+
203
+ const report = validateContracts(registry);
204
+ const formatted = formatValidationReport(report);
205
+
206
+ expect(formatted).toContain('Contract Validation Report');
207
+ expect(formatted).toContain('✓ Complete Contracts:');
208
+ expect(formatted).toContain('test.rule');
209
+ });
210
+
211
+ it('should support strict validation mode', () => {
212
+ const registry = new PraxisRegistry();
213
+
214
+ registry.registerRule(
215
+ defineRule({
216
+ id: 'missing.contract',
217
+ description: 'No contract',
218
+ impl: () => [],
219
+ })
220
+ );
221
+
222
+ const report = validateContracts(registry, { strict: true });
223
+
224
+ // Rules without contracts only appear in missing array, not incomplete
225
+ expect(report.missing).toHaveLength(1);
226
+ expect(report.missing).toContain('missing.contract');
227
+ expect(report.incomplete).toHaveLength(0);
228
+ });
229
+ });
230
+
231
+ describe('Facts and Events', () => {
232
+ // Example 3 from behavior ledger: Runtime Validation
233
+ it('should create ContractMissing fact', () => {
234
+ const fact = ContractMissing.create({
235
+ ruleId: 'test.rule',
236
+ missing: ['behavior', 'examples'],
237
+ severity: 'warning',
238
+ });
239
+
240
+ expect(fact.tag).toBe('ContractMissing');
241
+ expect(fact.payload.ruleId).toBe('test.rule');
242
+ expect(fact.payload.missing).toContain('behavior');
243
+ expect(fact.payload.severity).toBe('warning');
244
+ });
245
+
246
+ // Example 4 from behavior ledger: Contract Gap Acknowledgment
247
+ it('should create AcknowledgeContractGap event', () => {
248
+ const event = AcknowledgeContractGap.create({
249
+ ruleId: 'legacy.process',
250
+ missing: ['spec', 'tests'],
251
+ justification: 'Legacy rule to be deprecated in v2.0',
252
+ expiresAt: '2025-12-31',
253
+ });
254
+
255
+ expect(event.tag).toBe('ACKNOWLEDGE_CONTRACT_GAP');
256
+ expect(event.payload.ruleId).toBe('legacy.process');
257
+ expect(event.payload.justification).toBe('Legacy rule to be deprecated in v2.0');
258
+ expect(event.payload.expiresAt).toBe('2025-12-31');
259
+ });
260
+
261
+ it('should use type guards for facts and events', () => {
262
+ const fact = ContractMissing.create({
263
+ ruleId: 'test',
264
+ missing: ['contract'],
265
+ severity: 'warning',
266
+ });
267
+
268
+ expect(ContractMissing.is(fact)).toBe(true);
269
+
270
+ const event = AcknowledgeContractGap.create({
271
+ ruleId: 'test',
272
+ missing: ['contract'],
273
+ justification: 'test',
274
+ });
275
+
276
+ expect(AcknowledgeContractGap.is(event)).toBe(true);
277
+ });
278
+ });
279
+
280
+ describe('Behavior Ledger', () => {
281
+ // Invariant: Ledger Append-Only
282
+ it('should maintain append-only ledger', () => {
283
+ const ledger = createBehaviorLedger();
284
+
285
+ const contract1 = defineContract({
286
+ ruleId: 'test.rule',
287
+ behavior: 'Version 1',
288
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
289
+ invariants: [],
290
+ });
291
+
292
+ ledger.append({
293
+ id: 'entry-1',
294
+ timestamp: new Date().toISOString(),
295
+ status: 'active',
296
+ author: 'system',
297
+ contract: contract1,
298
+ });
299
+
300
+ expect(ledger.getAllEntries()).toHaveLength(1);
301
+
302
+ // Cannot append entry with same ID
303
+ expect(() => {
304
+ ledger.append({
305
+ id: 'entry-1',
306
+ timestamp: new Date().toISOString(),
307
+ status: 'active',
308
+ author: 'system',
309
+ contract: contract1,
310
+ });
311
+ }).toThrow('already exists');
312
+ });
313
+
314
+ // Invariant: Ledger Unique IDs
315
+ it('should enforce unique entry IDs', () => {
316
+ const ledger = createBehaviorLedger();
317
+
318
+ const contract = defineContract({
319
+ ruleId: 'test',
320
+ behavior: 'test',
321
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
322
+ invariants: [],
323
+ });
324
+
325
+ ledger.append({
326
+ id: 'unique-1',
327
+ timestamp: new Date().toISOString(),
328
+ status: 'active',
329
+ author: 'system',
330
+ contract,
331
+ });
332
+
333
+ expect(() => {
334
+ ledger.append({
335
+ id: 'unique-1',
336
+ timestamp: new Date().toISOString(),
337
+ status: 'active',
338
+ author: 'system',
339
+ contract,
340
+ });
341
+ }).toThrow();
342
+ });
343
+
344
+ it('should supersede previous entries', () => {
345
+ const ledger = createBehaviorLedger();
346
+
347
+ const contract1 = defineContract({
348
+ ruleId: 'test.rule',
349
+ behavior: 'Version 1',
350
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
351
+ invariants: [],
352
+ version: '1.0.0',
353
+ });
354
+
355
+ ledger.append({
356
+ id: 'entry-1',
357
+ timestamp: '2025-01-01T00:00:00Z',
358
+ status: 'active',
359
+ author: 'system',
360
+ contract: contract1,
361
+ });
362
+
363
+ const contract2 = defineContract({
364
+ ruleId: 'test.rule',
365
+ behavior: 'Version 2',
366
+ examples: [{ given: 'x', when: 'y', then: 'z' }],
367
+ invariants: [],
368
+ version: '2.0.0',
369
+ });
370
+
371
+ ledger.append({
372
+ id: 'entry-2',
373
+ timestamp: '2025-01-02T00:00:00Z',
374
+ status: 'active',
375
+ author: 'system',
376
+ contract: contract2,
377
+ supersedes: 'entry-1',
378
+ });
379
+
380
+ const latest = ledger.getLatestEntry('test.rule');
381
+ expect(latest?.id).toBe('entry-2');
382
+ expect(latest?.contract.version).toBe('2.0.0');
383
+
384
+ const entry1 = ledger.getEntry('entry-1');
385
+ expect(entry1?.status).toBe('superseded');
386
+ });
387
+
388
+ it('should track assumptions', () => {
389
+ const ledger = createBehaviorLedger();
390
+
391
+ const contract = defineContract({
392
+ ruleId: 'test.rule',
393
+ behavior: 'Test',
394
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
395
+ invariants: [],
396
+ assumptions: [
397
+ {
398
+ id: 'test-assumption',
399
+ statement: 'Test assumption',
400
+ confidence: 0.8,
401
+ justification: 'For testing',
402
+ impacts: ['tests'],
403
+ status: 'active',
404
+ },
405
+ ],
406
+ });
407
+
408
+ ledger.append({
409
+ id: 'entry-1',
410
+ timestamp: new Date().toISOString(),
411
+ status: 'active',
412
+ author: 'system',
413
+ contract,
414
+ });
415
+
416
+ const assumptions = ledger.getActiveAssumptions();
417
+ expect(assumptions.size).toBe(1);
418
+ expect(assumptions.get('test-assumption')?.statement).toBe('Test assumption');
419
+ });
420
+
421
+ it('should export and import ledger as JSON', () => {
422
+ const ledger1 = createBehaviorLedger();
423
+
424
+ const contract = defineContract({
425
+ ruleId: 'test.rule',
426
+ behavior: 'Test',
427
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
428
+ invariants: [],
429
+ });
430
+
431
+ ledger1.append({
432
+ id: 'entry-1',
433
+ timestamp: new Date().toISOString(),
434
+ status: 'active',
435
+ author: 'system',
436
+ contract,
437
+ });
438
+
439
+ const json = ledger1.toJSON();
440
+ const ledger2 = BehaviorLedger.fromJSON(json);
441
+
442
+ expect(ledger2.getAllEntries()).toHaveLength(1);
443
+ expect(ledger2.getEntry('entry-1')?.contract.ruleId).toBe('test.rule');
444
+ });
445
+
446
+ it('should provide ledger statistics', () => {
447
+ const ledger = createBehaviorLedger();
448
+
449
+ const contract1 = defineContract({
450
+ ruleId: 'rule1',
451
+ behavior: 'Rule 1',
452
+ examples: [{ given: 'a', when: 'b', then: 'c' }],
453
+ invariants: [],
454
+ });
455
+
456
+ const contract2 = defineContract({
457
+ ruleId: 'rule2',
458
+ behavior: 'Rule 2',
459
+ examples: [{ given: 'x', when: 'y', then: 'z' }],
460
+ invariants: [],
461
+ });
462
+
463
+ ledger.append({
464
+ id: 'entry-1',
465
+ timestamp: new Date().toISOString(),
466
+ status: 'active',
467
+ author: 'system',
468
+ contract: contract1,
469
+ });
470
+
471
+ ledger.append({
472
+ id: 'entry-2',
473
+ timestamp: new Date().toISOString(),
474
+ status: 'active',
475
+ author: 'system',
476
+ contract: contract2,
477
+ });
478
+
479
+ const stats = ledger.getStats();
480
+ expect(stats.totalEntries).toBe(2);
481
+ expect(stats.activeEntries).toBe(2);
482
+ expect(stats.uniqueRules).toBe(2);
483
+ });
484
+ });
485
+ });
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Decision Ledger - Reverse Generator Tests
3
+ *
4
+ * Tests for reverse contract generation from existing code.
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import { generateContractFromRule } from '../decision-ledger/reverse-generator.js';
9
+ import type { RuleDescriptor } from '../core/rules.js';
10
+
11
+ describe('Reverse Contract Generator', () => {
12
+ describe('generateContractFromRule', () => {
13
+ it('should generate contract with default values when no files provided', async () => {
14
+ const rule: RuleDescriptor = {
15
+ id: 'test.rule',
16
+ description: 'Test rule description',
17
+ impl: () => [],
18
+ };
19
+
20
+ const result = await generateContractFromRule(rule, {
21
+ aiProvider: 'none',
22
+ });
23
+
24
+ expect(result.contract.ruleId).toBe('test.rule');
25
+ expect(result.contract.behavior).toContain('Test rule description');
26
+ expect(result.contract.examples.length).toBeGreaterThan(0);
27
+ expect(result.contract.invariants.length).toBeGreaterThan(0);
28
+ expect(result.method).toBe('heuristic');
29
+ expect(result.confidence).toBeGreaterThan(0);
30
+ });
31
+
32
+ it('should generate assumptions when requested', async () => {
33
+ const rule: RuleDescriptor = {
34
+ id: 'auth.login',
35
+ description: 'Process login events',
36
+ impl: () => [],
37
+ };
38
+
39
+ const result = await generateContractFromRule(rule, {
40
+ aiProvider: 'none',
41
+ includeAssumptions: true,
42
+ });
43
+
44
+ expect(result.contract.assumptions).toBeDefined();
45
+ expect(result.contract.assumptions!.length).toBeGreaterThan(0);
46
+
47
+ const assumption = result.contract.assumptions![0];
48
+ expect(assumption.id).toBeDefined();
49
+ expect(assumption.statement).toBeDefined();
50
+ expect(assumption.confidence).toBeGreaterThanOrEqual(0);
51
+ expect(assumption.confidence).toBeLessThanOrEqual(1);
52
+ expect(assumption.status).toBe('active');
53
+ });
54
+
55
+ it('should not generate assumptions when not requested', async () => {
56
+ const rule: RuleDescriptor = {
57
+ id: 'test.rule',
58
+ description: 'Test rule',
59
+ impl: () => [],
60
+ };
61
+
62
+ const result = await generateContractFromRule(rule, {
63
+ aiProvider: 'none',
64
+ includeAssumptions: false,
65
+ });
66
+
67
+ expect(result.contract.assumptions).toBeDefined();
68
+ expect(result.contract.assumptions!.length).toBe(0);
69
+ });
70
+
71
+ it('should increase confidence when artifacts are provided', async () => {
72
+ const rule: RuleDescriptor = {
73
+ id: 'test.rule',
74
+ description: 'Test rule',
75
+ impl: () => [],
76
+ };
77
+
78
+ // Baseline: no artifacts
79
+ const resultBaseline = await generateContractFromRule(rule, {
80
+ aiProvider: 'none',
81
+ });
82
+
83
+ // With test files (should increase confidence)
84
+ const resultWithTests = await generateContractFromRule(rule, {
85
+ aiProvider: 'none',
86
+ testFiles: ['/path/to/test1.ts', '/path/to/test2.ts'],
87
+ });
88
+
89
+ // With spec files (should increase confidence)
90
+ const resultWithSpecs = await generateContractFromRule(rule, {
91
+ aiProvider: 'none',
92
+ specFiles: ['/path/to/spec.tla'],
93
+ });
94
+
95
+ // Confidence should increase with more artifacts
96
+ expect(resultWithTests.confidence).toBeGreaterThan(resultBaseline.confidence);
97
+ expect(resultWithSpecs.confidence).toBeGreaterThan(resultBaseline.confidence);
98
+ });
99
+
100
+ it('should include warnings for missing information', async () => {
101
+ const rule: RuleDescriptor = {
102
+ id: 'test.rule',
103
+ description: 'Test rule',
104
+ impl: () => [],
105
+ };
106
+
107
+ const result = await generateContractFromRule(rule, {
108
+ aiProvider: 'none',
109
+ generateExamples: true,
110
+ });
111
+
112
+ expect(result.warnings).toBeDefined();
113
+ expect(Array.isArray(result.warnings)).toBe(true);
114
+
115
+ // Should warn about missing test files
116
+ const hasTestWarning = result.warnings.some((w) =>
117
+ w.includes('test') || w.includes('example')
118
+ );
119
+ expect(hasTestWarning).toBe(true);
120
+ });
121
+
122
+ it('should use rule description as behavior if no other source', async () => {
123
+ const rule: RuleDescriptor = {
124
+ id: 'custom.rule',
125
+ description: 'Custom rule that does something specific',
126
+ impl: () => [],
127
+ };
128
+
129
+ const result = await generateContractFromRule(rule, {
130
+ aiProvider: 'none',
131
+ });
132
+
133
+ expect(result.contract.behavior).toContain('Custom rule that does something specific');
134
+ });
135
+
136
+ it('should generate default examples when no tests provided', async () => {
137
+ const rule: RuleDescriptor = {
138
+ id: 'test.rule',
139
+ description: 'Test rule',
140
+ impl: () => [],
141
+ };
142
+
143
+ const result = await generateContractFromRule(rule, {
144
+ aiProvider: 'none',
145
+ generateExamples: true,
146
+ });
147
+
148
+ expect(result.contract.examples.length).toBeGreaterThan(0);
149
+
150
+ const example = result.contract.examples[0];
151
+ expect(example.given).toBeDefined();
152
+ expect(example.when).toBeDefined();
153
+ expect(example.then).toBeDefined();
154
+ });
155
+
156
+ it('should handle constraint descriptors', async () => {
157
+ const constraint = {
158
+ id: 'test.constraint',
159
+ description: 'Test constraint',
160
+ impl: () => true,
161
+ };
162
+
163
+ const result = await generateContractFromRule(constraint, {
164
+ aiProvider: 'none',
165
+ });
166
+
167
+ expect(result.contract.ruleId).toBe('test.constraint');
168
+ expect(result.contract.behavior).toContain('Test constraint');
169
+ });
170
+
171
+ it('should cap confidence at reasonable level for heuristic method', async () => {
172
+ const rule: RuleDescriptor = {
173
+ id: 'test.rule',
174
+ description: 'Test rule',
175
+ impl: () => [],
176
+ };
177
+
178
+ const result = await generateContractFromRule(rule, {
179
+ aiProvider: 'none',
180
+ sourceFile: '/path/to/rule.ts',
181
+ testFiles: ['/path/to/test1.ts', '/path/to/test2.ts'],
182
+ specFiles: ['/path/to/spec.tla'],
183
+ });
184
+
185
+ // Heuristic should not claim perfect confidence
186
+ expect(result.confidence).toBeLessThan(1.0);
187
+ });
188
+ });
189
+ });