@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,193 @@
1
+ import {
2
+ generateContractFromRule,
3
+ scanRepository,
4
+ writeLogicLedgerEntry
5
+ } from "./chunk-WZ6B3LZ6.js";
6
+ import {
7
+ PraxisRegistry
8
+ } from "./chunk-R2PSBPKQ.js";
9
+ import "./chunk-QGM4M3NI.js";
10
+
11
+ // src/cli/commands/reverse.ts
12
+ async function reverseCommand(options) {
13
+ const rootDir = options.dir || process.cwd();
14
+ const aiProvider = options.ai || "none";
15
+ const outputDir = options.output || "./contracts";
16
+ const dryRun = options.dryRun || false;
17
+ const interactive = options.interactive || false;
18
+ const confidenceThreshold = parseFloat(options.confidence || "0.7");
19
+ const limit = options.limit ? parseInt(options.limit, 10) : void 0;
20
+ const author = options.author || "reverse-engineer";
21
+ const format = options.format || "json";
22
+ console.log("\u{1F50D} Scanning repository for rules and constraints...");
23
+ console.log(` Directory: ${rootDir}`);
24
+ console.log(` AI Provider: ${aiProvider}`);
25
+ console.log("");
26
+ const registry = new PraxisRegistry();
27
+ const scanResult = await scanRepository({
28
+ rootDir,
29
+ scanTests: true,
30
+ scanSpecs: true,
31
+ maxDepth: 10
32
+ });
33
+ console.log(`\u2705 Scan complete in ${scanResult.duration}ms`);
34
+ console.log(` Files scanned: ${scanResult.filesScanned}`);
35
+ console.log(` Rules found: ${scanResult.rules.length}`);
36
+ console.log(` Constraints found: ${scanResult.constraints.length}`);
37
+ console.log(` Test files: ${scanResult.testFiles.size} mapped`);
38
+ console.log(` Spec files: ${scanResult.specFiles.size} mapped`);
39
+ if (scanResult.warnings.length > 0) {
40
+ console.log(` \u26A0\uFE0F Warnings: ${scanResult.warnings.length}`);
41
+ scanResult.warnings.slice(0, 5).forEach((w) => console.log(` - ${w}`));
42
+ if (scanResult.warnings.length > 5) {
43
+ console.log(` ... and ${scanResult.warnings.length - 5} more`);
44
+ }
45
+ }
46
+ console.log("");
47
+ for (const rule of scanResult.rules) {
48
+ registry.registerRule(rule);
49
+ }
50
+ for (const constraint of scanResult.constraints) {
51
+ registry.registerConstraint(constraint);
52
+ }
53
+ const allDescriptors = [
54
+ ...scanResult.rules.map((r) => ({ ...r, type: "rule" })),
55
+ ...scanResult.constraints.map((c) => ({ ...c, type: "constraint" }))
56
+ ];
57
+ const toProcess = limit ? allDescriptors.slice(0, limit) : allDescriptors;
58
+ console.log(`\u{1F916} Generating contracts for ${toProcess.length} items...`);
59
+ console.log("");
60
+ const results = [];
61
+ let generated = 0;
62
+ let skipped = 0;
63
+ for (const descriptor of toProcess) {
64
+ console.log(`\u{1F4DD} Processing ${descriptor.type}: ${descriptor.id}`);
65
+ const testFiles = scanResult.testFiles.get(descriptor.id) || [];
66
+ const specFiles = scanResult.specFiles.get(descriptor.id) || [];
67
+ const sourceFile = descriptor.meta?.sourceFile;
68
+ if (interactive) {
69
+ const readline = await import("readline");
70
+ const rl = readline.createInterface({
71
+ input: process.stdin,
72
+ output: process.stdout
73
+ });
74
+ try {
75
+ const answer = await new Promise((resolve) => {
76
+ rl.question(` Generate contract for ${descriptor.id}? (y/n) `, resolve);
77
+ });
78
+ if (answer.toLowerCase() !== "y") {
79
+ console.log(" \u23ED\uFE0F Skipped");
80
+ console.log("");
81
+ skipped++;
82
+ continue;
83
+ }
84
+ } finally {
85
+ rl.close();
86
+ }
87
+ }
88
+ try {
89
+ const result = await generateContractFromRule(descriptor, {
90
+ aiProvider,
91
+ confidenceThreshold,
92
+ includeAssumptions: true,
93
+ generateExamples: true,
94
+ sourceFile,
95
+ testFiles,
96
+ specFiles
97
+ });
98
+ console.log(` \u2705 Generated (${result.method}, confidence: ${result.confidence.toFixed(2)})`);
99
+ if (result.warnings.length > 0) {
100
+ console.log(` \u26A0\uFE0F Warnings:`);
101
+ result.warnings.forEach((warning) => console.log(` - ${warning}`));
102
+ }
103
+ console.log(` \u{1F4CB} Contract summary:`);
104
+ console.log(` Behavior: ${result.contract.behavior}`);
105
+ console.log(` Examples: ${result.contract.examples.length}`);
106
+ console.log(` Invariants: ${result.contract.invariants.length}`);
107
+ if (result.contract.assumptions) {
108
+ console.log(` Assumptions: ${result.contract.assumptions.length}`);
109
+ }
110
+ console.log("");
111
+ results.push({
112
+ id: descriptor.id,
113
+ type: descriptor.type,
114
+ success: true,
115
+ confidence: result.confidence,
116
+ method: result.method,
117
+ warnings: result.warnings
118
+ });
119
+ if (!dryRun) {
120
+ await writeContractToFile(result.contract, outputDir, format);
121
+ if (options.ledger) {
122
+ await writeLogicLedgerEntry(result.contract, {
123
+ rootDir,
124
+ author,
125
+ testsPresent: testFiles.length > 0,
126
+ specPresent: specFiles.length > 0
127
+ });
128
+ }
129
+ }
130
+ generated++;
131
+ } catch (error) {
132
+ console.log(` \u274C Failed: ${error instanceof Error ? error.message : String(error)}`);
133
+ console.log("");
134
+ results.push({
135
+ id: descriptor.id,
136
+ type: descriptor.type,
137
+ success: false,
138
+ confidence: 0,
139
+ method: "none",
140
+ warnings: [error instanceof Error ? error.message : String(error)]
141
+ });
142
+ }
143
+ }
144
+ console.log("");
145
+ console.log("\u{1F4CA} Summary");
146
+ console.log("=".repeat(50));
147
+ console.log(`Total processed: ${toProcess.length}`);
148
+ console.log(`Generated: ${generated}`);
149
+ console.log(`Skipped: ${skipped}`);
150
+ console.log(`Failed: ${results.filter((r) => !r.success).length}`);
151
+ console.log("");
152
+ const successfulResults = results.filter((r) => r.success);
153
+ const avgConfidence = successfulResults.length > 0 ? successfulResults.reduce((sum, r) => sum + r.confidence, 0) / successfulResults.length : 0;
154
+ console.log(`Average confidence: ${avgConfidence > 0 ? avgConfidence.toFixed(2) : "N/A"}`);
155
+ const methodCounts = results.reduce((acc, r) => {
156
+ acc[r.method] = (acc[r.method] || 0) + 1;
157
+ return acc;
158
+ }, {});
159
+ console.log(`Methods used:`);
160
+ for (const [method, count] of Object.entries(methodCounts)) {
161
+ console.log(` - ${method}: ${count}`);
162
+ }
163
+ console.log("");
164
+ if (dryRun) {
165
+ console.log("\u2139\uFE0F Dry run mode - no files were written");
166
+ } else {
167
+ console.log(`\u2705 Contracts written to: ${outputDir}`);
168
+ if (options.ledger) {
169
+ console.log(`\u2705 Logic ledger updated: ${rootDir}/logic-ledger`);
170
+ }
171
+ }
172
+ }
173
+ async function writeContractToFile(contract, outputDir, format) {
174
+ const fs = await import("fs/promises");
175
+ const path = await import("path");
176
+ const crypto = await import("crypto");
177
+ await fs.mkdir(outputDir, { recursive: true });
178
+ const hash = crypto.createHash("md5").update(contract.ruleId).digest("hex").slice(0, 6);
179
+ const sanitized = contract.ruleId.replace(/[^a-zA-Z0-9_-]/g, "-");
180
+ const fileName = `${sanitized}-${hash}.${format === "yaml" ? "yaml" : "json"}`;
181
+ const filePath = path.join(outputDir, fileName);
182
+ let content;
183
+ if (format === "yaml") {
184
+ const yaml = await import("js-yaml");
185
+ content = yaml.dump(contract);
186
+ } else {
187
+ content = JSON.stringify(contract, null, 2);
188
+ }
189
+ await fs.writeFile(filePath, content);
190
+ }
191
+ export {
192
+ reverseCommand
193
+ };
@@ -1,4 +1,5 @@
1
1
  import { TerminalNodeProps } from './schema.js';
