@mastra/factory 0.21.0-alpha.7 → 0.21.0-alpha.9

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 (131) hide show
  1. package/README.md +72 -1
  2. package/dist/boards/define-board.d.ts +36 -1
  3. package/dist/boards/define-board.d.ts.map +1 -1
  4. package/dist/boards/define-board.js +46 -0
  5. package/dist/boards/define-board.js.map +1 -1
  6. package/dist/boards/index.d.ts +2 -2
  7. package/dist/boards/index.d.ts.map +1 -1
  8. package/dist/boards/index.js +2 -2
  9. package/dist/boards/index.js.map +1 -1
  10. package/dist/boards/semantics.d.ts +34 -1
  11. package/dist/boards/semantics.d.ts.map +1 -1
  12. package/dist/boards/semantics.js +65 -1
  13. package/dist/boards/semantics.js.map +1 -1
  14. package/dist/boards/work.d.ts.map +1 -1
  15. package/dist/boards/work.js +5 -1
  16. package/dist/boards/work.js.map +1 -1
  17. package/dist/factory.d.ts.map +1 -1
  18. package/dist/factory.js +8 -0
  19. package/dist/factory.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/integrations/github/default-rules.d.ts +2 -2
  23. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  24. package/dist/integrations/github/default-rules.js +11 -8
  25. package/dist/integrations/github/default-rules.js.map +1 -1
  26. package/dist/integrations/github/integration.d.ts +18 -0
  27. package/dist/integrations/github/integration.d.ts.map +1 -1
  28. package/dist/integrations/github/integration.js +53 -2
  29. package/dist/integrations/github/integration.js.map +1 -1
  30. package/dist/integrations/github/routes.d.ts +12 -0
  31. package/dist/integrations/github/routes.d.ts.map +1 -1
  32. package/dist/integrations/github/routes.js +63 -1
  33. package/dist/integrations/github/routes.js.map +1 -1
  34. package/dist/integrations/github/rules.d.ts +5 -2
  35. package/dist/integrations/github/rules.d.ts.map +1 -1
  36. package/dist/integrations/github/rules.js +72 -19
  37. package/dist/integrations/github/rules.js.map +1 -1
  38. package/dist/integrations/gitlab/default-rules.d.ts +3 -3
  39. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -1
  40. package/dist/integrations/gitlab/default-rules.js +13 -6
  41. package/dist/integrations/gitlab/default-rules.js.map +1 -1
  42. package/dist/integrations/gitlab/rules.d.ts.map +1 -1
  43. package/dist/integrations/gitlab/rules.js +39 -8
  44. package/dist/integrations/gitlab/rules.js.map +1 -1
  45. package/dist/integrations/incidentio/default-rules.d.ts +3 -3
  46. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -1
  47. package/dist/integrations/incidentio/default-rules.js +8 -6
  48. package/dist/integrations/incidentio/default-rules.js.map +1 -1
  49. package/dist/integrations/incidentio/rules.d.ts.map +1 -1
  50. package/dist/integrations/incidentio/rules.js +6 -3
  51. package/dist/integrations/incidentio/rules.js.map +1 -1
  52. package/dist/integrations/jira/default-rules.d.ts +3 -3
  53. package/dist/integrations/jira/default-rules.d.ts.map +1 -1
  54. package/dist/integrations/jira/default-rules.js +8 -6
  55. package/dist/integrations/jira/default-rules.js.map +1 -1
  56. package/dist/integrations/jira/rules.d.ts.map +1 -1
  57. package/dist/integrations/jira/rules.js +6 -3
  58. package/dist/integrations/jira/rules.js.map +1 -1
  59. package/dist/integrations/linear/default-rules.d.ts +3 -3
  60. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  61. package/dist/integrations/linear/default-rules.js +8 -6
  62. package/dist/integrations/linear/default-rules.js.map +1 -1
  63. package/dist/integrations/linear/routes.js +1 -1
  64. package/dist/integrations/linear/rules.d.ts.map +1 -1
  65. package/dist/integrations/linear/rules.js +6 -3
  66. package/dist/integrations/linear/rules.js.map +1 -1
  67. package/dist/integrations/pull-request-head.d.ts +18 -0
  68. package/dist/integrations/pull-request-head.d.ts.map +1 -0
  69. package/dist/integrations/pull-request-head.js +45 -0
  70. package/dist/integrations/pull-request-head.js.map +1 -0
  71. package/dist/routes/attention-providers.js +3 -3
  72. package/dist/routes/attention-providers.js.map +1 -1
  73. package/dist/routes/contracts.js +1 -1
  74. package/dist/routes/intake.d.ts +3 -0
  75. package/dist/routes/intake.d.ts.map +1 -1
  76. package/dist/routes/intake.js +47 -6
  77. package/dist/routes/intake.js.map +1 -1
  78. package/dist/routes/surface.d.ts.map +1 -1
  79. package/dist/routes/surface.js +3 -2
  80. package/dist/routes/surface.js.map +1 -1
  81. package/dist/routes/work-items.d.ts.map +1 -1
  82. package/dist/routes/work-items.js +3 -4
  83. package/dist/routes/work-items.js.map +1 -1
  84. package/dist/rules/decision-applicability.d.ts +6 -0
  85. package/dist/rules/decision-applicability.d.ts.map +1 -1
  86. package/dist/rules/decision-applicability.js +10 -1
  87. package/dist/rules/decision-applicability.js.map +1 -1
  88. package/dist/rules/dispatcher.d.ts.map +1 -1
  89. package/dist/rules/dispatcher.js +20 -15
  90. package/dist/rules/dispatcher.js.map +1 -1
  91. package/dist/rules/event-rules.d.ts +23 -0
  92. package/dist/rules/event-rules.d.ts.map +1 -0
  93. package/dist/rules/event-rules.js +46 -0
  94. package/dist/rules/event-rules.js.map +1 -0
  95. package/dist/rules/index.js +1 -1
  96. package/dist/rules/processor.js +1 -1
  97. package/dist/rules/review-source-tool.d.ts +5 -3
  98. package/dist/rules/review-source-tool.d.ts.map +1 -1
  99. package/dist/rules/review-source-tool.js.map +1 -1
  100. package/dist/rules/tools.js +1 -1
  101. package/dist/rules/transition-service.js +1 -1
  102. package/dist/rules/types.d.ts +47 -0
  103. package/dist/rules/types.d.ts.map +1 -1
  104. package/dist/rules/types.js.map +1 -1
  105. package/dist/storage/domains/comments/base.d.ts +3 -0
  106. package/dist/storage/domains/comments/base.d.ts.map +1 -1
  107. package/dist/storage/domains/comments/base.js +8 -1
  108. package/dist/storage/domains/comments/base.js.map +1 -1
  109. package/dist/storage/domains/work-items/base.d.ts +7 -3
  110. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  111. package/dist/storage/domains/work-items/base.js +66 -16
  112. package/dist/storage/domains/work-items/base.js.map +1 -1
  113. package/dist/storage/domains/work-items/board-version.d.ts +26 -0
  114. package/dist/storage/domains/work-items/board-version.d.ts.map +1 -0
  115. package/dist/storage/domains/work-items/board-version.js +110 -0
  116. package/dist/storage/domains/work-items/board-version.js.map +1 -0
  117. package/dist/supervisor/health.d.ts.map +1 -1
  118. package/dist/supervisor/health.js +17 -5
  119. package/dist/supervisor/health.js.map +1 -1
  120. package/dist/supervisor/read-tools.d.ts.map +1 -1
  121. package/dist/supervisor/read-tools.js +13 -8
  122. package/dist/supervisor/read-tools.js.map +1 -1
  123. package/dist/supervisor/write-tools.d.ts.map +1 -1
  124. package/dist/supervisor/write-tools.js +5 -1
  125. package/dist/supervisor/write-tools.js.map +1 -1
  126. package/dist/workspace.d.ts.map +1 -1
  127. package/dist/workspace.js +17 -1
  128. package/dist/workspace.js.map +1 -1
  129. package/factory-skills/factory-rereview/SKILL.md +4 -4
  130. package/factory-skills/factory-review/SKILL.md +2 -2
  131. package/package.json +7 -7
package/README.md CHANGED
@@ -117,7 +117,27 @@ const intakeRule = context =>
117
117
 
118
118
  **Migration:** Remove former global `rules.work` and `rules.review` configuration. Built-in customization is deferred; there is no built-in override or replacement API. Define custom-board handlers on their phases instead. Deployments that relied on automatic issue triage or review proposals on arrival should add an appropriate custom-board handler.
119
119
 
120
- There is no global rules object. Every rule has one owner: boards own lifecycle handlers, transition policy, phase semantics, and tool-result rules; integrations own their event handlers. The runtime only executes rules.
120
+ There is no global rules object. Every rule has one owner: boards own lifecycle handlers, transition policy, phase semantics, tool-result rules, and how their cards react to integration events; integrations own their default event handlers. The runtime only executes rules.
121
+
122
+ ### Closing cards when their source closes
123
+
124
+ When a tracked GitHub, GitLab, Linear, or Jira issue or incident.io follow-up is closed, the stock integration rules move its card to a terminal phase of the board the card is on. The board declares which phases with `sourceClosed`: `completed` receives sources that were completed, and `canceled` receives sources that were canceled, not planned, or closed as duplicates. GitLab reports no close reason, so its closures always use `completed`. Both entries must name terminal phases.
125
+
126
+ ```typescript
127
+ const releaseBoard = defineBoard({
128
+ id: 'release',
129
+ title: 'Release',
130
+ initialPhase: 'queued',
131
+ sourceClosed: { completed: 'shipped', canceled: 'dropped' },
132
+ phases: {
133
+ queued: { title: 'Queued', kind: 'resting', outcomes: { ship: 'shipped', drop: 'dropped' } },
134
+ shipped: { title: 'Shipped', kind: 'terminal' },
135
+ dropped: { title: 'Dropped', kind: 'terminal' },
136
+ },
137
+ });
138
+ ```
139
+
140
+ Work declares `{ completed: 'done', canceled: 'canceled' }`. A board without `sourceClosed` leaves its cards where they are, and cards already in a terminal phase are never moved. The move is an ordinary transition, so the board must allow it from the card's current phase. Integration rule overrides receive the same targets as `context.boardClosure`.
121
141
 
122
142
  ### Board tool-result rules
123
143
 
@@ -148,6 +168,57 @@ The handler receives the bound item, actor, board, tool name, normalized result,
148
168
 
149
169
  Work declares one rule: `submit_plan`. When a `plan`-seated agent on a Planning card reports a result starting with `Plan approved.`, the card transitions to Execute. Review declares none. Resolution is fail-closed: a tool result on a card whose board is not installed, or whose board does not declare that tool, fires no rule — a custom board inherits nothing from Work even if it reuses Work's phase names.
150
170
 
