@plures/praxis 1.3.0 → 1.4.4
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/dist/browser/{chunk-N63K4KWS.js → chunk-4IRUGWR3.js} +1 -1
- package/dist/browser/chunk-6SJ44Q64.js +473 -0
- package/dist/browser/chunk-BQOYZBWA.js +282 -0
- package/dist/browser/chunk-IG5BJ2MT.js +91 -0
- package/dist/browser/{chunk-MJK3IYTJ.js → chunk-JZDJU2DO.js} +4 -84
- package/dist/browser/chunk-ZEW4LJAJ.js +353 -0
- package/dist/browser/{engine-YIEGSX7U.js → engine-3B5WJPGT.js} +2 -1
- package/dist/browser/expectations/index.d.ts +180 -0
- package/dist/browser/expectations/index.js +14 -0
- package/dist/browser/factory/index.d.ts +149 -0
- package/dist/browser/factory/index.js +15 -0
- package/dist/browser/index.d.ts +274 -3
- package/dist/browser/index.js +407 -54
- package/dist/browser/integrations/svelte.d.ts +3 -2
- package/dist/browser/integrations/svelte.js +3 -2
- package/dist/browser/project/index.d.ts +176 -0
- package/dist/browser/project/index.js +19 -0
- package/dist/browser/reactive-engine.svelte-DgVTqHLc.d.ts +223 -0
- package/dist/browser/{reactive-engine.svelte-DjynI82A.d.ts → rules-i1LHpnGd.d.ts} +13 -221
- package/dist/node/chunk-2IUFZBH3.js +87 -0
- package/dist/node/{chunk-WZ6B3LZ6.js → chunk-7CSWBDFL.js} +3 -56
- package/dist/node/chunk-AZLNISFI.js +1690 -0
- package/dist/node/chunk-IG5BJ2MT.js +91 -0
- package/dist/node/{chunk-KMJWAFZV.js → chunk-JZDJU2DO.js} +4 -89
- package/dist/node/chunk-PGVSB6NR.js +59 -0
- package/dist/node/{chunk-5JQJZADT.js → chunk-ZO2LU4G4.js} +4 -4
- package/dist/node/cli/index.cjs +1126 -211
- package/dist/node/cli/index.js +21 -2
- package/dist/node/{engine-FEN5IYZ5.js → engine-VFHCIEM4.js} +2 -1
- package/dist/node/index.cjs +5623 -2765
- package/dist/node/index.d.cts +1181 -1
- package/dist/node/index.d.ts +1181 -1
- package/dist/node/index.js +1646 -79
- package/dist/node/integrations/svelte.js +4 -3
- package/dist/node/{reverse-W7THPV45.js → reverse-YD3CWIGM.js} +3 -2
- package/dist/node/rules-4DAJ4Z4N.js +7 -0
- package/dist/node/server-FKLVY57V.js +363 -0
- package/dist/node/{validate-EN3M4FUR.js → validate-5PSWJTIC.js} +5 -3
- package/package.json +50 -3
- package/src/__tests__/chronos-project.test.ts +799 -0
- package/src/__tests__/decision-ledger.test.ts +857 -402
- package/src/__tests__/expectations.test.ts +364 -0
- package/src/__tests__/factory.test.ts +426 -0
- package/src/__tests__/mcp-server.test.ts +310 -0
- package/src/__tests__/project.test.ts +396 -0
- package/src/chronos/diff.ts +336 -0
- package/src/chronos/hooks.ts +227 -0
- package/src/chronos/index.ts +83 -0
- package/src/chronos/project-chronicle.ts +198 -0
- package/src/chronos/timeline.ts +152 -0
- package/src/cli/index.ts +28 -0
- package/src/decision-ledger/analyzer-types.ts +280 -0
- package/src/decision-ledger/analyzer.ts +518 -0
- package/src/decision-ledger/contract-verification.ts +456 -0
- package/src/decision-ledger/derivation.ts +158 -0
- package/src/decision-ledger/index.ts +59 -0
- package/src/decision-ledger/report.ts +378 -0
- package/src/decision-ledger/suggestions.ts +287 -0
- package/src/expectations/expectations.ts +471 -0
- package/src/expectations/index.ts +29 -0
- package/src/expectations/types.ts +95 -0
- package/src/factory/factory.ts +634 -0
- package/src/factory/index.ts +27 -0
- package/src/factory/types.ts +64 -0
- package/src/index.browser.ts +83 -0
- package/src/index.ts +134 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +485 -0
- package/src/mcp/types.ts +161 -0
- package/src/project/index.ts +31 -0
- package/src/project/project.ts +423 -0
- package/src/project/types.ts +87 -0
- package/dist/node/chunk-PTH6MD6P.js +0 -487
- /package/dist/node/{chunk-R2PSBPKQ.js → chunk-TEMFJOIH.js} +0 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Praxis MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes Praxis engine operations as MCP tools for AI assistants.
|
|
5
|
+
* Supports both stdio transport (CLI usage) and library import.
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* - praxis.inspect — list all registered rules, constraints, contracts
|
|
9
|
+
* - praxis.evaluate — run a rule against given events
|
|
10
|
+
* - praxis.audit — run completeness audit against a manifest
|
|
11
|
+
* - praxis.suggest — suggest rules/constraints for a gap
|
|
12
|
+
* - praxis.facts — get current fact state
|
|
13
|
+
* - praxis.step — step the engine with events
|
|
14
|
+
* - praxis.contracts — list all contracts with coverage status
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { LogicEngine } from '../core/engine.js';
|
|
21
|
+
import { RuleResult } from '../core/rule-result.js';
|
|
22
|
+
import { auditCompleteness, formatReport } from '../core/completeness.js';
|
|
23
|
+
import { getContractFromDescriptor } from '../decision-ledger/types.js';
|
|
24
|
+
import type {
|
|
25
|
+
PraxisMcpServerOptions,
|
|
26
|
+
InspectOutput,
|
|
27
|
+
EvaluateOutput,
|
|
28
|
+
StepOutput,
|
|
29
|
+
FactsOutput,
|
|
30
|
+
ContractsOutput,
|
|
31
|
+
AuditOutput,
|
|
32
|
+
SuggestOutput,
|
|
33
|
+
RuleInfo,
|
|
34
|
+
ConstraintInfo,
|
|
35
|
+
} from './types.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a Praxis MCP server with all tools registered.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { createPraxisMcpServer } from '@plures/praxis/mcp';
|
|
43
|
+
* import { PraxisRegistry } from '@plures/praxis';
|
|
44
|
+
*
|
|
45
|
+
* const registry = new PraxisRegistry();
|
|
46
|
+
* // ... register rules ...
|
|
47
|
+
*
|
|
48
|
+
* const server = createPraxisMcpServer({
|
|
49
|
+
* initialContext: {},
|
|
50
|
+
* registry,
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* // Start via stdio for CLI usage
|
|
54
|
+
* await server.start();
|
|
55
|
+
*
|
|
56
|
+
* // Or use the McpServer instance directly
|
|
57
|
+
* const mcpServer = server.mcpServer;
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function createPraxisMcpServer<TContext = unknown>(
|
|
61
|
+
options: PraxisMcpServerOptions<TContext>,
|
|
62
|
+
) {
|
|
63
|
+
const {
|
|
64
|
+
name = '@plures/praxis',
|
|
65
|
+
version = '1.0.0',
|
|
66
|
+
initialContext,
|
|
67
|
+
registry,
|
|
68
|
+
initialFacts,
|
|
69
|
+
} = options;
|
|
70
|
+
|
|
71
|
+
// Create the engine that backs all operations
|
|
72
|
+
const engine = new LogicEngine<TContext>({
|
|
73
|
+
initialContext,
|
|
74
|
+
registry,
|
|
75
|
+
initialFacts,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Create MCP server
|
|
79
|
+
const server = new McpServer({
|
|
80
|
+
name,
|
|
81
|
+
version,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ── praxis.inspect ──────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
server.tool(
|
|
87
|
+
'praxis.inspect',
|
|
88
|
+
'List all registered rules, constraints, and their contracts',
|
|
89
|
+
{
|
|
90
|
+
filter: z.string().optional().describe('Filter rule/constraint IDs by pattern (substring match)'),
|
|
91
|
+
includeContracts: z.boolean().optional().describe('Include full contract details (default: false)'),
|
|
92
|
+
},
|
|
93
|
+
async (params): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
|
|
94
|
+
const filter = params.filter;
|
|
95
|
+
const includeContracts = params.includeContracts ?? false;
|
|
96
|
+
|
|
97
|
+
let rules = registry.getAllRules();
|
|
98
|
+
let constraints = registry.getAllConstraints();
|
|
99
|
+
|
|
100
|
+
if (filter) {
|
|
101
|
+
rules = rules.filter(r => r.id.includes(filter));
|
|
102
|
+
constraints = constraints.filter(c => c.id.includes(filter));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const ruleInfos: RuleInfo[] = rules.map(r => {
|
|
106
|
+
const contract = getContractFromDescriptor(r);
|
|
107
|
+
return {
|
|
108
|
+
id: r.id,
|
|
109
|
+
description: r.description,
|
|
110
|
+
eventTypes: r.eventTypes,
|
|
111
|
+
hasContract: !!contract,
|
|
112
|
+
contract: includeContracts ? contract : undefined,
|
|
113
|
+
meta: r.meta,
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const constraintInfos: ConstraintInfo[] = constraints.map(c => {
|
|
118
|
+
const contract = getContractFromDescriptor(c);
|
|
119
|
+
return {
|
|
120
|
+
id: c.id,
|
|
121
|
+
description: c.description,
|
|
122
|
+
hasContract: !!contract,
|
|
123
|
+
contract: includeContracts ? contract : undefined,
|
|
124
|
+
meta: c.meta,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const output: InspectOutput = {
|
|
129
|
+
rules: ruleInfos,
|
|
130
|
+
constraints: constraintInfos,
|
|
131
|
+
summary: {
|
|
132
|
+
totalRules: ruleInfos.length,
|
|
133
|
+
totalConstraints: constraintInfos.length,
|
|
134
|
+
rulesWithContracts: ruleInfos.filter(r => r.hasContract).length,
|
|
135
|
+
constraintsWithContracts: constraintInfos.filter(c => c.hasContract).length,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// ── praxis.evaluate ─────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
server.tool(
|
|
146
|
+
'praxis.evaluate',
|
|
147
|
+
'Run a specific rule against given events and return the result',
|
|
148
|
+
{
|
|
149
|
+
ruleId: z.string().describe('The rule ID to evaluate'),
|
|
150
|
+
events: z.array(z.object({
|
|
151
|
+
tag: z.string(),
|
|
152
|
+
payload: z.unknown(),
|
|
153
|
+
})).describe('Events to process through the rule'),
|
|
154
|
+
},
|
|
155
|
+
async (params): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
|
|
156
|
+
const rule = registry.getRule(params.ruleId);
|
|
157
|
+
if (!rule) {
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: 'text', text: JSON.stringify({ error: `Rule "${params.ruleId}" not found` }) }],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const state = engine.getState();
|
|
164
|
+
const stateWithEvents = { ...state, events: params.events };
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const rawResult = rule.impl(stateWithEvents as Parameters<typeof rule.impl>[0], params.events);
|
|
168
|
+
|
|
169
|
+
let output: EvaluateOutput;
|
|
170
|
+
if (rawResult instanceof RuleResult) {
|
|
171
|
+
output = {
|
|
172
|
+
ruleId: params.ruleId,
|
|
173
|
+
resultKind: rawResult.kind,
|
|
174
|
+
facts: rawResult.facts,
|
|
175
|
+
retractedTags: rawResult.retractTags,
|
|
176
|
+
reason: rawResult.reason,
|
|
177
|
+
diagnostics: [],
|
|
178
|
+
};
|
|
179
|
+
} else if (Array.isArray(rawResult)) {
|
|
180
|
+
output = {
|
|
181
|
+
ruleId: params.ruleId,
|
|
182
|
+
resultKind: 'emit',
|
|
183
|
+
facts: rawResult,
|
|
184
|
+
retractedTags: [],
|
|
185
|
+
diagnostics: [],
|
|
186
|
+
};
|
|
187
|
+
} else {
|
|
188
|
+
output = {
|
|
189
|
+
ruleId: params.ruleId,
|
|
190
|
+
resultKind: 'noop',
|
|
191
|
+
facts: [],
|
|
192
|
+
retractedTags: [],
|
|
193
|
+
diagnostics: [],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return {
|
|
200
|
+
content: [{
|
|
201
|
+
type: 'text',
|
|
202
|
+
text: JSON.stringify({
|
|
203
|
+
error: `Rule evaluation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
204
|
+
ruleId: params.ruleId,
|
|
205
|
+
}),
|
|
206
|
+
}],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// ── praxis.audit ────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
server.tool(
|
|
215
|
+
'praxis.audit',
|
|
216
|
+
'Run completeness audit against a manifest and return the report',
|
|
217
|
+
{
|
|
218
|
+
branches: z.array(z.object({
|
|
219
|
+
location: z.string(),
|
|
220
|
+
condition: z.string(),
|
|
221
|
+
kind: z.enum(['domain', 'invariant', 'ui', 'transport', 'wiring', 'transform']),
|
|
222
|
+
coveredBy: z.string().nullable(),
|
|
223
|
+
note: z.string().optional(),
|
|
224
|
+
})).describe('Logic branches to audit'),
|
|
225
|
+
stateFields: z.array(z.object({
|
|
226
|
+
source: z.string(),
|
|
227
|
+
field: z.string(),
|
|
228
|
+
inContext: z.boolean(),
|
|
229
|
+
usedByRule: z.boolean(),
|
|
230
|
+
})).describe('State fields to check context coverage'),
|
|
231
|
+
transitions: z.array(z.object({
|
|
232
|
+
description: z.string(),
|
|
233
|
+
eventTag: z.string().nullable(),
|
|
234
|
+
location: z.string(),
|
|
235
|
+
})).describe('State transitions to check event coverage'),
|
|
236
|
+
rulesNeedingContracts: z.array(z.string()).describe('Rule IDs that should have contracts'),
|
|
237
|
+
threshold: z.number().optional().describe('Minimum passing score (default: 90)'),
|
|
238
|
+
},
|
|
239
|
+
async (params): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
|
|
240
|
+
const rulesWithContracts = registry.getAllRules()
|
|
241
|
+
.filter(r => getContractFromDescriptor(r))
|
|
242
|
+
.map(r => r.id);
|
|
243
|
+
|
|
244
|
+
const report = auditCompleteness(
|
|
245
|
+
{
|
|
246
|
+
branches: params.branches,
|
|
247
|
+
stateFields: params.stateFields,
|
|
248
|
+
transitions: params.transitions,
|
|
249
|
+
rulesNeedingContracts: params.rulesNeedingContracts,
|
|
250
|
+
},
|
|
251
|
+
registry.getRuleIds(),
|
|
252
|
+
registry.getConstraintIds(),
|
|
253
|
+
rulesWithContracts,
|
|
254
|
+
{ threshold: params.threshold },
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const output: AuditOutput = {
|
|
258
|
+
report,
|
|
259
|
+
formatted: formatReport(report),
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// ── praxis.suggest ──────────────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
server.tool(
|
|
269
|
+
'praxis.suggest',
|
|
270
|
+
'Given a gap or description, suggest rules/constraints to add',
|
|
271
|
+
{
|
|
272
|
+
gap: z.string().describe('Description of the gap or failing expectation'),
|
|
273
|
+
context: z.record(z.string(), z.unknown()).optional().describe('Current context for suggestions'),
|
|
274
|
+
},
|
|
275
|
+
async (params): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
|
|
276
|
+
const existingRules = registry.getAllRules();
|
|
277
|
+
const _constraints = registry.getAllConstraints();
|
|
278
|
+
void _constraints; // used for future constraint-aware suggestions
|
|
279
|
+
const suggestions: SuggestOutput['suggestions'] = [];
|
|
280
|
+
|
|
281
|
+
// Analyze gap description against existing rules
|
|
282
|
+
const gapLower = params.gap.toLowerCase();
|
|
283
|
+
|
|
284
|
+
// Check if any existing rule partially covers this
|
|
285
|
+
const relatedRules = existingRules.filter(r =>
|
|
286
|
+
r.description.toLowerCase().includes(gapLower) ||
|
|
287
|
+
gapLower.includes(r.id.toLowerCase()),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (relatedRules.length > 0) {
|
|
291
|
+
// Suggest adding a constraint to complement existing rules
|
|
292
|
+
for (const rule of relatedRules) {
|
|
293
|
+
suggestions.push({
|
|
294
|
+
type: 'constraint',
|
|
295
|
+
id: `${rule.id}/guard`,
|
|
296
|
+
description: `Add a constraint to guard the behavior described by rule "${rule.id}"`,
|
|
297
|
+
rationale: `Rule "${rule.id}" (${rule.description}) exists but may not fully cover: ${params.gap}`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// If gap mentions validation/invariant-like terms, suggest constraint
|
|
303
|
+
const invariantTerms = ['must', 'never', 'always', 'require', 'valid', 'invalid', 'prevent'];
|
|
304
|
+
if (invariantTerms.some(t => gapLower.includes(t))) {
|
|
305
|
+
suggestions.push({
|
|
306
|
+
type: 'constraint',
|
|
307
|
+
id: suggestId(params.gap, 'constraint'),
|
|
308
|
+
description: `Constraint: ${params.gap}`,
|
|
309
|
+
rationale: 'Gap description contains invariant language — a constraint would encode this guarantee',
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// If gap mentions behavior/action terms, suggest rule
|
|
314
|
+
const ruleTerms = ['when', 'if', 'show', 'emit', 'trigger', 'display', 'update'];
|
|
315
|
+
if (ruleTerms.some(t => gapLower.includes(t))) {
|
|
316
|
+
suggestions.push({
|
|
317
|
+
type: 'rule',
|
|
318
|
+
id: suggestId(params.gap, 'rule'),
|
|
319
|
+
description: `Rule: ${params.gap}`,
|
|
320
|
+
rationale: 'Gap description contains conditional behavior — a rule would implement this logic',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check if relevant rules lack contracts
|
|
325
|
+
const contractGaps = registry.getContractGaps();
|
|
326
|
+
if (contractGaps.length > 0) {
|
|
327
|
+
const relatedGaps = contractGaps.filter(g =>
|
|
328
|
+
gapLower.includes(g.ruleId.toLowerCase()),
|
|
329
|
+
);
|
|
330
|
+
for (const g of relatedGaps) {
|
|
331
|
+
suggestions.push({
|
|
332
|
+
type: 'contract',
|
|
333
|
+
id: g.ruleId,
|
|
334
|
+
description: `Add contract for "${g.ruleId}" — missing: ${g.missing.join(', ')}`,
|
|
335
|
+
rationale: `Related rule "${g.ruleId}" lacks a contract, which could prevent this gap`,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Suggest an event if the gap describes a state transition
|
|
341
|
+
const eventTerms = ['transition', 'change', 'happen', 'occur', 'fire', 'dispatch'];
|
|
342
|
+
if (eventTerms.some(t => gapLower.includes(t))) {
|
|
343
|
+
suggestions.push({
|
|
344
|
+
type: 'event',
|
|
345
|
+
id: suggestId(params.gap, 'event'),
|
|
346
|
+
description: `Event for: ${params.gap}`,
|
|
347
|
+
rationale: 'Gap description suggests a state transition — an event would make it observable',
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Fallback: always suggest at least a rule
|
|
352
|
+
if (suggestions.length === 0) {
|
|
353
|
+
suggestions.push({
|
|
354
|
+
type: 'rule',
|
|
355
|
+
id: suggestId(params.gap, 'rule'),
|
|
356
|
+
description: `Rule: ${params.gap}`,
|
|
357
|
+
rationale: 'No existing rules or constraints cover this gap — a new rule is recommended',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const output: SuggestOutput = { suggestions };
|
|
362
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
363
|
+
},
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// ── praxis.facts ────────────────────────────────────────────────────────
|
|
367
|
+
|
|
368
|
+
server.tool(
|
|
369
|
+
'praxis.facts',
|
|
370
|
+
'Get the current fact state of the engine',
|
|
371
|
+
{},
|
|
372
|
+
async (): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
|
|
373
|
+
const facts = engine.getFacts();
|
|
374
|
+
const output: FactsOutput = {
|
|
375
|
+
facts,
|
|
376
|
+
count: facts.length,
|
|
377
|
+
};
|
|
378
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
379
|
+
},
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// ── praxis.step ─────────────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
server.tool(
|
|
385
|
+
'praxis.step',
|
|
386
|
+
'Step the engine with events and return the new state',
|
|
387
|
+
{
|
|
388
|
+
events: z.array(z.object({
|
|
389
|
+
tag: z.string(),
|
|
390
|
+
payload: z.unknown(),
|
|
391
|
+
})).describe('Events to process'),
|
|
392
|
+
},
|
|
393
|
+
async (params): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
|
|
394
|
+
const result = engine.step(params.events);
|
|
395
|
+
const output: StepOutput = {
|
|
396
|
+
facts: result.state.facts,
|
|
397
|
+
diagnostics: result.diagnostics,
|
|
398
|
+
factCount: result.state.facts.length,
|
|
399
|
+
};
|
|
400
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
401
|
+
},
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// ── praxis.contracts ────────────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
server.tool(
|
|
407
|
+
'praxis.contracts',
|
|
408
|
+
'List all contracts with their coverage status',
|
|
409
|
+
{
|
|
410
|
+
filter: z.string().optional().describe('Filter by rule/constraint ID (substring match)'),
|
|
411
|
+
},
|
|
412
|
+
async (params): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
|
|
413
|
+
const rules = registry.getAllRules();
|
|
414
|
+
const constraints = registry.getAllConstraints();
|
|
415
|
+
|
|
416
|
+
type LocalContractInfo = {
|
|
417
|
+
ruleId: string;
|
|
418
|
+
hasContract: boolean;
|
|
419
|
+
contract?: import('../decision-ledger/types.js').Contract;
|
|
420
|
+
type: 'rule' | 'constraint';
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
let contracts: LocalContractInfo[] = [
|
|
424
|
+
...rules.map(r => ({
|
|
425
|
+
ruleId: r.id,
|
|
426
|
+
hasContract: !!getContractFromDescriptor(r),
|
|
427
|
+
contract: getContractFromDescriptor(r),
|
|
428
|
+
type: 'rule' as const,
|
|
429
|
+
})),
|
|
430
|
+
...constraints.map(c => ({
|
|
431
|
+
ruleId: c.id,
|
|
432
|
+
hasContract: !!getContractFromDescriptor(c),
|
|
433
|
+
contract: getContractFromDescriptor(c),
|
|
434
|
+
type: 'constraint' as const,
|
|
435
|
+
})),
|
|
436
|
+
];
|
|
437
|
+
|
|
438
|
+
if (params.filter) {
|
|
439
|
+
contracts = contracts.filter(c => c.ruleId.includes(params.filter!));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const total = contracts.length;
|
|
443
|
+
const withContracts = contracts.filter(c => c.hasContract).length;
|
|
444
|
+
|
|
445
|
+
const output: ContractsOutput = {
|
|
446
|
+
contracts,
|
|
447
|
+
coverage: {
|
|
448
|
+
total,
|
|
449
|
+
withContracts,
|
|
450
|
+
percentage: total > 0 ? Math.round((withContracts / total) * 100) : 100,
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
455
|
+
},
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
// ── Public API ──────────────────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
/** The underlying MCP server instance */
|
|
462
|
+
mcpServer: server,
|
|
463
|
+
/** The underlying Praxis engine */
|
|
464
|
+
engine,
|
|
465
|
+
/** Start the server on stdio transport */
|
|
466
|
+
async start(): Promise<void> {
|
|
467
|
+
const transport = new StdioServerTransport();
|
|
468
|
+
await server.connect(transport);
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Generate a suggested ID from a gap description and type.
|
|
475
|
+
*/
|
|
476
|
+
function suggestId(description: string, type: string): string {
|
|
477
|
+
const slug = description
|
|
478
|
+
.toLowerCase()
|
|
479
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
480
|
+
.trim()
|
|
481
|
+
.split(/\s+/)
|
|
482
|
+
.slice(0, 3)
|
|
483
|
+
.join('-');
|
|
484
|
+
return `suggested/${type}/${slug}`;
|
|
485
|
+
}
|
package/src/mcp/types.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Praxis MCP Server — Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the MCP (Model Context Protocol) server that exposes
|
|
5
|
+
* Praxis engine operations as tools for AI assistants.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PraxisEvent, PraxisFact, PraxisDiagnostics } from '../core/protocol.js';
|
|
9
|
+
import type { CompletenessReport, LogicBranch, StateField, StateTransition } from '../core/completeness.js';
|
|
10
|
+
import type { Contract } from '../decision-ledger/types.js';
|
|
11
|
+
|
|
12
|
+
// ─── Tool Input Types ───────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export interface InspectInput {
|
|
15
|
+
/** Filter by rule/constraint ID pattern (glob-like) */
|
|
16
|
+
filter?: string;
|
|
17
|
+
/** Include contract details */
|
|
18
|
+
includeContracts?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface EvaluateInput {
|
|
22
|
+
/** Rule ID to evaluate */
|
|
23
|
+
ruleId: string;
|
|
24
|
+
/** Events to process */
|
|
25
|
+
events: PraxisEvent[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AuditInput {
|
|
29
|
+
/** Completeness manifest */
|
|
30
|
+
manifest: {
|
|
31
|
+
branches: LogicBranch[];
|
|
32
|
+
stateFields: StateField[];
|
|
33
|
+
transitions: StateTransition[];
|
|
34
|
+
rulesNeedingContracts: string[];
|
|
35
|
+
};
|
|
36
|
+
/** Minimum passing score (default: 90) */
|
|
37
|
+
threshold?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CheckExpectationsInput {
|
|
41
|
+
/** Expectation set name to verify */
|
|
42
|
+
setName?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SuggestInput {
|
|
46
|
+
/** Description of the gap or failing expectation */
|
|
47
|
+
gap: string;
|
|
48
|
+
/** Current context for suggestions */
|
|
49
|
+
context?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface StepInput {
|
|
53
|
+
/** Events to step the engine with */
|
|
54
|
+
events: PraxisEvent[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ContractsInput {
|
|
58
|
+
/** Filter by rule ID pattern */
|
|
59
|
+
filter?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface GatesInput {
|
|
63
|
+
/** Filter by gate name */
|
|
64
|
+
filter?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Tool Output Types ──────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export interface RuleInfo {
|
|
70
|
+
id: string;
|
|
71
|
+
description: string;
|
|
72
|
+
eventTypes?: string | string[];
|
|
73
|
+
hasContract: boolean;
|
|
74
|
+
contract?: Contract;
|
|
75
|
+
meta?: Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ConstraintInfo {
|
|
79
|
+
id: string;
|
|
80
|
+
description: string;
|
|
81
|
+
hasContract: boolean;
|
|
82
|
+
contract?: Contract;
|
|
83
|
+
meta?: Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface InspectOutput {
|
|
87
|
+
rules: RuleInfo[];
|
|
88
|
+
constraints: ConstraintInfo[];
|
|
89
|
+
summary: {
|
|
90
|
+
totalRules: number;
|
|
91
|
+
totalConstraints: number;
|
|
92
|
+
rulesWithContracts: number;
|
|
93
|
+
constraintsWithContracts: number;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface EvaluateOutput {
|
|
98
|
+
ruleId: string;
|
|
99
|
+
resultKind: 'emit' | 'noop' | 'skip' | 'retract';
|
|
100
|
+
facts: PraxisFact[];
|
|
101
|
+
retractedTags: string[];
|
|
102
|
+
reason?: string;
|
|
103
|
+
diagnostics: PraxisDiagnostics[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface AuditOutput {
|
|
107
|
+
report: CompletenessReport;
|
|
108
|
+
formatted: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface SuggestOutput {
|
|
112
|
+
suggestions: Array<{
|
|
113
|
+
type: 'rule' | 'constraint' | 'contract' | 'event';
|
|
114
|
+
id: string;
|
|
115
|
+
description: string;
|
|
116
|
+
rationale: string;
|
|
117
|
+
}>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface StepOutput {
|
|
121
|
+
facts: PraxisFact[];
|
|
122
|
+
diagnostics: PraxisDiagnostics[];
|
|
123
|
+
factCount: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ContractInfo {
|
|
127
|
+
ruleId: string;
|
|
128
|
+
hasContract: boolean;
|
|
129
|
+
contract?: Contract;
|
|
130
|
+
type: 'rule' | 'constraint';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ContractsOutput {
|
|
134
|
+
contracts: ContractInfo[];
|
|
135
|
+
coverage: {
|
|
136
|
+
total: number;
|
|
137
|
+
withContracts: number;
|
|
138
|
+
percentage: number;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface FactsOutput {
|
|
143
|
+
facts: PraxisFact[];
|
|
144
|
+
count: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Options for creating a Praxis MCP server.
|
|
149
|
+
*/
|
|
150
|
+
export interface PraxisMcpServerOptions<TContext = unknown> {
|
|
151
|
+
/** Name for the MCP server */
|
|
152
|
+
name?: string;
|
|
153
|
+
/** Version string */
|
|
154
|
+
version?: string;
|
|
155
|
+
/** Initial context for the engine */
|
|
156
|
+
initialContext: TContext;
|
|
157
|
+
/** Pre-configured registry (rules + constraints already registered) */
|
|
158
|
+
registry: import('../core/rules.js').PraxisRegistry<TContext>;
|
|
159
|
+
/** Initial facts */
|
|
160
|
+
initialFacts?: PraxisFact[];
|
|
161
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Praxis Project Logic
|
|
3
|
+
*
|
|
4
|
+
* Public API for developer workflow rules.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { defineGate, commitFromState, branchRules } from '@plures/praxis/project';
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
defineGate,
|
|
14
|
+
semverContract,
|
|
15
|
+
commitFromState,
|
|
16
|
+
branchRules,
|
|
17
|
+
lintGate,
|
|
18
|
+
formatGate,
|
|
19
|
+
expectationGate,
|
|
20
|
+
} from './project.js';
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
GateConfig,
|
|
24
|
+
GateState,
|
|
25
|
+
GateStatus,
|
|
26
|
+
SemverContractConfig,
|
|
27
|
+
SemverReport,
|
|
28
|
+
PraxisDiff,
|
|
29
|
+
BranchRulesConfig,
|
|
30
|
+
PredefinedGateConfig,
|
|
31
|
+
} from './types.js';
|