@plures/praxis 1.2.13 → 1.3.0
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/README.md +44 -0
- package/dist/browser/chunk-MJK3IYTJ.js +384 -0
- package/dist/browser/{chunk-K377RW4V.js → chunk-N63K4KWS.js} +1 -1
- package/dist/browser/{engine-YJZV4SLD.js → engine-YIEGSX7U.js} +1 -1
- package/dist/browser/index.d.ts +104 -2
- package/dist/browser/index.js +188 -7
- package/dist/browser/integrations/svelte.d.ts +2 -2
- package/dist/browser/integrations/svelte.js +2 -2
- package/dist/browser/{reactive-engine.svelte-9aS0kTa8.d.ts → reactive-engine.svelte-DjynI82A.d.ts} +139 -5
- package/dist/node/{chunk-PRPQO6R5.js → chunk-5JQJZADT.js} +1 -1
- package/dist/node/chunk-KMJWAFZV.js +389 -0
- package/dist/node/{chunk-5RH7UAQC.js → chunk-PTH6MD6P.js} +1 -0
- package/dist/node/cli/index.cjs +1553 -839
- package/dist/node/cli/index.js +39 -2
- package/dist/node/cloud/index.d.cts +1 -1
- package/dist/node/cloud/index.d.ts +1 -1
- package/dist/node/components/index.d.cts +2 -2
- package/dist/node/components/index.d.ts +2 -2
- package/dist/node/conversations-KQBXTP3N.js +596 -0
- package/dist/node/{engine-2DQBKBJC.js → engine-FEN5IYZ5.js} +1 -1
- package/dist/node/index.cjs +911 -43
- package/dist/node/index.d.cts +574 -7
- package/dist/node/index.d.ts +574 -7
- package/dist/node/index.js +672 -26
- package/dist/node/integrations/svelte.cjs +190 -3
- package/dist/node/integrations/svelte.d.cts +3 -3
- package/dist/node/integrations/svelte.d.ts +3 -3
- package/dist/node/integrations/svelte.js +2 -2
- package/dist/node/{protocol-Qek7ebBl.d.ts → protocol-DcyGMmWY.d.cts} +8 -1
- package/dist/node/{protocol-Qek7ebBl.d.cts → protocol-DcyGMmWY.d.ts} +8 -1
- package/dist/node/{reactive-engine.svelte-CRNqHlbv.d.ts → reactive-engine.svelte-Cg0Yc2Hs.d.cts} +145 -6
- package/dist/node/{reactive-engine.svelte-BFIZfawz.d.cts → reactive-engine.svelte-DekxqFu0.d.ts} +145 -6
- package/dist/node/{terminal-adapter-B-UK_Vdz.d.ts → terminal-adapter-CvIvgTo4.d.ts} +1 -1
- package/dist/node/{terminal-adapter-BQSIF5bf.d.cts → terminal-adapter-Db-snPJ3.d.cts} +1 -1
- package/dist/node/{validate-CNHUULQE.js → validate-EN3M4FUR.js} +1 -1
- package/dist/node/{verify-KLJRXVJS.js → verify-7VZRP2WS.js} +2 -2
- package/docs/BOT_UPDATE_POLICY.md +125 -0
- package/docs/DOGFOODING_CHECKLIST.md +254 -0
- package/docs/DOGFOODING_INDEX.md +169 -0
- package/docs/DOGFOODING_QUICK_START.md +140 -0
- package/docs/KNO_ENG_EXTRACTION_PLAN.md +577 -0
- package/docs/PLURES_TOOLS_INVENTORY.md +170 -0
- package/docs/README.md +12 -0
- package/docs/TESTING_BOT_WORKFLOWS.md +154 -0
- package/docs/conversations/INTEGRATION_POINTS.md +719 -0
- package/docs/conversations/README.md +168 -0
- package/docs/core/extending-praxis-core.md +604 -0
- package/docs/core/praxis-core-api.md +385 -0
- package/docs/decision-ledger/contract-index.json +2 -2
- package/docs/decision-ledger/decisions/2026-02-01-monorepo-organization.md +130 -0
- package/docs/examples/DOGFOODING_WORKFLOW_EXAMPLE.md +295 -0
- package/docs/examples/README.md +41 -0
- package/docs/workflows/pr-overlap-guard.md +50 -0
- package/package.json +8 -3
- package/src/__tests__/chronicle.test.ts +512 -0
- package/src/__tests__/conversations.test.ts +312 -0
- package/src/__tests__/edge-cases.test.ts +1 -1
- package/src/__tests__/engine-dx.test.ts +355 -0
- package/src/__tests__/engine-v2.test.ts +532 -0
- package/src/cli/commands/conversations.ts +252 -0
- package/src/cli/index.ts +73 -0
- package/src/conversations/README.md +230 -0
- package/src/conversations/candidate.schema.json +123 -0
- package/src/conversations/candidates.ts +114 -0
- package/src/conversations/capture.ts +56 -0
- package/src/conversations/classify.ts +110 -0
- package/src/conversations/conversation.schema.json +106 -0
- package/src/conversations/emitters/fs.ts +65 -0
- package/src/conversations/emitters/github.ts +115 -0
- package/src/conversations/gate.ts +102 -0
- package/src/conversations/index.ts +28 -0
- package/src/conversations/normalize.ts +51 -0
- package/src/conversations/redact.ts +57 -0
- package/src/conversations/types.ts +96 -0
- package/src/core/chronicle/chronicle.ts +227 -0
- package/src/core/chronicle/context.ts +80 -0
- package/src/core/chronicle/index.ts +53 -0
- package/src/core/chronicle/mcp.ts +135 -0
- package/src/core/chronicle/types.ts +61 -0
- package/src/core/completeness.ts +274 -0
- package/src/core/engine.ts +143 -3
- package/src/core/pluresdb/index.ts +22 -0
- package/src/core/pluresdb/store.ts +171 -8
- package/src/core/protocol.ts +7 -0
- package/src/core/rule-result.ts +130 -0
- package/src/core/rules.ts +24 -5
- package/src/core/ui-rules.ts +340 -0
- package/src/dsl/index.ts +6 -0
- package/src/index.ts +45 -0
- package/src/integrations/pluresdb.ts +22 -0
- package/src/vite/completeness-plugin.ts +72 -0
- package/dist/browser/chunk-VOMLVI6V.js +0 -197
- package/dist/node/chunk-VOMLVI6V.js +0 -197
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Praxis UI Rules
|
|
3
|
+
*
|
|
4
|
+
* Lightweight, predefined UI-specific rules and constraints.
|
|
5
|
+
* These govern UI behavior without muddying business logic rules.
|
|
6
|
+
*
|
|
7
|
+
* UI rules are separated from domain rules by convention:
|
|
8
|
+
* - Domain rules: business decisions, data invariants, workflow logic
|
|
9
|
+
* - UI rules: visibility, loading states, error display, navigation guards
|
|
10
|
+
*
|
|
11
|
+
* Ship predefined rules that apps can opt into. Every `if` in the UI
|
|
12
|
+
* can be governed by Praxis — business rules stay clean, UI rules stay separate.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { PraxisEvent } from './protocol.js';
|
|
16
|
+
import type { PraxisModule, RuleDescriptor, ConstraintDescriptor } from './rules.js';
|
|
17
|
+
import { RuleResult, fact } from './rule-result.js';
|
|
18
|
+
|
|
19
|
+
// ─── UI Rule Tag Prefix ─────────────────────────────────────────────────────
|
|
20
|
+
// All UI facts are prefixed with 'ui.' to distinguish from domain facts.
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Standard UI state fields that UI rules can read from context.
|
|
24
|
+
* Apps extend their context with these fields to enable UI rules.
|
|
25
|
+
*/
|
|
26
|
+
export interface UIContext {
|
|
27
|
+
/** Whether the app is currently loading data */
|
|
28
|
+
loading?: boolean;
|
|
29
|
+
/** Current error message, if any */
|
|
30
|
+
error?: string | null;
|
|
31
|
+
/** Whether the app is in offline mode */
|
|
32
|
+
offline?: boolean;
|
|
33
|
+
/** Whether there are unsaved changes */
|
|
34
|
+
dirty?: boolean;
|
|
35
|
+
/** Current route/view name */
|
|
36
|
+
route?: string;
|
|
37
|
+
/** Whether the app has completed initialization */
|
|
38
|
+
initialized?: boolean;
|
|
39
|
+
/** Screen width category: 'mobile' | 'tablet' | 'desktop' */
|
|
40
|
+
viewport?: 'mobile' | 'tablet' | 'desktop';
|
|
41
|
+
/** Whether a modal/dialog is currently open */
|
|
42
|
+
modalOpen?: boolean;
|
|
43
|
+
/** Active panel/tab name */
|
|
44
|
+
activePanel?: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Predefined UI Rules ────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Loading gate: emits ui.loading-gate when data is loading.
|
|
51
|
+
* UI components can subscribe to this fact to show loading indicators.
|
|
52
|
+
*/
|
|
53
|
+
export const loadingGateRule: RuleDescriptor<UIContext> = {
|
|
54
|
+
id: 'ui/loading-gate',
|
|
55
|
+
description: 'Signals when the app is in a loading state',
|
|
56
|
+
eventTypes: ['ui.state-change', 'app.init'],
|
|
57
|
+
impl: (state) => {
|
|
58
|
+
const ctx = state.context;
|
|
59
|
+
if (ctx.loading) {
|
|
60
|
+
return RuleResult.emit([fact('ui.loading-gate', { active: true })]);
|
|
61
|
+
}
|
|
62
|
+
return RuleResult.retract(['ui.loading-gate'], 'Not loading');
|
|
63
|
+
},
|
|
64
|
+
contract: {
|
|
65
|
+
ruleId: 'RULE_ID_PLACEHOLDER',
|
|
66
|
+
behavior: 'Emits ui.loading-gate when context.loading is true, retracts when false',
|
|
67
|
+
examples: [
|
|
68
|
+
{ given: 'loading is true', when: 'ui state changes', then: 'ui.loading-gate emitted' },
|
|
69
|
+
{ given: 'loading is false', when: 'ui state changes', then: 'ui.loading-gate retracted' },
|
|
70
|
+
],
|
|
71
|
+
invariants: ['Loading gate must reflect context.loading exactly'],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Error display: emits ui.error-display with the error message.
|
|
77
|
+
* Retracts when error clears.
|
|
78
|
+
*/
|
|
79
|
+
export const errorDisplayRule: RuleDescriptor<UIContext> = {
|
|
80
|
+
id: 'ui/error-display',
|
|
81
|
+
description: 'Signals when an error should be displayed to the user',
|
|
82
|
+
eventTypes: ['ui.state-change', 'app.error'],
|
|
83
|
+
impl: (state) => {
|
|
84
|
+
const ctx = state.context;
|
|
85
|
+
if (ctx.error) {
|
|
86
|
+
return RuleResult.emit([fact('ui.error-display', { message: ctx.error, severity: 'error' })]);
|
|
87
|
+
}
|
|
88
|
+
return RuleResult.retract(['ui.error-display'], 'Error cleared');
|
|
89
|
+
},
|
|
90
|
+
contract: {
|
|
91
|
+
ruleId: 'RULE_ID_PLACEHOLDER',
|
|
92
|
+
behavior: 'Emits ui.error-display when context.error is non-null, retracts when cleared',
|
|
93
|
+
examples: [
|
|
94
|
+
{ given: 'error is set', when: 'ui state changes', then: 'ui.error-display emitted with message' },
|
|
95
|
+
{ given: 'error is null', when: 'ui state changes', then: 'ui.error-display retracted' },
|
|
96
|
+
],
|
|
97
|
+
invariants: ['Error display must clear when error is null'],
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Offline indicator: emits ui.offline-indicator when the app detects offline state.
|
|
103
|
+
*/
|
|
104
|
+
export const offlineIndicatorRule: RuleDescriptor<UIContext> = {
|
|
105
|
+
id: 'ui/offline-indicator',
|
|
106
|
+
description: 'Signals when the app is offline',
|
|
107
|
+
eventTypes: ['ui.state-change', 'network.change'],
|
|
108
|
+
impl: (state) => {
|
|
109
|
+
if (state.context.offline) {
|
|
110
|
+
return RuleResult.emit([fact('ui.offline', { message: 'You are offline. Changes will sync when reconnected.' })]);
|
|
111
|
+
}
|
|
112
|
+
return RuleResult.retract(['ui.offline'], 'Back online');
|
|
113
|
+
},
|
|
114
|
+
contract: {
|
|
115
|
+
ruleId: 'RULE_ID_PLACEHOLDER',
|
|
116
|
+
behavior: 'Emits ui.offline when context.offline is true, retracts when back online',
|
|
117
|
+
examples: [
|
|
118
|
+
{ given: 'offline is true', when: 'network changes', then: 'ui.offline emitted' },
|
|
119
|
+
{ given: 'offline is false', when: 'network changes', then: 'ui.offline retracted' },
|
|
120
|
+
],
|
|
121
|
+
invariants: ['Offline indicator must match actual connectivity'],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Dirty guard: emits ui.unsaved-warning when there are unsaved changes.
|
|
127
|
+
* Can be used to block navigation or show save prompts.
|
|
128
|
+
*/
|
|
129
|
+
export const dirtyGuardRule: RuleDescriptor<UIContext> = {
|
|
130
|
+
id: 'ui/dirty-guard',
|
|
131
|
+
description: 'Warns when there are unsaved changes',
|
|
132
|
+
eventTypes: ['ui.state-change', 'navigation.request'],
|
|
133
|
+
impl: (state) => {
|
|
134
|
+
if (state.context.dirty) {
|
|
135
|
+
return RuleResult.emit([fact('ui.unsaved-warning', {
|
|
136
|
+
message: 'You have unsaved changes',
|
|
137
|
+
blocking: true,
|
|
138
|
+
})]);
|
|
139
|
+
}
|
|
140
|
+
return RuleResult.retract(['ui.unsaved-warning'], 'No unsaved changes');
|
|
141
|
+
},
|
|
142
|
+
contract: {
|
|
143
|
+
ruleId: 'RULE_ID_PLACEHOLDER',
|
|
144
|
+
behavior: 'Emits ui.unsaved-warning when context.dirty is true, retracts when saved',
|
|
145
|
+
examples: [
|
|
146
|
+
{ given: 'dirty is true', when: 'ui state changes', then: 'ui.unsaved-warning emitted with blocking=true' },
|
|
147
|
+
{ given: 'dirty is false', when: 'ui state changes', then: 'ui.unsaved-warning retracted' },
|
|
148
|
+
],
|
|
149
|
+
invariants: ['Dirty guard must clear after save'],
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Init gate: blocks UI interactions until app is initialized.
|
|
155
|
+
*/
|
|
156
|
+
export const initGateRule: RuleDescriptor<UIContext> = {
|
|
157
|
+
id: 'ui/init-gate',
|
|
158
|
+
description: 'Signals whether the app has completed initialization',
|
|
159
|
+
eventTypes: ['ui.state-change', 'app.init'],
|
|
160
|
+
impl: (state) => {
|
|
161
|
+
if (!state.context.initialized) {
|
|
162
|
+
return RuleResult.emit([fact('ui.init-pending', {
|
|
163
|
+
message: 'App is initializing...',
|
|
164
|
+
})]);
|
|
165
|
+
}
|
|
166
|
+
return RuleResult.retract(['ui.init-pending'], 'App initialized');
|
|
167
|
+
},
|
|
168
|
+
contract: {
|
|
169
|
+
ruleId: 'RULE_ID_PLACEHOLDER',
|
|
170
|
+
behavior: 'Emits ui.init-pending until context.initialized is true',
|
|
171
|
+
examples: [
|
|
172
|
+
{ given: 'initialized is false', when: 'app starts', then: 'ui.init-pending emitted' },
|
|
173
|
+
{ given: 'initialized is true', when: 'init completes', then: 'ui.init-pending retracted' },
|
|
174
|
+
],
|
|
175
|
+
invariants: ['Init gate must clear exactly once, when initialization completes'],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Viewport-responsive: emits ui.viewport-class based on screen size.
|
|
181
|
+
*/
|
|
182
|
+
export const viewportRule: RuleDescriptor<UIContext> = {
|
|
183
|
+
id: 'ui/viewport-class',
|
|
184
|
+
description: 'Classifies viewport size for responsive layout decisions',
|
|
185
|
+
eventTypes: ['ui.state-change', 'ui.resize'],
|
|
186
|
+
impl: (state) => {
|
|
187
|
+
const vp = state.context.viewport;
|
|
188
|
+
if (!vp) return RuleResult.skip('No viewport data');
|
|
189
|
+
return RuleResult.emit([fact('ui.viewport-class', {
|
|
190
|
+
viewport: vp,
|
|
191
|
+
compact: vp === 'mobile',
|
|
192
|
+
showSidebar: vp !== 'mobile',
|
|
193
|
+
})]);
|
|
194
|
+
},
|
|
195
|
+
contract: {
|
|
196
|
+
ruleId: 'RULE_ID_PLACEHOLDER',
|
|
197
|
+
behavior: 'Classifies viewport into responsive layout hints',
|
|
198
|
+
examples: [
|
|
199
|
+
{ given: 'viewport is mobile', when: 'resize event', then: 'compact=true, showSidebar=false' },
|
|
200
|
+
{ given: 'viewport is desktop', when: 'resize event', then: 'compact=false, showSidebar=true' },
|
|
201
|
+
],
|
|
202
|
+
invariants: ['Viewport class must update on every resize event'],
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// ─── UI Constraints ─────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* No interaction while loading: constraint that fails if actions are taken during loading.
|
|
210
|
+
*/
|
|
211
|
+
export const noInteractionWhileLoadingConstraint: ConstraintDescriptor<UIContext> = {
|
|
212
|
+
id: 'ui/no-interaction-while-loading',
|
|
213
|
+
description: 'Prevents data mutations while a load operation is in progress',
|
|
214
|
+
impl: (state) => {
|
|
215
|
+
// This constraint is advisory — apps can use checkConstraints() before mutations
|
|
216
|
+
if (state.context.loading) {
|
|
217
|
+
return 'Cannot perform action while data is loading';
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
},
|
|
221
|
+
contract: {
|
|
222
|
+
ruleId: 'RULE_ID_PLACEHOLDER',
|
|
223
|
+
behavior: 'Fails when context.loading is true',
|
|
224
|
+
examples: [
|
|
225
|
+
{ given: 'loading is true', when: 'action attempted', then: 'violation' },
|
|
226
|
+
{ given: 'loading is false', when: 'action attempted', then: 'pass' },
|
|
227
|
+
],
|
|
228
|
+
invariants: ['Must always fail during loading'],
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Must be initialized: constraint that fails if app hasn't completed init.
|
|
234
|
+
*/
|
|
235
|
+
export const mustBeInitializedConstraint: ConstraintDescriptor<UIContext> = {
|
|
236
|
+
id: 'ui/must-be-initialized',
|
|
237
|
+
description: 'Requires app initialization before user interactions',
|
|
238
|
+
impl: (state) => {
|
|
239
|
+
if (!state.context.initialized) {
|
|
240
|
+
return 'App must be initialized before performing this action';
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
},
|
|
244
|
+
contract: {
|
|
245
|
+
ruleId: 'RULE_ID_PLACEHOLDER',
|
|
246
|
+
behavior: 'Fails when context.initialized is false',
|
|
247
|
+
examples: [
|
|
248
|
+
{ given: 'initialized is false', when: 'action attempted', then: 'violation' },
|
|
249
|
+
{ given: 'initialized is true', when: 'action attempted', then: 'pass' },
|
|
250
|
+
],
|
|
251
|
+
invariants: ['Must always fail before init completes'],
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// ─── Module Bundle ──────────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The complete UI rules module.
|
|
259
|
+
* Register this to get all predefined UI rules and constraints.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* import { uiModule } from '@plures/praxis';
|
|
263
|
+
* registry.registerModule(uiModule);
|
|
264
|
+
*/
|
|
265
|
+
export const uiModule: PraxisModule<UIContext> = {
|
|
266
|
+
rules: [
|
|
267
|
+
loadingGateRule,
|
|
268
|
+
errorDisplayRule,
|
|
269
|
+
offlineIndicatorRule,
|
|
270
|
+
dirtyGuardRule,
|
|
271
|
+
initGateRule,
|
|
272
|
+
viewportRule,
|
|
273
|
+
],
|
|
274
|
+
constraints: [
|
|
275
|
+
noInteractionWhileLoadingConstraint,
|
|
276
|
+
mustBeInitializedConstraint,
|
|
277
|
+
],
|
|
278
|
+
meta: {
|
|
279
|
+
name: 'praxis-ui',
|
|
280
|
+
version: '1.0.0',
|
|
281
|
+
description: 'Predefined UI rules and constraints — separate from business logic',
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Create a customized UI module with only the rules you need.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* const myUI = createUIModule({
|
|
290
|
+
* rules: ['ui/loading-gate', 'ui/dirty-guard'],
|
|
291
|
+
* constraints: ['ui/must-be-initialized'],
|
|
292
|
+
* });
|
|
293
|
+
* registry.registerModule(myUI);
|
|
294
|
+
*/
|
|
295
|
+
export function createUIModule<TContext extends UIContext>(options: {
|
|
296
|
+
rules?: string[];
|
|
297
|
+
constraints?: string[];
|
|
298
|
+
extraRules?: RuleDescriptor<TContext>[];
|
|
299
|
+
extraConstraints?: ConstraintDescriptor<TContext>[];
|
|
300
|
+
}): PraxisModule<TContext> {
|
|
301
|
+
const allRules = uiModule.rules as RuleDescriptor<TContext>[];
|
|
302
|
+
const allConstraints = uiModule.constraints as ConstraintDescriptor<TContext>[];
|
|
303
|
+
|
|
304
|
+
const selectedRules = options.rules
|
|
305
|
+
? allRules.filter(r => options.rules!.includes(r.id))
|
|
306
|
+
: allRules;
|
|
307
|
+
|
|
308
|
+
const selectedConstraints = options.constraints
|
|
309
|
+
? allConstraints.filter(c => options.constraints!.includes(c.id))
|
|
310
|
+
: allConstraints;
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
rules: [...selectedRules, ...(options.extraRules ?? [])],
|
|
314
|
+
constraints: [...selectedConstraints, ...(options.extraConstraints ?? [])],
|
|
315
|
+
meta: { ...uiModule.meta, customized: true },
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ─── UI Event Helpers ───────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create a UI state change event. Fire this when UIContext fields change.
|
|
323
|
+
*/
|
|
324
|
+
export function uiStateChanged(changes?: Record<string, unknown>): PraxisEvent {
|
|
325
|
+
return { tag: 'ui.state-change', payload: changes ?? {} };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Create a navigation request event. Used with dirty guard.
|
|
330
|
+
*/
|
|
331
|
+
export function navigationRequest(to: string): PraxisEvent {
|
|
332
|
+
return { tag: 'navigation.request', payload: { to } };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Create a resize event. Used with viewport rule.
|
|
337
|
+
*/
|
|
338
|
+
export function resizeEvent(width: number, height: number): PraxisEvent {
|
|
339
|
+
return { tag: 'ui.resize', payload: { width, height } };
|
|
340
|
+
}
|
package/src/dsl/index.ts
CHANGED
|
@@ -85,6 +85,11 @@ export interface DefineRuleOptions<TContext = unknown> {
|
|
|
85
85
|
id: string;
|
|
86
86
|
description: string;
|
|
87
87
|
impl: RuleFn<TContext>;
|
|
88
|
+
/**
|
|
89
|
+
* Optional event type filter — only evaluate this rule when at least one
|
|
90
|
+
* event in the batch has a matching `tag`. Accepts a single tag or array.
|
|
91
|
+
*/
|
|
92
|
+
eventTypes?: string | string[];
|
|
88
93
|
contract?: Contract;
|
|
89
94
|
meta?: Record<string, unknown>;
|
|
90
95
|
}
|
|
@@ -115,6 +120,7 @@ export function defineRule<TContext = unknown>(
|
|
|
115
120
|
id: options.id,
|
|
116
121
|
description: options.description,
|
|
117
122
|
impl: options.impl,
|
|
123
|
+
eventTypes: options.eventTypes,
|
|
118
124
|
contract,
|
|
119
125
|
meta,
|
|
120
126
|
};
|
package/src/index.ts
CHANGED
|
@@ -208,6 +208,18 @@ export type {
|
|
|
208
208
|
GeneratedPluresDBFile,
|
|
209
209
|
PluresDBAdapter,
|
|
210
210
|
PluresDBAdapterOptions,
|
|
211
|
+
// Chronicle
|
|
212
|
+
TraceDirection,
|
|
213
|
+
EdgeType,
|
|
214
|
+
ChronicleEvent,
|
|
215
|
+
ChronicleNode,
|
|
216
|
+
ChronicleEdge,
|
|
217
|
+
Chronicle,
|
|
218
|
+
ChronicleSpan,
|
|
219
|
+
ChronosTraceParams,
|
|
220
|
+
ChronosSearchParams,
|
|
221
|
+
McpToolResult,
|
|
222
|
+
ChronosMcpTools,
|
|
211
223
|
} from './integrations/pluresdb.js';
|
|
212
224
|
export {
|
|
213
225
|
InMemoryPraxisDB,
|
|
@@ -229,6 +241,12 @@ export {
|
|
|
229
241
|
createPluresDBGenerator,
|
|
230
242
|
createPluresDBAdapter,
|
|
231
243
|
attachToEngine,
|
|
244
|
+
// Chronicle
|
|
245
|
+
ChronicleContext,
|
|
246
|
+
PluresDbChronicle,
|
|
247
|
+
createChronicle,
|
|
248
|
+
CHRONICLE_PATHS,
|
|
249
|
+
createChronosMcpTools,
|
|
232
250
|
} from './integrations/pluresdb.js';
|
|
233
251
|
|
|
234
252
|
// Unum Integration (Identity & Channels)
|
|
@@ -308,3 +326,30 @@ export {
|
|
|
308
326
|
// Unified Integration Helpers
|
|
309
327
|
export type { UnifiedAppConfig, UnifiedApp } from './integrations/unified.js';
|
|
310
328
|
export { createUnifiedApp, attachAllIntegrations } from './integrations/unified.js';
|
|
329
|
+
|
|
330
|
+
// ── Rule Result (typed rule returns — no empty arrays) ──────────────────────
|
|
331
|
+
export { RuleResult, fact } from './core/rule-result.js';
|
|
332
|
+
export type { TypedRuleFn } from './core/rule-result.js';
|
|
333
|
+
|
|
334
|
+
// ── UI Rules (predefined, lightweight, separate from business logic) ────────
|
|
335
|
+
export {
|
|
336
|
+
uiModule,
|
|
337
|
+
createUIModule,
|
|
338
|
+
loadingGateRule,
|
|
339
|
+
errorDisplayRule,
|
|
340
|
+
offlineIndicatorRule,
|
|
341
|
+
dirtyGuardRule,
|
|
342
|
+
initGateRule,
|
|
343
|
+
viewportRule,
|
|
344
|
+
noInteractionWhileLoadingConstraint,
|
|
345
|
+
mustBeInitializedConstraint,
|
|
346
|
+
uiStateChanged,
|
|
347
|
+
navigationRequest,
|
|
348
|
+
resizeEvent,
|
|
349
|
+
} from './core/ui-rules.js';
|
|
350
|
+
export type { UIContext } from './core/ui-rules.js';
|
|
351
|
+
|
|
352
|
+
// ── Completeness Analysis ───────────────────────────────────────────────────
|
|
353
|
+
export { auditCompleteness, formatReport } from './core/completeness.js';
|
|
354
|
+
export type { LogicBranch, StateField, StateTransition, CompletenessReport, CompletenessConfig } from './core/completeness.js';
|
|
355
|
+
|
|
@@ -57,6 +57,28 @@ export type {
|
|
|
57
57
|
GeneratedPluresDBFile,
|
|
58
58
|
} from '../core/pluresdb/generator.js';
|
|
59
59
|
|
|
60
|
+
// Chronicle - Causal graph tracking for Praxis state transitions
|
|
61
|
+
export type {
|
|
62
|
+
TraceDirection,
|
|
63
|
+
EdgeType,
|
|
64
|
+
ChronicleEvent,
|
|
65
|
+
ChronicleNode,
|
|
66
|
+
ChronicleEdge,
|
|
67
|
+
Chronicle,
|
|
68
|
+
ChronicleSpan,
|
|
69
|
+
ChronosTraceParams,
|
|
70
|
+
ChronosSearchParams,
|
|
71
|
+
McpToolResult,
|
|
72
|
+
ChronosMcpTools,
|
|
73
|
+
} from '../core/chronicle/index.js';
|
|
74
|
+
export {
|
|
75
|
+
ChronicleContext,
|
|
76
|
+
PluresDbChronicle,
|
|
77
|
+
createChronicle,
|
|
78
|
+
CHRONICLE_PATHS,
|
|
79
|
+
createChronosMcpTools,
|
|
80
|
+
} from '../core/chronicle/index.js';
|
|
81
|
+
|
|
60
82
|
/**
|
|
61
83
|
* PluresDB adapter interface for engine integration
|
|
62
84
|
*
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Plugin: Praxis Completeness Report
|
|
3
|
+
*
|
|
4
|
+
* Automatically outputs completeness score in build output.
|
|
5
|
+
* Silent by default in production builds unless `verbose: true`.
|
|
6
|
+
* Always shows in dev mode unless explicitly silenced.
|
|
7
|
+
*
|
|
8
|
+
* Usage in vite.config.ts:
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { praxisCompletenessPlugin } from '@plures/praxis/vite';
|
|
11
|
+
*
|
|
12
|
+
* export default defineConfig({
|
|
13
|
+
* plugins: [
|
|
14
|
+
* praxisCompletenessPlugin({
|
|
15
|
+
* manifestPath: './src/lib/completeness-manifest.ts',
|
|
16
|
+
* threshold: 90, // fail build if below (CI mode)
|
|
17
|
+
* strict: false, // set to true for CI
|
|
18
|
+
* silent: false, // set to true to suppress output
|
|
19
|
+
* }),
|
|
20
|
+
* ],
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export interface PraxisCompletenessPluginOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Path to the completeness manifest module.
|
|
28
|
+
* Must default-export or named-export { branches, stateFields, transitions, rulesNeedingContracts }.
|
|
29
|
+
*/
|
|
30
|
+
manifestPath?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Minimum score to pass (default: 90). Below this is a warning; in strict mode, an error.
|
|
33
|
+
*/
|
|
34
|
+
threshold?: number;
|
|
35
|
+
/**
|
|
36
|
+
* If true, build fails when below threshold. Use for CI.
|
|
37
|
+
*/
|
|
38
|
+
strict?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* If true, suppresses all completeness output.
|
|
41
|
+
*/
|
|
42
|
+
silent?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a Vite plugin that reports Praxis completeness in build output.
|
|
47
|
+
*
|
|
48
|
+
* The plugin loads the manifest at build time, evaluates coverage, and
|
|
49
|
+
* prints a summary. In strict mode, it fails the build if below threshold.
|
|
50
|
+
*/
|
|
51
|
+
export function praxisCompletenessPlugin(_options?: PraxisCompletenessPluginOptions) {
|
|
52
|
+
const options = _options ?? {};
|
|
53
|
+
const threshold = options.threshold ?? 90;
|
|
54
|
+
const silent = options.silent ?? false;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
name: 'vite-plugin-praxis-completeness',
|
|
58
|
+
enforce: 'post' as const,
|
|
59
|
+
|
|
60
|
+
// We hook into buildEnd so the report appears at the end of the build
|
|
61
|
+
buildEnd() {
|
|
62
|
+
if (silent) return;
|
|
63
|
+
|
|
64
|
+
// This plugin works as a hook point — the actual manifest loading
|
|
65
|
+
// happens at the app level since manifests are app-specific.
|
|
66
|
+
// The plugin provides the infrastructure; apps wire their manifest.
|
|
67
|
+
console.log(`\n⟐ Praxis Completeness: threshold=${threshold}, strict=${options.strict ?? false}`);
|
|
68
|
+
console.log(' Import your manifest and call auditCompleteness() in a build script or test.');
|
|
69
|
+
console.log(' See @plures/praxis docs for setup.\n');
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|