d365fo-mcp 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (443) hide show
  1. package/.github/copilot-instructions.md +20 -2
  2. package/README.md +6 -6
  3. package/bridge/D365MetadataBridge/Models/Models.cs +31 -8
  4. package/bridge/D365MetadataBridge/Protocol/BridgeProtocol.cs +80 -5
  5. package/bridge/D365MetadataBridge/Protocol/RequestDispatcher.cs +24 -6
  6. package/bridge/D365MetadataBridge/Services/CrossReferenceService.cs +31 -15
  7. package/bridge/D365MetadataBridge/Services/MetadataReadService.cs +59 -4
  8. package/bridge/D365MetadataBridge/Services/MetadataWriteService.cs +600 -195
  9. package/dist/bridge/bridgeAdapter.d.ts +52 -14
  10. package/dist/bridge/bridgeAdapter.js +144 -61
  11. package/dist/bridge/bridgeClient.d.ts +30 -3
  12. package/dist/bridge/bridgeClient.js +101 -44
  13. package/dist/bridge/bridgeFailure.d.ts +82 -0
  14. package/dist/bridge/bridgeFailure.js +96 -0
  15. package/dist/bridge/bridgeReadiness.d.ts +90 -0
  16. package/dist/bridge/bridgeReadiness.js +124 -0
  17. package/dist/bridge/bridgeTypes.d.ts +6 -0
  18. package/dist/bridge/debouncedRefresh.d.ts +16 -2
  19. package/dist/bridge/debouncedRefresh.js +74 -16
  20. package/dist/bridge/index.d.ts +2 -0
  21. package/dist/bridge/index.js +1 -0
  22. package/dist/cli/commands/doctor.d.ts +16 -0
  23. package/dist/cli/commands/doctor.js +117 -0
  24. package/dist/cli/commands/session.d.ts +9 -0
  25. package/dist/cli/commands/session.js +185 -0
  26. package/dist/cli/commands/setup.d.ts +0 -2
  27. package/dist/cli/commands/setup.js +1 -1
  28. package/dist/cli/context.d.ts +0 -2
  29. package/dist/cli/context.js +1 -1
  30. package/dist/cli/envFile.d.ts +0 -6
  31. package/dist/cli/envFile.js +0 -11
  32. package/dist/cli/exec.d.ts +0 -2
  33. package/dist/cli/exec.js +0 -7
  34. package/dist/cli/index.js +10 -0
  35. package/dist/cli/mcpJson.d.ts +0 -1
  36. package/dist/cli/mcpJson.js +1 -1
  37. package/dist/cli/npmRegistry.d.ts +0 -1
  38. package/dist/cli/npmRegistry.js +1 -1
  39. package/dist/cli/session/analyze.d.ts +138 -0
  40. package/dist/cli/session/analyze.js +340 -0
  41. package/dist/cli/session/copilotChatLog.d.ts +31 -0
  42. package/dist/cli/session/copilotChatLog.js +125 -0
  43. package/dist/cli/session/sessionLog.d.ts +58 -0
  44. package/dist/cli/session/sessionLog.js +50 -0
  45. package/dist/cli/settingsStore.js +4 -0
  46. package/dist/cli/xppConfig.d.ts +0 -2
  47. package/dist/cli/xppConfig.js +0 -3
  48. package/dist/config/configFile.d.ts +2 -7
  49. package/dist/config/configFile.js +9 -10
  50. package/dist/config/settings.d.ts +18 -4
  51. package/dist/config/settings.js +116 -4
  52. package/dist/database/download.d.ts +14 -0
  53. package/dist/database/download.js +38 -4
  54. package/dist/index.js +67 -25
  55. package/dist/knowledge/formPatterns/catalog/topLevel/simpleListDetails.d.ts +0 -1
  56. package/dist/knowledge/formPatterns/catalog/topLevel/simpleListDetails.js +0 -4
  57. package/dist/knowledge/formPatterns/methodStubs.js +3 -2
  58. package/dist/metadata/enhancedParser.d.ts +0 -8
  59. package/dist/metadata/enhancedParser.js +3 -0
  60. package/dist/metadata/formPatternMiner.js +3 -0
  61. package/dist/metadata/labelParser.d.ts +0 -12
  62. package/dist/metadata/labelParser.js +13 -3
  63. package/dist/metadata/modelDescriptor.d.ts +0 -2
  64. package/dist/metadata/modelDescriptor.js +0 -4
  65. package/dist/metadata/symbolIndex.d.ts +133 -15
  66. package/dist/metadata/symbolIndex.js +439 -154
  67. package/dist/metadata/types.d.ts +6 -112
  68. package/dist/metadata/xmlParser.js +1 -1
  69. package/dist/metadata/xppDeclaration.d.ts +0 -11
  70. package/dist/metadata/xppDeclaration.js +2 -2
  71. package/dist/middleware/rateLimiter.js +1 -1
  72. package/dist/prompts/systemInstructions.js +2 -2
  73. package/dist/scripts/build-database.js +454 -136
  74. package/dist/scripts/build-fts.js +456 -138
  75. package/dist/scripts/extract-metadata.js +370 -141
  76. package/dist/server/mcpServer.d.ts +2 -2
  77. package/dist/server/mcpServer.js +13 -12
  78. package/dist/server/serverMode.d.ts +58 -6
  79. package/dist/server/serverMode.js +88 -7
  80. package/dist/server/toolAnnotations.d.ts +4 -2
  81. package/dist/server/toolAnnotations.js +4 -5
  82. package/dist/server/toolSchemas/buildD365foProject.js +2 -3
  83. package/dist/server/toolSchemas/d365foFile.d.ts +22 -19
  84. package/dist/server/toolSchemas/d365foFile.js +45 -103
  85. package/dist/server/toolSchemas/generateObject.d.ts +14 -211
  86. package/dist/server/toolSchemas/generateObject.js +24 -116
  87. package/dist/server/toolSchemas/getKnowledge.js +7 -5
  88. package/dist/server/toolSchemas/getObjectInfo.d.ts +25 -1
  89. package/dist/server/toolSchemas/getObjectInfo.js +20 -6
  90. package/dist/server/toolSchemas/getWorkspaceInfo.js +1 -1
  91. package/dist/server/toolSchemas/index.d.ts +63 -342
  92. package/dist/server/toolSchemas/index.js +0 -6
  93. package/dist/server/toolSchemas/labels.d.ts +17 -59
  94. package/dist/server/toolSchemas/labels.js +20 -64
  95. package/dist/server/toolSchemas/prepare.d.ts +4 -0
  96. package/dist/server/toolSchemas/prepare.js +4 -0
  97. package/dist/server/toolSchemas/runBpCheck.d.ts +18 -0
  98. package/dist/server/toolSchemas/runBpCheck.js +14 -2
  99. package/dist/server/toolSchemas/search.d.ts +0 -2
  100. package/dist/server/toolSchemas/search.js +4 -18
  101. package/dist/server/toolSchemas/updateSymbolIndex.js +5 -4
  102. package/dist/server/transport.js +1 -1
  103. package/dist/tools/{analyzeCode.d.ts → analysis/analyzeCode.d.ts} +1 -1
  104. package/dist/tools/{analyzeCode.js → analysis/analyzeCode.js} +7 -5
  105. package/dist/tools/{analyzeCompleteness.d.ts → analysis/analyzeCompleteness.d.ts} +1 -1
  106. package/dist/tools/{batchSearch.d.ts → analysis/batchSearch.d.ts} +1 -1
  107. package/dist/tools/{batchSearch.js → analysis/batchSearch.js} +6 -3
  108. package/dist/tools/{extensionSearch.d.ts → analysis/extensionSearch.d.ts} +1 -1
  109. package/dist/tools/{extensionSearch.js → analysis/extensionSearch.js} +19 -2
  110. package/dist/tools/{findReferences.d.ts → analysis/findReferences.d.ts} +2 -2
  111. package/dist/tools/{findReferences.js → analysis/findReferences.js} +6 -4
  112. package/dist/tools/analysis/labelSearchHistory.d.ts +25 -0
  113. package/dist/tools/analysis/labelSearchHistory.js +65 -0
  114. package/dist/tools/{prefixDiagnostics.d.ts → analysis/prefixDiagnostics.d.ts} +4 -9
  115. package/dist/tools/{prefixDiagnostics.js → analysis/prefixDiagnostics.js} +11 -50
  116. package/dist/tools/{search.d.ts → analysis/search.d.ts} +2 -2
  117. package/dist/tools/{search.js → analysis/search.js} +72 -13
  118. package/dist/tools/analysis/searchLabels.d.ts +47 -0
  119. package/dist/tools/analysis/searchLabels.js +225 -0
  120. package/dist/tools/{searchUnified.d.ts → analysis/searchUnified.d.ts} +2 -2
  121. package/dist/tools/{searchUnified.js → analysis/searchUnified.js} +5 -2
  122. package/dist/tools/{validateCode.d.ts → analysis/validateCode.d.ts} +1 -1
  123. package/dist/tools/{validateCode.js → analysis/validateCode.js} +9 -7
  124. package/dist/tools/analysis/validateFormPattern.d.ts +99 -0
  125. package/dist/tools/{validateFormPattern.js → analysis/validateFormPattern.js} +111 -9
  126. package/dist/tools/{validateObjectNaming.d.ts → analysis/validateObjectNaming.d.ts} +1 -1
  127. package/dist/tools/{validateObjectNaming.js → analysis/validateObjectNaming.js} +198 -46
  128. package/dist/tools/{validateXpp.d.ts → analysis/validateXpp.d.ts} +3 -0
  129. package/dist/tools/{validateXpp.js → analysis/validateXpp.js} +239 -6
  130. package/dist/tools/d365foFile.js +132 -5
  131. package/dist/tools/generateObject.d.ts +20 -7
  132. package/dist/tools/generateObject.js +59 -17
  133. package/dist/tools/{analyzeExtensionPoints.d.ts → knowledge/analyzeExtensionPoints.d.ts} +1 -1
  134. package/dist/tools/{analyzeExtensionPoints.js → knowledge/analyzeExtensionPoints.js} +4 -4
  135. package/dist/tools/{analyzePatterns.d.ts → knowledge/analyzePatterns.d.ts} +1 -1
  136. package/dist/tools/{apiUsagePatterns.d.ts → knowledge/apiUsagePatterns.d.ts} +2 -2
  137. package/dist/tools/{apiUsagePatterns.js → knowledge/apiUsagePatterns.js} +1 -1
  138. package/dist/tools/{d365foErrorHelp.js → knowledge/d365foErrorHelp.js} +96 -3
  139. package/dist/tools/{extensionStrategyAdvisor.d.ts → knowledge/extensionStrategyAdvisor.d.ts} +1 -1
  140. package/dist/tools/{extensionStrategyAdvisor.js → knowledge/extensionStrategyAdvisor.js} +1 -1
  141. package/dist/tools/{findCocExtensions.d.ts → knowledge/findCocExtensions.d.ts} +2 -2
  142. package/dist/tools/{findCocExtensions.js → knowledge/findCocExtensions.js} +4 -4
  143. package/dist/tools/{findEventHandlers.d.ts → knowledge/findEventHandlers.d.ts} +2 -2
  144. package/dist/tools/{findEventHandlers.js → knowledge/findEventHandlers.js} +1 -1
  145. package/dist/tools/{formPattern.d.ts → knowledge/formPattern.d.ts} +1 -1
  146. package/dist/tools/{formPattern.js → knowledge/formPattern.js} +9 -6
  147. package/dist/tools/{getFormPatterns.d.ts → knowledge/getFormPatterns.d.ts} +2 -2
  148. package/dist/tools/{getFormPatterns.js → knowledge/getFormPatterns.js} +8 -5
  149. package/dist/tools/{getKnowledge.d.ts → knowledge/getKnowledge.d.ts} +7 -3
  150. package/dist/tools/{getKnowledge.js → knowledge/getKnowledge.js} +25 -6
  151. package/dist/tools/{getTablePatterns.d.ts → knowledge/getTablePatterns.d.ts} +1 -1
  152. package/dist/tools/{getTablePatterns.js → knowledge/getTablePatterns.js} +6 -3
  153. package/dist/tools/{methodSignature.d.ts → knowledge/methodSignature.d.ts} +1 -1
  154. package/dist/tools/{methodSignature.js → knowledge/methodSignature.js} +29 -14
  155. package/dist/tools/{objectPatterns.d.ts → knowledge/objectPatterns.d.ts} +1 -1
  156. package/dist/tools/{objectPatterns.js → knowledge/objectPatterns.js} +5 -3
  157. package/dist/tools/{xppKnowledge.d.ts → knowledge/xppKnowledge.d.ts} +7 -0
  158. package/dist/tools/{xppKnowledge.js → knowledge/xppKnowledge.js} +120 -16
  159. package/dist/tools/labels.d.ts +2 -0
  160. package/dist/tools/labels.js +160 -8
  161. package/dist/tools/{prepare.d.ts → prepare/prepare.d.ts} +14 -1
  162. package/dist/tools/prepare/prepare.js +133 -0
  163. package/dist/tools/{prepareChange.d.ts → prepare/prepareChange.d.ts} +1 -1
  164. package/dist/tools/{prepareChange.js → prepare/prepareChange.js} +20 -8
  165. package/dist/tools/{prepareCreate.d.ts → prepare/prepareCreate.d.ts} +1 -1
  166. package/dist/tools/{prepareCreate.js → prepare/prepareCreate.js} +23 -10
  167. package/dist/tools/{classInfo.d.ts → readers/classInfo.d.ts} +1 -1
  168. package/dist/tools/{classInfo.js → readers/classInfo.js} +3 -3
  169. package/dist/tools/{completion.d.ts → readers/completion.d.ts} +1 -1
  170. package/dist/tools/{completion.js → readers/completion.js} +7 -7
  171. package/dist/tools/{configKeyInfo.d.ts → readers/configKeyInfo.d.ts} +1 -1
  172. package/dist/tools/{configKeyInfo.js → readers/configKeyInfo.js} +1 -1
  173. package/dist/tools/{dataEntityInfo.d.ts → readers/dataEntityInfo.d.ts} +1 -1
  174. package/dist/tools/{dataEntityInfo.js → readers/dataEntityInfo.js} +1 -1
  175. package/dist/tools/{edtInfo.d.ts → readers/edtInfo.d.ts} +1 -1
  176. package/dist/tools/{edtInfo.js → readers/edtInfo.js} +7 -4
  177. package/dist/tools/{enumInfo.d.ts → readers/enumInfo.d.ts} +1 -1
  178. package/dist/tools/{enumInfo.js → readers/enumInfo.js} +16 -11
  179. package/dist/tools/{extensionInfo.d.ts → readers/extensionInfo.d.ts} +2 -2
  180. package/dist/tools/{extensionInfo.js → readers/extensionInfo.js} +8 -6
  181. package/dist/tools/{formInfo.d.ts → readers/formInfo.d.ts} +1 -1
  182. package/dist/tools/{formInfo.js → readers/formInfo.js} +37 -22
  183. package/dist/tools/{getLabelInfo.d.ts → readers/getLabelInfo.d.ts} +1 -1
  184. package/dist/tools/{getLabelInfo.js → readers/getLabelInfo.js} +11 -9
  185. package/dist/tools/{getMethod.d.ts → readers/getMethod.d.ts} +1 -1
  186. package/dist/tools/{getMethod.js → readers/getMethod.js} +5 -3
  187. package/dist/tools/{getMethodSource.d.ts → readers/getMethodSource.d.ts} +2 -2
  188. package/dist/tools/{getMethodSource.js → readers/getMethodSource.js} +3 -3
  189. package/dist/tools/{getObjectInfo.d.ts → readers/getObjectInfo.d.ts} +6 -3
  190. package/dist/tools/readers/getObjectInfo.js +251 -0
  191. package/dist/tools/readers/getWorkspaceInfo.d.ts +14 -0
  192. package/dist/tools/readers/getWorkspaceInfo.js +292 -0
  193. package/dist/tools/{macroInfo.d.ts → readers/macroInfo.d.ts} +1 -1
  194. package/dist/tools/{macroInfo.js → readers/macroInfo.js} +1 -1
  195. package/dist/tools/{mapInfo.d.ts → readers/mapInfo.d.ts} +1 -1
  196. package/dist/tools/{mapInfo.js → readers/mapInfo.js} +1 -1
  197. package/dist/tools/{menuItemInfo.d.ts → readers/menuItemInfo.d.ts} +2 -2
  198. package/dist/tools/{menuItemInfo.js → readers/menuItemInfo.js} +2 -2
  199. package/dist/tools/{objectInfoRegistry.d.ts → readers/objectInfoRegistry.d.ts} +7 -8
  200. package/dist/tools/{objectInfoRegistry.js → readers/objectInfoRegistry.js} +7 -15
  201. package/dist/tools/readers/objectXml.d.ts +27 -0
  202. package/dist/tools/readers/objectXml.js +58 -0
  203. package/dist/tools/{queryInfo.d.ts → readers/queryInfo.d.ts} +1 -1
  204. package/dist/tools/{queryInfo.js → readers/queryInfo.js} +11 -8
  205. package/dist/tools/{reportInfo.d.ts → readers/reportInfo.d.ts} +1 -1
  206. package/dist/tools/{reportInfo.js → readers/reportInfo.js} +40 -13
  207. package/dist/tools/{securityArtifactInfo.d.ts → readers/securityArtifactInfo.d.ts} +1 -1
  208. package/dist/tools/{securityArtifactInfo.js → readers/securityArtifactInfo.js} +2 -2
  209. package/dist/tools/{securityCoverageInfo.d.ts → readers/securityCoverageInfo.d.ts} +1 -1
  210. package/dist/tools/{securityCoverageInfo.js → readers/securityCoverageInfo.js} +1 -1
  211. package/dist/tools/{securityInfo.d.ts → readers/securityInfo.d.ts} +1 -1
  212. package/dist/tools/{securityInfo.js → readers/securityInfo.js} +4 -2
  213. package/dist/tools/{securityPolicyInfo.d.ts → readers/securityPolicyInfo.d.ts} +1 -1
  214. package/dist/tools/{serviceInfo.d.ts → readers/serviceInfo.d.ts} +1 -1
  215. package/dist/tools/{serviceInfo.js → readers/serviceInfo.js} +1 -1
  216. package/dist/tools/{tableExtensionInfo.d.ts → readers/tableExtensionInfo.d.ts} +2 -2
  217. package/dist/tools/{tableExtensionInfo.js → readers/tableExtensionInfo.js} +4 -4
  218. package/dist/tools/{tableInfo.d.ts → readers/tableInfo.d.ts} +2 -2
  219. package/dist/tools/{tableInfo.js → readers/tableInfo.js} +24 -10
  220. package/dist/tools/{viewInfo.d.ts → readers/viewInfo.d.ts} +1 -1
  221. package/dist/tools/{viewInfo.js → readers/viewInfo.js} +15 -10
  222. package/dist/tools/{buildProject.d.ts → sdlc/buildProject.d.ts} +16 -2
  223. package/dist/tools/{buildProject.js → sdlc/buildProject.js} +301 -82
  224. package/dist/tools/{dbSync.d.ts → sdlc/dbSync.d.ts} +1 -1
  225. package/dist/tools/{dbSync.js → sdlc/dbSync.js} +10 -7
  226. package/dist/tools/{reviewWorkspaceChanges.js → sdlc/reviewWorkspaceChanges.js} +4 -2
  227. package/dist/tools/sdlc/runBpCheck.d.ts +80 -0
  228. package/dist/tools/sdlc/runBpCheck.js +530 -0
  229. package/dist/tools/{sysTestRunner.js → sdlc/sysTestRunner.js} +8 -5
  230. package/dist/tools/sdlc/undoLastModification.d.ts +31 -0
  231. package/dist/tools/{undoLastModification.js → sdlc/undoLastModification.js} +45 -10
  232. package/dist/tools/{updateSymbolIndex.d.ts → sdlc/updateSymbolIndex.d.ts} +19 -2
  233. package/dist/tools/{updateSymbolIndex.js → sdlc/updateSymbolIndex.js} +48 -17
  234. package/dist/tools/{verifyD365Project.d.ts → sdlc/verifyD365Project.d.ts} +2 -2
  235. package/dist/tools/{verifyD365Project.js → sdlc/verifyD365Project.js} +65 -35
  236. package/dist/tools/{codeGen.js → smart/codeGen.js} +5 -5
  237. package/dist/tools/{generateFindMethods.d.ts → smart/generateFindMethods.d.ts} +1 -1
  238. package/dist/tools/{generateFindMethods.js → smart/generateFindMethods.js} +1 -1
  239. package/dist/tools/{generateRelationXpp.d.ts → smart/generateRelationXpp.d.ts} +1 -1
  240. package/dist/tools/{generateSmart.d.ts → smart/generateSmart.d.ts} +1 -1
  241. package/dist/tools/{generateSmart.js → smart/generateSmart.js} +7 -4
  242. package/dist/tools/{generateSmartForm.d.ts → smart/generateSmartForm.d.ts} +1 -1
  243. package/dist/tools/{generateSmartForm.js → smart/generateSmartForm.js} +24 -22
  244. package/dist/tools/{generateSmartReport.d.ts → smart/generateSmartReport.d.ts} +2 -2
  245. package/dist/tools/{generateSmartReport.js → smart/generateSmartReport.js} +14 -14
  246. package/dist/tools/{generateSmartTable.d.ts → smart/generateSmartTable.d.ts} +9 -3
  247. package/dist/tools/{generateSmartTable.js → smart/generateSmartTable.js} +51 -14
  248. package/dist/tools/{suggestEdt.d.ts → smart/suggestEdt.d.ts} +1 -1
  249. package/dist/tools/{suggestImplementation.d.ts → smart/suggestImplementation.d.ts} +1 -1
  250. package/dist/tools/{d365foFileOpSpecs.d.ts → specs/d365foFileOpSpecs.d.ts} +16 -0
  251. package/dist/tools/{d365foFileOpSpecs.js → specs/d365foFileOpSpecs.js} +105 -4
  252. package/dist/tools/specs/generateObjectOpSpecs.d.ts +42 -0
  253. package/dist/tools/specs/generateObjectOpSpecs.js +235 -0
  254. package/dist/tools/{getFormPatternSpec.js → specs/getFormPatternSpec.js} +8 -5
  255. package/dist/tools/specs/labelsOpSpecs.d.ts +27 -0
  256. package/dist/tools/specs/labelsOpSpecs.js +64 -0
  257. package/dist/tools/specs/opSpecs.d.ts +50 -0
  258. package/dist/tools/specs/opSpecs.js +195 -0
  259. package/dist/tools/toolHandler.d.ts +1 -0
  260. package/dist/tools/toolHandler.js +140 -335
  261. package/dist/tools/{compileLabels.js → write/compileLabels.js} +1 -1
  262. package/dist/tools/{createD365File.d.ts → write/createD365File.d.ts} +34 -84
  263. package/dist/tools/{createD365File.js → write/createD365File.js} +348 -1012
  264. package/dist/tools/{createLabel.d.ts → write/createLabel.d.ts} +1 -1
  265. package/dist/tools/{createLabel.js → write/createLabel.js} +79 -22
  266. package/dist/tools/write/inlineIndexUpsert.d.ts +28 -0
  267. package/dist/tools/write/inlineIndexUpsert.js +43 -0
  268. package/dist/tools/write/inlineWriteVerification.d.ts +72 -0
  269. package/dist/tools/write/inlineWriteVerification.js +123 -0
  270. package/dist/tools/write/inlineXppValidation.d.ts +24 -0
  271. package/dist/tools/write/inlineXppValidation.js +115 -0
  272. package/dist/tools/{modifyD365File.d.ts → write/modifyD365File.d.ts} +107 -19
  273. package/dist/tools/{modifyD365File.js → write/modifyD365File.js} +818 -317
  274. package/dist/tools/{renameLabel.d.ts → write/renameLabel.d.ts} +1 -1
  275. package/dist/tools/{renameLabel.js → write/renameLabel.js} +5 -5
  276. package/dist/tools/{resolveReferences.d.ts → write/resolveReferences.d.ts} +5 -3
  277. package/dist/tools/{resolveReferences.js → write/resolveReferences.js} +114 -10
  278. package/dist/tools/{writeAnchorGuard.d.ts → write/writeAnchorGuard.d.ts} +33 -3
  279. package/dist/tools/write/writeAnchorGuard.js +76 -0
  280. package/dist/tools/{createTablePropertyHonesty.d.ts → xml/createTablePropertyHonesty.d.ts} +20 -0
  281. package/dist/tools/{createTablePropertyHonesty.js → xml/createTablePropertyHonesty.js} +72 -2
  282. package/dist/tools/{dataEntityViewExtensionXml.js → xml/dataEntityViewExtensionXml.js} +1 -3
  283. package/dist/tools/xml/dataEntityXml.d.ts +14 -0
  284. package/dist/tools/{dataEntityXml.js → xml/dataEntityXml.js} +40 -7
  285. package/dist/tools/{edtExtensionXml.js → xml/edtExtensionXml.js} +1 -3
  286. package/dist/tools/xml/formXml.d.ts +24 -0
  287. package/dist/tools/xml/formXml.js +40 -0
  288. package/dist/tools/{generateD365Xml.d.ts → xml/generateD365Xml.d.ts} +1 -4
  289. package/dist/tools/{generateD365Xml.js → xml/generateD365Xml.js} +40 -168
  290. package/dist/tools/{generateTableFields.d.ts → xml/generateTableFields.d.ts} +2 -2
  291. package/dist/tools/{generateTableFields.js → xml/generateTableFields.js} +9 -39
  292. package/dist/tools/{generateTableRelation.d.ts → xml/generateTableRelation.d.ts} +1 -1
  293. package/dist/tools/{generateTableRelation.js → xml/generateTableRelation.js} +1 -1
  294. package/dist/tools/{mapXml.d.ts → xml/mapXml.d.ts} +0 -8
  295. package/dist/tools/{mapXml.js → xml/mapXml.js} +12 -14
  296. package/dist/tools/{menuItemExtensionXml.js → xml/menuItemExtensionXml.js} +1 -3
  297. package/dist/tools/{queryViewXml.d.ts → xml/queryViewXml.d.ts} +0 -15
  298. package/dist/tools/{queryViewXml.js → xml/queryViewXml.js} +5 -4
  299. package/dist/tools/{repairFormControls.d.ts → xml/repairFormControls.d.ts} +1 -1
  300. package/dist/tools/{repairFormControls.js → xml/repairFormControls.js} +12 -8
  301. package/dist/tools/xml/securityDutyRoleXml.d.ts +63 -0
  302. package/dist/tools/xml/securityDutyRoleXml.js +117 -0
  303. package/dist/tools/xml/securityPrivilegeXml.d.ts +2 -0
  304. package/dist/tools/{securityPrivilegeXml.js → xml/securityPrivilegeXml.js} +27 -5
  305. package/dist/tools/{serviceXml.d.ts → xml/serviceXml.d.ts} +0 -20
  306. package/dist/tools/{serviceXml.js → xml/serviceXml.js} +3 -2
  307. package/dist/tools/xml/tableXml.d.ts +49 -0
  308. package/dist/tools/xml/tableXml.js +163 -0
  309. package/dist/types/context.d.ts +8 -0
  310. package/dist/utils/atomicFileWrite.d.ts +39 -0
  311. package/dist/utils/atomicFileWrite.js +105 -0
  312. package/dist/utils/axEnumProperties.d.ts +72 -0
  313. package/dist/utils/axEnumProperties.js +120 -0
  314. package/dist/utils/axFieldTypes.d.ts +53 -0
  315. package/dist/utils/axFieldTypes.js +130 -0
  316. package/dist/utils/axFormDesignProperties.d.ts +0 -2
  317. package/dist/utils/axFormDesignProperties.js +1 -1
  318. package/dist/utils/axTablePropertyOrder.d.ts +0 -6
  319. package/dist/utils/axTablePropertyOrder.js +3 -3
  320. package/dist/utils/blobDownloadMarker.d.ts +39 -0
  321. package/dist/utils/blobDownloadMarker.js +52 -0
  322. package/dist/utils/buildMarker.d.ts +47 -0
  323. package/dist/utils/buildMarker.js +102 -0
  324. package/dist/utils/callDedup.d.ts +0 -1
  325. package/dist/utils/callDedup.js +1 -1
  326. package/dist/utils/configManager.d.ts +50 -0
  327. package/dist/utils/configManager.js +143 -26
  328. package/dist/utils/crossModelWriteGuard.d.ts +38 -2
  329. package/dist/utils/crossModelWriteGuard.js +77 -3
  330. package/dist/utils/edtExtensionValidator.d.ts +0 -5
  331. package/dist/utils/edtExtensionValidator.js +1 -1
  332. package/dist/utils/effectivePrefix.d.ts +79 -0
  333. package/dist/utils/effectivePrefix.js +114 -0
  334. package/dist/utils/extractManifest.d.ts +0 -1
  335. package/dist/utils/extractManifest.js +1 -1
  336. package/dist/utils/fieldControlTypes.js +11 -4
  337. package/dist/utils/formExtensionShapeValidator.d.ts +0 -5
  338. package/dist/utils/formExtensionShapeValidator.js +1 -1
  339. package/dist/utils/fsExtensionScanner.js +1 -1
  340. package/dist/utils/fuzzyMatching.d.ts +0 -5
  341. package/dist/utils/fuzzyMatching.js +1 -1
  342. package/dist/utils/indexStaleness.d.ts +4 -0
  343. package/dist/utils/indexStaleness.js +24 -0
  344. package/dist/utils/indexedXmlLookup.d.ts +60 -7
  345. package/dist/utils/indexedXmlLookup.js +127 -5
  346. package/dist/utils/labelReference.d.ts +7 -0
  347. package/dist/utils/labelReference.js +15 -0
  348. package/dist/utils/logger.d.ts +0 -1
  349. package/dist/utils/logger.js +0 -3
  350. package/dist/utils/metadataResolver.d.ts +0 -39
  351. package/dist/utils/metadataResolver.js +2 -42
  352. package/dist/utils/modelClassifier.d.ts +10 -37
  353. package/dist/utils/modelClassifier.js +12 -49
  354. package/dist/utils/modelPrefixInference.js +72 -8
  355. package/dist/utils/objectFileLookup.d.ts +15 -0
  356. package/dist/utils/objectFileLookup.js +144 -0
  357. package/dist/utils/objectNaming.d.ts +31 -0
  358. package/dist/utils/objectNaming.js +87 -0
  359. package/dist/utils/operationLocks.js +78 -22
  360. package/dist/utils/packagesRoot.d.ts +23 -0
  361. package/dist/utils/packagesRoot.js +55 -0
  362. package/dist/utils/pathContainment.js +85 -9
  363. package/dist/utils/payloadBudget.d.ts +70 -0
  364. package/dist/utils/payloadBudget.js +115 -0
  365. package/dist/utils/phaseTimer.d.ts +23 -0
  366. package/dist/utils/phaseTimer.js +50 -0
  367. package/dist/utils/progressReporter.d.ts +37 -0
  368. package/dist/utils/progressReporter.js +53 -0
  369. package/dist/utils/richContext.js +6 -0
  370. package/dist/utils/smartXmlBuilder.d.ts +4 -1
  371. package/dist/utils/smartXmlBuilder.js +13 -44
  372. package/dist/utils/symbolLookup.d.ts +0 -2
  373. package/dist/utils/symbolLookup.js +0 -0
  374. package/dist/utils/terminalUi.d.ts +0 -7
  375. package/dist/utils/terminalUi.js +8 -2
  376. package/dist/utils/toolMetrics.d.ts +2 -0
  377. package/dist/utils/toolMetrics.js +29 -0
  378. package/dist/utils/toolProgressMessage.js +14 -4
  379. package/dist/utils/workspaceDetectionStatus.d.ts +49 -0
  380. package/dist/utils/workspaceDetectionStatus.js +91 -0
  381. package/dist/utils/workspaceDetector.d.ts +6 -0
  382. package/dist/utils/workspaceDetector.js +30 -12
  383. package/dist/utils/xml.d.ts +22 -0
  384. package/dist/utils/xml.js +21 -0
  385. package/dist/utils/xmlEscape.d.ts +48 -0
  386. package/dist/utils/xmlEscape.js +76 -0
  387. package/dist/utils/xppDocGen.js +14 -5
  388. package/dist/utils/xppFormat.d.ts +41 -1
  389. package/dist/utils/xppFormat.js +182 -27
  390. package/dist/validation/formPatternValidator.js +1 -1
  391. package/dist/{tools → workspace}/createdArtifactLedger.js +0 -0
  392. package/dist/workspace/projectFile.d.ts +115 -0
  393. package/dist/workspace/projectFile.js +609 -0
  394. package/dist/workspace/projectMembership.d.ts +94 -0
  395. package/dist/workspace/projectMembership.js +210 -0
  396. package/dist/workspace/projectSelector.d.ts +53 -0
  397. package/dist/workspace/projectSelector.js +118 -0
  398. package/dist/workspace/workspaceScanner.d.ts +0 -5
  399. package/dist/workspace/workspaceScanner.js +20 -7
  400. package/dist/workspace/workspaceUtils.d.ts +0 -9
  401. package/dist/workspace/workspaceUtils.js +1 -22
  402. package/package.json +11 -4
  403. package/dist/prompts/index.d.ts +0 -6
  404. package/dist/prompts/index.js +0 -6
  405. package/dist/server/toolSchemas/batchGetInfo.d.ts +0 -32
  406. package/dist/server/toolSchemas/batchGetInfo.js +0 -35
  407. package/dist/server/toolSchemas/getMethod.d.ts +0 -30
  408. package/dist/server/toolSchemas/getMethod.js +0 -34
  409. package/dist/server/toolSchemas/suggestEdt.d.ts +0 -29
  410. package/dist/server/toolSchemas/suggestEdt.js +0 -29
  411. package/dist/tools/batchGetInfo.d.ts +0 -50
  412. package/dist/tools/batchGetInfo.js +0 -52
  413. package/dist/tools/dataEntityXml.d.ts +0 -97
  414. package/dist/tools/getObjectInfo.js +0 -88
  415. package/dist/tools/index.d.ts +0 -11
  416. package/dist/tools/index.js +0 -11
  417. package/dist/tools/prepare.js +0 -47
  418. package/dist/tools/runBpCheck.d.ts +0 -26
  419. package/dist/tools/runBpCheck.js +0 -296
  420. package/dist/tools/searchLabels.d.ts +0 -26
  421. package/dist/tools/searchLabels.js +0 -119
  422. package/dist/tools/securityPrivilegeXml.d.ts +0 -21
  423. package/dist/tools/undoLastModification.d.ts +0 -9
  424. package/dist/tools/validateFormPattern.d.ts +0 -51
  425. package/dist/tools/writeAnchorGuard.js +0 -44
  426. /package/dist/tools/{analyzeCompleteness.js → analysis/analyzeCompleteness.js} +0 -0
  427. /package/dist/tools/{analyzePatterns.js → knowledge/analyzePatterns.js} +0 -0
  428. /package/dist/tools/{d365foErrorHelp.d.ts → knowledge/d365foErrorHelp.d.ts} +0 -0
  429. /package/dist/tools/{securityPolicyInfo.js → readers/securityPolicyInfo.js} +0 -0
  430. /package/dist/tools/{reviewWorkspaceChanges.d.ts → sdlc/reviewWorkspaceChanges.d.ts} +0 -0
  431. /package/dist/tools/{sysTestRunner.d.ts → sdlc/sysTestRunner.d.ts} +0 -0
  432. /package/dist/tools/{codeGen.d.ts → smart/codeGen.d.ts} +0 -0
  433. /package/dist/tools/{generateRelationXpp.js → smart/generateRelationXpp.js} +0 -0
  434. /package/dist/tools/{suggestEdt.js → smart/suggestEdt.js} +0 -0
  435. /package/dist/tools/{suggestImplementation.js → smart/suggestImplementation.js} +0 -0
  436. /package/dist/tools/{getFormPatternSpec.d.ts → specs/getFormPatternSpec.d.ts} +0 -0
  437. /package/dist/tools/{compileLabels.d.ts → write/compileLabels.d.ts} +0 -0
  438. /package/dist/tools/{dataEntityViewExtensionXml.d.ts → xml/dataEntityViewExtensionXml.d.ts} +0 -0
  439. /package/dist/tools/{edtExtensionXml.d.ts → xml/edtExtensionXml.d.ts} +0 -0
  440. /package/dist/tools/{generateMetadata.d.ts → xml/generateMetadata.d.ts} +0 -0
  441. /package/dist/tools/{generateMetadata.js → xml/generateMetadata.js} +0 -0
  442. /package/dist/tools/{menuItemExtensionXml.d.ts → xml/menuItemExtensionXml.d.ts} +0 -0
  443. /package/dist/{tools → workspace}/createdArtifactLedger.d.ts +0 -0
