@nhtio/adk 0.1.0-master-e6c58205 → 0.1.0-master-89b47bec

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 (290) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/__vite-browser-external-CU7uua8b.mjs +9 -0
  3. package/__vite-browser-external-CU7uua8b.mjs.map +1 -0
  4. package/__vite-browser-external-DAHxjh-D.js +13 -0
  5. package/__vite-browser-external-DAHxjh-D.js.map +1 -0
  6. package/batteries/embeddings/openai/adapter.cjs +1 -1
  7. package/batteries/embeddings/openai/adapter.mjs +1 -1
  8. package/batteries/embeddings/webllm/adapter.cjs +1 -1
  9. package/batteries/embeddings/webllm/adapter.mjs +1 -1
  10. package/batteries/llm/ollama/adapter.cjs +5 -5
  11. package/batteries/llm/ollama/adapter.mjs +5 -5
  12. package/batteries/llm/ollama/helpers.cjs +2 -2
  13. package/batteries/llm/ollama/helpers.mjs +2 -2
  14. package/batteries/llm/ollama/validation.cjs +1 -1
  15. package/batteries/llm/ollama/validation.mjs +1 -1
  16. package/batteries/llm/openai_chat_completions/adapter.cjs +5 -5
  17. package/batteries/llm/openai_chat_completions/adapter.mjs +5 -5
  18. package/batteries/llm/openai_chat_completions/helpers.cjs +2 -2
  19. package/batteries/llm/openai_chat_completions/helpers.mjs +2 -2
  20. package/batteries/llm/openai_chat_completions/validation.cjs +1 -1
  21. package/batteries/llm/openai_chat_completions/validation.mjs +1 -1
  22. package/batteries/llm/webllm_chat_completions/adapter.cjs +5 -5
  23. package/batteries/llm/webllm_chat_completions/adapter.mjs +5 -5
  24. package/batteries/llm/webllm_chat_completions/validation.cjs +1 -1
  25. package/batteries/llm/webllm_chat_completions/validation.mjs +1 -1
  26. package/batteries/media/contracts.cjs +38 -8
  27. package/batteries/media/contracts.cjs.map +1 -1
  28. package/batteries/media/contracts.d.ts +86 -6
  29. package/batteries/media/contracts.mjs +37 -9
  30. package/batteries/media/contracts.mjs.map +1 -1
  31. package/batteries/media/engines/audio_decode.cjs +46 -6
  32. package/batteries/media/engines/audio_decode.cjs.map +1 -1
  33. package/batteries/media/engines/audio_decode.d.ts +9 -5
  34. package/batteries/media/engines/audio_decode.mjs +47 -7
  35. package/batteries/media/engines/audio_decode.mjs.map +1 -1
  36. package/batteries/media/engines/data.cjs +159 -0
  37. package/batteries/media/engines/data.cjs.map +1 -0
  38. package/batteries/media/engines/data.d.ts +36 -0
  39. package/batteries/media/engines/data.mjs +157 -0
  40. package/batteries/media/engines/data.mjs.map +1 -0
  41. package/batteries/media/engines/exceljs.cjs +290 -0
  42. package/batteries/media/engines/exceljs.cjs.map +1 -0
  43. package/batteries/media/engines/exceljs.d.ts +41 -0
  44. package/batteries/media/engines/exceljs.mjs +288 -0
  45. package/batteries/media/engines/exceljs.mjs.map +1 -0
  46. package/batteries/media/engines/execa_executor.cjs +1 -1
  47. package/batteries/media/engines/execa_executor.mjs +1 -1
  48. package/batteries/media/engines/fs_workspace.cjs +1 -1
  49. package/batteries/media/engines/fs_workspace.mjs +1 -1
  50. package/batteries/media/engines/jimp.cjs +24 -2
  51. package/batteries/media/engines/jimp.cjs.map +1 -1
  52. package/batteries/media/engines/jimp.d.ts +3 -1
  53. package/batteries/media/engines/jimp.mjs +24 -2
  54. package/batteries/media/engines/jimp.mjs.map +1 -1
  55. package/batteries/media/engines/sharp.cjs +37 -4
  56. package/batteries/media/engines/sharp.cjs.map +1 -1
  57. package/batteries/media/engines/sharp.d.ts +5 -3
  58. package/batteries/media/engines/sharp.mjs +37 -4
  59. package/batteries/media/engines/sharp.mjs.map +1 -1
  60. package/batteries/media/engines/sheetjs.cjs +438 -0
  61. package/batteries/media/engines/sheetjs.cjs.map +1 -0
  62. package/batteries/media/engines/sheetjs.d.ts +51 -0
  63. package/batteries/media/engines/sheetjs.mjs +436 -0
  64. package/batteries/media/engines/sheetjs.mjs.map +1 -0
  65. package/batteries/media/engines/soffice.cjs +37 -3
  66. package/batteries/media/engines/soffice.cjs.map +1 -1
  67. package/batteries/media/engines/soffice.mjs +38 -4
  68. package/batteries/media/engines/soffice.mjs.map +1 -1
  69. package/batteries/media/engines/tesseract_js.cjs +1 -1
  70. package/batteries/media/engines/tesseract_js.mjs +1 -1
  71. package/batteries/media/engines/transformers_asr.cjs +1 -1
  72. package/batteries/media/engines/transformers_asr.mjs +1 -1
  73. package/batteries/media/forge.cjs +89 -20
  74. package/batteries/media/forge.cjs.map +1 -1
  75. package/batteries/media/forge.mjs +89 -20
  76. package/batteries/media/forge.mjs.map +1 -1
  77. package/batteries/media/formats.d.ts +9 -0
  78. package/batteries/media/index.d.ts +8 -6
  79. package/batteries/media/lint.cjs +270 -58
  80. package/batteries/media/lint.cjs.map +1 -1
  81. package/batteries/media/lint.d.ts +45 -0
  82. package/batteries/media/lint.mjs +267 -58
  83. package/batteries/media/lint.mjs.map +1 -1
  84. package/batteries/media/registry.d.ts +11 -2
  85. package/batteries/media/steps/data.d.ts +26 -0
  86. package/batteries/media/steps/doc.d.ts +17 -7
  87. package/batteries/media/steps/patch.d.ts +90 -0
  88. package/batteries/media/steps/sheet.d.ts +10 -6
  89. package/batteries/media/steps/text.d.ts +4 -1
  90. package/batteries/media/validate.d.ts +5 -0
  91. package/batteries/media/verbs.d.ts +12 -1
  92. package/batteries/media.cjs +869 -448
  93. package/batteries/media.cjs.map +1 -1
  94. package/batteries/media.mjs +829 -409
  95. package/batteries/media.mjs.map +1 -1
  96. package/batteries/storage/flydrive.cjs +3 -6
  97. package/batteries/storage/flydrive.cjs.map +1 -1
  98. package/batteries/storage/flydrive.mjs +3 -6
  99. package/batteries/storage/flydrive.mjs.map +1 -1
  100. package/batteries/storage/in_memory.cjs +1 -1
  101. package/batteries/storage/in_memory.mjs +1 -1
  102. package/batteries/storage/opfs.cjs +1 -1
  103. package/batteries/storage/opfs.mjs +1 -1
  104. package/batteries/tools/_shared.cjs +2 -2
  105. package/batteries/tools/_shared.mjs +2 -2
  106. package/batteries/tools/color.cjs +2 -2
  107. package/batteries/tools/color.mjs +2 -2
  108. package/batteries/tools/comparison.cjs +3 -3
  109. package/batteries/tools/comparison.mjs +3 -3
  110. package/batteries/tools/data_structure.cjs +3 -3
  111. package/batteries/tools/data_structure.mjs +3 -3
  112. package/batteries/tools/datetime_extended.cjs +3 -3
  113. package/batteries/tools/datetime_extended.mjs +3 -3
  114. package/batteries/tools/datetime_math.cjs +3 -3
  115. package/batteries/tools/datetime_math.mjs +3 -3
  116. package/batteries/tools/encoding.cjs +3 -3
  117. package/batteries/tools/encoding.mjs +3 -3
  118. package/batteries/tools/formatting.cjs +3 -3
  119. package/batteries/tools/formatting.mjs +3 -3
  120. package/batteries/tools/geo_basics.cjs +2 -2
  121. package/batteries/tools/geo_basics.mjs +2 -2
  122. package/batteries/tools/math.cjs +3 -3
  123. package/batteries/tools/math.mjs +3 -3
  124. package/batteries/tools/memory.cjs +6 -6
  125. package/batteries/tools/memory.mjs +6 -6
  126. package/batteries/tools/parsing.cjs +4 -4
  127. package/batteries/tools/parsing.mjs +4 -4
  128. package/batteries/tools/retrievables.cjs +5 -5
  129. package/batteries/tools/retrievables.mjs +5 -5
  130. package/batteries/tools/scrapper.cjs +1 -1
  131. package/batteries/tools/scrapper.mjs +1 -1
  132. package/batteries/tools/searxng.cjs +1 -1
  133. package/batteries/tools/searxng.mjs +1 -1
  134. package/batteries/tools/standing_instructions.cjs +4 -4
  135. package/batteries/tools/standing_instructions.mjs +4 -4
  136. package/batteries/tools/statistics.cjs +4 -4
  137. package/batteries/tools/statistics.mjs +4 -4
  138. package/batteries/tools/string_processing.cjs +3 -3
  139. package/batteries/tools/string_processing.mjs +3 -3
  140. package/batteries/tools/structured_data.cjs +3 -3
  141. package/batteries/tools/structured_data.mjs +3 -3
  142. package/batteries/tools/text_analysis.cjs +3 -3
  143. package/batteries/tools/text_analysis.mjs +3 -3
  144. package/batteries/tools/text_comparison.cjs +2 -2
  145. package/batteries/tools/text_comparison.mjs +2 -2
  146. package/batteries/tools/time.cjs +3 -3
  147. package/batteries/tools/time.mjs +3 -3
  148. package/batteries/tools/unit_conversion.cjs +2 -2
  149. package/batteries/tools/unit_conversion.mjs +2 -2
  150. package/batteries/tools/web_retrieval.cjs +1 -1
  151. package/batteries/tools/web_retrieval.mjs +1 -1
  152. package/batteries/tools.cjs +2 -2
  153. package/batteries/tools.mjs +2 -2
  154. package/batteries/vector/arangodb.cjs +1 -1
  155. package/batteries/vector/arangodb.mjs +1 -1
  156. package/batteries/vector/clickhouse.cjs +1 -1
  157. package/batteries/vector/clickhouse.mjs +1 -1
  158. package/batteries/vector/cloudflare.cjs +1 -1
  159. package/batteries/vector/cloudflare.mjs +1 -1
  160. package/batteries/vector/couchbase.cjs +1 -1
  161. package/batteries/vector/couchbase.mjs +1 -1
  162. package/batteries/vector/duckdb.cjs +1 -1
  163. package/batteries/vector/duckdb.mjs +1 -1
  164. package/batteries/vector/elasticsearch.cjs +1 -1
  165. package/batteries/vector/elasticsearch.mjs +1 -1
  166. package/batteries/vector/filters.cjs +1 -1
  167. package/batteries/vector/filters.mjs +1 -1
  168. package/batteries/vector/hnswlib.cjs +1 -1
  169. package/batteries/vector/hnswlib.mjs +1 -1
  170. package/batteries/vector/lancedb.cjs +1 -1
  171. package/batteries/vector/lancedb.mjs +1 -1
  172. package/batteries/vector/mariadb.cjs +1 -1
  173. package/batteries/vector/mariadb.mjs +1 -1
  174. package/batteries/vector/meilisearch.cjs +1 -1
  175. package/batteries/vector/meilisearch.mjs +1 -1
  176. package/batteries/vector/migrate.cjs +1 -1
  177. package/batteries/vector/migrate.mjs +1 -1
  178. package/batteries/vector/mongodb.cjs +1 -1
  179. package/batteries/vector/mongodb.mjs +1 -1
  180. package/batteries/vector/neo4j.cjs +1 -1
  181. package/batteries/vector/neo4j.mjs +1 -1
  182. package/batteries/vector/opensearch.cjs +1 -1
  183. package/batteries/vector/opensearch.mjs +1 -1
  184. package/batteries/vector/oracle23ai.cjs +1 -1
  185. package/batteries/vector/oracle23ai.mjs +1 -1
  186. package/batteries/vector/pinecone.cjs +1 -1
  187. package/batteries/vector/pinecone.mjs +1 -1
  188. package/batteries/vector/redis.cjs +1 -1
  189. package/batteries/vector/redis.mjs +1 -1
  190. package/batteries/vector/retrievable.cjs +1 -1
  191. package/batteries/vector/retrievable.mjs +1 -1
  192. package/batteries/vector/s3vectors.cjs +1 -1
  193. package/batteries/vector/s3vectors.mjs +1 -1
  194. package/batteries/vector/solr.cjs +1 -1
  195. package/batteries/vector/solr.mjs +1 -1
  196. package/batteries/vector/surrealdb.cjs +1 -1
  197. package/batteries/vector/surrealdb.mjs +1 -1
  198. package/batteries/vector/typesense.cjs +1 -1
  199. package/batteries/vector/typesense.mjs +1 -1
  200. package/batteries/vector/vespa.cjs +1 -1
  201. package/batteries/vector/vespa.mjs +1 -1
  202. package/batteries/vector/weaviate.cjs +1 -1
  203. package/batteries/vector/weaviate.mjs +1 -1
  204. package/batteries.cjs +2 -2
  205. package/batteries.mjs +2 -2
  206. package/bundled_xlsx.d.ts +1075 -0
  207. package/{common-BxoznqbX.mjs → common-BQoFv108.mjs} +7 -7
  208. package/{common-BxoznqbX.mjs.map → common-BQoFv108.mjs.map} +1 -1
  209. package/{common-BBK2e5ro.js → common-DmDn4Gfl.js} +7 -7
  210. package/{common-BBK2e5ro.js.map → common-DmDn4Gfl.js.map} +1 -1
  211. package/common.cjs +7 -7
  212. package/common.mjs +7 -7
  213. package/{dispatch_runner-Hn5idJYx.js → dispatch_runner-BwplbCPr.js} +3 -3
  214. package/{dispatch_runner-Hn5idJYx.js.map → dispatch_runner-BwplbCPr.js.map} +1 -1
  215. package/{dispatch_runner-DUrpj0xR.mjs → dispatch_runner-C1ar_D7W.mjs} +3 -3
  216. package/{dispatch_runner-DUrpj0xR.mjs.map → dispatch_runner-C1ar_D7W.mjs.map} +1 -1
  217. package/dispatch_runner.cjs +1 -1
  218. package/dispatch_runner.mjs +1 -1
  219. package/eslint.cjs +1 -1
  220. package/eslint.mjs +1 -1
  221. package/forge.cjs +4 -4
  222. package/forge.mjs +4 -4
  223. package/formats-CCylUHZw.mjs +161 -0
  224. package/formats-CCylUHZw.mjs.map +1 -0
  225. package/formats-DZVuqZUN.js +196 -0
  226. package/formats-DZVuqZUN.js.map +1 -0
  227. package/guards.cjs +8 -8
  228. package/guards.mjs +8 -8
  229. package/index.cjs +11 -11
  230. package/index.mjs +11 -11
  231. package/mcp/adk-docs-corpus.json +1 -1
  232. package/package.json +198 -182
  233. package/{scrapper-DLj_KkxK.mjs → scrapper-Dn2wXu0Y.mjs} +5 -5
  234. package/{scrapper-DLj_KkxK.mjs.map → scrapper-Dn2wXu0Y.mjs.map} +1 -1
  235. package/{scrapper-Cmf1CSA-.js → scrapper-mBZ0Odq7.js} +5 -5
  236. package/{scrapper-Cmf1CSA-.js.map → scrapper-mBZ0Odq7.js.map} +1 -1
  237. package/{searxng-BKs5s8Su.js → searxng-Dj-iP_Vz.js} +5 -5
  238. package/{searxng-BKs5s8Su.js.map → searxng-Dj-iP_Vz.js.map} +1 -1
  239. package/{searxng-BJ-aZcMz.mjs → searxng-DjFxtFf6.mjs} +5 -5
  240. package/{searxng-BJ-aZcMz.mjs.map → searxng-DjFxtFf6.mjs.map} +1 -1
  241. package/server.json +21 -0
  242. package/skills/adk-assembly/SKILL.md +2 -2
  243. package/{spooled_artifact-BR0IhU9y.js → spooled_artifact-BCmPrxZS.js} +3 -3
  244. package/{spooled_artifact-BR0IhU9y.js.map → spooled_artifact-BCmPrxZS.js.map} +1 -1
  245. package/{spooled_artifact-nTE6ujM8.mjs → spooled_artifact-DL36jnqA.mjs} +3 -3
  246. package/{spooled_artifact-nTE6ujM8.mjs.map → spooled_artifact-DL36jnqA.mjs.map} +1 -1
  247. package/spooled_artifact.cjs +2 -2
  248. package/spooled_artifact.mjs +2 -2
  249. package/{spooled_markdown_artifact-Dj3fNowz.js → spooled_markdown_artifact-CD4PrrpV.js} +3 -3
  250. package/{spooled_markdown_artifact-Dj3fNowz.js.map → spooled_markdown_artifact-CD4PrrpV.js.map} +1 -1
  251. package/{spooled_markdown_artifact-DIYzbQv8.mjs → spooled_markdown_artifact-Da-Xnebh.mjs} +3 -3
  252. package/{spooled_markdown_artifact-DIYzbQv8.mjs.map → spooled_markdown_artifact-Da-Xnebh.mjs.map} +1 -1
  253. package/{thought-Cde2Y_GK.mjs → thought-CtOoZUSE.mjs} +3 -3
  254. package/{thought-Cde2Y_GK.mjs.map → thought-CtOoZUSE.mjs.map} +1 -1
  255. package/{thought-BFtsnUTU.js → thought-eka4iUHa.js} +3 -3
  256. package/{thought-BFtsnUTU.js.map → thought-eka4iUHa.js.map} +1 -1
  257. package/{tool-CcxYxA5X.mjs → tool-CITF20yJ.mjs} +3 -3
  258. package/{tool-CcxYxA5X.mjs.map → tool-CITF20yJ.mjs.map} +1 -1
  259. package/{tool-B2nnkwNU.js → tool-Dhjca7rC.js} +3 -3
  260. package/{tool-B2nnkwNU.js.map → tool-Dhjca7rC.js.map} +1 -1
  261. package/{tool_call-BxL3eU1k.js → tool_call-Cyi0hUUc.js} +4 -4
  262. package/{tool_call-BxL3eU1k.js.map → tool_call-Cyi0hUUc.js.map} +1 -1
  263. package/{tool_call-B3MOog2R.mjs → tool_call-D_k36aI4.mjs} +4 -4
  264. package/{tool_call-B3MOog2R.mjs.map → tool_call-D_k36aI4.mjs.map} +1 -1
  265. package/{tool_registry-BaLUIzWz.mjs → tool_registry-CZ55mIti.mjs} +152 -143
  266. package/tool_registry-CZ55mIti.mjs.map +1 -0
  267. package/{tool_registry-BdKlPuHS.js → tool_registry-DwZ6pzN4.js} +151 -142
  268. package/tool_registry-DwZ6pzN4.js.map +1 -0
  269. package/{turn_runner-M2I8G2ok.js → turn_runner-CCZcsoDU.js} +5 -5
  270. package/{turn_runner-M2I8G2ok.js.map → turn_runner-CCZcsoDU.js.map} +1 -1
  271. package/{turn_runner-BV4BaZK9.mjs → turn_runner-DRPpVvsd.mjs} +5 -5
  272. package/{turn_runner-BV4BaZK9.mjs.map → turn_runner-DRPpVvsd.mjs.map} +1 -1
  273. package/turn_runner.cjs +1 -1
  274. package/turn_runner.mjs +1 -1
  275. package/{validate-JOHfh-BB.mjs → validate-C_hTnDBE.mjs} +95 -8
  276. package/validate-C_hTnDBE.mjs.map +1 -0
  277. package/{validate-DgDZkUKs.js → validate-gaSa9bsk.js} +95 -8
  278. package/validate-gaSa9bsk.js.map +1 -0
  279. package/xlsx-B0sTpEC2.js +30615 -0
  280. package/xlsx-B0sTpEC2.js.map +1 -0
  281. package/xlsx-DiMpn_Xv.mjs +30596 -0
  282. package/xlsx-DiMpn_Xv.mjs.map +1 -0
  283. package/xlsx.zahl-D2_Q-Xs4.mjs +6 -0
  284. package/xlsx.zahl-D2_Q-Xs4.mjs.map +1 -0
  285. package/xlsx.zahl-ZIcUQVv8.js +7 -0
  286. package/xlsx.zahl-ZIcUQVv8.js.map +1 -0
  287. package/tool_registry-BaLUIzWz.mjs.map +0 -1
  288. package/tool_registry-BdKlPuHS.js.map +0 -1
  289. package/validate-DgDZkUKs.js.map +0 -1
  290. package/validate-JOHfh-BB.mjs.map +0 -1
