@directive-run/knowledge 1.3.0 → 1.5.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/examples/ai-guardrails.ts +89 -41
- package/examples/checkers.ts +1 -1
- package/examples/counter-react.ts +21 -6
- package/examples/counter-svelte.ts +21 -6
- package/examples/counter-vue.ts +21 -6
- package/examples/counter.ts +15 -4
- package/examples/data-triggers.ts +175 -0
- package/examples/sudoku.ts +1 -6
- package/package.json +3 -3
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import {
|
|
13
13
|
type InjectionDetectionResult,
|
|
14
14
|
type PIIDetectionResult,
|
|
15
|
+
detectAndRedactPII,
|
|
15
16
|
detectPII,
|
|
16
17
|
detectPromptInjection,
|
|
17
18
|
} from "@directive-run/ai";
|
|
@@ -169,7 +170,84 @@ export const system = createSystem({
|
|
|
169
170
|
// Analysis Functions
|
|
170
171
|
// ============================================================================
|
|
171
172
|
|
|
172
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Compliance gate: blocks the message when the active mode forbids the kinds
|
|
175
|
+
* of PII detected. Emits its own devtools event and returns whether it blocked.
|
|
176
|
+
*/
|
|
177
|
+
function runComplianceCheck(
|
|
178
|
+
piiResult: PIIDetectionResult,
|
|
179
|
+
textLength: number,
|
|
180
|
+
): boolean {
|
|
181
|
+
const mode = system.facts.complianceMode as ComplianceMode;
|
|
182
|
+
let blocked = false;
|
|
183
|
+
|
|
184
|
+
if (mode !== "standard" && piiResult.detected) {
|
|
185
|
+
// HIPAA Safe Harbor (45 CFR 164.514) enumerates 18 identifier classes —
|
|
186
|
+
// names, geographic data, dates, contact info, account/certificate/license
|
|
187
|
+
// numbers, IP addresses, and more. That is effectively every PII type this
|
|
188
|
+
// detector emits, so under HIPAA *any* detected PII is PHI and blocks.
|
|
189
|
+
const hasPHI = piiResult.detected;
|
|
190
|
+
const hasContactInfo = piiResult.items.some(
|
|
191
|
+
(i) =>
|
|
192
|
+
i.type === "email" ||
|
|
193
|
+
i.type === "phone" ||
|
|
194
|
+
i.type === "name" ||
|
|
195
|
+
i.type === "date_of_birth" ||
|
|
196
|
+
i.type === "address",
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (mode === "hipaa" && hasPHI) {
|
|
200
|
+
blocked = true;
|
|
201
|
+
system.facts.complianceBlocks =
|
|
202
|
+
(system.facts.complianceBlocks as number) + 1;
|
|
203
|
+
addTimeline("compliance", "HIPAA: PHI detected", "compliance");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (mode === "gdpr" && hasContactInfo) {
|
|
207
|
+
blocked = true;
|
|
208
|
+
system.facts.complianceBlocks =
|
|
209
|
+
(system.facts.complianceBlocks as number) + 1;
|
|
210
|
+
addTimeline("compliance", "GDPR: personal data detected", "compliance");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
emitDevToolsEvent({
|
|
215
|
+
type: "guardrail_check",
|
|
216
|
+
guardrailName: `compliance-${mode}`,
|
|
217
|
+
guardrailType: "input",
|
|
218
|
+
passed: !blocked || !piiResult.detected,
|
|
219
|
+
inputLength: textLength,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return blocked;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* When redaction is on, never persist raw PII into reactive facts: strip the
|
|
227
|
+
* plaintext `value` from each detected item before it reaches facts.
|
|
228
|
+
*/
|
|
229
|
+
function toSafePiiResult(
|
|
230
|
+
piiResult: PIIDetectionResult,
|
|
231
|
+
redactionEnabled: boolean,
|
|
232
|
+
): PIIDetectionResult | null {
|
|
233
|
+
if (!piiResult.detected) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!redactionEnabled) {
|
|
238
|
+
return piiResult;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
...piiResult,
|
|
243
|
+
items: piiResult.items.map((item) => ({
|
|
244
|
+
...item,
|
|
245
|
+
value: "[redacted]",
|
|
246
|
+
})),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function analyzeMessage(text: string): Promise<ChatMessage> {
|
|
173
251
|
const id = `msg-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
174
252
|
let blocked = false;
|
|
175
253
|
|
|
@@ -192,11 +270,12 @@ export function analyzeMessage(text: string): ChatMessage {
|
|
|
192
270
|
inputLength: text.length,
|
|
193
271
|
});
|
|
194
272
|
|
|
195
|
-
// 2. PII detection
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
273
|
+
// 2. PII detection. detectPII is detection-only; detectAndRedactPII also
|
|
274
|
+
// populates `redactedText` so we never have to mutate a returned result.
|
|
275
|
+
const redactionEnabled = system.facts.redactionEnabled;
|
|
276
|
+
const piiResult = redactionEnabled
|
|
277
|
+
? await detectAndRedactPII(text, { style: "typed" })
|
|
278
|
+
: await detectPII(text);
|
|
200
279
|
if (piiResult.detected) {
|
|
201
280
|
system.facts.piiDetections = (system.facts.piiDetections as number) + 1;
|
|
202
281
|
for (const item of piiResult.items) {
|
|
@@ -213,41 +292,10 @@ export function analyzeMessage(text: string): ChatMessage {
|
|
|
213
292
|
});
|
|
214
293
|
|
|
215
294
|
// 3. Compliance check
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const hasPHI = piiResult.items.some(
|
|
219
|
-
(i) =>
|
|
220
|
-
i.type === "medical_id" ||
|
|
221
|
-
i.type === "ssn" ||
|
|
222
|
-
i.type === "date_of_birth",
|
|
223
|
-
);
|
|
224
|
-
const hasContactInfo = piiResult.items.some(
|
|
225
|
-
(i) => i.type === "email" || i.type === "phone" || i.type === "name",
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
if (mode === "hipaa" && hasPHI) {
|
|
229
|
-
blocked = true;
|
|
230
|
-
system.facts.complianceBlocks =
|
|
231
|
-
(system.facts.complianceBlocks as number) + 1;
|
|
232
|
-
addTimeline("compliance", "HIPAA: PHI detected", "compliance");
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (mode === "gdpr" && hasContactInfo) {
|
|
236
|
-
blocked = true;
|
|
237
|
-
system.facts.complianceBlocks =
|
|
238
|
-
(system.facts.complianceBlocks as number) + 1;
|
|
239
|
-
addTimeline("compliance", "GDPR: personal data detected", "compliance");
|
|
240
|
-
}
|
|
295
|
+
if (runComplianceCheck(piiResult, text.length)) {
|
|
296
|
+
blocked = true;
|
|
241
297
|
}
|
|
242
298
|
|
|
243
|
-
emitDevToolsEvent({
|
|
244
|
-
type: "guardrail_check",
|
|
245
|
-
guardrailName: `compliance-${mode}`,
|
|
246
|
-
guardrailType: "input",
|
|
247
|
-
passed: !blocked || !piiResult.detected,
|
|
248
|
-
inputLength: text.length,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
299
|
if (blocked) {
|
|
252
300
|
system.facts.blockedCount = (system.facts.blockedCount as number) + 1;
|
|
253
301
|
}
|
|
@@ -260,10 +308,10 @@ export function analyzeMessage(text: string): ChatMessage {
|
|
|
260
308
|
|
|
261
309
|
return {
|
|
262
310
|
id,
|
|
263
|
-
text,
|
|
311
|
+
text: redactionEnabled ? redactedText : text,
|
|
264
312
|
blocked,
|
|
265
313
|
redactedText,
|
|
266
314
|
injectionResult: injectionResult.detected ? injectionResult : null,
|
|
267
|
-
piiResult: piiResult
|
|
315
|
+
piiResult: toSafePiiResult(piiResult, redactionEnabled),
|
|
268
316
|
};
|
|
269
317
|
}
|
package/examples/checkers.ts
CHANGED
|
@@ -48,8 +48,8 @@ import {
|
|
|
48
48
|
} from "@directive-run/core/plugins";
|
|
49
49
|
// createObservability is alpha (not in bundle) — direct source import
|
|
50
50
|
import {
|
|
51
|
-
createObservability,
|
|
52
51
|
createAgentMetrics,
|
|
52
|
+
createObservability,
|
|
53
53
|
} from "../../../packages/core/src/plugins/observability.lab.js";
|
|
54
54
|
import {
|
|
55
55
|
analysisAgent,
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* Shared counter module — same file used by all framework examples.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type ModuleSchema,
|
|
11
|
+
createModule,
|
|
12
|
+
createSystem,
|
|
13
|
+
t,
|
|
14
|
+
} from "@directive-run/core";
|
|
10
15
|
|
|
11
16
|
const schema = {
|
|
12
17
|
facts: { count: t.number() },
|
|
@@ -17,15 +22,23 @@ const schema = {
|
|
|
17
22
|
|
|
18
23
|
export const counterModule = createModule("counter", {
|
|
19
24
|
schema,
|
|
20
|
-
init: (facts) => {
|
|
25
|
+
init: (facts) => {
|
|
26
|
+
facts.count = 0;
|
|
27
|
+
},
|
|
21
28
|
derive: {
|
|
22
29
|
doubled: (facts) => facts.count * 2,
|
|
23
30
|
isPositive: (facts) => facts.count > 0,
|
|
24
31
|
},
|
|
25
32
|
events: {
|
|
26
|
-
increment: (facts) => {
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
increment: (facts) => {
|
|
34
|
+
facts.count += 1;
|
|
35
|
+
},
|
|
36
|
+
decrement: (facts) => {
|
|
37
|
+
facts.count -= 1;
|
|
38
|
+
},
|
|
39
|
+
reset: (facts) => {
|
|
40
|
+
facts.count = 0;
|
|
41
|
+
},
|
|
29
42
|
},
|
|
30
43
|
constraints: {
|
|
31
44
|
noNegative: {
|
|
@@ -36,7 +49,9 @@ export const counterModule = createModule("counter", {
|
|
|
36
49
|
resolvers: {
|
|
37
50
|
clamp: {
|
|
38
51
|
requirement: "CLAMP_TO_ZERO",
|
|
39
|
-
resolve: async (req, context) => {
|
|
52
|
+
resolve: async (req, context) => {
|
|
53
|
+
context.facts.count = 0;
|
|
54
|
+
},
|
|
40
55
|
},
|
|
41
56
|
},
|
|
42
57
|
});
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* Shared counter module — same file used by all framework examples.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type ModuleSchema,
|
|
11
|
+
createModule,
|
|
12
|
+
createSystem,
|
|
13
|
+
t,
|
|
14
|
+
} from "@directive-run/core";
|
|
10
15
|
|
|
11
16
|
const schema = {
|
|
12
17
|
facts: { count: t.number() },
|
|
@@ -17,15 +22,23 @@ const schema = {
|
|
|
17
22
|
|
|
18
23
|
export const counterModule = createModule("counter", {
|
|
19
24
|
schema,
|
|
20
|
-
init: (facts) => {
|
|
25
|
+
init: (facts) => {
|
|
26
|
+
facts.count = 0;
|
|
27
|
+
},
|
|
21
28
|
derive: {
|
|
22
29
|
doubled: (facts) => facts.count * 2,
|
|
23
30
|
isPositive: (facts) => facts.count > 0,
|
|
24
31
|
},
|
|
25
32
|
events: {
|
|
26
|
-
increment: (facts) => {
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
increment: (facts) => {
|
|
34
|
+
facts.count += 1;
|
|
35
|
+
},
|
|
36
|
+
decrement: (facts) => {
|
|
37
|
+
facts.count -= 1;
|
|
38
|
+
},
|
|
39
|
+
reset: (facts) => {
|
|
40
|
+
facts.count = 0;
|
|
41
|
+
},
|
|
29
42
|
},
|
|
30
43
|
constraints: {
|
|
31
44
|
noNegative: {
|
|
@@ -36,7 +49,9 @@ export const counterModule = createModule("counter", {
|
|
|
36
49
|
resolvers: {
|
|
37
50
|
clamp: {
|
|
38
51
|
requirement: "CLAMP_TO_ZERO",
|
|
39
|
-
resolve: async (req, context) => {
|
|
52
|
+
resolve: async (req, context) => {
|
|
53
|
+
context.facts.count = 0;
|
|
54
|
+
},
|
|
40
55
|
},
|
|
41
56
|
},
|
|
42
57
|
});
|
package/examples/counter-vue.ts
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* Shared counter module — same file used by all framework examples.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type ModuleSchema,
|
|
11
|
+
createModule,
|
|
12
|
+
createSystem,
|
|
13
|
+
t,
|
|
14
|
+
} from "@directive-run/core";
|
|
10
15
|
|
|
11
16
|
const schema = {
|
|
12
17
|
facts: { count: t.number() },
|
|
@@ -17,15 +22,23 @@ const schema = {
|
|
|
17
22
|
|
|
18
23
|
export const counterModule = createModule("counter", {
|
|
19
24
|
schema,
|
|
20
|
-
init: (facts) => {
|
|
25
|
+
init: (facts) => {
|
|
26
|
+
facts.count = 0;
|
|
27
|
+
},
|
|
21
28
|
derive: {
|
|
22
29
|
doubled: (facts) => facts.count * 2,
|
|
23
30
|
isPositive: (facts) => facts.count > 0,
|
|
24
31
|
},
|
|
25
32
|
events: {
|
|
26
|
-
increment: (facts) => {
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
increment: (facts) => {
|
|
34
|
+
facts.count += 1;
|
|
35
|
+
},
|
|
36
|
+
decrement: (facts) => {
|
|
37
|
+
facts.count -= 1;
|
|
38
|
+
},
|
|
39
|
+
reset: (facts) => {
|
|
40
|
+
facts.count = 0;
|
|
41
|
+
},
|
|
29
42
|
},
|
|
30
43
|
constraints: {
|
|
31
44
|
noNegative: {
|
|
@@ -36,7 +49,9 @@ export const counterModule = createModule("counter", {
|
|
|
36
49
|
resolvers: {
|
|
37
50
|
clamp: {
|
|
38
51
|
requirement: "CLAMP_TO_ZERO",
|
|
39
|
-
resolve: async (req, context) => {
|
|
52
|
+
resolve: async (req, context) => {
|
|
53
|
+
context.facts.count = 0;
|
|
54
|
+
},
|
|
40
55
|
},
|
|
41
56
|
},
|
|
42
57
|
});
|
package/examples/counter.ts
CHANGED
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
* Total: ~40 lines.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
type ModuleSchema,
|
|
14
|
+
createModule,
|
|
15
|
+
createSystem,
|
|
16
|
+
t,
|
|
17
|
+
} from "@directive-run/core";
|
|
13
18
|
|
|
14
19
|
const schema = {
|
|
15
20
|
facts: {
|
|
@@ -42,9 +47,15 @@ export const counterModule = createModule("counter", {
|
|
|
42
47
|
},
|
|
43
48
|
|
|
44
49
|
events: {
|
|
45
|
-
increment: (facts) => {
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
increment: (facts) => {
|
|
51
|
+
facts.count += 1;
|
|
52
|
+
},
|
|
53
|
+
decrement: (facts) => {
|
|
54
|
+
facts.count -= 1;
|
|
55
|
+
},
|
|
56
|
+
reset: (facts) => {
|
|
57
|
+
facts.count = 0;
|
|
58
|
+
},
|
|
48
59
|
},
|
|
49
60
|
|
|
50
61
|
// When count goes negative, automatically fix it
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// Example: data-triggers
|
|
2
|
+
// Source: examples/data-triggers/src/index.ts
|
|
3
|
+
// Extracted for AI rules — DOM wiring stripped
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* All five Directive definition surfaces written as DATA.
|
|
7
|
+
*
|
|
8
|
+
* Run with: `pnpm --filter @directive-run/example-data-triggers start`
|
|
9
|
+
*
|
|
10
|
+
* Demonstrates:
|
|
11
|
+
* - constraint `when: { ... }`
|
|
12
|
+
* - effect `on: { ... }`
|
|
13
|
+
* - resolver `key: [ ... ]`
|
|
14
|
+
* - event `patch: { $set: { ... } }`
|
|
15
|
+
* - derive `compute: { ... }` (predicate + template)
|
|
16
|
+
*
|
|
17
|
+
* And the introspection that only a data form makes possible:
|
|
18
|
+
* - `system.inspect().constraints[].whenSpec`
|
|
19
|
+
* - `constraint.evaluate` observer event with `whenExplain`
|
|
20
|
+
* - `system.explain()` rendering a clause-by-clause ✓/✗ tree.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
type ModuleSchema,
|
|
25
|
+
createModule,
|
|
26
|
+
createSystem,
|
|
27
|
+
t,
|
|
28
|
+
} from "@directive-run/core";
|
|
29
|
+
|
|
30
|
+
// ----------------------------------------------------------------------------
|
|
31
|
+
// Module
|
|
32
|
+
// ----------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
const schema = {
|
|
35
|
+
facts: {
|
|
36
|
+
phase: t.string<"red" | "yellow" | "green">(),
|
|
37
|
+
elapsed: t.number(),
|
|
38
|
+
transitionCount: t.number(),
|
|
39
|
+
label: t.string(),
|
|
40
|
+
firstName: t.string(),
|
|
41
|
+
lastName: t.string(),
|
|
42
|
+
age: t.number(),
|
|
43
|
+
},
|
|
44
|
+
derivations: {
|
|
45
|
+
isAdult: t.boolean(),
|
|
46
|
+
fullName: t.string(),
|
|
47
|
+
},
|
|
48
|
+
events: {
|
|
49
|
+
advanceTo: { value: t.string(), userName: t.string() },
|
|
50
|
+
},
|
|
51
|
+
requirements: {
|
|
52
|
+
TRANSITION: { to: t.string<"red" | "yellow" | "green">() },
|
|
53
|
+
},
|
|
54
|
+
} satisfies ModuleSchema;
|
|
55
|
+
|
|
56
|
+
const trafficLight = createModule("traffic", {
|
|
57
|
+
schema,
|
|
58
|
+
|
|
59
|
+
init: (facts) => {
|
|
60
|
+
facts.phase = "red";
|
|
61
|
+
facts.elapsed = 0;
|
|
62
|
+
facts.transitionCount = 0;
|
|
63
|
+
facts.label = "";
|
|
64
|
+
facts.firstName = "Grace";
|
|
65
|
+
facts.lastName = "Hopper";
|
|
66
|
+
facts.age = 30;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// ── Derivations — predicate + template, no functions ─────────────────────
|
|
70
|
+
derive: {
|
|
71
|
+
isAdult: { compute: { age: { $gte: 18 } } },
|
|
72
|
+
fullName: { compute: { $template: "${firstName} ${lastName}" } },
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// ── Event — declarative patch instead of a handler ────────────────────────
|
|
76
|
+
events: {
|
|
77
|
+
advanceTo: {
|
|
78
|
+
patch: {
|
|
79
|
+
$set: {
|
|
80
|
+
phase: { $ref: "value" },
|
|
81
|
+
elapsed: 0,
|
|
82
|
+
label: { $template: "Set by ${userName} → ${value}" },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// ── Constraint — data `when`, declarative `require` ──────────────────────
|
|
89
|
+
constraints: {
|
|
90
|
+
transition: {
|
|
91
|
+
when: { phase: "red", elapsed: { $gte: 30 } },
|
|
92
|
+
require: { type: "TRANSITION", to: "green" },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// ── Resolver — KeySelector array dedups by requirement field ─────────────
|
|
97
|
+
resolvers: {
|
|
98
|
+
transition: {
|
|
99
|
+
requirement: "TRANSITION",
|
|
100
|
+
key: ["to"],
|
|
101
|
+
resolve: async (req, ctx) => {
|
|
102
|
+
ctx.facts.phase = req.to;
|
|
103
|
+
ctx.facts.elapsed = 0;
|
|
104
|
+
ctx.facts.transitionCount = ctx.facts.transitionCount + 1;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ----------------------------------------------------------------------------
|
|
111
|
+
// System + observer
|
|
112
|
+
// ----------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
const system = createSystem({ module: trafficLight });
|
|
115
|
+
|
|
116
|
+
system.observe((event) => {
|
|
117
|
+
if (event.type === "constraint.evaluate" && event.whenExplain) {
|
|
118
|
+
console.log(
|
|
119
|
+
`\n[observe] constraint.evaluate ${event.id} → ${event.active}`,
|
|
120
|
+
);
|
|
121
|
+
for (const clause of event.whenExplain) {
|
|
122
|
+
const mark = clause.pass ? "✓" : "✗";
|
|
123
|
+
console.log(
|
|
124
|
+
` ${mark} ${clause.path} ${clause.op} ${JSON.stringify(clause.expected)} (actual: ${JSON.stringify(clause.actual)})`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
system.start();
|
|
131
|
+
|
|
132
|
+
// ----------------------------------------------------------------------------
|
|
133
|
+
// Run
|
|
134
|
+
// ----------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
console.log("--- Initial derivations (data forms) ---");
|
|
137
|
+
console.log(`isAdult: ${system.derive.isAdult}`);
|
|
138
|
+
console.log(`fullName: ${system.derive.fullName}`);
|
|
139
|
+
|
|
140
|
+
console.log("\n--- inspect().constraints[] surfaces whenSpec ---");
|
|
141
|
+
for (const c of system.inspect().constraints) {
|
|
142
|
+
console.log(`${c.id}: whenSpec = ${JSON.stringify(c.whenSpec)}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Trip the predicate: elapsed goes from 0 → 30, so the constraint fires.
|
|
146
|
+
console.log("\n--- elapsed → 30 (predicate now holds) ---");
|
|
147
|
+
system.facts.elapsed = 30;
|
|
148
|
+
await settle();
|
|
149
|
+
|
|
150
|
+
console.log(`phase after resolver: ${system.facts.phase}`);
|
|
151
|
+
console.log(`transitionCount: ${system.facts.transitionCount}`);
|
|
152
|
+
|
|
153
|
+
// Show explain() on the next emitted requirement (if any), or on a re-fire.
|
|
154
|
+
// Two writes: phase ← "red" satisfies the first clause; elapsed ← 5 fails the
|
|
155
|
+
// `$gte: 30` clause, so the predicate as a whole is false and the constraint
|
|
156
|
+
// does not fire.
|
|
157
|
+
console.log("\n--- phase = red, elapsed → 5 (predicate fails on elapsed) ---");
|
|
158
|
+
system.facts.phase = "red";
|
|
159
|
+
system.facts.elapsed = 5;
|
|
160
|
+
await settle();
|
|
161
|
+
|
|
162
|
+
// Dispatch the data-form event — sets multiple facts from the payload.
|
|
163
|
+
console.log("\n--- dispatch advanceTo (patch) ---");
|
|
164
|
+
system.events.advanceTo({ value: "yellow", userName: "Ada" });
|
|
165
|
+
await settle();
|
|
166
|
+
console.log(`phase: ${system.facts.phase}`);
|
|
167
|
+
console.log(`label: ${system.facts.label}`);
|
|
168
|
+
|
|
169
|
+
system.destroy();
|
|
170
|
+
|
|
171
|
+
async function settle(): Promise<void> {
|
|
172
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
173
|
+
await Promise.resolve();
|
|
174
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
175
|
+
}
|
package/examples/sudoku.ts
CHANGED
|
@@ -110,12 +110,7 @@ export const sudokuSchema = {
|
|
|
110
110
|
export const sudokuGame = createModule("sudoku", {
|
|
111
111
|
schema: sudokuSchema,
|
|
112
112
|
history: {
|
|
113
|
-
snapshotEvents: [
|
|
114
|
-
"inputNumber",
|
|
115
|
-
"toggleNote",
|
|
116
|
-
"requestHint",
|
|
117
|
-
"newGame",
|
|
118
|
-
],
|
|
113
|
+
snapshotEvents: ["inputNumber", "toggleNote", "requestHint", "newGame"],
|
|
119
114
|
},
|
|
120
115
|
|
|
121
116
|
init: (facts) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directive-run/knowledge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Knowledge files, examples, and validation for Directive — the constraint-driven TypeScript runtime.",
|
|
5
5
|
"license": "(MIT OR Apache-2.0)",
|
|
6
6
|
"author": "Jason Comes",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"tsx": "^4.19.2",
|
|
52
52
|
"typescript": "^5.7.2",
|
|
53
53
|
"vitest": "^3.0.0",
|
|
54
|
-
"@directive-run/
|
|
55
|
-
"@directive-run/
|
|
54
|
+
"@directive-run/core": "1.5.0",
|
|
55
|
+
"@directive-run/ai": "1.5.0"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsx scripts/generate-api-skeleton.ts && tsx scripts/generate-sitemap.ts && tsx scripts/extract-examples.ts && tsup",
|