@plures/praxis 1.2.0 → 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 (63) hide show
  1. package/README.md +10 -96
  2. package/dist/browser/{adapter-TM4IS5KT.js → adapter-CIMBGDC7.js} +5 -3
  3. package/dist/browser/{chunk-LE2ZJYFC.js → chunk-K377RW4V.js} +76 -0
  4. package/dist/{node/chunk-JQ64KMLN.js → browser/chunk-MBVHLOU2.js} +12 -1
  5. package/dist/browser/index.d.ts +32 -5
  6. package/dist/browser/index.js +15 -7
  7. package/dist/browser/integrations/svelte.d.ts +2 -2
  8. package/dist/browser/integrations/svelte.js +1 -1
  9. package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts} +136 -1
  10. package/dist/node/{adapter-K6DOX6XS.js → adapter-75ISSMWD.js} +5 -3
  11. package/dist/node/chunk-5RH7UAQC.js +486 -0
  12. package/dist/{browser/chunk-JQ64KMLN.js → node/chunk-MBVHLOU2.js} +12 -1
  13. package/dist/node/{chunk-LE2ZJYFC.js → chunk-PRPQO6R5.js} +3 -72
  14. package/dist/node/chunk-R2PSBPKQ.js +150 -0
  15. package/dist/node/chunk-WZ6B3LZ6.js +638 -0
  16. package/dist/node/cli/index.cjs +2316 -832
  17. package/dist/node/cli/index.js +18 -0
  18. package/dist/node/components/index.d.cts +3 -2
  19. package/dist/node/components/index.d.ts +3 -2
  20. package/dist/node/index.cjs +620 -38
  21. package/dist/node/index.d.cts +259 -5
  22. package/dist/node/index.d.ts +259 -5
  23. package/dist/node/index.js +55 -65
  24. package/dist/node/integrations/svelte.cjs +76 -0
  25. package/dist/node/integrations/svelte.d.cts +2 -2
  26. package/dist/node/integrations/svelte.d.ts +2 -2
  27. package/dist/node/integrations/svelte.js +2 -1
  28. package/dist/node/{reactive-engine.svelte-1M4m_C_v.d.cts → reactive-engine.svelte-BFIZfawz.d.cts} +199 -1
  29. package/dist/node/{reactive-engine.svelte-ChNFn4Hj.d.ts → reactive-engine.svelte-CRNqHlbv.d.ts} +199 -1
  30. package/dist/node/reverse-W7THPV45.js +193 -0
  31. package/dist/node/{terminal-adapter-CWka-yL8.d.ts → terminal-adapter-B-UK_Vdz.d.ts} +28 -3
  32. package/dist/node/{terminal-adapter-CDzxoLKR.d.cts → terminal-adapter-BQSIF5bf.d.cts} +28 -3
  33. package/dist/node/validate-CNHUULQE.js +180 -0
  34. package/docs/core/pluresdb-integration.md +15 -15
  35. package/docs/decision-ledger/BEHAVIOR_LEDGER.md +225 -0
  36. package/docs/decision-ledger/DecisionLedger.tla +180 -0
  37. package/docs/decision-ledger/IMPLEMENTATION_SUMMARY.md +217 -0
  38. package/docs/decision-ledger/LATEST.md +166 -0
  39. package/docs/guides/cicd-pipeline.md +142 -0
  40. package/package.json +2 -2
  41. package/src/__tests__/cli-validate.test.ts +197 -0
  42. package/src/__tests__/decision-ledger.test.ts +485 -0
  43. package/src/__tests__/reverse-generator.test.ts +189 -0
  44. package/src/__tests__/scanner.test.ts +215 -0
  45. package/src/cli/commands/reverse.ts +289 -0
  46. package/src/cli/commands/validate.ts +264 -0
  47. package/src/cli/index.ts +47 -0
  48. package/src/core/pluresdb/adapter.ts +45 -2
  49. package/src/core/rules.ts +133 -0
  50. package/src/decision-ledger/README.md +400 -0
  51. package/src/decision-ledger/REVERSE_ENGINEERING.md +484 -0
  52. package/src/decision-ledger/facts-events.ts +121 -0
  53. package/src/decision-ledger/index.ts +70 -0
  54. package/src/decision-ledger/ledger.ts +246 -0
  55. package/src/decision-ledger/logic-ledger.ts +158 -0
  56. package/src/decision-ledger/reverse-generator.ts +426 -0
  57. package/src/decision-ledger/scanner.ts +506 -0
  58. package/src/decision-ledger/types.ts +247 -0
  59. package/src/decision-ledger/validation.ts +336 -0
  60. package/src/dsl/index.ts +13 -2
  61. package/src/index.browser.ts +2 -0
  62. package/src/index.ts +36 -0
  63. package/src/integrations/pluresdb.ts +14 -2
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Decision Ledger - Reverse Contract Generator
3
+ *
4
+ * Generates contracts from existing code using AI (GitHub Copilot/OpenAI) or
5
+ * non-AI heuristic approaches.
6
+ */
7
+
8
+ import type { Contract, Example, Assumption, Reference } from './types.js';
9
+ import type { RuleDescriptor, ConstraintDescriptor } from '../core/rules.js';
10
+ import { defineContract } from './types.js';
11
+ import { inferContractFromFile } from './scanner.js';
12
+
13
+ /**
14
+ * AI provider for contract generation.
15
+ */
16
+ export type AIProvider = 'none' | 'github-copilot' | 'openai' | 'auto';
17
+
18
+ /**
19
+ * Options for reverse contract generation.
20
+ */
21
+ export interface ReverseGenerationOptions {
22
+ /** AI provider to use */
23
+ aiProvider?: AIProvider;
24
+ /** OpenAI API key (if using OpenAI) */
25
+ openaiApiKey?: string;
26
+ /** GitHub token (if using GitHub Copilot) */
27
+ githubToken?: string;
28
+ /** Confidence threshold for AI-generated content (0.0 to 1.0) */
29
+ confidenceThreshold?: number;
30
+ /** Whether to include assumptions */
31
+ includeAssumptions?: boolean;
32
+ /** Whether to generate examples from tests */
33
+ generateExamples?: boolean;
34
+ /** Source file path for the rule/constraint */
35
+ sourceFile?: string;
36
+ /** Test file paths associated with the rule */
37
+ testFiles?: string[];
38
+ /** Spec file paths associated with the rule */
39
+ specFiles?: string[];
40
+ }
41
+
42
+ /**
43
+ * Result of reverse contract generation.
44
+ */
45
+ export interface GenerationResult {
46
+ /** Generated contract */
47
+ contract: Contract;
48
+ /** Confidence score (0.0 to 1.0) */
49
+ confidence: number;
50
+ /** Method used for generation */
51
+ method: 'ai' | 'heuristic' | 'hybrid';
52
+ /** Warnings or issues encountered */
53
+ warnings: string[];
54
+ }
55
+
56
+ /**
57
+ * Generate a contract from an existing rule or constraint.
58
+ *
59
+ * @param descriptor The rule or constraint descriptor
60
+ * @param options Generation options
61
+ * @returns Generation result
62
+ */
63
+ export async function generateContractFromRule(
64
+ descriptor: RuleDescriptor | ConstraintDescriptor,
65
+ options: ReverseGenerationOptions = {}
66
+ ): Promise<GenerationResult> {
67
+ const {
68
+ aiProvider = 'none',
69
+ confidenceThreshold = 0.7,
70
+ includeAssumptions = true,
71
+ generateExamples = true,
72
+ sourceFile,
73
+ testFiles = [],
74
+ specFiles = [],
75
+ } = options;
76
+
77
+ const warnings: string[] = [];
78
+
79
+ // Attempt AI-powered generation first
80
+ if (aiProvider !== 'none') {
81
+ try {
82
+ const aiResult = await generateWithAI(descriptor, options);
83
+ if (aiResult.confidence >= confidenceThreshold) {
84
+ return aiResult;
85
+ }
86
+ warnings.push(
87
+ `AI confidence ${aiResult.confidence} below threshold ${confidenceThreshold}, falling back to heuristic`
88
+ );
89
+ } catch (error) {
90
+ warnings.push(`AI generation failed: ${error instanceof Error ? error.message : String(error)}`);
91
+ }
92
+ }
93
+
94
+ // Fallback to heuristic generation
95
+ const heuristicResult = await generateWithHeuristics(
96
+ descriptor,
97
+ { sourceFile, testFiles, specFiles, includeAssumptions, generateExamples }
98
+ );
99
+
100
+ return {
101
+ ...heuristicResult,
102
+ warnings: [...warnings, ...heuristicResult.warnings],
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Generate contract using AI.
108
+ */
109
+ async function generateWithAI(
110
+ descriptor: RuleDescriptor | ConstraintDescriptor,
111
+ options: ReverseGenerationOptions
112
+ ): Promise<GenerationResult> {
113
+ const { aiProvider, openaiApiKey, githubToken } = options;
114
+
115
+ if (aiProvider === 'openai') {
116
+ if (!openaiApiKey) {
117
+ throw new Error('OpenAI API key is required for OpenAI provider');
118
+ }
119
+ return await generateWithOpenAI(descriptor, openaiApiKey, options);
120
+ }
121
+
122
+ if (aiProvider === 'github-copilot') {
123
+ if (!githubToken) {
124
+ throw new Error('GitHub token is required for GitHub Copilot provider');
125
+ }
126
+ return await generateWithGitHubCopilot(descriptor, githubToken, options);
127
+ }
128
+
129
+ if (aiProvider === 'auto') {
130
+ // Try GitHub Copilot first, then OpenAI
131
+ if (githubToken) {
132
+ return await generateWithGitHubCopilot(descriptor, githubToken, options);
133
+ }
134
+ if (openaiApiKey) {
135
+ return await generateWithOpenAI(descriptor, openaiApiKey, options);
136
+ }
137
+ throw new Error('Auto AI provider requires either GitHub token or OpenAI API key');
138
+ }
139
+
140
+ throw new Error(`Unsupported AI provider: ${aiProvider}`);
141
+ }
142
+
143
+ /**
144
+ * Generate contract using OpenAI.
145
+ */
146
+ async function generateWithOpenAI(
147
+ descriptor: RuleDescriptor | ConstraintDescriptor,
148
+ _apiKey: string,
149
+ options: ReverseGenerationOptions
150
+ ): Promise<GenerationResult> {
151
+ // Build prompt for OpenAI
152
+ // const prompt = buildPromptForContract(descriptor, options);
153
+
154
+ // In a real implementation, this would call the OpenAI API
155
+ // For now, we'll return a placeholder indicating AI would be used
156
+ // const warnings: string[] = [
157
+ // 'OpenAI integration is a placeholder - implement with actual API calls',
158
+ // ];
159
+
160
+ // Fallback to heuristic for now
161
+ return await generateWithHeuristics(descriptor, {
162
+ sourceFile: options.sourceFile,
163
+ testFiles: options.testFiles,
164
+ specFiles: options.specFiles,
165
+ includeAssumptions: options.includeAssumptions,
166
+ generateExamples: options.generateExamples,
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Generate contract using GitHub Copilot.
172
+ */
173
+ async function generateWithGitHubCopilot(
174
+ descriptor: RuleDescriptor | ConstraintDescriptor,
175
+ _token: string,
176
+ options: ReverseGenerationOptions
177
+ ): Promise<GenerationResult> {
178
+ // Build prompt for GitHub Copilot
179
+ // const prompt = buildPromptForContract(descriptor, options);
180
+
181
+ // In a real implementation, this would call the GitHub Copilot API
182
+ // For now, we'll return a placeholder indicating AI would be used
183
+ // const warnings: string[] = [
184
+ // 'GitHub Copilot integration is a placeholder - implement with actual API calls',
185
+ // ];
186
+
187
+ // Fallback to heuristic for now
188
+ return await generateWithHeuristics(descriptor, {
189
+ sourceFile: options.sourceFile,
190
+ testFiles: options.testFiles,
191
+ specFiles: options.specFiles,
192
+ includeAssumptions: options.includeAssumptions,
193
+ generateExamples: options.generateExamples,
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Generate contract using heuristic analysis.
199
+ */
200
+ async function generateWithHeuristics(
201
+ descriptor: RuleDescriptor | ConstraintDescriptor,
202
+ options: {
203
+ sourceFile?: string;
204
+ testFiles?: string[];
205
+ specFiles?: string[];
206
+ includeAssumptions?: boolean;
207
+ generateExamples?: boolean;
208
+ }
209
+ ): Promise<GenerationResult> {
210
+ const warnings: string[] = [];
211
+ const { sourceFile, testFiles = [], specFiles = [] } = options;
212
+
213
+ // Start with basic information
214
+ let behavior = descriptor.description || `Process ${descriptor.id}`;
215
+ let examples: Example[] = [];
216
+ let invariants: string[] = [];
217
+ let assumptions: Assumption[] = [];
218
+ let references: Reference[] = [];
219
+
220
+ // Infer from source file if available
221
+ if (sourceFile) {
222
+ try {
223
+ const inferred = await inferContractFromFile(sourceFile, descriptor.id);
224
+ if (inferred.behavior) {
225
+ behavior = inferred.behavior;
226
+ }
227
+ if (inferred.invariants && inferred.invariants.length > 0) {
228
+ invariants = inferred.invariants;
229
+ }
230
+ } catch (error) {
231
+ warnings.push(`Failed to analyze source file: ${error instanceof Error ? error.message : String(error)}`);
232
+ }
233
+ }
234
+
235
+ // Extract examples from test files
236
+ if (options.generateExamples && testFiles.length > 0) {
237
+ for (const testFile of testFiles) {
238
+ try {
239
+ const testExamples = await extractExamplesFromTests(testFile, descriptor.id);
240
+ examples.push(...testExamples);
241
+ } catch (error) {
242
+ warnings.push(`Failed to extract examples from ${testFile}: ${error instanceof Error ? error.message : String(error)}`);
243
+ }
244
+ }
245
+ }
246
+
247
+ // If no examples found, create a default one
248
+ if (examples.length === 0) {
249
+ examples.push({
250
+ given: `System is in a valid state`,
251
+ when: `${descriptor.id} is triggered`,
252
+ then: `Expected outcome is produced`,
253
+ });
254
+
255
+ if (!options.generateExamples) {
256
+ warnings.push('Example generation disabled - using default example');
257
+ } else if (!testFiles || testFiles.length === 0) {
258
+ warnings.push('No test files provided - using default example');
259
+ } else {
260
+ warnings.push('No examples could be extracted from provided test files - using default example');
261
+ }
262
+ }
263
+
264
+ // Add references to spec files
265
+ if (specFiles.length > 0) {
266
+ references = specFiles.map((file) => ({
267
+ type: 'spec',
268
+ url: file,
269
+ description: `Specification for ${descriptor.id}`,
270
+ }));
271
+ }
272
+
273
+ // Generate assumptions if requested
274
+ if (options.includeAssumptions) {
275
+ assumptions = generateDefaultAssumptions(descriptor);
276
+ }
277
+
278
+ // Create the contract
279
+ const contract = defineContract({
280
+ ruleId: descriptor.id,
281
+ behavior,
282
+ examples,
283
+ invariants: invariants.length > 0 ? invariants : [`${descriptor.id} maintains system invariants`],
284
+ assumptions,
285
+ references,
286
+ });
287
+
288
+ // Calculate confidence based on what we found
289
+ // Use a normalized approach to avoid exceeding cap
290
+ let confidence = 0.5; // Base confidence for heuristic
291
+ const bonuses = [];
292
+
293
+ if (sourceFile) bonuses.push(0.1);
294
+ if (testFiles.length > 0) bonuses.push(0.2);
295
+ if (specFiles.length > 0) bonuses.push(0.1);
296
+ if (examples.length > 1) bonuses.push(0.1);
297
+
298
+ // Sum bonuses but cap total at 0.4 (to reach 0.9 with 0.5 base)
299
+ const totalBonus = Math.min(bonuses.reduce((sum, b) => sum + b, 0), 0.4);
300
+ confidence = confidence + totalBonus;
301
+
302
+ return {
303
+ contract,
304
+ confidence,
305
+ method: 'heuristic',
306
+ warnings,
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Extract examples from test files.
312
+ */
313
+ async function extractExamplesFromTests(
314
+ testFile: string,
315
+ _ruleId: string
316
+ ): Promise<Example[]> {
317
+ const fs = await import('node:fs/promises');
318
+ const content = await fs.readFile(testFile, 'utf-8');
319
+ const examples: Example[] = [];
320
+
321
+ // Look for test descriptions that might indicate Given/When/Then
322
+ // Improved pattern to handle escaped quotes and template literals
323
+ // Using simpler pattern for template literals to avoid expensive matching on large files
324
+ const testPattern =
325
+ /(?:it|test)\s*\(\s*(?:'((?:\\'|[^'])*)'|"((?:\\"|[^"])*)"|`((?:\\`|[^`])*?)`)/g;
326
+ let match;
327
+
328
+ while ((match = testPattern.exec(content)) !== null) {
329
+ const description = match[1] ?? match[2] ?? match[3] ?? '';
330
+
331
+ // Try to parse as Given/When/Then
332
+ if (description.includes('when') || description.includes('should')) {
333
+ examples.push(parseTestDescription(description));
334
+ }
335
+ }
336
+
337
+ return examples;
338
+ }
339
+
340
+ /**
341
+ * Parse a test description into a Given/When/Then example.
342
+ */
343
+ function parseTestDescription(description: string): Example {
344
+ // Simple heuristic parsing with word boundaries
345
+ const parts = description.split(/\b(?:when|should)\b/i);
346
+
347
+ if (parts.length >= 2) {
348
+ return {
349
+ given: parts[0].trim() || 'Initial state',
350
+ when: parts.length > 2 ? parts[1].trim() : 'Action is triggered',
351
+ then: parts[parts.length - 1].trim() || 'Expected outcome occurs',
352
+ };
353
+ }
354
+
355
+ return {
356
+ given: description,
357
+ when: 'Action is triggered',
358
+ then: 'Expected outcome occurs',
359
+ };
360
+ }
361
+
362
+ /**
363
+ * Generate default assumptions for a rule.
364
+ */
365
+ function generateDefaultAssumptions(
366
+ descriptor: RuleDescriptor | ConstraintDescriptor
367
+ ): Assumption[] {
368
+ return [
369
+ {
370
+ id: `${descriptor.id}-assumption-1`,
371
+ statement: 'Input data is valid and well-formed',
372
+ confidence: 0.8,
373
+ justification: 'Standard assumption for rule processing',
374
+ impacts: ['tests', 'code'],
375
+ status: 'active',
376
+ },
377
+ {
378
+ id: `${descriptor.id}-assumption-2`,
379
+ statement: 'System state is consistent before rule execution',
380
+ confidence: 0.7,
381
+ justification: 'Required for deterministic rule behavior',
382
+ impacts: ['spec', 'tests'],
383
+ status: 'active',
384
+ },
385
+ ];
386
+ }
387
+
388
+ /**
389
+ * Build a prompt for AI contract generation.
390
+ * Currently unused but kept for future AI integration.
391
+ * @internal
392
+ */
393
+ export function buildPromptForContract(
394
+ descriptor: RuleDescriptor | ConstraintDescriptor,
395
+ options: ReverseGenerationOptions
396
+ ): string {
397
+ const parts: string[] = [
398
+ 'Generate a comprehensive contract for the following rule/constraint:',
399
+ '',
400
+ `ID: ${descriptor.id}`,
401
+ `Description: ${descriptor.description}`,
402
+ '',
403
+ 'The contract should include:',
404
+ '1. A clear canonical behavior description',
405
+ '2. At least 2-3 Given/When/Then examples',
406
+ '3. Key invariants that must hold',
407
+ '4. Assumptions with confidence levels',
408
+ '',
409
+ ];
410
+
411
+ if (options.testFiles && options.testFiles.length > 0) {
412
+ parts.push('Test files available:');
413
+ options.testFiles.forEach((file) => parts.push(`- ${file}`));
414
+ parts.push('');
415
+ }
416
+
417
+ if (options.specFiles && options.specFiles.length > 0) {
418
+ parts.push('Specification files available:');
419
+ options.specFiles.forEach((file) => parts.push(`- ${file}`));
420
+ parts.push('');
421
+ }
422
+
423
+ parts.push('Return the contract as a JSON object matching the Contract interface.');
424
+
425
+ return parts.join('\n');
426
+ }