171
+ ### Board event rules
172
+
173
+ A board can declare how its own cards react to GitHub, GitLab, Linear, Jira, and incident.io events. Declare handlers under `events`, keyed by integration and then by event name:
174
+
175
+ ```ts
176
+ import { defineBoard } from '@mastra/factory';
177
+
178
+ const deliveryBoard = defineBoard({
179
+ id: 'delivery',
180
+ title: 'Delivery',
181
+ initialPhase: 'triage',
182
+ phases: {/* ... */},
183
+ events: {
184
+ github: {
185
+ pullRequestMerged: context => ({
186
+ type: 'sendMessage',
187
+ idempotencyKey: `${context.ingress.id}:verify-merge`,
188
+ role: 'work',
189
+ message: `Pull request #${context.pullRequest?.number} merged. Verify the work is complete.`,
190
+ }),
191
+ pullRequestCommentCreated: null,
192
+ },
193
+ linear: {
194
+ issueClosed: context =>
195
+ context.issue.stateType === 'canceled' && context.phase?.outcomes.includes('canceled')
196
+ ? {
197
+ type: 'transition',
198
+ idempotencyKey: `${context.ingress.id}:canceled`,
199
+ board: 'delivery',
200
+ stage: 'canceled',
201
+ }
202
+ : undefined,
203
+ },
204
+ },
205
+ });
206
+ ```
207
+
208
+ A board handler runs only for events bound to a card on that board. Events without a card yet — issue intake and filing a pull request's own Review card — always use the integration's handlers.
209
+
210
+ For each event, Factory picks the handler in this order:
211
+
212
+ 1. A handler the deployment set explicitly in the integration's `rules` (for example `new PlatformGithubIntegration({ rules })`). Existing integration-level handlers keep working unchanged.
213
+ 2. The card's board handler. `null` switches the event off for cards on that board only; cards on other boards are unaffected.
214
+ 3. The integration default.
215
+
216
+ A board handler that returns a decision replaces the default for that card. One that returns nothing lets the default run, so card-independent defaults such as dismissing stale Factory reviews still apply. The stock Work and Review behavior stays in the integration defaults and never acts on cards on other boards, so a custom board needs no board guards and no delegation to stock handlers.
217
+
218
+ Every integration rule context bound to a card carries `phase`: `{ id, kind, role?, outcomes }`, describing what the card's current phase means on its board. `outcomes` lists the outcome names the phase declares. `phase` is absent when the card sits in more than one stage or its board does not declare the phase.
219
+
220
+ Unknown integrations, unknown event names, and handler values other than a function or `null` are rejected by `defineBoard` with a `BoardDefinitionError`.
221
+
151
222
  ### Config version
152
223
 
153
224
  `configVersion` is an operator-maintained deployment label stamped onto transition audit rows, deferred decisions, reconciler audit, and the session kickoff header (`Config: …`). Nothing branches on it; it exists so an audit row can be traced back to the deployment that produced it. It defaults to `factory-config-v1` and must be a non-empty bounded string. The storage column keeps its shipped name, `rule_set_version`.
@@ -1,4 +1,4 @@
1
- import type { FactoryRuleHandler, FactoryRuleSource, FactoryStageRuleContext, FactoryToolResultRuleContext } from '../rules/types.js';
1
+ import type { FactoryGithubEventName, FactoryGithubRuleContext, FactoryGitLabEventName, FactoryGitLabRuleContext, FactoryIncidentioEventName, FactoryIncidentioRuleContext, FactoryJiraEventName, FactoryJiraRuleContext, FactoryLinearEventName, FactoryLinearRuleContext, FactoryRuleHandler, FactoryRuleSource, FactoryStageRuleContext, FactoryToolResultRuleContext } from '../rules/types.js';
2
2
  import type { BoardTransitionPolicy } from './transition-policy.js';
3
3
  type BoardPhaseHandlers = Partial<Record<FactoryRuleSource, FactoryRuleHandler<FactoryStageRuleContext>>>;
4
4
  type ReadonlyBoardPhaseHandlers = Readonly<BoardPhaseHandlers>;
@@ -8,6 +8,24 @@ export type BoardToolRule = {
8
8
  readonly onResult: BoardToolResultRuleHandler;
9
9
  };
10
10
  export type BoardToolRules = Readonly<Record<string, BoardToolRule>>;
11
+ /**
12
+ * A board's handler for one integration event. `null` switches the integration default off
13
+ * for cards on this board only.
14
+ */
15
+ type BoardEventHandlerMap<EventName extends string, Context> = Readonly<Partial<Record<EventName, FactoryRuleHandler<Context> | null>>>;
16
+ /**
17
+ * How cards on this board react to integration events, keyed by integration then event.
18
+ * Consulted for events bound to a card on this board, after any handler the deployment set
19
+ * explicitly on the integration and before the integration default.
20
+ */
21
+ export interface BoardEventRules {
22
+ readonly github?: BoardEventHandlerMap<FactoryGithubEventName, FactoryGithubRuleContext>;
23
+ readonly gitlab?: BoardEventHandlerMap<FactoryGitLabEventName, FactoryGitLabRuleContext>;
24
+ readonly linear?: BoardEventHandlerMap<FactoryLinearEventName, FactoryLinearRuleContext>;
25
+ readonly jira?: BoardEventHandlerMap<FactoryJiraEventName, FactoryJiraRuleContext>;
26
+ readonly incidentio?: BoardEventHandlerMap<FactoryIncidentioEventName, FactoryIncidentioRuleContext>;
27
+ }
28
+ export type BoardEventIntegration = keyof BoardEventRules;
11
29
  type ReadonlyFactoryBoardRules = Readonly<Record<string, Readonly<Partial<Record<FactoryRuleSource, Readonly<{
12
30
  onEnter?: FactoryRuleHandler<FactoryStageRuleContext>;
13
31
  onExit?: FactoryRuleHandler<FactoryStageRuleContext>;
@@ -40,6 +58,14 @@ export interface BoardTransition<PhaseId extends string> {
40
58
  readonly outcome: string | null;
41
59
  readonly to: PhaseId;
42
60
  }
61
+ /**
62
+ * Terminal phases a card moves to when its tracked source (issue or follow-up) closes.
63
+ * `completed` is used when the source was completed, `canceled` when it was canceled or not planned.
64
+ */
65
+ export type BoardSourceClosed<PhaseId extends string> = Readonly<{
66
+ completed: PhaseId;
67
+ canceled: PhaseId;
68
+ }>;
43
69
  export interface BoardDefinition<BoardId extends string, PhaseId extends string> {
44
70
  readonly id: BoardId;
45
71
  readonly title: string;
@@ -49,7 +75,11 @@ export interface BoardDefinition<BoardId extends string, PhaseId extends string>
49
75
  readonly rules: ReadonlyFactoryBoardRules;
50
76
  /** Tool-result rules keyed by tool name; empty when the board declares none. */
51
77
  readonly tools: BoardToolRules;
78
+ /** Integration event handlers for cards on this board; empty when the board declares none. */
79
+ readonly events: BoardEventRules;
52
80
  readonly transitionPolicy?: BoardTransitionPolicy;
81
+ /** Where source-closed rules move a card; absent when the board opts out of auto-closing. */
82
+ readonly sourceClosed?: BoardSourceClosed<PhaseId>;
53
83
  allowsTransition(from: PhaseId, to: PhaseId): boolean;
54
84
  /** Declared kind of a phase, or undefined when the board has no such phase. */
55
85
  phaseKind(phase: string): BoardPhaseKind | undefined;
@@ -66,7 +96,12 @@ type BoardConfig<BoardId extends string, Phases extends Record<string, BoardPhas
66
96
  initialPhase: keyof Phases & string;
67
97
  phases: Phases;
68
98
  tools?: Record<string, BoardToolRule>;
99
+ events?: BoardEventRules;
69
100
  transitionPolicy?: BoardTransitionPolicy;
101
+ sourceClosed?: {
102
+ completed: keyof Phases & string;
103
+ canceled: keyof Phases & string;
104
+ };
70
105
  };
71
106
  export declare class BoardDefinitionError extends Error {
72
107
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"define-board.d.ts","sourceRoot":"","sources":["../../src/boards/define-board.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"define-board.d.ts","sourceRoot":"","sources":["../../src/boards/define-board.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,13 @@
1
+ import { FACTORY_GITHUB_EVENTS, FACTORY_GITLAB_EVENTS, FACTORY_INCIDENTIO_EVENTS, FACTORY_JIRA_EVENTS, FACTORY_LINEAR_EVENTS } from "../rules/types.js";
1
2
  import { IDENTIFIER_RE, isBoardIdentifier } from "../rules/validation.js";
2
3
  //#region src/boards/define-board.ts
4
+ const BOARD_EVENT_NAMES = {
5
+ github: FACTORY_GITHUB_EVENTS,
6
+ gitlab: FACTORY_GITLAB_EVENTS,
7
+ linear: FACTORY_LINEAR_EVENTS,
8
+ jira: FACTORY_JIRA_EVENTS,
9
+ incidentio: FACTORY_INCIDENTIO_EVENTS
10
+ };
3
11
  var BoardDefinitionError = class extends Error {
4
12
  name = "BoardDefinitionError";
5
13
  };
@@ -35,10 +43,45 @@ function validateToolRules(tools) {
35
43
  }
36
44
  return Object.freeze(frozen);
37
45
  }
46
+ function validateSourceClosed(sourceClosed, phases) {
47
+ if (sourceClosed === void 0) return void 0;
48
+ if (!isPlainObject(sourceClosed)) throw new BoardDefinitionError("Board sourceClosed must be a plain object.");
49
+ const keys = Object.keys(sourceClosed);
50
+ if (keys.length !== 2 || !keys.includes("completed") || !keys.includes("canceled")) throw new BoardDefinitionError("Board sourceClosed must declare exactly completed and canceled.");
51
+ for (const key of ["completed", "canceled"]) {
52
+ const phase = sourceClosed[key];
53
+ if (typeof phase !== "string" || !Object.hasOwn(phases, phase) || phases[phase].kind !== "terminal") throw new BoardDefinitionError(`Board sourceClosed.${key} "${String(phase)}" must name a terminal phase.`);
54
+ }
55
+ return Object.freeze({
56
+ completed: sourceClosed.completed,
57
+ canceled: sourceClosed.canceled
58
+ });
59
+ }
60
+ function validateEventRules(events) {
61
+ if (events === void 0) return Object.freeze({});
62
+ if (!isPlainObject(events)) throw new BoardDefinitionError("Board events must be a plain object.");
63
+ const frozen = {};
64
+ for (const [integration, handlers] of Object.entries(events)) {
65
+ const known = Object.hasOwn(BOARD_EVENT_NAMES, integration) ? BOARD_EVENT_NAMES[integration] : void 0;
66
+ if (!known) throw new BoardDefinitionError(`Board events name unknown integration "${integration}".`);
67
+ if (handlers === void 0) continue;
68
+ if (!isPlainObject(handlers)) throw new BoardDefinitionError(`Board events for "${integration}" must be a plain object.`);
69
+ const entries = {};
70
+ for (const [event, handler] of Object.entries(handlers)) {
71
+ if (!known.includes(event)) throw new BoardDefinitionError(`Board events for "${integration}" name unknown event "${event}".`);
72
+ if (handler === void 0) continue;
73
+ if (handler !== null && typeof handler !== "function") throw new BoardDefinitionError(`Board event handler "${integration}.${event}" must be a function or null.`);
74
+ entries[event] = handler;
75
+ }
76
+ frozen[integration] = Object.freeze(entries);
77
+ }
78
+ return Object.freeze(frozen);
79
+ }
38
80
  function defineBoard(config) {
39
81
  validateBoardIdentifier(config.id, "Board id");
40
82
  if (config.transitionPolicy !== void 0 && typeof config.transitionPolicy !== "function") throw new BoardDefinitionError("Board transitionPolicy must be a function.");
41
83
  const tools = validateToolRules(config.tools);
84
+ const events = validateEventRules(config.events);
42
85
  const phaseIds = new Set(Object.keys(config.phases));
43
86
  if (phaseIds.size === 0) throw new BoardDefinitionError("A board must define at least one phase.");
44
87
  if (!phaseIds.has(config.initialPhase)) throw new BoardDefinitionError(`Initial phase "${config.initialPhase}" is not defined.`);
@@ -47,6 +90,7 @@ function defineBoard(config) {
47
90
  validatePhaseSemantics(phaseId, phase);
48
91
  }
49
92
  if (config.phases[config.initialPhase].kind !== "resting") throw new BoardDefinitionError(`Initial phase "${config.initialPhase}" must be a resting phase.`);
93
+ const sourceClosed = validateSourceClosed(config.sourceClosed, config.phases);
50
94
  const transitions = Object.fromEntries(Object.entries(config.phases).map(([phaseId, phase]) => {
51
95
  if (phase.next !== void 0 && phase.outcomes) throw new BoardDefinitionError(`Phase "${phaseId}" cannot define both next and outcomes.`);
52
96
  const targets = phase.next !== void 0 ? [Object.freeze({
@@ -93,7 +137,9 @@ function defineBoard(config) {
93
137
  transitions: Object.freeze(transitions),
94
138
  rules: Object.freeze(rules),
95
139
  tools,
140
+ events,
96
141
  ...config.transitionPolicy ? { transitionPolicy: config.transitionPolicy } : {},
142
+ ...sourceClosed ? { sourceClosed } : {},
97
143
  allowsTransition(from, to) {
98
144
  return from === to || transitions[from]?.some((transition) => transition.to === to) === true;
99
145
  },
@@ -1 +1 @@
1
- {"version":3,"file":"define-board.js","names":[],"sources":["../../src/boards/define-board.ts"],"sourcesContent":["import type {\n FactoryRuleHandler,\n FactoryRuleSource,\n FactoryStageRuleContext,\n FactoryToolResultRuleContext,\n} from '../rules/types.js';\nimport { IDENTIFIER_RE, isBoardIdentifier, MAX_ROLE_LENGTH, MAX_TOOL_NAME_LENGTH } from '../rules/validation.js';\nimport type { BoardTransitionPolicy } from './transition-policy.js';\n\ntype BoardPhaseHandlers = Partial<Record<FactoryRuleSource, FactoryRuleHandler<FactoryStageRuleContext>>>;\ntype ReadonlyBoardPhaseHandlers = Readonly<BoardPhaseHandlers>;\n\n/** Runs when an agent seated on this board completes the named tool. */\nexport type BoardToolResultRuleHandler = FactoryRuleHandler<FactoryToolResultRuleContext>;\nexport type BoardToolRule = { readonly onResult: BoardToolResultRuleHandler };\nexport type BoardToolRules = Readonly<Record<string, BoardToolRule>>;\n\ntype ReadonlyFactoryBoardRules = Readonly<\n Record<\n string,\n Readonly<\n Partial<\n Record<\n FactoryRuleSource,\n Readonly<{\n onEnter?: FactoryRuleHandler<FactoryStageRuleContext>;\n onExit?: FactoryRuleHandler<FactoryStageRuleContext>;\n }>\n >\n >\n >\n >\n>;\n\n/**\n * What a phase means to the runtime.\n * - `resting`: nobody is seated; a card waits for a person or an event.\n * - `working`: an agent in `role` carries the card; a person's move in arms autonomy.\n * - `terminal`: the card is finished; sessions stop and held resources are released.\n */\nexport type BoardPhaseKind = 'resting' | 'working' | 'terminal';\n\ntype BoardPhaseBase<PhaseId extends string> = {\n readonly title: string;\n readonly next?: PhaseId;\n readonly outcomes?: Readonly<Record<string, PhaseId>>;\n readonly onEnter?: ReadonlyBoardPhaseHandlers;\n readonly onExit?: ReadonlyBoardPhaseHandlers;\n};\n\nexport type BoardPhaseDefinition<PhaseId extends string> =\n | (BoardPhaseBase<PhaseId> & { readonly kind: 'resting'; readonly role?: never })\n | (BoardPhaseBase<PhaseId> & { readonly kind: 'working'; readonly role: string })\n | (BoardPhaseBase<PhaseId> & { readonly kind: 'terminal'; readonly role?: never });\n\nexport interface BoardTransition<PhaseId extends string> {\n readonly outcome: string | null;\n readonly to: PhaseId;\n}\n\nexport interface BoardDefinition<BoardId extends string, PhaseId extends string> {\n readonly id: BoardId;\n readonly title: string;\n readonly initialPhase: PhaseId;\n readonly phases: Readonly<Record<PhaseId, BoardPhaseDefinition<PhaseId>>>;\n readonly transitions: Readonly<Record<PhaseId, readonly BoardTransition<PhaseId>[]>>;\n readonly rules: ReadonlyFactoryBoardRules;\n /** Tool-result rules keyed by tool name; empty when the board declares none. */\n readonly tools: BoardToolRules;\n readonly transitionPolicy?: BoardTransitionPolicy;\n allowsTransition(from: PhaseId, to: PhaseId): boolean;\n /** Declared kind of a phase, or undefined when the board has no such phase. */\n phaseKind(phase: string): BoardPhaseKind | undefined;\n isWorking(phase: string): boolean;\n isTerminal(phase: string): boolean;\n /** Role seated in a working phase; undefined for resting/terminal/unknown phases. */\n roleForPhase(phase: string): string | undefined;\n /** First working phase, in declaration order, carried by `role`. */\n phaseForRole(role: string): PhaseId | undefined;\n}\n\ntype BoardConfig<BoardId extends string, Phases extends Record<string, BoardPhaseDefinition<keyof Phases & string>>> = {\n id: BoardId;\n title: string;\n initialPhase: keyof Phases & string;\n phases: Phases;\n tools?: Record<string, BoardToolRule>;\n transitionPolicy?: BoardTransitionPolicy;\n};\n\nexport class BoardDefinitionError extends Error {\n override name = 'BoardDefinitionError';\n}\n\nconst PHASE_KINDS: ReadonlySet<string> = new Set<BoardPhaseKind>(['resting', 'working', 'terminal']);\n\nfunction validateBoardIdentifier(value: unknown, label: string): void {\n if (!isBoardIdentifier(value)) {\n throw new BoardDefinitionError(`${label} must be a valid board identifier.`);\n }\n}\n\nfunction validatePhaseSemantics(phaseId: string, phase: BoardPhaseDefinition<string>): void {\n if (!PHASE_KINDS.has(phase.kind as string)) {\n throw new BoardDefinitionError(`Phase \"${phaseId}\" must declare kind 'resting', 'working', or 'terminal'.`);\n }\n if (phase.kind === 'working') {\n const role: unknown = phase.role;\n if (typeof role !== 'string' || role.length === 0 || role.length > MAX_ROLE_LENGTH || !IDENTIFIER_RE.test(role)) {\n throw new BoardDefinitionError(`Working phase \"${phaseId}\" must declare a valid role.`);\n }\n } else if ((phase as { role?: unknown }).role !== undefined) {\n throw new BoardDefinitionError(`Phase \"${phaseId}\" is ${phase.kind} and cannot declare a role.`);\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction validateToolRules(tools: unknown): BoardToolRules {\n if (tools === undefined) return Object.freeze({});\n if (!isPlainObject(tools)) throw new BoardDefinitionError('Board tools must be a plain object.');\n const frozen: Record<string, BoardToolRule> = {};\n for (const [toolName, leaf] of Object.entries(tools)) {\n if (toolName.length === 0 || toolName.length > MAX_TOOL_NAME_LENGTH || !IDENTIFIER_RE.test(toolName)) {\n throw new BoardDefinitionError(`Tool rule \"${toolName}\" has an invalid tool name.`);\n }\n if (!isPlainObject(leaf) || Object.keys(leaf).some(key => key !== 'onResult')) {\n throw new BoardDefinitionError(`Tool rule \"${toolName}\" must be an object with only onResult.`);\n }\n if (typeof leaf.onResult !== 'function') {\n throw new BoardDefinitionError(`Tool rule \"${toolName}\" must declare an onResult function.`);\n }\n frozen[toolName] = Object.freeze({ onResult: leaf.onResult as BoardToolResultRuleHandler });\n }\n return Object.freeze(frozen);\n}\n\nexport function defineBoard<\n const BoardId extends string,\n const Phases extends Record<string, BoardPhaseDefinition<keyof Phases & string>>,\n>(config: BoardConfig<BoardId, Phases>): BoardDefinition<BoardId, keyof Phases & string> {\n type PhaseId = keyof Phases & string;\n validateBoardIdentifier(config.id, 'Board id');\n if (config.transitionPolicy !== undefined && typeof config.transitionPolicy !== 'function') {\n throw new BoardDefinitionError('Board transitionPolicy must be a function.');\n }\n const tools = validateToolRules(config.tools);\n const phaseIds = new Set(Object.keys(config.phases));\n if (phaseIds.size === 0) throw new BoardDefinitionError('A board must define at least one phase.');\n if (!phaseIds.has(config.initialPhase)) {\n throw new BoardDefinitionError(`Initial phase \"${config.initialPhase}\" is not defined.`);\n }\n for (const [phaseId, phase] of Object.entries(config.phases)) {\n validateBoardIdentifier(phaseId, 'Phase id');\n validatePhaseSemantics(phaseId, phase);\n }\n if (config.phases[config.initialPhase]!.kind !== 'resting') {\n throw new BoardDefinitionError(`Initial phase \"${config.initialPhase}\" must be a resting phase.`);\n }\n\n const transitions = Object.fromEntries(\n Object.entries(config.phases).map(([phaseId, phase]) => {\n if (phase.next !== undefined && phase.outcomes) {\n throw new BoardDefinitionError(`Phase \"${phaseId}\" cannot define both next and outcomes.`);\n }\n const targets: BoardTransition<string>[] =\n phase.next !== undefined\n ? [Object.freeze({ outcome: null, to: phase.next })]\n : Object.entries(phase.outcomes ?? {}).map(([outcome, to]) => Object.freeze({ outcome, to }));\n for (const { to } of targets) {\n if (!phaseIds.has(to)) {\n throw new BoardDefinitionError(`Phase \"${phaseId}\" targets undefined phase \"${to}\".`);\n }\n }\n return [phaseId, Object.freeze(targets)];\n }),\n ) as Record<PhaseId, readonly BoardTransition<PhaseId>[]>;\n\n const phases = Object.freeze(\n Object.fromEntries(\n Object.entries(config.phases).map(([phaseId, phase]) => [\n phaseId,\n Object.freeze({\n title: phase.title,\n kind: phase.kind,\n ...(phase.kind === 'working' ? { role: phase.role } : {}),\n ...(phase.next !== undefined ? { next: phase.next } : {}),\n ...(phase.outcomes ? { outcomes: Object.freeze({ ...phase.outcomes }) } : {}),\n ...(phase.onEnter ? { onEnter: Object.freeze({ ...phase.onEnter }) } : {}),\n ...(phase.onExit ? { onExit: Object.freeze({ ...phase.onExit }) } : {}),\n }),\n ]),\n ),\n ) as Readonly<Record<PhaseId, BoardPhaseDefinition<PhaseId>>>;\n const rules = Object.freeze(\n Object.fromEntries(\n Object.entries(config.phases).flatMap(([phaseId, phase]) => {\n const sources = new Set([...Object.keys(phase.onEnter ?? {}), ...Object.keys(phase.onExit ?? {})]);\n if (sources.size === 0) return [];\n return [\n [\n phaseId,\n Object.freeze(\n Object.fromEntries(\n [...sources].map(source => [\n source,\n Object.freeze({\n ...(phase.onEnter?.[source as FactoryRuleSource]\n ? { onEnter: phase.onEnter[source as FactoryRuleSource] }\n : {}),\n ...(phase.onExit?.[source as FactoryRuleSource]\n ? { onExit: phase.onExit[source as FactoryRuleSource] }\n : {}),\n }),\n ]),\n ),\n ),\n ],\n ];\n }),\n ),\n ) as ReadonlyFactoryBoardRules;\n\n const kinds = new Map<string, BoardPhaseKind>();\n const roles = new Map<string, string>();\n const phaseByRole = new Map<string, PhaseId>();\n for (const [phaseId, phase] of Object.entries(phases) as [PhaseId, BoardPhaseDefinition<PhaseId>][]) {\n kinds.set(phaseId, phase.kind);\n if (phase.kind !== 'working') continue;\n roles.set(phaseId, phase.role);\n if (!phaseByRole.has(phase.role)) phaseByRole.set(phase.role, phaseId);\n }\n\n return Object.freeze({\n id: config.id,\n title: config.title,\n initialPhase: config.initialPhase,\n phases,\n transitions: Object.freeze(transitions),\n rules: Object.freeze(rules),\n tools,\n ...(config.transitionPolicy ? { transitionPolicy: config.transitionPolicy } : {}),\n allowsTransition(from: PhaseId, to: PhaseId) {\n return from === to || transitions[from]?.some(transition => transition.to === to) === true;\n },\n phaseKind: (phase: string) => kinds.get(phase),\n isWorking: (phase: string) => kinds.get(phase) === 'working',\n isTerminal: (phase: string) => kinds.get(phase) === 'terminal',\n roleForPhase: (phase: string) => roles.get(phase),\n phaseForRole: (role: string) => phaseByRole.get(role),\n });\n}\n"],"mappings":";;AA0FA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,OAAgB;AAClB;AAEA,MAAM,8BAAmC,IAAI,IAAoB;CAAC;CAAW;CAAW;AAAU,CAAC;AAEnG,SAAS,wBAAwB,OAAgB,OAAqB;CACpE,IAAI,CAAC,kBAAkB,KAAK,GAC1B,MAAM,IAAI,qBAAqB,GAAG,MAAM,mCAAmC;AAE/E;AAEA,SAAS,uBAAuB,SAAiB,OAA2C;CAC1F,IAAI,CAAC,YAAY,IAAI,MAAM,IAAc,GACvC,MAAM,IAAI,qBAAqB,UAAU,QAAQ,yDAAyD;CAE5G,IAAI,MAAM,SAAS,WAAW;EAC5B,MAAM,OAAgB,MAAM;EAC5B,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,KAAK,KAAK,SAAA,MAA4B,CAAC,cAAc,KAAK,IAAI,GAC5G,MAAM,IAAI,qBAAqB,kBAAkB,QAAQ,6BAA6B;CAE1F,OAAO,IAAK,MAA6B,SAAS,KAAA,GAChD,MAAM,IAAI,qBAAqB,UAAU,QAAQ,OAAO,MAAM,KAAK,4BAA4B;AAEnG;AAEA,SAAS,cAAc,OAAkD;CACvE,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GAAG,OAAO;CAChF,MAAM,YAAY,OAAO,eAAe,KAAK;CAC7C,OAAO,cAAc,OAAO,aAAa,cAAc;AACzD;AAEA,SAAS,kBAAkB,OAAgC;CACzD,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO,OAAO,CAAC,CAAC;CAChD,IAAI,CAAC,cAAc,KAAK,GAAG,MAAM,IAAI,qBAAqB,qCAAqC;CAC/F,MAAM,SAAwC,CAAC;CAC/C,KAAK,MAAM,CAAC,UAAU,SAAS,OAAO,QAAQ,KAAK,GAAG;EACpD,IAAI,SAAS,WAAW,KAAK,SAAS,SAAA,OAAiC,CAAC,cAAc,KAAK,QAAQ,GACjG,MAAM,IAAI,qBAAqB,cAAc,SAAS,4BAA4B;EAEpF,IAAI,CAAC,cAAc,IAAI,KAAK,OAAO,KAAK,IAAI,CAAC,CAAC,MAAK,QAAO,QAAQ,UAAU,GAC1E,MAAM,IAAI,qBAAqB,cAAc,SAAS,wCAAwC;EAEhG,IAAI,OAAO,KAAK,aAAa,YAC3B,MAAM,IAAI,qBAAqB,cAAc,SAAS,qCAAqC;EAE7F,OAAO,YAAY,OAAO,OAAO,EAAE,UAAU,KAAK,SAAuC,CAAC;CAC5F;CACA,OAAO,OAAO,OAAO,MAAM;AAC7B;AAEA,SAAgB,YAGd,QAAuF;CAEvF,wBAAwB,OAAO,IAAI,UAAU;CAC7C,IAAI,OAAO,qBAAqB,KAAA,KAAa,OAAO,OAAO,qBAAqB,YAC9E,MAAM,IAAI,qBAAqB,4CAA4C;CAE7E,MAAM,QAAQ,kBAAkB,OAAO,KAAK;CAC5C,MAAM,WAAW,IAAI,IAAI,OAAO,KAAK,OAAO,MAAM,CAAC;CACnD,IAAI,SAAS,SAAS,GAAG,MAAM,IAAI,qBAAqB,yCAAyC;CACjG,IAAI,CAAC,SAAS,IAAI,OAAO,YAAY,GACnC,MAAM,IAAI,qBAAqB,kBAAkB,OAAO,aAAa,kBAAkB;CAEzF,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,OAAO,MAAM,GAAG;EAC5D,wBAAwB,SAAS,UAAU;EAC3C,uBAAuB,SAAS,KAAK;CACvC;CACA,IAAI,OAAO,OAAO,OAAO,aAAa,CAAE,SAAS,WAC/C,MAAM,IAAI,qBAAqB,kBAAkB,OAAO,aAAa,2BAA2B;CAGlG,MAAM,cAAc,OAAO,YACzB,OAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,SAAS,WAAW;EACtD,IAAI,MAAM,SAAS,KAAA,KAAa,MAAM,UACpC,MAAM,IAAI,qBAAqB,UAAU,QAAQ,wCAAwC;EAE3F,MAAM,UACJ,MAAM,SAAS,KAAA,IACX,CAAC,OAAO,OAAO;GAAE,SAAS;GAAM,IAAI,MAAM;EAAK,CAAC,CAAC,IACjD,OAAO,QAAQ,MAAM,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,QAAQ,OAAO,OAAO;GAAE;GAAS;EAAG,CAAC,CAAC;EAChG,KAAK,MAAM,EAAE,QAAQ,SACnB,IAAI,CAAC,SAAS,IAAI,EAAE,GAClB,MAAM,IAAI,qBAAqB,UAAU,QAAQ,6BAA6B,GAAG,GAAG;EAGxF,OAAO,CAAC,SAAS,OAAO,OAAO,OAAO,CAAC;CACzC,CAAC,CACH;CAEA,MAAM,SAAS,OAAO,OACpB,OAAO,YACL,OAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,SAAS,WAAW,CACtD,SACA,OAAO,OAAO;EACZ,OAAO,MAAM;EACb,MAAM,MAAM;EACZ,GAAI,MAAM,SAAS,YAAY,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;EACvD,GAAI,MAAM,SAAS,KAAA,IAAY,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;EACvD,GAAI,MAAM,WAAW,EAAE,UAAU,OAAO,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,EAAE,IAAI,CAAC;EAC3E,GAAI,MAAM,UAAU,EAAE,SAAS,OAAO,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,EAAE,IAAI,CAAC;EACxE,GAAI,MAAM,SAAS,EAAE,QAAQ,OAAO,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC,EAAE,IAAI,CAAC;CACvE,CAAC,CACH,CAAC,CACH,CACF;CACA,MAAM,QAAQ,OAAO,OACnB,OAAO,YACL,OAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,WAAW;EAC1D,MAAM,0BAAU,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,MAAM,WAAW,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC;EACjG,IAAI,QAAQ,SAAS,GAAG,OAAO,CAAC;EAChC,OAAO,CACL,CACE,SACA,OAAO,OACL,OAAO,YACL,CAAC,GAAG,OAAO,CAAC,CAAC,KAAI,WAAU,CACzB,QACA,OAAO,OAAO;GACZ,GAAI,MAAM,UAAU,UAChB,EAAE,SAAS,MAAM,QAAQ,QAA6B,IACtD,CAAC;GACL,GAAI,MAAM,SAAS,UACf,EAAE,QAAQ,MAAM,OAAO,QAA6B,IACpD,CAAC;EACP,CAAC,CACH,CAAC,CACH,CACF,CACF,CACF;CACF,CAAC,CACH,CACF;CAEA,MAAM,wBAAQ,IAAI,IAA4B;CAC9C,MAAM,wBAAQ,IAAI,IAAoB;CACtC,MAAM,8BAAc,IAAI,IAAqB;CAC7C,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,MAAM,GAAiD;EACnG,MAAM,IAAI,SAAS,MAAM,IAAI;EAC7B,IAAI,MAAM,SAAS,WAAW;EAC9B,MAAM,IAAI,SAAS,MAAM,IAAI;EAC7B,IAAI,CAAC,YAAY,IAAI,MAAM,IAAI,GAAG,YAAY,IAAI,MAAM,MAAM,OAAO;CACvE;CAEA,OAAO,OAAO,OAAO;EACnB,IAAI,OAAO;EACX,OAAO,OAAO;EACd,cAAc,OAAO;EACrB;EACA,aAAa,OAAO,OAAO,WAAW;EACtC,OAAO,OAAO,OAAO,KAAK;EAC1B;EACA,GAAI,OAAO,mBAAmB,EAAE,kBAAkB,OAAO,iBAAiB,IAAI,CAAC;EAC/E,iBAAiB,MAAe,IAAa;GAC3C,OAAO,SAAS,MAAM,YAAY,KAAK,EAAE,MAAK,eAAc,WAAW,OAAO,EAAE,MAAM;EACxF;EACA,YAAY,UAAkB,MAAM,IAAI,KAAK;EAC7C,YAAY,UAAkB,MAAM,IAAI,KAAK,MAAM;EACnD,aAAa,UAAkB,MAAM,IAAI,KAAK,MAAM;EACpD,eAAe,UAAkB,MAAM,IAAI,KAAK;EAChD,eAAe,SAAiB,YAAY,IAAI,IAAI;CACtD,CAAC;AACH"}
1
+ {"version":3,"file":"define-board.js","names":[],"sources":["../../src/boards/define-board.ts"],"sourcesContent":["import {\n FACTORY_GITHUB_EVENTS,\n FACTORY_GITLAB_EVENTS,\n FACTORY_INCIDENTIO_EVENTS,\n FACTORY_JIRA_EVENTS,\n FACTORY_LINEAR_EVENTS,\n} from '../rules/types.js';\nimport type {\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryGitLabEventName,\n FactoryGitLabRuleContext,\n FactoryIncidentioEventName,\n FactoryIncidentioRuleContext,\n FactoryJiraEventName,\n FactoryJiraRuleContext,\n FactoryLinearEventName,\n FactoryLinearRuleContext,\n FactoryRuleHandler,\n FactoryRuleSource,\n FactoryStageRuleContext,\n FactoryToolResultRuleContext,\n} from '../rules/types.js';\nimport { IDENTIFIER_RE, isBoardIdentifier, MAX_ROLE_LENGTH, MAX_TOOL_NAME_LENGTH } from '../rules/validation.js';\nimport type { BoardTransitionPolicy } from './transition-policy.js';\n\ntype BoardPhaseHandlers = Partial<Record<FactoryRuleSource, FactoryRuleHandler<FactoryStageRuleContext>>>;\ntype ReadonlyBoardPhaseHandlers = Readonly<BoardPhaseHandlers>;\n\n/** Runs when an agent seated on this board completes the named tool. */\nexport type BoardToolResultRuleHandler = FactoryRuleHandler<FactoryToolResultRuleContext>;\nexport type BoardToolRule = { readonly onResult: BoardToolResultRuleHandler };\nexport type BoardToolRules = Readonly<Record<string, BoardToolRule>>;\n\n/**\n * A board's handler for one integration event. `null` switches the integration default off\n * for cards on this board only.\n */\ntype BoardEventHandlerMap<EventName extends string, Context> = Readonly<\n Partial<Record<EventName, FactoryRuleHandler<Context> | null>>\n>;\n\n/**\n * How cards on this board react to integration events, keyed by integration then event.\n * Consulted for events bound to a card on this board, after any handler the deployment set\n * explicitly on the integration and before the integration default.\n */\nexport interface BoardEventRules {\n readonly github?: BoardEventHandlerMap<FactoryGithubEventName, FactoryGithubRuleContext>;\n readonly gitlab?: BoardEventHandlerMap<FactoryGitLabEventName, FactoryGitLabRuleContext>;\n readonly linear?: BoardEventHandlerMap<FactoryLinearEventName, FactoryLinearRuleContext>;\n readonly jira?: BoardEventHandlerMap<FactoryJiraEventName, FactoryJiraRuleContext>;\n readonly incidentio?: BoardEventHandlerMap<FactoryIncidentioEventName, FactoryIncidentioRuleContext>;\n}\nexport type BoardEventIntegration = keyof BoardEventRules;\n\nconst BOARD_EVENT_NAMES: Readonly<Record<BoardEventIntegration, readonly string[]>> = {\n github: FACTORY_GITHUB_EVENTS,\n gitlab: FACTORY_GITLAB_EVENTS,\n linear: FACTORY_LINEAR_EVENTS,\n jira: FACTORY_JIRA_EVENTS,\n incidentio: FACTORY_INCIDENTIO_EVENTS,\n};\n\ntype ReadonlyFactoryBoardRules = Readonly<\n Record<\n string,\n Readonly<\n Partial<\n Record<\n FactoryRuleSource,\n Readonly<{\n onEnter?: FactoryRuleHandler<FactoryStageRuleContext>;\n onExit?: FactoryRuleHandler<FactoryStageRuleContext>;\n }>\n >\n >\n >\n >\n>;\n\n/**\n * What a phase means to the runtime.\n * - `resting`: nobody is seated; a card waits for a person or an event.\n * - `working`: an agent in `role` carries the card; a person's move in arms autonomy.\n * - `terminal`: the card is finished; sessions stop and held resources are released.\n */\nexport type BoardPhaseKind = 'resting' | 'working' | 'terminal';\n\ntype BoardPhaseBase<PhaseId extends string> = {\n readonly title: string;\n readonly next?: PhaseId;\n readonly outcomes?: Readonly<Record<string, PhaseId>>;\n readonly onEnter?: ReadonlyBoardPhaseHandlers;\n readonly onExit?: ReadonlyBoardPhaseHandlers;\n};\n\nexport type BoardPhaseDefinition<PhaseId extends string> =\n | (BoardPhaseBase<PhaseId> & { readonly kind: 'resting'; readonly role?: never })\n | (BoardPhaseBase<PhaseId> & { readonly kind: 'working'; readonly role: string })\n | (BoardPhaseBase<PhaseId> & { readonly kind: 'terminal'; readonly role?: never });\n\nexport interface BoardTransition<PhaseId extends string> {\n readonly outcome: string | null;\n readonly to: PhaseId;\n}\n\n/**\n * Terminal phases a card moves to when its tracked source (issue or follow-up) closes.\n * `completed` is used when the source was completed, `canceled` when it was canceled or not planned.\n */\nexport type BoardSourceClosed<PhaseId extends string> = Readonly<{ completed: PhaseId; canceled: PhaseId }>;\n\nexport interface BoardDefinition<BoardId extends string, PhaseId extends string> {\n readonly id: BoardId;\n readonly title: string;\n readonly initialPhase: PhaseId;\n readonly phases: Readonly<Record<PhaseId, BoardPhaseDefinition<PhaseId>>>;\n readonly transitions: Readonly<Record<PhaseId, readonly BoardTransition<PhaseId>[]>>;\n readonly rules: ReadonlyFactoryBoardRules;\n /** Tool-result rules keyed by tool name; empty when the board declares none. */\n readonly tools: BoardToolRules;\n /** Integration event handlers for cards on this board; empty when the board declares none. */\n readonly events: BoardEventRules;\n readonly transitionPolicy?: BoardTransitionPolicy;\n /** Where source-closed rules move a card; absent when the board opts out of auto-closing. */\n readonly sourceClosed?: BoardSourceClosed<PhaseId>;\n allowsTransition(from: PhaseId, to: PhaseId): boolean;\n /** Declared kind of a phase, or undefined when the board has no such phase. */\n phaseKind(phase: string): BoardPhaseKind | undefined;\n isWorking(phase: string): boolean;\n isTerminal(phase: string): boolean;\n /** Role seated in a working phase; undefined for resting/terminal/unknown phases. */\n roleForPhase(phase: string): string | undefined;\n /** First working phase, in declaration order, carried by `role`. */\n phaseForRole(role: string): PhaseId | undefined;\n}\n\ntype BoardConfig<BoardId extends string, Phases extends Record<string, BoardPhaseDefinition<keyof Phases & string>>> = {\n id: BoardId;\n title: string;\n initialPhase: keyof Phases & string;\n phases: Phases;\n tools?: Record<string, BoardToolRule>;\n events?: BoardEventRules;\n transitionPolicy?: BoardTransitionPolicy;\n sourceClosed?: { completed: keyof Phases & string; canceled: keyof Phases & string };\n};\n\nexport class BoardDefinitionError extends Error {\n override name = 'BoardDefinitionError';\n}\n\nconst PHASE_KINDS: ReadonlySet<string> = new Set<BoardPhaseKind>(['resting', 'working', 'terminal']);\n\nfunction validateBoardIdentifier(value: unknown, label: string): void {\n if (!isBoardIdentifier(value)) {\n throw new BoardDefinitionError(`${label} must be a valid board identifier.`);\n }\n}\n\nfunction validatePhaseSemantics(phaseId: string, phase: BoardPhaseDefinition<string>): void {\n if (!PHASE_KINDS.has(phase.kind as string)) {\n throw new BoardDefinitionError(`Phase \"${phaseId}\" must declare kind 'resting', 'working', or 'terminal'.`);\n }\n if (phase.kind === 'working') {\n const role: unknown = phase.role;\n if (typeof role !== 'string' || role.length === 0 || role.length > MAX_ROLE_LENGTH || !IDENTIFIER_RE.test(role)) {\n throw new BoardDefinitionError(`Working phase \"${phaseId}\" must declare a valid role.`);\n }\n } else if ((phase as { role?: unknown }).role !== undefined) {\n throw new BoardDefinitionError(`Phase \"${phaseId}\" is ${phase.kind} and cannot declare a role.`);\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction validateToolRules(tools: unknown): BoardToolRules {\n if (tools === undefined) return Object.freeze({});\n if (!isPlainObject(tools)) throw new BoardDefinitionError('Board tools must be a plain object.');\n const frozen: Record<string, BoardToolRule> = {};\n for (const [toolName, leaf] of Object.entries(tools)) {\n if (toolName.length === 0 || toolName.length > MAX_TOOL_NAME_LENGTH || !IDENTIFIER_RE.test(toolName)) {\n throw new BoardDefinitionError(`Tool rule \"${toolName}\" has an invalid tool name.`);\n }\n if (!isPlainObject(leaf) || Object.keys(leaf).some(key => key !== 'onResult')) {\n throw new BoardDefinitionError(`Tool rule \"${toolName}\" must be an object with only onResult.`);\n }\n if (typeof leaf.onResult !== 'function') {\n throw new BoardDefinitionError(`Tool rule \"${toolName}\" must declare an onResult function.`);\n }\n frozen[toolName] = Object.freeze({ onResult: leaf.onResult as BoardToolResultRuleHandler });\n }\n return Object.freeze(frozen);\n}\n\nfunction validateSourceClosed(\n sourceClosed: unknown,\n phases: Readonly<Record<string, BoardPhaseDefinition<string>>>,\n): BoardSourceClosed<string> | undefined {\n if (sourceClosed === undefined) return undefined;\n if (!isPlainObject(sourceClosed)) throw new BoardDefinitionError('Board sourceClosed must be a plain object.');\n const keys = Object.keys(sourceClosed);\n if (keys.length !== 2 || !keys.includes('completed') || !keys.includes('canceled')) {\n throw new BoardDefinitionError('Board sourceClosed must declare exactly completed and canceled.');\n }\n for (const key of ['completed', 'canceled'] as const) {\n const phase = sourceClosed[key];\n if (typeof phase !== 'string' || !Object.hasOwn(phases, phase) || phases[phase]!.kind !== 'terminal') {\n throw new BoardDefinitionError(`Board sourceClosed.${key} \"${String(phase)}\" must name a terminal phase.`);\n }\n }\n return Object.freeze({ completed: sourceClosed.completed as string, canceled: sourceClosed.canceled as string });\n}\n\nfunction validateEventRules(events: unknown): BoardEventRules {\n if (events === undefined) return Object.freeze({});\n if (!isPlainObject(events)) throw new BoardDefinitionError('Board events must be a plain object.');\n const frozen: Record<string, Readonly<Record<string, unknown>>> = {};\n for (const [integration, handlers] of Object.entries(events)) {\n const known = Object.hasOwn(BOARD_EVENT_NAMES, integration)\n ? BOARD_EVENT_NAMES[integration as BoardEventIntegration]\n : undefined;\n if (!known) throw new BoardDefinitionError(`Board events name unknown integration \"${integration}\".`);\n if (handlers === undefined) continue;\n if (!isPlainObject(handlers)) {\n throw new BoardDefinitionError(`Board events for \"${integration}\" must be a plain object.`);\n }\n const entries: Record<string, unknown> = {};\n for (const [event, handler] of Object.entries(handlers)) {\n if (!known.includes(event)) {\n throw new BoardDefinitionError(`Board events for \"${integration}\" name unknown event \"${event}\".`);\n }\n if (handler === undefined) continue;\n if (handler !== null && typeof handler !== 'function') {\n throw new BoardDefinitionError(`Board event handler \"${integration}.${event}\" must be a function or null.`);\n }\n entries[event] = handler;\n }\n frozen[integration] = Object.freeze(entries);\n }\n return Object.freeze(frozen) as BoardEventRules;\n}\n\nexport function defineBoard<\n const BoardId extends string,\n const Phases extends Record<string, BoardPhaseDefinition<keyof Phases & string>>,\n>(config: BoardConfig<BoardId, Phases>): BoardDefinition<BoardId, keyof Phases & string> {\n type PhaseId = keyof Phases & string;\n validateBoardIdentifier(config.id, 'Board id');\n if (config.transitionPolicy !== undefined && typeof config.transitionPolicy !== 'function') {\n throw new BoardDefinitionError('Board transitionPolicy must be a function.');\n }\n const tools = validateToolRules(config.tools);\n const events = validateEventRules(config.events);\n const phaseIds = new Set(Object.keys(config.phases));\n if (phaseIds.size === 0) throw new BoardDefinitionError('A board must define at least one phase.');\n if (!phaseIds.has(config.initialPhase)) {\n throw new BoardDefinitionError(`Initial phase \"${config.initialPhase}\" is not defined.`);\n }\n for (const [phaseId, phase] of Object.entries(config.phases)) {\n validateBoardIdentifier(phaseId, 'Phase id');\n validatePhaseSemantics(phaseId, phase);\n }\n if (config.phases[config.initialPhase]!.kind !== 'resting') {\n throw new BoardDefinitionError(`Initial phase \"${config.initialPhase}\" must be a resting phase.`);\n }\n const sourceClosed = validateSourceClosed(config.sourceClosed, config.phases) as\n | BoardSourceClosed<PhaseId>\n | undefined;\n\n const transitions = Object.fromEntries(\n Object.entries(config.phases).map(([phaseId, phase]) => {\n if (phase.next !== undefined && phase.outcomes) {\n throw new BoardDefinitionError(`Phase \"${phaseId}\" cannot define both next and outcomes.`);\n }\n const targets: BoardTransition<string>[] =\n phase.next !== undefined\n ? [Object.freeze({ outcome: null, to: phase.next })]\n : Object.entries(phase.outcomes ?? {}).map(([outcome, to]) => Object.freeze({ outcome, to }));\n for (const { to } of targets) {\n if (!phaseIds.has(to)) {\n throw new BoardDefinitionError(`Phase \"${phaseId}\" targets undefined phase \"${to}\".`);\n }\n }\n return [phaseId, Object.freeze(targets)];\n }),\n ) as Record<PhaseId, readonly BoardTransition<PhaseId>[]>;\n\n const phases = Object.freeze(\n Object.fromEntries(\n Object.entries(config.phases).map(([phaseId, phase]) => [\n phaseId,\n Object.freeze({\n title: phase.title,\n kind: phase.kind,\n ...(phase.kind === 'working' ? { role: phase.role } : {}),\n ...(phase.next !== undefined ? { next: phase.next } : {}),\n ...(phase.outcomes ? { outcomes: Object.freeze({ ...phase.outcomes }) } : {}),\n ...(phase.onEnter ? { onEnter: Object.freeze({ ...phase.onEnter }) } : {}),\n ...(phase.onExit ? { onExit: Object.freeze({ ...phase.onExit }) } : {}),\n }),\n ]),\n ),\n ) as Readonly<Record<PhaseId, BoardPhaseDefinition<PhaseId>>>;\n const rules = Object.freeze(\n Object.fromEntries(\n Object.entries(config.phases).flatMap(([phaseId, phase]) => {\n const sources = new Set([...Object.keys(phase.onEnter ?? {}), ...Object.keys(phase.onExit ?? {})]);\n if (sources.size === 0) return [];\n return [\n [\n phaseId,\n Object.freeze(\n Object.fromEntries(\n [...sources].map(source => [\n source,\n Object.freeze({\n ...(phase.onEnter?.[source as FactoryRuleSource]\n ? { onEnter: phase.onEnter[source as FactoryRuleSource] }\n : {}),\n ...(phase.onExit?.[source as FactoryRuleSource]\n ? { onExit: phase.onExit[source as FactoryRuleSource] }\n : {}),\n }),\n ]),\n ),\n ),\n ],\n ];\n }),\n ),\n ) as ReadonlyFactoryBoardRules;\n\n const kinds = new Map<string, BoardPhaseKind>();\n const roles = new Map<string, string>();\n const phaseByRole = new Map<string, PhaseId>();\n for (const [phaseId, phase] of Object.entries(phases) as [PhaseId, BoardPhaseDefinition<PhaseId>][]) {\n kinds.set(phaseId, phase.kind);\n if (phase.kind !== 'working') continue;\n roles.set(phaseId, phase.role);\n if (!phaseByRole.has(phase.role)) phaseByRole.set(phase.role, phaseId);\n }\n\n return Object.freeze({\n id: config.id,\n title: config.title,\n initialPhase: config.initialPhase,\n phases,\n transitions: Object.freeze(transitions),\n rules: Object.freeze(rules),\n tools,\n events,\n ...(config.transitionPolicy ? { transitionPolicy: config.transitionPolicy } : {}),\n ...(sourceClosed ? { sourceClosed } : {}),\n allowsTransition(from: PhaseId, to: PhaseId) {\n return from === to || transitions[from]?.some(transition => transition.to === to) === true;\n },\n phaseKind: (phase: string) => kinds.get(phase),\n isWorking: (phase: string) => kinds.get(phase) === 'working',\n isTerminal: (phase: string) => kinds.get(phase) === 'terminal',\n roleForPhase: (phase: string) => roles.get(phase),\n phaseForRole: (role: string) => phaseByRole.get(role),\n });\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  export { BoardDefinitionError, defineBoard } from './define-board.js';
2
- export type { BoardDefinition, BoardPhaseDefinition, BoardPhaseKind, BoardToolResultRuleHandler, BoardToolRule, BoardToolRules, BoardTransition, } from './define-board.js';
3
- export { boardForWorkItem, isTerminalWorkItem, resolveBoardToolRule, resolvePhaseSemantics, workItemPhaseSemantics, } from './semantics.js';
2
+ export type { BoardDefinition, BoardEventIntegration, BoardEventRules, BoardPhaseDefinition, BoardPhaseKind, BoardSourceClosed, BoardToolResultRuleHandler, BoardToolRule, BoardToolRules, BoardTransition, } from './define-board.js';
3
+ export { boardEventHandlers, boardForWorkItem, eventBoardId, isTerminalWorkItem, resolveBoardClosure, resolveBoardToolRule, rulePhaseContext, rulePhaseField, resolvePhaseSemantics, withBoardClosure, workItemPhaseSemantics, } from './semantics.js';
4
4
  export type { PhaseSemantics } from './semantics.js';
5
5
  export type { BoardTransitionPolicy, BoardTransitionPolicyContext, BoardTransitionPolicyResult, } from './transition-policy.js';
6
6
  export { createBoardRegistry, defaultBoards } from './registry.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/boards/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtE,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,0BAA0B,EAC1B,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EACV,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAInE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIhD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,yOAEpD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAKxG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/boards/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtE,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,0BAA0B,EAC1B,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EACV,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAInE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIhD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,yOAEpD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAKxG"}
@@ -1,5 +1,5 @@
1
1
  import { BoardDefinitionError, defineBoard } from "./define-board.js";
2
- import { boardForWorkItem, isTerminalWorkItem, resolveBoardToolRule, resolvePhaseSemantics, workItemPhaseSemantics } from "./semantics.js";
2
+ import { boardEventHandlers, boardForWorkItem, eventBoardId, isTerminalWorkItem, resolveBoardClosure, resolveBoardToolRule, resolvePhaseSemantics, rulePhaseContext, rulePhaseField, withBoardClosure, workItemPhaseSemantics } from "./semantics.js";
3
3
  import { isReviewBoardPhase, reviewBoard } from "./review.js";
4
4
  import { isWorkBoardPhase, workBoard } from "./work.js";
5
5
  import { createBoardRegistry, defaultBoards } from "./registry.js";
@@ -16,6 +16,6 @@ function allowsBuiltInBoardTransition(board, from, to) {
16
16
  return isReviewBoardPhase(from) && isReviewBoardPhase(to) && reviewBoard.allowsTransition(from, to);
17
17
  }
18
18
  //#endregion
19
- export { BoardDefinitionError, allowsBuiltInBoardTransition, boardForWorkItem, builtInBoard, createBoardRegistry, defaultBoards, defineBoard, isTerminalWorkItem, resolveBoardToolRule, resolvePhaseSemantics, reviewBoard, workBoard, workItemPhaseSemantics };
19
+ export { BoardDefinitionError, allowsBuiltInBoardTransition, boardEventHandlers, boardForWorkItem, builtInBoard, createBoardRegistry, defaultBoards, defineBoard, eventBoardId, isTerminalWorkItem, resolveBoardClosure, resolveBoardToolRule, resolvePhaseSemantics, reviewBoard, rulePhaseContext, rulePhaseField, withBoardClosure, workBoard, workItemPhaseSemantics };
20
20
 
21
21
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/boards/index.ts"],"sourcesContent":["export { BoardDefinitionError, defineBoard } from './define-board.js';\nexport type {\n BoardDefinition,\n BoardPhaseDefinition,\n BoardPhaseKind,\n BoardToolResultRuleHandler,\n BoardToolRule,\n BoardToolRules,\n BoardTransition,\n} from './define-board.js';\nexport {\n boardForWorkItem,\n isTerminalWorkItem,\n resolveBoardToolRule,\n resolvePhaseSemantics,\n workItemPhaseSemantics,\n} from './semantics.js';\nexport type { PhaseSemantics } from './semantics.js';\nexport type {\n BoardTransitionPolicy,\n BoardTransitionPolicyContext,\n BoardTransitionPolicyResult,\n} from './transition-policy.js';\nexport { createBoardRegistry, defaultBoards } from './registry.js';\nexport type { BoardRegistry, InstalledBoard } from './registry.js';\nimport { isReviewBoardPhase, reviewBoard } from './review.js';\nimport { isWorkBoardPhase, workBoard } from './work.js';\n\nexport { reviewBoard } from './review.js';\nexport type { ReviewBoardPhase } from './review.js';\nexport { workBoard } from './work.js';\nexport type { WorkBoardPhase } from './work.js';\n\nconst builtInBoards = { work: workBoard, review: reviewBoard } as const;\n\nexport function builtInBoard(board: 'work' | 'review') {\n return builtInBoards[board];\n}\n\nexport function allowsBuiltInBoardTransition(board: 'work' | 'review', from: string, to: string): boolean {\n if (board === 'work') {\n return isWorkBoardPhase(from) && isWorkBoardPhase(to) && workBoard.allowsTransition(from, to);\n }\n return isReviewBoardPhase(from) && isReviewBoardPhase(to) && reviewBoard.allowsTransition(from, to);\n}\n"],"mappings":";;;;;;AAiCA,MAAM,gBAAgB;CAAE,MAAM;CAAW,QAAQ;AAAY;AAE7D,SAAgB,aAAa,OAA0B;CACrD,OAAO,cAAc;AACvB;AAEA,SAAgB,6BAA6B,OAA0B,MAAc,IAAqB;CACxG,IAAI,UAAU,QACZ,OAAO,iBAAiB,IAAI,KAAK,iBAAiB,EAAE,KAAK,UAAU,iBAAiB,MAAM,EAAE;CAE9F,OAAO,mBAAmB,IAAI,KAAK,mBAAmB,EAAE,KAAK,YAAY,iBAAiB,MAAM,EAAE;AACpG"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/boards/index.ts"],"sourcesContent":["export { BoardDefinitionError, defineBoard } from './define-board.js';\nexport type {\n BoardDefinition,\n BoardEventIntegration,\n BoardEventRules,\n BoardPhaseDefinition,\n BoardPhaseKind,\n BoardSourceClosed,\n BoardToolResultRuleHandler,\n BoardToolRule,\n BoardToolRules,\n BoardTransition,\n} from './define-board.js';\nexport {\n boardEventHandlers,\n boardForWorkItem,\n eventBoardId,\n isTerminalWorkItem,\n resolveBoardClosure,\n resolveBoardToolRule,\n rulePhaseContext,\n rulePhaseField,\n resolvePhaseSemantics,\n withBoardClosure,\n workItemPhaseSemantics,\n} from './semantics.js';\nexport type { PhaseSemantics } from './semantics.js';\nexport type {\n BoardTransitionPolicy,\n BoardTransitionPolicyContext,\n BoardTransitionPolicyResult,\n} from './transition-policy.js';\nexport { createBoardRegistry, defaultBoards } from './registry.js';\nexport type { BoardRegistry, InstalledBoard } from './registry.js';\nimport { isReviewBoardPhase, reviewBoard } from './review.js';\nimport { isWorkBoardPhase, workBoard } from './work.js';\n\nexport { reviewBoard } from './review.js';\nexport type { ReviewBoardPhase } from './review.js';\nexport { workBoard } from './work.js';\nexport type { WorkBoardPhase } from './work.js';\n\nconst builtInBoards = { work: workBoard, review: reviewBoard } as const;\n\nexport function builtInBoard(board: 'work' | 'review') {\n return builtInBoards[board];\n}\n\nexport function allowsBuiltInBoardTransition(board: 'work' | 'review', from: string, to: string): boolean {\n if (board === 'work') {\n return isWorkBoardPhase(from) && isWorkBoardPhase(to) && workBoard.allowsTransition(from, to);\n }\n return isReviewBoardPhase(from) && isReviewBoardPhase(to) && reviewBoard.allowsTransition(from, to);\n}\n"],"mappings":";;;;;;AA0CA,MAAM,gBAAgB;CAAE,MAAM;CAAW,QAAQ;AAAY;AAE7D,SAAgB,aAAa,OAA0B;CACrD,OAAO,cAAc;AACvB;AAEA,SAAgB,6BAA6B,OAA0B,MAAc,IAAqB;CACxG,IAAI,UAAU,QACZ,OAAO,iBAAiB,IAAI,KAAK,iBAAiB,EAAE,KAAK,UAAU,iBAAiB,MAAM,EAAE;CAE9F,OAAO,mBAAmB,IAAI,KAAK,mBAAmB,EAAE,KAAK,YAAY,iBAAiB,MAAM,EAAE;AACpG"}
@@ -1,5 +1,6 @@
1
+ import type { FactoryRuleBoardClosure, FactoryRulePhaseContext } from '../rules/types.js';
1
2
  import type { WorkItemRow } from '../storage/domains/work-items/base.js';
