@sublang/playbook 0.8.0 → 1.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 (55) hide show
  1. package/README.md +243 -193
  2. package/package.json +52 -17
  3. package/reference/sdlc/captain.md +102 -0
  4. package/reference/sdlc/captain.playbook/captain.fsm.d.ts +227 -0
  5. package/reference/sdlc/captain.playbook/captain.fsm.js +628 -0
  6. package/reference/sdlc/captain.playbook/captain.fsm.ts +851 -0
  7. package/reference/sdlc/captain.playbook/captain.gears.md +60 -0
  8. package/reference/sdlc/captain.playbook/captain.playbook.d.ts +23 -0
  9. package/reference/sdlc/captain.playbook/captain.playbook.js +1053 -0
  10. package/reference/sdlc/captain.playbook/captain.playbook.ts +1144 -0
  11. package/reference/sdlc/code.playbook/bin/playbook.js +580 -0
  12. package/reference/sdlc/code.playbook/bin/run.js +893 -0
  13. package/reference/sdlc/code.playbook/code.fsm.d.ts +11 -4
  14. package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +2 -2
  15. package/reference/sdlc/code.playbook/code.fsm.introspect.js +1 -1
  16. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +6 -6
  17. package/reference/sdlc/code.playbook/code.fsm.js +334 -102
  18. package/reference/sdlc/code.playbook/code.fsm.ts +470 -182
  19. package/reference/sdlc/code.playbook/code.gears.md +11 -10
  20. package/reference/sdlc/code.playbook/code.playbook.d.ts +18 -9
  21. package/reference/sdlc/code.playbook/code.playbook.js +1098 -202
  22. package/reference/sdlc/code.playbook/code.playbook.ts +1440 -258
  23. package/reference/sdlc/code.playbook/code.registry.d.ts +17 -5
  24. package/reference/sdlc/code.playbook/code.registry.js +49 -34
  25. package/reference/sdlc/code.playbook/code.registry.ts +75 -41
  26. package/reference/sdlc/code.playbook/playbook-captain.d.ts +16 -8
  27. package/reference/sdlc/code.playbook/playbook-captain.js +1005 -240
  28. package/reference/sdlc/code.playbook/playbook-captain.ts +1310 -301
  29. package/reference/sdlc/code.playbook/playbook.config.template.yaml +68 -0
  30. package/reference/sdlc/discuss.playbook/discuss.fsm.d.ts +396 -0
  31. package/reference/sdlc/discuss.playbook/discuss.fsm.js +2066 -0
  32. package/reference/sdlc/discuss.playbook/discuss.fsm.ts +2464 -0
  33. package/reference/sdlc/discuss.playbook/discuss.gears.md +251 -0
  34. package/reference/sdlc/discuss.playbook/discuss.playbook.d.ts +113 -0
  35. package/reference/sdlc/discuss.playbook/discuss.playbook.js +1514 -0
  36. package/reference/sdlc/discuss.playbook/discuss.playbook.ts +1926 -0
  37. package/reference/sdlc/discuss.playbook/discuss.registry.d.ts +58 -0
  38. package/reference/sdlc/discuss.playbook/discuss.registry.js +97 -0
  39. package/reference/sdlc/discuss.playbook/discuss.registry.ts +153 -0
  40. package/slc/gears2fsm.md +557 -57
  41. package/slc/link.md +1097 -80
  42. package/slc/optimize.md +88 -0
  43. package/slc/text2gears.md +247 -5
  44. package/src/runtime.d.ts +145 -3
  45. package/src/runtime.ts +200 -2
  46. package/src/xstate-runtime.d.ts +94 -0
  47. package/src/xstate-runtime.js +1247 -0
  48. package/src/xstate-runtime.ts +1802 -0
  49. package/reference/sdlc/code.playbook/bin/playbook-code.js +0 -487
  50. package/reference/sdlc/code.playbook/code.tmux-play.d.ts +0 -4
  51. package/reference/sdlc/code.playbook/code.tmux-play.js +0 -11
  52. package/reference/sdlc/code.playbook/code.tmux-play.ts +0 -29
  53. package/reference/sdlc/code.playbook/playbook-code.config.template.yaml +0 -72
  54. package/reference/sdlc/code.playbook/tmux-play.config.yaml +0 -55
  55. package/reference/sdlc/code.playbook/tmux-play.production.config.yaml +0 -38
@@ -15,6 +15,19 @@ export declare const codeStateCountLabels: {
15
15
  readonly reviewChangesAndChallengesCode: "review round";
16
16
  readonly reviewChangesAndChallengesMixed: "review round";
17
17
  };
