@hobin/developer 0.1.6 → 0.1.8

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.
@@ -1,344 +1,476 @@
1
1
  import { assign, setup, transition, type StateValue } from "xstate";
2
2
 
3
3
  import type {
4
- DeveloperEvent,
5
- DeveloperState,
6
- FocusEvent,
7
- JudgmentEvent,
8
- ModeEvent,
9
- PendingQuestion,
10
- RouteEvent,
4
+ ActivationEvent,
5
+ DeveloperEvent,
6
+ DeveloperState,
7
+ FocusEvent,
8
+ JudgmentEvent,
9
+ PendingQuestion,
10
+ RouteEvent,
11
11
  } from "./state.ts";
12
12
 
13
13
  type DeveloperMachineEvent =
14
- | { type: "MODE"; event: ModeEvent }
15
- | { type: "FOCUS"; event: FocusEvent }
16
- | { type: "ROUTE"; event: RouteEvent }
17
- | { type: "JUDGMENT"; event: JudgmentEvent };
14
+ | { type: "ACTIVATION"; event: ActivationEvent }
15
+ | { type: "FOCUS"; event: FocusEvent }
16
+ | { type: "ROUTE"; event: RouteEvent }
17
+ | { type: "JUDGMENT"; event: JudgmentEvent };
18
18
 
19
19
  export type DeveloperMachineTag =
20
- | "execute"
21
- | "mutate"
22
- | "blocks-direct"
23
- | "blocks-completion"
24
- | "reroute-required"
25
- | "framing-required"
26
- | "verification-required";
20
+ | "execute"
21
+ | "mutate"
22
+ | "blocks-implementation"
23
+ | "blocks-completion"
24
+ | "reroute-required"
25
+ | "framing-required"
26
+ | "verification-required";
27
27
 
28
28
  export const initialState = (): DeveloperState => ({
29
- mode: "off",
30
- routeHistory: [],
31
- judgmentHistory: [],
32
- pendingQuestions: [],
33
- rerouteRequired: false,
34
- implementationFramingRequired: false,
35
- verificationRequired: false,
29
+ enabled: false,
30
+ activeRoute: undefined,
31
+ lastRoute: undefined,
32
+ lastJudgment: undefined,
33
+ routeHistory: [],
34
+ judgmentHistory: [],
35
+ pendingQuestions: [],
36
+ focusedQuestionId: undefined,
37
+ rerouteRequired: false,
38
+ implementationFramingRequired: false,
39
+ verificationRequired: false,
36
40
  });
37
41
 
38
42
  function normalizedQuestion(value: string): string {
39
- return value.toLocaleLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
43
+ return value
44
+ .toLocaleLowerCase()
45
+ .replace(/[^\p{L}\p{N}]+/gu, " ")
46
+ .trim();
40
47
  }
41
48
 
