@boboddy/sdk 0.4.2 → 0.4.3

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.
@@ -2,6 +2,7 @@ import { z, type ZodType } from "zod";
2
2
  import { type AdditionalStepInputBinding, type TypedStepDefinitionSpec } from "../steps/define-step";
3
3
  import { type AnyBinding, type FanOutItemBinding, type LiteralBinding, type SignalsListBinding, type StepOutputBinding, type StepSignalBinding, type WorkItemBinding } from "./define-pipeline";
4
4
  import { type InputAccessor } from "./input-accessor";
5
+ import { type WorkItemTopLevelField } from "./work-item-fields";
5
6
  export type AnyTypedStep = TypedStepDefinitionSpec<any, any, any, any>;
6
7
  type ElementOf<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer U> ? U : never;
7
8
  export type LastStep<T extends ReadonlyArray<AnyTypedStep>> = T extends readonly [...AnyTypedStep[], infer L] ? L extends AnyTypedStep ? L : never : never;
@@ -30,10 +31,31 @@ export type FanOutInputCtx<TInput extends ZodType, TSteps extends ReadonlyArray<
30
31
  item: FanOutItemBinding & FanOutItemType<TSteps, K>;
31
32
  });
32
33
  export type IsAny<T> = 0 extends 1 & T ? true : false;
34
+ /**
35
+ * Work-item accessor for `additionalPipelineInput.bindings(({ workItem }) => ...)`:
36
+ * a top-level property (`workItem.title`, `workItem.platform`,
37
+ * `workItem.url`, ...) for every member of `WORK_ITEM_TOP_LEVEL_FIELDS`, plus
38
+ * `workItem.field(name)` for the platform-specific `fields` bag.
39
+ *
40
+ * Mirrors the default-pipeline-assignment DSL's `WorkItemAssignmentAccessor`
41
+ * (`define-default-pipeline-assignment.ts`) in which properties it exposes —
42
+ * both are generated off the same `WORK_ITEM_TOP_LEVEL_FIELDS` list — but
43
+ * returns a plain `WorkItemBinding` per property rather than a
44
+ * comparator-bound `AssignmentFieldRef`: a regular pipeline input binding
45
+ * has no conditions to compare against, it's just "read this work-item
46
+ * property into this input field."
47
+ *
48
+ * `field`'s optional `TName` type parameter accepts a literal union of known
49
+ * field names (e.g. the `WorkItemFieldName` type `boboddy pipelines pull`
50
+ * generates into `work-item-fields.ts`) for compile-time validation and
51
+ * autocomplete on the field name, exactly like the assignment DSL's
52
+ * `workItem.field<TName>(...)`. Defaults to permissive `string`, so
53
+ * `workItem.field(name)` with no type argument keeps working as before.
54
+ */
33
55
  export type WorkItemAccessor = {
34
- readonly title: WorkItemBinding;
35
- readonly description: WorkItemBinding;
36
- readonly field: (fieldName: string) => WorkItemBinding;
56
+ readonly [K in WorkItemTopLevelField]: WorkItemBinding;
57
+ } & {
58
+ field<TName extends string = string>(fieldName: TName): WorkItemBinding;
37
59
  };
