@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.
- package/README.md +11 -7
- package/docs/cli.md +38 -35
- package/docs/configuration.md +58 -15
- package/docs/embedding.md +24 -16
- package/package.json +40 -21
- package/reference/sdlc/captain.playbook/captain.playbook.js +2 -0
- package/reference/sdlc/captain.playbook/captain.playbook.ts +2 -0
- package/reference/sdlc/code.md +55 -97
- package/reference/sdlc/code.playbook/code.fsm.d.ts +229 -94
- package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +26 -44
- package/reference/sdlc/code.playbook/code.fsm.introspect.js +61 -66
- package/reference/sdlc/code.playbook/code.fsm.introspect.ts +100 -149
- package/reference/sdlc/code.playbook/code.fsm.js +587 -1347
- package/reference/sdlc/code.playbook/code.fsm.ts +809 -1650
- package/reference/sdlc/code.playbook/code.gears.md +51 -263
- package/reference/sdlc/code.playbook/code.playbook.d.ts +8 -47
- package/reference/sdlc/code.playbook/code.playbook.js +69 -656
- package/reference/sdlc/code.playbook/code.playbook.ts +90 -867
- package/reference/sdlc/code.playbook/code.registry.d.ts +9 -25
- package/reference/sdlc/code.playbook/code.registry.js +20 -78
- package/reference/sdlc/code.playbook/code.registry.ts +58 -122
- package/reference/sdlc/code.playbook/playbook-captain.js +93 -15
- package/reference/sdlc/code.playbook/playbook-captain.ts +115 -19
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +33 -22
- package/reference/sdlc/decide.md +54 -0
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +261 -0
- package/reference/sdlc/decide.playbook/decide.fsm.js +894 -0
- package/reference/sdlc/decide.playbook/decide.fsm.ts +1152 -0
- package/reference/sdlc/decide.playbook/decide.gears.md +88 -0
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +67 -0
- package/reference/sdlc/{discuss.playbook/discuss.playbook.js → decide.playbook/decide.playbook.js} +471 -362
- package/reference/sdlc/{discuss.playbook/discuss.playbook.ts → decide.playbook/decide.playbook.ts} +575 -443
- package/reference/sdlc/decide.playbook/decide.registry.d.ts +41 -0
- package/reference/sdlc/decide.playbook/decide.registry.js +60 -0
- package/reference/sdlc/decide.playbook/decide.registry.ts +125 -0
- package/reference/sdlc/review.md +81 -0
- package/reference/sdlc/review.playbook/review.fsm.d.ts +183 -0
- package/reference/sdlc/review.playbook/review.fsm.js +524 -0
- package/reference/sdlc/review.playbook/review.fsm.ts +652 -0
- package/reference/sdlc/review.playbook/review.gears.md +112 -0
- package/reference/sdlc/review.playbook/review.playbook.d.ts +12 -0
- package/reference/sdlc/review.playbook/review.playbook.js +112 -0
- package/reference/sdlc/review.playbook/review.playbook.ts +201 -0
- package/reference/sdlc/review.playbook/review.registry.d.ts +43 -0
- package/reference/sdlc/review.playbook/review.registry.js +73 -0
- package/reference/sdlc/review.playbook/review.registry.ts +138 -0
- package/slc/gears2fsm.md +13 -4
- package/slc/link.md +48 -2
- package/slc/text2gears.md +22 -2
- package/src/runtime.d.ts +7 -0
- package/src/runtime.ts +12 -0
- package/src/xstate-playbook-runtime.d.ts +9 -2
- package/src/xstate-playbook-runtime.js +255 -21
- package/src/xstate-playbook-runtime.ts +333 -28
- package/src/xstate-runtime.js +25 -0
- package/src/xstate-runtime.ts +51 -0
- package/reference/sdlc/discuss.md +0 -93
- package/reference/sdlc/discuss.playbook/discuss.fsm.d.ts +0 -396
- package/reference/sdlc/discuss.playbook/discuss.fsm.js +0 -2067
- package/reference/sdlc/discuss.playbook/discuss.fsm.ts +0 -2465
- package/reference/sdlc/discuss.playbook/discuss.gears.md +0 -258
- package/reference/sdlc/discuss.playbook/discuss.playbook.d.ts +0 -113
- package/reference/sdlc/discuss.playbook/discuss.registry.d.ts +0 -58
- package/reference/sdlc/discuss.playbook/discuss.registry.js +0 -97
- package/reference/sdlc/discuss.playbook/discuss.registry.ts +0 -153
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
PlaybookPorts,
|
|
21
21
|
PlaybookRunResult,
|
|
22
22
|
PlaybookRuntime,
|
|
23
|
+
PlayerSessionStore,
|
|
23
24
|
PlaybookState,
|
|
24
25
|
} from '@sublang/playbook/runtime';
|
|
25
26
|
import {
|
|
@@ -71,7 +72,11 @@ interface Enablement {
|
|
|
71
72
|
optionInput: unknown;
|
|
72
73
|
boundPlayers: readonly RegistryPlayer[];
|
|
73
74
|
hostPlayerId: (localRole: string) => string;
|
|
74
|
-
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface EffectivePlayerBinding {
|
|
78
|
+
readonly hostPlayerId: string;
|
|
79
|
+
readonly player: RegistryPlayer;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
interface EngagementFrame {
|
|
@@ -81,6 +86,8 @@ interface EngagementFrame {
|
|
|
81
86
|
sessionId: string;
|
|
82
87
|
rootSessionId: string;
|
|
83
88
|
depth: number;
|
|
89
|
+
playerBindings: ReadonlyMap<string, EffectivePlayerBinding>;
|
|
90
|
+
playerResumeTokens: Map<string, string>;
|
|
84
91
|
parent?: {
|
|
85
92
|
frame: EngagementFrame;
|
|
86
93
|
callId: string;
|
|
@@ -874,9 +881,6 @@ async function buildEnablements(
|
|
|
874
881
|
optionInput: record.options,
|
|
875
882
|
boundPlayers,
|
|
876
883
|
hostPlayerId: (localRole) => `${entry.id}-${localRole}`,
|
|
877
|
-
visiblePlayerIds: entry.requiredRoleIds.map(
|
|
878
|
-
(role) => `${entry.id}-${role}`,
|
|
879
|
-
),
|
|
880
884
|
});
|
|
881
885
|
}
|
|
882
886
|
return { entries, byCommand, byId, enablementById };
|
|
@@ -956,6 +960,29 @@ export function createPlaybookCaptainShell(
|
|
|
956
960
|
const frameLabel = (frame: EngagementFrame): string =>
|
|
957
961
|
`/${frame.enablement.command}`;
|
|
958
962
|
|
|
963
|
+
const bindingFor = (
|
|
964
|
+
frame: EngagementFrame,
|
|
965
|
+
localRole: string,
|
|
966
|
+
): EffectivePlayerBinding => {
|
|
967
|
+
const binding = frame.playerBindings.get(localRole);
|
|
968
|
+
if (!binding) {
|
|
969
|
+
throw new Error(
|
|
970
|
+
`${frameLabel(frame)} resolved undeclared player role ${JSON.stringify(localRole)}`,
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
return binding;
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
const summaryIncludes = (frame: EngagementFrame): boolean => {
|
|
977
|
+
const owner = activeTurnSummary?.owner;
|
|
978
|
+
if (!owner) return false;
|
|
979
|
+
for (let current: EngagementFrame | undefined = frame; current; ) {
|
|
980
|
+
if (current === owner) return true;
|
|
981
|
+
current = current.parent?.frame;
|
|
982
|
+
}
|
|
983
|
+
return false;
|
|
984
|
+
};
|
|
985
|
+
|
|
959
986
|
const requireSession = (): CaptainSession => {
|
|
960
987
|
if (!session) {
|
|
961
988
|
throw new Error('init must be called first');
|
|
@@ -1170,7 +1197,8 @@ export function createPlaybookCaptainShell(
|
|
|
1170
1197
|
const previousActiveIds = new Set(frame.state?.activeStateIds ?? []);
|
|
1171
1198
|
frame.state = state;
|
|
1172
1199
|
|
|
1173
|
-
|
|
1200
|
+
const summary = activeTurnSummary;
|
|
1201
|
+
if (summary && summaryIncludes(frame)) {
|
|
1174
1202
|
for (const stateId of state.activeStateIds) {
|
|
1175
1203
|
const newlyActive = !previousActiveIds.has(stateId);
|
|
1176
1204
|
const structuredEntry =
|
|
@@ -1179,9 +1207,9 @@ export function createPlaybookCaptainShell(
|
|
|
1179
1207
|
if (!newlyActive && !structuredEntry) continue;
|
|
1180
1208
|
const countLabel = stateCountLabel(stateId, frame.entry);
|
|
1181
1209
|
if (countLabel) {
|
|
1182
|
-
|
|
1210
|
+
summary.stateCounts.set(
|
|
1183
1211
|
countLabel,
|
|
1184
|
-
(
|
|
1212
|
+
(summary.stateCounts.get(countLabel) ?? 0) + 1,
|
|
1185
1213
|
);
|
|
1186
1214
|
}
|
|
1187
1215
|
}
|
|
@@ -1213,7 +1241,7 @@ export function createPlaybookCaptainShell(
|
|
|
1213
1241
|
}
|
|
1214
1242
|
const context = activeContext;
|
|
1215
1243
|
signal.throwIfAborted();
|
|
1216
|
-
const hostPlayerId = frame.
|
|
1244
|
+
const hostPlayerId = bindingFor(frame, playerId).hostPlayerId;
|
|
1217
1245
|
const result = await trackHostCall(
|
|
1218
1246
|
frame,
|
|
1219
1247
|
context.callPlayer(hostPlayerId, prompt, {
|
|
@@ -1228,8 +1256,9 @@ export function createPlaybookCaptainShell(
|
|
|
1228
1256
|
// CAPTAIN-20: only a player call that actually produced work is an
|
|
1229
1257
|
// interruption the Boss was spared. A call that errored or aborted
|
|
1230
1258
|
// saved nothing, so it never feeds the saved-counts gate.
|
|
1231
|
-
|
|
1232
|
-
|
|
1259
|
+
const summary = activeTurnSummary;
|
|
1260
|
+
if (summary && summaryIncludes(frame) && result.status === 'ok') {
|
|
1261
|
+
summary.counts.interruptions++;
|
|
1233
1262
|
}
|
|
1234
1263
|
return {
|
|
1235
1264
|
status: result.status,
|
|
@@ -1289,12 +1318,14 @@ export function createPlaybookCaptainShell(
|
|
|
1289
1318
|
throw new Error('callCaptain returned status=ok with no finalText');
|
|
1290
1319
|
}
|
|
1291
1320
|
const guard = guardFromJudgeReply(result.finalText);
|
|
1321
|
+
const summary = activeTurnSummary;
|
|
1292
1322
|
if (
|
|
1293
1323
|
guard &&
|
|
1294
|
-
|
|
1324
|
+
summary &&
|
|
1325
|
+
summaryIncludes(frame) &&
|
|
1295
1326
|
frame.entry.summaryPolicy?.copyPasteGuardNames.includes(guard)
|
|
1296
1327
|
) {
|
|
1297
|
-
|
|
1328
|
+
summary.counts.copyPastes++;
|
|
1298
1329
|
}
|
|
1299
1330
|
return result.finalText;
|
|
1300
1331
|
},
|
|
@@ -1329,8 +1360,10 @@ export function createPlaybookCaptainShell(
|
|
|
1329
1360
|
// visibility for that playbook's generated host players. A pane
|
|
1330
1361
|
// reconciliation failure is display-only in tmux-play and does not
|
|
1331
1362
|
// reject; the legacy path carries no generated set and skips this.
|
|
1332
|
-
const requestVisibility = async (
|
|
1333
|
-
const ids =
|
|
1363
|
+
const requestVisibility = async (frame: EngagementFrame): Promise<void> => {
|
|
1364
|
+
const ids = [...new Set(
|
|
1365
|
+
[...frame.playerBindings.values()].map(({ hostPlayerId }) => hostPlayerId),
|
|
1366
|
+
)];
|
|
1334
1367
|
if (!ids || ids.length === 0 || !activeContext) return;
|
|
1335
1368
|
try {
|
|
1336
1369
|
await activeContext.setVisiblePlayers(ids);
|
|
@@ -1375,9 +1408,37 @@ export function createPlaybookCaptainShell(
|
|
|
1375
1408
|
): EngagementFrame => {
|
|
1376
1409
|
const entry = enablement.entry;
|
|
1377
1410
|
const sessionId = allocateSessionId();
|
|
1411
|
+
const playerBindings = new Map<string, EffectivePlayerBinding>();
|
|
1412
|
+
for (const role of entry.requiredRoleIds) {
|
|
1413
|
+
let inherited: EffectivePlayerBinding | undefined;
|
|
1414
|
+
for (
|
|
1415
|
+
let ancestor = parent?.frame;
|
|
1416
|
+
ancestor && inherited === undefined;
|
|
1417
|
+
ancestor = ancestor.parent?.frame
|
|
1418
|
+
) {
|
|
1419
|
+
inherited = ancestor.playerBindings.get(role);
|
|
1420
|
+
}
|
|
1421
|
+
if (inherited) {
|
|
1422
|
+
playerBindings.set(role, inherited);
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
const configured = enablement.boundPlayers.find(
|
|
1426
|
+
(player) => player.id === role,
|
|
1427
|
+
) ?? { id: role };
|
|
1428
|
+
playerBindings.set(role, {
|
|
1429
|
+
hostPlayerId: enablement.hostPlayerId(role),
|
|
1430
|
+
player: configured,
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
const playerResumeTokens =
|
|
1434
|
+
parent?.frame.playerResumeTokens ?? new Map<string, string>();
|
|
1378
1435
|
const runtime = entry.createRuntime({
|
|
1379
1436
|
captainOptions: enablement.optionInput,
|
|
1380
|
-
players:
|
|
1437
|
+
players: [...playerBindings].map(([role, { player }]) => ({
|
|
1438
|
+
id: role,
|
|
1439
|
+
...(player.adapter === undefined ? {} : { adapter: player.adapter }),
|
|
1440
|
+
...(player.model === undefined ? {} : { model: player.model }),
|
|
1441
|
+
})),
|
|
1381
1442
|
});
|
|
1382
1443
|
return {
|
|
1383
1444
|
entry,
|
|
@@ -1386,11 +1447,45 @@ export function createPlaybookCaptainShell(
|
|
|
1386
1447
|
sessionId,
|
|
1387
1448
|
rootSessionId: parent?.frame.rootSessionId ?? sessionId,
|
|
1388
1449
|
depth: parent ? parent.frame.depth + 1 : 0,
|
|
1450
|
+
playerBindings,
|
|
1451
|
+
playerResumeTokens,
|
|
1389
1452
|
...(parent ? { parent } : {}),
|
|
1390
1453
|
inFlightHostCalls: new Set(),
|
|
1391
1454
|
};
|
|
1392
1455
|
};
|
|
1393
1456
|
|
|
1457
|
+
const playerSessionStore = (frame: EngagementFrame): PlayerSessionStore => ({
|
|
1458
|
+
select(playerId) {
|
|
1459
|
+
const binding = bindingFor(frame, playerId);
|
|
1460
|
+
return frame.playerResumeTokens.get(binding.hostPlayerId) ?? false;
|
|
1461
|
+
},
|
|
1462
|
+
update(playerId, resumeToken) {
|
|
1463
|
+
const binding = bindingFor(frame, playerId);
|
|
1464
|
+
if (resumeToken === undefined) {
|
|
1465
|
+
frame.playerResumeTokens.delete(binding.hostPlayerId);
|
|
1466
|
+
} else {
|
|
1467
|
+
frame.playerResumeTokens.set(binding.hostPlayerId, resumeToken);
|
|
1468
|
+
}
|
|
1469
|
+
},
|
|
1470
|
+
snapshot() {
|
|
1471
|
+
const tokens: Record<string, string> = {};
|
|
1472
|
+
for (const [playerId, binding] of frame.playerBindings) {
|
|
1473
|
+
const token = frame.playerResumeTokens.get(binding.hostPlayerId);
|
|
1474
|
+
if (token !== undefined) tokens[playerId] = token;
|
|
1475
|
+
}
|
|
1476
|
+
return tokens;
|
|
1477
|
+
},
|
|
1478
|
+
restore(tokens) {
|
|
1479
|
+
for (const binding of frame.playerBindings.values()) {
|
|
1480
|
+
frame.playerResumeTokens.delete(binding.hostPlayerId);
|
|
1481
|
+
}
|
|
1482
|
+
for (const [playerId, token] of Object.entries(tokens)) {
|
|
1483
|
+
const binding = bindingFor(frame, playerId);
|
|
1484
|
+
frame.playerResumeTokens.set(binding.hostPlayerId, token);
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1394
1489
|
const initFrame = async (frame: EngagementFrame): Promise<void> => {
|
|
1395
1490
|
await frame.runtime.init({
|
|
1396
1491
|
sessionId: frame.sessionId,
|
|
@@ -1403,6 +1498,7 @@ export function createPlaybookCaptainShell(
|
|
|
1403
1498
|
}
|
|
1404
1499
|
: {}),
|
|
1405
1500
|
depth: frame.depth,
|
|
1501
|
+
playerSessions: playerSessionStore(frame),
|
|
1406
1502
|
ports: createPorts(frame),
|
|
1407
1503
|
});
|
|
1408
1504
|
};
|
|
@@ -1599,7 +1695,7 @@ export function createPlaybookCaptainShell(
|
|
|
1599
1695
|
}
|
|
1600
1696
|
let visibilityError: unknown;
|
|
1601
1697
|
try {
|
|
1602
|
-
await requestVisibility(parent
|
|
1698
|
+
await requestVisibility(parent);
|
|
1603
1699
|
} catch (error) {
|
|
1604
1700
|
visibilityError = error;
|
|
1605
1701
|
}
|
|
@@ -1720,7 +1816,7 @@ export function createPlaybookCaptainShell(
|
|
|
1720
1816
|
// boundary — a `setVisiblePlayers` or telemetry rejection here leaves the
|
|
1721
1817
|
// runtime uninvoked and owes the Boss the CAPTAIN-34 reply rather than an
|
|
1722
1818
|
// exception filed against an effect that never ran.
|
|
1723
|
-
await requestVisibility(frame
|
|
1819
|
+
await requestVisibility(frame);
|
|
1724
1820
|
await setMode('engaged.driving', 'submit');
|
|
1725
1821
|
const result = await runEffect(() =>
|
|
1726
1822
|
frame.runtime.handleBossInput({ text, signal }),
|
|
@@ -1867,7 +1963,7 @@ export function createPlaybookCaptainShell(
|
|
|
1867
1963
|
cleanupError = error;
|
|
1868
1964
|
}
|
|
1869
1965
|
if (frames.includes(parent)) {
|
|
1870
|
-
await requestVisibility(parent
|
|
1966
|
+
await requestVisibility(parent);
|
|
1871
1967
|
}
|
|
1872
1968
|
if (cleanupError !== undefined) throw cleanupError;
|
|
1873
1969
|
};
|
|
@@ -2005,7 +2101,7 @@ export function createPlaybookCaptainShell(
|
|
|
2005
2101
|
}
|
|
2006
2102
|
if (frames.includes(parent)) {
|
|
2007
2103
|
try {
|
|
2008
|
-
await requestVisibility(parent
|
|
2104
|
+
await requestVisibility(parent);
|
|
2009
2105
|
} catch (visibilityError) {
|
|
2010
2106
|
visibilityControlFailure = true;
|
|
2011
2107
|
boundaryError = new AggregateError(
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
# Every seeded agent runs in cligent's protected auto mode
|
|
21
21
|
# (permissions.mode: auto): claude maps it to permissionMode auto, codex to
|
|
22
22
|
# on-request + auto_review. Codex roles also grant writablePaths: ['.git']
|
|
23
|
-
# so
|
|
23
|
+
# so they can inspect and work with repository metadata under the sandbox.
|
|
24
24
|
|
|
25
25
|
# The Captain/Judge agent.
|
|
26
26
|
# Captain routing/adjudication calls run tool-free. Claude enforces that at
|
|
@@ -40,11 +40,25 @@ notifications:
|
|
|
40
40
|
|
|
41
41
|
# Enabled playbooks. Each is loaded from its explicit `from` module; the
|
|
42
42
|
# `<id>` key must equal that module's manifest id. `from`, `command`, and
|
|
43
|
-
# `players` are launcher-owned; every other key
|
|
44
|
-
#
|
|
43
|
+
# `players` are launcher-owned; every other key is that playbook's option
|
|
44
|
+
# slice.
|
|
45
|
+
#
|
|
46
|
+
# Nested calls map roles by exact id. CODE and DECIDE call REVIEW with the
|
|
47
|
+
# same `coder` and `reviewer` ids, so those child roles continue the nearest
|
|
48
|
+
# ancestor sessions. A standalone REVIEW uses its own bindings below.
|
|
45
49
|
playbooks:
|
|
46
50
|
code:
|
|
47
51
|
from: "@sublang/playbook/code/registry"
|
|
52
|
+
players:
|
|
53
|
+
coder:
|
|
54
|
+
adapter: claude
|
|
55
|
+
model: claude-opus-4-8[1m]
|
|
56
|
+
effort: xhigh
|
|
57
|
+
permissions:
|
|
58
|
+
mode: auto
|
|
59
|
+
|
|
60
|
+
review:
|
|
61
|
+
from: "@sublang/playbook/review/registry"
|
|
48
62
|
players:
|
|
49
63
|
coder:
|
|
50
64
|
adapter: claude
|
|
@@ -59,26 +73,23 @@ playbooks:
|
|
|
59
73
|
permissions:
|
|
60
74
|
mode: auto
|
|
61
75
|
writablePaths: ['.git']
|
|
62
|
-
committer: coder
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
# writablePaths: ['.git']
|
|
81
|
-
# committer: host
|
|
77
|
+
decide:
|
|
78
|
+
from: "@sublang/playbook/decide/registry"
|
|
79
|
+
players:
|
|
80
|
+
coder:
|
|
81
|
+
adapter: claude
|
|
82
|
+
model: claude-opus-4-8[1m]
|
|
83
|
+
effort: xhigh
|
|
84
|
+
permissions:
|
|
85
|
+
mode: auto
|
|
86
|
+
reviewer:
|
|
87
|
+
adapter: codex
|
|
88
|
+
model: gpt-5.5
|
|
89
|
+
effort: xhigh
|
|
90
|
+
permissions:
|
|
91
|
+
mode: auto
|
|
92
|
+
writablePaths: ['.git']
|
|
82
93
|
|
|
83
94
|
# Non-interactive `playbook run` defaults (optional). Each value is an
|
|
84
95
|
# agent string <adapter>[:<model>][@<effort>]; `player` is the catch-all
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
2
|
+
<!-- SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai> -->
|
|
3
|
+
<!-- AI agents: do not edit this file without human approval. -->
|
|
4
|
+
|
|
5
|
+
# Decide
|
|
6
|
+
|
|
7
|
+
Players:
|
|
8
|
+
|
|
9
|
+
- Coder
|
|
10
|
+
- Reviewer
|
|
11
|
+
|
|
12
|
+
The caller supplies the topic including any specific context.
|
|
13
|
+
|
|
14
|
+
When the caller gives a topic, Captain shall relay the complete topic in quotes (`>`) to Coder and Reviewer concurrently and independently.
|
|
15
|
+
Captain shall not wait for either proposal before requesting the other.
|
|
16
|
+
Captain shall give each player the following instruction:
|
|
17
|
+
|
|
18
|
+
```markdown
|
|
19
|
+
Assess whether the topic is better expressed as a few spec items under @specs/packages/ or requires one or more DRs under @specs/decisions/.
|
|
20
|
+
Propose your design.
|
|
21
|
+
Keep your proposal coherent, focused, and concise.
|
|
22
|
+
Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.
|
|
23
|
+
Do not change any files.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Neither player shall receive the other's proposal until both proposals are complete and Coder has committed Coder's own proposal.
|
|
27
|
+
A Boss interrupt during the parallel proposal pair shall restart the whole pair so both players receive the same new topic and remain independent.
|
|
28
|
+
|
|
29
|
+
When both proposals are complete, Captain shall then give Coder the following instruction:
|
|
30
|
+
|
|
31
|
+
```markdown
|
|
32
|
+
Turn your proposal into the necessary spec items or DRs.
|
|
33
|
+
Follow @specs/meta.md and update @specs/map.md when needed.
|
|
34
|
+
Do not inspect or incorporate Reviewer's proposal before this commit.
|
|
35
|
+
Do not change code or implement the proposal.
|
|
36
|
+
|
|
37
|
+
Commit the result as one new commit, following @specs/packages/git.md.
|
|
38
|
+
Make the commit message explain concisely what changed and why.
|
|
39
|
+
Report it as exactly one final-response line beginning `Commit: `, followed only by the exact commit identity.
|
|
40
|
+
Coder is <coder-llm>; format the model token in conventional human form.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
After Coder commits, Captain shall call playbook `review`:
|
|
44
|
+
|
|
45
|
+
> Review the latest commit as a spec-design change against the initial intent.
|
|
46
|
+
> Compare it with your independent proposal and take the best of both.
|
|
47
|
+
> Make your suggestions.
|
|
48
|
+
>
|
|
49
|
+
> Initial intent: \<caller-topic\>.
|
|
50
|
+
> Coder's independent proposal: \<coder-proposal\>.
|
|
51
|
+
|
|
52
|
+
`decide` is complete when `review` returns the approved latest commit and the fact that no unsettled findings remain.
|
|
53
|
+
When `review` returns an authored abort or failure, or a terminal result that does not prove exact approval, `decide` shall report the failure and the last `decide`-owned commit to its caller.
|
|
54
|
+
When the nested `review` call fails outside that authored result contract, `decide` shall park as failed and retain the control-plane error instead of reporting an authored review outcome.
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
2
|
+
readonly [key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
type Player = 'Coder' | 'Reviewer';
|
|
5
|
+
type JumpableStateId = 'independentProposals';
|
|
6
|
+
type ResumableStateId = 'askCoderProposal' | 'askReviewerProposal' | 'commitCoderProposal';
|
|
7
|
+
export interface PendingBossQuestion {
|
|
8
|
+
questionId: ResumableStateId;
|
|
9
|
+
resumeStateId: ResumableStateId;
|
|
10
|
+
sourceItem: string;
|
|
11
|
+
player: Player;
|
|
12
|
+
question: string;
|
|
13
|
+
}
|
|
14
|
+
type PendingBossQuestions = Partial<Record<ResumableStateId, PendingBossQuestion>>;
|
|
15
|
+
type BossReplies = Partial<Record<ResumableStateId, string>>;
|
|
16
|
+
type PendingBossQuestionParams = Omit<PendingBossQuestion, 'questionId'>;
|
|
17
|
+
export interface ReviewSuccessOutput {
|
|
18
|
+
approvedCommit: 'latest';
|
|
19
|
+
noUnsettledFindings: true;
|
|
20
|
+
}
|
|
21
|
+
export interface DecideFailureOutput {
|
|
22
|
+
lastDecideCommit: string;
|
|
23
|
+
noUnsettledFindings: false;
|
|
24
|
+
reviewStatus: 'aborted' | 'error';
|
|
25
|
+
error?: {
|
|
26
|
+
name: string;
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export type DecideOutput = ReviewSuccessOutput | DecideFailureOutput;
|
|
31
|
+
interface ChildFailure {
|
|
32
|
+
status: 'aborted' | 'error';
|
|
33
|
+
playbookId: 'review';
|
|
34
|
+
error?: {
|
|
35
|
+
name: string;
|
|
36
|
+
message: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface DecideContext {
|
|
40
|
+
coderLlm: string;
|
|
41
|
+
callerTopic?: string;
|
|
42
|
+
coderProposal?: string;
|
|
43
|
+
reviewerProposal?: string;
|
|
44
|
+
latestCommit?: string;
|
|
45
|
+
reviewResult?: ReviewSuccessOutput;
|
|
46
|
+
reviewFailure?: ChildFailure;
|
|
47
|
+
lastResult?: PlayerOutput;
|
|
48
|
+
lastError?: unknown;
|
|
49
|
+
pendingBossQuestions?: PendingBossQuestions;
|
|
50
|
+
bossReplies?: BossReplies;
|
|
51
|
+
stagedCoderResult?: PlayerOutput;
|
|
52
|
+
stagedReviewerResult?: PlayerOutput;
|
|
53
|
+
}
|
|
54
|
+
export type DecideEvent = {
|
|
55
|
+
type: 'START_DECIDE';
|
|
56
|
+
callerTopic: string;
|
|
57
|
+
} | {
|
|
58
|
+
type: 'BOSS_INTERRUPT';
|
|
59
|
+
targetId: JumpableStateId;
|
|
60
|
+
bossIntent: string;
|
|
61
|
+
} | {
|
|
62
|
+
type: 'BOSS_REPLY';
|
|
63
|
+
questionId?: ResumableStateId;
|
|
64
|
+
answer: string;
|
|
65
|
+
};
|
|
66
|
+
export interface DecideInput {
|
|
67
|
+
coderLlm: string;
|
|
68
|
+
}
|
|
69
|
+
export interface PlayerInput {
|
|
70
|
+
stateId: ResumableStateId;
|
|
71
|
+
sourceItem: string;
|
|
72
|
+
prompt: string;
|
|
73
|
+
result: Record<string, string>;
|
|
74
|
+
player: Player;
|
|
75
|
+
callerTopic?: string;
|
|
76
|
+
coderLlm?: string;
|
|
77
|
+
pendingBossQuestion?: PendingBossQuestion;
|
|
78
|
+
bossReply?: string;
|
|
79
|
+
}
|
|
80
|
+
type AdditionalPlayerFields = {
|
|
81
|
+
coderProposal?: string;
|
|
82
|
+
reviewerProposal?: string;
|
|
83
|
+
coderOutput?: string;
|
|
84
|
+
latestCommit?: string;
|
|
85
|
+
question?: string;
|
|
86
|
+
readonly [key: string]: unknown;
|
|
87
|
+
};
|
|
88
|
+
export type PlayerOutput = ({
|
|
89
|
+
guard: 'proposed';
|
|
90
|
+
coderProposal: string;
|
|
91
|
+
} & AdditionalPlayerFields) | ({
|
|
92
|
+
guard: 'proposed';
|
|
93
|
+
reviewerProposal: string;
|
|
94
|
+
} & AdditionalPlayerFields) | ({
|
|
95
|
+
guard: 'committed';
|
|
96
|
+
coderOutput: string;
|
|
97
|
+
latestCommit: string;
|
|
98
|
+
} & AdditionalPlayerFields) | ({
|
|
99
|
+
guard: 'needsBossReply';
|
|
100
|
+
question: string;
|
|
101
|
+
} & AdditionalPlayerFields);
|
|
102
|
+
export interface PlaybookInput {
|
|
103
|
+
stateId: 'reviewCommit';
|
|
104
|
+
sourceItem: 'DECIDE-4';
|
|
105
|
+
playbookId: 'review';
|
|
106
|
+
text: string;
|
|
107
|
+
}
|
|
108
|
+
export declare const decideMachine: import("xstate").StateMachine<DecideContext, {
|
|
109
|
+
type: "START_DECIDE";
|
|
110
|
+
callerTopic: string;
|
|
111
|
+
} | {
|
|
112
|
+
type: "BOSS_INTERRUPT";
|
|
113
|
+
targetId: JumpableStateId;
|
|
114
|
+
bossIntent: string;
|
|
115
|
+
} | {
|
|
116
|
+
type: "BOSS_REPLY";
|
|
117
|
+
questionId?: ResumableStateId;
|
|
118
|
+
answer: string;
|
|
119
|
+
}, {
|
|
120
|
+
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<JsonValue | undefined, PlaybookInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<PlayerOutput, PlayerInput, import("xstate").EventObject>> | undefined;
|
|
121
|
+
}, {
|
|
122
|
+
src: "playbook";
|
|
123
|
+
logic: import("xstate").PromiseActorLogic<JsonValue | undefined, PlaybookInput, import("xstate").EventObject>;
|
|
124
|
+
id: string | undefined;
|
|
125
|
+
} | {
|
|
126
|
+
src: "player";
|
|
127
|
+
logic: import("xstate").PromiseActorLogic<PlayerOutput, PlayerInput, import("xstate").EventObject>;
|
|
128
|
+
id: string | undefined;
|
|
129
|
+
}, {
|
|
130
|
+
type: "rememberActorError";
|
|
131
|
+
params: import("xstate").NonReducibleUnknown;
|
|
132
|
+
} | {
|
|
133
|
+
type: "rememberBossReply";
|
|
134
|
+
params: import("xstate").NonReducibleUnknown;
|
|
135
|
+
} | {
|
|
136
|
+
type: "clearBossReplyContext";
|
|
137
|
+
params: import("xstate").NonReducibleUnknown;
|
|
138
|
+
} | {
|
|
139
|
+
type: "copyInterruptedTopic";
|
|
140
|
+
params: import("xstate").NonReducibleUnknown;
|
|
141
|
+
} | {
|
|
142
|
+
type: "copyStartTopic";
|
|
143
|
+
params: import("xstate").NonReducibleUnknown;
|
|
144
|
+
} | {
|
|
145
|
+
type: "stageCoderProposal";
|
|
146
|
+
params: import("xstate").NonReducibleUnknown;
|
|
147
|
+
} | {
|
|
148
|
+
type: "stageReviewerProposal";
|
|
149
|
+
params: import("xstate").NonReducibleUnknown;
|
|
150
|
+
} | {
|
|
151
|
+
type: "promoteProposals";
|
|
152
|
+
params: import("xstate").NonReducibleUnknown;
|
|
153
|
+
} | {
|
|
154
|
+
type: "rememberCommit";
|
|
155
|
+
params: import("xstate").NonReducibleUnknown;
|
|
156
|
+
} | {
|
|
157
|
+
type: "rememberReviewSuccess";
|
|
158
|
+
params: import("xstate").NonReducibleUnknown;
|
|
159
|
+
} | {
|
|
160
|
+
type: "rememberReviewFailure";
|
|
161
|
+
params: import("xstate").NonReducibleUnknown;
|
|
162
|
+
} | {
|
|
163
|
+
type: "rememberReviewProtocolFailure";
|
|
164
|
+
params: import("xstate").NonReducibleUnknown;
|
|
165
|
+
} | {
|
|
166
|
+
type: "rememberMalformedActorOutput";
|
|
167
|
+
params: import("xstate").NonReducibleUnknown;
|
|
168
|
+
} | {
|
|
169
|
+
type: "rememberMalformedBossReply";
|
|
170
|
+
params: import("xstate").NonReducibleUnknown;
|
|
171
|
+
} | {
|
|
172
|
+
type: "setPendingBossQuestion";
|
|
173
|
+
params: PendingBossQuestionParams;
|
|
174
|
+
} | {
|
|
175
|
+
type: "clearBranchBossReplyContext";
|
|
176
|
+
params: {
|
|
177
|
+
stateId: ResumableStateId;
|
|
178
|
+
};
|
|
179
|
+
} | {
|
|
180
|
+
type: "clearProposalRoundContext";
|
|
181
|
+
params: import("xstate").NonReducibleUnknown;
|
|
182
|
+
}, {
|
|
183
|
+
type: "authoredReviewFailure";
|
|
184
|
+
params: unknown;
|
|
185
|
+
} | {
|
|
186
|
+
type: "committed";
|
|
187
|
+
params: unknown;
|
|
188
|
+
} | {
|
|
189
|
+
type: "coderProposed";
|
|
190
|
+
params: unknown;
|
|
191
|
+
} | {
|
|
192
|
+
type: "reviewerProposed";
|
|
193
|
+
params: unknown;
|
|
194
|
+
} | {
|
|
195
|
+
type: "needsBossReplyWithQuestion";
|
|
196
|
+
params: unknown;
|
|
197
|
+
} | {
|
|
198
|
+
type: "needsBossReplyWithoutQuestion";
|
|
199
|
+
params: unknown;
|
|
200
|
+
} | {
|
|
201
|
+
type: "validReviewSuccess";
|
|
202
|
+
params: unknown;
|
|
203
|
+
}, never, "done" | "failed" | "awaitBossReply" | "ready" | "commitCoderProposal" | "reviewCommit" | "reportedReviewFailure" | {
|
|
204
|
+
independentProposals: {
|
|
205
|
+
coder: "complete" | "working" | "waiting";
|
|
206
|
+
reviewer: "complete" | "working" | "waiting";
|
|
207
|
+
};
|
|
208
|
+
}, string, DecideInput, ReviewSuccessOutput | DecideFailureOutput, import("xstate").EventObject, import("xstate").MetaObject, {
|
|
209
|
+
id: "decide";
|
|
210
|
+
states: {
|
|
211
|
+
readonly ready: {
|
|
212
|
+
id: "ready";
|
|
213
|
+
};
|
|
214
|
+
readonly independentProposals: {
|
|
215
|
+
id: "independentProposals";
|
|
216
|
+
states: {
|
|
217
|
+
readonly coder: {
|
|
218
|
+
states: {
|
|
219
|
+
readonly working: {
|
|
220
|
+
id: "askCoderProposal";
|
|
221
|
+
};
|
|
222
|
+
readonly waiting: {
|
|
223
|
+
id: "waitCoderProposalReply";
|
|
224
|
+
};
|
|
225
|
+
readonly complete: {};
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
readonly reviewer: {
|
|
229
|
+
states: {
|
|
230
|
+
readonly working: {
|
|
231
|
+
id: "askReviewerProposal";
|
|
232
|
+
};
|
|
233
|
+
readonly waiting: {
|
|
234
|
+
id: "waitReviewerProposalReply";
|
|
235
|
+
};
|
|
236
|
+
readonly complete: {};
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
readonly commitCoderProposal: {
|
|
242
|
+
id: "commitCoderProposal";
|
|
243
|
+
};
|
|
244
|
+
readonly awaitBossReply: {
|
|
245
|
+
id: "awaitBossReply";
|
|
246
|
+
};
|
|
247
|
+
readonly reviewCommit: {
|
|
248
|
+
id: "reviewCommit";
|
|
249
|
+
};
|
|
250
|
+
readonly failed: {
|
|
251
|
+
id: "failed";
|
|
252
|
+
};
|
|
253
|
+
readonly reportedReviewFailure: {
|
|
254
|
+
id: "reportedReviewFailure";
|
|
255
|
+
};
|
|
256
|
+
readonly done: {
|
|
257
|
+
id: "done";
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
}>;
|
|
261
|
+
export default decideMachine;
|