2
+ import { LocalFirstOptions } from '@plures/pluresdb/local-first';
2
3
 
3
4
  /**
4
5
  * PraxisDB Adapter
@@ -6,6 +7,7 @@ import { TerminalNodeProps } from './schema.js';
6
7
  * Provides a minimal adapter layer for PluresDB integration.
7
8
  * This module defines the core interface and an in-memory implementation.
8
9
  */
10
+
9
11
  /**
10
12
  * Function to unsubscribe from a watch
11
13
  */
@@ -76,7 +78,10 @@ declare function createInMemoryDB(): InMemoryPraxisDB;
76
78
  */
77
79
  type PluresDBInstance = {
78
80
  get(key: string): Promise<any>;
79
- put(key: string, value: any): Promise<void>;
81
+ put(key: string, value: any): Promise<any>;
82
+ delete?(key: string): Promise<void>;
83
+ list?(): Promise<any[]>;
84
+ close?(): Promise<void>;
80
85
  };
81
86
  /**
82
87
  * Configuration options for PluresDBPraxisAdapter
@@ -118,7 +123,7 @@ declare class PluresDBPraxisAdapter implements PraxisDB {
118
123
  *
119
124
  * @example
120
125
  * ```typescript
121
- * import { PluresNode } from 'pluresdb';
126
+ * import { PluresNode } from '@plures/pluresdb';
122
127
  * import { createPluresDB } from '@plures/praxis';
123
128
  *
124
129
  * const pluresdb = new PluresNode({ autoStart: true });
@@ -138,6 +143,26 @@ declare class PluresDBPraxisAdapter implements PraxisDB {
138
143
  * ```
139
144
  */