18
+ export declare function codeSavedCountsLine(counts: {
19
+ interruptions: number;
20
+ copyPastes: number;
21
+ }, rounds: number): string;
22
+ export interface PlaybookSummaryPolicy {
23
+ stateCountLabels: Readonly<Record<string, string>>;
24
+ copyPasteGuardNames: readonly string[];
25
+ savedCountsLine(counts: {
26
+ interruptions: number;
27
+ copyPastes: number;
28
+ }, rounds: number): string;
29
+ }
30
+ export declare const codeSummaryPolicy: PlaybookSummaryPolicy;
18
31
  export interface CodeOptions {
19
32
  committer?: 'coder' | 'reviewer';
20
33
  }
@@ -31,13 +44,12 @@ export interface CodePlaybookRegistryEntry {
31
44
  id: 'code';
32
45
  command: 'code';
33
46
  intent: string;
34
- idleStateId: 'ready';
35
- finalStateId: 'done';
36
- copyPasteGuardNames: readonly string[];
37
- stateCountLabels: typeof codeStateCountLabels;
47
+ requiredRoleIds: readonly string[];
48
+ summaryPolicy: PlaybookSummaryPolicy;
38
49
  validateOptions(captainOptions: unknown): CodeOptions;
39
50
  createRuntime(options: CreateCodeRuntimeOptions): PlaybookRuntime;
40
51
  }
41
- export declare function validateCodeOptions(captainOptions: unknown): CodeOptions;
52
+ export declare function validateCodeOptions(optionSlice: unknown): CodeOptions;
42
53
  export declare function createCodeRuntimeOptions({ captainOptions, players, }: CreateCodeRuntimeOptions): CodePlaybookOptions;
43
54
  export declare const codePlaybookRegistryEntry: CodePlaybookRegistryEntry;
55
+ export default codePlaybookRegistryEntry;
@@ -1,19 +1,15 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
3
  import createPlaybookRuntime from './code.playbook.js';
4
- // PBRT-29/30: CODE runtime options are carried under
5
- // `captain.options.code`, a namespaced object the host forwards
6
- // verbatim through `captain.options`. cligent neither reads nor
7
- // validates `options.code`; the CODE registry entry is the sole
8
- // validator. The CODE options schema defines one key, `committer`: an
9
- // optional Committer-alias player id, one of the baked player ids
10
- // `coder` / `reviewer`. A valid `options.code` is absent, `{}`, or
11
- // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown
12
- // and rejected with a path-named error, and an out-of-range
13
- // `committer` value is rejected naming `captain.options.code.committer`.
14
- // A further CODE option shall be introduced as its own higher-numbered
15
- // item that widens `CODE_OPTION_KEYS`; the validator still fails closed
16
- // on stray keys.
4
+ // PBRT-30: the CODE registry entry validates the option slice the shell
5
+ // passes it (`captain.options.playbooks.code.options`), not a namespace
6
+ // it extracts from the full Captain options bag. The schema defines one
7
+ // key, `committer`: an optional Committer-alias player id, one of the
8
+ // baked role ids `coder` / `reviewer`. A valid slice is absent, `{}`, or
9
+ // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown and
10
+ // rejected with a path-named error. A further CODE option shall be
11
+ // introduced as its own higher-numbered item that widens
12
+ // `CODE_OPTION_KEYS`; the validator still fails closed on stray keys.
17
13
  const CODE_OPTION_KEYS = new Set(['committer']);
18
14
  const COMMITTER_PLAYER_IDS = new Set(['coder', 'reviewer']);
