@directive-run/knowledge 0.2.0 → 0.4.2
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 +3 -3
- package/ai/ai-adapters.md +7 -7
- package/ai/ai-agents-streaming.md +8 -8
- package/ai/ai-budget-resilience.md +5 -5
- package/ai/ai-communication.md +1 -1
- package/ai/ai-guardrails-memory.md +7 -7
- package/ai/ai-mcp-rag.md +5 -5
- package/ai/ai-multi-agent.md +14 -14
- package/ai/ai-orchestrator.md +8 -8
- package/ai/ai-security.md +2 -2
- package/ai/ai-tasks.md +9 -9
- package/ai/ai-testing-evals.md +2 -2
- package/core/anti-patterns.md +39 -39
- package/core/constraints.md +15 -15
- package/core/core-patterns.md +9 -9
- package/core/error-boundaries.md +7 -7
- package/core/multi-module.md +16 -16
- package/core/naming.md +21 -21
- package/core/plugins.md +14 -14
- package/core/react-adapter.md +13 -13
- package/core/resolvers.md +14 -14
- package/core/schema-types.md +22 -22
- package/core/system-api.md +16 -16
- package/core/testing.md +5 -5
- package/core/time-travel.md +20 -20
- package/dist/index.cjs +6 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -97
- package/dist/index.js.map +1 -1
- package/examples/ab-testing.ts +18 -90
- package/examples/ai-checkpoint.ts +68 -87
- package/examples/ai-guardrails.ts +20 -70
- package/examples/auth-flow.ts +2 -2
- package/examples/batch-resolver.ts +19 -59
- package/examples/contact-form.ts +220 -69
- package/examples/counter.ts +77 -95
- package/examples/dashboard-loader.ts +37 -55
- package/examples/debounce-constraints.ts +0 -2
- package/examples/dynamic-modules.ts +17 -20
- package/examples/error-boundaries.ts +30 -81
- package/examples/newsletter.ts +22 -49
- package/examples/notifications.ts +24 -23
- package/examples/optimistic-updates.ts +36 -41
- package/examples/pagination.ts +2 -2
- package/examples/permissions.ts +22 -32
- package/examples/provider-routing.ts +26 -83
- package/examples/shopping-cart.ts +8 -8
- package/examples/sudoku.ts +55 -62
- package/examples/theme-locale.ts +4 -7
- package/examples/time-machine.ts +12 -90
- package/examples/topic-guard.ts +30 -38
- package/examples/url-sync.ts +8 -8
- package/examples/websocket.ts +5 -5
- package/package.json +3 -3
package/examples/ab-testing.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Example: ab-testing
|
|
2
|
-
// Source: examples/ab-testing/src/
|
|
3
|
-
//
|
|
2
|
+
// Source: examples/ab-testing/src/module.ts
|
|
3
|
+
// Pure module file — no DOM wiring
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* A/B Testing Engine —
|
|
6
|
+
* A/B Testing Engine — Directive Module
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Types, schema, helpers, module definition, timeline, and system creation
|
|
9
|
+
* for a constraint-driven A/B testing engine with deterministic hashing.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {
|
|
@@ -21,20 +21,20 @@ import { devtoolsPlugin } from "@directive-run/core/plugins";
|
|
|
21
21
|
// Types
|
|
22
22
|
// ============================================================================
|
|
23
23
|
|
|
24
|
-
interface Variant {
|
|
24
|
+
export interface Variant {
|
|
25
25
|
id: string;
|
|
26
26
|
weight: number;
|
|
27
27
|
label: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
interface Experiment {
|
|
30
|
+
export interface Experiment {
|
|
31
31
|
id: string;
|
|
32
32
|
name: string;
|
|
33
33
|
variants: Variant[];
|
|
34
34
|
active: boolean;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
interface TimelineEntry {
|
|
37
|
+
export interface TimelineEntry {
|
|
38
38
|
time: number;
|
|
39
39
|
event: string;
|
|
40
40
|
detail: string;
|
|
@@ -78,9 +78,9 @@ function pickVariant(
|
|
|
78
78
|
// Timeline
|
|
79
79
|
// ============================================================================
|
|
80
80
|
|
|
81
|
-
const timeline: TimelineEntry[] = [];
|
|
81
|
+
export const timeline: TimelineEntry[] = [];
|
|
82
82
|
|
|
83
|
-
function
|
|
83
|
+
export function addLog(type: "event" | "constraint" | "resolver", msg: string) {
|
|
84
84
|
console.log(`[AB] [${type}] ${msg}`);
|
|
85
85
|
|
|
86
86
|
// Classify for timeline
|
|
@@ -125,16 +125,16 @@ function log(type: "event" | "constraint" | "resolver", msg: string) {
|
|
|
125
125
|
// Schema
|
|
126
126
|
// ============================================================================
|
|
127
127
|
|
|
128
|
-
const schema = {
|
|
128
|
+
export const schema = {
|
|
129
129
|
facts: {
|
|
130
|
-
experiments: t.
|
|
130
|
+
experiments: t.array<Experiment>(),
|
|
131
131
|
assignments: t.object<Record<string, string>>(),
|
|
132
132
|
exposures: t.object<Record<string, number>>(),
|
|
133
133
|
userId: t.string(),
|
|
134
134
|
paused: t.boolean(),
|
|
135
135
|
},
|
|
136
136
|
derivations: {
|
|
137
|
-
activeExperiments: t.
|
|
137
|
+
activeExperiments: t.array<Experiment>(),
|
|
138
138
|
assignedCount: t.number(),
|
|
139
139
|
exposedCount: t.number(),
|
|
140
140
|
},
|
|
@@ -142,7 +142,7 @@ const schema = {
|
|
|
142
142
|
registerExperiment: {
|
|
143
143
|
id: t.string(),
|
|
144
144
|
name: t.string(),
|
|
145
|
-
variants: t.
|
|
145
|
+
variants: t.array<Variant>(),
|
|
146
146
|
},
|
|
147
147
|
assignVariant: { experimentId: t.string(), variantId: t.string() },
|
|
148
148
|
recordExposure: { experimentId: t.string() },
|
|
@@ -173,9 +173,7 @@ const abTesting = createModule("ab-testing", {
|
|
|
173
173
|
|
|
174
174
|
derive: {
|
|
175
175
|
activeExperiments: (facts) =>
|
|
176
|
-
|
|
177
|
-
(e) => e.active && !facts.paused,
|
|
178
|
-
),
|
|
176
|
+
facts.experiments.filter((e: Experiment) => e.active && !facts.paused),
|
|
179
177
|
assignedCount: (facts) => Object.keys(facts.assignments).length,
|
|
180
178
|
exposedCount: (facts) => Object.keys(facts.exposures).length,
|
|
181
179
|
},
|
|
@@ -283,7 +281,7 @@ const abTesting = createModule("ab-testing", {
|
|
|
283
281
|
...context.facts.assignments,
|
|
284
282
|
[req.experimentId]: variantId,
|
|
285
283
|
};
|
|
286
|
-
|
|
284
|
+
addLog("resolver", `Assigned ${req.experimentId} → ${variantId}`);
|
|
287
285
|
},
|
|
288
286
|
},
|
|
289
287
|
|
|
@@ -295,7 +293,7 @@ const abTesting = createModule("ab-testing", {
|
|
|
295
293
|
...context.facts.exposures,
|
|
296
294
|
[req.experimentId]: now,
|
|
297
295
|
};
|
|
298
|
-
|
|
296
|
+
addLog(
|
|
299
297
|
"resolver",
|
|
300
298
|
`Exposure tracked: ${req.experimentId} (variant: ${req.variantId}) at ${new Date(now).toLocaleTimeString()}`,
|
|
301
299
|
);
|
|
@@ -308,78 +306,8 @@ const abTesting = createModule("ab-testing", {
|
|
|
308
306
|
// System
|
|
309
307
|
// ============================================================================
|
|
310
308
|
|
|
311
|
-
const system = createSystem({
|
|
309
|
+
export const system = createSystem({
|
|
312
310
|
module: abTesting,
|
|
313
311
|
debug: { runHistory: true },
|
|
314
312
|
plugins: [devtoolsPlugin({ name: "ab-testing" })],
|
|
315
313
|
});
|
|
316
|
-
system.start();
|
|
317
|
-
|
|
318
|
-
// ============================================================================
|
|
319
|
-
// DOM References
|
|
320
|
-
// ============================================================================
|
|
321
|
-
|
|
322
|
-
// Stats
|
|
323
|
-
|
|
324
|
-
// Timeline
|
|
325
|
-
|
|
326
|
-
// ============================================================================
|
|
327
|
-
// Render
|
|
328
|
-
// ============================================================================
|
|
329
|
-
|
|
330
|
-
function escapeHtml(text: string): string {
|
|
331
|
-
|
|
332
|
-
return div.innerHTML;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// ============================================================================
|
|
337
|
-
// Subscribe
|
|
338
|
-
// ============================================================================
|
|
339
|
-
|
|
340
|
-
system.subscribe(
|
|
341
|
-
[
|
|
342
|
-
"experiments",
|
|
343
|
-
"assignments",
|
|
344
|
-
"exposures",
|
|
345
|
-
"userId",
|
|
346
|
-
"paused",
|
|
347
|
-
"activeExperiments",
|
|
348
|
-
"assignedCount",
|
|
349
|
-
"exposedCount",
|
|
350
|
-
],
|
|
351
|
-
render,
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
// Button handlers
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
// ============================================================================
|
|
358
|
-
// Register sample experiments
|
|
359
|
-
// ============================================================================
|
|
360
|
-
|
|
361
|
-
system.events.registerExperiment({
|
|
362
|
-
id: "theme-icons",
|
|
363
|
-
name: "Theme Icons",
|
|
364
|
-
variants: [
|
|
365
|
-
{ id: "custom-svg", weight: 50, label: "Custom SVG" },
|
|
366
|
-
{ id: "phosphor", weight: 50, label: "Phosphor" },
|
|
367
|
-
],
|
|
368
|
-
});
|
|
369
|
-
log("event", "Registered experiment: theme-icons");
|
|
370
|
-
|
|
371
|
-
system.events.registerExperiment({
|
|
372
|
-
id: "cta-color",
|
|
373
|
-
name: "CTA Button Color",
|
|
374
|
-
variants: [
|
|
375
|
-
{ id: "brand", weight: 50, label: "Brand" },
|
|
376
|
-
{ id: "green", weight: 30, label: "Green" },
|
|
377
|
-
{ id: "orange", weight: 20, label: "Orange" },
|
|
378
|
-
],
|
|
379
|
-
});
|
|
380
|
-
log("event", "Registered experiment: cta-color");
|
|
381
|
-
|
|
382
|
-
// Initial render
|
|
383
|
-
render();
|
|
384
|
-
|
|
385
|
-
// Signal to tests that initialization is complete
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Example: ai-checkpoint
|
|
2
|
-
// Source: examples/ai-checkpoint/src/
|
|
3
|
-
//
|
|
2
|
+
// Source: examples/ai-checkpoint/src/module.ts
|
|
3
|
+
// Pure module file — no DOM wiring
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* AI Pipeline Checkpoint —
|
|
6
|
+
* AI Pipeline Checkpoint — Module Definition
|
|
7
7
|
*
|
|
8
8
|
* 4-stage pipeline (extract → summarize → classify → archive) with checkpoint
|
|
9
9
|
* at every stage. Save/restore/delete checkpoints. Retry with backoff on failures.
|
|
@@ -27,7 +27,7 @@ import { devtoolsPlugin } from "@directive-run/core/plugins";
|
|
|
27
27
|
// Types
|
|
28
28
|
// ============================================================================
|
|
29
29
|
|
|
30
|
-
type PipelineStage =
|
|
30
|
+
export type PipelineStage =
|
|
31
31
|
| "idle"
|
|
32
32
|
| "extract"
|
|
33
33
|
| "summarize"
|
|
@@ -36,21 +36,21 @@ type PipelineStage =
|
|
|
36
36
|
| "done"
|
|
37
37
|
| "error";
|
|
38
38
|
|
|
39
|
-
interface StageResult {
|
|
39
|
+
export interface StageResult {
|
|
40
40
|
stage: string;
|
|
41
41
|
output: string;
|
|
42
42
|
tokens: number;
|
|
43
43
|
durationMs: number;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
interface CheckpointEntry {
|
|
46
|
+
export interface CheckpointEntry {
|
|
47
47
|
id: string;
|
|
48
48
|
label: string;
|
|
49
49
|
createdAt: string;
|
|
50
50
|
stage: PipelineStage;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
interface TimelineEntry {
|
|
53
|
+
export interface TimelineEntry {
|
|
54
54
|
time: number;
|
|
55
55
|
event: string;
|
|
56
56
|
detail: string;
|
|
@@ -61,7 +61,12 @@ interface TimelineEntry {
|
|
|
61
61
|
// Constants
|
|
62
62
|
// ============================================================================
|
|
63
63
|
|
|
64
|
-
const STAGES: PipelineStage[] = [
|
|
64
|
+
export const STAGES: PipelineStage[] = [
|
|
65
|
+
"extract",
|
|
66
|
+
"summarize",
|
|
67
|
+
"classify",
|
|
68
|
+
"archive",
|
|
69
|
+
];
|
|
65
70
|
|
|
66
71
|
const STAGE_CONFIG = {
|
|
67
72
|
extract: {
|
|
@@ -92,9 +97,9 @@ const STAGE_CONFIG = {
|
|
|
92
97
|
// Timeline
|
|
93
98
|
// ============================================================================
|
|
94
99
|
|
|
95
|
-
const timeline: TimelineEntry[] = [];
|
|
100
|
+
export const timeline: TimelineEntry[] = [];
|
|
96
101
|
|
|
97
|
-
function addTimeline(
|
|
102
|
+
export function addTimeline(
|
|
98
103
|
event: string,
|
|
99
104
|
detail: string,
|
|
100
105
|
type: TimelineEntry["type"],
|
|
@@ -109,23 +114,25 @@ function addTimeline(
|
|
|
109
114
|
// Checkpoint Store
|
|
110
115
|
// ============================================================================
|
|
111
116
|
|
|
112
|
-
const checkpointStore = new InMemoryCheckpointStore({
|
|
117
|
+
export const checkpointStore = new InMemoryCheckpointStore({
|
|
118
|
+
maxCheckpoints: 20,
|
|
119
|
+
});
|
|
113
120
|
|
|
114
121
|
// ============================================================================
|
|
115
122
|
// Schema
|
|
116
123
|
// ============================================================================
|
|
117
124
|
|
|
118
|
-
const schema = {
|
|
125
|
+
export const schema = {
|
|
119
126
|
facts: {
|
|
120
127
|
currentStage: t.string<PipelineStage>(),
|
|
121
|
-
stageResults: t.
|
|
128
|
+
stageResults: t.array<StageResult>(),
|
|
122
129
|
totalTokens: t.number(),
|
|
123
130
|
retryCount: t.number(),
|
|
124
131
|
maxRetries: t.number(),
|
|
125
132
|
failStage: t.string(),
|
|
126
133
|
isRunning: t.boolean(),
|
|
127
134
|
lastError: t.string(),
|
|
128
|
-
checkpoints: t.
|
|
135
|
+
checkpoints: t.array<CheckpointEntry>(),
|
|
129
136
|
selectedCheckpoint: t.string(),
|
|
130
137
|
},
|
|
131
138
|
derivations: {
|
|
@@ -173,11 +180,11 @@ const pipelineModule = createModule("pipeline", {
|
|
|
173
180
|
return 100;
|
|
174
181
|
}
|
|
175
182
|
if (facts.currentStage === "error") {
|
|
176
|
-
const idx =
|
|
183
|
+
const idx = facts.stageResults.length;
|
|
177
184
|
|
|
178
185
|
return Math.round((idx / STAGES.length) * 100);
|
|
179
186
|
}
|
|
180
|
-
const idx = STAGES.indexOf(facts.currentStage
|
|
187
|
+
const idx = STAGES.indexOf(facts.currentStage);
|
|
181
188
|
|
|
182
189
|
return Math.round((idx / STAGES.length) * 100);
|
|
183
190
|
},
|
|
@@ -189,7 +196,7 @@ const pipelineModule = createModule("pipeline", {
|
|
|
189
196
|
return STAGES.length;
|
|
190
197
|
}
|
|
191
198
|
|
|
192
|
-
return STAGES.indexOf(facts.currentStage
|
|
199
|
+
return STAGES.indexOf(facts.currentStage);
|
|
193
200
|
},
|
|
194
201
|
canAdvance: (facts) => {
|
|
195
202
|
return (
|
|
@@ -229,12 +236,11 @@ const pipelineModule = createModule("pipeline", {
|
|
|
229
236
|
// System
|
|
230
237
|
// ============================================================================
|
|
231
238
|
|
|
232
|
-
const system = createSystem({
|
|
239
|
+
export const system = createSystem({
|
|
233
240
|
module: pipelineModule,
|
|
234
241
|
debug: { runHistory: true },
|
|
235
242
|
plugins: [devtoolsPlugin({ name: "ai-checkpoint" })],
|
|
236
243
|
});
|
|
237
|
-
system.start();
|
|
238
244
|
|
|
239
245
|
// ============================================================================
|
|
240
246
|
// Pipeline Logic
|
|
@@ -263,8 +269,11 @@ async function runStage(stage: PipelineStage): Promise<StageResult> {
|
|
|
263
269
|
};
|
|
264
270
|
}
|
|
265
271
|
|
|
266
|
-
async function runStageWithRetry(
|
|
267
|
-
|
|
272
|
+
export async function runStageWithRetry(
|
|
273
|
+
stage: PipelineStage,
|
|
274
|
+
renderCallback: () => void,
|
|
275
|
+
): Promise<StageResult> {
|
|
276
|
+
const maxRetries = system.facts.maxRetries;
|
|
268
277
|
let lastError: Error | null = null;
|
|
269
278
|
|
|
270
279
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
@@ -272,30 +281,34 @@ async function runStageWithRetry(stage: PipelineStage): Promise<StageResult> {
|
|
|
272
281
|
if (attempt > 0) {
|
|
273
282
|
const delay = Math.min(500 * 2 ** (attempt - 1), 4000);
|
|
274
283
|
const jitter = Math.random() * delay * 0.1;
|
|
275
|
-
system.facts.retryCount =
|
|
284
|
+
system.facts.retryCount = system.facts.retryCount + 1;
|
|
285
|
+
addTimeline(
|
|
276
286
|
"retry",
|
|
277
287
|
`${stage}: attempt ${attempt + 1}/${maxRetries + 1} (delay ${Math.round(delay)}ms)`,
|
|
278
288
|
"retry",
|
|
279
289
|
);
|
|
280
|
-
|
|
290
|
+
renderCallback();
|
|
281
291
|
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
282
292
|
}
|
|
283
293
|
|
|
284
294
|
return await runStage(stage);
|
|
285
295
|
} catch (err) {
|
|
286
296
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
297
|
+
addTimeline("error", `${stage}: ${lastError.message}`, "error");
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
300
|
|
|
290
301
|
throw lastError!;
|
|
291
302
|
}
|
|
292
303
|
|
|
293
|
-
async function advancePipeline(
|
|
304
|
+
export async function advancePipeline(
|
|
305
|
+
renderCallback: () => void,
|
|
306
|
+
): Promise<void> {
|
|
294
307
|
if (system.facts.isRunning) {
|
|
295
308
|
return;
|
|
296
309
|
}
|
|
297
310
|
|
|
298
|
-
const current = system.facts.currentStage
|
|
311
|
+
const current = system.facts.currentStage;
|
|
299
312
|
let nextStage: PipelineStage;
|
|
300
313
|
|
|
301
314
|
if (current === "idle") {
|
|
@@ -314,14 +327,15 @@ async function advancePipeline() {
|
|
|
314
327
|
|
|
315
328
|
system.facts.isRunning = true;
|
|
316
329
|
system.facts.currentStage = nextStage;
|
|
317
|
-
|
|
330
|
+
addTimeline("stage", `${nextStage}: starting`, "stage");
|
|
331
|
+
renderCallback();
|
|
318
332
|
|
|
319
333
|
try {
|
|
320
|
-
const result = await runStageWithRetry(nextStage);
|
|
321
|
-
const results = [...
|
|
334
|
+
const result = await runStageWithRetry(nextStage, renderCallback);
|
|
335
|
+
const results = [...system.facts.stageResults, result];
|
|
322
336
|
system.facts.stageResults = results;
|
|
323
|
-
system.facts.totalTokens =
|
|
324
|
-
|
|
337
|
+
system.facts.totalTokens = system.facts.totalTokens + result.tokens;
|
|
338
|
+
addTimeline(
|
|
325
339
|
"success",
|
|
326
340
|
`${nextStage}: complete (${result.tokens} tokens)`,
|
|
327
341
|
"success",
|
|
@@ -330,18 +344,20 @@ async function advancePipeline() {
|
|
|
330
344
|
const idx = STAGES.indexOf(nextStage);
|
|
331
345
|
if (idx >= STAGES.length - 1) {
|
|
332
346
|
system.facts.currentStage = "done";
|
|
347
|
+
addTimeline("info", "pipeline complete", "info");
|
|
333
348
|
} else {
|
|
334
349
|
system.facts.currentStage = nextStage;
|
|
335
350
|
}
|
|
336
351
|
} catch (err) {
|
|
337
352
|
system.facts.currentStage = "error";
|
|
338
353
|
system.facts.lastError = err instanceof Error ? err.message : String(err);
|
|
354
|
+
addTimeline("error", `pipeline halted: ${system.facts.lastError}`, "error");
|
|
339
355
|
} finally {
|
|
340
356
|
system.facts.isRunning = false;
|
|
341
357
|
}
|
|
342
358
|
}
|
|
343
359
|
|
|
344
|
-
async function autoRun() {
|
|
360
|
+
export async function autoRun(renderCallback: () => void): Promise<void> {
|
|
345
361
|
if (system.facts.isRunning) {
|
|
346
362
|
return;
|
|
347
363
|
}
|
|
@@ -351,18 +367,20 @@ async function autoRun() {
|
|
|
351
367
|
system.facts.totalTokens = 0;
|
|
352
368
|
system.facts.retryCount = 0;
|
|
353
369
|
system.facts.lastError = "";
|
|
370
|
+
addTimeline("info", "auto-run started", "info");
|
|
354
371
|
|
|
355
372
|
for (const stage of STAGES) {
|
|
356
373
|
system.facts.isRunning = true;
|
|
357
374
|
system.facts.currentStage = stage;
|
|
358
|
-
|
|
375
|
+
addTimeline("stage", `${stage}: starting`, "stage");
|
|
376
|
+
renderCallback();
|
|
359
377
|
|
|
360
378
|
try {
|
|
361
|
-
const result = await runStageWithRetry(stage);
|
|
362
|
-
const results = [...
|
|
379
|
+
const result = await runStageWithRetry(stage, renderCallback);
|
|
380
|
+
const results = [...system.facts.stageResults, result];
|
|
363
381
|
system.facts.stageResults = results;
|
|
364
|
-
system.facts.totalTokens =
|
|
365
|
-
|
|
382
|
+
system.facts.totalTokens = system.facts.totalTokens + result.tokens;
|
|
383
|
+
addTimeline(
|
|
366
384
|
"success",
|
|
367
385
|
`${stage}: complete (${result.tokens} tokens)`,
|
|
368
386
|
"success",
|
|
@@ -371,6 +389,7 @@ async function autoRun() {
|
|
|
371
389
|
system.facts.currentStage = "error";
|
|
372
390
|
system.facts.lastError = err instanceof Error ? err.message : String(err);
|
|
373
391
|
system.facts.isRunning = false;
|
|
392
|
+
addTimeline(
|
|
374
393
|
"error",
|
|
375
394
|
`pipeline halted at ${stage}: ${system.facts.lastError}`,
|
|
376
395
|
"error",
|
|
@@ -383,14 +402,15 @@ async function autoRun() {
|
|
|
383
402
|
}
|
|
384
403
|
|
|
385
404
|
system.facts.currentStage = "done";
|
|
405
|
+
addTimeline("info", "pipeline complete (auto-run)", "info");
|
|
386
406
|
}
|
|
387
407
|
|
|
388
408
|
// ============================================================================
|
|
389
409
|
// Checkpoint Logic
|
|
390
410
|
// ============================================================================
|
|
391
411
|
|
|
392
|
-
async function saveCheckpoint() {
|
|
393
|
-
const stage = system.facts.currentStage
|
|
412
|
+
export async function saveCheckpoint(): Promise<void> {
|
|
413
|
+
const stage = system.facts.currentStage;
|
|
394
414
|
const id = createCheckpointId();
|
|
395
415
|
const label = `Stage: ${stage} (${new Date().toLocaleTimeString()})`;
|
|
396
416
|
|
|
@@ -420,21 +440,21 @@ async function saveCheckpoint() {
|
|
|
420
440
|
createdAt: checkpoint.createdAt,
|
|
421
441
|
stage,
|
|
422
442
|
};
|
|
423
|
-
system.facts.checkpoints = [
|
|
424
|
-
...(system.facts.checkpoints as CheckpointEntry[]),
|
|
425
|
-
entry,
|
|
426
|
-
];
|
|
443
|
+
system.facts.checkpoints = [...system.facts.checkpoints, entry];
|
|
427
444
|
|
|
445
|
+
addTimeline("checkpoint", `saved: ${label}`, "checkpoint");
|
|
428
446
|
}
|
|
429
447
|
|
|
430
|
-
async function restoreCheckpoint(checkpointId: string) {
|
|
448
|
+
export async function restoreCheckpoint(checkpointId: string): Promise<void> {
|
|
431
449
|
const checkpoint = await checkpointStore.load(checkpointId);
|
|
432
450
|
if (!checkpoint) {
|
|
451
|
+
addTimeline("error", "checkpoint not found", "error");
|
|
433
452
|
|
|
434
453
|
return;
|
|
435
454
|
}
|
|
436
455
|
|
|
437
456
|
if (!validateCheckpoint(checkpoint)) {
|
|
457
|
+
addTimeline("error", "invalid checkpoint data", "error");
|
|
438
458
|
|
|
439
459
|
return;
|
|
440
460
|
}
|
|
@@ -448,62 +468,23 @@ async function restoreCheckpoint(checkpointId: string) {
|
|
|
448
468
|
system.facts.isRunning = false;
|
|
449
469
|
|
|
450
470
|
if (checkpoint.timelineExport) {
|
|
471
|
+
const savedTimeline = JSON.parse(checkpoint.timelineExport);
|
|
451
472
|
timeline.length = 0;
|
|
452
473
|
for (const entry of savedTimeline) {
|
|
453
474
|
timeline.push(entry);
|
|
454
475
|
}
|
|
455
476
|
}
|
|
456
477
|
|
|
478
|
+
addTimeline("checkpoint", `restored: ${checkpoint.label}`, "checkpoint");
|
|
457
479
|
}
|
|
458
480
|
|
|
459
|
-
async function deleteCheckpoint(checkpointId: string) {
|
|
481
|
+
export async function deleteCheckpoint(checkpointId: string): Promise<void> {
|
|
460
482
|
const deleted = await checkpointStore.delete(checkpointId);
|
|
461
483
|
if (deleted) {
|
|
462
|
-
const checkpoints =
|
|
484
|
+
const checkpoints = system.facts.checkpoints.filter(
|
|
463
485
|
(c) => c.id !== checkpointId,
|
|
464
486
|
);
|
|
465
487
|
system.facts.checkpoints = checkpoints;
|
|
488
|
+
addTimeline("checkpoint", "deleted checkpoint", "checkpoint");
|
|
466
489
|
}
|
|
467
490
|
}
|
|
468
|
-
|
|
469
|
-
// ============================================================================
|
|
470
|
-
// DOM References
|
|
471
|
-
// ============================================================================
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// ============================================================================
|
|
475
|
-
// Render
|
|
476
|
-
// ============================================================================
|
|
477
|
-
|
|
478
|
-
function escapeHtml(text: string): string {
|
|
479
|
-
|
|
480
|
-
return div.innerHTML;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// ============================================================================
|
|
485
|
-
// Subscribe
|
|
486
|
-
// ============================================================================
|
|
487
|
-
|
|
488
|
-
const allKeys = [
|
|
489
|
-
...Object.keys(schema.facts),
|
|
490
|
-
...Object.keys(schema.derivations),
|
|
491
|
-
];
|
|
492
|
-
system.subscribe(allKeys, render);
|
|
493
|
-
|
|
494
|
-
// ============================================================================
|
|
495
|
-
// Controls
|
|
496
|
-
// ============================================================================
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
"cp-fail-stage",
|
|
500
|
-
|
|
501
|
-
// Delegated click for checkpoint restore/delete
|
|
502
|
-
|
|
503
|
-
"cp-max-retries",
|
|
504
|
-
|
|
505
|
-
// ============================================================================
|
|
506
|
-
// Initial Render
|
|
507
|
-
// ============================================================================
|
|
508
|
-
|
|
509
|
-
render();
|