@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.
- package/FRAMEWORK.md +106 -15
- package/README.md +194 -119
- package/dist/browser/adapter-CIMBGDC7.js +14 -0
- package/dist/browser/chunk-K377RW4V.js +230 -0
- package/dist/browser/chunk-MBVHLOU2.js +152 -0
- package/dist/browser/{chunk-R45WXWKH.js → chunk-VOMLVI6V.js} +1 -149
- package/dist/browser/engine-YJZV4SLD.js +8 -0
- package/dist/browser/index.d.ts +161 -5
- package/dist/browser/index.js +156 -141
- package/dist/browser/integrations/svelte.d.ts +2 -2
- package/dist/browser/integrations/svelte.js +2 -1
- package/dist/browser/{reactive-engine.svelte-C9OpcTHf.d.ts → reactive-engine.svelte-9aS0kTa8.d.ts} +136 -1
- package/dist/node/adapter-75ISSMWD.js +15 -0
- package/dist/node/chunk-5RH7UAQC.js +486 -0
- package/dist/node/chunk-MBVHLOU2.js +152 -0
- package/dist/node/chunk-PRPQO6R5.js +85 -0
- package/dist/node/chunk-R2PSBPKQ.js +150 -0
- package/dist/node/chunk-S54337I5.js +446 -0
- package/dist/node/{chunk-R45WXWKH.js → chunk-VOMLVI6V.js} +1 -149
- package/dist/node/chunk-WZ6B3LZ6.js +638 -0
- package/dist/node/cli/index.cjs +2936 -897
- package/dist/node/cli/index.js +27 -0
- package/dist/node/components/index.d.cts +3 -2
- package/dist/node/components/index.d.ts +3 -2
- package/dist/node/docs-JFNYTOJA.js +102 -0
- package/dist/node/engine-2DQBKBJC.js +9 -0
- package/dist/node/index.cjs +1114 -354
- package/dist/node/index.d.cts +388 -5
- package/dist/node/index.d.ts +388 -5
- package/dist/node/index.js +201 -640
- package/dist/node/integrations/svelte.cjs +76 -0
- package/dist/node/integrations/svelte.d.cts +2 -2
- package/dist/node/integrations/svelte.d.ts +2 -2
- package/dist/node/integrations/svelte.js +3 -1
- package/dist/node/{reactive-engine.svelte-1M4m_C_v.d.cts → reactive-engine.svelte-BFIZfawz.d.cts} +199 -1
- package/dist/node/{reactive-engine.svelte-ChNFn4Hj.d.ts → reactive-engine.svelte-CRNqHlbv.d.ts} +199 -1
- package/dist/node/reverse-W7THPV45.js +193 -0
- package/dist/node/{terminal-adapter-CWka-yL8.d.ts → terminal-adapter-B-UK_Vdz.d.ts} +28 -3
- package/dist/node/{terminal-adapter-CDzxoLKR.d.cts → terminal-adapter-BQSIF5bf.d.cts} +28 -3
- package/dist/node/validate-CNHUULQE.js +180 -0
- package/docs/core/pluresdb-integration.md +15 -15
- package/docs/decision-ledger/BEHAVIOR_LEDGER.md +225 -0
- package/docs/decision-ledger/DecisionLedger.tla +180 -0
- package/docs/decision-ledger/IMPLEMENTATION_SUMMARY.md +217 -0
- package/docs/decision-ledger/LATEST.md +166 -0
- package/docs/guides/cicd-pipeline.md +142 -0
- package/package.json +2 -2
- package/src/__tests__/cli-validate.test.ts +197 -0
- package/src/__tests__/decision-ledger.test.ts +485 -0
- package/src/__tests__/reverse-generator.test.ts +189 -0
- package/src/__tests__/scanner.test.ts +215 -0
- package/src/cli/commands/docs.ts +147 -0
- package/src/cli/commands/reverse.ts +289 -0
- package/src/cli/commands/validate.ts +264 -0
- package/src/cli/index.ts +68 -0
- package/src/core/pluresdb/adapter.ts +46 -3
- package/src/core/reactive-engine.svelte.ts +6 -1
- package/src/core/reactive-engine.ts +1 -1
- package/src/core/rules.ts +133 -0
- package/src/decision-ledger/README.md +400 -0
- package/src/decision-ledger/REVERSE_ENGINEERING.md +484 -0
- package/src/decision-ledger/facts-events.ts +121 -0
- package/src/decision-ledger/index.ts +70 -0
- package/src/decision-ledger/ledger.ts +246 -0
- package/src/decision-ledger/logic-ledger.ts +158 -0
- package/src/decision-ledger/reverse-generator.ts +426 -0
- package/src/decision-ledger/scanner.ts +506 -0
- package/src/decision-ledger/types.ts +247 -0
- package/src/decision-ledger/validation.ts +336 -0
- package/src/dsl/index.ts +13 -2
- package/src/index.browser.ts +6 -0
- package/src/index.ts +40 -0
- package/src/integrations/pluresdb.ts +14 -2
- 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<
|
|
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,
|
|
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<
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
name: 'my-app-db',
|
|
61
|
+
const db = createPluresDB(new PluresNode({ autoStart: true }));
|
|
62
|
+
```
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
**Local-first (auto-detect):**
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createPraxisLocalFirst } from '@plures/praxis';
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|