@@ -34,8 +34,8 @@ PowerShell / any terminal command **WILL HANG** in VS 2022 / VS 2026 MCP integra
34
34
  | Edit an existing object | `d365fo_file(action="modify")` (applies immediately — confirm in chat first) |
35
35
  | Revert the last write | `undo_last_modification` |
36
36
  | Search objects | `search` — multiple via `search(queries[])`, custom-only via `search(scope="extensions")` |
37
- | Read any object's metadata | `get_object_info(objectType, name, options?)` — objectType ∈ class/table/form/query/view/enum/edt/report/data-entity/menu-item/service/map/config-key/security-policy/macro. 2+ known names: `batch_get_info(objects[])` |
38
- | Method signature for CoC | `get_method(include="signature")` (already returned by `prepare(mode="change")`) |
37
+ | Read any object's metadata | `get_object_info(objectType, name, options?)` — objectType ∈ class/table/form/query/view/enum/edt/report/data-entity/menu-item/service/map/config-key/security-policy/macro. 2+ known names: `get_object_info(objects=[{objectType,objectName},…])` — ONE call, never a loop |
38
+ | Method signature for CoC | `get_object_info(objectType="class", name, options={method, include:"signature"})` (already returned by `prepare(mode="change")`) |
39
39
  | Validate X++ before write | `validate_code(mode="syntax", code)` — offline BP check, <50 ms |