140
145
  declare function createPluresDB(config: PluresDBAdapterConfig | PluresDBInstance): PluresDBPraxisAdapter;
146
+ /**
147
+ * Options for creating a local-first PluresDB adapter using the unified API
148
+ */
149
+ interface PraxisLocalFirstOptions extends LocalFirstOptions {
150
+ /** Optional polling interval override for watch semantics (ms). Defaults to 1000ms. */
151
+ pollInterval?: number;
152
+ }
153
+ /**
154
+ * Create a PraxisDB adapter backed by PluresDB's unified local-first API.
155
+ *
156
+ * This will auto-detect the best backend (WASM/Tauri/IPC/network) unless a mode is provided.
157
+ * Uses dynamic import to avoid bundling the local-first module in environments that don't need it.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const db = await createPraxisLocalFirst({ mode: 'auto' });
162
+ * await db.set('/_praxis/facts/user/1', { id: '1', name: 'Alice' });
163
+ * ```
164
+ */
165
+ declare function createPraxisLocalFirst(options?: PraxisLocalFirstOptions): Promise<PluresDBPraxisAdapter>;
141
166
 
142
167
  /**
143
168
  * Terminal Node Runtime Adapter
@@ -295,4 +320,4 @@ declare function createMockExecutor(responses: Record<string, {
295
320
  error?: string;
296
321
  }>): CommandExecutor;
297
322
 
298
- export { type CommandExecutor as C, InMemoryPraxisDB as I, type PraxisDB as P, TerminalAdapter as T, type UnsubscribeFn as U, type TerminalExecutionResult as a, type TerminalNodeState as b, createTerminalAdapter as c, type TerminalAdapterOptions as d, createMockExecutor as e, type PluresDBInstance as f, type PluresDBAdapterConfig as g, createInMemoryDB as h, PluresDBPraxisAdapter as i, createPluresDB as j, runTerminalCommand as r };
323
+ export { type CommandExecutor as C, InMemoryPraxisDB as I, type PraxisDB as P, TerminalAdapter as T, type UnsubscribeFn as U, type TerminalExecutionResult as a, type TerminalNodeState as b, createTerminalAdapter as c, type TerminalAdapterOptions as d, createMockExecutor as e, type PluresDBInstance as f, type PluresDBAdapterConfig as g, type PraxisLocalFirstOptions as h, createInMemoryDB as i, PluresDBPraxisAdapter as j, createPluresDB as k, createPraxisLocalFirst as l, runTerminalCommand as r };
@@ -1,4 +1,5 @@
1
1
  import { TerminalNodeProps } from './schema.cjs';
2
+ import { LocalFirstOptions } from '@plures/pluresdb/local-first';
2
3
 
3
4
  /**
4
5
  * PraxisDB Adapter
@@ -6,6 +7,7 @@ import { TerminalNodeProps } from './schema.cjs';
6
7
  * Provides a minimal adapter layer for PluresDB integration.
7
8
  * This module defines the core interface and an in-memory implementation.
8
9
  */