@@ -1,30 +1,7 @@
1
+ import { t as require___vite_browser_external } from "../../__vite-browser-external-CU7uua8b.mjs";
1
2
  import { ESLintUtils } from "@typescript-eslint/utils";
2
3
  //#region src/batteries/media/lint.ts
3
- /**
4
- * @module @nhtio/adk/batteries/media/lint
5
- *
6
- * ESLint rules for consumers of the media pipeline battery: machine-checkable enforcement of
7
- * the engine-composition contracts implementors are most likely to get wrong.
8
- *
9
- * @remarks
10
- * Battery-scoped rules ship with the battery, not with the core `@nhtio/adk/eslint` plugin —
11
- * they only matter to deployments that compose this battery, and they version with the
12
- * battery's own API. The rules are report-only (no autofix); carve out a deliberate exception
13
- * with an inline `// eslint-disable-next-line adk-media/{rule} -- {reason}` comment.
14
- *
15
- * `@typescript-eslint/utils` and `eslint` are OPTIONAL peer dependencies of `@nhtio/adk` —
16
- * installed only by consumers who lint with this plugin. The battery never imports this
17
- * module at runtime.
18
- *
19
- * @example Flat config
20
- * ```ts
21
- * import adkMedia from '@nhtio/adk/batteries/media/lint'
22
- *
23
- * export default [
24
- * { plugins: { 'adk-media': adkMedia.plugin }, rules: adkMedia.configs.recommended.rules },
25
- * ]
26
- * ```
27
- */
4
+ var import___vite_browser_external = require___vite_browser_external();
28
5
  var createRule = ESLintUtils.RuleCreator((name) => `https://adk.nht.io/batteries/media/lint#${name}`);