19
15
  export const codeCopyPasteGuardNames = [
@@ -48,36 +44,53 @@ export const codeStateCountLabels = {
48
44
  reviewChangesAndChallengesCode: 'review round',
49
45
  reviewChangesAndChallengesMixed: 'review round',
50
46
  };
51
- export function validateCodeOptions(captainOptions) {
52
- const code = readCodeNamespace(captainOptions);
53
- if (code === undefined)
47
+ function countNoun(count, singular, plural = `${singular}s`) {
48
+ return `${count} ${count === 1 ? singular : plural}`;
49
+ }
50
+ // PBRT-15 / CAPTAIN-19: the CODE saved-counts line wording is
51
+ // registry-owned. The shell renders this exact line through the active
52
+ // entry's summary policy rather than hardcoding CODE phrasing.
53
+ export function codeSavedCountsLine(counts, rounds) {
54
+ return [
55
+ 'Saved you',
56
+ countNoun(counts.interruptions, 'interruption'),
57
+ 'and',
58
+ countNoun(counts.copyPastes, 'copy-paste'),
59
+ 'across',
60
+ countNoun(rounds, 'round'),
61
+ 'of reviews/rebuttals.',
62
+ ].join(' ');
63
+ }
64
+ export const codeSummaryPolicy = {
65
+ stateCountLabels: codeStateCountLabels,
66
+ copyPasteGuardNames: codeCopyPasteGuardNames,
67
+ savedCountsLine: codeSavedCountsLine,
68
+ };
69
+ export function validateCodeOptions(optionSlice) {
70
+ if (optionSlice === undefined)
54
71
  return {};
55
- if (typeof code !== 'object' || code === null || Array.isArray(code)) {
56
- throw new Error('captain.options.code must be an object');
72
+ if (typeof optionSlice !== 'object' ||
73
+ optionSlice === null ||
74
+ Array.isArray(optionSlice)) {
75
+ throw new Error('captain.options.playbooks.code.options must be an object');
57
76
  }
58
- for (const key of Object.keys(code)) {
77
+ const slice = optionSlice;
78
+ for (const key of Object.keys(slice)) {
59
79
  if (!CODE_OPTION_KEYS.has(key)) {
60
- throw new Error(`Unknown config field captain.options.code.${key}`);
80
+ throw new Error(`Unknown config field captain.options.playbooks.code.options.${key}`);
61
81
  }
62
82
  }
63
83
  const options = {};
64
- const committer = code.committer;
84
+ const committer = slice.committer;
65
85
  if (committer !== undefined) {
66
86
  if (typeof committer !== 'string' || !COMMITTER_PLAYER_IDS.has(committer)) {
67
- throw new Error("captain.options.code.committer must be 'coder' or 'reviewer'");
87
+ throw new Error("captain.options.playbooks.code.options.committer must be " +
88
+ "'coder' or 'reviewer'");
68
89
  }
69
90
  options.committer = committer;
70
91
  }
71
92
  return options;
72
93
  }
73
- function readCodeNamespace(captainOptions) {
74
- if (typeof captainOptions !== 'object' ||
75
- captainOptions === null ||
76
- Array.isArray(captainOptions)) {
77
- return undefined;
78
- }
79
- return captainOptions.code;
80
- }
81
94
  function playerIdentity(players, id) {
82
95
  const entry = players.find((p) => p.id === id);
83
96
  return entry?.model ?? entry?.adapter;
@@ -98,12 +111,14 @@ export const codePlaybookRegistryEntry = {
98
111
  id: 'code',
99
112
  command: 'code',
100
113
  intent: 'software development / SDLC coding workflow',
101
- idleStateId: 'ready',
102
- finalStateId: 'done',
103
- copyPasteGuardNames: codeCopyPasteGuardNames,
104
- stateCountLabels: codeStateCountLabels,
114
+ requiredRoleIds: ['coder', 'reviewer'],
115
+ summaryPolicy: codeSummaryPolicy,
105
116
  validateOptions: validateCodeOptions,
106
117
  createRuntime(options) {
107
118
  return createPlaybookRuntime(createCodeRuntimeOptions(options));
108
119
  },
109
120
  };
121
+ // CAPTAIN-16 / PBRT-16: the published `@sublang/playbook/code/registry`
122
+ // module's default export is the CODE registry entry the Playbook Captain
123
+ // shell loads when a playbook block's `from` names this module.
124
+ export default codePlaybookRegistryEntry;
@@ -6,19 +6,15 @@ import createPlaybookRuntime, {
6
6
  type PlaybookRuntime,
7
7
  } from './code.playbook.js';
8
8
 
9
- // PBRT-29/30: CODE runtime options are carried under
10
- // `captain.options.code`, a namespaced object the host forwards
11
- // verbatim through `captain.options`. cligent neither reads nor
12
- // validates `options.code`; the CODE registry entry is the sole
13
- // validator. The CODE options schema defines one key, `committer`: an
14
- // optional Committer-alias player id, one of the baked player ids
15
- // `coder` / `reviewer`. A valid `options.code` is absent, `{}`, or
16
- // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown
17
- // and rejected with a path-named error, and an out-of-range
18
- // `committer` value is rejected naming `captain.options.code.committer`.
19
- // A further CODE option shall be introduced as its own higher-numbered
20
- // item that widens `CODE_OPTION_KEYS`; the validator still fails closed
21
- // on stray keys.
9
+ // PBRT-30: the CODE registry entry validates the option slice the shell
10
+ // passes it (`captain.options.playbooks.code.options`), not a namespace
11
+ // it extracts from the full Captain options bag. The schema defines one
12
+ // key, `committer`: an optional Committer-alias player id, one of the
13
+ // baked role ids `coder` / `reviewer`. A valid slice is absent, `{}`, or
14
+ // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown and
15
+ // rejected with a path-named error. A further CODE option shall be
16
+ // introduced as its own higher-numbered item that widens
17
+ // `CODE_OPTION_KEYS`; the validator still fails closed on stray keys.
22
18
  const CODE_OPTION_KEYS = new Set<string>(['committer']);
23
19
  const COMMITTER_PLAYER_IDS = new Set<string>(['coder', 'reviewer']);
24
20
  export const codeCopyPasteGuardNames = [
@@ -55,6 +51,47 @@ export const codeStateCountLabels = {
55
51
  reviewChangesAndChallengesMixed: 'review round',
56
52
  } as const;
57
53
 
54
+ function countNoun(count: number, singular: string, plural = `${singular}s`): string {
55
+ return `${count} ${count === 1 ? singular : plural}`;
56
+ }
57
+
58
+ // PBRT-15 / CAPTAIN-19: the CODE saved-counts line wording is
59
+ // registry-owned. The shell renders this exact line through the active
60
+ // entry's summary policy rather than hardcoding CODE phrasing.
61
+ export function codeSavedCountsLine(
62
+ counts: { interruptions: number; copyPastes: number },
63
+ rounds: number,
64
+ ): string {
65
+ return [
66
+ 'Saved you',
67
+ countNoun(counts.interruptions, 'interruption'),
68
+ 'and',
69
+ countNoun(counts.copyPastes, 'copy-paste'),
70
+ 'across',
71
+ countNoun(rounds, 'round'),
72
+ 'of reviews/rebuttals.',
73
+ ].join(' ');
74
+ }
75
+
76
+ // CAPTAIN-20 summary policy: the counted state-id labels, the
77
+ // copy-paste guard names, and the saved-counts line wording an entry
78
+ // owns for the shell's turn-summary aggregation. An entry without a
79
+ // summary policy opts out of visible turn summaries.
80
+ export interface PlaybookSummaryPolicy {
81
+ stateCountLabels: Readonly<Record<string, string>>;
82
+ copyPasteGuardNames: readonly string[];
83
+ savedCountsLine(
84
+ counts: { interruptions: number; copyPastes: number },
85
+ rounds: number,
86
+ ): string;
87
+ }
88
+
89
+ export const codeSummaryPolicy: PlaybookSummaryPolicy = {
90
+ stateCountLabels: codeStateCountLabels,
91
+ copyPasteGuardNames: codeCopyPasteGuardNames,
92
+ savedCountsLine: codeSavedCountsLine,
93
+ };
94
+
58
95
  // The validated CODE options set. `committer`, when present, is the
59
96
  // resolved Committer-alias player id (PBRT-8 / PBRT-30); a future CODE
60
97
  // option widens both this type and `CODE_OPTION_KEYS`.
@@ -77,31 +114,36 @@ export interface CodePlaybookRegistryEntry {
77
114
  id: 'code';
78
115
  command: 'code';
79
116
  intent: string;
80
- idleStateId: 'ready';
81
- finalStateId: 'done';
82
- copyPasteGuardNames: readonly string[];
83
- stateCountLabels: typeof codeStateCountLabels;
117
+ requiredRoleIds: readonly string[];
118
+ summaryPolicy: PlaybookSummaryPolicy;
84
119
  validateOptions(captainOptions: unknown): CodeOptions;
85
120
  createRuntime(options: CreateCodeRuntimeOptions): PlaybookRuntime;
86
121
  }
87
122
 
88
- export function validateCodeOptions(captainOptions: unknown): CodeOptions {
89
- const code = readCodeNamespace(captainOptions);
90
- if (code === undefined) return {};
91
- if (typeof code !== 'object' || code === null || Array.isArray(code)) {
92
- throw new Error('captain.options.code must be an object');
123
+ export function validateCodeOptions(optionSlice: unknown): CodeOptions {
124
+ if (optionSlice === undefined) return {};
125
+ if (
126
+ typeof optionSlice !== 'object' ||
127
+ optionSlice === null ||
128
+ Array.isArray(optionSlice)
129
+ ) {
130
+ throw new Error('captain.options.playbooks.code.options must be an object');
93
131
  }
94
- for (const key of Object.keys(code)) {
132
+ const slice = optionSlice as Record<string, unknown>;
133
+ for (const key of Object.keys(slice)) {
95
134
  if (!CODE_OPTION_KEYS.has(key)) {
96
- throw new Error(`Unknown config field captain.options.code.${key}`);
135
+ throw new Error(
136
+ `Unknown config field captain.options.playbooks.code.options.${key}`,
137
+ );
97
138
  }
98
139
  }
99
140
  const options: CodeOptions = {};
100
- const committer = (code as Record<string, unknown>).committer;
141
+ const committer = slice.committer;
101
142
  if (committer !== undefined) {
102
143
  if (typeof committer !== 'string' || !COMMITTER_PLAYER_IDS.has(committer)) {
103
144
  throw new Error(
104
- "captain.options.code.committer must be 'coder' or 'reviewer'",
145
+ "captain.options.playbooks.code.options.committer must be " +
146
+ "'coder' or 'reviewer'",
105
147
  );
106
148
  }
107
149
  options.committer = committer as 'coder' | 'reviewer';
@@ -109,17 +151,6 @@ export function validateCodeOptions(captainOptions: unknown): CodeOptions {
109
151
  return options;
110
152
  }
111
153
 
112
- function readCodeNamespace(captainOptions: unknown): unknown {
113
- if (
114
- typeof captainOptions !== 'object' ||
115
- captainOptions === null ||
116
- Array.isArray(captainOptions)
117
- ) {
118
- return undefined;
119
- }
120
- return (captainOptions as Record<string, unknown>).code;
121
- }
122
-
123
154
  function playerIdentity(
124
155
  players: readonly RegistryPlayer[],
125
156
  id: string,
@@ -148,12 +179,15 @@ export const codePlaybookRegistryEntry: CodePlaybookRegistryEntry = {
148
179
  id: 'code',
149
180
  command: 'code',
150
181
  intent: 'software development / SDLC coding workflow',
151
- idleStateId: 'ready',
152
- finalStateId: 'done',
153
- copyPasteGuardNames: codeCopyPasteGuardNames,
154
- stateCountLabels: codeStateCountLabels,
182
+ requiredRoleIds: ['coder', 'reviewer'],
183
+ summaryPolicy: codeSummaryPolicy,
155
184
  validateOptions: validateCodeOptions,
156
185
  createRuntime(options) {
157
186
  return createPlaybookRuntime(createCodeRuntimeOptions(options));
158
187
  },
159
188
  };
189
+
190
+ // CAPTAIN-16 / PBRT-16: the published `@sublang/playbook/code/registry`
191
+ // module's default export is the CODE registry entry the Playbook Captain
192
+ // shell loads when a playbook block's `from` names this module.
193
+ export default codePlaybookRegistryEntry;
@@ -1,21 +1,29 @@
1
1
  import type { Captain } from '@sublang/cligent/tmux-play';
2
- import type { PlaybookRuntime } from './code.playbook.js';
3
- import { type RegistryPlayer } from './code.registry.js';
2
+ import type { PlaybookRuntime } from '@sublang/playbook/runtime';
3
+ import type { PlaybookSummaryPolicy, RegistryPlayer } from './code.registry.js';
4
4
  export interface CreatePlaybookRuntimeOptions {
5
5
  captainOptions: unknown;
6
6
  players: readonly RegistryPlayer[];
7
7
  }
8
+ export interface PlaybookCaptainDeps {
9
+ loadModule?: (specifier: string) => Promise<unknown>;
10
+ createSessionId?: () => string;
11
+ createCaptainRuntime?: (options: {
12
+ readonly enabledPlaybooks: readonly {
13
+ readonly id: string;
14
+ readonly command: string;
15
+ readonly intent: string;
16
+ }[];
17
+ }) => PlaybookRuntime;
18
+ }
8
19
  export interface PlaybookCaptainRegistryEntry {
9
20
  id: string;
10
21
  command: string;
11
22
  intent: string;
12
- idleStateId: string;
13
- finalStateId: string;
14
- copyPasteGuardNames: readonly string[];
15
- stateCountLabels?: Readonly<Record<string, string>>;
23
+ requiredRoleIds: readonly string[];
24
+ summaryPolicy?: PlaybookSummaryPolicy;
16
25
  validateOptions(captainOptions: unknown): unknown;
17
26
  createRuntime(options: CreatePlaybookRuntimeOptions): PlaybookRuntime;
18
27
  }
19
- export declare const playbookCaptainRegistry: readonly PlaybookCaptainRegistryEntry[];
20
- export declare function createPlaybookCaptainShell(options: unknown, registry?: readonly PlaybookCaptainRegistryEntry[]): Captain;
28
+ export declare function createPlaybookCaptainShell(options: unknown, deps?: PlaybookCaptainDeps): Captain;
21
29
  export default createPlaybookCaptainShell;