42
- function upsertQuestion(questions: PendingQuestion[], next: PendingQuestion): PendingQuestion[] {
43
- const nextKey = normalizedQuestion(next.question);
44
- const existingIndex = questions.findIndex(
45
- (question) => question.id === next.id || normalizedQuestion(question.question) === nextKey,
46
- );
47
- if (existingIndex === -1) return [...questions, next];
49
+ function upsertQuestion(
50
+ questions: PendingQuestion[],
51
+ next: PendingQuestion,
52
+ ): PendingQuestion[] {
53
+ const nextKey = normalizedQuestion(next.question);
54
+ const existingIndex = questions.findIndex(
55
+ (question) =>
56
+ question.id === next.id ||
57
+ normalizedQuestion(question.question) === nextKey,
58
+ );
59
+ if (existingIndex === -1) return [...questions, next];
48
60
 
49
- const existing = questions[existingIndex];
50
- if (!existing) return [...questions, next];
51
- const updated = [...questions];
52
- updated[existingIndex] = { ...next, id: existing.id };
53
- return updated;
61
+ const existing = questions[existingIndex];
62
+ if (!existing) return [...questions, next];
63
+ const updated = [...questions];
64
+ updated[existingIndex] = { ...next, id: existing.id };
65
+ return updated;
54
66
  }
55
67
 
56
- function applyRouteContext(state: DeveloperState, event: RouteEvent): DeveloperState {
57
- return {
58
- ...state,
59
- activeRoute: event,
60
- lastRoute: event,
61
- routeHistory: [...state.routeHistory, event],
62
- rerouteRequired: false,
63
- focusedQuestionId:
64
- event.targetQuestionId === state.focusedQuestionId ? undefined : state.focusedQuestionId,
65
- };
68
+ function applyRouteContext(
69
+ state: DeveloperState,
70
+ event: RouteEvent,
71
+ ): DeveloperState {
72
+ return {
73
+ ...state,
74
+ activeRoute: event,
75
+ lastRoute: event,
76
+ routeHistory: [...state.routeHistory, event],
77
+ rerouteRequired: false,
78
+ focusedQuestionId:
79
+ event.targetQuestionId === state.focusedQuestionId
80
+ ? undefined
81
+ : state.focusedQuestionId,
82
+ };
66
83
  }
67
84
 
68
85
  function questionsAfterJudgment(
69
- state: DeveloperState,
70
- route: RouteEvent,
71
- event: JudgmentEvent,
86
+ state: DeveloperState,
87
+ route: RouteEvent,
88
+ event: JudgmentEvent,
72
89
  ): PendingQuestion[] {
73
- let pending = [...state.pendingQuestions];
74
- const remainsOpen = event.status === "needs-evidence" || event.status === "blocked";
75
- if (!route.targetQuestionId && remainsOpen && event.openedQuestions.length === 0) {
76
- pending = upsertQuestion(pending, {
77
- id: `question:${route.routeId}`,
78
- question: route.question,
79
- status: event.status === "blocked" ? "blocked" : "open",
80
- resolutionOwner: "unknown",
81
- gate: event.status === "blocked" ? "before-completion" : "none",
82
- resolutionCriteria: `Obtain evidence that settles: ${route.question}`,
83
- sourceRouteId: route.routeId,
84
- });
85
- }
90
+ let pending = [...state.pendingQuestions];
91
+ const remainsOpen =
92
+ event.status === "needs-evidence" || event.status === "blocked";
93
+ if (
94
+ !route.targetQuestionId &&
95
+ remainsOpen &&
96
+ event.openedQuestions.length === 0
97
+ ) {
98
+ pending = upsertQuestion(pending, {
99
+ id: `question:${route.routeId}`,
100
+ question: route.question,
101
+ status: event.status === "blocked" ? "blocked" : "open",
102
+ resolutionOwner: "unknown",
103
+ gate: event.status === "blocked" ? "before-completion" : "none",
104
+ resolutionCriteria: `Obtain evidence that settles: ${route.question}`,
105
+ sourceRouteId: route.routeId,
106
+ });
107
+ }
86
108
 
87
- for (const question of event.openedQuestions) pending = upsertQuestion(pending, question);
88
- for (const update of event.questionUpdates) {
89
- if (update.status === "resolved" || update.status === "not-applicable") {
90
- pending = pending.filter((question) => question.id !== update.questionId);
91
- continue;
92
- }
93
- const existing = pending.find((question) => question.id === update.questionId);
94
- if (!existing) continue;
95
- pending = upsertQuestion(pending, {
96
- ...existing,
97
- status: update.status,
98
- sourceRouteId: route.routeId,
99
- });
100
- }
101
- return pending;
109
+ for (const question of event.openedQuestions)
110
+ pending = upsertQuestion(pending, question);
111
+ for (const update of event.questionUpdates) {
112
+ if (update.status === "resolved" || update.status === "not-applicable") {
113
+ pending = pending.filter((question) => question.id !== update.questionId);
114
+ continue;
115
+ }
116
+ const existing = pending.find(
117
+ (question) => question.id === update.questionId,
118
+ );
119
+ if (!existing) continue;
120
+ pending = upsertQuestion(pending, {
121
+ ...existing,
122
+ status: update.status,
123
+ sourceRouteId: route.routeId,
124
+ });
125
+ }
126
+ return pending;
102
127
  }
103
128
 
104
- function framingAfterJudgment(state: DeveloperState, route: RouteEvent, event: JudgmentEvent): boolean {
105
- if (route.owner === "model" && event.status === "resolved") return true;
106
- const closesGate =
107
- (route.owner === "sketch" || route.owner === "signal") &&
108
- (event.status === "resolved" || event.status === "not-applicable");
109
- return closesGate ? false : state.implementationFramingRequired;
129
+ function framingAfterJudgment(
130
+ state: DeveloperState,
131
+ route: RouteEvent,
132
+ event: JudgmentEvent,
133
+ ): boolean {
134
+ if (route.target === "model" && event.status === "resolved") return true;
135
+ const closesGate =
136
+ (route.target === "sketch" || route.target === "signal") &&
137
+ (event.status === "resolved" || event.status === "not-applicable");
138
+ return closesGate ? false : state.implementationFramingRequired;
110
139
  }
111
140
 
112
141
  function verificationAfterJudgment(
113
- state: DeveloperState,
114
- route: RouteEvent,
115
- event: JudgmentEvent,
116
- pending: PendingQuestion[],
142
+ state: DeveloperState,
143
+ route: RouteEvent,
144
+ event: JudgmentEvent,
145
+ pending: PendingQuestion[],
117
146
  ): boolean {
118
- if (route.owner === "direct" && event.changedArtifacts) return true;
119
- const completionQuestionRemains = pending.some(
120
- (question) => question.gate === "before-completion" || question.gate === "before-direct",
121
- );
122
- if (route.owner === "verify" && event.status === "resolved" && !completionQuestionRemains) {
123
- return false;
124
- }
125
- return state.verificationRequired;
147
+ if (route.target === "implementation" && event.changedArtifacts) return true;
148
+ const completionQuestionRemains = pending.some(
149
+ (question) =>
150
+ question.gate === "before-completion" ||
151
+ question.gate === "before-implementation",
152
+ );
153
+ if (
154
+ route.target === "verify" &&
155
+ event.status === "resolved" &&
156
+ !completionQuestionRemains
157
+ ) {
158
+ return false;
159
+ }
160
+ return state.verificationRequired;
126
161
  }
127
162
 
128
- function applyJudgmentContext(state: DeveloperState, event: JudgmentEvent): DeveloperState {
129
- const route = state.activeRoute;
130
- if (!route) return state;
131
- const judgment = { ...event, question: route.question, owner: route.owner };
132
- const pending = questionsAfterJudgment(state, route, event);
133
- return {
134
- ...state,
135
- activeRoute: undefined,
136
- lastJudgment: judgment,
137
- judgmentHistory: [...state.judgmentHistory, judgment],
138
- pendingQuestions: pending,
139
- focusedQuestionId: pending.some((question) => question.id === state.focusedQuestionId)
140
- ? state.focusedQuestionId
141
- : undefined,
142
- rerouteRequired: route.owner === "direct",
143
- implementationFramingRequired: framingAfterJudgment(state, route, event),
144
- verificationRequired: verificationAfterJudgment(state, route, event, pending),
145
- };
163
+ function applyJudgmentContext(
164
+ state: DeveloperState,
165
+ event: JudgmentEvent,
166
+ ): DeveloperState {
167
+ const route = state.activeRoute;
168
+ if (!route) return state;
169
+ const judgment = { ...event, question: route.question, target: route.target };
170
+ const pending = questionsAfterJudgment(state, route, event);
171
+ return {
172
+ ...state,
173
+ activeRoute: undefined,
174
+ lastJudgment: judgment,
175
+ judgmentHistory: [...state.judgmentHistory, judgment],
176
+ pendingQuestions: pending,
177
+ focusedQuestionId: pending.some(
178
+ (question) => question.id === state.focusedQuestionId,
179
+ )
180
+ ? state.focusedQuestionId
181
+ : undefined,
182
+ rerouteRequired: route.target === "implementation",
183
+ implementationFramingRequired: framingAfterJudgment(state, route, event),
184
+ verificationRequired: verificationAfterJudgment(
185
+ state,
186
+ route,
187
+ event,
188
+ pending,
189
+ ),
190
+ };
146
191
  }
147
192
 
148
193
  function assertNever(value: never): never {
149
- throw new Error(`Unexpected Developer machine event: ${JSON.stringify(value)}`);
194
+ throw new Error(
195
+ `Unexpected Developer machine event: ${JSON.stringify(value)}`,
196
+ );
150
197
  }
151
198
 
152
- function reduceDeveloperContext(state: DeveloperState, event: DeveloperEvent): DeveloperState {
153
- switch (event.kind) {
154
- case "mode":
155
- return event.mode === "off" ? initialState() : { ...state, mode: event.mode };
156
- case "focus":
157
- return { ...state, focusedQuestionId: event.questionId };
158
- case "route":
159
- return applyRouteContext(state, event);
160
- case "judgment":
161
- return applyJudgmentContext(state, event);
162
- default:
163
- return assertNever(event);
164
- }
199
+ function reduceDeveloperContext(
200
+ state: DeveloperState,
201
+ event: DeveloperEvent,
202
+ ): DeveloperState {
203
+ switch (event.kind) {
204
+ case "activation":
205
+ return event.enabled ? { ...state, enabled: true } : initialState();
206
+ case "focus":
207
+ return { ...state, focusedQuestionId: event.questionId };
208
+ case "route":
209
+ return applyRouteContext(state, event);
210
+ case "judgment":
211
+ return applyJudgmentContext(state, event);
212
+ default:
213
+ return assertNever(event);
214
+ }
165
215
  }
166
216
 
167
217
  function machineEvent(event: DeveloperEvent): DeveloperMachineEvent {
168
- switch (event.kind) {
169
- case "mode":
170
- return { type: "MODE", event };
171
- case "focus":
172
- return { type: "FOCUS", event };
173
- case "route":
174
- return { type: "ROUTE", event };
175
- case "judgment":
176
- return { type: "JUDGMENT", event };
177
- default:
178
- return assertNever(event);
179
- }
218
+ switch (event.kind) {
219
+ case "activation":
220
+ return { type: "ACTIVATION", event };
221
+ case "focus":
222
+ return { type: "FOCUS", event };
223
+ case "route":
224
+ return { type: "ROUTE", event };
225
+ case "judgment":
226
+ return { type: "JUDGMENT", event };
227
+ default:
228
+ return assertNever(event);
229
+ }
180
230
  }
181
231
 
182
- function canApplyEvent(context: DeveloperState, event: DeveloperMachineEvent): boolean {
183
- switch (event.type) {
184
- case "MODE":
185
- return true;
186
- case "FOCUS":
187
- return context.pendingQuestions.some((question) => question.id === event.event.questionId);
188
- case "ROUTE":
189
- return (
190
- !context.activeRoute &&
191
- (event.event.owner !== "direct" ||
192
- !context.pendingQuestions.some((question) => question.gate === "before-direct"))
193
- );
194
- case "JUDGMENT":
195
- return context.activeRoute?.routeId === event.event.routeId;
196
- default:
197
- return assertNever(event);
198
- }
232
+ function canApplyEvent(
233
+ context: DeveloperState,
234
+ event: DeveloperMachineEvent,
235
+ ): boolean {
236
+ switch (event.type) {
237
+ case "ACTIVATION":
238
+ return true;
239
+ case "FOCUS":
240
+ return (
241
+ context.enabled &&
242
+ context.pendingQuestions.some(
243
+ (question) => question.id === event.event.questionId,
244
+ )
245
+ );
246
+ case "ROUTE":
247
+ return (
248
+ context.enabled &&
249
+ !context.activeRoute &&
250
+ (event.event.target !== "implementation" ||
251
+ !context.pendingQuestions.some(
252
+ (question) => question.gate === "before-implementation",
253
+ ))
254
+ );
255
+ case "JUDGMENT":
256
+ return (
257
+ context.enabled && context.activeRoute?.routeId === event.event.routeId
258
+ );
259
+ default:
260
+ return assertNever(event);
261
+ }
199
262
  }
200
263
 
201
264
  const machineSetup = setup({
202
- types: {
203
- context: {} as DeveloperState,
204
- events: {} as DeveloperMachineEvent,
205
- tags: {} as DeveloperMachineTag,
206
- },
207
- actions: {
208
- applyEvent: assign(({ context, event }) => reduceDeveloperContext(context, event.event)),
209
- },
210
- guards: {
211
- eventAllowed: ({ context, event }) => canApplyEvent(context, event),
212
- modeOff: ({ context }) => context.mode === "off",
213
- modeOn: ({ context }) => context.mode === "on",
214
- modeStrict: ({ context }) => context.mode === "strict",
215
- routeIdle: ({ context }) => !context.activeRoute,
216
- routeJudgment: ({ context }) => Boolean(context.activeRoute && context.activeRoute.owner !== "direct"),
217
- routeDirect: ({ context }) => context.activeRoute?.owner === "direct",
218
- questionsClear: ({ context }) => context.pendingQuestions.length === 0,
219
- questionsOpen: ({ context }) => context.pendingQuestions.length > 0,
220
- directGateClear: ({ context }) =>
221
- !context.pendingQuestions.some((question) => question.gate === "before-direct"),
222
- directGateBlocked: ({ context }) =>
223
- context.pendingQuestions.some((question) => question.gate === "before-direct"),
224
- completionGateClear: ({ context }) =>
225
- !context.pendingQuestions.some(
226
- (question) => question.gate === "before-direct" || question.gate === "before-completion",
227
- ),
228
- completionGateBlocked: ({ context }) =>
229
- context.pendingQuestions.some(
230
- (question) => question.gate === "before-direct" || question.gate === "before-completion",
231
- ),
232
- checkpointReady: ({ context }) => !context.rerouteRequired,
233
- checkpointRequired: ({ context }) => context.rerouteRequired,
234
- framingClear: ({ context }) => !context.implementationFramingRequired,
235
- framingRequired: ({ context }) => context.implementationFramingRequired,
236
- verificationCurrent: ({ context }) => !context.verificationRequired,
237
- verificationRequired: ({ context }) => context.verificationRequired,
238
- },
265
+ types: {
266
+ context: {} as DeveloperState,
267
+ events: {} as DeveloperMachineEvent,
268
+ tags: {} as DeveloperMachineTag,
269
+ },
270
+ actions: {
271
+ applyEvent: assign(({ context, event }) =>
272
+ reduceDeveloperContext(context, event.event),
273
+ ),
274
+ },
275
+ guards: {
276
+ eventAllowed: ({ context, event }) => canApplyEvent(context, event),
277
+ activationDisabled: ({ context }) => !context.enabled,
278
+ activationEnabled: ({ context }) => context.enabled,
279
+ routeIdle: ({ context }) => !context.activeRoute,
280
+ routeJudgment: ({ context }) =>
281
+ Boolean(
282
+ context.activeRoute && context.activeRoute.target !== "implementation",
283
+ ),
284
+ routeImplementation: ({ context }) =>
285
+ context.activeRoute?.target === "implementation",
286
+ questionsClear: ({ context }) => context.pendingQuestions.length === 0,
287
+ questionsOpen: ({ context }) => context.pendingQuestions.length > 0,
288
+ implementationGateClear: ({ context }) =>
289
+ !context.pendingQuestions.some(
290
+ (question) => question.gate === "before-implementation",
291
+ ),
292
+ implementationGateBlocked: ({ context }) =>
293
+ context.pendingQuestions.some(
294
+ (question) => question.gate === "before-implementation",
295
+ ),
296
+ completionGateClear: ({ context }) =>
297
+ !context.pendingQuestions.some(
298
+ (question) =>
299
+ question.gate === "before-implementation" ||
300
+ question.gate === "before-completion",
301
+ ),
302
+ completionGateBlocked: ({ context }) =>
303
+ context.pendingQuestions.some(
304
+ (question) =>
305
+ question.gate === "before-implementation" ||
306
+ question.gate === "before-completion",
307
+ ),
308
+ checkpointReady: ({ context }) => !context.rerouteRequired,
309
+ checkpointRequired: ({ context }) => context.rerouteRequired,
310
+ framingClear: ({ context }) => !context.implementationFramingRequired,
311
+ framingRequired: ({ context }) => context.implementationFramingRequired,
312
+ verificationCurrent: ({ context }) => !context.verificationRequired,
313
+ verificationRequired: ({ context }) => context.verificationRequired,
314
+ },
239
315
  });
240
316
 
241
317
  export const developerMachine = machineSetup.createMachine({
242
- id: "developer",
243
- type: "parallel",
244
- context: initialState(),
245
- on: {
246
- MODE: { guard: "eventAllowed", actions: "applyEvent" },
247
- FOCUS: { guard: "eventAllowed", actions: "applyEvent" },
248
- ROUTE: { guard: "eventAllowed", actions: "applyEvent" },
249
- JUDGMENT: { guard: "eventAllowed", actions: "applyEvent" },
250
- },
251
- states: {
252
- mode: {
253
- initial: "off",
254
- states: {
255
- off: { always: [{ guard: "modeOn", target: "on" }, { guard: "modeStrict", target: "strict" }] },
256
- on: { always: [{ guard: "modeOff", target: "off" }, { guard: "modeStrict", target: "strict" }] },
257
- strict: { always: [{ guard: "modeOff", target: "off" }, { guard: "modeOn", target: "on" }] },
258
- },
259
- },
260
- route: {
261
- initial: "idle",
262
- states: {
263
- idle: { always: [{ guard: "routeDirect", target: "direct" }, { guard: "routeJudgment", target: "judgment" }] },
264
- judgment: { tags: "execute", always: [{ guard: "routeIdle", target: "idle" }, { guard: "routeDirect", target: "direct" }] },
265
- direct: { tags: ["execute", "mutate"], always: [{ guard: "routeIdle", target: "idle" }, { guard: "routeJudgment", target: "judgment" }] },
266
- },
267
- },
268
- questions: {
269
- initial: "clear",
270
- states: {
271
- clear: { always: { guard: "questionsOpen", target: "open" } },
272
- open: { always: { guard: "questionsClear", target: "clear" } },
273
- },
274
- },
275
- directGate: {
276
- initial: "clear",
277
- states: {
278
- clear: { always: { guard: "directGateBlocked", target: "blocked" } },
279
- blocked: { tags: "blocks-direct", always: { guard: "directGateClear", target: "clear" } },
280
- },
281
- },
282
- completionGate: {
283
- initial: "clear",
284
- states: {
285
- clear: { always: { guard: "completionGateBlocked", target: "blocked" } },
286
- blocked: { tags: "blocks-completion", always: { guard: "completionGateClear", target: "clear" } },
287
- },
288
- },
289
- checkpoint: {
290
- initial: "ready",
291
- states: {
292
- ready: { always: { guard: "checkpointRequired", target: "required" } },
293
- required: { tags: "reroute-required", always: { guard: "checkpointReady", target: "ready" } },
294
- },
295
- },
296
- framing: {
297
- initial: "clear",
298
- states: {
299
- clear: { always: { guard: "framingRequired", target: "required" } },
300
- required: { tags: "framing-required", always: { guard: "framingClear", target: "clear" } },
301
- },
302
- },
303
- verification: {
304
- initial: "current",
305
- states: {
306
- current: { always: { guard: "verificationRequired", target: "required" } },
307
- required: { tags: "verification-required", always: { guard: "verificationCurrent", target: "current" } },
308
- },
309
- },
310
- },
318
+ id: "developer",
319
+ type: "parallel",
320
+ context: initialState(),
321
+ on: {
322
+ ACTIVATION: { guard: "eventAllowed", actions: "applyEvent" },
323
+ FOCUS: { guard: "eventAllowed", actions: "applyEvent" },
324
+ ROUTE: { guard: "eventAllowed", actions: "applyEvent" },
325
+ JUDGMENT: { guard: "eventAllowed", actions: "applyEvent" },
326
+ },
327
+ states: {
328
+ activation: {
329
+ initial: "disabled",
330
+ states: {
331
+ disabled: { always: { guard: "activationEnabled", target: "enabled" } },
332
+ enabled: {
333
+ always: { guard: "activationDisabled", target: "disabled" },
334
+ },
335
+ },
336
+ },
337
+ route: {
338
+ initial: "idle",
339
+ states: {
340
+ idle: {
341
+ always: [
342
+ { guard: "routeImplementation", target: "implementation" },
343
+ { guard: "routeJudgment", target: "judgment" },
344
+ ],
345
+ },
346
+ judgment: {
347
+ tags: "execute",
348
+ always: [
349
+ { guard: "routeIdle", target: "idle" },
350
+ { guard: "routeImplementation", target: "implementation" },
351
+ ],
352
+ },
353
+ implementation: {
354
+ tags: ["execute", "mutate"],
355
+ always: [
356
+ { guard: "routeIdle", target: "idle" },
357
+ { guard: "routeJudgment", target: "judgment" },
358
+ ],
359
+ },
360
+ },
361
+ },
362
+ questions: {
363
+ initial: "clear",
364
+ states: {
365
+ clear: { always: { guard: "questionsOpen", target: "open" } },
366
+ open: { always: { guard: "questionsClear", target: "clear" } },
367
+ },
368
+ },
369
+ implementationGate: {
370
+ initial: "clear",
371
+ states: {
372
+ clear: {
373
+ always: { guard: "implementationGateBlocked", target: "blocked" },
374
+ },
375
+ blocked: {
376
+ tags: "blocks-implementation",
377
+ always: { guard: "implementationGateClear", target: "clear" },
378
+ },
379
+ },
380
+ },
381
+ completionGate: {
382
+ initial: "clear",
383
+ states: {
384
+ clear: {
385
+ always: { guard: "completionGateBlocked", target: "blocked" },
386
+ },
387
+ blocked: {
388
+ tags: "blocks-completion",
389
+ always: { guard: "completionGateClear", target: "clear" },
390
+ },
391
+ },
392
+ },
393
+ checkpoint: {
394
+ initial: "ready",
395
+ states: {
396
+ ready: { always: { guard: "checkpointRequired", target: "required" } },
397
+ required: {
398
+ tags: "reroute-required",
399
+ always: { guard: "checkpointReady", target: "ready" },
400
+ },
401
+ },
402
+ },
403
+ framing: {
404
+ initial: "clear",
405
+ states: {
406
+ clear: { always: { guard: "framingRequired", target: "required" } },
407
+ required: {
408
+ tags: "framing-required",
409
+ always: { guard: "framingClear", target: "clear" },
410
+ },
411
+ },
412
+ },
413
+ verification: {
414
+ initial: "current",
415
+ states: {
416
+ current: {
417
+ always: { guard: "verificationRequired", target: "required" },
418
+ },
419
+ required: {
420
+ tags: "verification-required",
421
+ always: { guard: "verificationCurrent", target: "current" },
422
+ },
423
+ },
424
+ },
425
+ },
311
426
  });
312
427
 
313
428
  function machineValue(state: DeveloperState): StateValue {
314
- let route = "idle";
315
- if (state.activeRoute?.owner === "direct") route = "direct";
316
- else if (state.activeRoute) route = "judgment";
317
- const directBlocked = state.pendingQuestions.some((question) => question.gate === "before-direct");
318
- const completionBlocked = state.pendingQuestions.some(
319
- (question) => question.gate === "before-direct" || question.gate === "before-completion",
320
- );
321
- return {
322
- mode: state.mode,
323
- route,
324
- questions: state.pendingQuestions.length > 0 ? "open" : "clear",
325
- directGate: directBlocked ? "blocked" : "clear",
326
- completionGate: completionBlocked ? "blocked" : "clear",
327
- checkpoint: state.rerouteRequired ? "required" : "ready",
328
- framing: state.implementationFramingRequired ? "required" : "clear",
329
- verification: state.verificationRequired ? "required" : "current",
330
- };
429
+ let route = "idle";
430
+ if (state.activeRoute?.target === "implementation") route = "implementation";
431
+ else if (state.activeRoute) route = "judgment";
432
+ const implementationBlocked = state.pendingQuestions.some(
433
+ (question) => question.gate === "before-implementation",
434
+ );
435
+ const completionBlocked = state.pendingQuestions.some(
436
+ (question) =>
437
+ question.gate === "before-implementation" ||
438
+ question.gate === "before-completion",
439
+ );
440
+ return {
441
+ activation: state.enabled ? "enabled" : "disabled",
442
+ route,
443
+ questions: state.pendingQuestions.length > 0 ? "open" : "clear",
444
+ implementationGate: implementationBlocked ? "blocked" : "clear",
445
+ completionGate: completionBlocked ? "blocked" : "clear",
446
+ checkpoint: state.rerouteRequired ? "required" : "ready",
447
+ framing: state.implementationFramingRequired ? "required" : "clear",
448
+ verification: state.verificationRequired ? "required" : "current",
449
+ };
331
450
  }
332
451
 
333
452
  export function developerSnapshot(state: DeveloperState) {
334
- return developerMachine.resolveState({ value: machineValue(state), context: state });
453
+ return developerMachine.resolveState({
454
+ value: machineValue(state),
455
+ context: state,
456
+ });
335
457
  }
336
458
 
337
- export function canApplyDeveloperEvent(state: DeveloperState, event: DeveloperEvent): boolean {
338
- return developerSnapshot(state).can(machineEvent(event));
459
+ export function canApplyDeveloperEvent(
460
+ state: DeveloperState,
461
+ event: DeveloperEvent,
462
+ ): boolean {
463
+ return developerSnapshot(state).can(machineEvent(event));
339
464
  }
340
465
 
341
- export function applyDeveloperEvent(state: DeveloperState, event: DeveloperEvent): DeveloperState {
342
- const [nextSnapshot] = transition(developerMachine, developerSnapshot(state), machineEvent(event));
343
- return nextSnapshot.context;
466
+ export function applyDeveloperEvent(
467
+ state: DeveloperState,
468
+ event: DeveloperEvent,
469
+ ): DeveloperState {
470
+ const [nextSnapshot] = transition(
471
+ developerMachine,
472
+ developerSnapshot(state),
473
+ machineEvent(event),
474
+ );
475
+ return nextSnapshot.context;
344
476
  }