29
6
  var ENGINE_SUBPATH = /^@nhtio\/adk\/batteries\/media\/engines\//;
30
7
  /**
@@ -61,6 +38,48 @@ var preferEngineResolver = createRule({
61
38
  } };
62
39
  }
63
40
  });
41
+ var SHEET_OPS = [
42
+ "sheet.add_rows",
43
+ "sheet.add_columns",
44
+ "sheet.update_cells",
45
+ "sheet.delete_rows",
46
+ "sheet.delete_columns",
47
+ "sheet.rename_sheet",
48
+ "sheet.add_sheet",
49
+ "sheet.remove_sheet",
50
+ "sheet.reorder_sheets",
51
+ "sheet.transform_table"
52
+ ];
53
+ var SHEET_WRITE_TARGETS = [
54
+ "xlsx",
55
+ "xlsm",
56
+ "xlsb",
57
+ "xls",
58
+ "ods",
59
+ "fods",
60
+ "csv",
61
+ "txt",
62
+ "html",
63
+ "rtf",
64
+ "sylk",
65
+ "dif",
66
+ "dbf",
67
+ "numbers",
68
+ "json"
69
+ ];
70
+ var SHEET_READ_MIMES = [
71
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
72
+ "application/vnd.ms-excel.sheet.macroEnabled.12",
73
+ "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
74
+ "application/vnd.ms-excel",
75
+ "application/vnd.oasis.opendocument.spreadsheet",
76
+ "application/vnd.oasis.opendocument.spreadsheet-flat-xml",
77
+ "text/csv",
78
+ "application/x-iwork-numbers-sffnumbers",
79
+ "application/x-sylk",
80
+ "application/x-dif",
81
+ "application/x-dbf"
82
+ ];
64
83
  /**
65
84
  * Known declarations of the bundled engine factories, for shadow analysis. An ESLint rule
66
85
  * cannot execute the factories it lints, so this table mirrors their declarations by hand —
@@ -68,6 +87,52 @@ var preferEngineResolver = createRule({
68
87
  * Exported for that test only.
69
88
  */
70
89
  var BUNDLED_SUMMARIES = {
90
+ dataEngine: {
91
+ label: "dataEngine",
92
+ mutates: [],
93
+ converts: [
94
+ {
95
+ from: ["application/x-adk-empty"],
96
+ to: [
97
+ "txt",
98
+ "md",
99
+ "json",
100
+ "yaml",
101
+ "csv",
102
+ "html"
103
+ ]
104
+ },
105
+ {
106
+ from: ["application/json"],
107
+ to: [
108
+ "yaml",
109
+ "txt",
110
+ "csv"
111
+ ]
112
+ },
113
+ {
114
+ from: ["application/yaml"],
115
+ to: ["json"]
116
+ },
117
+ {
118
+ from: ["text/csv"],
119
+ to: ["json"]
120
+ }
121
+ ],
122
+ edits: []
123
+ },
124
+ exceljsEngine: {
125
+ label: "exceljsEngine",
126
+ mutates: [],
127
+ converts: [{
128
+ from: ["application/x-adk-empty"],
129
+ to: ["xlsx"]
130
+ }],
131
+ edits: [{
132
+ over: ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
133
+ ops: SHEET_OPS
134
+ }]
135
+ },
71
136
  jimpEngine: {
72
137
  label: "jimpEngine",
73
138
  mutates: [{
@@ -93,7 +158,18 @@ var BUNDLED_SUMMARIES = {
93
158
  "tiff"
94
159
  ]
95
160
  }],
96
- converts: []
161
+ converts: [{
162
+ from: ["application/x-adk-empty"],
163
+ to: [
164
+ "png",
165
+ "jpg",
166
+ "jpeg",
167
+ "bmp",
168
+ "gif",
169
+ "tiff"
170
+ ]
171
+ }],
172
+ edits: []
97
173
  },
98
174
  sharpEngine: {
99
175
  label: "sharpEngine",
@@ -115,7 +191,34 @@ var BUNDLED_SUMMARIES = {
115
191
  "gif"
116
192
  ]
117
193
  }],
118
- converts: []
194
+ converts: [{
195
+ from: ["application/x-adk-empty"],
196
+ to: [
197
+ "png",
198
+ "jpg",
199
+ "jpeg",
200
+ "webp",
201
+ "tiff",
202
+ "avif",
203
+ "gif"
204
+ ]
205
+ }],
206
+ edits: []
207
+ },
208
+ sheetjsEngine: {
209
+ label: "sheetjsEngine",
210
+ mutates: [],
211
+ converts: [{
212
+ from: ["application/x-adk-empty"],
213
+ to: SHEET_WRITE_TARGETS
214
+ }, {
215
+ from: SHEET_READ_MIMES,
216
+ to: SHEET_WRITE_TARGETS
217
+ }],
218
+ edits: [{
219
+ over: SHEET_READ_MIMES,
220
+ ops: SHEET_OPS
221
+ }]
119
222
  }
120
223
  };
121
224
  var literalStrings = (node) => {
@@ -164,7 +267,8 @@ var summarize = (node) => {
164
267
  const summary = {
165
268
  label: idNode?.type === "Literal" && typeof idNode.value === "string" ? idNode.value : "engine",
166
269
  mutates: [],
167
- converts: []
270
+ converts: [],
271
+ edits: []
168
272
  };
169
273
  const mutates = propOf(node, "mutates");
170
274
  if (mutates) {
@@ -196,7 +300,21 @@ var summarize = (node) => {
196
300
  });
197
301
  }
198
302
  }
199
- if (summary.mutates.length + summary.converts.length === 0) return void 0;
303
+ const edits = propOf(node, "edits");
304
+ if (edits) {
305
+ if (edits.type !== "ArrayExpression") return void 0;
306
+ for (const el of edits.elements) {
307
+ if (!el || el.type !== "ObjectExpression") return void 0;
308
+ const over = literalStrings(propOf(el, "over"));
309
+ const ops = literalStrings(propOf(el, "ops"));
310
+ if (!over || !ops) return void 0;
311
+ summary.edits.push({
312
+ over,
313
+ ops
314
+ });
315
+ }
316
+ }
317
+ if (summary.mutates.length + summary.converts.length + summary.edits.length === 0) return;
200
318
  return summary;
201
319
  };
