@sublang/playbook 5.0.0 → 6.0.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.
Files changed (65) hide show
  1. package/README.md +11 -7
  2. package/docs/cli.md +38 -35
  3. package/docs/configuration.md +58 -15
  4. package/docs/embedding.md +24 -16
  5. package/package.json +40 -21
  6. package/reference/sdlc/captain.playbook/captain.playbook.js +2 -0
  7. package/reference/sdlc/captain.playbook/captain.playbook.ts +2 -0
  8. package/reference/sdlc/code.md +55 -97
  9. package/reference/sdlc/code.playbook/code.fsm.d.ts +229 -94
  10. package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +26 -44
  11. package/reference/sdlc/code.playbook/code.fsm.introspect.js +61 -66
  12. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +100 -149
  13. package/reference/sdlc/code.playbook/code.fsm.js +587 -1347
  14. package/reference/sdlc/code.playbook/code.fsm.ts +809 -1650
  15. package/reference/sdlc/code.playbook/code.gears.md +51 -263
  16. package/reference/sdlc/code.playbook/code.playbook.d.ts +8 -47
  17. package/reference/sdlc/code.playbook/code.playbook.js +69 -656
  18. package/reference/sdlc/code.playbook/code.playbook.ts +90 -867
  19. package/reference/sdlc/code.playbook/code.registry.d.ts +9 -25
  20. package/reference/sdlc/code.playbook/code.registry.js +20 -78
  21. package/reference/sdlc/code.playbook/code.registry.ts +58 -122
  22. package/reference/sdlc/code.playbook/playbook-captain.js +93 -15
  23. package/reference/sdlc/code.playbook/playbook-captain.ts +115 -19
  24. package/reference/sdlc/code.playbook/playbook.config.template.yaml +33 -22
  25. package/reference/sdlc/decide.md +54 -0
  26. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +261 -0
  27. package/reference/sdlc/decide.playbook/decide.fsm.js +894 -0
  28. package/reference/sdlc/decide.playbook/decide.fsm.ts +1152 -0
  29. package/reference/sdlc/decide.playbook/decide.gears.md +88 -0
  30. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +67 -0
  31. package/reference/sdlc/{discuss.playbook/discuss.playbook.js → decide.playbook/decide.playbook.js} +471 -362
  32. package/reference/sdlc/{discuss.playbook/discuss.playbook.ts → decide.playbook/decide.playbook.ts} +575 -443
  33. package/reference/sdlc/decide.playbook/decide.registry.d.ts +41 -0
  34. package/reference/sdlc/decide.playbook/decide.registry.js +60 -0
  35. package/reference/sdlc/decide.playbook/decide.registry.ts +125 -0
  36. package/reference/sdlc/review.md +81 -0
  37. package/reference/sdlc/review.playbook/review.fsm.d.ts +183 -0
  38. package/reference/sdlc/review.playbook/review.fsm.js +524 -0
  39. package/reference/sdlc/review.playbook/review.fsm.ts +652 -0
  40. package/reference/sdlc/review.playbook/review.gears.md +112 -0
  41. package/reference/sdlc/review.playbook/review.playbook.d.ts +12 -0
  42. package/reference/sdlc/review.playbook/review.playbook.js +112 -0
  43. package/reference/sdlc/review.playbook/review.playbook.ts +201 -0
  44. package/reference/sdlc/review.playbook/review.registry.d.ts +43 -0
  45. package/reference/sdlc/review.playbook/review.registry.js +73 -0
  46. package/reference/sdlc/review.playbook/review.registry.ts +138 -0
  47. package/slc/gears2fsm.md +13 -4
  48. package/slc/link.md +48 -2
  49. package/slc/text2gears.md +22 -2
  50. package/src/runtime.d.ts +7 -0
  51. package/src/runtime.ts +12 -0
  52. package/src/xstate-playbook-runtime.d.ts +9 -2
  53. package/src/xstate-playbook-runtime.js +255 -21
  54. package/src/xstate-playbook-runtime.ts +333 -28
  55. package/src/xstate-runtime.js +25 -0
  56. package/src/xstate-runtime.ts +51 -0
  57. package/reference/sdlc/discuss.md +0 -93
  58. package/reference/sdlc/discuss.playbook/discuss.fsm.d.ts +0 -396
  59. package/reference/sdlc/discuss.playbook/discuss.fsm.js +0 -2067
  60. package/reference/sdlc/discuss.playbook/discuss.fsm.ts +0 -2465
  61. package/reference/sdlc/discuss.playbook/discuss.gears.md +0 -258
  62. package/reference/sdlc/discuss.playbook/discuss.playbook.d.ts +0 -113
  63. package/reference/sdlc/discuss.playbook/discuss.registry.d.ts +0 -58
  64. package/reference/sdlc/discuss.playbook/discuss.registry.js +0 -97
  65. package/reference/sdlc/discuss.playbook/discuss.registry.ts +0 -153
