@sublang/playbook 13.3.0 → 14.1.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 (56) hide show
  1. package/README.md +5 -5
  2. package/docs/cli.md +14 -14
  3. package/docs/configuration.md +10 -2
  4. package/docs/embedding.md +20 -1
  5. package/package.json +6 -1
  6. package/reference/sdlc/branch.playbook/branch.fsm.d.ts +1 -1
  7. package/reference/sdlc/code.playbook/bin/launch-config.js +88 -5
  8. package/reference/sdlc/code.playbook/bin/playbook.js +4 -0
  9. package/reference/sdlc/code.playbook/bin/repository-effects.js +198 -17
  10. package/reference/sdlc/code.playbook/bin/run.js +4 -0
  11. package/reference/sdlc/code.playbook/bin/session-store.js +2 -2
  12. package/reference/sdlc/code.playbook/code.fsm.d.ts +1 -1
  13. package/reference/sdlc/code.playbook/host-capabilities.d.ts +6 -0
  14. package/reference/sdlc/code.playbook/playbook-captain.d.ts +3 -1
  15. package/reference/sdlc/code.playbook/playbook-captain.js +374 -20
  16. package/reference/sdlc/code.playbook/playbook-captain.ts +478 -38
  17. package/reference/sdlc/code.playbook/session-host.d.ts +6 -3
  18. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +1 -1
  19. package/reference/sdlc/decide.playbook/decide.playbook.js +25 -1
  20. package/reference/sdlc/decide.playbook/decide.playbook.ts +35 -2
  21. package/reference/sdlc/dev.md +15 -8
  22. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +1 -1
  23. package/reference/sdlc/dev.playbook/dev.fsm.js +18 -12
  24. package/reference/sdlc/dev.playbook/dev.fsm.ts +18 -13
  25. package/reference/sdlc/dev.playbook/dev.gears.md +19 -12
  26. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +3 -10
  27. package/reference/sdlc/dev.playbook/dev.playbook.js +6 -6
  28. package/reference/sdlc/dev.playbook/dev.playbook.ts +8 -9
  29. package/reference/sdlc/dev.playbook/dev.registry.js +1 -1
  30. package/reference/sdlc/dev.playbook/dev.registry.ts +1 -1
  31. package/reference/sdlc/pr.md +17 -4
  32. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +18 -4
  33. package/reference/sdlc/pr.playbook/pr.fsm.js +49 -18
  34. package/reference/sdlc/pr.playbook/pr.fsm.ts +63 -20
  35. package/reference/sdlc/pr.playbook/pr.gears.md +20 -7
  36. package/reference/sdlc/pr.playbook/pr.playbook.js +2 -1
  37. package/reference/sdlc/pr.playbook/pr.playbook.ts +2 -1
  38. package/reference/sdlc/review.playbook/review.fsm.d.ts +1 -1
  39. package/slc/experiments/fsm-scaffold-guidance.md +31 -0
  40. package/slc/gears2fsm.md +145 -58
  41. package/slc/link.md +190 -7
  42. package/slc/materialize-link.mjs +460 -0
  43. package/slc/optimize.md +46 -6
  44. package/slc/scaffold-fsm.mjs +225 -0
  45. package/slc/slc.pin-inputs.json +13 -0
  46. package/slc/text2gears.md +38 -3
  47. package/slc/workflow-contracts.json +371 -0
  48. package/src/runtime.d.ts +70 -0
  49. package/src/runtime.js +232 -1
  50. package/src/runtime.ts +358 -1
  51. package/src/xstate-playbook-runtime.d.ts +9 -10
  52. package/src/xstate-playbook-runtime.js +407 -21
  53. package/src/xstate-playbook-runtime.ts +514 -20
  54. package/src/xstate-runtime.d.ts +9 -1
  55. package/src/xstate-runtime.js +299 -19
  56. package/src/xstate-runtime.ts +362 -21