2
- import type { BoardPhaseKind, BoardToolResultRuleHandler } from './define-board.js';
3
+ import type { BoardEventIntegration, BoardEventRules, BoardPhaseKind, BoardToolResultRuleHandler } from './define-board.js';
3
4
  import type { BoardRegistry } from './registry.js';
4
5
  export interface PhaseSemantics {
5
6
  readonly kind: BoardPhaseKind;
@@ -23,6 +24,24 @@ export declare function resolvePhaseSemantics(boards: BoardRegistry, boardId: st
23
24
  * not installed or declares no rule for the tool; callers must run nothing in that case.
24
25
  */
25
26
  export declare function resolveBoardToolRule(boards: BoardRegistry, boardId: string, toolName: string): BoardToolResultRuleHandler | undefined;
27
+ /**
28
+ * Integration event handlers the installed board declares for cards on it, or undefined when the
29
+ * card is unbound, the board is not installed, or it declares none for this integration. Within the
30
+ * map, `null` means the board switched that event off for its cards.
31
+ */
32
+ export declare function boardEventHandlers<I extends BoardEventIntegration>(boards: Pick<BoardRegistry, 'get'>, boardId: string | undefined, integration: I): BoardEventRules[I] | undefined;
33
+ /**
34
+ * Board whose event handlers answer an integration evaluation: the bound card's board, except on
35
+ * the evaluation that files a pull request's own Review card, which is about a card that does
36
+ * not exist yet.
37
+ */
38
+ export declare function eventBoardId(context: {
39
+ readonly item?: unknown;
40
+ readonly board?: string;
41
+ readonly pullRequestIntake?: boolean;
42
+ }): string | undefined;
43
+ /** What a card's single current phase means on its installed board, for integration rule contexts. */
44
+ export declare function rulePhaseContext(boards: Pick<BoardRegistry, 'get'>, item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>): FactoryRulePhaseContext | undefined;
26
45
  /**
27
46
  * Whether a persisted card has finished, judged by the board it sits on: the installed
28
47
  * board's phase kind first, then the built-in Work and Review names for rows whose board is
@@ -32,4 +51,18 @@ export declare function resolveBoardToolRule(boards: BoardRegistry, boardId: str
32
51
  export declare function isTerminalWorkItem(boards: BoardRegistry, item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>): boolean;
33
52
  /** Semantics of the single stage a work item currently occupies; undefined for multi-stage rows. */
34
53
  export declare function workItemPhaseSemantics(boards: BoardRegistry, item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>): PhaseSemantics | undefined;
54
+ /**
55
+ * Where source-closed rules send a persisted card: the `sourceClosed` mapping its installed
56
+ * board declares, plus whether the card already sits in a terminal phase. Undefined when the
57
+ * board is not installed or declares no mapping, so closing the source leaves the card alone.
58
+ */
59
+ export declare function resolveBoardClosure(boards: BoardRegistry, item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>): FactoryRuleBoardClosure | undefined;
60
+ /** Rule-context fragment carrying the card's `boardClosure`, empty when the board declares none. */
61
+ export declare function withBoardClosure(boards: BoardRegistry, item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>): {
62
+ boardClosure?: FactoryRuleBoardClosure;
63
+ };
64
+ /** `{ phase }` for spreading into an integration rule context, or `{}` when the phase is unknown. */
65
+ export declare function rulePhaseField(boards: Pick<BoardRegistry, 'get'>, item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>): {
66
+ readonly phase?: FactoryRulePhaseContext;
67
+ };
35
68
  //# sourceMappingURL=semantics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"semantics.d.ts","sourceRoot":"","sources":["../../src/boards/semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,gBAAgB,CAAC,GAAG,MAAM,CAE5F;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,cAAc,GAAG,SAAS,CAM5B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,0BAA0B,GAAG,SAAS,CAExC;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,gBAAgB,GAAG,QAAQ,CAAC,GAC7D,OAAO,CAOT;AAED,oGAAoG;AACpG,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,gBAAgB,GAAG,QAAQ,CAAC,GAC7D,cAAc,GAAG,SAAS,CAI5B"}
1
+ {"version":3,"file":"semantics.d.ts","sourceRoot":"","sources":["../../src/boards/semantics.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,38 @@ function resolveBoardToolRule(boards, boardId, toolName) {
30
30
  return boards.get(boardId)?.tools[toolName]?.onResult;
31
31
  }
32
32
  /**
33
+ * Integration event handlers the installed board declares for cards on it, or undefined when the
34
+ * card is unbound, the board is not installed, or it declares none for this integration. Within the
35
+ * map, `null` means the board switched that event off for its cards.
36
+ */
37
+ function boardEventHandlers(boards, boardId, integration) {
38
+ return boardId === void 0 ? void 0 : boards.get(boardId)?.events[integration];
39
+ }
40
+ /**
41
+ * Board whose event handlers answer an integration evaluation: the bound card's board, except on
42
+ * the evaluation that files a pull request's own Review card, which is about a card that does
43
+ * not exist yet.
44
+ */
45
+ function eventBoardId(context) {
46
+ return context.item && !context.pullRequestIntake ? context.board : void 0;
47
+ }
48
+ /** What a card's single current phase means on its installed board, for integration rule contexts. */
49
+ function rulePhaseContext(boards, item) {
50
+ const stage = item.stages.length === 1 ? item.stages[0] : void 0;
51
+ if (!stage) return void 0;
52
+ const board = boards.get(boardForWorkItem(item));
53
+ const kind = board?.phaseKind(stage);
54
+ if (!board || !kind) return void 0;
55
+ const role = board.roleForPhase(stage);
56
+ const outcomes = Object.freeze(Object.keys(board.phases[stage]?.outcomes ?? {}));
57
+ return Object.freeze({
58
+ id: stage,
59
+ kind,
60
+ ...role === void 0 ? {} : { role },
61
+ outcomes
62
+ });
63
+ }
64
+ /**
33
65
  * Whether a persisted card has finished, judged by the board it sits on: the installed
34
66
  * board's phase kind first, then the built-in Work and Review names for rows whose board is
35
67
  * not installed. Unknown boards or phases are never terminal, so a card is only ever
@@ -49,7 +81,39 @@ function workItemPhaseSemantics(boards, item) {
49
81
  if (!stage) return void 0;
50
82
  return resolvePhaseSemantics(boards, boardForWorkItem(item), stage);
51
83
  }
84
+ /**
85
+ * Where source-closed rules send a persisted card: the `sourceClosed` mapping its installed
86
+ * board declares, plus whether the card already sits in a terminal phase. Undefined when the
87
+ * board is not installed or declares no mapping, so closing the source leaves the card alone.
88
+ */
89
+ function resolveBoardClosure(boards, item) {
90
+ const board = boards.get(boardForWorkItem(item));
91
+ if (!board?.sourceClosed) return void 0;
92
+ const { completed, canceled } = board.sourceClosed;
93
+ return {
94
+ boardTitle: board.title,
95
+ completed: {
96
+ phase: completed,
97
+ title: board.phases[completed].title
98
+ },
99
+ canceled: {
100
+ phase: canceled,
101
+ title: board.phases[canceled].title
102
+ },
103
+ itemTerminal: item.stages.some((stage) => board.isTerminal(stage))
104
+ };
105
+ }
106
+ /** Rule-context fragment carrying the card's `boardClosure`, empty when the board declares none. */
107
+ function withBoardClosure(boards, item) {
108
+ const boardClosure = resolveBoardClosure(boards, item);
109
+ return boardClosure ? { boardClosure } : {};
110
+ }
111
+ /** `{ phase }` for spreading into an integration rule context, or `{}` when the phase is unknown. */
112
+ function rulePhaseField(boards, item) {
113
+ const phase = rulePhaseContext(boards, item);
114
+ return phase ? { phase } : {};
115
+ }
52
116
  //#endregion
53
- export { boardForWorkItem, isTerminalWorkItem, resolveBoardToolRule, resolvePhaseSemantics, workItemPhaseSemantics };
117
+ export { boardEventHandlers, boardForWorkItem, eventBoardId, isTerminalWorkItem, resolveBoardClosure, resolveBoardToolRule, resolvePhaseSemantics, rulePhaseContext, rulePhaseField, withBoardClosure, workItemPhaseSemantics };
54
118
 
55
119
  //# sourceMappingURL=semantics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"semantics.js","names":[],"sources":["../../src/boards/semantics.ts"],"sourcesContent":["import type { WorkItemRow } from '../storage/domains/work-items/base.js';\nimport type { BoardPhaseKind, BoardToolResultRuleHandler } from './define-board.js';\nimport type { BoardRegistry } from './registry.js';\n\nexport interface PhaseSemantics {\n readonly kind: BoardPhaseKind;\n /** Seat that carries a working phase; absent for resting and terminal phases. */\n readonly role?: string;\n}\n\n/**\n * Board that owns a work item. Rows created before boards were persisted carry no `board`;\n * they belong to Review when they track a pull request and to Work otherwise, matching the\n * legacy assignment the transition service performs on their next move.\n */\nexport function boardForWorkItem(item: Pick<WorkItemRow, 'board' | 'externalSource'>): string {\n return item.board ?? (item.externalSource?.type === 'pull-request' ? 'review' : 'work');\n}\n\n/**\n * What the installed board says `phase` means. Returns undefined when the board is not\n * installed or does not declare the phase; callers must fail closed on undefined rather\n * than guess from the phase name.\n */\nexport function resolvePhaseSemantics(\n boards: BoardRegistry,\n boardId: string,\n phase: string,\n): PhaseSemantics | undefined {\n const board = boards.get(boardId);\n const kind = board?.phaseKind(phase);\n if (!board || !kind) return undefined;\n const role = board.roleForPhase(phase);\n return role === undefined ? { kind } : { kind, role };\n}\n\n/**\n * Tool-result rule the installed board declares for `toolName`. Undefined when the board is\n * not installed or declares no rule for the tool; callers must run nothing in that case.\n */\nexport function resolveBoardToolRule(\n boards: BoardRegistry,\n boardId: string,\n toolName: string,\n): BoardToolResultRuleHandler | undefined {\n return boards.get(boardId)?.tools[toolName]?.onResult;\n}\n\n/**\n * Whether a persisted card has finished, judged by the board it sits on: the installed\n * board's phase kind first, then the built-in Work and Review names for rows whose board is\n * not installed. Unknown boards or phases are never terminal, so a card is only ever\n * released from ownership on the board's own say-so.\n */\nexport function isTerminalWorkItem(\n boards: BoardRegistry,\n item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>,\n): boolean {\n const semantics = workItemPhaseSemantics(boards, item);\n if (semantics) return semantics.kind === 'terminal';\n const board = boardForWorkItem(item);\n if (board !== 'work' && board !== 'review') return false;\n const stage = item.stages.length === 1 ? item.stages[0] : undefined;\n return stage === 'done' || stage === 'canceled';\n}\n\n/** Semantics of the single stage a work item currently occupies; undefined for multi-stage rows. */\nexport function workItemPhaseSemantics(\n boards: BoardRegistry,\n item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>,\n): PhaseSemantics | undefined {\n const stage = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!stage) return undefined;\n return resolvePhaseSemantics(boards, boardForWorkItem(item), stage);\n}\n"],"mappings":";;;;;;AAeA,SAAgB,iBAAiB,MAA6D;CAC5F,OAAO,KAAK,UAAU,KAAK,gBAAgB,SAAS,iBAAiB,WAAW;AAClF;;;;;;AAOA,SAAgB,sBACd,QACA,SACA,OAC4B;CAC5B,MAAM,QAAQ,OAAO,IAAI,OAAO;CAChC,MAAM,OAAO,OAAO,UAAU,KAAK;CACnC,IAAI,CAAC,SAAS,CAAC,MAAM,OAAO,KAAA;CAC5B,MAAM,OAAO,MAAM,aAAa,KAAK;CACrC,OAAO,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI;EAAE;EAAM;CAAK;AACtD;;;;;AAMA,SAAgB,qBACd,QACA,SACA,UACwC;CACxC,OAAO,OAAO,IAAI,OAAO,CAAC,EAAE,MAAM,SAAS,EAAE;AAC/C;;;;;;;AAQA,SAAgB,mBACd,QACA,MACS;CACT,MAAM,YAAY,uBAAuB,QAAQ,IAAI;CACrD,IAAI,WAAW,OAAO,UAAU,SAAS;CACzC,MAAM,QAAQ,iBAAiB,IAAI;CACnC,IAAI,UAAU,UAAU,UAAU,UAAU,OAAO;CACnD,MAAM,QAAQ,KAAK,OAAO,WAAW,IAAI,KAAK,OAAO,KAAK,KAAA;CAC1D,OAAO,UAAU,UAAU,UAAU;AACvC;;AAGA,SAAgB,uBACd,QACA,MAC4B;CAC5B,MAAM,QAAQ,KAAK,OAAO,WAAW,IAAI,KAAK,OAAO,KAAK,KAAA;CAC1D,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,OAAO,sBAAsB,QAAQ,iBAAiB,IAAI,GAAG,KAAK;AACpE"}
1
+ {"version":3,"file":"semantics.js","names":[],"sources":["../../src/boards/semantics.ts"],"sourcesContent":["import type { FactoryRuleBoardClosure, FactoryRulePhaseContext } from '../rules/types.js';\nimport type { WorkItemRow } from '../storage/domains/work-items/base.js';\nimport type {\n BoardEventIntegration,\n BoardEventRules,\n BoardPhaseKind,\n BoardToolResultRuleHandler,\n} from './define-board.js';\nimport type { BoardRegistry } from './registry.js';\n\nexport interface PhaseSemantics {\n readonly kind: BoardPhaseKind;\n /** Seat that carries a working phase; absent for resting and terminal phases. */\n readonly role?: string;\n}\n\n/**\n * Board that owns a work item. Rows created before boards were persisted carry no `board`;\n * they belong to Review when they track a pull request and to Work otherwise, matching the\n * legacy assignment the transition service performs on their next move.\n */\nexport function boardForWorkItem(item: Pick<WorkItemRow, 'board' | 'externalSource'>): string {\n return item.board ?? (item.externalSource?.type === 'pull-request' ? 'review' : 'work');\n}\n\n/**\n * What the installed board says `phase` means. Returns undefined when the board is not\n * installed or does not declare the phase; callers must fail closed on undefined rather\n * than guess from the phase name.\n */\nexport function resolvePhaseSemantics(\n boards: BoardRegistry,\n boardId: string,\n phase: string,\n): PhaseSemantics | undefined {\n const board = boards.get(boardId);\n const kind = board?.phaseKind(phase);\n if (!board || !kind) return undefined;\n const role = board.roleForPhase(phase);\n return role === undefined ? { kind } : { kind, role };\n}\n\n/**\n * Tool-result rule the installed board declares for `toolName`. Undefined when the board is\n * not installed or declares no rule for the tool; callers must run nothing in that case.\n */\nexport function resolveBoardToolRule(\n boards: BoardRegistry,\n boardId: string,\n toolName: string,\n): BoardToolResultRuleHandler | undefined {\n return boards.get(boardId)?.tools[toolName]?.onResult;\n}\n\n/**\n * Integration event handlers the installed board declares for cards on it, or undefined when the\n * card is unbound, the board is not installed, or it declares none for this integration. Within the\n * map, `null` means the board switched that event off for its cards.\n */\nexport function boardEventHandlers<I extends BoardEventIntegration>(\n boards: Pick<BoardRegistry, 'get'>,\n boardId: string | undefined,\n integration: I,\n): BoardEventRules[I] | undefined {\n return boardId === undefined ? undefined : boards.get(boardId)?.events[integration];\n}\n\n/**\n * Board whose event handlers answer an integration evaluation: the bound card's board, except on\n * the evaluation that files a pull request's own Review card, which is about a card that does\n * not exist yet.\n */\nexport function eventBoardId(context: {\n readonly item?: unknown;\n readonly board?: string;\n readonly pullRequestIntake?: boolean;\n}): string | undefined {\n return context.item && !context.pullRequestIntake ? context.board : undefined;\n}\n\n/** What a card's single current phase means on its installed board, for integration rule contexts. */\nexport function rulePhaseContext(\n boards: Pick<BoardRegistry, 'get'>,\n item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>,\n): FactoryRulePhaseContext | undefined {\n const stage = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!stage) return undefined;\n const board = boards.get(boardForWorkItem(item));\n const kind = board?.phaseKind(stage);\n if (!board || !kind) return undefined;\n const role = board.roleForPhase(stage);\n const outcomes = Object.freeze(Object.keys(board.phases[stage]?.outcomes ?? {}));\n return Object.freeze({ id: stage, kind, ...(role === undefined ? {} : { role }), outcomes });\n}\n\n/**\n * Whether a persisted card has finished, judged by the board it sits on: the installed\n * board's phase kind first, then the built-in Work and Review names for rows whose board is\n * not installed. Unknown boards or phases are never terminal, so a card is only ever\n * released from ownership on the board's own say-so.\n */\nexport function isTerminalWorkItem(\n boards: BoardRegistry,\n item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>,\n): boolean {\n const semantics = workItemPhaseSemantics(boards, item);\n if (semantics) return semantics.kind === 'terminal';\n const board = boardForWorkItem(item);\n if (board !== 'work' && board !== 'review') return false;\n const stage = item.stages.length === 1 ? item.stages[0] : undefined;\n return stage === 'done' || stage === 'canceled';\n}\n\n/** Semantics of the single stage a work item currently occupies; undefined for multi-stage rows. */\nexport function workItemPhaseSemantics(\n boards: BoardRegistry,\n item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>,\n): PhaseSemantics | undefined {\n const stage = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!stage) return undefined;\n return resolvePhaseSemantics(boards, boardForWorkItem(item), stage);\n}\n\n/**\n * Where source-closed rules send a persisted card: the `sourceClosed` mapping its installed\n * board declares, plus whether the card already sits in a terminal phase. Undefined when the\n * board is not installed or declares no mapping, so closing the source leaves the card alone.\n */\nexport function resolveBoardClosure(\n boards: BoardRegistry,\n item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>,\n): FactoryRuleBoardClosure | undefined {\n const board = boards.get(boardForWorkItem(item));\n if (!board?.sourceClosed) return undefined;\n const { completed, canceled } = board.sourceClosed;\n return {\n boardTitle: board.title,\n completed: { phase: completed, title: board.phases[completed]!.title },\n canceled: { phase: canceled, title: board.phases[canceled]!.title },\n itemTerminal: item.stages.some(stage => board.isTerminal(stage)),\n };\n}\n\n/** Rule-context fragment carrying the card's `boardClosure`, empty when the board declares none. */\nexport function withBoardClosure(\n boards: BoardRegistry,\n item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>,\n): { boardClosure?: FactoryRuleBoardClosure } {\n const boardClosure = resolveBoardClosure(boards, item);\n return boardClosure ? { boardClosure } : {};\n}\n\n/** `{ phase }` for spreading into an integration rule context, or `{}` when the phase is unknown. */\nexport function rulePhaseField(\n boards: Pick<BoardRegistry, 'get'>,\n item: Pick<WorkItemRow, 'board' | 'externalSource' | 'stages'>,\n): { readonly phase?: FactoryRulePhaseContext } {\n const phase = rulePhaseContext(boards, item);\n return phase ? { phase } : {};\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/boards/work.ts"],"names":[],"mappings":"AAmKA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAY3G,eAAO,MAAM,SAAS,oIAwEpB,CAAC;AAEH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc,CAEvE"}
1
+ {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/boards/work.ts"],"names":[],"mappings":"AAmKA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAY3G,eAAO,MAAM,SAAS,oIAyEpB,CAAC;AAEH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc,CAEvE"}
@@ -1,6 +1,6 @@
1
+ import { needsApproval } from "../rules/types.js";
1
2
  import { defineBoard } from "./define-board.js";
2
3
  import { workItemNumber } from "../work-item-branch.js";
3
- import { needsApproval } from "../rules/types.js";
4
4
  import { advanceApprovedPlan } from "./work-tool-rules.js";
5
5
  import { workTransitionPolicy } from "./work-transition-policy.js";
6
6
  //#region src/boards/work.ts
@@ -142,6 +142,10 @@ const workBoard = defineBoard({
142
142
  title: "Work",
143
143
  initialPhase: "intake",
144
144
  transitionPolicy: workTransitionPolicy,
145
+ sourceClosed: {
146
+ completed: "done",
147
+ canceled: "canceled"
148
+ },
145
149
  tools: { submit_plan: { onResult: advanceApprovedPlan } },
146
150
  phases: {
147
151
  intake: {