@@ -1,77 +1,72 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
- export function enumerateCaptainStates(machine) {
4
- const states = getRawConfig(machine).states ?? {};
5
- const out = [];
6
- for (const [stateId, def] of Object.entries(states)) {
7
- const invoke = def.invoke;
8
- if (!invoke || invoke.src !== 'player' || typeof invoke.input !== 'function') {
9
- continue;
10
- }
11
- const inputFn = invoke.input;
12
- const getInput = (context) => inputFn({ context });
13
- const probe = getInput({});
14
- const transitions = toArmArray(invoke.onDone).map((arm, index) => ({
15
- index,
16
- target: stripIdPrefix(String(arm.target ?? '')),
17
- guard: arm.guard ?? alwaysTrue,
18
- actions: arm.actions,
19
- }));
20
- out.push({
21
- stateId,
22
- sourceItem: probe.sourceItem,
23
- getInput,
24
- transitions,
25
- });
26
- }
27
- return out;
3
+ function rawConfig(machine) {
4
+ return machine.config;
28
5
  }
29
- export function enumerateRootEvents(machine) {
30
- const cfg = getRawConfig(machine);
31
- const readyOn = (cfg.states?.ready?.on ?? {});
32
- const rootOn = (cfg.on ?? {});
33
- const bossInterruptTargets = toArmArray(rootOn.BOSS_INTERRUPT).map((arm) => stripIdPrefix(String(arm.target ?? '')));
34
- return {
35
- startCoding: { target: stripIdPrefix(String(readyOn.START_CODING?.target ?? '')) },
36
- continueIr: { target: stripIdPrefix(String(readyOn.CONTINUE_IR?.target ?? '')) },
37
- summarizeIr: { target: stripIdPrefix(String(readyOn.SUMMARIZE_IR?.target ?? '')) },
38
- bossInterruptTargets,
39
- bossInterruptTargetDescriptions: bossInterruptTargets.map((stateId) => {
40
- const description = cfg.states?.[stateId]?.description;
41
- return {
42
- stateId,
43
- description: typeof description === 'string' ? description : '',
44
- };
45
- }),
46
- };
6
+ function arms(value) {
7
+ if (value === undefined || value === null)
8
+ return [];
9
+ return (Array.isArray(value) ? value : [value]);
47
10
  }
48
- export function enumerateAwaitBossReply(machine) {
49
- const stateId = 'awaitBossReply';
50
- const awaitOn = getRawConfig(machine).states?.[stateId]?.on ?? {};
51
- const transitions = Object.entries(awaitOn).flatMap(([eventType, value]) => toArmArray(value).map((arm, index) => ({
52
- eventType,
53
- index,
54
- target: stripIdPrefix(String(arm.target ?? '')),
55
- guard: arm.guard ?? alwaysTrue,
56
- actions: arm.actions,
57
- })));
58
- const bossReplyTransitions = toArmArray(awaitOn.BOSS_REPLY).map((arm, index) => ({
11
+ function targetName(target) {
12
+ const value = String(target ?? '');
13
+ return value.startsWith('#') ? value.slice(1) : value;
14
+ }
15
+ function transitions(value) {
16
+ return arms(value).map((arm, index) => ({
59
17
  index,
60
- target: stripIdPrefix(String(arm.target ?? '')),
61
- guard: arm.guard ?? alwaysTrue,
18
+ target: targetName(arm.target),
19
+ ...(arm.guard === undefined ? {} : { guard: arm.guard }),
62
20
  actions: arm.actions,
63
21
  }));
64
- return { stateId, bossReplyTransitions, transitions };
65
22
  }
66
- function getRawConfig(machine) {
67
- return machine.config;
23
+ export function enumeratePlayerStates(machine) {
24
+ const states = rawConfig(machine).states ?? {};
25
+ return Object.entries(states).flatMap(([stateId, state]) => {
26
+ const invoke = state.invoke;
27
+ if (invoke?.src !== 'player' || invoke.input === undefined)
28
+ return [];
29
+ const getInput = (context) => invoke.input?.({ context });
30
+ const input = getInput({ coderPlayer: 'Coder', runResults: '' });
31
+ return [
32
+ {
33
+ stateId,
34
+ sourceItem: input.sourceItem,
35
+ getInput,
36
+ transitions: transitions(invoke.onDone),
37
+ },
38
+ ];
39
+ });
68
40
  }
69
- function toArmArray(value) {
70
- if (value === undefined || value === null)
71
- return [];
72
- return (Array.isArray(value) ? value : [value]);
41
+ // Backward-compatible internal name retained for repository conformance tools.
42
+ export const enumerateCaptainStates = enumeratePlayerStates;
43
+ export function enumerateNestedPlaybookStates(machine) {
44
+ const states = rawConfig(machine).states ?? {};
45
+ return Object.entries(states).flatMap(([stateId, state]) => {
46
+ const invoke = state.invoke;
47
+ if (invoke?.src !== 'playbook' || invoke.input === undefined)
48
+ return [];
49
+ const getInput = (context) => invoke.input?.({ context });
50
+ const input = getInput({ coderPlayer: 'Coder', runResults: '' });
51
+ return [
52
+ {
53
+ stateId,
54
+ sourceItem: input.sourceItem,
55
+ getInput,
56
+ transitions: transitions(invoke.onDone),
57
+ },
58
+ ];
59
+ });
73
60
  }
74
- function stripIdPrefix(target) {
75
- return target.startsWith('#') ? target.slice(1) : target;
61
+ export function enumerateAwaitBossReply(machine) {
62
+ const on = rawConfig(machine).states?.awaitBossReply?.on ?? {};
63
+ return {
64
+ stateId: 'awaitBossReply',
65
+ bossReplyTransitions: transitions(on.BOSS_REPLY),
66
+ };
67
+ }
68
+ export function enumerateRootEvents(machine) {
69
+ const on = rawConfig(machine).states?.ready?.on ?? {};
70
+ const start = arms(on.START_CODE)[0];
71
+ return { startCode: { target: targetName(start?.target) } };
76
72
  }
77
- const alwaysTrue = () => true;
@@ -1,196 +1,147 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
3
 
4
- // FSM introspection helpers — IR-005 Task 2.
5
- // Static walkers over `codingMachine.config` consumed by the
6
- // conformance, coverage, and prompt-contract tests landing in
7
- // Tasks 3–5. Reaching into `machine.config` is internal but stable
8
- // in xstate v5: it preserves the literal `createMachine` argument,
9
- // so `invoke.input` is still the original `({ context }) =>
10
- // PlayerInput` function and `invoke.onDone` is still the per-arm
11
- // array with `guard` / `target` / `actions` keys.
12
-
13
4
  import type {
14
5
  CodingContext,
6
+ PlaybookInput,
15
7
  PlayerInput,
16
8
  codingMachine,
17
9
  } from './code.fsm.js';
18
10
 
19
- export interface CaptainStateInfo {
20
- readonly stateId: string;
21
- readonly sourceItem: string;
22
- readonly getInput: (context: Partial<CodingContext>) => PlayerInput;
23
- readonly transitions: ReadonlyArray<CaptainTransition>;
24
- }
11
+ export type TransitionGuard = (args: {
12
+ context: CodingContext;
13
+ event: unknown;
14
+ }) => boolean;
25
15
 
26
- export interface CaptainTransition {
27
- // Position in the state's `invoke.onDone` array. Stable per FSM
28
- // source — the coverage test's fixture table keys
29
- // `(stateId, index)` to address each arm uniquely, since
30
- // `(stateId, target)` collides for arms that share a target
31
- // (e.g., `commitReviewerCleared` and `commitJoint` each route
32
- // both `committed && afterReview === 'done'` and
33
- // `noRelevantChanges` to `done`).
16
+ export interface InvokingTransition {
34
17
  readonly index: number;
35
18
  readonly target: string;
36
- readonly guard: TransitionGuard;
19
+ readonly guard?: TransitionGuard;
37
20
  readonly actions: unknown;
38
21
  }
39
22
 
40
- export interface AwaitBossReplyInfo {
23
+ export interface PlayerStateInfo {
41
24
  readonly stateId: string;
42
- readonly bossReplyTransitions: ReadonlyArray<BossReplyTransition>;
43
- readonly transitions: ReadonlyArray<AwaitBossReplyTransition>;
44
- }
45
-
46
- export interface BossReplyTransition {
47
- readonly index: number;
48
- readonly target: string;
49
- readonly guard: TransitionGuard;
50
- readonly actions: unknown;
25
+ readonly sourceItem: string;
26
+ readonly getInput: (context: CodingContext) => PlayerInput;
27
+ readonly transitions: readonly InvokingTransition[];
51
28
  }
52
29
 
53
- export interface AwaitBossReplyTransition {
54
- readonly eventType: string;
55
- readonly index: number;
56
- readonly target: string;
57
- readonly guard: TransitionGuard;
58
- readonly actions: unknown;
30
+ export interface NestedPlaybookStateInfo {
31
+ readonly stateId: string;
32
+ readonly sourceItem: string;
33
+ readonly getInput: (context: CodingContext) => PlaybookInput;
34
+ readonly transitions: readonly InvokingTransition[];
59
35
  }
60
36
 
61
- export type TransitionGuard = (args: {
62
- context: CodingContext;
63
- event: unknown;
64
- }) => boolean;
65
-
66
- export interface RootEventTable {
67
- readonly startCoding: { readonly target: string };
68
- readonly continueIr: { readonly target: string };
69
- readonly summarizeIr: { readonly target: string };
70
- readonly bossInterruptTargets: ReadonlyArray<string>;
71
- readonly bossInterruptTargetDescriptions: ReadonlyArray<{
72
- readonly stateId: string;
73
- readonly description: string;
74
- }>;
37
+ export interface AwaitBossReplyInfo {
38
+ readonly stateId: 'awaitBossReply';
39
+ readonly bossReplyTransitions: readonly InvokingTransition[];
75
40
  }
76
41
 
77
42
  type RawInvoke = {
78
43
  src?: unknown;
79
- input?: (args: { context: Partial<CodingContext> }) => PlayerInput;
44
+ input?: (args: { context: CodingContext }) => PlayerInput | PlaybookInput;
80
45
  onDone?: unknown;
81
46
  };
82
47
 
83
- type RawStateDef = {
84
- description?: unknown;
48
+ type RawState = {
85
49
  invoke?: RawInvoke;
86
- on?: Record<string, unknown>;
50
+ on?: Readonly<Record<string, unknown>>;
87
51
  };
88
52
 
89
- type RawArm = { target?: unknown; guard?: TransitionGuard };
90
- type RawArmWithActions = RawArm & { actions?: unknown };
53
+ type RawArm = {
54
+ target?: unknown;
55
+ guard?: TransitionGuard;
56
+ actions?: unknown;
57
+ };
91
58
 
92
59
  type RawConfig = {
93
- states?: Record<string, RawStateDef>;
94
- on?: Record<string, unknown>;
60
+ states?: Readonly<Record<string, RawState>>;
95
61
  };
96
62
 
97
- export function enumerateCaptainStates(
63
+ function rawConfig(machine: typeof codingMachine): RawConfig {
64
+ return (machine as unknown as { config: RawConfig }).config;
65
+ }
66
+
67
+ function arms(value: unknown): readonly RawArm[] {
68
+ if (value === undefined || value === null) return [];
69
+ return (Array.isArray(value) ? value : [value]) as readonly RawArm[];
70
+ }
71
+
72
+ function targetName(target: unknown): string {
73
+ const value = String(target ?? '');
74
+ return value.startsWith('#') ? value.slice(1) : value;
75
+ }
76
+
77
+ function transitions(value: unknown): readonly InvokingTransition[] {
78
+ return arms(value).map((arm, index) => ({
79
+ index,
80
+ target: targetName(arm.target),
81
+ ...(arm.guard === undefined ? {} : { guard: arm.guard }),
82
+ actions: arm.actions,
83
+ }));
84
+ }
85
+
86
+ export function enumeratePlayerStates(
98
87
  machine: typeof codingMachine,
99
- ): readonly CaptainStateInfo[] {
100
- const states = getRawConfig(machine).states ?? {};
101
- const out: CaptainStateInfo[] = [];
102
- for (const [stateId, def] of Object.entries(states)) {
103
- const invoke = def.invoke;
104
- if (!invoke || invoke.src !== 'player' || typeof invoke.input !== 'function') {
105
- continue;
106
- }
107
- const inputFn = invoke.input;
108
- const getInput = (context: Partial<CodingContext>): PlayerInput =>
109
- inputFn({ context });
110
- const probe = getInput({});
111
- const transitions = toArmArray(invoke.onDone).map(
112
- (arm, index): CaptainTransition => ({
113
- index,
114
- target: stripIdPrefix(String(arm.target ?? '')),
115
- guard: arm.guard ?? alwaysTrue,
116
- actions: arm.actions,
117
- }),
118
- );
119
- out.push({
120
- stateId,
121
- sourceItem: probe.sourceItem,
122
- getInput,
123
- transitions,
124
- });
125
- }
126
- return out;
88
+ ): readonly PlayerStateInfo[] {
89
+ const states = rawConfig(machine).states ?? {};
90
+ return Object.entries(states).flatMap(([stateId, state]) => {
91
+ const invoke = state.invoke;
92
+ if (invoke?.src !== 'player' || invoke.input === undefined) return [];
93
+ const getInput = (context: CodingContext): PlayerInput =>
94
+ invoke.input?.({ context }) as PlayerInput;
95
+ const input = getInput({ coderPlayer: 'Coder', runResults: '' });
96
+ return [
97
+ {
98
+ stateId,
99
+ sourceItem: input.sourceItem,
100
+ getInput,
101
+ transitions: transitions(invoke.onDone),
102
+ },
103
+ ];
104
+ });
127
105
  }
128
106
 
129
- export function enumerateRootEvents(
107
+ // Backward-compatible internal name retained for repository conformance tools.
108
+ export const enumerateCaptainStates = enumeratePlayerStates;
109
+
110
+ export function enumerateNestedPlaybookStates(
130
111
  machine: typeof codingMachine,
131
- ): RootEventTable {
132
- const cfg = getRawConfig(machine);
133
- const readyOn = (cfg.states?.ready?.on ?? {}) as Record<
134
- string,
135
- { target?: string }
136
- >;
137
- const rootOn = (cfg.on ?? {}) as Record<string, unknown>;
138
- const bossInterruptTargets = toArmArray(rootOn.BOSS_INTERRUPT).map((arm) =>
139
- stripIdPrefix(String(arm.target ?? '')),
140
- );
141
- return {
142
- startCoding: { target: stripIdPrefix(String(readyOn.START_CODING?.target ?? '')) },
143
- continueIr: { target: stripIdPrefix(String(readyOn.CONTINUE_IR?.target ?? '')) },
144
- summarizeIr: { target: stripIdPrefix(String(readyOn.SUMMARIZE_IR?.target ?? '')) },
145
- bossInterruptTargets,
146
- bossInterruptTargetDescriptions: bossInterruptTargets.map((stateId) => {
147
- const description = cfg.states?.[stateId]?.description;
148
- return {
112
+ ): readonly NestedPlaybookStateInfo[] {
113
+ const states = rawConfig(machine).states ?? {};
114
+ return Object.entries(states).flatMap(([stateId, state]) => {
115
+ const invoke = state.invoke;
116
+ if (invoke?.src !== 'playbook' || invoke.input === undefined) return [];
117
+ const getInput = (context: CodingContext): PlaybookInput =>
118
+ invoke.input?.({ context }) as PlaybookInput;
119
+ const input = getInput({ coderPlayer: 'Coder', runResults: '' });
120
+ return [
121
+ {
149
122
  stateId,
150
- description: typeof description === 'string' ? description : '',
151
- };
152
- }),
153
- };
123
+ sourceItem: input.sourceItem,
124
+ getInput,
125
+ transitions: transitions(invoke.onDone),
126
+ },
127
+ ];
128
+ });
154
129
  }
155
130
 
156
131
  export function enumerateAwaitBossReply(
157
132
  machine: typeof codingMachine,
158
133
  ): AwaitBossReplyInfo {
159
- const stateId = 'awaitBossReply';
160
- const awaitOn = getRawConfig(machine).states?.[stateId]?.on ?? {};
161
- const transitions = Object.entries(awaitOn).flatMap(([eventType, value]) =>
162
- toArmArray(value).map(
163
- (arm, index): AwaitBossReplyTransition => ({
164
- eventType,
165
- index,
166
- target: stripIdPrefix(String(arm.target ?? '')),
167
- guard: arm.guard ?? alwaysTrue,
168
- actions: arm.actions,
169
- }),
170
- ),
171
- );
172
- const bossReplyTransitions = toArmArray(awaitOn.BOSS_REPLY).map(
173
- (arm, index): BossReplyTransition => ({
174
- index,
175
- target: stripIdPrefix(String(arm.target ?? '')),
176
- guard: arm.guard ?? alwaysTrue,
177
- actions: arm.actions,
178
- }),
179
- );
180
- return { stateId, bossReplyTransitions, transitions };
181
- }
182
-
183
- function getRawConfig(machine: typeof codingMachine): RawConfig {
184
- return (machine as unknown as { config: RawConfig }).config;
185
- }
186
-
187
- function toArmArray(value: unknown): RawArmWithActions[] {
188
- if (value === undefined || value === null) return [];
189
- return (Array.isArray(value) ? value : [value]) as RawArmWithActions[];
134
+ const on = rawConfig(machine).states?.awaitBossReply?.on ?? {};
135
+ return {
136
+ stateId: 'awaitBossReply',
137
+ bossReplyTransitions: transitions(on.BOSS_REPLY),
138
+ };
190
139
  }
191
140
 
192
- function stripIdPrefix(target: string): string {
193
- return target.startsWith('#') ? target.slice(1) : target;
141
+ export function enumerateRootEvents(machine: typeof codingMachine): {
142
+ readonly startCode: { readonly target: string };
143
+ } {
144
+ const on = rawConfig(machine).states?.ready?.on ?? {};
145
+ const start = arms(on.START_CODE)[0];
146
+ return { startCode: { target: targetName(start?.target) } };
194
147
  }
195
-
196
- const alwaysTrue: TransitionGuard = () => true;