40
40
  | X++ rules & patterns | `get_knowledge(kind="knowledge", topic)` — select grammar, CoC, BP rules, SysOperation, workflow, … |
41
41
  | Create a NEW form | `object_patterns(domain="form", action="analyze", recommend={...})` → `object_patterns(domain="form", action="spec", pattern)` → `generate_object(mode="scaffold", objectType="form", cloneFrom=referenceForm, tableMapping={...})` → `object_patterns(domain="form", action="validate", xml)` |
@@ -43,6 +43,7 @@ PowerShell / any terminal command **WILL HANG** in VS 2022 / VS 2026 MCP integra
43
43
  | Resolve label / EDT / class refs | `validate_code(mode="references", code)` |
44
44
  | Build / BP / Sync | `build_d365fo_project` / `run_bp_check` / `trigger_db_sync` |
45
45
  | Error diagnosis | `get_knowledge(kind="error", errorText)` |
46
+ | Parameters for a `d365fo_file` operation / `generate_object` mode | `get_knowledge(kind="op-spec", topic="add-index" \| "table" \| "scaffold:form")` — those two tools keep their parameters OUT of the tool schema; look the contract up once for the operation you picked, then nest the values in `params` (`properties` for `action="create"`) |
46
47
 
47
48
  ## Key Rules