10
+
9
11
  /**
10
12
  * Function to unsubscribe from a watch
11
13
  */
@@ -76,7 +78,10 @@ declare function createInMemoryDB(): InMemoryPraxisDB;
76
78
  */
77
79
  type PluresDBInstance = {
78
80
  get(key: string): Promise<any>;
79
- put(key: string, value: any): Promise<void>;
81
+ put(key: string, value: any): Promise<any>;
82
+ delete?(key: string): Promise<void>;
83
+ list?(): Promise<any[]>;
84
+ close?(): Promise<void>;
80
85
  };
81
86
  /**
82
87
  * Configuration options for PluresDBPraxisAdapter
@@ -118,7 +123,7 @@ declare class PluresDBPraxisAdapter implements PraxisDB {
118
123
  *
119
124
  * @example
120
125
  * ```typescript
121
- * import { PluresNode } from 'pluresdb';
126
+ * import { PluresNode } from '@plures/pluresdb';
122
127
  * import { createPluresDB } from '@plures/praxis';
123
128
  *
124
129
  * const pluresdb = new PluresNode({ autoStart: true });
@@ -138,6 +143,26 @@ declare class PluresDBPraxisAdapter implements PraxisDB {
138
143
  * ```
139
144
  */
140
145
  declare function createPluresDB(config: PluresDBAdapterConfig | PluresDBInstance): PluresDBPraxisAdapter;
146
+ /**
147
+ * Options for creating a local-first PluresDB adapter using the unified API
148
+ */
149
+ interface PraxisLocalFirstOptions extends LocalFirstOptions {
150
+ /** Optional polling interval override for watch semantics (ms). Defaults to 1000ms. */
151
+ pollInterval?: number;
152
+ }
153
+ /**
154
+ * Create a PraxisDB adapter backed by PluresDB's unified local-first API.
155
+ *
156
+ * This will auto-detect the best backend (WASM/Tauri/IPC/network) unless a mode is provided.
157
+ * Uses dynamic import to avoid bundling the local-first module in environments that don't need it.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const db = await createPraxisLocalFirst({ mode: 'auto' });
162
+ * await db.set('/_praxis/facts/user/1', { id: '1', name: 'Alice' });
163
+ * ```
164
+ */
165
+ declare function createPraxisLocalFirst(options?: PraxisLocalFirstOptions): Promise<PluresDBPraxisAdapter>;
141
166
 
