coalesce-transform-mcp 0.3.0 → 0.4.2

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 (279) hide show
  1. package/README.md +74 -3
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +6 -2
  4. package/dist/client.js.map +1 -1
  5. package/dist/coalesce/api/environments.d.ts +0 -12
  6. package/dist/coalesce/api/environments.d.ts.map +1 -1
  7. package/dist/coalesce/api/environments.js +0 -4
  8. package/dist/coalesce/api/environments.js.map +1 -1
  9. package/dist/coalesce/api/jobs.d.ts +3 -5
  10. package/dist/coalesce/api/jobs.d.ts.map +1 -1
  11. package/dist/coalesce/api/jobs.js +3 -6
  12. package/dist/coalesce/api/jobs.js.map +1 -1
  13. package/dist/coalesce/api/nodes.d.ts +3 -3
  14. package/dist/coalesce/api/nodes.d.ts.map +1 -1
  15. package/dist/coalesce/api/nodes.js +6 -4
  16. package/dist/coalesce/api/nodes.js.map +1 -1
  17. package/dist/coalesce/api/runs.d.ts.map +1 -1
  18. package/dist/coalesce/api/runs.js +11 -1
  19. package/dist/coalesce/api/runs.js.map +1 -1
  20. package/dist/coalesce/api/scan.d.ts +14 -0
  21. package/dist/coalesce/api/scan.d.ts.map +1 -0
  22. package/dist/coalesce/api/scan.js +64 -0
  23. package/dist/coalesce/api/scan.js.map +1 -0
  24. package/dist/coalesce/api/subgraphs.d.ts +3 -2
  25. package/dist/coalesce/api/subgraphs.d.ts.map +1 -1
  26. package/dist/coalesce/api/subgraphs.js +3 -2
  27. package/dist/coalesce/api/subgraphs.js.map +1 -1
  28. package/dist/coalesce/run-schemas.d.ts.map +1 -1
  29. package/dist/coalesce/run-schemas.js +26 -16
  30. package/dist/coalesce/run-schemas.js.map +1 -1
  31. package/dist/coalesce/tool-response.d.ts +1 -13
  32. package/dist/coalesce/tool-response.d.ts.map +1 -1
  33. package/dist/coalesce/tool-response.js +20 -6
  34. package/dist/coalesce/tool-response.js.map +1 -1
  35. package/dist/coalesce/tool-schemas.d.ts +1 -2
  36. package/dist/coalesce/tool-schemas.d.ts.map +1 -1
  37. package/dist/coalesce/tool-schemas.js +368 -5
  38. package/dist/coalesce/tool-schemas.js.map +1 -1
  39. package/dist/coalesce/types.d.ts +8 -0
  40. package/dist/coalesce/types.d.ts.map +1 -1
  41. package/dist/coalesce/types.js +3 -1
  42. package/dist/coalesce/types.js.map +1 -1
  43. package/dist/constants.d.ts +18 -0
  44. package/dist/constants.d.ts.map +1 -0
  45. package/dist/constants.js +21 -0
  46. package/dist/constants.js.map +1 -0
  47. package/dist/mcp/cache.d.ts +2 -1
  48. package/dist/mcp/cache.d.ts.map +1 -1
  49. package/dist/mcp/cache.js +122 -138
  50. package/dist/mcp/cache.js.map +1 -1
  51. package/dist/mcp/environments.d.ts +2 -1
  52. package/dist/mcp/environments.d.ts.map +1 -1
  53. package/dist/mcp/environments.js +56 -112
  54. package/dist/mcp/environments.js.map +1 -1
  55. package/dist/mcp/git-accounts.d.ts +2 -1
  56. package/dist/mcp/git-accounts.d.ts.map +1 -1
  57. package/dist/mcp/git-accounts.js +74 -96
  58. package/dist/mcp/git-accounts.js.map +1 -1
  59. package/dist/mcp/jobs.d.ts +2 -1
  60. package/dist/mcp/jobs.d.ts.map +1 -1
  61. package/dist/mcp/jobs.js +68 -122
  62. package/dist/mcp/jobs.js.map +1 -1
  63. package/dist/mcp/lineage.d.ts +5 -0
  64. package/dist/mcp/lineage.d.ts.map +1 -0
  65. package/dist/mcp/lineage.js +410 -0
  66. package/dist/mcp/lineage.js.map +1 -0
  67. package/dist/mcp/node-type-corpus.d.ts +2 -1
  68. package/dist/mcp/node-type-corpus.d.ts.map +1 -1
  69. package/dist/mcp/node-type-corpus.js +148 -151
  70. package/dist/mcp/node-type-corpus.js.map +1 -1
  71. package/dist/mcp/nodes.d.ts +2 -1
  72. package/dist/mcp/nodes.d.ts.map +1 -1
  73. package/dist/mcp/nodes.js +358 -464
  74. package/dist/mcp/nodes.js.map +1 -1
  75. package/dist/mcp/pipelines.d.ts +2 -1
  76. package/dist/mcp/pipelines.d.ts.map +1 -1
  77. package/dist/mcp/pipelines.js +514 -314
  78. package/dist/mcp/pipelines.js.map +1 -1
  79. package/dist/mcp/projects.d.ts +2 -1
  80. package/dist/mcp/projects.d.ts.map +1 -1
  81. package/dist/mcp/projects.js +66 -100
  82. package/dist/mcp/projects.js.map +1 -1
  83. package/dist/mcp/repo-node-types.d.ts +2 -1
  84. package/dist/mcp/repo-node-types.d.ts.map +1 -1
  85. package/dist/mcp/repo-node-types.js +92 -121
  86. package/dist/mcp/repo-node-types.js.map +1 -1
  87. package/dist/mcp/runs.d.ts +3 -2
  88. package/dist/mcp/runs.d.ts.map +1 -1
  89. package/dist/mcp/runs.js +93 -148
  90. package/dist/mcp/runs.js.map +1 -1
  91. package/dist/mcp/skills.d.ts +13 -0
  92. package/dist/mcp/skills.d.ts.map +1 -0
  93. package/dist/mcp/skills.js +85 -0
  94. package/dist/mcp/skills.js.map +1 -0
  95. package/dist/mcp/subgraphs.d.ts +2 -1
  96. package/dist/mcp/subgraphs.d.ts.map +1 -1
  97. package/dist/mcp/subgraphs.js +61 -98
  98. package/dist/mcp/subgraphs.js.map +1 -1
  99. package/dist/mcp/tool-helpers.d.ts +37 -0
  100. package/dist/mcp/tool-helpers.d.ts.map +1 -0
  101. package/dist/mcp/tool-helpers.js +82 -0
  102. package/dist/mcp/tool-helpers.js.map +1 -0
  103. package/dist/mcp/users.d.ts +2 -1
  104. package/dist/mcp/users.d.ts.map +1 -1
  105. package/dist/mcp/users.js +92 -145
  106. package/dist/mcp/users.js.map +1 -1
  107. package/dist/mcp/workshop.d.ts +2 -1
  108. package/dist/mcp/workshop.d.ts.map +1 -1
  109. package/dist/mcp/workshop.js +66 -101
  110. package/dist/mcp/workshop.js.map +1 -1
  111. package/dist/mcp/workspaces.d.ts +2 -1
  112. package/dist/mcp/workspaces.d.ts.map +1 -1
  113. package/dist/mcp/workspaces.js +19 -34
  114. package/dist/mcp/workspaces.js.map +1 -1
  115. package/dist/prompts/index.d.ts.map +1 -1
  116. package/dist/prompts/index.js +85 -0
  117. package/dist/prompts/index.js.map +1 -1
  118. package/dist/resources/context/pipeline-workshop-guide.md +1 -1
  119. package/dist/resources/context/tool-usage.md +7 -0
  120. package/dist/resources/index.d.ts +13 -0
  121. package/dist/resources/index.d.ts.map +1 -1
  122. package/dist/resources/index.js +105 -5
  123. package/dist/resources/index.js.map +1 -1
  124. package/dist/schemas/node-payloads.d.ts +2 -2
  125. package/dist/server.d.ts +2 -1
  126. package/dist/server.d.ts.map +1 -1
  127. package/dist/server.js +158 -41
  128. package/dist/server.js.map +1 -1
  129. package/dist/services/cache/snapshots.d.ts.map +1 -1
  130. package/dist/services/cache/snapshots.js +9 -5
  131. package/dist/services/cache/snapshots.js.map +1 -1
  132. package/dist/services/config/schema-resolver.d.ts.map +1 -1
  133. package/dist/services/config/schema-resolver.js +3 -6
  134. package/dist/services/config/schema-resolver.js.map +1 -1
  135. package/dist/services/lineage/lineage-cache.d.ts +53 -0
  136. package/dist/services/lineage/lineage-cache.d.ts.map +1 -0
  137. package/dist/services/lineage/lineage-cache.js +335 -0
  138. package/dist/services/lineage/lineage-cache.js.map +1 -0
  139. package/dist/services/lineage/lineage-documentation.d.ts +29 -0
  140. package/dist/services/lineage/lineage-documentation.d.ts.map +1 -0
  141. package/dist/services/lineage/lineage-documentation.js +80 -0
  142. package/dist/services/lineage/lineage-documentation.js.map +1 -0
  143. package/dist/services/lineage/lineage-propagation.d.ts +47 -0
  144. package/dist/services/lineage/lineage-propagation.d.ts.map +1 -0
  145. package/dist/services/lineage/lineage-propagation.js +176 -0
  146. package/dist/services/lineage/lineage-propagation.js.map +1 -0
  147. package/dist/services/lineage/lineage-search.d.ts +33 -0
  148. package/dist/services/lineage/lineage-search.d.ts.map +1 -0
  149. package/dist/services/lineage/lineage-search.js +133 -0
  150. package/dist/services/lineage/lineage-search.js.map +1 -0
  151. package/dist/services/lineage/lineage-traversal.d.ts +34 -0
  152. package/dist/services/lineage/lineage-traversal.d.ts.map +1 -0
  153. package/dist/services/lineage/lineage-traversal.js +283 -0
  154. package/dist/services/lineage/lineage-traversal.js.map +1 -0
  155. package/dist/services/pipelines/clause-extraction.d.ts +3 -0
  156. package/dist/services/pipelines/clause-extraction.d.ts.map +1 -0
  157. package/dist/services/pipelines/clause-extraction.js +27 -0
  158. package/dist/services/pipelines/clause-extraction.js.map +1 -0
  159. package/dist/services/pipelines/column-helpers.d.ts +8 -0
  160. package/dist/services/pipelines/column-helpers.d.ts.map +1 -0
  161. package/dist/services/pipelines/column-helpers.js +125 -0
  162. package/dist/services/pipelines/column-helpers.js.map +1 -0
  163. package/dist/services/pipelines/cte-parsing.d.ts +29 -0
  164. package/dist/services/pipelines/cte-parsing.d.ts.map +1 -0
  165. package/dist/services/pipelines/cte-parsing.js +160 -0
  166. package/dist/services/pipelines/cte-parsing.js.map +1 -0
  167. package/dist/services/pipelines/cte-planning.d.ts +22 -0
  168. package/dist/services/pipelines/cte-planning.d.ts.map +1 -0
  169. package/dist/services/pipelines/cte-planning.js +206 -0
  170. package/dist/services/pipelines/cte-planning.js.map +1 -0
  171. package/dist/services/pipelines/execution.d.ts.map +1 -1
  172. package/dist/services/pipelines/execution.js +0 -1
  173. package/dist/services/pipelines/execution.js.map +1 -1
  174. package/dist/services/pipelines/intent-parsing.d.ts +24 -0
  175. package/dist/services/pipelines/intent-parsing.d.ts.map +1 -0
  176. package/dist/services/pipelines/intent-parsing.js +245 -0
  177. package/dist/services/pipelines/intent-parsing.js.map +1 -0
  178. package/dist/services/pipelines/intent-resolution.d.ts +24 -0
  179. package/dist/services/pipelines/intent-resolution.d.ts.map +1 -0
  180. package/dist/services/pipelines/intent-resolution.js +141 -0
  181. package/dist/services/pipelines/intent-resolution.js.map +1 -0
  182. package/dist/services/pipelines/intent.d.ts +4 -45
  183. package/dist/services/pipelines/intent.d.ts.map +1 -1
  184. package/dist/services/pipelines/intent.js +14 -408
  185. package/dist/services/pipelines/intent.js.map +1 -1
  186. package/dist/services/pipelines/node-type-candidates.d.ts +6 -0
  187. package/dist/services/pipelines/node-type-candidates.d.ts.map +1 -0
  188. package/dist/services/pipelines/node-type-candidates.js +165 -0
  189. package/dist/services/pipelines/node-type-candidates.js.map +1 -0
  190. package/dist/services/pipelines/node-type-intent.d.ts +1 -5
  191. package/dist/services/pipelines/node-type-intent.d.ts.map +1 -1
  192. package/dist/services/pipelines/node-type-intent.js +1 -5
  193. package/dist/services/pipelines/node-type-intent.js.map +1 -1
  194. package/dist/services/pipelines/node-type-scoring.d.ts +13 -0
  195. package/dist/services/pipelines/node-type-scoring.d.ts.map +1 -0
  196. package/dist/services/pipelines/node-type-scoring.js +322 -0
  197. package/dist/services/pipelines/node-type-scoring.js.map +1 -0
  198. package/dist/services/pipelines/node-type-selection.d.ts +22 -2
  199. package/dist/services/pipelines/node-type-selection.d.ts.map +1 -1
  200. package/dist/services/pipelines/node-type-selection.js +16 -538
  201. package/dist/services/pipelines/node-type-selection.js.map +1 -1
  202. package/dist/services/pipelines/plan-builder.d.ts +33 -0
  203. package/dist/services/pipelines/plan-builder.d.ts.map +1 -0
  204. package/dist/services/pipelines/plan-builder.js +224 -0
  205. package/dist/services/pipelines/plan-builder.js.map +1 -0
  206. package/dist/services/pipelines/planning-types.d.ts +543 -0
  207. package/dist/services/pipelines/planning-types.d.ts.map +1 -0
  208. package/dist/services/pipelines/planning-types.js +85 -0
  209. package/dist/services/pipelines/planning-types.js.map +1 -0
  210. package/dist/services/pipelines/planning.d.ts +8 -537
  211. package/dist/services/pipelines/planning.d.ts.map +1 -1
  212. package/dist/services/pipelines/planning.js +10 -1956
  213. package/dist/services/pipelines/planning.js.map +1 -1
  214. package/dist/services/pipelines/review.d.ts.map +1 -1
  215. package/dist/services/pipelines/review.js +3 -8
  216. package/dist/services/pipelines/review.js.map +1 -1
  217. package/dist/services/pipelines/select-parsing.d.ts +7 -0
  218. package/dist/services/pipelines/select-parsing.d.ts.map +1 -0
  219. package/dist/services/pipelines/select-parsing.js +185 -0
  220. package/dist/services/pipelines/select-parsing.js.map +1 -0
  221. package/dist/services/pipelines/source-parsing.d.ts +8 -0
  222. package/dist/services/pipelines/source-parsing.d.ts.map +1 -0
  223. package/dist/services/pipelines/source-parsing.js +151 -0
  224. package/dist/services/pipelines/source-parsing.js.map +1 -0
  225. package/dist/services/pipelines/sql-parsing.d.ts +8 -0
  226. package/dist/services/pipelines/sql-parsing.d.ts.map +1 -0
  227. package/dist/services/pipelines/sql-parsing.js +9 -0
  228. package/dist/services/pipelines/sql-parsing.js.map +1 -0
  229. package/dist/services/pipelines/sql-tokenizer.d.ts +42 -0
  230. package/dist/services/pipelines/sql-tokenizer.d.ts.map +1 -0
  231. package/dist/services/pipelines/sql-tokenizer.js +493 -0
  232. package/dist/services/pipelines/sql-tokenizer.js.map +1 -0
  233. package/dist/services/pipelines/sql-utils.d.ts +30 -0
  234. package/dist/services/pipelines/sql-utils.d.ts.map +1 -0
  235. package/dist/services/pipelines/sql-utils.js +62 -0
  236. package/dist/services/pipelines/sql-utils.js.map +1 -0
  237. package/dist/services/pipelines/workshop.d.ts.map +1 -1
  238. package/dist/services/pipelines/workshop.js +53 -25
  239. package/dist/services/pipelines/workshop.js.map +1 -1
  240. package/dist/services/pipelines/workspace-resolution.d.ts +18 -0
  241. package/dist/services/pipelines/workspace-resolution.d.ts.map +1 -0
  242. package/dist/services/pipelines/workspace-resolution.js +279 -0
  243. package/dist/services/pipelines/workspace-resolution.js.map +1 -0
  244. package/dist/services/runs/diagnostics.d.ts.map +1 -1
  245. package/dist/services/runs/diagnostics.js +3 -8
  246. package/dist/services/runs/diagnostics.js.map +1 -1
  247. package/dist/services/shared/elicitation.d.ts +14 -0
  248. package/dist/services/shared/elicitation.d.ts.map +1 -0
  249. package/dist/services/shared/elicitation.js +56 -0
  250. package/dist/services/shared/elicitation.js.map +1 -0
  251. package/dist/services/workspace/node-creation.d.ts.map +1 -1
  252. package/dist/services/workspace/node-creation.js +5 -1
  253. package/dist/services/workspace/node-creation.js.map +1 -1
  254. package/dist/services/workspace/node-update-helpers.d.ts.map +1 -1
  255. package/dist/services/workspace/node-update-helpers.js +3 -8
  256. package/dist/services/workspace/node-update-helpers.js.map +1 -1
  257. package/dist/utils.d.ts +11 -0
  258. package/dist/utils.d.ts.map +1 -1
  259. package/dist/utils.js +20 -1
  260. package/dist/utils.js.map +1 -1
  261. package/dist/workflows/get-environment-health.d.ts +49 -0
  262. package/dist/workflows/get-environment-health.d.ts.map +1 -0
  263. package/dist/workflows/get-environment-health.js +310 -0
  264. package/dist/workflows/get-environment-health.js.map +1 -0
  265. package/dist/workflows/get-environment-overview.d.ts +2 -1
  266. package/dist/workflows/get-environment-overview.d.ts.map +1 -1
  267. package/dist/workflows/get-environment-overview.js +13 -19
  268. package/dist/workflows/get-environment-overview.js.map +1 -1
  269. package/dist/workflows/get-run-details.d.ts +2 -2
  270. package/dist/workflows/get-run-details.d.ts.map +1 -1
  271. package/dist/workflows/get-run-details.js +14 -19
  272. package/dist/workflows/get-run-details.js.map +1 -1
  273. package/dist/workflows/retry-and-wait.d.ts.map +1 -1
  274. package/dist/workflows/retry-and-wait.js +3 -2
  275. package/dist/workflows/retry-and-wait.js.map +1 -1
  276. package/dist/workflows/run-and-wait.d.ts.map +1 -1
  277. package/dist/workflows/run-and-wait.js +3 -2
  278. package/dist/workflows/run-and-wait.js.map +1 -1
  279. package/package.json +2 -2