202
320
  var patternCovers = (broad, narrow) => {
@@ -208,10 +326,11 @@ var patternsCover = (broad, narrow) => narrow.every((n) => broad.some((b) => pat
208
326
  var subset = (sup, sub) => sub.every((s) => sup.includes(s));
209
327
  /** `true` when EVERY capability of `later` is subsumed by some capability of `earlier`. */
210
328
  var shadows = (earlier, later) => {
211
- if (later.mutates.length + later.converts.length === 0) return false;
329
+ if (later.mutates.length + later.converts.length + later.edits.length === 0) return false;
212
330
  const mutatesCovered = later.mutates.every((lm) => earlier.mutates.some((em) => patternsCover(em.over, lm.over) && subset(em.ops, lm.ops) && subset(em.encodes, lm.encodes)));
213
331
  const convertsCovered = later.converts.every((lc) => earlier.converts.some((ec) => patternsCover(ec.from, lc.from) && subset(ec.to, lc.to)));
214
- return mutatesCovered && convertsCovered;
332
+ const editsCovered = later.edits.every((le) => earlier.edits.some((ee) => patternsCover(ee.over, le.over) && subset(ee.ops, le.ops)));
333
+ return mutatesCovered && convertsCovered && editsCovered;
215
334
  };
216
335
  /**
217
336
  * Flags an engines-array element whose statically-known capabilities are entirely covered by
@@ -258,40 +377,130 @@ var noShadowedEngine = createRule({
258
377
  });
259
378
  var CONTRACTS_SUBPATH = "@nhtio/adk/batteries/media/contracts";
260
379
  /**
380
+ * Flags a `ConvertOptions` declaration-merging block whose module specifier is not the
381
+ * contracts subpath. Augmenting the barrel (or any other module) silently does nothing —
382
+ * the keys never merge, and the typo costs hours.
383
+ */
384
+ var augmentContractsModule = createRule({
385
+ name: "augment-contracts-module",
386
+ meta: {
387
+ type: "problem",
388
+ docs: { description: "ConvertOptions augmentation must target the contracts subpath — declaration merging against any other module specifier silently fails to merge." },
389
+ schema: [],
390
+ messages: { wrongModule: "This ConvertOptions augmentation targets \"{{actual}}\", so it will silently never merge. Declaration merging must target the module that declares the interface: declare module '{{expected}}'." }
391
+ },
392
+ defaultOptions: [],
393
+ create(context) {
394
+ return { TSModuleDeclaration(node) {
395
+ if (node.global) return;
396
+ if (node.id.type !== "Literal" || typeof node.id.value !== "string") return;
397
+ const specifier = node.id.value;
398
+ if (specifier === CONTRACTS_SUBPATH || specifier.endsWith("/media/contracts")) return;
399
+ if (!specifier.includes("media") && !specifier.includes("@nhtio/adk")) return;
400
+ const body = node.body;
401
+ if (!body || body.type !== "TSModuleBlock") return;
402
+ if (!body.body.some((stmt) => stmt.type === "TSInterfaceDeclaration" && stmt.id.name === "ConvertOptions")) return;
403
+ context.report({
404
+ node: node.id,
405
+ messageId: "wrongModule",
406
+ data: {
407
+ actual: specifier,
408
+ expected: CONTRACTS_SUBPATH
409
+ }
410
+ });
411
+ } };
412
+ }
413
+ });
414
+ /**
415
+ * The optional peer dependencies each bundled engine subpath lazily imports at runtime.
416
+ * Keyed by the engine's subpath suffix (after `engines/`). An engine with no external peer
417
+ * (soffice, fs_workspace) is absent — there's nothing to install. Kept in lockstep with the
418
+ * engines' actual `import(...)` calls by `engine_peers_drift.node.spec.ts`.
419
+ */
420
+ var ENGINE_PEERS = {
421
+ jimp: ["jimp"],
422
+ sharp: ["sharp"],
423
+ sheetjs: ["xlsx"],
424
+ exceljs: ["exceljs"],
425
+ data: ["js-yaml", "papaparse"],
426
+ tesseract_js: ["tesseract.js"],
427
+ audio_decode: ["audio-decode"],
428
+ transformers_asr: ["@huggingface/transformers"],
429
+ execa_executor: ["execa"]
430
+ };
431
+ /** Map a bundled engine subpath to its peer list, or undefined when it isn't a known engine. */
432
+ var peersForSubpath = (source) => {
433
+ const m = /^@nhtio\/adk\/batteries\/media\/engines\/([a-z_]+)$/.exec(source);
434
+ if (!m) return void 0;
435
+ return ENGINE_PEERS[m[1]];
436
+ };
437
+ var defaultPeerResolver = (peer, fromFile) => {
438
+ try {
439
+ (0, import___vite_browser_external.createRequire)(fromFile).resolve(peer);
440
+ return true;
441
+ } catch {
442
+ return false;
443
+ }
444
+ };
445
+ var activePeerResolver = defaultPeerResolver;
446
+ /** Override the peer resolver (spec-only seam). Pass nothing to restore the Node default. */
447
+ var setPeerResolverForTesting = (resolver) => {
448
+ activePeerResolver = resolver ?? defaultPeerResolver;
449
+ };
450
+ /**
261
451
  * Map of rule id (without the `adk-media/` plugin prefix) to its rule object. Registered on
262
452
  * the plugin as `rules`, so configs reference them as `adk-media/{id}`.
263
453
  */
264
454
  var rules = {
265
455
  "prefer-engine-resolver": preferEngineResolver,
266
456
  "no-shadowed-engine": noShadowedEngine,
267
- "augment-contracts-module": createRule({
268
- name: "augment-contracts-module",
457
+ "augment-contracts-module": augmentContractsModule,
458
+ "require-engine-peers": createRule({
459
+ name: "require-engine-peers",
269
460
  meta: {
270
461
  type: "problem",
271
- docs: { description: "ConvertOptions augmentation must target the contracts subpathdeclaration merging against any other module specifier silently fails to merge." },
272
- schema: [],
273
- messages: { wrongModule: "This ConvertOptions augmentation targets \"{{actual}}\", so it will silently never merge. Declaration merging must target the module that declares the interface: declare module '{{expected}}'." }
462
+ docs: { description: "A bundled media engine's optional peer dependency must be installed where it's composed otherwise the pipeline throws at first use, not at construction." },
463
+ schema: [{
464
+ type: "object",
465
+ properties: { ignore: {
466
+ type: "array",
467
+ items: { type: "string" },
468
+ description: "Peer package names to skip (e.g. one resolved via a custom loader the linter cannot see)."
469
+ } },
470
+ additionalProperties: false
471
+ }],
472
+ messages: { missingPeer: "The \"{{engine}}\" media engine requires the optional peer \"{{peer}}\", which cannot be resolved from this file. Install it ({{install}}) or the pipeline will throw E_INVALID_MEDIA_PIPELINE_CONFIG the first time this engine runs. Opt out with an eslint-disable-next-line adk-media/require-engine-peers comment + reason." }
274
473
  },
275
- defaultOptions: [],
276
- create(context) {
277
- return { TSModuleDeclaration(node) {
278
- if (node.global) return;
279
- if (node.id.type !== "Literal" || typeof node.id.value !== "string") return;
280
- const specifier = node.id.value;
281
- if (specifier === CONTRACTS_SUBPATH || specifier.endsWith("/media/contracts")) return;
282
- if (!specifier.includes("media") && !specifier.includes("@nhtio/adk")) return;
283
- const body = node.body;
284
- if (!body || body.type !== "TSModuleBlock") return;
285
- if (!body.body.some((stmt) => stmt.type === "TSInterfaceDeclaration" && stmt.id.name === "ConvertOptions")) return;
286
- context.report({
287
- node: node.id,
288
- messageId: "wrongModule",
289
- data: {
290
- actual: specifier,
291
- expected: CONTRACTS_SUBPATH
292
- }
293
- });
294
- } };
474
+ defaultOptions: [{ ignore: [] }],
475
+ create(context, [options]) {
476
+ const ignore = new Set(options?.ignore ?? []);
477
+ /** SheetJS ships only from its CDN tarball, so the install hint must not say the registry. */
478
+ const installHint = (peer) => peer === "xlsx" ? "pnpm add xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" : `pnpm add ${peer}`;
479
+ const checkSource = (source, reportNode) => {
480
+ const peers = peersForSubpath(source);
481
+ if (!peers) return;
482
+ const engine = source.slice(source.lastIndexOf("/") + 1);
483
+ for (const peer of peers) {
484
+ if (ignore.has(peer)) continue;
485
+ if (!activePeerResolver(peer, context.filename)) context.report({
486
+ node: reportNode,
487
+ messageId: "missingPeer",
488
+ data: {
489
+ engine,
490
+ peer,
491
+ install: installHint(peer)
492
+ }
493
+ });
494
+ }
495
+ };
496
+ return {
497
+ ImportDeclaration(node) {
498
+ if (typeof node.source.value === "string") checkSource(node.source.value, node.source);
499
+ },
500
+ ImportExpression(node) {
501
+ if (node.source.type === "Literal" && typeof node.source.value === "string") checkSource(node.source.value, node.source);
502
+ }
503
+ };
295
504
  }
296
505
  })
297
506
  };
@@ -302,7 +511,7 @@ var rules = {
302
511
  var plugin = {
303
512
  meta: {
304
513
  name: "@nhtio/adk/batteries/media/lint",
305
- version: "0.1.0-master-e6c58205"
514
+ version: "0.1.0-master-89b47bec"
306
515
  },
307
516
  rules
308
517
  };
@@ -326,6 +535,6 @@ var lint_default = {
326
535
  configs
327
536
  };
328
537
  //#endregion
329
- export { BUNDLED_SUMMARIES, configs, lint_default as default, plugin, rules };
538
+ export { BUNDLED_SUMMARIES, ENGINE_PEERS, configs, lint_default as default, plugin, rules, setPeerResolverForTesting };
330
539
 
331
540
  //# sourceMappingURL=lint.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"lint.mjs","names":[],"sources":["../../../src/batteries/media/lint.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/batteries/media/lint\n *\n * ESLint rules for consumers of the media pipeline battery: machine-checkable enforcement of\n * the engine-composition contracts implementors are most likely to get wrong.\n *\n * @remarks\n * Battery-scoped rules ship with the battery, not with the core `@nhtio/adk/eslint` plugin —\n * they only matter to deployments that compose this battery, and they version with the\n * battery's own API. The rules are report-only (no autofix); carve out a deliberate exception\n * with an inline `// eslint-disable-next-line adk-media/{rule} -- {reason}` comment.\n *\n * `@typescript-eslint/utils` and `eslint` are OPTIONAL peer dependencies of `@nhtio/adk` —\n * installed only by consumers who lint with this plugin. The battery never imports this\n * module at runtime.\n *\n * @example Flat config\n * ```ts\n * import adkMedia from '@nhtio/adk/batteries/media/lint'\n *\n * export default [\n * { plugins: { 'adk-media': adkMedia.plugin }, rules: adkMedia.configs.recommended.rules },\n * ]\n * ```\n */\n\nimport { ESLintUtils } from '@typescript-eslint/utils'\nimport type { TSESTree } from '@typescript-eslint/utils'\nimport type { FlatConfig } from '@typescript-eslint/utils/ts-eslint'\n\nconst createRule = ESLintUtils.RuleCreator(\n (name) => `https://adk.nht.io/batteries/media/lint#${name}`\n)\n\n// ── prefer-engine-resolver ───────────────────────────────────────────────────\n\nconst ENGINE_SUBPATH = /^@nhtio\\/adk\\/batteries\\/media\\/engines\\//\n\n/**\n * Flags a static value import of a bundled engine subpath. The documented canonical supply\n * form is the dynamic-import resolver, which keeps the engine wrapper module out of every\n * bundle that merely might use it. Type-only imports are fine.\n */\nconst preferEngineResolver = createRule({\n name: 'prefer-engine-resolver',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Supply bundled media engines as dynamic-import resolvers, not static imports — a static import puts the engine wrapper in every bundle, even when the engine is conditional.',\n },\n schema: [],\n messages: {\n preferResolver:\n \"Import bundled engines lazily: supply `() => import('{{source}}').then((m) => m.{{hint}}(…))` in the engines array instead of a static import. Static imports put the engine wrapper module in every bundle. Opt out with an eslint-disable-next-line adk-media/prefer-engine-resolver comment + reason.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n if (typeof node.source.value !== 'string') return\n if (!ENGINE_SUBPATH.test(node.source.value)) return\n if (node.importKind === 'type') return\n const valueSpecifiers = node.specifiers.filter(\n (s) => !(s.type === 'ImportSpecifier' && s.importKind === 'type')\n )\n if (valueSpecifiers.length === 0) return\n const first = valueSpecifiers[0]\n const hint =\n first.type === 'ImportSpecifier' && first.imported.type === 'Identifier'\n ? first.imported.name\n : 'engineFactory'\n context.report({\n node,\n messageId: 'preferResolver',\n data: { source: node.source.value, hint },\n })\n },\n }\n },\n})\n\n// ── no-shadowed-engine ───────────────────────────────────────────────────────\n\n/** A statically-derived capability summary for one engines-array element. */\nexport interface EngineSummary {\n /** Display label for the diagnostic. */\n label: string\n /** Mutate groups: input patterns, ops, encodes. */\n mutates: Array<{ over: string[]; ops: string[]; encodes: string[] }>\n /** Convert groups: input patterns, target tokens. */\n converts: Array<{ from: string[]; to: string[] }>\n}\n\n/**\n * Known declarations of the bundled engine factories, for shadow analysis. An ESLint rule\n * cannot execute the factories it lints, so this table mirrors their declarations by hand —\n * and is pinned against the live factories by a drift test (`lint_drift.node.spec.ts`).\n * Exported for that test only.\n */\nexport const BUNDLED_SUMMARIES: Record<string, EngineSummary> = {\n jimpEngine: {\n label: 'jimpEngine',\n mutates: [\n {\n over: ['image/png', 'image/jpeg', 'image/bmp', 'image/gif', 'image/tiff'],\n ops: ['resize', 'rotate', 'flip', 'strip_metadata'],\n encodes: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'tiff'],\n },\n ],\n converts: [],\n },\n sharpEngine: {\n label: 'sharpEngine',\n mutates: [\n {\n over: ['image/*'],\n ops: ['resize', 'rotate', 'flip', 'strip_metadata'],\n encodes: ['png', 'jpg', 'jpeg', 'webp', 'tiff', 'avif', 'gif'],\n },\n ],\n converts: [],\n },\n}\n\nconst literalStrings = (node: TSESTree.Node | undefined): string[] | undefined => {\n if (!node || node.type !== 'ArrayExpression') return undefined\n const out: string[] = []\n for (const el of node.elements) {\n if (!el || el.type !== 'Literal' || typeof el.value !== 'string') return undefined\n out.push(el.value)\n }\n return out\n}\n\nconst propOf = (obj: TSESTree.ObjectExpression, name: string): TSESTree.Node | undefined =>\n obj.properties.find(\n (p): p is TSESTree.Property =>\n p.type === 'Property' && !p.computed && p.key.type === 'Identifier' && p.key.name === name\n )?.value\n\n/** Find the bundled factory name referenced by an element (direct call or resolver arrow). */\nconst bundledFactoryName = (node: TSESTree.Node): string | undefined => {\n if (\n node.type === 'CallExpression' &&\n node.callee.type === 'Identifier' &&\n node.callee.name in BUNDLED_SUMMARIES\n ) {\n return node.callee.name\n }\n // Resolver forms: () => …; scan the body for m.jimpEngine(…) / jimpEngine(…).\n if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {\n let found: string | undefined\n const visit = (n: TSESTree.Node): void => {\n if (found) return\n if (n.type === 'CallExpression') {\n if (n.callee.type === 'Identifier' && n.callee.name in BUNDLED_SUMMARIES) {\n found = n.callee.name\n } else if (\n n.callee.type === 'MemberExpression' &&\n n.callee.property.type === 'Identifier' &&\n n.callee.property.name in BUNDLED_SUMMARIES\n ) {\n found = n.callee.property.name\n }\n }\n for (const key of Object.keys(n)) {\n if (key === 'parent') continue\n const value = (n as unknown as Record<string, unknown>)[key]\n const children = Array.isArray(value) ? value : [value]\n for (const child of children) {\n if (!child || typeof child !== 'object') continue\n const childNode = child as TSESTree.Node\n if (typeof childNode.type !== 'string') continue\n visit(childNode)\n }\n }\n }\n visit(node.body)\n return found\n }\n return undefined\n}\n\n/** Derive a capability summary from an element, or undefined when not statically known. */\nconst summarize = (node: TSESTree.Node): EngineSummary | undefined => {\n const factory = bundledFactoryName(node)\n if (factory) return BUNDLED_SUMMARIES[factory]\n if (node.type !== 'ObjectExpression') return undefined\n const idNode = propOf(node, 'id')\n const label =\n idNode?.type === 'Literal' && typeof idNode.value === 'string' ? idNode.value : 'engine'\n const summary: EngineSummary = { label, mutates: [], converts: [] }\n const mutates = propOf(node, 'mutates')\n if (mutates) {\n if (mutates.type !== 'ArrayExpression') return undefined\n for (const el of mutates.elements) {\n if (!el || el.type !== 'ObjectExpression') return undefined\n const over = literalStrings(propOf(el, 'over'))\n const ops = literalStrings(propOf(el, 'ops'))\n const encodes = literalStrings(propOf(el, 'encodes'))\n if (!over || !ops || !encodes) return undefined\n summary.mutates.push({ over, ops, encodes })\n }\n }\n const converts = propOf(node, 'converts')\n if (converts) {\n if (converts.type !== 'ArrayExpression') return undefined\n for (const el of converts.elements) {\n if (!el || el.type !== 'ObjectExpression') return undefined\n const from = literalStrings(propOf(el, 'from'))\n const to = literalStrings(propOf(el, 'to'))\n if (!from || !to) return undefined\n summary.converts.push({ from, to })\n }\n }\n if (summary.mutates.length + summary.converts.length === 0) return undefined\n return summary\n}\n\nconst patternCovers = (broad: string, narrow: string): boolean => {\n if (broad === narrow) return true\n if (broad.endsWith('/*')) return narrow.startsWith(broad.slice(0, -1))\n return false\n}\n\nconst patternsCover = (broad: string[], narrow: string[]): boolean =>\n narrow.every((n) => broad.some((b) => patternCovers(b, n)))\n\nconst subset = (sup: string[], sub: string[]): boolean => sub.every((s) => sup.includes(s))\n\n/** `true` when EVERY capability of `later` is subsumed by some capability of `earlier`. */\nconst shadows = (earlier: EngineSummary, later: EngineSummary): boolean => {\n if (later.mutates.length + later.converts.length === 0) return false\n const mutatesCovered = later.mutates.every((lm) =>\n earlier.mutates.some(\n (em) =>\n patternsCover(em.over, lm.over) && subset(em.ops, lm.ops) && subset(em.encodes, lm.encodes)\n )\n )\n const convertsCovered = later.converts.every((lc) =>\n earlier.converts.some((ec) => patternsCover(ec.from, lc.from) && subset(ec.to, lc.to))\n )\n return mutatesCovered && convertsCovered\n}\n\n/**\n * Flags an engines-array element whose statically-known capabilities are entirely covered by\n * an EARLIER element — dispatch is first-capable-wins, so the later engine is dead code.\n */\nconst noShadowedEngine = createRule({\n name: 'no-shadowed-engine',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'An engine whose declared capabilities are a subset of an earlier engine in the array can never be selected — first-capable-wins dispatch makes it dead code. Reorder (narrow before broad) or remove it.',\n },\n schema: [],\n messages: {\n shadowed:\n 'Engine \"{{later}}\" can never be selected: \"{{earlier}}\" appears earlier in the array and declares a superset of its capabilities (dispatch is first-capable-wins). Put the narrower engine first, or remove it. Opt out with an eslint-disable-next-line adk-media/no-shadowed-engine comment + reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n CallExpression(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'Identifier' || node.callee.name !== 'createMediaPipeline') {\n return\n }\n const config = node.arguments[0]\n if (!config || config.type !== 'ObjectExpression') return\n const engines = propOf(config, 'engines')\n if (!engines || engines.type !== 'ArrayExpression') return\n const summaries = engines.elements.map((el) => (el ? summarize(el) : undefined))\n for (let i = 1; i < summaries.length; i++) {\n const later = summaries[i]\n if (!later) continue\n for (let j = 0; j < i; j++) {\n const earlier = summaries[j]\n if (!earlier) continue\n if (shadows(earlier, later)) {\n context.report({\n node: engines.elements[i]!,\n messageId: 'shadowed',\n data: { earlier: earlier.label, later: later.label },\n })\n break\n }\n }\n }\n },\n }\n },\n})\n\n// ── augment-contracts-module ─────────────────────────────────────────────────\n\nconst CONTRACTS_SUBPATH = '@nhtio/adk/batteries/media/contracts'\n\n/**\n * Flags a `ConvertOptions` declaration-merging block whose module specifier is not the\n * contracts subpath. Augmenting the barrel (or any other module) silently does nothing —\n * the keys never merge, and the typo costs hours.\n */\nconst augmentContractsModule = createRule({\n name: 'augment-contracts-module',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'ConvertOptions augmentation must target the contracts subpath — declaration merging against any other module specifier silently fails to merge.',\n },\n schema: [],\n messages: {\n wrongModule:\n 'This ConvertOptions augmentation targets \"{{actual}}\", so it will silently never merge. Declaration merging must target the module that declares the interface: declare module \\'{{expected}}\\'.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n TSModuleDeclaration(node: TSESTree.TSModuleDeclaration) {\n if (node.global) return\n if (node.id.type !== 'Literal' || typeof node.id.value !== 'string') return\n const specifier = node.id.value\n if (specifier === CONTRACTS_SUBPATH || specifier.endsWith('/media/contracts')) return\n // Only flag specifiers that are plausibly aimed at this battery.\n if (!specifier.includes('media') && !specifier.includes('@nhtio/adk')) return\n const body = node.body\n if (!body || body.type !== 'TSModuleBlock') return\n const declaresConvertOptions = body.body.some(\n (stmt) => stmt.type === 'TSInterfaceDeclaration' && stmt.id.name === 'ConvertOptions'\n )\n if (!declaresConvertOptions) return\n context.report({\n node: node.id,\n messageId: 'wrongModule',\n data: { actual: specifier, expected: CONTRACTS_SUBPATH },\n })\n },\n }\n },\n})\n\n// ── plugin assembly ──────────────────────────────────────────────────────────\n\n/**\n * Map of rule id (without the `adk-media/` plugin prefix) to its rule object. Registered on\n * the plugin as `rules`, so configs reference them as `adk-media/{id}`.\n */\nexport const rules = {\n 'prefer-engine-resolver': preferEngineResolver,\n 'no-shadowed-engine': noShadowedEngine,\n 'augment-contracts-module': augmentContractsModule,\n} satisfies FlatConfig.Plugin['rules']\n\n/**\n * The media battery's ESLint plugin object. Register under the `adk-media` namespace:\n * `plugins: { 'adk-media': plugin }`.\n */\nexport const plugin: FlatConfig.Plugin = {\n meta: { name: '@nhtio/adk/batteries/media/lint', version: __VERSION__ },\n rules,\n}\n\nconst recommendedRules: NonNullable<FlatConfig.Config['rules']> = Object.fromEntries(\n Object.keys(rules).map((id) => [`adk-media/${id}`, 'error'])\n)\n\n/**\n * Named config presets. `recommended` enables every rule at `error` and registers the plugin\n * under the `adk-media` namespace — spread it into a flat config to adopt the full set.\n */\nexport const configs = {\n recommended: {\n name: '@nhtio/adk/batteries/media/lint/recommended',\n plugins: { 'adk-media': plugin },\n rules: recommendedRules,\n } satisfies FlatConfig.Config,\n}\n\n/**\n * Default export bundles the plugin and its config presets, mirroring the shape ESLint flat\n * configs expect from a plugin module.\n */\nexport default { plugin, rules, configs }\n"],"mappings":"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"}
1
+ {"version":3,"file":"lint.mjs","names":[],"sources":["../../../src/batteries/media/lint.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/batteries/media/lint\n *\n * ESLint rules for consumers of the media pipeline battery: machine-checkable enforcement of\n * the engine-composition contracts implementors are most likely to get wrong.\n *\n * @remarks\n * Battery-scoped rules ship with the battery, not with the core `@nhtio/adk/eslint` plugin —\n * they only matter to deployments that compose this battery, and they version with the\n * battery's own API. The rules are report-only (no autofix); carve out a deliberate exception\n * with an inline `// eslint-disable-next-line adk-media/{rule} -- {reason}` comment.\n *\n * `@typescript-eslint/utils` and `eslint` are OPTIONAL peer dependencies of `@nhtio/adk` —\n * installed only by consumers who lint with this plugin. The battery never imports this\n * module at runtime.\n *\n * @example Flat config\n * ```ts\n * import adkMedia from '@nhtio/adk/batteries/media/lint'\n *\n * export default [\n * { plugins: { 'adk-media': adkMedia.plugin }, rules: adkMedia.configs.recommended.rules },\n * ]\n * ```\n */\n\nimport { createRequire } from 'node:module'\nimport { ESLintUtils } from '@typescript-eslint/utils'\nimport type { TSESTree } from '@typescript-eslint/utils'\nimport type { FlatConfig } from '@typescript-eslint/utils/ts-eslint'\n\nconst createRule = ESLintUtils.RuleCreator(\n (name) => `https://adk.nht.io/batteries/media/lint#${name}`\n)\n\n// ── prefer-engine-resolver ───────────────────────────────────────────────────\n\nconst ENGINE_SUBPATH = /^@nhtio\\/adk\\/batteries\\/media\\/engines\\//\n\n/**\n * Flags a static value import of a bundled engine subpath. The documented canonical supply\n * form is the dynamic-import resolver, which keeps the engine wrapper module out of every\n * bundle that merely might use it. Type-only imports are fine.\n */\nconst preferEngineResolver = createRule({\n name: 'prefer-engine-resolver',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Supply bundled media engines as dynamic-import resolvers, not static imports — a static import puts the engine wrapper in every bundle, even when the engine is conditional.',\n },\n schema: [],\n messages: {\n preferResolver:\n \"Import bundled engines lazily: supply `() => import('{{source}}').then((m) => m.{{hint}}(…))` in the engines array instead of a static import. Static imports put the engine wrapper module in every bundle. Opt out with an eslint-disable-next-line adk-media/prefer-engine-resolver comment + reason.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n if (typeof node.source.value !== 'string') return\n if (!ENGINE_SUBPATH.test(node.source.value)) return\n if (node.importKind === 'type') return\n const valueSpecifiers = node.specifiers.filter(\n (s) => !(s.type === 'ImportSpecifier' && s.importKind === 'type')\n )\n if (valueSpecifiers.length === 0) return\n const first = valueSpecifiers[0]\n const hint =\n first.type === 'ImportSpecifier' && first.imported.type === 'Identifier'\n ? first.imported.name\n : 'engineFactory'\n context.report({\n node,\n messageId: 'preferResolver',\n data: { source: node.source.value, hint },\n })\n },\n }\n },\n})\n\n// ── no-shadowed-engine ───────────────────────────────────────────────────────\n\n/** A statically-derived capability summary for one engines-array element. */\nexport interface EngineSummary {\n /** Display label for the diagnostic. */\n label: string\n /** Mutate groups: input patterns, ops, encodes. */\n mutates: Array<{ over: string[]; ops: string[]; encodes: string[] }>\n /** Convert groups: input patterns, target tokens. */\n converts: Array<{ from: string[]; to: string[] }>\n /** Edit groups: input patterns, ops. */\n edits: Array<{ over: string[]; ops: string[] }>\n}\n\nconst SHEET_OPS = [\n 'sheet.add_rows',\n 'sheet.add_columns',\n 'sheet.update_cells',\n 'sheet.delete_rows',\n 'sheet.delete_columns',\n 'sheet.rename_sheet',\n 'sheet.add_sheet',\n 'sheet.remove_sheet',\n 'sheet.reorder_sheets',\n 'sheet.transform_table',\n]\n\nconst SHEET_WRITE_TARGETS = [\n 'xlsx',\n 'xlsm',\n 'xlsb',\n 'xls',\n 'ods',\n 'fods',\n 'csv',\n 'txt',\n 'html',\n 'rtf',\n 'sylk',\n 'dif',\n 'dbf',\n 'numbers',\n 'json',\n]\n\nconst SHEET_READ_MIMES = [\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.ms-excel.sheet.macroEnabled.12',\n 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',\n 'application/vnd.ms-excel',\n 'application/vnd.oasis.opendocument.spreadsheet',\n 'application/vnd.oasis.opendocument.spreadsheet-flat-xml',\n 'text/csv',\n 'application/x-iwork-numbers-sffnumbers',\n 'application/x-sylk',\n 'application/x-dif',\n 'application/x-dbf',\n]\n\n/**\n * Known declarations of the bundled engine factories, for shadow analysis. An ESLint rule\n * cannot execute the factories it lints, so this table mirrors their declarations by hand —\n * and is pinned against the live factories by a drift test (`lint_drift.node.spec.ts`).\n * Exported for that test only.\n */\nexport const BUNDLED_SUMMARIES: Record<string, EngineSummary> = {\n dataEngine: {\n label: 'dataEngine',\n mutates: [],\n converts: [\n { from: ['application/x-adk-empty'], to: ['txt', 'md', 'json', 'yaml', 'csv', 'html'] },\n { from: ['application/json'], to: ['yaml', 'txt', 'csv'] },\n { from: ['application/yaml'], to: ['json'] },\n { from: ['text/csv'], to: ['json'] },\n ],\n edits: [],\n },\n exceljsEngine: {\n label: 'exceljsEngine',\n mutates: [],\n converts: [{ from: ['application/x-adk-empty'], to: ['xlsx'] }],\n edits: [\n {\n over: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],\n ops: SHEET_OPS,\n },\n ],\n },\n jimpEngine: {\n label: 'jimpEngine',\n mutates: [\n {\n over: ['image/png', 'image/jpeg', 'image/bmp', 'image/gif', 'image/tiff'],\n ops: ['resize', 'rotate', 'flip', 'strip_metadata'],\n encodes: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'tiff'],\n },\n ],\n converts: [\n { from: ['application/x-adk-empty'], to: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'tiff'] },\n ],\n edits: [],\n },\n sharpEngine: {\n label: 'sharpEngine',\n mutates: [\n {\n over: ['image/*'],\n ops: ['resize', 'rotate', 'flip', 'strip_metadata'],\n encodes: ['png', 'jpg', 'jpeg', 'webp', 'tiff', 'avif', 'gif'],\n },\n ],\n converts: [\n {\n from: ['application/x-adk-empty'],\n to: ['png', 'jpg', 'jpeg', 'webp', 'tiff', 'avif', 'gif'],\n },\n ],\n edits: [],\n },\n sheetjsEngine: {\n label: 'sheetjsEngine',\n mutates: [],\n converts: [\n { from: ['application/x-adk-empty'], to: SHEET_WRITE_TARGETS },\n { from: SHEET_READ_MIMES, to: SHEET_WRITE_TARGETS },\n ],\n edits: [{ over: SHEET_READ_MIMES, ops: SHEET_OPS }],\n },\n}\n\nconst literalStrings = (node: TSESTree.Node | undefined): string[] | undefined => {\n if (!node || node.type !== 'ArrayExpression') return undefined\n const out: string[] = []\n for (const el of node.elements) {\n if (!el || el.type !== 'Literal' || typeof el.value !== 'string') return undefined\n out.push(el.value)\n }\n return out\n}\n\nconst propOf = (obj: TSESTree.ObjectExpression, name: string): TSESTree.Node | undefined =>\n obj.properties.find(\n (p): p is TSESTree.Property =>\n p.type === 'Property' && !p.computed && p.key.type === 'Identifier' && p.key.name === name\n )?.value\n\n/** Find the bundled factory name referenced by an element (direct call or resolver arrow). */\nconst bundledFactoryName = (node: TSESTree.Node): string | undefined => {\n if (\n node.type === 'CallExpression' &&\n node.callee.type === 'Identifier' &&\n node.callee.name in BUNDLED_SUMMARIES\n ) {\n return node.callee.name\n }\n // Resolver forms: () => …; scan the body for m.jimpEngine(…) / jimpEngine(…).\n if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {\n let found: string | undefined\n const visit = (n: TSESTree.Node): void => {\n if (found) return\n if (n.type === 'CallExpression') {\n if (n.callee.type === 'Identifier' && n.callee.name in BUNDLED_SUMMARIES) {\n found = n.callee.name\n } else if (\n n.callee.type === 'MemberExpression' &&\n n.callee.property.type === 'Identifier' &&\n n.callee.property.name in BUNDLED_SUMMARIES\n ) {\n found = n.callee.property.name\n }\n }\n for (const key of Object.keys(n)) {\n if (key === 'parent') continue\n const value = (n as unknown as Record<string, unknown>)[key]\n const children = Array.isArray(value) ? value : [value]\n for (const child of children) {\n if (!child || typeof child !== 'object') continue\n const childNode = child as TSESTree.Node\n if (typeof childNode.type !== 'string') continue\n visit(childNode)\n }\n }\n }\n visit(node.body)\n return found\n }\n return undefined\n}\n\n/** Derive a capability summary from an element, or undefined when not statically known. */\nconst summarize = (node: TSESTree.Node): EngineSummary | undefined => {\n const factory = bundledFactoryName(node)\n if (factory) return BUNDLED_SUMMARIES[factory]\n if (node.type !== 'ObjectExpression') return undefined\n const idNode = propOf(node, 'id')\n const label =\n idNode?.type === 'Literal' && typeof idNode.value === 'string' ? idNode.value : 'engine'\n const summary: EngineSummary = { label, mutates: [], converts: [], edits: [] }\n const mutates = propOf(node, 'mutates')\n if (mutates) {\n if (mutates.type !== 'ArrayExpression') return undefined\n for (const el of mutates.elements) {\n if (!el || el.type !== 'ObjectExpression') return undefined\n const over = literalStrings(propOf(el, 'over'))\n const ops = literalStrings(propOf(el, 'ops'))\n const encodes = literalStrings(propOf(el, 'encodes'))\n if (!over || !ops || !encodes) return undefined\n summary.mutates.push({ over, ops, encodes })\n }\n }\n const converts = propOf(node, 'converts')\n if (converts) {\n if (converts.type !== 'ArrayExpression') return undefined\n for (const el of converts.elements) {\n if (!el || el.type !== 'ObjectExpression') return undefined\n const from = literalStrings(propOf(el, 'from'))\n const to = literalStrings(propOf(el, 'to'))\n if (!from || !to) return undefined\n summary.converts.push({ from, to })\n }\n }\n const edits = propOf(node, 'edits')\n if (edits) {\n if (edits.type !== 'ArrayExpression') return undefined\n for (const el of edits.elements) {\n if (!el || el.type !== 'ObjectExpression') return undefined\n const over = literalStrings(propOf(el, 'over'))\n const ops = literalStrings(propOf(el, 'ops'))\n if (!over || !ops) return undefined\n summary.edits.push({ over, ops })\n }\n }\n if (summary.mutates.length + summary.converts.length + summary.edits.length === 0) {\n return undefined\n }\n return summary\n}\n\nconst patternCovers = (broad: string, narrow: string): boolean => {\n if (broad === narrow) return true\n if (broad.endsWith('/*')) return narrow.startsWith(broad.slice(0, -1))\n return false\n}\n\nconst patternsCover = (broad: string[], narrow: string[]): boolean =>\n narrow.every((n) => broad.some((b) => patternCovers(b, n)))\n\nconst subset = (sup: string[], sub: string[]): boolean => sub.every((s) => sup.includes(s))\n\n/** `true` when EVERY capability of `later` is subsumed by some capability of `earlier`. */\nconst shadows = (earlier: EngineSummary, later: EngineSummary): boolean => {\n if (later.mutates.length + later.converts.length + later.edits.length === 0) return false\n const mutatesCovered = later.mutates.every((lm) =>\n earlier.mutates.some(\n (em) =>\n patternsCover(em.over, lm.over) && subset(em.ops, lm.ops) && subset(em.encodes, lm.encodes)\n )\n )\n const convertsCovered = later.converts.every((lc) =>\n earlier.converts.some((ec) => patternsCover(ec.from, lc.from) && subset(ec.to, lc.to))\n )\n const editsCovered = later.edits.every((le) =>\n earlier.edits.some((ee) => patternsCover(ee.over, le.over) && subset(ee.ops, le.ops))\n )\n return mutatesCovered && convertsCovered && editsCovered\n}\n\n/**\n * Flags an engines-array element whose statically-known capabilities are entirely covered by\n * an EARLIER element — dispatch is first-capable-wins, so the later engine is dead code.\n */\nconst noShadowedEngine = createRule({\n name: 'no-shadowed-engine',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'An engine whose declared capabilities are a subset of an earlier engine in the array can never be selected — first-capable-wins dispatch makes it dead code. Reorder (narrow before broad) or remove it.',\n },\n schema: [],\n messages: {\n shadowed:\n 'Engine \"{{later}}\" can never be selected: \"{{earlier}}\" appears earlier in the array and declares a superset of its capabilities (dispatch is first-capable-wins). Put the narrower engine first, or remove it. Opt out with an eslint-disable-next-line adk-media/no-shadowed-engine comment + reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n CallExpression(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'Identifier' || node.callee.name !== 'createMediaPipeline') {\n return\n }\n const config = node.arguments[0]\n if (!config || config.type !== 'ObjectExpression') return\n const engines = propOf(config, 'engines')\n if (!engines || engines.type !== 'ArrayExpression') return\n const summaries = engines.elements.map((el) => (el ? summarize(el) : undefined))\n for (let i = 1; i < summaries.length; i++) {\n const later = summaries[i]\n if (!later) continue\n for (let j = 0; j < i; j++) {\n const earlier = summaries[j]\n if (!earlier) continue\n if (shadows(earlier, later)) {\n context.report({\n node: engines.elements[i]!,\n messageId: 'shadowed',\n data: { earlier: earlier.label, later: later.label },\n })\n break\n }\n }\n }\n },\n }\n },\n})\n\n// ── augment-contracts-module ─────────────────────────────────────────────────\n\nconst CONTRACTS_SUBPATH = '@nhtio/adk/batteries/media/contracts'\n\n/**\n * Flags a `ConvertOptions` declaration-merging block whose module specifier is not the\n * contracts subpath. Augmenting the barrel (or any other module) silently does nothing —\n * the keys never merge, and the typo costs hours.\n */\nconst augmentContractsModule = createRule({\n name: 'augment-contracts-module',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'ConvertOptions augmentation must target the contracts subpath — declaration merging against any other module specifier silently fails to merge.',\n },\n schema: [],\n messages: {\n wrongModule:\n 'This ConvertOptions augmentation targets \"{{actual}}\", so it will silently never merge. Declaration merging must target the module that declares the interface: declare module \\'{{expected}}\\'.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n TSModuleDeclaration(node: TSESTree.TSModuleDeclaration) {\n if (node.global) return\n if (node.id.type !== 'Literal' || typeof node.id.value !== 'string') return\n const specifier = node.id.value\n if (specifier === CONTRACTS_SUBPATH || specifier.endsWith('/media/contracts')) return\n // Only flag specifiers that are plausibly aimed at this battery.\n if (!specifier.includes('media') && !specifier.includes('@nhtio/adk')) return\n const body = node.body\n if (!body || body.type !== 'TSModuleBlock') return\n const declaresConvertOptions = body.body.some(\n (stmt) => stmt.type === 'TSInterfaceDeclaration' && stmt.id.name === 'ConvertOptions'\n )\n if (!declaresConvertOptions) return\n context.report({\n node: node.id,\n messageId: 'wrongModule',\n data: { actual: specifier, expected: CONTRACTS_SUBPATH },\n })\n },\n }\n },\n})\n\n// ── require-engine-peers ─────────────────────────────────────────────────────\n\n/**\n * The optional peer dependencies each bundled engine subpath lazily imports at runtime.\n * Keyed by the engine's subpath suffix (after `engines/`). An engine with no external peer\n * (soffice, fs_workspace) is absent — there's nothing to install. Kept in lockstep with the\n * engines' actual `import(...)` calls by `engine_peers_drift.node.spec.ts`.\n */\nexport const ENGINE_PEERS: Record<string, readonly string[]> = {\n jimp: ['jimp'],\n sharp: ['sharp'],\n sheetjs: ['xlsx'],\n exceljs: ['exceljs'],\n data: ['js-yaml', 'papaparse'],\n tesseract_js: ['tesseract.js'],\n audio_decode: ['audio-decode'],\n transformers_asr: ['@huggingface/transformers'],\n execa_executor: ['execa'],\n}\n\n/** Map a bundled engine subpath to its peer list, or undefined when it isn't a known engine. */\nconst peersForSubpath = (source: string): readonly string[] | undefined => {\n const m = /^@nhtio\\/adk\\/batteries\\/media\\/engines\\/([a-z_]+)$/.exec(source)\n if (!m) return undefined\n return ENGINE_PEERS[m[1]]\n}\n\n/**\n * Flags an import of a bundled engine subpath whose optional peer dependency cannot be\n * resolved from the linting file — the \"I composed `sheetjsEngine()` but never installed\n * `xlsx`, and now my pipeline throws `E_INVALID_MEDIA_PIPELINE_CONFIG` at the first sheet\n * op\" footgun, caught at lint time instead of runtime. Resolution uses Node's resolver from\n * the file's own directory, so it honors the consumer's real `node_modules` layout.\n *\n * Reports once per missing peer per engine import (whether the import is a static value\n * import or a dynamic-import resolver — both reference the engine and both need the peer).\n */\n/**\n * The peer-resolution probe: returns `true` when `peer` resolves from `fromFile`. Defaults to\n * Node's resolver anchored at the linted file (so it sees the consumer's `node_modules`).\n * Exposed as a settable seam so the rule's spec can simulate installed/missing peers without\n * depending on the test runner's own `node_modules` layout — production never sets it.\n */\nexport type PeerResolver = (peer: string, fromFile: string) => boolean\n\nconst defaultPeerResolver: PeerResolver = (peer, fromFile) => {\n try {\n createRequire(fromFile).resolve(peer)\n return true\n } catch {\n return false\n }\n}\n\nlet activePeerResolver: PeerResolver = defaultPeerResolver\n\n/** Override the peer resolver (spec-only seam). Pass nothing to restore the Node default. */\nexport const setPeerResolverForTesting = (resolver?: PeerResolver): void => {\n activePeerResolver = resolver ?? defaultPeerResolver\n}\n\nconst requireEnginePeers = createRule<[{ ignore?: readonly string[] }], 'missingPeer'>({\n name: 'require-engine-peers',\n meta: {\n type: 'problem',\n docs: {\n description:\n \"A bundled media engine's optional peer dependency must be installed where it's composed — otherwise the pipeline throws at first use, not at construction.\",\n },\n schema: [\n {\n type: 'object',\n properties: {\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Peer package names to skip (e.g. one resolved via a custom loader the linter cannot see).',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n missingPeer:\n 'The \"{{engine}}\" media engine requires the optional peer \"{{peer}}\", which cannot be resolved from this file. Install it ({{install}}) or the pipeline will throw E_INVALID_MEDIA_PIPELINE_CONFIG the first time this engine runs. Opt out with an eslint-disable-next-line adk-media/require-engine-peers comment + reason.',\n },\n },\n defaultOptions: [{ ignore: [] }],\n create(context, [options]) {\n const ignore = new Set(options?.ignore ?? [])\n\n /** SheetJS ships only from its CDN tarball, so the install hint must not say the registry. */\n const installHint = (peer: string): string =>\n peer === 'xlsx'\n ? 'pnpm add xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz'\n : `pnpm add ${peer}`\n\n const checkSource = (source: string, reportNode: TSESTree.Node): void => {\n const peers = peersForSubpath(source)\n if (!peers) return\n const engine = source.slice(source.lastIndexOf('/') + 1)\n for (const peer of peers) {\n if (ignore.has(peer)) continue\n if (!activePeerResolver(peer, context.filename)) {\n context.report({\n node: reportNode,\n messageId: 'missingPeer',\n data: { engine, peer, install: installHint(peer) },\n })\n }\n }\n }\n\n return {\n // Static value/type import of an engine subpath.\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n if (typeof node.source.value === 'string') checkSource(node.source.value, node.source)\n },\n // The canonical resolver form: import('@nhtio/adk/batteries/media/engines/…').\n ImportExpression(node: TSESTree.ImportExpression) {\n if (node.source.type === 'Literal' && typeof node.source.value === 'string') {\n checkSource(node.source.value, node.source)\n }\n },\n }\n },\n})\n\n// ── plugin assembly ──────────────────────────────────────────────────────────\n\n/**\n * Map of rule id (without the `adk-media/` plugin prefix) to its rule object. Registered on\n * the plugin as `rules`, so configs reference them as `adk-media/{id}`.\n */\nexport const rules = {\n 'prefer-engine-resolver': preferEngineResolver,\n 'no-shadowed-engine': noShadowedEngine,\n 'augment-contracts-module': augmentContractsModule,\n 'require-engine-peers': requireEnginePeers,\n} satisfies FlatConfig.Plugin['rules']\n\n/**\n * The media battery's ESLint plugin object. Register under the `adk-media` namespace:\n * `plugins: { 'adk-media': plugin }`.\n */\nexport const plugin: FlatConfig.Plugin = {\n meta: { name: '@nhtio/adk/batteries/media/lint', version: __VERSION__ },\n rules,\n}\n\nconst recommendedRules: NonNullable<FlatConfig.Config['rules']> = Object.fromEntries(\n Object.keys(rules).map((id) => [`adk-media/${id}`, 'error'])\n)\n\n/**\n * Named config presets. `recommended` enables every rule at `error` and registers the plugin\n * under the `adk-media` namespace — spread it into a flat config to adopt the full set.\n */\nexport const configs = {\n recommended: {\n name: '@nhtio/adk/batteries/media/lint/recommended',\n plugins: { 'adk-media': plugin },\n rules: recommendedRules,\n } satisfies FlatConfig.Config,\n}\n\n/**\n * Default export bundles the plugin and its config presets, mirroring the shape ESLint flat\n * configs expect from a plugin module.\n */\nexport default { plugin, rules, configs }\n"],"mappings":"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"}
@@ -16,14 +16,14 @@
16
16
  */
17
17
  import type { NextFn } from '@nhtio/middleware';
18
18
  import type { CapabilityProbe } from "./validate";
19
- import type { MediaEngine, ConvertRequest, ConvertResult, MutateRequest, EngineBytesResult } from "./contracts";
19
+ import type { MediaEngine, ConvertRequest, ConvertResult, MutateRequest, EditRequest, EditResult, EngineBytesResult } from "./contracts";
20
20
  /**
21
21
  * The request summary a selection stage sees: enough to implement content- and
22
22
  * format-dependent rules without exposing dispatch internals.
23
23
  */
24
24
  export interface EngineSelectionContext {
25
25
  /** Which capability is being dispatched. */
26
- kind: 'convert' | 'mutate';
26
+ kind: 'convert' | 'mutate' | 'edit';
27
27
  /** The dispatch request (for convert under multi-hop, the CURRENT hop's input/target). */
28
28
  request: {
29
29
  /** The input MIME type. */
@@ -81,6 +81,15 @@ export interface EngineRegistry extends CapabilityProbe {
81
81
  * @returns The transformed bytes.
82
82
  */
83
83
  mutate(request: MutateRequest): Promise<EngineBytesResult>;
84
+ /**
85
+ * Apply one structural document operation. Candidates are engines whose `over` matches the
86
+ * input and whose `ops` include the requested op; the selection rule arbitrates between
87
+ * engines of differing fidelity (supply order wins by default).
88
+ *
89
+ * @param request - The op, its args, and the input bytes.
90
+ * @returns The restructured bytes plus optional change counts.
91
+ */
92
+ edit(request: EditRequest): Promise<EditResult>;
84
93
  }
85
94
  /**
86
95
  * Build the registry over a resolved, validated engine array.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `append` and `data.*` step implementations: deterministic text/data mutations on in-memory
3
+ * bytes — append a line, set/merge/delete at a JSON or YAML path.
4
+ *
5
+ * @remarks
6
+ * Internal sibling of the `@nhtio/adk/batteries/media` entry. Pure and cross-env: text ops
7
+ * are string concatenation; structured ops parse (JSON natively, YAML via the `js-yaml`
8
+ * dependency), mutate the value tree, and re-serialize **preserving the source format** —
9
+ * `data.set` on a YAML file yields YAML, on a JSON file yields JSON. These are the verbs
10
+ * that make the lossy text family first-class media: `empty:json | data set path=… value=…`
11
+ * is a create-then-populate chain with no engine requirement at all.
12
+ */
13
+ import type { StepImpl } from "../runtime";
14
+ /** `append` — append text to text-family media. */
15
+ export declare const appendStep: StepImpl;
16
+ /** `data.set` — set a value at a JSON/YAML path, creating intermediate containers. */
17
+ export declare const dataSetStep: StepImpl;
18
+ /** `data.merge` — merge a JSON fragment into the document (deep by default). */
19
+ export declare const dataMergeStep: StepImpl;
20
+ /** `data.delete` — remove a key/index at a JSON/YAML path. */
21
+ export declare const dataDeleteStep: StepImpl;
22
+ /** The data step registry fragment, keyed by canonical verb id. */
23
+ export declare const DATA_STEPS: ReadonlyArray<[
24
+ string,
25
+ StepImpl
26
+ ]>;
@@ -1,23 +1,33 @@
1
1
  /**
2
- * Doc-domain step extensions: format-preserving text mutation for DOCX/PPTX, asset
3
- * extraction from OOXML/ODF archives and PDFs, and format conversion via the convert engine.
2
+ * Doc-domain step extensions: format-preserving text mutation for DOCX/PPTX/ODF, PDF visual
3
+ * redaction, asset extraction from OOXML/ODF archives and PDFs, and format conversion via the
4
+ * convert engine.
4
5
  *
5
6
  * @remarks
6
7
  * Internal sibling of the `@nhtio/adk/batteries/media` entry. Ported from the source server's
7
8
  * doc adapters; the format-dispatch contract follows the server's green e2e suite: DOCX and
8
9
  * PPTX redact/sanitize/normalize/update_text mutate text in place inside the container
9
- * (preserving the source format); text-like inputs mutate as text; PDF redact falls back to
10
- * text extraction (the server's PDF "visual annotation" path is content-stream-preserving and
11
- * out of scope here the failure message says exactly that).
10
+ * (preserving the source format); ODT/ODS/ODP redact/update_text mutate `content.xml` the
11
+ * same way; text-like inputs mutate as text. PDF redact is VISUAL (pdf-lib draw-over +
12
+ * metadata strip content streams keep the text); the result carries that warning verbatim
13
+ * because it is a trust boundary, not a footnote.
12
14
  */
13
15
  import type { StepImpl } from "../runtime";
14
- /** `redact` — format dispatch: text media → text path; DOCX/PPTX → in-place container edit. */
16
+ /**
17
+ * The honest caveat every PDF redaction result must carry. Visual redaction draws over
18
+ * matched pages and strips metadata; the content streams still contain the original text.
19
+ */
20
+ export declare const PDF_REDACTION_WARNING = "PDF redaction applies visual annotations. Content streams are not modified. For full content-level redaction, convert to text first.";
21
+ /**
22
+ * `redact` — format dispatch: text media → text path; DOCX/PPTX/ODF → in-place container
23
+ * edit; PDF → visual redaction (draw-over + metadata strip — see {@link PDF_REDACTION_WARNING}).
24
+ */
15
25
  export declare const docRedactStep: StepImpl;
16
26
  /** `sanitize` — strip blocked control characters from text nodes / text media. */
17
27
  export declare const docSanitizeStep: StepImpl;
18
28
  /** `normalize` — CRLF→LF + trailing-whitespace removal in text nodes / text media. */
19
29
  export declare const docNormalizeStep: StepImpl;
20
- /** `update_text` — anchor replacement; DOCX/PPTX in-place, TARGET_NOT_FOUND when absent. */
30
+ /** `update_text` — anchor replacement; DOCX/PPTX/ODF in-place, TARGET_NOT_FOUND when absent. */
21
31
  export declare const docUpdateTextStep: StepImpl;
22
32
  /**
23
33
  * `extract.assets` — enumerate embedded assets. OOXML/ODF archives are walked natively