142
167
  /**
143
168
  * Terminal Node Runtime Adapter
@@ -295,4 +320,4 @@ declare function createMockExecutor(responses: Record<string, {
295
320
  error?: string;
296
321
  }>): CommandExecutor;
297
322
 
298
- export { type CommandExecutor as C, InMemoryPraxisDB as I, type PraxisDB as P, TerminalAdapter as T, type UnsubscribeFn as U, type TerminalExecutionResult as a, type TerminalNodeState as b, createTerminalAdapter as c, type TerminalAdapterOptions as d, createMockExecutor as e, type PluresDBInstance as f, type PluresDBAdapterConfig as g, createInMemoryDB as h, PluresDBPraxisAdapter as i, createPluresDB as j, runTerminalCommand as r };
323
+ export { type CommandExecutor as C, InMemoryPraxisDB as I, type PraxisDB as P, TerminalAdapter as T, type UnsubscribeFn as U, type TerminalExecutionResult as a, type TerminalNodeState as b, createTerminalAdapter as c, type TerminalAdapterOptions as d, createMockExecutor as e, type PluresDBInstance as f, type PluresDBAdapterConfig as g, type PraxisLocalFirstOptions as h, createInMemoryDB as i, PluresDBPraxisAdapter as j, createPluresDB as k, createPraxisLocalFirst as l, runTerminalCommand as r };
@@ -0,0 +1,180 @@
1
+ import {
2
+ ContractMissing,
3
+ formatValidationReport,
4
+ formatValidationReportJSON,
5
+ formatValidationReportSARIF,
6
+ validateContracts
7
+ } from "./chunk-5RH7UAQC.js";
8
+ import {
9
+ writeLogicLedgerEntry
10
+ } from "./chunk-WZ6B3LZ6.js";
11
+ import {
12
+ PraxisRegistry
13
+ } from "./chunk-R2PSBPKQ.js";
14
+ import "./chunk-QGM4M3NI.js";
15
+
16
+ // src/cli/commands/validate.ts
17
+ async function validateCommand(options) {
18
+ const outputFormat = options.output || "console";
19
+ const strict = options.strict || false;
20
+ const registry = await loadRegistry(options.registry);
21
+ const artifactIndex = await buildArtifactIndex(registry, {
22
+ includeTests: options.tests ?? true,
23
+ includeSpec: options.spec ?? true
24
+ });
25
+ const report = validateContracts(registry, {
26
+ strict,
27
+ requiredFields: ["behavior", "examples", "invariants"],
28
+ missingSeverity: strict ? "error" : "warning",
29
+ artifactIndex
30
+ });
31
+ if (options.emitFacts) {
32
+ const facts = gapsToFacts(report.incomplete);
33
+ const events = gapsToEvents(report.incomplete);
34
+ await emitGapArtifacts({ facts, events, gapOutput: options.gapOutput });
35
+ }
36
+ if (options.ledger) {
37
+ await writeLedgerSnapshots(registry, {
38
+ rootDir: options.ledger,
39
+ author: options.author ?? "system",
40
+ artifactIndex
41
+ });
42
+ }
43
+ switch (outputFormat) {
44
+ case "json":
45
+ console.log(formatValidationReportJSON(report));
46
+ break;
47
+ case "sarif":
48
+ console.log(formatValidationReportSARIF(report));
49
+ break;
50
+ case "console":
51
+ default:
52
+ console.log(formatValidationReport(report));
53
+ break;
54
+ }
55
+ if (strict && (report.incomplete.length > 0 || report.missing.length > 0)) {
56
+ const incompleteErrors = report.incomplete.filter((gap) => gap.severity === "error").length;
57
+ const totalErrors = incompleteErrors + report.missing.length;
58
+ if (totalErrors > 0) {
59
+ console.error(`
60
+ \u274C Validation failed: ${totalErrors} error(s) found`);
61
+ process.exit(1);
62
+ }
63
+ }
64
+ if (outputFormat === "console") {
65
+ if (report.incomplete.length === 0 && report.missing.length === 0) {
66
+ console.log("\n\u2705 All contracts validated successfully!");
67
+ } else {
68
+ const warningCount = report.incomplete.filter((gap) => gap.severity === "warning").length;
69
+ if (warningCount > 0) {
70
+ console.log(`
71
+ \u26A0\uFE0F ${warningCount} warning(s) found`);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ async function loadRegistry(registryPath) {
77
+ const registry = new PraxisRegistry();
78
+ if (registryPath) {
79
+ try {
80
+ const module = await import(resolveRegistryPath(registryPath));
81
+ const candidate = module.registry ?? module.default ?? module.createRegistry?.();
82
+ if (candidate && candidate instanceof PraxisRegistry) {
83
+ return candidate;
84
+ }
85
+ throw new Error("Registry module did not export a PraxisRegistry instance");
86
+ } catch (error) {
87
+ console.warn(`Warning: Could not load registry from ${registryPath}:`, error);
88
+ }
89
+ }
90
+ return registry;
91
+ }
92
+ function resolveRegistryPath(registryPath) {
93
+ if (registryPath.startsWith(".") || registryPath.startsWith("/")) {
94
+ return new URL(registryPath, `file://${process.cwd()}/`).href;
95
+ }
96
+ return registryPath;
97
+ }
98
+ async function buildArtifactIndex(registry, options) {
99
+ const index = {};
100
+ const ruleIds = new Set(registry.getRuleIds().concat(registry.getConstraintIds()));
101
+ if (options.includeTests) {
102
+ index.tests = /* @__PURE__ */ new Set();
103
+ for (const id of ruleIds) {
104
+ if (await hasArtifactFile("tests", id)) {
105
+ index.tests.add(id);
106
+ }
107
+ }
108
+ }
109
+ if (options.includeSpec) {
110
+ index.spec = /* @__PURE__ */ new Set();
111
+ for (const id of ruleIds) {
112
+ if (await hasArtifactFile("spec", id)) {
113
+ index.spec.add(id);
114
+ }
115
+ }
116
+ }
117
+ return index;
118
+ }
119
+ async function writeLedgerSnapshots(registry, options) {
120
+ const { rootDir, author, artifactIndex } = options;
121
+ const processDescriptor = async (descriptor) => {
122
+ if (!descriptor.contract && !descriptor.meta?.contract) {
123
+ return;
124
+ }
125
+ const contract = descriptor.contract ?? descriptor.meta?.contract;
126
+ await writeLogicLedgerEntry(contract, {
127
+ rootDir,
128
+ author,
129
+ testsPresent: artifactIndex?.tests?.has(contract.ruleId) ?? false,
130
+ specPresent: artifactIndex?.spec?.has(contract.ruleId) ?? false
131
+ });
132
+ };
133
+ for (const descriptor of registry.getAllRules()) {
134
+ await processDescriptor(descriptor);
135
+ }
136
+ for (const descriptor of registry.getAllConstraints()) {
137
+ await processDescriptor(descriptor);
138
+ }
139
+ }
140
+ async function hasArtifactFile(type, ruleId) {
141
+ const fs = await import("fs/promises");
142
+ const path = await import("path");
143
+ const candidateDirs = type === "tests" ? ["src/__tests__", "tests", "test"] : ["spec", "specs"];
144
+ const sanitized = ruleId.replace(/[^a-zA-Z0-9_-]/g, "_");
145
+ for (const dir of candidateDirs) {
146
+ const fullDir = path.resolve(process.cwd(), dir);
147
+ try {
148
+ const entries = await fs.readdir(fullDir);
149
+ if (entries.some((file) => file.includes(sanitized))) {
150
+ return true;
151
+ }
152
+ } catch {
153
+ }
154
+ }
155
+ return false;
156
+ }
157
+ function gapsToFacts(gaps) {
158
+ return gaps.map(
159
+ (gap) => ContractMissing.create({
160
+ ruleId: gap.ruleId,
161
+ missing: gap.missing,
162
+ severity: gap.severity,
163
+ message: gap.message
164
+ })
165
+ );
166
+ }
167
+ function gapsToEvents(_gaps) {
168
+ return [];
169
+ }
170
+ async function emitGapArtifacts(payload) {
171
+ if (payload.gapOutput) {
172
+ const fs = await import("fs/promises");
173
+ await fs.writeFile(payload.gapOutput, JSON.stringify(payload, null, 2));
174
+ } else {
175
+ console.log(JSON.stringify(payload, null, 2));
176
+ }
177
+ }
178
+ export {
179
+ validateCommand
180
+ };
@@ -50,28 +50,28 @@ npm install @plures/praxis
50
50
 