@@ -4,6 +4,25 @@ export type PipelineTemplateDefaults = {
4
4
  };
5
5
  export declare const PIPELINE_NODE_TYPE_FAMILIES: readonly ["stage", "persistent-stage", "view", "work", "dimension", "fact", "hub", "satellite", "link", "unknown"];
6
6
  export type PipelineNodeTypeFamily = (typeof PIPELINE_NODE_TYPE_FAMILIES)[number];
7
+ export type InternalPipelineNodeTypeCandidate = {
8
+ nodeType: string;
9
+ displayName: string | null;
10
+ shortName: string | null;
11
+ family: PipelineNodeTypeFamily;
12
+ usageCount: number;
13
+ workspaceUsageCount: number;
14
+ observedInWorkspace: boolean;
15
+ autoExecutable: boolean;
16
+ semanticSignals: string[];
17
+ missingDefaultFields: string[];
18
+ templateWarnings: string[];
19
+ templateDefaults?: PipelineTemplateDefaults;
20
+ score: number;
21
+ reasons: string[];
22
+ source: "repo" | "workspace";
23
+ resolutionKind?: "direct" | "package";
24
+ packageAlias?: string;
25
+ };
7
26
  export type PipelineNodeTypeSelectionCandidate = {
8
27
  nodeType: string;
9
28
  displayName: string | null;
@@ -46,7 +65,7 @@ export type PipelineNodeTypeSelectionResult = {
46
65
  selection: PipelineNodeTypeSelection;
47
66
  warnings: string[];
48
67
  };
49
- type PipelineNodeTypeSelectionContext = {
68
+ export type PipelineNodeTypeSelectionContext = {
50
69
  explicitNodeType?: string;
51
70
  goal?: string;
52
71
  targetName?: string;
@@ -62,7 +81,8 @@ type PipelineNodeTypeSelectionContext = {
62
81
  /** Structural hint: are business keys explicitly defined? */
63
82
  hasBusinessKeys?: boolean;
64
83
  };
84
+ export declare function matchesNodeTypeIdentity(left: string, right: string): boolean;
85
+ export declare function isAutoExecutableFamily(family: PipelineNodeTypeFamily): boolean;
65
86
  export declare function inferFamily(signals: string[]): PipelineNodeTypeFamily;
66
87
  export declare function selectPipelineNodeType(context: PipelineNodeTypeSelectionContext): PipelineNodeTypeSelectionResult;
67
- export {};
68
88
  //# sourceMappingURL=node-type-selection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-type-selection.d.ts","sourceRoot":"","sources":["../../../src/services/pipelines/node-type-selection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-type-selection.d.ts","sourceRoot":"","sources":["../../../src/services/pipelines/node-type-selection.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,7 @@
1
- import { parseRepo, resolveRepoNodeType, } from "../repo/parser.js";
1
+ import { parseRepo, } from "../repo/parser.js";
2
2
  import { resolveOptionalRepoPathInput } from "../repo/path.js";
3
- import { buildSetWorkspaceNodeTemplateFromDefinition } from "../templates/nodes.js";
4
- import { isPlainObject } from "../../utils.js";
5
- import { NODE_TYPE_INTENT, hasAntiSignal, detectSpecializedPatternPenalty, detectSpecializedPatternMatch } from "./node-type-intent.js";
3
+ import { scoreCandidate, challengeCandidate, rankCandidates } from "./node-type-scoring.js";
4
+ import { collectRepoResolutions, buildRepoCandidate, buildWorkspaceCandidate } from "./node-type-candidates.js";
6
5
  export const PIPELINE_NODE_TYPE_FAMILIES = [
7
6
  "stage",
8
7
  "persistent-stage",
@@ -15,15 +14,9 @@ export const PIPELINE_NODE_TYPE_FAMILIES = [
15
14
  "link",
16
15
  "unknown",
17
16
  ];
18
- function getString(value) {
19
- return typeof value === "string" ? value : null;
20
- }
21
- function compareStrings(left, right) {
22
- return left.localeCompare(right, undefined, {
23
- numeric: true,
24
- sensitivity: "case",
25
- });
26
- }
17
+ // ---------------------------------------------------------------------------
18
+ // Shared helpers (used by scoring / candidates / external consumers)
19
+ // ---------------------------------------------------------------------------
27
20
  function nodeTypeID(nodeType) {
28
21
  const delimiterIndex = nodeType.indexOf(":::");
29
22
  return delimiterIndex === -1 ? nodeType : nodeType.slice(delimiterIndex + 3);
@@ -34,7 +27,6 @@ function isExcludedNodeTypeID(nodeType) {
34
27
  return EXCLUDED_NODE_TYPE_IDS.has(nodeTypeID(nodeType));
35
28
  }
36
29
  function isExcludedByInputMode(resolution) {
37
- // inputMode can be in outerDefinition (top-level) or nodeDefinition (nodeMetadataSpec)
38
30
  if (resolution.nodeTypeRecord.outerDefinition.inputMode === "sql") {
39
31
  return true;
40
32
  }
@@ -47,27 +39,14 @@ function isExcludedByInputMode(resolution) {
47
39
  function isDisabledNodeType(resolution) {
48
40
  return resolution.nodeTypeRecord.outerDefinition.isDisabled === true;
49
41
  }
50
- function matchesNodeTypeIdentity(left, right) {
42
+ export function matchesNodeTypeIdentity(left, right) {
51
43
  return left === right || nodeTypeID(left) === nodeTypeID(right);
52
44
  }
53
- function collectRepoResolutions(parsedRepo) {
54
- const resolutions = [];
55
- for (const [id, matches] of parsedRepo.nodeTypesByID.entries()) {
56
- if (matches.length !== 1) {
57
- continue;
58
- }
59
- resolutions.push(resolveRepoNodeType(parsedRepo, id));
60
- }
61
- for (const packageRecord of parsedRepo.packages) {
62
- const packageMatches = parsedRepo.packagesByAlias.get(packageRecord.alias) ?? [];
63
- if (packageMatches.length !== 1) {
64
- continue;
65
- }
66
- for (const definitionID of packageRecord.resolvedDefinitionIDs) {
67
- resolutions.push(resolveRepoNodeType(parsedRepo, `${packageRecord.alias}:::${definitionID}`));
68
- }
69
- }
70
- return resolutions.sort((left, right) => compareStrings(left.resolvedNodeType, right.resolvedNodeType));
45
+ export function isAutoExecutableFamily(family) {
46
+ return (family === "stage" ||
47
+ family === "persistent-stage" ||
48
+ family === "view" ||
49
+ family === "work");
71
50
  }
72
51
  export function inferFamily(signals) {
73
52
  const combined = signals
@@ -103,506 +82,9 @@ export function inferFamily(signals) {
103
82
  }
104
83
  return "unknown";
105
84
  }
106
- function getDefinitionConfigItems(nodeDefinition) {
107
- const groups = Array.isArray(nodeDefinition.config)
108
- ? nodeDefinition.config.filter(isPlainObject)
109
- : [];
110
- return groups.flatMap((group) => Array.isArray(group.items) ? group.items.filter(isPlainObject) : []);
111
- }
112
- function analyzeDefinition(nodeDefinition) {
113
- if (!nodeDefinition) {
114
- return {
115
- semanticSignals: [],
116
- missingDefaultFields: [],
117
- };
118
- }
119
- const semanticSignals = new Set();
120
- const missingDefaultFields = new Set();
121
- for (const item of getDefinitionConfigItems(nodeDefinition)) {
122
- const label = getString(item.attributeName) ??
123
- getString(item.displayName) ??
124
- getString(item.type) ??
125
- "unknown";
126
- const normalizedLabel = label.toLowerCase();
127
- const itemType = getString(item.type) ?? "";
128
- const hasDefault = Object.prototype.hasOwnProperty.call(item, "default");
129
- if (/(business.?key|surrogate|scd|effective|current.?flag|grain|hash|hub|satellite|link|fact|dimension|merge.?key)/u.test(normalizedLabel)) {
130
- semanticSignals.add(label);
131
- }
132
- if (!hasDefault &&
133
- itemType !== "materializationSelector" &&
134
- itemType !== "multisourceToggle" &&
135
- itemType !== "overrideSQLToggle") {
136
- missingDefaultFields.add(label);
137
- }
138
- }
139
- return {
140
- semanticSignals: Array.from(semanticSignals).sort(compareStrings),
141
- missingDefaultFields: Array.from(missingDefaultFields).sort(compareStrings),
142
- };
143
- }
144
- function isAutoExecutableFamily(family) {
145
- return (family === "stage" ||
146
- family === "persistent-stage" ||
147
- family === "view" ||
148
- family === "work");
149
- }
150
- const CATEGORY_FAMILIES = {
151
- "general-purpose": ["stage", "work"],
152
- "view": ["view"],
153
- "persistent": ["persistent-stage"],
154
- "dimensional": ["dimension", "fact"],
155
- "data-vault": ["hub", "satellite", "link"],
156
- };
157
- /**
158
- * Determine the desired category from context.
159
- *
160
- * Decision tree:
161
- * 1. Explicit dimensional/data-vault/CDC/view intent → that category
162
- * 2. Strong signal from name (dim_, fct_, hub_, etc.) → that category
163
- * 3. Otherwise → general-purpose (stage/work, pick by workspace usage)
164
- *
165
- * Stage vs Work within general-purpose: prefer whichever the workspace
166
- * already uses more, or default to stage.
167
- */
168
- function buildUseCaseContext(context) {
169
- const freeText = [context.goal, context.targetName].filter(Boolean).join(" ").toLowerCase();
170
- const multiSource = context.sourceCount > 1;
171
- // Dimensional modeling requires explicit intent — not just GROUP BY
172
- const dimensionalModeling = /\bdimension(al)?\s+model/u.test(freeText) ||
173
- /\bstar\s+schema\b/u.test(freeText) ||
174
- /\bsnowflake\s+schema\b/u.test(freeText);
175
- // Data Vault requires explicit intent
176
- const dataVaultIntent = /\bdata\s*vault\b/u.test(freeText);
177
- // CDC / persistent stage requires explicit intent
178
- const persistentIntent = /\bpersistent\s*stage\b/u.test(freeText) ||
179
- /\bcdc\b/u.test(freeText) ||
180
- /\bchange\s*track/u.test(freeText);
181
- // View requires explicit intent
182
- const viewIntent = /\bview\b/u.test(freeText) ||
183
- /\bno\s+materialization/u.test(freeText) ||
184
- /\bvirtual\s+table/u.test(freeText);
185
- // Check name-based strong signals ONLY for specialized categories
186
- // (not for stage/work — those are general-purpose regardless of name)
187
- const targetName = (context.targetName ?? "").toLowerCase();
188
- const combinedText = `${targetName} ${freeText}`;
189
- const specializedSignalChecks = [
190
- { category: "persistent", families: ["persistent-stage"] },
191
- { category: "dimensional", families: ["dimension", "fact"] },
192
- { category: "data-vault", families: ["hub", "satellite", "link"] },
193
- { category: "view", families: ["view"] },
194
- ];
195
- // 1. Explicit intent from goal text
196
- if (dataVaultIntent) {
197
- return { desiredFamilies: ["hub", "satellite", "link"], category: "data-vault", dimensionalModeling, multiSource };
198
- }
199
- if (dimensionalModeling) {
200
- return { desiredFamilies: ["dimension", "fact"], category: "dimensional", dimensionalModeling, multiSource };
201
- }
202
- if (persistentIntent) {
203
- return { desiredFamilies: ["persistent-stage", "stage"], category: "persistent", dimensionalModeling, multiSource };
204
- }
205
- if (viewIntent && !context.hasJoin && !context.hasGroupBy) {
206
- return { desiredFamilies: ["view", "stage"], category: "view", dimensionalModeling, multiSource };
207
- }
208
- // 2. Strong signal from name (only for specialized families)
209
- for (const { category, families } of specializedSignalChecks) {
210
- for (const family of families) {
211
- const intent = NODE_TYPE_INTENT[family];
212
- if (intent.strongSignals.test(combinedText)) {
213
- return { desiredFamilies: families, category, dimensionalModeling, multiSource };
214
- }
215
- }
216
- }
217
- // 3. General-purpose — stage and work are interchangeable.
218
- // Tiebreaker priority:
219
- // a) Workspace pattern — which does the workspace already use more?
220
- // b) Default to stage (base node type package always has Stage)
221
- const counts = context.workspaceNodeTypeCounts ?? {};
222
- const stageUsage = Object.entries(counts)
223
- .filter(([nodeType]) => inferFamily([nodeType]) === "stage")
224
- .reduce((sum, [, count]) => sum + count, 0);
225
- const workUsage = Object.entries(counts)
226
- .filter(([nodeType]) => inferFamily([nodeType]) === "work")
227
- .reduce((sum, [, count]) => sum + count, 0);
228
- const desiredFamilies = workUsage > stageUsage
229
- ? ["work", "stage", "view"]
230
- : ["stage", "work", "view"];
231
- return { desiredFamilies, category: "general-purpose", dimensionalModeling, multiSource };
232
- }
233
- function familyScore(candidate, useCase) {
234
- const { desiredFamilies, category } = useCase;
235
- if (desiredFamilies.length === 0) {
236
- return { score: 0, reasons: [] };
237
- }
238
- // For general-purpose category, stage and work get the same top score.
239
- // They're interchangeable — the tiebreaker is workspace usage.
240
- // Candidates observed in the workspace get a bonus to prefer established patterns.
241
- if (category === "general-purpose") {
242
- if (candidate.family === "stage" || candidate.family === "work") {
243
- const workspaceBonus = candidate.observedInWorkspace ? 20 : 0;
244
- const reasons = [`general-purpose ${candidate.family} node — fits standard transforms`];
245
- if (workspaceBonus > 0) {
246
- reasons.push("preferred — already used in this workspace");
247
- }
248
- return {
249
- score: 120 + workspaceBonus,
250
- reasons,
251
- };
252
- }
253
- if (candidate.family === "view") {
254
- return {
255
- score: 60,
256
- reasons: ["view is acceptable for general-purpose transforms"],
257
- };
258
- }
259
- return { score: 0, reasons: [] };
260
- }
261
- // For specialized categories, rank by position in desired families list
262
- if (desiredFamilies.includes(candidate.family)) {
263
- const position = desiredFamilies.indexOf(candidate.family);
264
- const score = position === 0 ? 120 : 60;
265
- return {
266
- score,
267
- reasons: [`matches the ${category} category (${candidate.family})`],
268
- };
269
- }
270
- // General-purpose families are always a fallback for specialized categories
271
- if (candidate.family === "stage" || candidate.family === "work") {
272
- return {
273
- score: 25,
274
- reasons: [`general-purpose fallback for ${category} category`],
275
- };
276
- }
277
- return { score: 0, reasons: [] };
278
- }
279
- function scoreCandidate(candidate, context) {
280
- const reasons = [...candidate.reasons];
281
- let score = candidate.score;
282
- const useCase = buildUseCaseContext(context);
283
- if (context.explicitNodeType) {
284
- if (candidate.nodeType === context.explicitNodeType) {
285
- score += 1000;
286
- reasons.push("matches the explicit targetNodeType override");
287
- }
288
- else if (matchesNodeTypeIdentity(candidate.nodeType, context.explicitNodeType)) {
289
- score += 900;
290
- reasons.push("matches the explicit targetNodeType ID");
291
- }
292
- else {
293
- score -= 200;
294
- }
295
- }
296
- const familyMatch = familyScore(candidate, useCase);
297
- score += familyMatch.score;
298
- reasons.push(...familyMatch.reasons);
299
- if (useCase.multiSource && isAutoExecutableFamily(candidate.family)) {
300
- score += 15;
301
- reasons.push("fits a multisource projection workflow");
302
- }
303
- if (useCase.dimensionalModeling && (candidate.family === "dimension" || candidate.family === "fact")) {
304
- score += 40;
305
- reasons.push(`${candidate.family} is designed for dimensional modeling with business keys`);
306
- }
307
- if (!useCase.multiSource && (candidate.family === "stage" || candidate.family === "work")) {
308
- score += 10;
309
- reasons.push("general-purpose node for single-source transforms");
310
- }
311
- // Anti-signal penalty: if this family's anti-signals match the context, penalize it.
312
- // Prevents dimension/fact from being chosen for generic transforms.
313
- const contextText = [context.goal, context.targetName].filter(Boolean).join(" ");
314
- if (contextText.length > 0 && hasAntiSignal(candidate.family, contextText)) {
315
- score -= 30;
316
- reasons.push(`context suggests this is not a ${candidate.family} use case`);
317
- }
318
- // Semantic config penalty: types that require business keys, SCD, etc.
319
- // get penalized when there's no dimensional modeling intent
320
- const intent = NODE_TYPE_INTENT[candidate.family];
321
- if (intent.requiresSemanticConfig && !useCase.dimensionalModeling) {
322
- score -= 15;
323
- reasons.push(`${candidate.family} requires semantic config (business keys, SCD) — no dimensional modeling intent detected`);
324
- }
325
- // Specialized materialization patterns: Dynamic Tables, Incremental Loads, etc.
326
- // Decision is binary from node-type-intent.ts:
327
- // - If context explicitly requests the pattern (contextRequired matches) → keep it, add bonus
328
- // - If context doesn't request it → mark as not applicable (score = -Infinity)
329
- // This is the same logic as validateNodeTypeChoice() at creation time.
330
- const candidateSignals = [candidate.nodeType, candidate.displayName ?? "", candidate.shortName ?? ""].join(" ");
331
- const specializedResult = detectSpecializedPatternPenalty(candidateSignals, contextText);
332
- if (specializedResult) {
333
- // Context doesn't match — this specialized type is not appropriate
334
- score = -Infinity;
335
- reasons.push(`not applicable: ${specializedResult.reason}`);
336
- }
337
- else {
338
- // Check if context positively matches a specialized pattern (context requested it)
339
- const positiveMatch = detectSpecializedPatternMatch(candidateSignals, contextText);
340
- if (positiveMatch) {
341
- score += 50;
342
- reasons.push(`context explicitly requests ${positiveMatch} pattern`);
343
- }
344
- }
345
- // Hard exclusion: data-vault package types are NEVER selected unless
346
- // the context explicitly requests data vault. These types serve a fundamentally
347
- // different modeling paradigm and should not appear in standard pipelines.
348
- if (!useCase.dimensionalModeling &&
349
- candidate.family !== "hub" && candidate.family !== "satellite" && candidate.family !== "link" &&
350
- ((candidate.packageAlias && /data.vault/iu.test(candidate.packageAlias)) ||
351
- /data.vault/iu.test(candidate.nodeType))) {
352
- const hasDataVaultIntent = /\bdata\s*vault\b/iu.test(contextText);
353
- if (!hasDataVaultIntent) {
354
- score = -Infinity;
355
- reasons.push(`data vault package type excluded — no data vault intent in context`);
356
- return { ...candidate, score, reasons: Array.from(new Set(reasons)) };
357
- }
358
- }
359
- // Non-base type exclusion for general-purpose selections.
360
- // Priority: workspace pattern > base node type package > 4 defaults.
361
- // Types NOT from the base package are EXCLUDED unless observed in workspace.
362
- if (useCase.category === "general-purpose" && candidate.source === "repo") {
363
- const isBasePackage = candidate.packageAlias && /base.node.type/iu.test(candidate.packageAlias);
364
- if (isBasePackage) {
365
- score += 15;
366
- reasons.push("from base node type package — preferred default");
367
- }
368
- else if (!candidate.observedInWorkspace) {
369
- // Non-base, non-workspace types are excluded for general-purpose transforms.
370
- // Only types from the base node type package or already in use in the workspace
371
- // are eligible for standard staging/transform/join operations.
372
- score = -Infinity;
373
- const source = candidate.packageAlias
374
- ? `non-base package "${candidate.packageAlias}"`
375
- : `custom repo type "${candidate.nodeType}"`;
376
- reasons.push(`excluded: ${source} — not observed in workspace. Use base node types or workspace-established types.`);
377
- return { ...candidate, score, reasons: Array.from(new Set(reasons)) };
378
- }
379
- }
380
- // Penalize "Copy of" types — these are user-cloned definitions.
381
- // The original base type should be preferred unless explicitly requested.
382
- if (candidate.displayName &&
383
- /\bcopy\s+of\b/iu.test(candidate.displayName) &&
384
- !context.explicitNodeType) {
385
- score -= 30;
386
- reasons.push(`"${candidate.displayName}" is a cloned type — prefer the original`);
387
- }
388
- // Unknown family types should never beat known general-purpose types.
389
- // If inferFamily couldn't classify it, it's likely a custom/specialized type
390
- // that shouldn't be auto-selected for standard transforms.
391
- if (candidate.family === "unknown" && !context.explicitNodeType) {
392
- score -= 50;
393
- reasons.push("unknown node type family — cannot verify suitability for this use case");
394
- }
395
- if (candidate.autoExecutable) {
396
- score += 25;
397
- reasons.push("supports template-based automatic creation");
398
- }
399
- else {
400
- score -= 25;
401
- reasons.push("likely needs extra semantic configuration before automatic creation");
402
- }
403
- if (candidate.missingDefaultFields.length > 0) {
404
- score -= candidate.missingDefaultFields.length * 8;
405
- reasons.push(`has config fields without defaults: ${candidate.missingDefaultFields.join(", ")}`);
406
- }
407
- if (candidate.semanticSignals.length > 0) {
408
- score -= candidate.semanticSignals.length * 6;
409
- reasons.push(`exposes semantic config signals: ${candidate.semanticSignals.join(", ")}`);
410
- }
411
- if (candidate.templateWarnings.length > 0) {
412
- score -= Math.min(candidate.templateWarnings.length, 3) * 3;
413
- }
414
- return {
415
- ...candidate,
416
- score,
417
- reasons: Array.from(new Set(reasons)),
418
- };
419
- }
420
- function buildRepoCandidate(resolution, workspaceNodeTypes, workspaceNodeTypeCounts) {
421
- const generated = resolution.nodeTypeRecord.nodeDefinition
422
- ? buildSetWorkspaceNodeTemplateFromDefinition(resolution.nodeTypeRecord.nodeDefinition, { nodeType: resolution.resolvedNodeType })
423
- : undefined;
424
- const displayName = getString(resolution.nodeTypeRecord.outerDefinition.name) ??
425
- generated?.definitionSummary.capitalized ??
426
- null;
427
- const shortName = generated?.definitionSummary.short ?? null;
428
- const family = inferFamily([
429
- resolution.resolvedNodeType,
430
- resolution.nodeTypeRecord.dirName,
431
- displayName ?? "",
432
- shortName ?? "",
433
- generated?.definitionSummary.capitalized ?? "",
434
- ].filter((value) => value.length > 0));
435
- const insights = analyzeDefinition(resolution.nodeTypeRecord.nodeDefinition);
436
- const observedInWorkspace = workspaceNodeTypes.some((nodeType) => matchesNodeTypeIdentity(nodeType, resolution.resolvedNodeType));
437
- const workspaceUsageCount = workspaceNodeTypes.reduce((sum, nodeType) => {
438
- if (!matchesNodeTypeIdentity(nodeType, resolution.resolvedNodeType)) {
439
- return sum;
440
- }
441
- return sum + (workspaceNodeTypeCounts[nodeType] ?? 0);
442
- }, 0);
443
- const autoExecutable = (isAutoExecutableFamily(family) ||
444
- (family === "unknown" &&
445
- insights.semanticSignals.length === 0 &&
446
- insights.missingDefaultFields.length === 0 &&
447
- !!generated)) &&
448
- !generated?.warnings.some((warning) => warning.includes("does not map cleanly"));
449
- const reasons = [];
450
- if (resolution.usageCount > 0) {
451
- reasons.push(`used ${resolution.usageCount} time(s) in committed nodes/`);
452
- }
453
- if (observedInWorkspace) {
454
- reasons.push("already observed in current workspace nodes");
455
- }
456
- if (displayName) {
457
- reasons.push(`definition resolves to ${displayName}`);
458
- }
459
- return {
460
- nodeType: resolution.resolvedNodeType,
461
- displayName,
462
- shortName,
463
- family,
464
- usageCount: resolution.usageCount,
465
- workspaceUsageCount,
466
- observedInWorkspace,
467
- autoExecutable,
468
- semanticSignals: insights.semanticSignals,
469
- missingDefaultFields: insights.missingDefaultFields,
470
- templateWarnings: generated?.warnings ?? [],
471
- templateDefaults: generated
472
- ? {
473
- inferredTopLevelFields: generated.inferredTopLevelFields,
474
- inferredConfig: generated.inferredConfig,
475
- }
476
- : undefined,
477
- score: resolution.usageCount * 20 + workspaceUsageCount * 10 + (generated ? 5 : 0),
478
- reasons,
479
- source: "repo",
480
- resolutionKind: resolution.resolutionKind,
481
- ...(resolution.resolutionKind === "package"
482
- ? { packageAlias: resolution.packageAlias }
483
- : {}),
484
- };
485
- }
486
- function buildWorkspaceCandidate(nodeType, workspaceNodeTypeCounts) {
487
- const family = inferFamily([nodeType]);
488
- return {
489
- nodeType,
490
- displayName: nodeType,
491
- shortName: null,
492
- family,
493
- usageCount: 0,
494
- workspaceUsageCount: workspaceNodeTypeCounts[nodeType] ?? 0,
495
- observedInWorkspace: true,
496
- autoExecutable: isAutoExecutableFamily(family),
497
- semanticSignals: [],
498
- missingDefaultFields: [],
499
- templateWarnings: [],
500
- score: (workspaceNodeTypeCounts[nodeType] ?? 0) * 10,
501
- reasons: ["observed in current workspace nodes"],
502
- source: "workspace",
503
- };
504
- }
505
- /**
506
- * Sort candidates by score descending (highest first).
507
- */
508
- function rankCandidates(candidates) {
509
- return [...candidates].sort((a, b) => b.score - a.score);
510
- }
511
- /**
512
- * Challenge the top-ranked candidate against the intent corpus.
513
- * Returns an array of challenge reasons. Empty = candidate passed.
514
- *
515
- * Checks:
516
- * 1. Anti-signals from the intent doc match the context
517
- * 2. Specialized pattern not requested by context
518
- * 3. Requires semantic config but no dimensional modeling intent
519
- * 4. Another family's strong signal matches context but this candidate doesn't belong to it
520
- * 5. doNotUseWhen entries match the context
521
- */
522
- function challengeCandidate(candidate, context, contextText) {
523
- const challenges = [];
524
- const intent = NODE_TYPE_INTENT[candidate.family];
525
- const contextLower = contextText.toLowerCase();
526
- // 1. Anti-signals: the intent doc says this family should NOT be used for this context
527
- if (intent.antiSignals !== null && intent.antiSignals.test(contextLower)) {
528
- challenges.push(`${candidate.family} anti-signal matched — intent says not for this context`);
529
- }
530
- // 2. Specialized pattern penalty: candidate is a specialized type but context doesn't request it
531
- const candidateSignals = [
532
- candidate.nodeType,
533
- candidate.displayName ?? "",
534
- candidate.shortName ?? "",
535
- ].join(" ");
536
- const specializedPenalty = detectSpecializedPatternPenalty(candidateSignals, contextText);
537
- if (specializedPenalty) {
538
- challenges.push(specializedPenalty.reason);
539
- }
540
- // 3. Requires semantic config (business keys, SCD) but context has no dimensional modeling intent
541
- if (intent.requiresSemanticConfig) {
542
- const hasDimensionalIntent = /\bdimension(al)?\s+model/u.test(contextLower) ||
543
- /\bstar\s+schema\b/u.test(contextLower) ||
544
- /\bsnowflake\s+schema\b/u.test(contextLower) ||
545
- /\bdata\s*vault\b/u.test(contextLower);
546
- // Also check if context has a strong signal for THIS family (e.g. name starts with dim_)
547
- const hasOwnStrongSignal = intent.strongSignals.test(contextLower);
548
- if (!hasDimensionalIntent && !hasOwnStrongSignal) {
549
- challenges.push(`${candidate.family} requires semantic config (business keys, SCD) but no dimensional modeling intent detected`);
550
- }
551
- }
552
- // 4. Another family has a strong signal match but this candidate is a different CATEGORY
553
- // Stage and work are interchangeable — don't challenge one for the other.
554
- const generalPurposeFamilies = new Set(["stage", "work"]);
555
- const signalCheckOrder = [
556
- "persistent-stage", "dimension", "fact", "hub", "satellite", "link",
557
- "view", "work", "stage",
558
- ];
559
- for (const family of signalCheckOrder) {
560
- if (family === candidate.family)
561
- continue;
562
- // Skip stage↔work challenges — they're the same category
563
- if (generalPurposeFamilies.has(family) && generalPurposeFamilies.has(candidate.family))
564
- continue;
565
- const otherIntent = NODE_TYPE_INTENT[family];
566
- if (otherIntent.strongSignals.test(contextLower)) {
567
- challenges.push(`context has a strong signal for ${family} but candidate is ${candidate.family}`);
568
- break; // One mismatch is enough
569
- }
570
- }
571
- // 5. doNotUseWhen: check if any anti-pattern descriptions match the context
572
- for (const antiPattern of intent.doNotUseWhen) {
573
- const antiLower = antiPattern.toLowerCase();
574
- // Extract key phrases from the doNotUseWhen text and check against context
575
- // Only trigger for phrases that are specific enough (> 10 chars, not generic advice)
576
- if (antiLower.length > 10) {
577
- // Check for CTE decomposition anti-pattern
578
- if (/cte\s+decomposition/u.test(antiLower) && /cte\s+decomposition/u.test(contextLower)) {
579
- challenges.push(`intent says do not use ${candidate.family} for CTE decomposition`);
580
- }
581
- // Check for batch ETL anti-pattern
582
- if (/batch\s+etl/u.test(antiLower) && /batch\s+etl/u.test(contextLower)) {
583
- challenges.push(`intent says do not use ${candidate.family} for batch ETL`);
584
- }
585
- // Check for general/simple transforms anti-pattern
586
- if (/general.purpose|simple\s+stag/u.test(antiLower) && /general|simple|basic/u.test(contextLower)) {
587
- challenges.push(`intent says do not use ${candidate.family} for general-purpose transforms`);
588
- }
589
- }
590
- }
591
- // 6. Package-level challenge: non-base packages for general-purpose context
592
- // Data vault, functional, and other specialized packages should not be selected
593
- // for standard staging/transform/join operations — unless already in workspace.
594
- if (candidate.packageAlias &&
595
- !/base.node.type/iu.test(candidate.packageAlias) &&
596
- !candidate.observedInWorkspace &&
597
- /batch\s+etl|staging|transform|general/iu.test(contextLower)) {
598
- challenges.push(`from specialized package "${candidate.packageAlias}" — not appropriate for general-purpose transforms`);
599
- }
600
- // 7. "Copy of" types should be challenged in favor of originals
601
- if (candidate.displayName && /\bcopy\s+of\b/iu.test(candidate.displayName)) {
602
- challenges.push(`"${candidate.displayName}" is a cloned type — original should be preferred`);
603
- }
604
- return challenges;
605
- }
85
+ // ---------------------------------------------------------------------------
86
+ // Main selection orchestrator
87
+ // ---------------------------------------------------------------------------
606
88
  export function selectPipelineNodeType(context) {
607
89
  const warnings = [];
608
90
  const workspaceNodeTypes = context.workspaceNodeTypes ?? [];
@@ -647,8 +129,6 @@ export function selectPipelineNodeType(context) {
647
129
  candidates.push(scoreCandidate(buildWorkspaceCandidate(nodeType, workspaceNodeTypeCounts), context));
648
130
  }
649
131
  // === DELIBERATIVE SELECTION: Match → Rank → Challenge → Repeat (twice) ===
650
- // Two rounds of scoring + challenge to ensure the best type is selected.
651
- // The challenge step uses the intent doc to verify the top candidate is appropriate.
652
132
  const contextText = [context.goal, context.targetName].filter(Boolean).join(" ");
653
133
  let sorted = rankCandidates(candidates);
654
134
  const challengeLog = [];
@@ -659,18 +139,16 @@ export function selectPipelineNodeType(context) {
659
139
  const challenges = challengeCandidate(top, context, contextText);
660
140
  if (challenges.length > 0) {
661
141
  challengeLog.push(`Round ${round}: challenged "${top.nodeType}" (${top.displayName ?? top.family}) — ${challenges.join("; ")}`);
662
- // Disqualify the top candidate and re-rank
663
142
  top.score = -Infinity;
664
143
  top.reasons.push(...challenges.map((c) => `CHALLENGED: ${c}`));
665
144
  sorted = rankCandidates(sorted);
666
145
  }
667
146
  else {
668
147
  challengeLog.push(`Round ${round}: "${top.nodeType}" (${top.displayName ?? top.family}) passed challenge`);
669
- break; // Candidate passed — no need for another round
148
+ break;
670
149
  }
671
150
  }
672
151
  let selected = sorted[0] ?? null;
673
- // Skip candidates that were disqualified
674
152
  if (selected && selected.score === -Infinity) {
675
153
  selected = sorted.find((c) => c.score > -Infinity) ?? null;
676
154
  }