48
49
 
@@ -80,6 +81,23 @@ PowerShell / any terminal command **WILL HANG** in VS 2022 / VS 2026 MCP integra
80
81
  11. **The post-write diff must be additive or narrowly targeted** — verify via `review_workspace_changes` (or re-read with `get_*_info`) that no unrelated XML nodes (`<DataSources>`, `<Controls>`, methods, pattern metadata) disappeared. If they did, the edit failed: `undo_last_modification`.
81
82
  12. **An example form named by the user is a pattern contract** — keep its pattern family and required scaffolding (datasources, ActionPane/Tab/grid/QuickFilter); missing pattern elements are a failed generation even if the XML is well-formed.
82
83
 
84
+ ### Spending tool calls
85
+
86
+ 13. **Issue independent read-only calls together, in one step.** Every tool call re-reads the whole conversation, so a turn costs the round trip, not the tool. `get_object_info`, `search`, `labels`, `get_knowledge`, `object_patterns` and `find_references` have no side effects and never need to wait for each other — five lookups are one step, not five.
87
+ 14. **Use the plural form when there is one** — `get_object_info(objects[])`, `run_bp_check(objects[])`, `verify_d365fo_project(objects[])`, `search(queries[])`. All three `objects[]` forms take `{objectType, objectName}`; `queries[]` takes `{query}`. Note `get_object_info`'s SINGLE-object form still spells it `name`, not `objectName` — take the key from the tool's schema, don't assume.
88
+ 15. **Plan the reads before the first one.** Decide which objects the change touches, then fetch them in a single step instead of discovering them one call at a time.
89
+
90
+ ### Reading D365FO objects
91
+
92
+ 16. **Use `get_object_info`, not `read_file`, for anything under `PackagesLocalDirectory`.** Raw AOT XML is verbose and stays in context for the rest of the session; `get_object_info` returns the same facts structured and an order of magnitude smaller. Read the raw file only when you need its literal bytes.
93
+ 17. **Never hand-edit AOT XML with text replacement** (rule 3) — whitespace and element ordering are load-bearing, so the match fails or the write corrupts the object. `d365fo_file(action="modify")` exists for exactly this.
94
+ 18. **Do not call `update_symbol_index` after `d365fo_file` create/modify** — the index is already refreshed. It is for files changed OUTSIDE the server. Sole exception: a brand-new AxEdt/AxEnum you are about to name in a `generate_object` `fieldsHint`.
95
+ 19. **Ask for the smallest result set that answers the question** — `labels(action="search")` returns 10 one-line hits; raise `maxResults` or set `verbose` only when that is genuinely not enough.
96
+
97
+ ### Finishing
98
+
99
+ 20. **Keep the closing summary to what changed and what to do next.** Long recaps are the most expensive single output of a session and are re-read by nothing.
100
+
83
101
  ## Full Instructions