51
51
  ### Configuration
52
52
 
53
- Configure PluresDB in your application:
53
+ You can choose network-only (previous default) or the new local-first unified API (auto-detects WASM/Tauri/IPC/network).
54
+
55
+ **Network (unchanged):**
54
56
 
55
57
  ```typescript
56
58
  import { createPluresDB } from '@plures/praxis';
59
+ import { PluresNode } from '@plures/pluresdb';
57
60
 
58
- const db = createPluresDB({
59
- // Database name (stored in IndexedDB)
60
- name: 'my-app-db',
61
+ const db = createPluresDB(new PluresNode({ autoStart: true }));
62
+ ```
61
63
 
62
- // Schema version (increment to migrate)
63
- version: 1,
64
+ **Local-first (auto-detect):**
65
+
66
+ ```typescript
67
+ import { createPraxisLocalFirst } from '@plures/praxis';
64
68
 
65
- // Collections to create
66
- collections: ['users', 'posts', 'comments'],
69
+ // Auto mode picks the best backend (WASM in browser, Tauri/IPC on desktop, network fallback)
70
+ const db = await createPraxisLocalFirst({ mode: 'auto' });
67
71
 
68
- // Sync configuration (optional)
69
- sync: {
70
- enabled: true,
71
- endpoint: 'https://your-sync-server.com',
72
- interval: 5000, // ms
73
- },
74
- });
72
+ // Optional: override
73
+ // const db = await createPraxisLocalFirst({ mode: 'wasm', dbName: 'my-app' });
74
+ // const db = await createPraxisLocalFirst({ mode: 'ipc', channelName: 'my-channel' });
75
75
  ```
76
76
 
77
77
  ### From Schema