@@ -72,7 +72,9 @@ export interface SessionHostController {
72
72
  /**
73
73
  * The active leaf's currently advertised runtime actions, read live between
74
74
  * turns; empty whenever nothing is advertised, a turn is active, or the
75
- * leaf's control view cannot be read.
75
+ * leaf's control view cannot be read. Each pair carries the leaf's own
76
+ * `standing` and its `reason`, so a host never draws a control without
77
+ * saying what running it would do (DR-063).
76
78
  */
77
79
  listRuntimeActions(): readonly PlaybookControlAction[];
78
80
  /**
@@ -83,8 +85,9 @@ export interface SessionHostController {
83
85
  submitRuntimeAction(actionId: string): Promise<SessionRecovery>;
84
86
  /**
85
87
  * The controls the shell offers on its own behalf, read live between turns;
86
- * today one give-up while a root is engaged, and empty while idle or while a
87
- * turn is active. Disjoint from `listRuntimeActions`, which reads the leaf.
88
+ * today one `ready` give-up while a root is engaged, and empty while idle or
89
+ * while a turn is active. Disjoint from `listRuntimeActions`, which reads the
90
+ * leaf.
88
91
  */
89
92
  listShellActions(): readonly PlaybookControlAction[];
90
93
  /**
@@ -214,7 +214,7 @@ export declare const decideMachine: import("xstate").StateMachine<DecideContext,
214
214
  } | {
215
215
  type: "validReviewSuccess";
216
216
  params: unknown;
217
- }, never, "done" | "failed" | "awaitBossReply" | "ready" | "reportedReviewFailure" | "commitCoderProposal" | "reviewCommit" | {
217
+ }, never, "done" | "failed" | "ready" | "awaitBossReply" | "reportedReviewFailure" | "commitCoderProposal" | "reviewCommit" | {
218
218
  independentProposals: {
219
219
  coder: "complete" | "working" | "waiting";
220
220
  reviewer: "complete" | "working" | "waiting";
@@ -2481,20 +2481,42 @@ function createDecidePlaybookRuntime(options, deferredEffects) {
2481
2481
  }
2482
2482
  return 'restored';
2483
2483
  };
2484
+ /**
2485
+ * DR-063 §3: reconciliation re-reads the host's ledger, which the control
2486
+ * view has just done. With no eligible checkpoint restoration and a complete
2487
+ * receipt on every unresolved boundary, re-reading resolves nothing.
2488
+ */
2489
+ const reconciliationStanding = (restorationEligible) => {
2490
+ if (restorationEligible)
2491
+ return { standing: 'ready' };
2492
+ const envelopes = unresolvedEffectEnvelopeIdentities();
2493
+ if (envelopes.length === 0)
2494
+ return { standing: 'ready' };
2495
+ const boundaryIds = envelopes.flatMap((envelope) => envelope.kind === 'boundary'
2496
+ ? [envelope.boundaryId]
2497
+ : (effectLedgerMirror.logicalOperations.find(({ operationId }) => operationId === envelope.operationId)?.boundaryIds ?? []));
2498
+ if (boundaryIds.length === 0)
2499
+ return { standing: 'ready' };
2500
+ return boundaryIds.every((boundaryId) => effectLedgerMirror.boundaries.find((candidate) => candidate.boundaryId === boundaryId)?.physicalReceipt !== undefined)
2501
+ ? { standing: 'no-op', reason: 'receipt-complete' }
2502
+ : { standing: 'ready' };
2503
+ };
2484
2504
  const unresolvedControlCandidates = () => {
2485
2505
  if (!hasUnresolvedReconciliation())
2486
2506
  return [];
2487
2507
  const operation = hiddenDeferredOperationId === undefined
2488
2508
  ? undefined
2489
2509
  : effectLedgerMirror.logicalOperations.find(({ operationId }) => operationId === hiddenDeferredOperationId);
2510
+ const restorationEligible = operation?.checkpointRestorationEligible === true;
2490
2511
  return [
2491
2512
  {
2492
2513
  action: {
2493
2514
  id: UNRESOLVED_EFFECT_RECONCILIATION_ACTION_ID,
2494
2515
  label: 'Retry unresolved effect reconciliation',
2516
+ ...reconciliationStanding(restorationEligible),
2495
2517
  },
2496
2518
  kind: 'reconcile',
2497
- ...(operation?.checkpointRestorationEligible === true
2519
+ ...(restorationEligible
2498
2520
  ? { deferredRestoreOperationId: operation.operationId }
2499
2521
  : {}),
2500
2522
  },
@@ -2502,6 +2524,8 @@ function createDecidePlaybookRuntime(options, deferredEffects) {
2502
2524
  action: {
2503
2525
  id: UNRESOLVED_EFFECT_ABANDONMENT_ACTION_ID,
2504
2526
  label: 'Abandon unresolved workflow attempt',
2527
+ // DR-063 §3: abandonment always changes the session's standing.
2528
+ standing: 'ready',
2505
2529
  },
2506
2530
  kind: 'abandon',
2507
2531
  },
@@ -3850,6 +3850,35 @@ function createDecidePlaybookRuntime(
3850
3850
  };
3851
3851
  type UnresolvedControlCandidates = readonly UnresolvedControlCandidate[];
3852
3852
 
3853
+ /**
3854
+ * DR-063 §3: reconciliation re-reads the host's ledger, which the control
3855
+ * view has just done. With no eligible checkpoint restoration and a complete
3856
+ * receipt on every unresolved boundary, re-reading resolves nothing.
3857
+ */
3858
+ const reconciliationStanding = (
3859
+ restorationEligible: boolean,
3860
+ ): Pick<PlaybookControlAction, 'standing' | 'reason'> => {
3861
+ if (restorationEligible) return { standing: 'ready' };
3862
+ const envelopes = unresolvedEffectEnvelopeIdentities();
3863
+ if (envelopes.length === 0) return { standing: 'ready' };
3864
+ const boundaryIds = envelopes.flatMap((envelope) =>
3865
+ envelope.kind === 'boundary'
3866
+ ? [envelope.boundaryId]
3867
+ : (effectLedgerMirror.logicalOperations.find(
3868
+ ({ operationId }) => operationId === envelope.operationId,
3869
+ )?.boundaryIds ?? []),
3870
+ );
3871
+ if (boundaryIds.length === 0) return { standing: 'ready' };
3872
+ return boundaryIds.every(
3873
+ (boundaryId) =>
3874
+ effectLedgerMirror.boundaries.find(
3875
+ (candidate) => candidate.boundaryId === boundaryId,
3876
+ )?.physicalReceipt !== undefined,
3877
+ )
3878
+ ? { standing: 'no-op', reason: 'receipt-complete' }
3879
+ : { standing: 'ready' };
3880
+ };
3881
+
3853
3882
  const unresolvedControlCandidates = (): UnresolvedControlCandidates => {
3854
3883
  if (!hasUnresolvedReconciliation()) return [];
3855
3884
  const operation =
@@ -3859,21 +3888,25 @@ function createDecidePlaybookRuntime(
3859
3888
  ({ operationId }) =>
3860
3889
  operationId === hiddenDeferredOperationId,
3861
3890
  );
3891
+ const restorationEligible = operation?.checkpointRestorationEligible === true;
3862
3892
  return [
3863
3893
  {
3864
3894
  action: {
3865
3895
  id: UNRESOLVED_EFFECT_RECONCILIATION_ACTION_ID,
3866
3896
  label: 'Retry unresolved effect reconciliation',
3897
+ ...reconciliationStanding(restorationEligible),
3867
3898
  },
3868
3899
  kind: 'reconcile',
3869
- ...(operation?.checkpointRestorationEligible === true
3870
- ? { deferredRestoreOperationId: operation.operationId }
3900
+ ...(restorationEligible
3901
+ ? { deferredRestoreOperationId: operation!.operationId }
3871
3902
  : {}),
3872
3903
  },
3873
3904
  {
3874
3905
  action: {
3875
3906
  id: UNRESOLVED_EFFECT_ABANDONMENT_ACTION_ID,
3876
3907
  label: 'Abandon unresolved workflow attempt',
3908
+ // DR-063 §3: abandonment always changes the session's standing.
3909
+ standing: 'ready',
3877
3910
  },
3878
3911
  kind: 'abandon',
3879
3912
  },
@@ -12,6 +12,7 @@ The caller supplies a development request including any desired outcome, scope,
12
12
 
13
13
  `dev` is an optional repository-aware planner for a development request that needs more analysis before choosing a development path.
14
14
  It coordinates existing playbooks and owns no repository commit itself.
15
+ Analyst chooses the path and states why in a short planning note; analysis, design, and implementation belong to the playbooks `dev` calls, and a question to Boss serves only the choice of path.
15
16
 
16
17
  At the start of `dev` and after each Boss reply, Captain shall relay the development request, relevant discussion context, and any relevant run results to Analyst in quotes (`>`), along with the following instruction:
17
18
 
@@ -20,17 +21,23 @@ At the start of `dev` and after each Boss reply, Captain shall relay the develop
20
21
  > Run results: <run-results>
21
22
 
22
23
  ```markdown
23
- Inspect the request and the relevant repository and specs only as needed to determine the smallest sound next step.
24
+ Plan which playbooks run for this request; the playbooks do the work.
25
+ Read the request, then the specs and the repository only as far as choosing the path requires.
24
26
  Do not change files or commit while planning or discussing the request.
25
27
 
26
- - If useful analysis or clarification should be discussed before any repository work, give Boss the useful response and ask one material question that advances the decision.
27
- - If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.
28
- - If implementation can proceed under the existing decisions, choose `code`.
29
- - If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.
30
- - If the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery, read the issue and its comments as part of the analysis (`gh issue view --comments` with the issue number) and choose `code via pull request` or `decide then code via pull request` in place of `code` or `decide then code`.
28
+ Choose exactly one:
31
29
 
30
+ - `code`: the existing decisions and spec items settle how the work is done.
31
+ - `decide then code`: the work turns on a rule, concept, term, shape, or trade-off the specs leave open or contradict; name what is open, do not settle it.
32
+ - `code via pull request` or `decide then code via pull request`, in place of the two above, when the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery; read the issue and its comments (`gh issue view --comments` with the issue number) while choosing.
33
+ - A question to Boss, only when the answer would change which path runs or whether any work is wanted: one short question naming the alternatives it decides between, and nothing else. When every answer leads to the same path, choose it; the playbook settles the open point.
34
+ - `discussion complete`, after a Boss reply, when no repository work should follow.
35
+
36
+ Your reply is the planning note the chosen playbook receives. It holds only the path and, in at most ten lines, why — the decisions and spec items that settle the work, or the open point a decision must settle — plus the scope the request implies and any fact from the issue the playbooks need.
37
+ It holds no design, proposal, implementation instruction, or file-level finding: the playbooks own those.
38
+ A reply that chooses a path asks Boss nothing; a reply that asks Boss chooses no path.
32
39
  A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.
33
- Do not choose `decide then code` merely because the work is large.
40
+ Do not choose `decide then code` merely because the work is large, nor `code` merely because it is small.
34
41
  Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.
35
42
  ```
36
43
 
@@ -42,7 +49,7 @@ No outcome depends on a fixed presentation format of Analyst's reply.
42
49
  For needs Boss reply, `dev` shall use the standard Boss-question suspension with Analyst's complete response.
43
50
  The session Captain shall present that response to Boss and, after Boss replies, resume `dev` with the answer in the same Analyst conversation; include the previous question only when that conversation must start fresh.
44
51
 
45
- Discussion complete is available only after a Boss reply, when any useful analysis has already been presented through needs Boss reply.
52
+ Discussion complete is available only after a Boss reply, when that reply settles that no repository work should follow.
46
53
  It completes `dev` without a child call or repository change.
47
54
 
48
55
  For code, `dev` shall directly call playbook `code` with the development request, relevant discussion context, and planning result in quotes (`>`).
@@ -296,7 +296,7 @@ export declare const devMachine: import("xstate").StateMachine<DevContext, {
296
296
  } | {
297
297
  type: "resumesPlanAnalysis";
298
298
  params: unknown;
299
- }, never, "done" | "failed" | "awaitBossReply" | "ready" | "planAnalysis" | "createBranch" | "callCode" | "callDecide" | "callCodeAfterDecide" | "openPullRequest" | "discussionComplete" | "reportedChildFailure", string, DevInput, {
299
+ }, never, "done" | "failed" | "ready" | "awaitBossReply" | "planAnalysis" | "createBranch" | "callCode" | "callDecide" | "callCodeAfterDecide" | "openPullRequest" | "discussionComplete" | "reportedChildFailure", string, DevInput, {
300
300
  readonly status: "discussion-complete";
301
301
  } | {
302
302
  readonly status: "complete";
@@ -8,30 +8,36 @@ const PLAN_ANALYSIS_PROMPT = [
8
8
  '> Prior discussion: <discussion-context>',
9
9
  '> Run results: <run-results>',
10
10
  '',
11
- 'Inspect the request and the relevant repository and specs only as needed to determine the smallest sound next step.',
11
+ 'Plan which playbooks run for this request; the playbooks do the work.',
12
+ 'Read the request, then the specs and the repository only as far as choosing the path requires.',
12
13
  'Do not change files or commit while planning or discussing the request.',
13
14
  '',
14
- '- If useful analysis or clarification should be discussed before any repository work, give Boss the useful response and ask one material question that advances the decision.',
15
- '- If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.',
16
- '- If implementation can proceed under the existing decisions, choose `code`.',
17
- '- If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.',
18
- '- If the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery, read the issue and its comments as part of the analysis (`gh issue view --comments` with the issue number) and choose `code via pull request` or `decide then code via pull request` in place of `code` or `decide then code`.',
15
+ 'Choose exactly one:',
19
16
  '',
17
+ '- `code`: the existing decisions and spec items settle how the work is done.',
18
+ '- `decide then code`: the work turns on a rule, concept, term, shape, or trade-off the specs leave open or contradict; name what is open, do not settle it.',
19
+ '- `code via pull request` or `decide then code via pull request`, in place of the two above, when the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery; read the issue and its comments (`gh issue view --comments` with the issue number) while choosing.',
20
+ '- A question to Boss, only when the answer would change which path runs or whether any work is wanted: one short question naming the alternatives it decides between, and nothing else. When every answer leads to the same path, choose it; the playbook settles the open point.',
21
+ '- `discussion complete`, after a Boss reply, when no repository work should follow.',
22
+ '',
23
+ 'Your reply is the planning note the chosen playbook receives. It holds only the path and, in at most ten lines, why — the decisions and spec items that settle the work, or the open point a decision must settle — plus the scope the request implies and any fact from the issue the playbooks need.',
24
+ 'It holds no design, proposal, implementation instruction, or file-level finding: the playbooks own those.',
25
+ 'A reply that chooses a path asks Boss nothing; a reply that asks Boss chooses no path.',
20
26
  'A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.',
21
- 'Do not choose `decide then code` merely because the work is large.',
27
+ 'Do not choose `decide then code` merely because the work is large, nor `code` merely because it is small.',
22
28
  'Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.',
23
29
  ].join('\n');
24
30
  const PLAN_ANALYSIS_RESULTS = {
25
31
  discussionComplete: 'Analyst concluded the discussion after a Boss reply, with no repository work to follow.',
26
- code: 'Analyst determined implementation can proceed under the existing decisions. Output shall include `planningResult: <verbatim final text>`.',
27
- decideThenCode: 'Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle. Output shall include `planningResult: <verbatim final text>`.',
28
- codeViaPullRequest: 'Analyst determined implementation can proceed under the existing decisions and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
29
- decideThenCodeViaPullRequest: 'Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
32
+ code: 'Analyst chose `code`: the existing decisions and spec items settle how the work is done. Output shall include `planningResult: <verbatim final text>`.',
33
+ decideThenCode: 'Analyst chose `decide then code`: the work turns on a point the specs leave open or contradict. Output shall include `planningResult: <verbatim final text>`.',
34
+ codeViaPullRequest: 'Analyst chose `code via pull request`: the existing decisions settle the work, and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
35
+ decideThenCodeViaPullRequest: 'Analyst chose `decide then code via pull request`: the work turns on a point the specs leave open or contradict, and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
30
36
  needsBossReply: "The acting agent's prose surfaces a clarifying question for Boss that the agent cannot answer alone. Output shall include `question: <verbatim question text from the acting agent's prose>`.",
31
37
  };
32
38
  const STATE_DESCRIPTIONS = {
33
39
  ready: 'Waiting for a development request.',
34
- planAnalysis: 'Analyst is analyzing the development request to choose the smallest sound next step.',
40
+ planAnalysis: 'Analyst is choosing the development path.',
35
41
  createBranch: 'The BRANCH playbook is preparing the pull-request branch for the planned development path.',
36
42
  callCode: 'The CODE playbook is implementing the planned development path.',
37
43
  callDecide: 'The DECIDE playbook is settling the durable decision the planned path requires.',
@@ -198,17 +198,23 @@ const PLAN_ANALYSIS_PROMPT = [
198
198
  '> Prior discussion: <discussion-context>',
199
199
  '> Run results: <run-results>',
200
200
  '',
201
- 'Inspect the request and the relevant repository and specs only as needed to determine the smallest sound next step.',
201
+ 'Plan which playbooks run for this request; the playbooks do the work.',
202
+ 'Read the request, then the specs and the repository only as far as choosing the path requires.',
202
203
  'Do not change files or commit while planning or discussing the request.',
203
204
  '',
204
- '- If useful analysis or clarification should be discussed before any repository work, give Boss the useful response and ask one material question that advances the decision.',
205
- '- If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.',
206
- '- If implementation can proceed under the existing decisions, choose `code`.',
207
- '- If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.',
208
- '- If the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery, read the issue and its comments as part of the analysis (`gh issue view --comments` with the issue number) and choose `code via pull request` or `decide then code via pull request` in place of `code` or `decide then code`.',
205
+ 'Choose exactly one:',
209
206
  '',
207
+ '- `code`: the existing decisions and spec items settle how the work is done.',
208
+ '- `decide then code`: the work turns on a rule, concept, term, shape, or trade-off the specs leave open or contradict; name what is open, do not settle it.',
209
+ '- `code via pull request` or `decide then code via pull request`, in place of the two above, when the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery; read the issue and its comments (`gh issue view --comments` with the issue number) while choosing.',
210
+ '- A question to Boss, only when the answer would change which path runs or whether any work is wanted: one short question naming the alternatives it decides between, and nothing else. When every answer leads to the same path, choose it; the playbook settles the open point.',
211
+ '- `discussion complete`, after a Boss reply, when no repository work should follow.',
212
+ '',
213
+ 'Your reply is the planning note the chosen playbook receives. It holds only the path and, in at most ten lines, why — the decisions and spec items that settle the work, or the open point a decision must settle — plus the scope the request implies and any fact from the issue the playbooks need.',
214
+ 'It holds no design, proposal, implementation instruction, or file-level finding: the playbooks own those.',
215
+ 'A reply that chooses a path asks Boss nothing; a reply that asks Boss chooses no path.',
210
216
  'A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.',
211
- 'Do not choose `decide then code` merely because the work is large.',
217
+ 'Do not choose `decide then code` merely because the work is large, nor `code` merely because it is small.',
212
218
  'Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.',
213
219
  ].join('\n');
214
220
 
@@ -216,21 +222,20 @@ const PLAN_ANALYSIS_RESULTS = {
216
222
  discussionComplete:
217
223
  'Analyst concluded the discussion after a Boss reply, with no repository work to follow.',
218
224
  code:
219
- 'Analyst determined implementation can proceed under the existing decisions. Output shall include `planningResult: <verbatim final text>`.',
225
+ 'Analyst chose `code`: the existing decisions and spec items settle how the work is done. Output shall include `planningResult: <verbatim final text>`.',
220
226
  decideThenCode:
221
- 'Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle. Output shall include `planningResult: <verbatim final text>`.',
227
+ 'Analyst chose `decide then code`: the work turns on a point the specs leave open or contradict. Output shall include `planningResult: <verbatim final text>`.',
222
228
  codeViaPullRequest:
223
- 'Analyst determined implementation can proceed under the existing decisions and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
229
+ 'Analyst chose `code via pull request`: the existing decisions settle the work, and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
224
230
  decideThenCodeViaPullRequest:
225
- 'Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
231
+ 'Analyst chose `decide then code via pull request`: the work turns on a point the specs leave open or contradict, and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.',
226
232
  needsBossReply:
227
233
  "The acting agent's prose surfaces a clarifying question for Boss that the agent cannot answer alone. Output shall include `question: <verbatim question text from the acting agent's prose>`.",
228
234
  } as const;
229
235
 
230
236
  const STATE_DESCRIPTIONS = {
231
237
  ready: 'Waiting for a development request.',
232
- planAnalysis:
233
- 'Analyst is analyzing the development request to choose the smallest sound next step.',
238
+ planAnalysis: 'Analyst is choosing the development path.',
234
239
  createBranch:
235
240
  'The BRANCH playbook is preparing the pull-request branch for the planned development path.',
236
241
  callCode: 'The CODE playbook is implementing the planned development path.',
@@ -9,6 +9,7 @@ Roles:
9
9
 
10
10
  `dev` is an optional repository-aware planner for a development request that needs more analysis before choosing a development path.
11
11
  It coordinates existing playbooks and owns no repository commit itself.
12
+ Analyst chooses the path and states why in a short planning note; analysis, design, and implementation belong to the playbooks `dev` calls, and a question to Boss serves only the choice of path.
12
13
 
13
14
  ## Analyst
14
15
 
@@ -20,31 +21,37 @@ At the start of `dev` and after each Boss reply, Captain shall relay the develop
20
21
  > > Prior discussion: <discussion-context>
21
22
  > > Run results: <run-results>
22
23
  >
23
- > Inspect the request and the relevant repository and specs only as needed to determine the smallest sound next step.
24
+ > Plan which playbooks run for this request; the playbooks do the work.
25
+ > Read the request, then the specs and the repository only as far as choosing the path requires.
24
26
  > Do not change files or commit while planning or discussing the request.
25
27
  >
26
- > - If useful analysis or clarification should be discussed before any repository work, give Boss the useful response and ask one material question that advances the decision.
27
- > - If the discussion has concluded after a Boss reply and no repository work should follow, choose `discussion complete`.
28
- > - If implementation can proceed under the existing decisions, choose `code`.
29
- > - If implementation first requires a new or amended durable decision that the existing specs do not settle, choose `decide then code`.
30
- > - If the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery, read the issue and its comments as part of the analysis (`gh issue view --comments` with the issue number) and choose `code via pull request` or `decide then code via pull request` in place of `code` or `decide then code`.
28
+ > Choose exactly one:
31
29
  >
30
+ > - `code`: the existing decisions and spec items settle how the work is done.
31
+ > - `decide then code`: the work turns on a rule, concept, term, shape, or trade-off the specs leave open or contradict; name what is open, do not settle it.
32
+ > - `code via pull request` or `decide then code via pull request`, in place of the two above, when the request names a GitHub issue (number or URL) or explicitly asks for pull-request delivery; read the issue and its comments (`gh issue view --comments` with the issue number) while choosing.
33
+ > - A question to Boss, only when the answer would change which path runs or whether any work is wanted: one short question naming the alternatives it decides between, and nothing else. When every answer leads to the same path, choose it; the playbook settles the open point.
34
+ > - `discussion complete`, after a Boss reply, when no repository work should follow.
35
+ >
36
+ > Your reply is the planning note the chosen playbook receives. It holds only the path and, in at most ten lines, why — the decisions and spec items that settle the work, or the open point a decision must settle — plus the scope the request implies and any fact from the issue the playbooks need.
37
+ > It holds no design, proposal, implementation instruction, or file-level finding: the playbooks own those.
38
+ > A reply that chooses a path asks Boss nothing; a reply that asks Boss chooses no path.
32
39
  > A question or exploratory discussion is not by itself authorization to create a durable decision or implement changes.
33
- > Do not choose `decide then code` merely because the work is large.
40
+ > Do not choose `decide then code` merely because the work is large, nor `code` merely because it is small.
34
41
  > Consult @specs/map.md for relevant context and @specs/meta.md for spec requirements, if needed.
35
42
 
36
43
  Results:
37
44
  - `discussionComplete`: Analyst concluded the discussion after a Boss reply, with no repository work to follow.
38
- - `code`: Analyst determined implementation can proceed under the existing decisions. Output shall include `planningResult: <verbatim final text>`.
39
- - `decideThenCode`: Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle. Output shall include `planningResult: <verbatim final text>`.
40
- - `codeViaPullRequest`: Analyst determined implementation can proceed under the existing decisions and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.
41
- - `decideThenCodeViaPullRequest`: Analyst determined implementation first requires a new or amended durable decision that the existing specs do not settle and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.
45
+ - `code`: Analyst chose `code`: the existing decisions and spec items settle how the work is done. Output shall include `planningResult: <verbatim final text>`.
46
+ - `decideThenCode`: Analyst chose `decide then code`: the work turns on a point the specs leave open or contradict. Output shall include `planningResult: <verbatim final text>`.
47
+ - `codeViaPullRequest`: Analyst chose `code via pull request`: the existing decisions settle the work, and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.
48
+ - `decideThenCodeViaPullRequest`: Analyst chose `decide then code via pull request`: the work turns on a point the specs leave open or contradict, and the request names a GitHub issue or explicitly asks for pull-request delivery. Output shall include `planningResult: <verbatim final text>`.
42
49
 
43
50
  Workflow outcomes:
44
51
  - The planning result has six semantic outcomes: needs Boss reply, discussion complete, code, decide then code, code via pull request, and decide then code via pull request.
45
52
  - Each outcome requires affirmative support in Analyst's result; absence of a reason to choose another outcome is not support, and no outcome depends on a fixed presentation format of Analyst's reply.
46
53
  - Needs Boss reply uses the standard Boss-question suspension with Analyst's complete response; after Boss replies, `dev` resumes with the answer in the same Analyst conversation; the previous question is included only when that conversation must start fresh.
47
- - Discussion complete is available only after a Boss reply, when any useful analysis has already been presented through needs Boss reply; it completes `dev` without a child call or repository change.
54
+ - Discussion complete is available only after a Boss reply, when that reply settles that no repository work should follow; it completes `dev` without a child call or repository change.
48
55
  - `dev` acts on the accepted outcome itself and does not return to the session Captain for another routing decision.
49
56
 
50
57
  ## Nested development paths
@@ -1,19 +1,12 @@
1
- import { type XStatePlaybookRuntimeFactory, type XStatePlaybookRuntimeConstruction } from '@sublang/playbook/xstate-runtime';
2
- import { type DevInput, type PlayerInput } from './dev.fsm.js';
1
+ import { type PlaybookPlayerInput, type XStatePlaybookRuntimeFactory, type XStatePlaybookRuntimeConstruction } from '@sublang/playbook/xstate-runtime';
2
+ import { type DevInput } from './dev.fsm.js';
3
3
  import type { PlaybookHostConstructionCapabilities } from '../code.playbook/playbook-captain.js';
4
4
  import type { CaptainCallOptions, CaptainResult, JsonValue, NormalizedError, PlayerCallOptions, PlaybookCallRequest, PlaybookCallResult, PlaybookCallStart, PlaybookControlReceipt, PlaybookControlView, PlaybookPendingCall, PlaybookPorts, PlaybookRunResult, PlaybookRuntime, PlaybookRuntimeFactory, PlaybookRuntimeSnapshot, PlaybookSession, PlaybookState, PlaybookStateValue, PlaybookTraceEvent, PlaybookTraceType, PlayerResult, PlayerSessionStore } from '@sublang/playbook/runtime';
5
5
  export type { CaptainCallOptions, CaptainResult, JsonValue, NormalizedError, PlayerCallOptions, PlaybookCallRequest, PlaybookCallResult, PlaybookCallStart, PlaybookControlReceipt, PlaybookControlView, PlaybookPendingCall, PlaybookPorts, PlaybookRunResult, PlaybookRuntime, PlaybookRuntimeFactory, PlaybookRuntimeSnapshot, PlaybookSession, PlaybookState, PlaybookStateValue, PlaybookTraceEvent, PlaybookTraceType, PlayerResult, PlayerSessionStore, };
6
6
  export type DevPlaybookOptions = DevInput;
7
7
  export type DevPlaybookHostCapabilities = PlaybookHostConstructionCapabilities & XStatePlaybookRuntimeConstruction<DevPlaybookOptions, object>['hostCapabilities'];
8
- /**
9
- * Preserve authored Markdown quote markers around every line of relayed
10
- * runtime text. The generic composer preserves the marker itself; DEV's
11
- * override additionally keeps a multiline value inside that quote and drops
12
- * the optional relays that have no value yet.
13
- */
14
- declare function composePlayerPrompt(input: PlayerInput, resuming?: boolean): string;
15
8
  export declare const _internal: {
16
- composePlayerPrompt: typeof composePlayerPrompt;
9
+ composePlayerPrompt: (input: PlaybookPlayerInput, _identity: import("@sublang/playbook/xstate-runtime").XStatePromptIdentity, resuming?: boolean) => string;
17
10
  VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string>;
18
11
  UNFINISHED_FINAL_STATE_IDS: ReadonlySet<string>;
19
12
  };
@@ -73,11 +73,6 @@ function composePlayerPrompt(input, resuming = false) {
73
73
  });
74
74
  return composePlayerContinuation(input, body, resuming);
75
75
  }
76
- export const _internal = {
77
- composePlayerPrompt,
78
- VERBATIM_PAYLOAD_FIELDS,
79
- UNFINISHED_FINAL_STATE_IDS,
80
- };
81
76
  const runtimeSpec = {
82
77
  label: 'DEV',
83
78
  // DR-022 / slc/link.md: the declaration carries the value current at link
@@ -97,7 +92,7 @@ const runtimeSpec = {
97
92
  roleStates: {
98
93
  planAnalysis: {
99
94
  role: 'analyst',
100
- label: 'Analyst is analyzing the development request to choose the smallest sound next step.',
95
+ label: 'Analyst is choosing the development path.',
101
96
  },
102
97
  },
103
98
  // DR-044: DEV owns no repository commit — every Analyst outcome is
@@ -141,5 +136,10 @@ const runtimeSpec = {
141
136
  unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
142
137
  transitionEventFields: ['developmentRequest', 'answer', 'questionId'],
143
138
  };
139
+ export const _internal = {
140
+ composePlayerPrompt: runtimeSpec.composePlayerPrompt,
141
+ VERBATIM_PAYLOAD_FIELDS,
142
+ UNFINISHED_FINAL_STATE_IDS,
143
+ };
144
144
  const createPlaybookRuntime = createXStatePlaybookRuntime(devMachine, runtimeSpec);
145
145
  export default createPlaybookRuntime;
@@ -160,12 +160,6 @@ function composePlayerPrompt(input: PlayerInput, resuming = false): string {
160
160
  return composePlayerContinuation(input, body, resuming);
161
161
  }
162
162
 
163
- export const _internal = {
164
- composePlayerPrompt,
165
- VERBATIM_PAYLOAD_FIELDS,
166
- UNFINISHED_FINAL_STATE_IDS,
167
- };
168
-
169
163
  const runtimeSpec = {
170
164
  label: 'DEV',
171
165
  // DR-022 / slc/link.md: the declaration carries the value current at link
@@ -185,8 +179,7 @@ const runtimeSpec = {
185
179
  roleStates: {
186
180
  planAnalysis: {
187
181
  role: 'analyst',
188
- label:
189
- 'Analyst is analyzing the development request to choose the smallest sound next step.',
182
+ label: 'Analyst is choosing the development path.',
190
183
  },
191
184
  },
192
185
  // DR-044: DEV owns no repository commit — every Analyst outcome is
@@ -224,7 +217,7 @@ const runtimeSpec = {
224
217
  },
225
218
  },
226
219
  },
227
- composePlayerPrompt: (input: PlaybookPlayerInput, _identity, resuming) =>
220
+ composePlayerPrompt: (input: PlaybookPlayerInput, _identity, resuming?: boolean) =>
228
221
  composePlayerPrompt(input as PlayerInput, resuming),
229
222
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
230
223
  controlContextFields: [],
@@ -232,6 +225,12 @@ const runtimeSpec = {
232
225
  transitionEventFields: ['developmentRequest', 'answer', 'questionId'],
233
226
  } satisfies XStatePlaybookRuntimeSpecV3<DevPlaybookOptions>;
234
227
 
228
+ export const _internal = {
229
+ composePlayerPrompt: runtimeSpec.composePlayerPrompt,
230
+ VERBATIM_PAYLOAD_FIELDS,
231
+ UNFINISHED_FINAL_STATE_IDS,
232
+ };
233
+
235
234
  const createPlaybookRuntime: XStatePlaybookRuntimeFactory<
236
235
  XStatePlaybookRuntimeConstruction<
237
236
  DevPlaybookOptions,
@@ -49,7 +49,7 @@ export function validateDevOptions(optionSlice) {
49
49
  export const devPlaybookRegistryEntry = {
50
50
  id: 'dev',
51
51
  command: 'dev',
52
- intent: 'analyze a development request that needs planning before choosing direct implementation or a durable decision first, delivered through a pull request when the request names a GitHub issue or asks for one',
52
+ intent: 'plan the development path for a request: direct implementation, or a durable decision first, delivered through a pull request when the request names a GitHub issue or asks for one',
53
53
  artifactSchema: 3,
54
54
  runtimeProfile: Object.freeze({
55
55
  kind: 'shared-factory',
@@ -104,7 +104,7 @@ export const devPlaybookRegistryEntry: DevPlaybookRegistryEntry = {
104
104
  id: 'dev',
105
105
  command: 'dev',
106
106
  intent:
107
- 'analyze a development request that needs planning before choosing direct implementation or a durable decision first, delivered through a pull request when the request names a GitHub issue or asks for one',
107
+ 'plan the development path for a request: direct implementation, or a durable decision first, delivered through a pull request when the request names a GitHub issue or asks for one',
108
108
  artifactSchema: 3,
109
109
  runtimeProfile: Object.freeze({
110
110
  kind: 'shared-factory',
@@ -19,7 +19,9 @@ The caller supplies:
19
19
  `pr` delivers a reviewed branch into the repository default branch through a GitHub pull request: it publishes the branch, waits for the pull request's checks, fixes red checks through playbook `code` no more than once, and merges.
20
20
  It changes no files and owns no repository commit; the one fix it may request is owned by `code`.
21
21
  The check waits, the fix publication, the merge, and the local update are mechanical steps: each runs one fixed command whose exit status alone decides its two outcomes, reads no conversation, and produces no prose.
22
- `gh` infers the pull request from the checked-out branch, so no mechanical step needs a runtime value.
22
+ `gh` infers the pull request from the checked-out branch, which the run does not own: the nested `code` call suspends across Boss turns, so the checkout can change before the fix is published or the merge runs.
23
+ The two steps that act on the pull request itself therefore carry one runtime value — the pull request `pr` published — and refuse unless the checkout still infers exactly it.
24
+ That identity is reported text, so it binds as a single-quoted shell literal: the command compares it as data and never executes it as shell syntax.
23
25
 
24
26
  When the caller gives its input, Captain shall relay the complete caller input in quotes (`>`) to Coder, along with the following instruction:
25
27
 
@@ -66,6 +68,7 @@ When the nested `code` call fails outside that authored result contract, `pr` sh
66
68
 
67
69
  When `code` succeeds, Captain shall publish the fix to the pull request by running exactly the following command in the repository, with no other action and without reading its output:
68
70
 
71
+ > [ "$(gh pr view --json url --jq .url)" = '<pull-request-url>' ] || exit 1
69
72
  > git push || exit 1
70
73
  > n=0
71
74
  > until [ "$(gh pr view --json headRefOid --jq .headRefOid 2>/dev/null)" = "$(git rev-parse HEAD)" ]; do
@@ -75,6 +78,7 @@ When `code` succeeds, Captain shall publish the fix to the pull request by runni
75
78
  > done
76
79
 
77
80
  The publication has exactly two outcomes decided by the exit status alone: fix published on status zero, once the pull request's head is the pushed commit, and fix not published otherwise.
81
+ The command pushes nothing until the checked-out branch infers the pull request `pr` published, so a checkout that changed while `code` ran publishes to no other branch.
78
82
  Fix not published is an authored failure that leaves the pull request open.
79
83
 
80
84
  When the fix is published, Captain shall wait for the pull request's checks again by running exactly the same command as the first wait in the repository, with no other action and without reading its output:
@@ -92,11 +96,20 @@ Checks still failing is an authored failure that leaves the pull request open; t
92
96
 
93
97
  When the checks pass, before or after the one fix attempt, Captain shall merge the pull request by running exactly the following command in the repository, with no other action and without reading its output:
94
98
 
95
- > gh pr merge --merge --delete-branch --match-head-commit "$(git rev-parse HEAD)"
99
+ > pr=$(gh pr view --json url --jq .url) || exit 1
100
+ > [ "$pr" = '<pull-request-url>' ] || exit 1
101
+ > base=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name) || exit 1
102
+ > [ -n "$base" ] || exit 1
103
+ > [ "$(gh pr view "$pr" --json baseRefName --jq .baseRefName)" = "$base" ] || exit 1
104
+ > gh pr merge --merge --delete-branch --match-head-commit "$(git rev-parse HEAD)" || exit 1
105
+ > [ "$(gh pr view "$pr" --json state --jq .state)" = MERGED ] || exit 1
106
+ > [ "$(git branch --show-current)" = "$base" ]
96
107
 
97
- The merge creates a merge commit on the repository default branch, deletes the remote and local branch, and checks out the local default branch; GitHub closes the linked issue on merge.
108
+ The merge creates a merge commit on the repository default branch, requests deletion of the remote and local branch, and checks out the local default branch; GitHub closes the linked issue on merge.
98
109
  The merge has exactly two outcomes decided by the exit status alone: merged on status zero and merge refused otherwise.
99
- Merge refused is an authored failure that leaves the pull request in the state GitHub reports: GitHub refused the merge for a conflict, a branch protection, a forbidden merge method, or a head that moved, or the merge landed but the local switch to the default branch or the branch deletion failed.
110
+ The command requires the inferred pull request to be the one `pr` published and to target the repository default branch before the irreversible merge, and confirms that same pull request's merged state and the default-branch checkout after the merge command succeeds; a queued pull request is not a merged result.
111
+ Merge refused is an authored failure that leaves the pull request in the state GitHub reports: the checkout no longer infers the published pull request, the pull request targets another branch, GitHub refused the merge for a conflict, a branch protection, a forbidden merge method, or a head that moved, or the merge may have landed while its confirmation, the local switch to the default branch, or the branch deletion failed.
112
+ A refused merge does not establish that the pull request is unmerged, so `pr` reports it as an unconfirmed merge rather than as not merged, and no outcome claims a branch was deleted, because `gh` skips the remote deletion for a pull request from another repository or one already merged and exits zero anyway.
100
113
 
101
114
  When the pull request is merged, Captain shall bring the local default branch to the merged head by running exactly the following command in the repository, with no other action and without reading its output:
102
115