38
60
  export type PinnedWorkItemComment = {
39
61
  createdAt: string;
@@ -91,7 +113,14 @@ export type PipelineMeta<TInput extends ZodType = z.ZodUnknown> = {
91
113
  additionalStepInput?: {
92
114
  schema: ZodType;
93
115
  bindings: (ctx: {
94
- workItemField: (fieldName: string) => WorkItemBinding;
116
+ /**
117
+ * References a platform-specific field (Jira custom field, GitHub
118
+ * label, etc.) in the work item's `fields` bag. `TName` optionally
119
+ * accepts a literal union of known field names (e.g. the generated
120
+ * `WorkItemFieldName`) for compile-time validation and autocomplete,
121
+ * exactly like `WorkItemAccessor.field` above.
122
+ */
123
+ workItemField: <TName extends string = string>(fieldName: TName) => WorkItemBinding;
95
124
  literal: (value: unknown) => LiteralBinding;
96
125
  }) => Partial<Record<string, AdditionalStepInputBinding>>;
97
126
  };
@@ -1,4 +1,5 @@
1
1
  import type { PipelineDefinitionSpec } from "./define-pipeline";
2
+ import { type WorkItemTopLevelField, type WorkItemTopLevelFieldTypeMap } from "./work-item-fields";
2
3
  /**
3
4
  * The reserved filename for the default pipeline assignment file.
4
5
  * This file is NOT treated as a pipeline definition by the push scanner.
@@ -47,31 +48,80 @@ export interface AssignmentGroup {
47
48
  readonly _condition: AssignmentCondition;
48
49
  }
49
50
  type AssignmentNestable = AssignmentLeaf | AssignmentGroup;
50
- /** Comparator-bound reference to a work-item field or context fact. */
51
- export interface AssignmentFieldRef {
52
- eq(value: unknown): AssignmentLeaf;
53
- ne(value: unknown): AssignmentLeaf;
51
+ /**
52
+ * `.gt`/`.gte`/`.lt`/`.lte` are only ever meaningful for numeric fields.
53
+ * Intersected onto `AssignmentFieldRef<T>` only when `T` is (or includes)
54
+ * `number`, so e.g. `workItem.title.gt(...)` doesn't even type-check, let
55
+ * alone appear in autocomplete.
56
+ */
57
+ type NumericComparators = {
54
58
  gt(value: number): AssignmentLeaf;
55
59
  gte(value: number): AssignmentLeaf;
56
60
  lt(value: number): AssignmentLeaf;
57
61
  lte(value: number): AssignmentLeaf;
58
- in(values: ReadonlyArray<unknown>): AssignmentLeaf;
59
- notIn(values: ReadonlyArray<unknown>): AssignmentLeaf;
60
- contains(value: unknown): AssignmentLeaf;
61
- doesNotContain(value: unknown): AssignmentLeaf;
62
- }
62
+ };
63
+ /**
64
+ * Comparator-bound reference to a work-item field or context fact.
65
+ *
66
+ * `T` is the field's value type (defaulting to `unknown`, which preserves
67
+ * the pre-generic behavior for any caller that doesn't otherwise constrain
68
+ * it). `.contains`/`.doesNotContain` narrow to the array element type when
69
+ * `T` is an array — json-rules-engine's `contains`/`doesNotContain`
70
+ * operators check array-membership (or substring-membership for strings)
71
+ * against the fact's actual runtime shape, so an array-typed field's
72
+ * `.contains(...)` should accept one element, not the whole array.
73
+ */
74
+ export type AssignmentFieldRef<T = unknown> = {
75
+ eq(value: T): AssignmentLeaf;
76
+ ne(value: T): AssignmentLeaf;
77
+ in(values: ReadonlyArray<T>): AssignmentLeaf;
78
+ notIn(values: ReadonlyArray<T>): AssignmentLeaf;
79
+ contains(value: T extends ReadonlyArray<infer U> ? U : T): AssignmentLeaf;
80
+ doesNotContain(value: T extends ReadonlyArray<infer U> ? U : T): AssignmentLeaf;
81
+ } & (T extends number ? NumericComparators : Record<string, never>);
82
+ /** Comparator-bound accessor for the work item passed to `defaultPipelineAssignment`. */
83
+ export type WorkItemAssignmentAccessor = {
84
+ [K in WorkItemTopLevelField]: AssignmentFieldRef<WorkItemTopLevelFieldTypeMap[K]>;
85
+ } & {
86
+ /**
87
+ * Accessor for a platform-specific field (Jira custom field, GitHub label,
88
+ * etc.) stored in the work item's `fields` bag. Since `fields` keys and
89
+ * value shapes are platform- and project-dependent, there is no static
90
+ * schema to check either against by default.
91
+ *
92
+ * Two independent, optional type parameters:
93
+ * - `TName` constrains the `name` argument itself — pass a literal union
94
+ * of known field names (e.g. the `WorkItemFieldName` type
95
+ * `boboddy pipelines pull` generates into `work-item-fields.ts`) to get
96
+ * compile-time validation and autocomplete on the field name.
97
+ * - `TValue` types the comparator (`.eq`/`.gte`/etc.) argument, exactly
98
+ * like the top-level accessors above — pass it for a known-shaped
99
+ * custom field (e.g. a numeric one).
100
+ * Both default to permissive types, so `workItem.field(name)` with no
101
+ * type arguments keeps working exactly as before.
102
+ *
103
+ * @example
104
+ * workItem.field("issueType").eq("bug")
105
+ * workItem.field("labels").contains("regression")
106
+ * workItem.field<WorkItemFieldName>("issueType").eq("bug")
107
+ * workItem.field<WorkItemFieldName, number>("storyPoints").gte(3)
108
+ */
109
+ field<TName extends string = string, TValue = unknown>(name: TName): AssignmentFieldRef<TValue>;
110
+ };
63
111
  export type DefaultPipelineAssignmentCtx = {
64
112
  /**
65
- * Work-item field accessor.
113
+ * Work-item accessor: top-level properties (`workItem.title`,
114
+ * `workItem.platform`, `workItem.url`, ...) plus `workItem.field(name)`
115
+ * for the platform-specific `fields` bag.
66
116
  *
67
117
  * @example
68
118
  * workItem.field("issueType").eq("bug").then(assign(bugTriage))
69
119
  * workItem.field("labels").contains("regression").then(assign(regressionReview))
70
120
  * workItem.field("status").eq("resolved").then(skip())
121
+ * workItem.platform.eq("github").then(assign(githubTriage))
122
+ * workItem.title.contains("[urgent]").then(assign(urgentTriage))
71
123
  */
72
- workItem: {
73
- field(name: string): AssignmentFieldRef;
74
- };
124
+ workItem: WorkItemAssignmentAccessor;
75
125
  /**
76
126
  * Context facts for the current ingestion event.
77
127
  * `context.isNew` is `true` when the work item is being created for the first time.
@@ -80,7 +130,7 @@ export type DefaultPipelineAssignmentCtx = {
80
130
  * context.isNew.eq(true).then(assign(onboarding))
81
131
  */
82
132
  context: {
83
- isNew: AssignmentFieldRef;
133
+ isNew: AssignmentFieldRef<boolean>;
84
134
  };
85
135
  /**
86
136
  * Outcome: assign the work item to the given pipeline.
@@ -3,3 +3,4 @@ export * from "./builder";
3
3
  export * from "./input-accessor";
4
4
  export * from "./pipeline-definitions-client";
5
5
  export * from "./define-default-pipeline-assignment";
6
+ export * from "./work-item-fields";
@@ -14821,14 +14821,48 @@ function materializeAccessor(accessor) {
14821
14821
  };
14822
14822
  }
14823
14823
 
14824
+ // src/definitions/pipelines/work-item-fields.ts
14825
+ var WORK_ITEM_TOP_LEVEL_FIELDS = [
14826
+ "id",
14827
+ "projectId",
14828
+ "platform",
14829
+ "platformId",
14830
+ "platformKey",
14831
+ "url",
14832
+ "title",
14833
+ "description",
14834
+ "sourceCreatedAt",
14835
+ "sourceUpdatedAt",
14836
+ "createdByUserId",
14837
+ "parentWorkItemId",
14838
+ "createdAt",
14839
+ "updatedAt"
14840
+ ];
14841
+ var WORK_ITEM_FIELDS_PATH_PREFIX = "fields.";
14842
+ function resolveWorkItemFieldPath(record2, path) {
14843
+ if (typeof record2 !== "object" || record2 === null)
14844
+ return;
14845
+ const asRecord = record2;
14846
+ if (path.startsWith(WORK_ITEM_FIELDS_PATH_PREFIX)) {
14847
+ const fieldName = path.slice(WORK_ITEM_FIELDS_PATH_PREFIX.length);
14848
+ const fields = asRecord["fields"];
14849
+ if (typeof fields !== "object" || fields === null)
14850
+ return;
14851
+ return fields[fieldName];
14852
+ }
14853
+ return asRecord[path];
14854
+ }
14855
+
14824
14856
  // src/definitions/pipelines/builder-helpers.ts
14825
14857
  var WORK_ITEM_ACCESSOR = Object.freeze({
14826
- title: Object.freeze({ source: "work_item", field: "title" }),
14827
- description: Object.freeze({
14858
+ ...Object.fromEntries(WORK_ITEM_TOP_LEVEL_FIELDS.map((field) => [
14859
+ field,
14860
+ Object.freeze({ source: "work_item", field })
14861
+ ])),
14862
+ field: (fieldName) => Object.freeze({
14828
14863
  source: "work_item",
14829
- field: "description"
14830
- }),
14831
- field: (fieldName) => Object.freeze({ source: "work_item", field: `fields.${fieldName}` })
14864
+ field: `${WORK_ITEM_FIELDS_PATH_PREFIX}${fieldName}`
14865
+ })
14832
14866
  });
14833
14867
  var WORK_ITEM_FIELD_BINDINGS = {
14834
14868
  workItemTitle: { source: "work_item", field: "title" },
@@ -14879,7 +14913,7 @@ function resolveAdditionalStepInputBindings(label, definition) {
14879
14913
  const raw = definition.bindings({
14880
14914
  workItemField: (fieldName) => ({
14881
14915
  source: "work_item",
14882
- field: `fields.${fieldName}`
14916
+ field: `${WORK_ITEM_FIELDS_PATH_PREFIX}${fieldName}`
14883
14917
  }),
14884
14918
  literal: literal2
14885
14919
  });
@@ -16892,7 +16926,7 @@ function makeLeaf(fact, path, operator, value) {
16892
16926
  };
16893
16927
  }
16894
16928
  function makeFieldRef(fact, path) {
16895
- return {
16929
+ const untyped = {
16896
16930
  eq: (v) => makeLeaf(fact, path, "equal", v),
16897
16931
  ne: (v) => makeLeaf(fact, path, "notEqual", v),
16898
16932
  gt: (v) => makeLeaf(fact, path, "greaterThan", v),
@@ -16904,6 +16938,16 @@ function makeFieldRef(fact, path) {
16904
16938
  contains: (v) => makeLeaf(fact, path, "contains", v),
16905
16939
  doesNotContain: (v) => makeLeaf(fact, path, "doesNotContain", v)
16906
16940
  };
16941
+ return untyped;
16942
+ }
16943
+ function buildWorkItemAccessor() {
16944
+ const accessor = {
16945
+ field: (name) => makeFieldRef("workItem", `$.${WORK_ITEM_FIELDS_PATH_PREFIX}${name}`)
16946
+ };
16947
+ for (const key of WORK_ITEM_TOP_LEVEL_FIELDS) {
16948
+ accessor[key] = makeFieldRef("workItem", `$.${key}`);
16949
+ }
16950
+ return accessor;
16907
16951
  }
16908
16952
  function makeAssign(pipeline2) {
16909
16953
  if (typeof pipeline2 !== "object" || typeof pipeline2["key"] !== "string" || !Array.isArray(pipeline2["nodeDefinitions"])) {
@@ -16926,9 +16970,7 @@ function makeGroup(mode, refs) {
16926
16970
  }
16927
16971
  function buildCtx() {
16928
16972
  return {
16929
- workItem: {
16930
- field: (name) => makeFieldRef("workItem", `$.fields.${name}`)
16931
- },
16973
+ workItem: buildWorkItemAccessor(),
16932
16974
  context: {
16933
16975
  isNew: makeFieldRef("context", "$.isNew")
16934
16976
  },
@@ -17008,6 +17050,7 @@ function isDefaultPipelineAssignmentSpec(value) {
17008
17050
  }
17009
17051
  export {
17010
17052
  serializeDefaultPipelineAssignment,
17053
+ resolveWorkItemFieldPath,
17011
17054
  pipeline,
17012
17055
  materializeAccessor,
17013
17056
  literal2 as literal,
@@ -17017,6 +17060,8 @@ export {
17017
17060
  createPipelineDefinitionsClient,
17018
17061
  createInputAccessor,
17019
17062
  buildPipelineSpec,
17063
+ WORK_ITEM_TOP_LEVEL_FIELDS,
17064
+ WORK_ITEM_FIELDS_PATH_PREFIX,
17020
17065
  Rule,
17021
17066
  PipelineStepBuilder,
17022
17067
  PipelineBuilder,
@@ -20,7 +20,9 @@ declare const buildPipelineDefinitionsClient: (pipelineDefinitions: PipelineDefi
20
20
  description: string | unknown;
21
21
  status: "draft" | "active" | "archived";
22
22
  archivedAt: string | unknown;
23
- inputSchemaJson: unknown;
23
+ inputSchemaJson: {
24
+ [key: string]: unknown;
25
+ } | unknown;
24
26
  stepDefinitions: Array<{
25
27
  id: string;
26
28
  pipelineDefinitionId: string;
@@ -51,7 +53,9 @@ declare const buildPipelineDefinitionsClient: (pipelineDefinitions: PipelineDefi
51
53
  };
52
54
  } | unknown;
53
55
  timeoutSeconds: number | unknown;
54
- retryPolicyJson: unknown;
56
+ retryPolicyJson: {
57
+ [key: string]: unknown;
58
+ } | unknown;
55
59
  advancementPolicyDefinition: {
56
60
  id: string;
57
61
  pipelineStepDefinitionId: string;
@@ -100,7 +104,9 @@ declare const buildPipelineDefinitionsClient: (pipelineDefinitions: PipelineDefi
100
104
  }>;
101
105
  };
102
106
  defaultEventType: "continue" | "block" | "complete" | "route";
103
- defaultEventParamsJson: unknown;
107
+ defaultEventParamsJson: {
108
+ [key: string]: unknown;
109
+ } | unknown;
104
110
  allowedEventTypes: Array<"continue" | "block" | "complete" | "route">;
105
111
  createdAt: string;
106
112
  updatedAt: string;
@@ -110,7 +116,9 @@ declare const buildPipelineDefinitionsClient: (pipelineDefinitions: PipelineDefi
110
116
  key: string;
111
117
  type: "average" | "weighted_average" | "sum" | "min" | "max" | "count" | "boolean_any" | "boolean_all";
112
118
  inputSignalKeys: Array<string>;
113
- configJson: unknown;
119
+ configJson: {
120
+ [key: string]: unknown;
121
+ } | unknown;
114
122
  availableWhenResultStatusIn: Array<string> | unknown;
115
123
  createdAt: string;
116
124
  updatedAt: string;
@@ -138,7 +146,9 @@ declare const buildPipelineDefinitionsClient: (pipelineDefinitions: PipelineDefi
138
146
  description: string | unknown;
139
147
  status: "draft" | "active" | "archived";
140
148
  archivedAt: string | unknown;
141
- inputSchemaJson: unknown;
149
+ inputSchemaJson: {
150
+ [key: string]: unknown;
151
+ } | unknown;
142
152
  stepDefinitions: Array<{
143
153
  id: string;
144
154
  pipelineDefinitionId: string;
@@ -169,7 +179,9 @@ declare const buildPipelineDefinitionsClient: (pipelineDefinitions: PipelineDefi
169
179
  };
170
180
  } | unknown;
171
181
  timeoutSeconds: number | unknown;
172
- retryPolicyJson: unknown;
182
+ retryPolicyJson: {
183
+ [key: string]: unknown;
184
+ } | unknown;
173
185
  advancementPolicyDefinition: {
174
186
  id: string;
175
187
  pipelineStepDefinitionId: string;
@@ -218,7 +230,9 @@ declare const buildPipelineDefinitionsClient: (pipelineDefinitions: PipelineDefi
218
230
  }>;
219
231
  };
220
232
  defaultEventType: "continue" | "block" | "complete" | "route";
221
- defaultEventParamsJson: unknown;
233
+ defaultEventParamsJson: {
234
+ [key: string]: unknown;
235
+ } | unknown;
222
236
  allowedEventTypes: Array<"continue" | "block" | "complete" | "route">;
223
237
  createdAt: string;
224
238
  updatedAt: string;
@@ -228,7 +242,9 @@ declare const buildPipelineDefinitionsClient: (pipelineDefinitions: PipelineDefi
228
242
  key: string;
229
243
  type: "average" | "weighted_average" | "sum" | "min" | "max" | "count" | "boolean_any" | "boolean_all";
230
244
  inputSignalKeys: Array<string>;
231
- configJson: unknown;
245
+ configJson: {
246
+ [key: string]: unknown;
247
+ } | unknown;
232
248
  availableWhenResultStatusIn: Array<string> | unknown;
233
249
  createdAt: string;
234
250
  updatedAt: string;
@@ -0,0 +1,98 @@
1
+ import type { GetApiWorkItemsByWorkItemIdResponses } from "../../generated/types.gen";
2
+ /**
3
+ * Top-level work-item properties addressable by every work-item binding
4
+ * mechanism in the SDK: the default-pipeline-assignment DSL's
5
+ * `workItem.<field>` accessor (`define-default-pipeline-assignment.ts`) and
6
+ * the regular pipeline/step `workItem.<field>` accessor
7
+ * (`builder-helpers.ts`'s `WorkItemAccessor`) — and, reversed, both
8
+ * `boboddy pipelines pull` file generators
9
+ * (`default-pipeline-assignment-file-generator.ts`,
10
+ * `pipeline-file-generator.ts`). `fields` is deliberately excluded from this
11
+ * list: it's reached via `.field(name)` on every one of those accessors, not
12
+ * as a top-level property.
13
+ *
14
+ * This is the single source of truth for the list — it must stay in sync
15
+ * with the `workItem` fact/context shape every corresponding server-side
16
+ * resolver publishes (`buildFacts` in
17
+ * `evaluate-default-pipeline-assignment.ts`; `buildResolvedWorkItemContext`
18
+ * in `resolve-node-input.ts`).
19
+ */
20
+ export declare const WORK_ITEM_TOP_LEVEL_FIELDS: readonly ["id", "projectId", "platform", "platformId", "platformKey", "url", "title", "description", "sourceCreatedAt", "sourceUpdatedAt", "createdByUserId", "parentWorkItemId", "createdAt", "updatedAt"];
21
+ export type WorkItemTopLevelField = (typeof WORK_ITEM_TOP_LEVEL_FIELDS)[number];
22
+ /**
23
+ * The literal union of valid work-item platforms, sourced from the generated
24
+ * OpenAPI response type (`GetApiWorkItemsByWorkItemIdResponses[200]["platform"]`)
25
+ * rather than hand-duplicated, so it cannot drift from the API contract. It
26
+ * must mirror `WorkItemPlatform` in
27
+ * `packages/core/src/work-items/work-item/domain/work-item-platform.ts`.
28
+ */
29
+ type WorkItemPlatformLiteral = GetApiWorkItemsByWorkItemIdResponses[200]["platform"];
30
+ /**
31
+ * Per-field value types for every member of `WORK_ITEM_TOP_LEVEL_FIELDS`,
32
+ * mirroring exactly what the server-side resolvers publish at evaluation/
33
+ * resolution time (`buildFacts` for default-pipeline-assignment;
34
+ * `buildResolvedWorkItemContext` for regular pipeline/step input bindings).
35
+ * Notably, `sourceCreatedAt`/`sourceUpdatedAt`/`createdAt`/`updatedAt` are
36
+ * serialized as ISO date strings (or `null`), never `Date` objects.
37
+ */
38
+ export type WorkItemTopLevelFieldTypeMap = {
39
+ id: string;
40
+ projectId: string;
41
+ platform: WorkItemPlatformLiteral;
42
+ platformId: string | null;
43
+ platformKey: string;
44
+ url: string | null;
45
+ title: string;
46
+ description: string | null;
47
+ sourceCreatedAt: string | null;
48
+ sourceUpdatedAt: string | null;
49
+ createdByUserId: string | null;
50
+ parentWorkItemId: string | null;
51
+ createdAt: string | null;
52
+ updatedAt: string | null;
53
+ };
54
+ /**
55
+ * The path prefix marking a reference into a work item's platform-specific
56
+ * `fields` bag (a Jira custom field, a GitHub label, etc.), as opposed to a
57
+ * `WorkItemTopLevelField`. Shared by every accessor that authors these paths
58
+ * (`workItem.field(name)` in both the default-pipeline-assignment DSL and
59
+ * the regular pipeline/step `WorkItemAccessor`) and every resolver that
60
+ * reads them back (`resolveWorkItemFieldPath` below; the JSONPath-rooted
61
+ * variant in `evaluate-default-pipeline-assignment.ts`, which layers a `$.`
62
+ * prefix on top of this same convention).
63
+ */
64
+ export declare const WORK_ITEM_FIELDS_PATH_PREFIX = "fields.";
65
+ /**
66
+ * Resolves a work-item path — either a `WorkItemTopLevelField` name (a
67
+ * single, code-controlled segment) or a `fields.<name>` reference into the
68
+ * platform-specific `fields` bag — against a plain object shaped like
69
+ * `WorkItemTopLevelFieldTypeMap & { fields: ... }`.
70
+ *
71
+ * `<name>` in `fields.<name>` is an arbitrary, unescaped, platform-supplied
72
+ * field name (a Jira custom field label, a GitHub key, etc.) that may
73
+ * contain any character, including `.`, `[`, `]`, `~`, `^`, `;`, or even be
74
+ * the empty string. A naive dot-splitting path walker would silently
75
+ * misresolve a field literally named `"a.b"` as nested property access
76
+ * (`fields.a.b`) instead of the flat key `fields["a.b"]` — a silent-wrong-
77
+ * match hazard, not a visible error, since the walker degrades to "no
78
+ * match"/"wrong match" rather than throwing.
79
+ *
80
+ * This resolver sidesteps that hazard entirely: everything after `fields.`
81
+ * is treated as one atomic, unsplit key. Every work-item path resolver in
82
+ * the codebase must use this same convention for a field name to round-trip
83
+ * correctly — see `evaluate-default-pipeline-assignment.ts`'s
84
+ * `resolveWorkItemAssignmentPath` (which layers `$.` path-rooting on top of
85
+ * this function) and `resolve-node-input.ts`'s `work_item` binding
86
+ * resolution (which calls this function directly, replacing what used to be
87
+ * a dot-splitting walker shared with unrelated binding sources and
88
+ * susceptible to the exact hazard described above).
89
+ *
90
+ * `path` is untyped (`string`, not `WorkItemTopLevelField | \`fields.${string}\``)
91
+ * because callers validate membership separately
92
+ * (`WORK_ITEM_TOP_LEVEL_FIELDS`/`isSupportedWorkItemField`) before ever
93
+ * reaching a resolver — this function's contract is purely "resolve
94
+ * whatever path string you hand it, treating `fields.` as atomic," not
95
+ * "validate the path."
96
+ */
97
+ export declare function resolveWorkItemFieldPath(record: unknown, path: string): unknown;
98
+ export {};
@@ -1,6 +1,7 @@
1
1
  import type { ZodType } from "zod";
2
2
  import type { AnyStepFeature, FeatureResultExtensions, FeatureSignalKeys } from "./step-features";
3
3
  import { type PromptTemplateContext } from "./prompt-template";
4
+ import type { LiteralBinding, WorkItemBinding } from "../pipelines/define-pipeline";
4
5
  type OpenCodeMcpServers = Record<string, {
5
6
  type: "local";
6
7
  command: string[];
@@ -89,14 +90,24 @@ export type DefineStepInput<TInput extends ZodType = ZodType, TResult extends Zo
89
90
  status?: "draft" | "active";
90
91
  executionMode?: "workspace" | "no_workspace";
91
92
  };
92
- export type AdditionalStepInputLiteralBinding = {
93
- source: "literal";
94
- value: unknown;
95
- };
96
- export type AdditionalStepInputBinding = {
97
- source: "work_item";
98
- field: string;
99
- } | AdditionalStepInputLiteralBinding;
93
+ /**
94
+ * @deprecated Alias for `LiteralBinding` (`define-pipeline.ts`) — the two
95
+ * used to be independently declared, structurally-identical types. Kept
96
+ * only so existing imports of this name keep working.
97
+ */
98
+ export type AdditionalStepInputLiteralBinding = LiteralBinding;
99
+ /**
100
+ * The binding sources available to `PipelineMeta.additionalStepInput`'s
101
+ * `bindings` callback (`workItemField(...)`/`literal(...)` in
102
+ * `builder-helpers.ts`'s `resolveAdditionalStepInputBindings`) — the same
103
+ * `WorkItemBinding`/`LiteralBinding` shapes `.step()`'s own `input` mapper
104
+ * uses (`define-pipeline.ts`'s `AnyBinding`), restricted to the subset
105
+ * `resolveAdditionalStepInputBindings` can actually produce. Previously an
106
+ * independently-declared `{ source: "work_item"; field: string }` union
107
+ * member that had drifted apart from `WorkItemBinding` in shape only by
108
+ * coincidence, not by design.
109
+ */
110
+ export type AdditionalStepInputBinding = WorkItemBinding | LiteralBinding;
100
111
  export type StepDefinitionSpec = {
101
112
  key: string;
102
113
  name: string;
@@ -18,8 +18,12 @@ declare const buildStepDefinitionsClient: (stepDefinitions: StepDefinitions) =>
18
18
  version: number;
19
19
  kind: "built_in" | "user_defined";
20
20
  executionMode: "workspace" | "no_workspace";
21
- inputSchemaJson: unknown;
22
- resultSchemaJson: unknown;
21
+ inputSchemaJson: {
22
+ [key: string]: unknown;
23
+ } | unknown;
24
+ resultSchemaJson: {
25
+ [key: string]: unknown;
26
+ } | unknown;
23
27
  opencodeMcpJson: {
24
28
  [key: string]: {
25
29
  type: string;
@@ -82,8 +86,12 @@ declare const buildStepDefinitionsClient: (stepDefinitions: StepDefinitions) =>
82
86
  version: number;
83
87
  kind: "built_in" | "user_defined";
84
88
  executionMode: "workspace" | "no_workspace";
85
- inputSchemaJson: unknown;
86
- resultSchemaJson: unknown;
89
+ inputSchemaJson: {
90
+ [key: string]: unknown;
91
+ } | unknown;
92
+ resultSchemaJson: {
93
+ [key: string]: unknown;
94
+ } | unknown;
87
95
  opencodeMcpJson: {
88
96
  [key: string]: {
89
97
  type: string;
@@ -151,8 +159,12 @@ declare const buildStepDefinitionsClient: (stepDefinitions: StepDefinitions) =>
151
159
  version: number;
152
160
  kind: "built_in" | "user_defined";
153
161
  executionMode: "workspace" | "no_workspace";
154
- inputSchemaJson: unknown;
155
- resultSchemaJson: unknown;
162
+ inputSchemaJson: {
163
+ [key: string]: unknown;
164
+ } | unknown;
165
+ resultSchemaJson: {
166
+ [key: string]: unknown;
167
+ } | unknown;
156
168
  opencodeMcpJson: {
157
169
  [key: string]: {
158
170
  type: string;