84
102
 
85
103
  The complete X++ rules, query grammar, CoC authoring rules, and workflow details are delivered via the MCP prompt `xpp_system_instructions`. If that prompt is not loaded, request it or consult [src/prompts/systemInstructions.ts](../src/prompts/systemInstructions.ts) directly.
package/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- **26 AI tools that know every X++ class, table, form, and EDT in your D365FO codebase**
5
+ **23 AI tools that know every X++ class, table, form, and EDT in your D365FO codebase**
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/d365fo-mcp.svg?logo=npm&color=cb3837)](https://www.npmjs.com/package/d365fo-mcp)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
9
  [![Node.js](https://img.shields.io/badge/node-%3E%3D24.0.0-brightgreen.svg)](https://nodejs.org/)
10
10
  [![TypeScript](https://img.shields.io/badge/TypeScript-7.0-blue.svg)](https://www.typescriptlang.org/)
11
- [![Tests](https://img.shields.io/badge/tests-2500%2B-brightgreen.svg)](docs/TESTING.md)
11
+ [![Tests](https://img.shields.io/badge/tests-2800%2B-brightgreen.svg)](docs/TESTING.md)
12
12
  <!-- coverage-badge:start -->
13
13
  [![Core coverage](https://img.shields.io/badge/core_coverage-100%25-brightgreen.svg)](eval/COVERAGE.md) [![Total coverage](https://img.shields.io/badge/total_coverage-100%25-lightgrey.svg)](eval/COVERAGE.md)
14
14
  <!-- coverage-badge:end -->
@@ -29,7 +29,7 @@
29
29
 
30
30
  AI assistants excel at C#, Python, and JavaScript. X++ is different: your D365FO codebase is private, deeply customized, and invisible to every model — so AI confidently generates code that doesn't compile.
31
31
 
32
- This server pre-indexes your entire D365FO installation (580 000+ symbols across standard, ISV, and custom models) and exposes it as 26 specialized MCP tools. Every signature, every CoC wrapper, every label, every form pattern — verified against your real metadata **before** the AI writes a single line.
32
+ This server pre-indexes your entire D365FO installation (580 000+ symbols across standard, ISV, and custom models) and exposes it as 23 specialized MCP tools. Every signature, every CoC wrapper, every label, every form pattern — verified against your real metadata **before** the AI writes a single line.
33
33
 
34
34
  ![Solution Architecture](docs/img/solution-architecture-diagram.svg)
35
35
 
@@ -115,12 +115,12 @@ Deployment guide: [docs/SETUP_AZURE.md](docs/SETUP_AZURE.md) — includes CI/CD
115
115
 
116
116
  | Getting started | Reference | Operations |
117
117
  |-----------------|-----------|------------|
118
- | [Quick Start](docs/QUICK_START.md) — connect or install | [All 26 tools](docs/MCP_TOOLS.md) | [Azure deployment](docs/SETUP_AZURE.md) |
118
+ | [Quick Start](docs/QUICK_START.md) — connect or install | [All 23 tools](docs/MCP_TOOLS.md) | [Azure deployment](docs/SETUP_AZURE.md) |
119
119
  | [Setup scenarios A–F](docs/SETUP.md) | [`.mcp.json` reference](docs/MCP_CONFIG.md) | [DevOps pipelines](docs/SETUP_AZURE.md#azure-devops-pipelines) |
120
120
  | [Claude Code setup](docs/SETUP.md#claude-code-cli) | [Configuration](docs/CONFIGURATION.md) | [Testing](docs/TESTING.md) |
121
121
  | [Usage examples](docs/USAGE_EXAMPLES.md) — real tool chains | [Architecture](docs/ARCHITECTURE.md) | [Custom / ISV models](docs/CUSTOM_EXTENSIONS.md) |
122
- | | | [Coverage](eval/COVERAGE.md) — what the badge counts |
123
- | | | [Eval loop](docs/AGENT_EVAL_LOOP.md) — the self-improvement harness |
122
+ | [Changelog](CHANGELOG.md) | [Knowledge authoring](docs/KNOWLEDGE_AUTHORING.md) | [Coverage](eval/COVERAGE.md) — what the badge counts |
123
+ | [Backlog](docs/BACKLOG.md) — deferred work | [New tool checklist](docs/NEW_TOOL_CHECKLIST.md) | [Eval loop](docs/AGENT_EVAL_LOOP.md) — the self-improvement harness |
124
124
 
125
125
  ## License
126
126
 
@@ -781,23 +781,46 @@ namespace D365MetadataBridge.Models
781
781
  /// <summary>
782
782
  /// Deserializes a typed parameter from the Params dictionary.
783
783
  /// Values arrive as JsonElement from System.Text.Json — convert to the target type.
784
+ ///
785
+ /// The old `catch { return null; }` made a WRONGLY SHAPED parameter
786
+ /// indistinguishable from an absent one, and the dispatcher's
787
+ /// `?? throw new ArgumentException("Missing: fields")` then told the caller the key
788
+ /// was missing while it sat right there in the request. That is the param-shape
789
+ /// contract bug this project keeps re-living — e.g. constraints sent as
790
+ /// [{fieldName, relatedFieldName}] when this side reads [{field, relatedField}], or
791
+ /// index fields sent as [{fieldName}] when this side reads a flat string[]. A
792
+ /// failure now names the parameter and shows what actually arrived.
793
+ ///
794
+ /// JsonOptions.Default is used so the batch path resolves property names exactly the
795
+ /// way the single-op BridgeRequest.GetParam&lt;T&gt; does; deserializing without it was
796
+ /// a second, quieter way for the two paths to disagree.
784
797
  /// </summary>
785
798
  public T? GetTypedParam<T>(string key) where T : class
786
799
  {
787
800
  if (Params == null || !Params.TryGetValue(key, out var raw) || raw == null)
788
801
  return null;
802
+ // Already the correct type (in-process caller, not a JSON round trip).
803
+ if (raw is T typed) return typed;
804
+
805
+ var json = raw is System.Text.Json.JsonElement je
806
+ ? je.GetRawText()
807
+ : System.Text.Json.JsonSerializer.Serialize(raw, Protocol.JsonOptions.Default);
789
808
  try
790
809
  {
791
- if (raw is System.Text.Json.JsonElement je)
792
- return System.Text.Json.JsonSerializer.Deserialize<T>(je.GetRawText());
793
- // Already correct type
794
- if (raw is T typed) return typed;
795
- // Fallback: serialize then deserialize
796
- var json = System.Text.Json.JsonSerializer.Serialize(raw);
797
- return System.Text.Json.JsonSerializer.Deserialize<T>(json);
810
+ // A JSON `null` deserializes to null here — same as an absent key, which is
811
+ // what the caller's "Missing: <key>" message correctly describes.
812
+ return System.Text.Json.JsonSerializer.Deserialize<T>(json, Protocol.JsonOptions.Default);
813
+ }
814
+ catch (System.Text.Json.JsonException ex)
815
+ {
816
+ throw new System.ArgumentException(
817
+ $"Parameter '{key}' has the wrong shape: {ex.Message} Received: {Preview(json)}");
798
818
  }
799
- catch { return null; }
800
819
  }
820
+
821
+ /// <summary>Caps the echoed payload — a batch op can carry a large array.</summary>
822
+ private static string Preview(string json) =>
823
+ json.Length <= 300 ? json : json.Substring(0, 300) + "…(truncated)";
801
824
  }
802
825
 
803
826
  public class BatchOperationResult
@@ -48,18 +48,19 @@ namespace D365MetadataBridge.Protocol
48
48
  }
49
49
 
50
50
  /// <summary>
51
- /// Helper to extract a boolean parameter from Params
51
+ /// Helper to extract a boolean parameter from Params.
52
+ /// Reads through ParamCoercion so this path and HandleBatchModify cannot disagree
53
+ /// about what a given value means.
52
54
  /// </summary>
53
55
  public bool? GetBoolParam(string name)
54
56
  {
55
57
  if (Params == null || Params.Value.ValueKind != JsonValueKind.Object)
56
58
  return null;
57
59
 
58
- if (Params.Value.TryGetProperty(name, out var prop) &&
59
- (prop.ValueKind == JsonValueKind.True || prop.ValueKind == JsonValueKind.False))
60
- return prop.GetBoolean();
60
+ if (!Params.Value.TryGetProperty(name, out var prop))
61
+ return null;
61
62
 
62
- return null;
63
+ return ParamCoercion.ToBool(prop, name);
63
64
  }
64
65
 
65
66
  /// <summary>
@@ -116,6 +117,80 @@ namespace D365MetadataBridge.Protocol
116
117
  }
117
118
  }
118
119
 
120
+ /// <summary>
121
+ /// Parameter coercion shared by BOTH dispatch paths.
122
+ ///
123
+ /// The single-op path reads its params straight off the request's JsonElement; the
124
+ /// batch path gets them as Dictionary&lt;string, object&gt;, whose values System.Text.Json
125
+ /// boxes as JsonElement. JsonElement does not implement IConvertible, so the batch
126
+ /// path's Convert.ToBoolean(value) threw InvalidCastException ("Object must implement
127
+ /// IConvertible") for EVERY operation carrying a bool — mandatory, allowDuplicates,
128
+ /// alternateKey, extendBaseFieldGroup — and HandleBatchModify's per-op catch reported
129
+ /// that cast as the operation's reason for failing. Booleans are read by JSON kind, in
130
+ /// one place, so the two paths cannot drift apart again.
131
+ /// </summary>
132
+ public static class ParamCoercion
133
+ {
134
+ /// <summary>
135
+ /// Boolean from a raw (boxed) batch parameter value.
136
+ /// </summary>
137
+ public static bool? ToBool(object? value, string paramName)
138
+ {
139
+ switch (value)
140
+ {
141
+ case null: return null;
142
+ case bool b: return b;
143
+ case JsonElement el: return ToBool(el, paramName);
144
+ case string s: return ParseBoolText(s, paramName, s);
145
+ default:
146
+ throw new ArgumentException(
147
+ $"Parameter '{paramName}' must be a boolean — got {value.GetType().Name} '{value}'.");
148
+ }
149
+ }
150
+
151
+ /// <summary>
152
+ /// Boolean from a JSON value. Absent/null yields null so the caller's own default
153
+ /// applies; a value that is present but not readable as a boolean throws rather
154
+ /// than degrading to false — a silent false writes a field that is not mandatory,
155
+ /// or an index that allows duplicates, and reports success either way.
156
+ ///
157
+ /// The string spellings are accepted because the AxTable XML value is No/Yes and
158
+ /// callers reach for that form (#27); it is a typo, not a shape, that fails here.
159
+ /// </summary>
160
+ public static bool? ToBool(JsonElement el, string paramName)
161
+ {
162
+ switch (el.ValueKind)
163
+ {
164
+ case JsonValueKind.True: return true;
165
+ case JsonValueKind.False: return false;
166
+ case JsonValueKind.Null:
167
+ case JsonValueKind.Undefined: return null;
168
+ case JsonValueKind.String: return ParseBoolText(el.GetString(), paramName, el.GetRawText());
169
+ case JsonValueKind.Number when el.TryGetInt32(out var n) && (n == 0 || n == 1):
170
+ return n == 1;
171
+ default:
172
+ throw new ArgumentException(
173
+ $"Parameter '{paramName}' must be a boolean — got {el.ValueKind} {el.GetRawText()}. " +
174
+ "Accepted: true/false, \"true\"/\"false\", \"Yes\"/\"No\", 1/0.");
175
+ }
176
+ }
177
+
178
+ private static bool? ParseBoolText(string? text, string paramName, string received)
179
+ {
180
+ switch (text?.Trim().ToLowerInvariant())
181
+ {
182
+ case null:
183
+ case "": return null;
184
+ case "true": case "yes": case "1": return true;
185
+ case "false": case "no": case "0": return false;
186
+ default:
187
+ throw new ArgumentException(
188
+ $"Parameter '{paramName}' must be a boolean — got {received}. " +
189
+ "Accepted: true/false, \"true\"/\"false\", \"Yes\"/\"No\", 1/0.");
190
+ }
191
+ }
192
+ }
193
+
119
194
  /// <summary>
120
195
  /// JSON-RPC style response sent to the Node.js MCP server
121
196
  /// </summary>
@@ -461,9 +461,12 @@ namespace D365MetadataBridge.Protocol
461
461
  // path inside AddField; both are absent for table/table-extension.
462
462
  // This is the single-op RPC that BridgeClient.addField() calls —
463
463
  // it must forward the same parameters as the batch-modify case below.
464
+ // `type`/`extendedDataType` are the spellings the create path's
465
+ // fields[] uses for the same two things; accepting only the
466
+ // add-field spelling silently defaulted them (String, no EDT).
464
467
  return _writeService!.AddField(tableName, fieldName,
465
- request.GetStringParam("fieldType") ?? "String",
466
- request.GetStringParam("edt"),
468
+ request.GetStringParam("fieldType") ?? request.GetStringParam("type") ?? "String",
469
+ request.GetStringParam("edt") ?? request.GetStringParam("extendedDataType"),
467
470
  request.GetBoolParam("mandatory") ?? false,
468
471
  request.GetStringParam("label"),
469
472
  request.GetStringParam("dataField"),
@@ -951,7 +954,12 @@ namespace D365MetadataBridge.Protocol
951
954
 
952
955
  // Extract string helper
953
956
  string? S(string key) => p.TryGetValue(key, out var v) ? v?.ToString() : null;
954
- bool? B(string key) => p.TryGetValue(key, out var v) && v != null ? Convert.ToBoolean(v) : null;
957
+ // System.Text.Json boxes every params value as JsonElement, which does
958
+ // not implement IConvertible — Convert.ToBoolean(v) therefore threw
959
+ // InvalidCastException on every batch op carrying a bool, and the catch
960
+ // below reported that cast as the operation's failure. Same coercion as
961
+ // the single-op arms' request.GetBoolParam.
962
+ bool? B(string key) => p.TryGetValue(key, out var v) ? ParamCoercion.ToBool(v, key) : null;
955
963
 
956
964
  switch (op.Operation.ToLowerInvariant())
957
965
  {
@@ -972,10 +980,11 @@ namespace D365MetadataBridge.Protocol
972
980
  case "add-field":
973
981
  // dataField/dataSource select the data-entity-extension mapped-field
974
982
  // path inside AddField; both are absent for table/table-extension.
983
+ // Same `type`/`extendedDataType` aliasing as the single-op RPC.
975
984
  writeResult = _writeService.AddField(objectName,
976
985
  S("fieldName") ?? throw new ArgumentException("Missing: fieldName"),
977
- S("fieldType") ?? "String",
978
- S("edt"),
986
+ S("fieldType") ?? S("type") ?? "String",
987
+ S("edt") ?? S("extendedDataType"),
979
988
  B("mandatory") ?? false,
980
989
  S("label"),
981
990
  S("dataField"),
@@ -1096,9 +1105,18 @@ namespace D365MetadataBridge.Protocol
1096
1105
  case "addenumvalue":
1097
1106
  case "add-enum-value":
1098
1107
  {
1108
+ // A value that would not parse used to fall through as 0 —
1109
+ // so add-enum-value(value:"three") wrote element 0, a
1110
+ // duplicate of the enum's first member, and reported success.
1099
1111
  int enumVal = 0;
1100
1112
  if (p.TryGetValue("enumValue", out var ev) || p.TryGetValue("value", out ev))
1101
- int.TryParse(ev?.ToString(), out enumVal);
1113
+ {
1114
+ var evText = ev?.ToString();
1115
+ if (!string.IsNullOrWhiteSpace(evText) && !int.TryParse(evText, out enumVal))
1116
+ throw new ArgumentException(
1117
+ $"add-enum-value: value '{evText}' is not an integer — nothing was written. " +
1118
+ "Enum values are integers 0-250; omit the parameter to take the next free position.");
1119
+ }
1102
1120
  writeResult = _writeService.AddEnumValue(objectName,
1103
1121
  S("enumValueName") ?? S("valueName") ?? throw new ArgumentException("Missing: enumValueName"),
1104
1122
  enumVal, S("label"), S("countryRegionCodes"));
@@ -10,9 +10,36 @@ namespace D365MetadataBridge.Services
10
10
  /// <summary>
11
11
  /// Provides cross-reference queries using the DYNAMICSXREFDB SQL database.
12
12
  /// This replaces the FTS5 text-search approach with real compiler-resolved references.
13
+ ///
14
+ /// ## Query failures are errors, never empty results
15
+ ///
16
+ /// Every method here answers one question — "what else touches this?" — and the answer
17
+ /// drives whether it is safe to change something. A failed query used to be returned as
18
+ /// a SUCCESSFUL response carrying count=0 plus an `error` field, which reads to any
19
+ /// caller that does not inspect the extra field as the strongest possible clearance:
20
+ /// "nothing references this, go ahead". A down or unreachable xref database therefore
21
+ /// produced false negatives on exactly the question the service exists to answer, and
22
+ /// the more broken the database, the safer everything looked.
23
+ ///
24
+ /// So failures throw. RequestDispatcher.HandleXref turns them into a JSON-RPC error, and
25
+ /// the TS adapters already treat a rejected call as "no answer from the bridge" and fall
26
+ /// back to their own scan instead of trusting a zero.
13
27
  /// </summary>
14
28
  public class CrossReferenceService
15
29
  {
30
+ /// <summary>
31
+ /// Rethrows a failed xref query as an error the dispatcher will surface, with the
32
+ /// query's subject in the message so the caller can see what was NOT answered.
33
+ /// </summary>
34
+ private static Exception QueryFailed(string operation, string subject, Exception cause)
35
+ {
36
+ Console.Error.WriteLine($"[CrossRefService] {operation}({subject}) failed: {cause.Message}");
37
+ return new InvalidOperationException(
38
+ $"Cross-reference query {operation}('{subject}') failed: {cause.Message}. " +
39
+ "This is NOT an empty result — the cross-reference database did not answer, so nothing " +
40
+ "can be concluded about references to this object.", cause);
41
+ }
42
+
16
43
  private readonly string _connectionString;
17
44
 
18
45
  public CrossReferenceService(string server, string database)
@@ -209,8 +236,7 @@ namespace D365MetadataBridge.Services
209
236
  }
210
237
  catch (SqlException ex)
211
238
  {
212
- Console.Error.WriteLine($"[CrossRefService] SQL error: {ex.Message}");
213
- return new { objectPath, count = 0, references = new List<ReferenceInfoModel>(), error = ex.Message };
239
+ throw QueryFailed("findReferences", objectPath, ex);
214
240
  }
215
241
 
216
242
  return new { objectPath, count = references.Count, references };
@@ -335,15 +361,7 @@ namespace D365MetadataBridge.Services
335
361
  }
336
362
  catch (Exception ex)
337
363
  {
338
- Console.Error.WriteLine($"[WARN] FindExtensionClasses({baseClassName}): {ex.Message}");
339
- return new
340
- {
341
- baseClassName,
342
- count = 0,
343
- extensions = new List<ExtensionClassDetailModel>(),
344
- error = ex.Message,
345
- _source = "C# bridge (DYNAMICSXREFDB)"
346
- };
364
+ throw QueryFailed("findExtensionClasses", baseClassName, ex);
347
365
  }
348
366
  }
349
367
 
@@ -447,8 +465,7 @@ namespace D365MetadataBridge.Services
447
465
  }
448
466
  catch (Exception ex)
449
467
  {
450
- Console.Error.WriteLine($"[WARN] FindEventSubscribers({targetName}): {ex.Message}");
451
- return new { targetName, count = 0, handlers = results, error = ex.Message, _source = "C# bridge (DYNAMICSXREFDB)" };
468
+ throw QueryFailed("findEventSubscribers", targetName, ex);
452
469
  }
453
470
  }
454
471
 
@@ -574,8 +591,7 @@ namespace D365MetadataBridge.Services
574
591
  }
575
592
  catch (SqlException ex)
576
593
  {
577
- Console.Error.WriteLine($"[CrossRefService] FindApiUsageCallers SQL error: {ex.Message}");
578
- return new { apiName, count = 0, callers = new List<ApiUsageCallerModel>(), error = ex.Message, _source = "C# bridge (DYNAMICSXREFDB)" };
594
+ throw QueryFailed("findApiUsageCallers", apiName, ex);
579
595
  }
580
596
 
581
597
  // Group by caller class for pattern summary
@@ -125,13 +125,36 @@ namespace D365MetadataBridge.Services
125
125
  {
126
126
  var sw = System.Diagnostics.Stopwatch.StartNew();
127
127
  var factory = new MetadataProviderFactory();
128
+ var previous = _provider;
128
129
  _provider = CreatePrimaryProvider(factory, _packagesPath, _referencePackagesPath);
130
+ // Hand the new provider over BEFORE releasing the old one, so nothing is
131
+ // holding a provider that is about to be disposed.
129
132
  OnProviderRefreshed?.Invoke(_provider);
133
+ // Every write auto-refreshes (HandleWrite refreshAfterSuccess), so a long
134
+ // authoring session builds one DiskProvider per write. Dropping the old one
135
+ // on the floor kept its file handles and per-package metadata caches alive
136
+ // over the whole PackagesLocalDirectory until GC felt like it — the bridge
137
+ // grew until it stopped answering, which is the state the client sees as a
138
+ // wedged process.
139
+ DisposeProvider(previous);
130
140
  sw.Stop();
131
141
  Console.Error.WriteLine($"[MetadataService] Provider refreshed in {sw.ElapsedMilliseconds}ms");
132
142
  return new { refreshed = true, elapsedMs = sw.ElapsedMilliseconds };
133
143
  }
134
144
 
145
+ /// <summary>
146
+ /// Releases a superseded provider. Whether DiskProvider implements IDisposable
147
+ /// varies across metamodel versions, so this asks rather than assumes; a failure
148
+ /// to release is logged and swallowed because the replacement is already live and
149
+ /// the refresh itself succeeded.
150
+ /// </summary>
151
+ private static void DisposeProvider(IMetadataProvider? provider)
152
+ {
153
+ if (provider is not IDisposable disposable) return;
154
+ try { disposable.Dispose(); }
155
+ catch (Exception ex) { Console.Error.WriteLine($"[WARN] Failed to dispose superseded metadata provider: {ex.Message}"); }
156
+ }
157
+
135
158
  /// <summary>
136
159
  /// Asks IMetadataProvider to read back an object that was just written to disk.
137
160
  /// Returns field/method counts and a success flag — proves the XML is well-formed
@@ -144,11 +167,23 @@ namespace D365MetadataBridge.Services
144
167
  switch (objectType.ToLowerInvariant())
145
168
  {
146
169
  case "table":
147
- case "table-extension":
148
170
  if (!_provider.Tables.Exists(objectName)) return new { valid = false, reason = $"Table '{objectName}' not found by IMetadataProvider after refresh" };
149
171
  var t = _provider.Tables.Read(objectName);
150
172
  return new { valid = true, objectType, objectName, fieldCount = t?.Fields?.Count ?? 0, methodCount = t?.Methods?.Count ?? 0, indexCount = t?.Indexes?.Count ?? 0 };
151
173
 
174
+ // Extensions live in their OWN provider collection, keyed by the dotted
175
+ // "Base.ModelExtension" name. Tables/Forms are keyed by plain names, so
176
+ // looking an extension up there always misses — a false negative on every
177
+ // extension write, regardless of refresh.
178
+ case "table-extension":
179
+ {
180
+ var tx = _provider.TableExtensions.Read(objectName);
181
+ if (tx == null) return new { valid = false, reason = $"TableExtension '{objectName}' not found by IMetadataProvider after refresh" };
182
+ int txMethods = 0;
183
+ try { dynamic dtx = tx; if (dtx?.Methods != null) foreach (var _ in dtx.Methods) txMethods++; } catch { }
184
+ return new { valid = true, objectType, objectName, fieldCount = tx.Fields?.Count ?? 0, methodCount = txMethods, indexCount = tx.Indexes?.Count ?? 0 };
185
+ }
186
+
152
187
  case "class":
153
188
  case "class-extension":
154
189
  if (!_provider.Classes.Exists(objectName)) return new { valid = false, reason = $"Class '{objectName}' not found by IMetadataProvider after refresh" };
@@ -167,10 +202,13 @@ namespace D365MetadataBridge.Services
167
202
  return new { valid = true, objectType, objectName };
168
203
 
169
204
  case "form":
170
- case "form-extension":
171
205
  if (!_provider.Forms.Exists(objectName)) return new { valid = false, reason = $"Form '{objectName}' not found by IMetadataProvider after refresh" };
172
206
  return new { valid = true, objectType, objectName };
173
207
 
208
+ case "form-extension":
209
+ if (_provider.FormExtensions.Read(objectName) == null) return new { valid = false, reason = $"FormExtension '{objectName}' not found by IMetadataProvider after refresh" };
210
+ return new { valid = true, objectType, objectName };
211
+
174
212
  case "query":
175
213
  if (!_provider.Queries.Exists(objectName)) return new { valid = false, reason = $"Query '{objectName}' not found by IMetadataProvider after refresh" };
176
214
  return new { valid = true, objectType, objectName };
@@ -235,7 +273,6 @@ namespace D365MetadataBridge.Services
235
273
  switch (objectType.ToLowerInvariant())
236
274
  {
237
275
  case "table":
238
- case "table-extension":
239
276
  {
240
277
  var prov = PickProvider(p => p.Tables.Exists(objectName));
241
278
  if (prov == null) return null;
@@ -243,6 +280,17 @@ namespace D365MetadataBridge.Services
243
280
  try { var mi = prov.Tables.GetModelInfo(objectName); if (mi?.Count > 0) model = mi.First().Name; } catch { }
244
281
  return new { exists = true, objectType, objectName, model };
245
282
  }
283
+ // Probed against TableExtensions/FormExtensions, not Tables/Forms — see
284
+ // the note in ValidateObject: the dotted extension name is never a key
285
+ // in the base collection, so probing there never resolves an extension.
286
+ case "table-extension":
287
+ {
288
+ var prov = PickProvider(p => p.TableExtensions.Read(objectName) != null);
289
+ if (prov == null) return null;
290
+ string? model = null;
291
+ try { var mi = prov.TableExtensions.GetModelInfo(objectName); if (mi?.Count > 0) model = mi.First().Name; } catch { }
292
+ return new { exists = true, objectType, objectName, model };
293
+ }
246
294
  case "class":
247
295
  case "class-extension":
248
296
  {
@@ -269,7 +317,6 @@ namespace D365MetadataBridge.Services
269
317
  return new { exists = true, objectType, objectName, model };
270
318
  }
271
319
  case "form":
272
- case "form-extension":
273
320
  {
274
321
  var prov = PickProvider(p => p.Forms.Exists(objectName));
275
322
  if (prov == null) return null;
@@ -277,6 +324,14 @@ namespace D365MetadataBridge.Services
277
324
  try { var mi = prov.Forms.GetModelInfo(objectName); if (mi?.Count > 0) model = mi.First().Name; } catch { }
278
325
  return new { exists = true, objectType, objectName, model };
279
326
  }
327
+ case "form-extension":
328
+ {
329
+ var prov = PickProvider(p => p.FormExtensions.Read(objectName) != null);
330
+ if (prov == null) return null;
331
+ string? model = null;
332
+ try { var mi = prov.FormExtensions.GetModelInfo(objectName); if (mi?.Count > 0) model = mi.First().Name; } catch { }
333
+ return new { exists = true, objectType, objectName, model };
334
+ }
280
335
  case "query":
281
336
  {
282
337
  var prov = PickProvider(p => p.Queries.Exists(objectName));