@sublang/playbook 10.0.0 → 11.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 +1 -1
- package/docs/cli.md +58 -13
- package/docs/configuration.md +89 -40
- package/docs/embedding.md +88 -0
- package/package.json +9 -2
- package/reference/sdlc/code.md +35 -15
- package/reference/sdlc/code.playbook/bin/interactive-session.js +58 -6
- package/reference/sdlc/code.playbook/bin/launch-config.js +499 -241
- package/reference/sdlc/code.playbook/bin/playbook.js +236 -187
- package/reference/sdlc/code.playbook/bin/replay-observer.js +221 -0
- package/reference/sdlc/code.playbook/bin/run.js +355 -203
- package/reference/sdlc/code.playbook/bin/session-store.js +1512 -136
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +4 -1
- package/reference/sdlc/code.playbook/playbook-captain.js +21 -3
- package/reference/sdlc/code.playbook/playbook-captain.ts +42 -6
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +14 -10
- package/reference/sdlc/code.playbook/session-store.d.ts +82 -0
- package/reference/sdlc/code.playbook/session-store.js +113 -0
- package/reference/sdlc/decide.md +24 -15
- package/reference/sdlc/review.md +36 -18
|
@@ -7,6 +7,9 @@ interface SessionAgent {
|
|
|
7
7
|
readonly adapter: string;
|
|
8
8
|
readonly model: TuningSelection;
|
|
9
9
|
readonly effort: TuningSelection<Effort>;
|
|
10
|
+
/** Adapter-scoped fast mode. Absence is the provider default; `false` is a
|
|
11
|
+
* literal request, so this carries no provider-default sentinel. */
|
|
12
|
+
readonly fastMode?: boolean;
|
|
10
13
|
readonly instruction?: string;
|
|
11
14
|
readonly permissions?: PermissionPolicy;
|
|
12
15
|
}
|
|
@@ -29,7 +32,7 @@ interface PlaybookCaptainUnresolvedEffectSettlementInput {
|
|
|
29
32
|
readonly rootPlaybookId: string;
|
|
30
33
|
readonly unresolvedEffects: readonly PlaybookCaptainUnresolvedEffect[];
|
|
31
34
|
}
|
|
32
|
-
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, 'model' | 'effort'>>;
|
|
35
|
+
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, 'model' | 'effort' | 'fastMode'>>;
|
|
33
36
|
type PlayerLedgerSnapshotEntry = DeepReadonly<PlayerLedgerEntry>;
|
|
34
37
|
export interface PlaybookCaptainDeps {
|
|
35
38
|
loadModule?: (specifier: string) => Promise<unknown>;
|
|
@@ -1379,10 +1379,16 @@ function snapshotEffortSelection(value, path) {
|
|
|
1379
1379
|
}
|
|
1380
1380
|
return selection;
|
|
1381
1381
|
}
|
|
1382
|
+
function snapshotFastMode(value, path) {
|
|
1383
|
+
if (value !== undefined && typeof value !== 'boolean') {
|
|
1384
|
+
throw new TypeError(`${path} must be a boolean`);
|
|
1385
|
+
}
|
|
1386
|
+
return value;
|
|
1387
|
+
}
|
|
1382
1388
|
function snapshotSessionAgent(value, path) {
|
|
1383
1389
|
const agent = snapshotRecord(value, path);
|
|
1384
|
-
rejectSnapshotKeys(agent, ['adapter', 'model', 'effort', 'instruction', 'permissions'], path);
|
|
1385
|
-
const fixed = snapshotFixedAgent(Object.fromEntries(Object.entries(agent).filter(([key]) => key !== 'model' && key !== 'effort')), path);
|
|
1390
|
+
rejectSnapshotKeys(agent, ['adapter', 'model', 'effort', 'fastMode', 'instruction', 'permissions'], path);
|
|
1391
|
+
const fixed = snapshotFixedAgent(Object.fromEntries(Object.entries(agent).filter(([key]) => key !== 'model' && key !== 'effort' && key !== 'fastMode')), path);
|
|
1386
1392
|
return {
|
|
1387
1393
|
adapter: fixed.adapter,
|
|
1388
1394
|
...(fixed.instruction === undefined
|
|
@@ -1393,6 +1399,9 @@ function snapshotSessionAgent(value, path) {
|
|
|
1393
1399
|
: { permissions: livePermissions(fixed.permissions) }),
|
|
1394
1400
|
model: snapshotTuningSelection(agent.model, `${path}.model`),
|
|
1395
1401
|
effort: snapshotEffortSelection(agent.effort, `${path}.effort`),
|
|
1402
|
+
...(agent.fastMode === undefined
|
|
1403
|
+
? {}
|
|
1404
|
+
: { fastMode: snapshotFastMode(agent.fastMode, `${path}.fastMode`) }),
|
|
1396
1405
|
};
|
|
1397
1406
|
}
|
|
1398
1407
|
function fixedAgent(agent) {
|
|
@@ -1403,9 +1412,13 @@ function fixedAgent(agent) {
|
|
|
1403
1412
|
};
|
|
1404
1413
|
}
|
|
1405
1414
|
function callSettings(agent, tuning = agent) {
|
|
1415
|
+
// cligent treats supplied call settings as a complete replacement, so an
|
|
1416
|
+
// omitted fastMode here is a request for the provider default, never an
|
|
1417
|
+
// inheritance of whatever the previous call left behind.
|
|
1406
1418
|
return {
|
|
1407
1419
|
model: tuning.model,
|
|
1408
1420
|
effort: tuning.effort,
|
|
1421
|
+
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
1409
1422
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
1410
1423
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
1411
1424
|
};
|
|
@@ -1533,7 +1546,7 @@ async function buildEnablements(options, loadModule, hostCapabilities) {
|
|
|
1533
1546
|
for (const role of entry.requiredRoleIds) {
|
|
1534
1547
|
const path = `captain.options.playbooks.${id}.roles.${role}`;
|
|
1535
1548
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
1536
|
-
rejectSnapshotKeys(rawBinding, ['playerId', 'model', 'effort'], path);
|
|
1549
|
+
rejectSnapshotKeys(rawBinding, ['playerId', 'model', 'effort', 'fastMode'], path);
|
|
1537
1550
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
1538
1551
|
if (!PLAYER_ID_PATTERN.test(playerId) || playerId === INTERNAL_CAPTAIN_ID) {
|
|
1539
1552
|
throw new Error(`${path}.playerId is not a canonical player id`);
|
|
@@ -1546,6 +1559,11 @@ async function buildEnablements(options, loadModule, hostCapabilities) {
|
|
|
1546
1559
|
playerId,
|
|
1547
1560
|
model: snapshotTuningSelection(rawBinding.model, `${path}.model`),
|
|
1548
1561
|
effort: snapshotEffortSelection(rawBinding.effort, `${path}.effort`),
|
|
1562
|
+
...(rawBinding.fastMode === undefined
|
|
1563
|
+
? {}
|
|
1564
|
+
: {
|
|
1565
|
+
fastMode: snapshotFastMode(rawBinding.fastMode, `${path}.fastMode`),
|
|
1566
|
+
}),
|
|
1549
1567
|
agent,
|
|
1550
1568
|
});
|
|
1551
1569
|
}
|
|
@@ -55,6 +55,9 @@ interface SessionAgent {
|
|
|
55
55
|
readonly adapter: string;
|
|
56
56
|
readonly model: TuningSelection;
|
|
57
57
|
readonly effort: TuningSelection<Effort>;
|
|
58
|
+
/** Adapter-scoped fast mode. Absence is the provider default; `false` is a
|
|
59
|
+
* literal request, so this carries no provider-default sentinel. */
|
|
60
|
+
readonly fastMode?: boolean;
|
|
58
61
|
readonly instruction?: string;
|
|
59
62
|
readonly permissions?: PermissionPolicy;
|
|
60
63
|
}
|
|
@@ -94,7 +97,7 @@ interface PlaybookCaptainUnresolvedEffectSettlementInput {
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
type SnapshotAgentEnvelope = DeepReadonly<
|
|
97
|
-
Omit<SessionAgent, 'model' | 'effort'>
|
|
100
|
+
Omit<SessionAgent, 'model' | 'effort' | 'fastMode'>
|
|
98
101
|
>;
|
|
99
102
|
|
|
100
103
|
type PlayerLedgerSnapshotEntry = DeepReadonly<PlayerLedgerEntry>;
|
|
@@ -377,6 +380,7 @@ interface EffectivePlayerBinding {
|
|
|
377
380
|
readonly playerId: string;
|
|
378
381
|
readonly model: TuningSelection;
|
|
379
382
|
readonly effort: TuningSelection<Effort>;
|
|
383
|
+
readonly fastMode?: boolean;
|
|
380
384
|
readonly agent: SessionAgent;
|
|
381
385
|
}
|
|
382
386
|
|
|
@@ -2562,6 +2566,16 @@ function snapshotEffortSelection(
|
|
|
2562
2566
|
return selection as TuningSelection<Effort>;
|
|
2563
2567
|
}
|
|
2564
2568
|
|
|
2569
|
+
function snapshotFastMode(
|
|
2570
|
+
value: JsonValue | undefined,
|
|
2571
|
+
path: string,
|
|
2572
|
+
): boolean | undefined {
|
|
2573
|
+
if (value !== undefined && typeof value !== 'boolean') {
|
|
2574
|
+
throw new TypeError(`${path} must be a boolean`);
|
|
2575
|
+
}
|
|
2576
|
+
return value;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2565
2579
|
function snapshotSessionAgent(
|
|
2566
2580
|
value: JsonValue | undefined,
|
|
2567
2581
|
path: string,
|
|
@@ -2569,13 +2583,14 @@ function snapshotSessionAgent(
|
|
|
2569
2583
|
const agent = snapshotRecord(value, path);
|
|
2570
2584
|
rejectSnapshotKeys(
|
|
2571
2585
|
agent,
|
|
2572
|
-
['adapter', 'model', 'effort', 'instruction', 'permissions'],
|
|
2586
|
+
['adapter', 'model', 'effort', 'fastMode', 'instruction', 'permissions'],
|
|
2573
2587
|
path,
|
|
2574
2588
|
);
|
|
2575
2589
|
const fixed = snapshotFixedAgent(
|
|
2576
2590
|
Object.fromEntries(
|
|
2577
2591
|
Object.entries(agent).filter(
|
|
2578
|
-
([key]) =>
|
|
2592
|
+
([key]) =>
|
|
2593
|
+
key !== 'model' && key !== 'effort' && key !== 'fastMode',
|
|
2579
2594
|
),
|
|
2580
2595
|
) as JsonValue,
|
|
2581
2596
|
path,
|
|
@@ -2590,10 +2605,15 @@ function snapshotSessionAgent(
|
|
|
2590
2605
|
: { permissions: livePermissions(fixed.permissions) }),
|
|
2591
2606
|
model: snapshotTuningSelection(agent.model, `${path}.model`),
|
|
2592
2607
|
effort: snapshotEffortSelection(agent.effort, `${path}.effort`),
|
|
2608
|
+
...(agent.fastMode === undefined
|
|
2609
|
+
? {}
|
|
2610
|
+
: { fastMode: snapshotFastMode(agent.fastMode, `${path}.fastMode`) }),
|
|
2593
2611
|
};
|
|
2594
2612
|
}
|
|
2595
2613
|
|
|
2596
|
-
function fixedAgent(
|
|
2614
|
+
function fixedAgent(
|
|
2615
|
+
agent: SessionAgent,
|
|
2616
|
+
): Omit<SessionAgent, 'model' | 'effort' | 'fastMode'> {
|
|
2597
2617
|
return {
|
|
2598
2618
|
adapter: agent.adapter,
|
|
2599
2619
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
@@ -2603,11 +2623,15 @@ function fixedAgent(agent: SessionAgent): Omit<SessionAgent, 'model' | 'effort'>
|
|
|
2603
2623
|
|
|
2604
2624
|
function callSettings(
|
|
2605
2625
|
agent: SessionAgent,
|
|
2606
|
-
tuning: Pick<SessionAgent, 'model' | 'effort'> = agent,
|
|
2626
|
+
tuning: Pick<SessionAgent, 'model' | 'effort' | 'fastMode'> = agent,
|
|
2607
2627
|
): AgentCallSettings {
|
|
2628
|
+
// cligent treats supplied call settings as a complete replacement, so an
|
|
2629
|
+
// omitted fastMode here is a request for the provider default, never an
|
|
2630
|
+
// inheritance of whatever the previous call left behind.
|
|
2608
2631
|
return {
|
|
2609
2632
|
model: tuning.model,
|
|
2610
2633
|
effort: tuning.effort,
|
|
2634
|
+
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
2611
2635
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
2612
2636
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
2613
2637
|
};
|
|
@@ -2833,7 +2857,11 @@ async function buildEnablements(
|
|
|
2833
2857
|
for (const role of entry.requiredRoleIds) {
|
|
2834
2858
|
const path = `captain.options.playbooks.${id}.roles.${role}`;
|
|
2835
2859
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
2836
|
-
rejectSnapshotKeys(
|
|
2860
|
+
rejectSnapshotKeys(
|
|
2861
|
+
rawBinding,
|
|
2862
|
+
['playerId', 'model', 'effort', 'fastMode'],
|
|
2863
|
+
path,
|
|
2864
|
+
);
|
|
2837
2865
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
2838
2866
|
if (!PLAYER_ID_PATTERN.test(playerId) || playerId === INTERNAL_CAPTAIN_ID) {
|
|
2839
2867
|
throw new Error(`${path}.playerId is not a canonical player id`);
|
|
@@ -2848,6 +2876,14 @@ async function buildEnablements(
|
|
|
2848
2876
|
playerId,
|
|
2849
2877
|
model: snapshotTuningSelection(rawBinding.model, `${path}.model`),
|
|
2850
2878
|
effort: snapshotEffortSelection(rawBinding.effort, `${path}.effort`),
|
|
2879
|
+
...(rawBinding.fastMode === undefined
|
|
2880
|
+
? {}
|
|
2881
|
+
: {
|
|
2882
|
+
fastMode: snapshotFastMode(
|
|
2883
|
+
rawBinding.fastMode,
|
|
2884
|
+
`${path}.fastMode`,
|
|
2885
|
+
),
|
|
2886
|
+
}),
|
|
2851
2887
|
agent,
|
|
2852
2888
|
});
|
|
2853
2889
|
}
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
# The Captain and each stable session player carries its own defaults inline:
|
|
10
10
|
# an adapter shorthand (claude, codex) or a block with
|
|
11
|
-
# adapter/model/effort/permissions. A role may override only model
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# adapter/model/effort/fastMode/permissions. A role may override only model,
|
|
12
|
+
# effort, or fastMode.
|
|
13
|
+
# Omit an override to inherit the player default. For model and effort, false
|
|
14
|
+
# selects the provider default; fastMode false is a literal disabled request.
|
|
14
15
|
|
|
15
16
|
# Each adapter needs its vendor SDK installed as its own top-level
|
|
16
17
|
# install root — they are optional peer dependencies, so you pay only
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
# list, so those captains degrade to a prompt-level restriction (DR-013 A1).
|
|
30
31
|
captain:
|
|
31
32
|
adapter: claude
|
|
32
|
-
model: claude-opus-
|
|
33
|
+
model: claude-opus-5
|
|
33
34
|
effort: high
|
|
34
35
|
permissions:
|
|
35
36
|
mode: auto
|
|
@@ -45,19 +46,22 @@ notifications:
|
|
|
45
46
|
# shares one provider conversation across those playbooks.
|
|
46
47
|
players:
|
|
47
48
|
dev.coder:
|
|
48
|
-
adapter:
|
|
49
|
-
model:
|
|
50
|
-
effort:
|
|
49
|
+
adapter: codex
|
|
50
|
+
model: gpt-5.6-sol
|
|
51
|
+
effort: ultra
|
|
52
|
+
# Adapter-scoped fast mode is validated by the installed Cligent contract;
|
|
53
|
+
# omitting it takes the provider default.
|
|
54
|
+
fastMode: true
|
|
51
55
|
permissions:
|
|
52
56
|
mode: auto
|
|
57
|
+
writablePaths: ['.git']
|
|
53
58
|
|
|
54
59
|
dev.reviewer:
|
|
55
|
-
adapter:
|
|
56
|
-
model:
|
|
60
|
+
adapter: claude
|
|
61
|
+
model: claude-opus-5
|
|
57
62
|
effort: xhigh
|
|
58
63
|
permissions:
|
|
59
64
|
mode: auto
|
|
60
|
-
writablePaths: ['.git']
|
|
61
65
|
|
|
62
66
|
# Enabled playbooks. Each is loaded from its explicit `from` module; the
|
|
63
67
|
# `<id>` key must equal that module's manifest id. `from`, `command`, and
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
|
|
4
|
+
export declare const RECORDS_STREAM_VERSION: 1;
|
|
5
|
+
export declare function defaultSessionsDir(): string;
|
|
6
|
+
export declare function openSessionStore(
|
|
7
|
+
sessionsDir: string,
|
|
8
|
+
): PlaybookSessionStore;
|
|
9
|
+
|
|
10
|
+
export type ReplayJsonValue =
|
|
11
|
+
| null
|
|
12
|
+
| boolean
|
|
13
|
+
| number
|
|
14
|
+
| string
|
|
15
|
+
| readonly ReplayJsonValue[]
|
|
16
|
+
| ReplayRecord;
|
|
17
|
+
export type ReplayRecord = {
|
|
18
|
+
readonly [key: string]: ReplayJsonValue;
|
|
19
|
+
};
|
|
20
|
+
export interface ReplayStreamEntry {
|
|
21
|
+
readonly v: 1;
|
|
22
|
+
readonly seq: number;
|
|
23
|
+
readonly role?: string;
|
|
24
|
+
readonly record: ReplayRecord;
|
|
25
|
+
}
|
|
26
|
+
export interface ReplayStreamReadOptions {
|
|
27
|
+
readonly afterSeq?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ReplayStreamReadResult {
|
|
30
|
+
readonly entries: readonly ReplayStreamEntry[];
|
|
31
|
+
readonly lastReadableSeq: number;
|
|
32
|
+
}
|
|
33
|
+
export interface LeaseReplayStreamReadResult extends ReplayStreamReadResult {
|
|
34
|
+
readonly lastDurableSeq: number;
|
|
35
|
+
readonly incomplete: boolean;
|
|
36
|
+
}
|
|
37
|
+
export type ReplayStreamStatus =
|
|
38
|
+
| {
|
|
39
|
+
readonly lastReadableSeq: number;
|
|
40
|
+
readonly lastDurableSeq: number;
|
|
41
|
+
readonly incomplete: boolean;
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
readonly lastReadableSeq: null;
|
|
45
|
+
readonly lastDurableSeq: null;
|
|
46
|
+
readonly incomplete: true;
|
|
47
|
+
};
|
|
48
|
+
export interface PlaybookSessionSummary {
|
|
49
|
+
readonly schemaVersion: number;
|
|
50
|
+
readonly sessionId: string;
|
|
51
|
+
readonly state: 'settled' | 'uncertain';
|
|
52
|
+
readonly cwd: string;
|
|
53
|
+
readonly updatedAt: string;
|
|
54
|
+
}
|
|
55
|
+
export interface SkippedPlaybookSession {
|
|
56
|
+
readonly sessionId: string;
|
|
57
|
+
readonly reason: string;
|
|
58
|
+
}
|
|
59
|
+
export interface PlaybookSessionListResult {
|
|
60
|
+
readonly sessions: readonly PlaybookSessionSummary[];
|
|
61
|
+
readonly skipped: readonly SkippedPlaybookSession[];
|
|
62
|
+
}
|
|
63
|
+
export interface PlaybookSessionStore {
|
|
64
|
+
readonly sessionsDir: string;
|
|
65
|
+
list(): Promise<PlaybookSessionListResult>;
|
|
66
|
+
read(sessionId: string): Promise<PlaybookSessionSummary>;
|
|
67
|
+
readStream(
|
|
68
|
+
sessionId: string,
|
|
69
|
+
options?: ReplayStreamReadOptions,
|
|
70
|
+
): Promise<ReplayStreamReadResult>;
|
|
71
|
+
acquire(sessionId: string): Promise<PlaybookSessionLease>;
|
|
72
|
+
}
|
|
73
|
+
export interface PlaybookSessionLease {
|
|
74
|
+
readonly sessionId: string;
|
|
75
|
+
readonly ownerToken: string;
|
|
76
|
+
append(record: object, role?: string): Promise<void>;
|
|
77
|
+
readStream(
|
|
78
|
+
options?: ReplayStreamReadOptions,
|
|
79
|
+
): Promise<LeaseReplayStreamReadResult>;
|
|
80
|
+
streamStatus(): ReplayStreamStatus;
|
|
81
|
+
release(): Promise<ReplayStreamStatus>;
|
|
82
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
RECORDS_STREAM_VERSION,
|
|
6
|
+
createCaptainSessionStore,
|
|
7
|
+
defaultCaptainSessionsDir,
|
|
8
|
+
} from './bin/session-store.js';
|
|
9
|
+
|
|
10
|
+
export { RECORDS_STREAM_VERSION };
|
|
11
|
+
|
|
12
|
+
export function defaultSessionsDir() {
|
|
13
|
+
return defaultCaptainSessionsDir();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function openSessionStore(sessionsDir) {
|
|
17
|
+
if (typeof sessionsDir !== 'string') {
|
|
18
|
+
throw new TypeError('session store path must be a string');
|
|
19
|
+
}
|
|
20
|
+
return wrapStore(createCaptainSessionStore({ sessionsDir }));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function wrapStore(store) {
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
sessionsDir: store.sessionsDir,
|
|
26
|
+
list: async () => projectListResult(await store.listSummaries()),
|
|
27
|
+
read: async (sessionId) =>
|
|
28
|
+
projectSummary(await store.readSummary(sessionId)),
|
|
29
|
+
readStream: async (sessionId, options) =>
|
|
30
|
+
projectReadResult(await store.readStream(sessionId, options)),
|
|
31
|
+
acquire: async (sessionId) => wrapLease(await store.acquire(sessionId)),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function wrapLease(lease) {
|
|
36
|
+
let finalStatus;
|
|
37
|
+
let releaseInFlight;
|
|
38
|
+
|
|
39
|
+
const release = () => {
|
|
40
|
+
if (finalStatus !== undefined) return Promise.resolve(finalStatus);
|
|
41
|
+
if (releaseInFlight !== undefined) return releaseInFlight;
|
|
42
|
+
const operation = lease.release();
|
|
43
|
+
releaseInFlight = operation.then(
|
|
44
|
+
(status) => {
|
|
45
|
+
finalStatus = projectStatus(status);
|
|
46
|
+
return finalStatus;
|
|
47
|
+
},
|
|
48
|
+
(cause) => {
|
|
49
|
+
releaseInFlight = undefined;
|
|
50
|
+
throw cause;
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
return releaseInFlight;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
sessionId: lease.sessionId,
|
|
58
|
+
ownerToken: lease.ownerToken,
|
|
59
|
+
append: (record, role) => lease.append(record, role),
|
|
60
|
+
readStream: async (options) => {
|
|
61
|
+
if (finalStatus !== undefined) {
|
|
62
|
+
throw new Error('released session lease has no live replay reader');
|
|
63
|
+
}
|
|
64
|
+
return projectLeaseReadResult(await lease.readStream(options));
|
|
65
|
+
},
|
|
66
|
+
streamStatus: () => finalStatus ?? projectStatus(lease.streamStatus()),
|
|
67
|
+
release,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function projectListResult(result) {
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
sessions: Object.freeze(result.sessions.map(projectSummary)),
|
|
74
|
+
skipped: Object.freeze(
|
|
75
|
+
result.skipped.map(({ sessionId, reason }) =>
|
|
76
|
+
Object.freeze({ sessionId, reason }),
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function projectSummary(summary) {
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
schemaVersion: summary.schemaVersion,
|
|
85
|
+
sessionId: summary.sessionId,
|
|
86
|
+
state: summary.state,
|
|
87
|
+
cwd: summary.cwd,
|
|
88
|
+
updatedAt: summary.updatedAt,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function projectReadResult(result) {
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
entries: Object.freeze([...result.entries]),
|
|
95
|
+
lastReadableSeq: result.lastReadableSeq,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function projectLeaseReadResult(result) {
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
...projectReadResult(result),
|
|
102
|
+
lastDurableSeq: result.lastDurableSeq,
|
|
103
|
+
incomplete: result.incomplete,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function projectStatus(status) {
|
|
108
|
+
return Object.freeze({
|
|
109
|
+
lastReadableSeq: status.lastReadableSeq,
|
|
110
|
+
lastDurableSeq: status.lastDurableSeq,
|
|
111
|
+
incomplete: status.incomplete,
|
|
112
|
+
});
|
|
113
|
+
}
|
package/reference/sdlc/decide.md
CHANGED
|
@@ -23,31 +23,40 @@ Consult @specs/map.md for relevant context and @specs/meta.md for spec requireme
|
|
|
23
23
|
Do not change any files.
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Neither role's player shall receive the other role's proposal until both proposals are complete
|
|
26
|
+
Neither role's player shall receive the other role's proposal until both proposals are complete.
|
|
27
|
+
Each proposal is complete only when its player affirmatively provides a complete design proposal.
|
|
28
|
+
A progress report, status update, or promise of a later proposal supports no proposal outcome.
|
|
27
29
|
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
30
|
|
|
29
|
-
When both proposals are complete, Captain shall
|
|
31
|
+
When both proposals are complete, Captain shall give Coder the following instruction, with the complete topic and Reviewer's complete proposal inserted under their own labels in quotes (`>`):
|
|
30
32
|
|
|
31
33
|
```markdown
|
|
32
|
-
|
|
34
|
+
Synthesize your independent proposal with Reviewer's proposal below.
|
|
35
|
+
Keep to the original topic below and follow what it asks.
|
|
36
|
+
Keep the best, essential parts of either proposal and reject any point that is unsound, unnecessary, or outside the topic.
|
|
37
|
+
Turn the resulting design into the necessary DRs and/or spec items.
|
|
33
38
|
Follow @specs/meta.md and update @specs/map.md when needed.
|
|
34
|
-
Do not
|
|
35
|
-
Do not change code or implement the proposal.
|
|
39
|
+
Do not change code or implement the design.
|
|
36
40
|
|
|
37
41
|
Commit the result as one new commit, following @specs/packages/git.md.
|
|
38
42
|
Make the commit message explain concisely what changed and why.
|
|
39
|
-
|
|
43
|
+
Identify every new commit you make.
|
|
44
|
+
Coder is <coder-llm> and Reviewer is <reviewer-llm>.
|
|
45
|
+
|
|
46
|
+
> Original topic: \<caller-topic\>
|
|
47
|
+
> Reviewer's independent proposal: \<reviewer-proposal\>
|
|
40
48
|
```
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
No transition shall depend on a fixed presentation format of either player's reply.
|
|
51
|
+
Captain shall use the repository-effect receipt as the authoritative identity of Coder's new `decide`-owned commit.
|
|
52
|
+
|
|
53
|
+
After Coder commits, Captain shall call playbook `review` with the following input in quotes (`>`):
|
|
43
54
|
|
|
44
|
-
>
|
|
45
|
-
>
|
|
46
|
-
>
|
|
47
|
-
>
|
|
48
|
-
> Initial intent: \<caller-topic\>.
|
|
49
|
-
> Coder's independent proposal: \<coder-proposal\>.
|
|
55
|
+
> Original intent: \<caller-topic\>
|
|
56
|
+
> Review scope: the `decide`-owned commit \<decide-commit\> and its resulting repository state.
|
|
57
|
+
> Coder output: \<coder-output\>
|
|
50
58
|
|
|
51
|
-
`decide` is complete when `review` returns the
|
|
52
|
-
|
|
59
|
+
`decide` is complete only when `review` returns a result that applies to the supplied review scope, gives the exact evaluated repository revision, and affirmatively establishes that no unsettled findings remain.
|
|
60
|
+
It then returns the `decide`-owned commit and that evaluated revision to its caller.
|
|
61
|
+
When `review` returns an authored abort or failure, or a terminal result that does not establish those facts, `decide` shall report the failure and the last `decide`-owned commit to its caller.
|
|
53
62
|
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.
|
package/reference/sdlc/review.md
CHANGED
|
@@ -9,45 +9,55 @@ Roles:
|
|
|
9
9
|
- Coder
|
|
10
10
|
- Reviewer
|
|
11
11
|
|
|
12
|
-
The caller supplies
|
|
12
|
+
The caller supplies:
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
- the original intent;
|
|
15
|
+
- the review scope in the caller's own words;
|
|
16
|
+
- optional relevant context and run results.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
The caller's review scope is the baseline for every round, and each review-fix commit joins that scope as it lands, so every later round reviews the cumulative committed state.
|
|
19
|
+
`review` examines committed work only.
|
|
20
|
+
Captain shall take the evaluated repository revision from repository authority, not from either player's prose.
|
|
18
21
|
|
|
19
22
|
At the first review round, Captain shall give Reviewer the following instruction:
|
|
20
23
|
|
|
21
24
|
```markdown
|
|
22
|
-
A new review begins
|
|
23
|
-
|
|
25
|
+
A new review begins for the review scope.
|
|
26
|
+
Keep to the original intent and follow what it asks.
|
|
27
|
+
When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.
|
|
24
28
|
```
|
|
25
29
|
|
|
26
30
|
After every review-fix commit, Captain shall give Reviewer the following instruction:
|
|
27
31
|
|
|
28
32
|
```markdown
|
|
29
|
-
|
|
33
|
+
A new review round begins for the review scope in the cumulative committed state, with particular attention to the latest review-fix commit.
|
|
34
|
+
Keep to the original intent and follow what it asks.
|
|
35
|
+
Read the latest review-fix commit's message and see Coder's feedback below.
|
|
30
36
|
```
|
|
31
37
|
|
|
32
38
|
When Coder rejects every finding and makes no commit, Captain shall give Reviewer the following instruction:
|
|
33
39
|
|
|
34
40
|
```markdown
|
|
35
41
|
No new commit was made because Coder rejected every finding.
|
|
42
|
+
See Coder's feedback below.
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
At the start of *every* review round, Captain shall relay to Reviewer any Coder feedback from the preceding round and any relevant run results, in quotes (`>`) after the instruction.
|
|
45
|
+
At the start of *every* review round, Captain shall relay to Reviewer the original intent, the review scope and context, the exact repository revision being evaluated, any Coder feedback from the preceding round, and any relevant run results, in quotes (`>`) after the instruction.
|
|
39
46
|
|
|
40
47
|
At the start of *every* review round, Captain shall append the following instruction to the end of the prompt:
|
|
41
48
|
|
|
42
49
|
```markdown
|
|
43
50
|
Understand the full picture and think systematically about the underlying design.
|
|
44
|
-
Continue to identify issues or improvements, if any
|
|
45
|
-
|
|
46
|
-
Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.
|
|
51
|
+
Continue to identify issues or improvements, if any, without duplication.
|
|
52
|
+
Number the findings consistently across rounds.
|
|
47
53
|
Flag only what materially affects correctness, behavior, or spec quality — not style, equally valid alternatives, or theoretical threats.
|
|
48
|
-
For specs, flag stale, missing, over-specified, or under-specified ones.
|
|
54
|
+
For specs, flag stale, missing, over-specified, or under-specified ones, if any.
|
|
49
55
|
Avoid unnecessary complexity in code or tests, but flag any fundamental design flaw when leaving it would cost more in later patches than fixing it now.
|
|
50
56
|
|
|
57
|
+
If an issue represents a class of defect, find every instance within the review scope worth fixing rather than surfacing one or two per round, which drags out the review.
|
|
58
|
+
For any rebuttal, accept or challenge it.
|
|
59
|
+
Treat as settled, and do not raise again, any finding in this review rejected twice with reasoning.
|
|
60
|
+
|
|
51
61
|
Do not re-run tests or builds whose inputs have not changed since any previous reported run.
|
|
52
62
|
Do not edit files or commit; report findings only.
|
|
53
63
|
|
|
@@ -55,26 +65,34 @@ Consult @specs/map.md for context if needed; verify it remains accurate.
|
|
|
55
65
|
Consult @specs/meta.md for spec requirements if needed; verify affected specs follow it.
|
|
56
66
|
```
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
Finding numbers are references within this review only.
|
|
69
|
+
No review transition shall depend on numbering or any fixed presentation format of either player's reply.
|
|
70
|
+
|
|
71
|
+
When Reviewer raises or keeps any finding, Captain shall relay the original intent, the review scope and context, the exact repository revision being evaluated, the Reviewer's findings, and any relevant run results to Coder, in quotes (`>`), along with the following prompt:
|
|
59
72
|
|
|
60
73
|
```markdown
|
|
61
74
|
For each review item, accept or reject it.
|
|
62
75
|
Before deciding, understand the full picture and think systematically about the underlying design.
|
|
76
|
+
Keep to the original intent and follow what it asks.
|
|
63
77
|
Reject anything that is not essential or is not worth fixing now.
|
|
64
|
-
If you accept an item, fix its root cause, including any fundamental design flaw
|
|
78
|
+
If you accept an item, fix its root cause, including any fundamental design flaw — do not patch around it; if it represents a class of defect, find every instance within the review scope worth fixing rather than addressing one or two per round, which drags out the review.
|
|
65
79
|
If you reject an item, give the reasoning and cite code or test output that supports it.
|
|
66
80
|
Do not re-run tests or builds whose inputs have not changed since any previous reported run.
|
|
67
81
|
|
|
68
|
-
If you accept any item, make minimal changes and
|
|
82
|
+
If you accept any item, make minimal changes and add one new review-fix commit; never rewrite any existing commit.
|
|
69
83
|
Follow @specs/packages/git.md.
|
|
70
84
|
Make the commit message explain concisely what changed and why, including relevant verification.
|
|
71
|
-
|
|
85
|
+
Identify every new commit you make.
|
|
86
|
+
Coder is <coder-llm>; Reviewer is <reviewer-llm>.
|
|
72
87
|
|
|
73
88
|
If you reject every item, change nothing and make no commit.
|
|
74
89
|
Report every disposition, all relevant run results, and every rebuttal.
|
|
75
90
|
```
|
|
76
91
|
|
|
92
|
+
Captain shall use the repository-effect receipt as the authoritative identity of any review-fix commit.
|
|
93
|
+
|
|
77
94
|
When Coder makes a new commit or decides to reject all findings, Captain shall relay the above required context to Reviewer and begin the next review round.
|
|
78
95
|
Reviewer shall read the context information and follow the corresponding instructions.
|
|
79
|
-
Rounds continue until Reviewer
|
|
80
|
-
|
|
96
|
+
Rounds continue until Reviewer affirmatively reports that the requested review is complete and no unsettled findings remain.
|
|
97
|
+
A progress report, status update, or promise of a later result supports no review outcome.
|
|
98
|
+
`review` then returns the exact repository revision at which the review scope was evaluated and the fact that no unsettled findings remain within that scope.
|