attention-manifest-protocol 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (827) hide show
  1. package/CHANGELOG.md +341 -293
  2. package/CHANGELOG.zh-CN.md +41 -8
  3. package/README.md +250 -171
  4. package/README.zh-CN.md +251 -171
  5. package/lib/assignment/assign.d.ts +102 -0
  6. package/lib/assignment/assign.js +556 -0
  7. package/lib/assignment/binding.d.ts +103 -0
  8. package/lib/assignment/binding.js +575 -0
  9. package/lib/assignment/errors.d.ts +14 -0
  10. package/lib/assignment/errors.js +16 -0
  11. package/lib/assignment/guards.d.ts +18 -0
  12. package/lib/assignment/guards.js +28 -0
  13. package/lib/assignment/index.d.ts +10 -0
  14. package/lib/assignment/index.js +10 -0
  15. package/lib/assignment/types.d.ts +169 -0
  16. package/lib/assignment/types.js +1 -0
  17. package/lib/attention/budget.d.ts +1 -1
  18. package/lib/attention/budget.js +31 -9
  19. package/lib/bayes/engine.js +2 -1
  20. package/lib/bayes/reliability.js +25 -10
  21. package/lib/boundary/frame.d.ts +10 -4
  22. package/lib/boundary/frame.js +346 -109
  23. package/lib/boundary/kinds.d.ts +11 -0
  24. package/lib/boundary/kinds.js +33 -0
  25. package/lib/boundary/refTable.d.ts +19 -9
  26. package/lib/boundary/refTable.js +128 -30
  27. package/lib/boundary/toolCall.d.ts +22 -10
  28. package/lib/boundary/toolCall.js +798 -97
  29. package/lib/concept/conceptBudget.js +10 -1
  30. package/lib/concurrency/idempotency.d.ts +4 -3
  31. package/lib/concurrency/idempotency.js +57 -13
  32. package/lib/concurrency/retry.js +18 -3
  33. package/lib/context/agentContext.d.ts +13 -0
  34. package/lib/context/agentContext.js +14 -1
  35. package/lib/context/ast.d.ts +4 -1
  36. package/lib/context/ast.js +13 -3
  37. package/lib/context/contentStore.js +22 -9
  38. package/lib/context/contextBuilder.d.ts +457 -0
  39. package/lib/context/contextBuilder.js +1395 -0
  40. package/lib/context/contextPackage.d.ts +214 -0
  41. package/lib/context/contextPackage.js +191 -0
  42. package/lib/context/index.d.ts +14 -3
  43. package/lib/context/index.js +9 -1
  44. package/lib/context/relevance.d.ts +171 -0
  45. package/lib/context/relevance.js +250 -0
  46. package/lib/context/workspaceDiff.d.ts +105 -0
  47. package/lib/context/workspaceDiff.js +238 -0
  48. package/lib/context/workspaceService.d.ts +236 -1
  49. package/lib/context/workspaceService.js +1501 -73
  50. package/lib/cooperation/collaboration.d.ts +9 -0
  51. package/lib/cooperation/session.d.ts +4 -4
  52. package/lib/cooperation/session.js +3 -3
  53. package/lib/database/adapter/filesystemStore.d.ts +145 -0
  54. package/lib/database/adapter/filesystemStore.js +321 -0
  55. package/lib/database/adapter/index.d.ts +2 -0
  56. package/lib/database/adapter/index.js +2 -0
  57. package/lib/database/adapter/workspaceSessionStore.d.ts +118 -0
  58. package/lib/database/adapter/workspaceSessionStore.js +120 -0
  59. package/lib/database/entity/fileGraph.d.ts +53 -0
  60. package/lib/database/entity/fileGraph.js +15 -0
  61. package/lib/database/entity/fileTree.d.ts +76 -0
  62. package/lib/database/entity/fileTree.js +39 -0
  63. package/lib/database/entity/hook.d.ts +29 -0
  64. package/lib/database/entity/hook.js +18 -0
  65. package/lib/database/entity/index.d.ts +4 -0
  66. package/lib/database/entity/index.js +4 -0
  67. package/lib/database/entity/workspaceSession.d.ts +53 -0
  68. package/lib/database/entity/workspaceSession.js +28 -0
  69. package/lib/database/repository/fileAnnotationRepository.d.ts +75 -0
  70. package/lib/database/repository/fileAnnotationRepository.js +109 -0
  71. package/lib/database/repository/fileGraphRepository.d.ts +188 -0
  72. package/lib/database/repository/fileGraphRepository.js +648 -0
  73. package/lib/database/repository/fileNodeRepository.d.ts +116 -0
  74. package/lib/database/repository/fileNodeRepository.js +263 -0
  75. package/lib/database/repository/fileOutlineRepository.d.ts +40 -0
  76. package/lib/database/repository/fileOutlineRepository.js +104 -0
  77. package/lib/database/repository/fileWriteRepository.d.ts +41 -0
  78. package/lib/database/repository/fileWriteRepository.js +93 -0
  79. package/lib/database/repository/hookRepository.d.ts +67 -0
  80. package/lib/database/repository/hookRepository.js +150 -0
  81. package/lib/database/repository/index.d.ts +7 -0
  82. package/lib/database/repository/index.js +7 -0
  83. package/lib/database/repository/workspaceSessionRepository.d.ts +134 -0
  84. package/lib/database/repository/workspaceSessionRepository.js +327 -0
  85. package/lib/database/service/database.d.ts +14 -0
  86. package/lib/database/service/database.js +21 -0
  87. package/lib/database/sql/schema.js +187 -0
  88. package/lib/database/store/database.js +6 -1
  89. package/lib/delegate/team.d.ts +5 -0
  90. package/lib/delegate/team.js +83 -20
  91. package/lib/discover/classify.d.ts +20 -0
  92. package/lib/discover/classify.js +85 -0
  93. package/lib/discover/host.d.ts +20 -0
  94. package/lib/discover/host.js +89 -0
  95. package/lib/discover/index.d.ts +11 -0
  96. package/lib/discover/index.js +11 -0
  97. package/lib/discover/kinds.d.ts +14 -0
  98. package/lib/discover/kinds.js +23 -0
  99. package/lib/discover/tree.d.ts +7 -0
  100. package/lib/discover/tree.js +432 -0
  101. package/lib/discover/types.d.ts +115 -0
  102. package/lib/discover/types.js +1 -0
  103. package/lib/error/boundaryRejection.d.ts +1 -1
  104. package/lib/evaluation/agreement.js +2 -2
  105. package/lib/execution/handles.js +6 -0
  106. package/lib/execution/pin.js +2 -2
  107. package/lib/file/cfb.d.ts +56 -0
  108. package/lib/file/cfb.js +396 -0
  109. package/lib/file/dependency.d.ts +151 -0
  110. package/lib/file/dependency.js +568 -0
  111. package/lib/file/directory.d.ts +34 -0
  112. package/lib/file/directory.js +141 -0
  113. package/lib/file/errors.d.ts +20 -0
  114. package/lib/file/errors.js +18 -0
  115. package/lib/file/exportGraph.d.ts +31 -0
  116. package/lib/file/exportGraph.js +554 -0
  117. package/lib/file/file.d.ts +40 -0
  118. package/lib/file/file.js +339 -0
  119. package/lib/file/importGraph.d.ts +121 -0
  120. package/lib/file/importGraph.js +1022 -0
  121. package/lib/file/index.d.ts +45 -0
  122. package/lib/file/index.js +30 -0
  123. package/lib/file/metadata.d.ts +66 -0
  124. package/lib/file/metadata.js +470 -0
  125. package/lib/file/path.d.ts +60 -0
  126. package/lib/file/path.js +259 -0
  127. package/lib/file/read.d.ts +168 -0
  128. package/lib/file/read.js +541 -0
  129. package/lib/file/runtime.d.ts +95 -0
  130. package/lib/file/runtime.js +150 -0
  131. package/lib/file/service.d.ts +227 -0
  132. package/lib/file/service.js +663 -0
  133. package/lib/file/syntaxTree.d.ts +7 -0
  134. package/lib/file/syntaxTree.js +38 -0
  135. package/lib/file/time.d.ts +38 -0
  136. package/lib/file/time.js +44 -0
  137. package/lib/file/tree.d.ts +53 -0
  138. package/lib/file/tree.js +417 -0
  139. package/lib/file/types.d.ts +371 -0
  140. package/lib/file/types.js +1 -0
  141. package/lib/file/write.d.ts +159 -0
  142. package/lib/file/write.js +1028 -0
  143. package/lib/file/zip.d.ts +35 -0
  144. package/lib/file/zip.js +349 -0
  145. package/lib/fingerprint/algorithm.d.ts +97 -0
  146. package/lib/fingerprint/algorithm.js +271 -0
  147. package/lib/fingerprint/bytes.d.ts +18 -0
  148. package/lib/fingerprint/bytes.js +31 -0
  149. package/lib/fingerprint/error.d.ts +73 -0
  150. package/lib/fingerprint/error.js +86 -0
  151. package/lib/fingerprint/file.d.ts +71 -0
  152. package/lib/fingerprint/file.js +254 -0
  153. package/lib/fingerprint/index.d.ts +37 -0
  154. package/lib/fingerprint/index.js +12 -0
  155. package/lib/fingerprint/json.d.ts +41 -0
  156. package/lib/fingerprint/json.js +94 -0
  157. package/lib/fingerprint/number.d.ts +32 -0
  158. package/lib/fingerprint/number.js +95 -0
  159. package/lib/fingerprint/policy.d.ts +54 -0
  160. package/lib/fingerprint/policy.js +50 -0
  161. package/lib/fingerprint/preimage.d.ts +84 -0
  162. package/lib/fingerprint/preimage.js +133 -0
  163. package/lib/fingerprint/raw.d.ts +26 -0
  164. package/lib/fingerprint/raw.js +45 -0
  165. package/lib/fingerprint/ref.d.ts +39 -0
  166. package/lib/fingerprint/ref.js +178 -0
  167. package/lib/fingerprint/text.d.ts +42 -0
  168. package/lib/fingerprint/text.js +155 -0
  169. package/lib/fingerprint/tree.d.ts +56 -0
  170. package/lib/fingerprint/tree.js +288 -0
  171. package/lib/fingerprint/verify.d.ts +20 -0
  172. package/lib/fingerprint/verify.js +99 -0
  173. package/lib/global/filesystem.d.ts +43 -0
  174. package/lib/global/filesystem.js +75 -0
  175. package/lib/global/index.d.ts +8 -0
  176. package/lib/global/index.js +8 -0
  177. package/lib/global/service.d.ts +51 -0
  178. package/lib/global/service.js +98 -0
  179. package/lib/governance/transport.js +18 -6
  180. package/lib/hook/ast.d.ts +29 -0
  181. package/lib/hook/ast.js +22 -0
  182. package/lib/hook/code.d.ts +55 -0
  183. package/lib/hook/code.js +47 -0
  184. package/lib/hook/dependency.d.ts +45 -0
  185. package/lib/hook/dependency.js +28 -0
  186. package/lib/hook/diff.d.ts +114 -0
  187. package/lib/hook/diff.js +327 -0
  188. package/lib/hook/exportation.d.ts +31 -0
  189. package/lib/hook/exportation.js +24 -0
  190. package/lib/hook/file.d.ts +44 -0
  191. package/lib/hook/file.js +154 -0
  192. package/lib/hook/importation.d.ts +33 -0
  193. package/lib/hook/importation.js +22 -0
  194. package/lib/hook/index.d.ts +30 -0
  195. package/lib/hook/index.js +23 -0
  196. package/lib/hook/outline.d.ts +35 -0
  197. package/lib/hook/outline.js +27 -0
  198. package/lib/hook/project.d.ts +65 -0
  199. package/lib/hook/project.js +217 -0
  200. package/lib/hook/types.d.ts +114 -0
  201. package/lib/hook/types.js +407 -0
  202. package/lib/index.d.ts +9 -0
  203. package/lib/index.js +9 -0
  204. package/lib/lock/lease.js +28 -3
  205. package/lib/manifest/cardSign.d.ts +1 -1
  206. package/lib/manifest/cardSign.js +37 -7
  207. package/lib/manifest/federation.js +13 -6
  208. package/lib/manifest/trust.d.ts +2 -0
  209. package/lib/manifest/trust.js +27 -6
  210. package/lib/module/index.d.ts +4 -0
  211. package/lib/module/index.js +4 -0
  212. package/lib/module/mcp/client.js +21 -2
  213. package/lib/module/mcp/filesystemServer.d.ts +24 -0
  214. package/lib/module/mcp/filesystemServer.js +102 -0
  215. package/lib/module/mcp/http.js +32 -19
  216. package/lib/module/mcp/workspaceServer.js +20 -3
  217. package/lib/module/tool/filesystemExecutor.d.ts +18 -0
  218. package/lib/module/tool/filesystemExecutor.js +49 -0
  219. package/lib/module/tool/globalExecutor.d.ts +17 -0
  220. package/lib/module/tool/globalExecutor.js +39 -0
  221. package/lib/module/tool/globalFilesystemExecutor.d.ts +18 -0
  222. package/lib/module/tool/globalFilesystemExecutor.js +49 -0
  223. package/lib/module/tool/workspaceExecutor.d.ts +9 -0
  224. package/lib/module/tool/workspaceExecutor.js +16 -3
  225. package/lib/naming/aliases.d.ts +48 -0
  226. package/lib/naming/aliases.js +79 -0
  227. package/lib/naming/catalog.d.ts +117 -0
  228. package/lib/naming/catalog.js +108 -0
  229. package/lib/naming/grammar.d.ts +67 -0
  230. package/lib/naming/grammar.js +212 -0
  231. package/lib/naming/ids.d.ts +157 -0
  232. package/lib/naming/ids.js +119 -0
  233. package/lib/naming/index.d.ts +13 -0
  234. package/lib/naming/index.js +13 -0
  235. package/lib/outline/index.d.ts +9 -0
  236. package/lib/outline/index.js +8 -0
  237. package/lib/outline/office.d.ts +160 -0
  238. package/lib/outline/office.js +2375 -0
  239. package/lib/outline/officeBridge.d.ts +32 -0
  240. package/lib/outline/officeBridge.js +27 -0
  241. package/lib/outline/outline.d.ts +159 -0
  242. package/lib/outline/outline.js +837 -0
  243. package/lib/outline/outlineIndex.d.ts +97 -0
  244. package/lib/outline/outlineIndex.js +325 -0
  245. package/lib/outline/outlineKeywords.d.ts +59 -0
  246. package/lib/outline/outlineKeywords.js +143 -0
  247. package/lib/outline/outlineStrategies.d.ts +414 -0
  248. package/lib/outline/outlineStrategies.js +8324 -0
  249. package/lib/outline/reactOutline.d.ts +37 -0
  250. package/lib/outline/reactOutline.js +1070 -0
  251. package/lib/outline/shellOutline.d.ts +65 -0
  252. package/lib/outline/shellOutline.js +873 -0
  253. package/lib/outline/sqlOutline.d.ts +170 -0
  254. package/lib/outline/sqlOutline.js +1536 -0
  255. package/lib/outline/strategy.d.ts +46 -0
  256. package/lib/outline/strategy.js +1 -0
  257. package/lib/outline/types.d.ts +95 -0
  258. package/lib/outline/types.js +1 -0
  259. package/lib/outline/visualBasicOutline.d.ts +27 -0
  260. package/lib/outline/visualBasicOutline.js +750 -0
  261. package/lib/protocol/filesystem.d.ts +55 -0
  262. package/lib/protocol/filesystem.js +105 -0
  263. package/lib/protocol/global.d.ts +42 -0
  264. package/lib/protocol/global.js +63 -0
  265. package/lib/protocol/globalFilesystem.d.ts +40 -0
  266. package/lib/protocol/globalFilesystem.js +59 -0
  267. package/lib/protocol/index.d.ts +4 -0
  268. package/lib/protocol/index.js +4 -0
  269. package/lib/protocol/protocol.d.ts +4 -0
  270. package/lib/protocol/wire.js +5 -2
  271. package/lib/protocol/workspace.d.ts +305 -0
  272. package/lib/protocol/workspace.js +258 -0
  273. package/lib/receipt/receipt.d.ts +8 -0
  274. package/lib/registry/content.js +9 -3
  275. package/lib/runtime/kernelRuntime.js +9 -1
  276. package/lib/scenario/scenario.js +5 -4
  277. package/lib/semantic/canonical.d.ts +61 -0
  278. package/lib/semantic/canonical.js +135 -8
  279. package/lib/semantic/jsonValue.d.ts +31 -0
  280. package/lib/semantic/jsonValue.js +122 -0
  281. package/lib/semantic/sha256.js +3 -0
  282. package/lib/semantic/time.d.ts +14 -0
  283. package/lib/semantic/time.js +56 -0
  284. package/lib/session/context.d.ts +76 -0
  285. package/lib/session/context.js +419 -0
  286. package/lib/session/errors.d.ts +24 -0
  287. package/lib/session/errors.js +22 -0
  288. package/lib/session/identity.d.ts +24 -0
  289. package/lib/session/identity.js +56 -0
  290. package/lib/session/index.d.ts +14 -0
  291. package/lib/session/index.js +12 -0
  292. package/lib/session/registry.d.ts +130 -0
  293. package/lib/session/registry.js +908 -0
  294. package/lib/session/types.d.ts +227 -0
  295. package/lib/session/types.js +14 -0
  296. package/lib/structure/index.d.ts +1 -0
  297. package/lib/structure/index.js +1 -0
  298. package/lib/structure/trie.d.ts +88 -0
  299. package/lib/structure/trie.js +183 -0
  300. package/lib/syntax/byteIndex.d.ts +44 -0
  301. package/lib/syntax/byteIndex.js +148 -0
  302. package/lib/syntax/charset.d.ts +88 -0
  303. package/lib/syntax/charset.js +276 -0
  304. package/lib/syntax/index.d.ts +6 -0
  305. package/lib/syntax/index.js +5 -0
  306. package/lib/syntax/syntaxRegistry.d.ts +150 -0
  307. package/lib/syntax/syntaxRegistry.js +314 -0
  308. package/lib/syntax/syntaxTree.d.ts +106 -0
  309. package/lib/syntax/syntaxTree.js +668 -0
  310. package/lib/syntax/types.d.ts +15 -0
  311. package/lib/syntax/types.js +1 -0
  312. package/lib/types/common.d.ts +1 -1
  313. package/package.json +72 -193
  314. package/.editorconfig +0 -12
  315. package/.prettierignore +0 -17
  316. package/MCP-Token/347/250/216/344/270/216/344/270/213/344/270/200/344/273/243/345/267/245/345/205/267/345/215/217/350/256/256.html +0 -1763
  317. package/docs/annotation-campaign-A.csv +0 -54
  318. package/docs/annotation-campaign-B.csv +0 -54
  319. package/docs/annotation-campaign.csv +0 -54
  320. package/docs/eval/annotations-A.jsonl +0 -16
  321. package/docs/eval/annotations.json +0 -122
  322. package/docs/eval/recorded-labels.json +0 -12
  323. package/docs/eval/replay-cases.json +0 -14
  324. package/docs/eval/replay-labels-b.json +0 -14
  325. package/docs/examples/attention-request.json +0 -16
  326. package/docs/examples/checkpoint.json +0 -15
  327. package/docs/examples/claim.json +0 -66
  328. package/docs/examples/collaboration.json +0 -66
  329. package/docs/examples/concept-checkpoint.json +0 -15
  330. package/docs/examples/concept-prune-plan.json +0 -45
  331. package/docs/examples/federation.json +0 -126
  332. package/docs/examples/frame-chain.json +0 -77
  333. package/docs/examples/ledger-snapshot.json +0 -108
  334. package/docs/examples/manifest.json +0 -77
  335. package/docs/examples/message.json +0 -19
  336. package/docs/examples/other-model.json +0 -45
  337. package/docs/examples/plan.json +0 -50
  338. package/docs/examples/prune-plan.json +0 -48
  339. package/docs/examples/receipt.json +0 -22
  340. package/docs/examples/runtime-run.json +0 -573
  341. package/docs/examples/team.json +0 -66
  342. package/docs/examples/verb-note.json +0 -11
  343. package/docs/probes/bayes-scores.json +0 -1452
  344. package/docs/probes/calibration-imported.json +0 -141
  345. package/docs/probes/context-ast-deepseek.json +0 -1577
  346. package/docs/probes/deepseek-probe.json +0 -917
  347. package/docs/probes/eval-recorded-replay.json +0 -597
  348. package/docs/probes/eval-replay.json +0 -124
  349. package/docs/probes/eval-summary.json +0 -42
  350. package/docs/probes/runtime-eval.json +0 -4230
  351. package/docs/schemas/collaboration.schema.json +0 -73
  352. package/docs/schemas/common.schema.json +0 -125
  353. package/docs/schemas/compaction.schema.json +0 -121
  354. package/docs/schemas/concept-compaction.schema.json +0 -73
  355. package/docs/schemas/concept.schema.json +0 -97
  356. package/docs/schemas/epistemic.schema.json +0 -178
  357. package/docs/schemas/federation.schema.json +0 -66
  358. package/docs/schemas/frame.schema.json +0 -86
  359. package/docs/schemas/ledger-snapshot.schema.json +0 -30
  360. package/docs/schemas/manifest.schema.json +0 -169
  361. package/docs/schemas/message.schema.json +0 -41
  362. package/docs/schemas/plan.schema.json +0 -189
  363. package/docs/schemas/receipt.schema.json +0 -45
  364. package/docs/schemas/social.schema.json +0 -153
  365. package/docs/schemas/team.schema.json +0 -56
  366. package/docs/schemas/verb.schema.json +0 -120
  367. package/eslint.config.mts +0 -96
  368. package/index.html +0 -13
  369. package/lib/context/workspaceProtocol.d.ts +0 -30
  370. package/lib/context/workspaceProtocol.js +0 -49
  371. package/prettier.config.mts +0 -37
  372. package/public/favicon.svg +0 -1
  373. package/public/icons.svg +0 -24
  374. package/src/attention/attention.ts +0 -347
  375. package/src/attention/budget.ts +0 -72
  376. package/src/attention/cognition.ts +0 -519
  377. package/src/attention/index.ts +0 -8
  378. package/src/attention/interrupts.ts +0 -228
  379. package/src/attention/resource.ts +0 -316
  380. package/src/attention/social.ts +0 -217
  381. package/src/attention/socialTypes.ts +0 -115
  382. package/src/authz/authz.ts +0 -305
  383. package/src/authz/index.ts +0 -1
  384. package/src/bayes/calibration.ts +0 -204
  385. package/src/bayes/calibrationRegistry.ts +0 -111
  386. package/src/bayes/calibrationSelection.ts +0 -154
  387. package/src/bayes/engine.ts +0 -532
  388. package/src/bayes/index.ts +0 -6
  389. package/src/bayes/reliability.ts +0 -62
  390. package/src/bayes/scopedPriors.ts +0 -88
  391. package/src/boundary/frame.ts +0 -322
  392. package/src/boundary/ids.ts +0 -14
  393. package/src/boundary/index.ts +0 -12
  394. package/src/boundary/refTable.ts +0 -125
  395. package/src/boundary/toolCall.ts +0 -212
  396. package/src/concept/concept.ts +0 -75
  397. package/src/concept/conceptBudget.ts +0 -346
  398. package/src/concept/conceptBudgetTypes.ts +0 -79
  399. package/src/concept/index.ts +0 -3
  400. package/src/concurrency/idempotency.ts +0 -41
  401. package/src/concurrency/index.ts +0 -5
  402. package/src/concurrency/mutex.ts +0 -26
  403. package/src/concurrency/namedLocks.ts +0 -35
  404. package/src/concurrency/retry.ts +0 -50
  405. package/src/concurrency/worker.ts +0 -128
  406. package/src/context/agentContext.ts +0 -308
  407. package/src/context/ast.ts +0 -499
  408. package/src/context/contentStore.ts +0 -75
  409. package/src/context/context.ts +0 -65
  410. package/src/context/contextIndex.ts +0 -85
  411. package/src/context/document.ts +0 -137
  412. package/src/context/fileStorePath.ts +0 -14
  413. package/src/context/index.ts +0 -43
  414. package/src/context/lifecycle.ts +0 -221
  415. package/src/context/projections.ts +0 -151
  416. package/src/context/workspace.ts +0 -422
  417. package/src/context/workspaceProtocol.ts +0 -112
  418. package/src/context/workspaceService.ts +0 -423
  419. package/src/cooperation/board.ts +0 -104
  420. package/src/cooperation/collaboration.ts +0 -60
  421. package/src/cooperation/index.ts +0 -6
  422. package/src/cooperation/merge.ts +0 -190
  423. package/src/cooperation/metrics.ts +0 -57
  424. package/src/cooperation/session.ts +0 -131
  425. package/src/cooperation/unified.ts +0 -56
  426. package/src/counter.ts +0 -9
  427. package/src/database/adapter/adapters.ts +0 -35
  428. package/src/database/adapter/index.ts +0 -4
  429. package/src/database/adapter/runtimeSink.ts +0 -74
  430. package/src/database/adapter/sessionStore.ts +0 -32
  431. package/src/database/adapter/turnStore.ts +0 -23
  432. package/src/database/entity/agent.ts +0 -199
  433. package/src/database/entity/entity.ts +0 -1046
  434. package/src/database/entity/handle.ts +0 -79
  435. package/src/database/entity/index.ts +0 -32
  436. package/src/database/entity/message.ts +0 -74
  437. package/src/database/entity/meta.ts +0 -11
  438. package/src/database/entity/object.ts +0 -42
  439. package/src/database/entity/outbox.ts +0 -15
  440. package/src/database/entity/process.ts +0 -65
  441. package/src/database/entity/registry.ts +0 -155
  442. package/src/database/entity/runtimeApproval.ts +0 -30
  443. package/src/database/entity/runtimeSession.ts +0 -13
  444. package/src/database/entity/runtimeTurn.ts +0 -15
  445. package/src/database/entity/thread.ts +0 -85
  446. package/src/database/entity/window.ts +0 -85
  447. package/src/database/entity/worker.ts +0 -90
  448. package/src/database/index.ts +0 -6
  449. package/src/database/repository/addressSpaceRepository.ts +0 -72
  450. package/src/database/repository/agentRepository.ts +0 -460
  451. package/src/database/repository/base.ts +0 -41
  452. package/src/database/repository/executionRepository.ts +0 -477
  453. package/src/database/repository/handleRepository.ts +0 -64
  454. package/src/database/repository/index.ts +0 -20
  455. package/src/database/repository/messageRepository.ts +0 -52
  456. package/src/database/repository/objectRepository.ts +0 -69
  457. package/src/database/repository/outboxRepository.ts +0 -63
  458. package/src/database/repository/processRepository.ts +0 -58
  459. package/src/database/repository/registryRepository.ts +0 -119
  460. package/src/database/repository/repository.ts +0 -491
  461. package/src/database/repository/runtimeApprovalRepository.ts +0 -44
  462. package/src/database/repository/runtimeSessionRepository.ts +0 -27
  463. package/src/database/repository/runtimeTurnRepository.ts +0 -39
  464. package/src/database/repository/socialRepository.ts +0 -185
  465. package/src/database/repository/threadRepository.ts +0 -57
  466. package/src/database/repository/windowRepository.ts +0 -88
  467. package/src/database/repository/workerRepository.ts +0 -69
  468. package/src/database/service/database.ts +0 -90
  469. package/src/database/service/handleService.ts +0 -48
  470. package/src/database/service/index.ts +0 -16
  471. package/src/database/service/messageService.ts +0 -61
  472. package/src/database/service/objectService.ts +0 -59
  473. package/src/database/service/processService.ts +0 -61
  474. package/src/database/service/registryServices.ts +0 -88
  475. package/src/database/service/service.ts +0 -183
  476. package/src/database/service/stepService.ts +0 -53
  477. package/src/database/service/threadService.ts +0 -65
  478. package/src/database/service/windowService.ts +0 -30
  479. package/src/database/service/workerService.ts +0 -57
  480. package/src/database/sql/index.ts +0 -3
  481. package/src/database/sql/schema.ts +0 -418
  482. package/src/database/sql/sql.ts +0 -543
  483. package/src/database/store/database.ts +0 -210
  484. package/src/database/store/index.ts +0 -3
  485. package/src/database/store/store.ts +0 -266
  486. package/src/delegate/index.ts +0 -2
  487. package/src/delegate/team.ts +0 -268
  488. package/src/delegate/teamTypes.ts +0 -47
  489. package/src/diagnosis/checklist.ts +0 -74
  490. package/src/diagnosis/context.ts +0 -93
  491. package/src/diagnosis/frame.ts +0 -256
  492. package/src/diagnosis/index.ts +0 -5
  493. package/src/diagnosis/router.ts +0 -72
  494. package/src/diagnosis/types.ts +0 -89
  495. package/src/error/agentError.ts +0 -16
  496. package/src/error/ampError.ts +0 -20
  497. package/src/error/authzError.ts +0 -14
  498. package/src/error/boundaryRejection.ts +0 -20
  499. package/src/error/index.ts +0 -7
  500. package/src/error/kernelError.ts +0 -27
  501. package/src/error/mailboxError.ts +0 -9
  502. package/src/error/windowError.ts +0 -12
  503. package/src/evaluation/agreement.ts +0 -96
  504. package/src/evaluation/index.ts +0 -2
  505. package/src/evaluation/labels.ts +0 -241
  506. package/src/evidence/evidenceGraph.ts +0 -205
  507. package/src/evidence/index.ts +0 -4
  508. package/src/evidence/revalidation.ts +0 -191
  509. package/src/execution/exec.ts +0 -408
  510. package/src/execution/handles.ts +0 -65
  511. package/src/execution/index.ts +0 -3
  512. package/src/execution/pin.ts +0 -154
  513. package/src/frame/frame.ts +0 -70
  514. package/src/frame/index.ts +0 -1
  515. package/src/goal/goal.ts +0 -43
  516. package/src/goal/index.ts +0 -2
  517. package/src/goal/plan.ts +0 -196
  518. package/src/governance/attentionManifest.ts +0 -802
  519. package/src/governance/audit.ts +0 -362
  520. package/src/governance/budget.ts +0 -628
  521. package/src/governance/context.ts +0 -356
  522. package/src/governance/control.ts +0 -159
  523. package/src/governance/cooperation.ts +0 -479
  524. package/src/governance/credential.ts +0 -359
  525. package/src/governance/httpServer.ts +0 -99
  526. package/src/governance/index.ts +0 -32
  527. package/src/governance/legal.ts +0 -574
  528. package/src/governance/matrix.ts +0 -460
  529. package/src/governance/perspectives.ts +0 -406
  530. package/src/governance/signing.ts +0 -407
  531. package/src/governance/store.ts +0 -125
  532. package/src/governance/switching.ts +0 -459
  533. package/src/governance/toolPolicy.ts +0 -80
  534. package/src/governance/transport.ts +0 -204
  535. package/src/governance/types.ts +0 -380
  536. package/src/governance/workflow.ts +0 -743
  537. package/src/index.ts +0 -11
  538. package/src/interaction/index.ts +0 -1
  539. package/src/interaction/interaction.ts +0 -391
  540. package/src/ipc/index.ts +0 -4
  541. package/src/ipc/mailbox.ts +0 -132
  542. package/src/ipc/memorySocialStore.ts +0 -93
  543. package/src/ipc/social.ts +0 -93
  544. package/src/kernel/index.ts +0 -14
  545. package/src/kernel/scheduler.ts +0 -498
  546. package/src/ledger/checkpoint.ts +0 -304
  547. package/src/ledger/index.ts +0 -4
  548. package/src/ledger/ledger.ts +0 -31
  549. package/src/ledger/persistence.ts +0 -159
  550. package/src/ledger/share.ts +0 -80
  551. package/src/lock/index.ts +0 -2
  552. package/src/lock/lease.ts +0 -74
  553. package/src/main.ts +0 -199
  554. package/src/manifest/agentCard.ts +0 -72
  555. package/src/manifest/cardSign.ts +0 -87
  556. package/src/manifest/federation.ts +0 -237
  557. package/src/manifest/federationTypes.ts +0 -55
  558. package/src/manifest/index.ts +0 -8
  559. package/src/manifest/keys.ts +0 -643
  560. package/src/manifest/manifest.ts +0 -114
  561. package/src/manifest/sign.ts +0 -134
  562. package/src/manifest/trust.ts +0 -146
  563. package/src/mission/index.ts +0 -2
  564. package/src/mission/missionQueue.ts +0 -75
  565. package/src/module/index.ts +0 -9
  566. package/src/module/llm/deepseek.ts +0 -214
  567. package/src/module/llm/llm.ts +0 -84
  568. package/src/module/mcp/client.ts +0 -110
  569. package/src/module/mcp/http.ts +0 -102
  570. package/src/module/mcp/protocol.ts +0 -77
  571. package/src/module/mcp/server.ts +0 -148
  572. package/src/module/mcp/transport.ts +0 -30
  573. package/src/module/mcp/workspaceServer.ts +0 -111
  574. package/src/module/sandbox/sandbox.ts +0 -29
  575. package/src/module/tool/mcpExecutor.ts +0 -54
  576. package/src/module/tool/policy.ts +0 -52
  577. package/src/module/tool/redact.ts +0 -29
  578. package/src/module/tool/tool.ts +0 -53
  579. package/src/module/tool/workspaceExecutor.ts +0 -35
  580. package/src/policy/guard.ts +0 -240
  581. package/src/policy/index.ts +0 -2
  582. package/src/policy/policy.ts +0 -561
  583. package/src/process/agent.ts +0 -341
  584. package/src/process/agentService.ts +0 -236
  585. package/src/process/agentStore.ts +0 -378
  586. package/src/process/executionStore.ts +0 -151
  587. package/src/process/index.ts +0 -5
  588. package/src/process/process.ts +0 -240
  589. package/src/process/transitions.ts +0 -19
  590. package/src/progress/index.ts +0 -2
  591. package/src/progress/progress.ts +0 -85
  592. package/src/protocol/a2a.ts +0 -120
  593. package/src/protocol/channel.ts +0 -44
  594. package/src/protocol/envelope.ts +0 -655
  595. package/src/protocol/httpChannel.ts +0 -87
  596. package/src/protocol/index.ts +0 -6
  597. package/src/protocol/protocol.ts +0 -138
  598. package/src/protocol/wire.ts +0 -60
  599. package/src/receipt/index.ts +0 -1
  600. package/src/receipt/receipt.ts +0 -47
  601. package/src/registry/content.ts +0 -69
  602. package/src/registry/index.ts +0 -23
  603. package/src/registry/registry.ts +0 -599
  604. package/src/runtime/action.ts +0 -87
  605. package/src/runtime/behavior.ts +0 -330
  606. package/src/runtime/behaviorTuning.ts +0 -163
  607. package/src/runtime/cognitiveRuntime.ts +0 -703
  608. package/src/runtime/escalation.ts +0 -35
  609. package/src/runtime/governedTask.ts +0 -233
  610. package/src/runtime/index.ts +0 -15
  611. package/src/runtime/kernelRuntime.ts +0 -144
  612. package/src/runtime/operation.ts +0 -35
  613. package/src/runtime/replay.ts +0 -71
  614. package/src/runtime/routing.ts +0 -58
  615. package/src/runtime/safety.ts +0 -48
  616. package/src/runtime/session.ts +0 -341
  617. package/src/runtime/sessionStore.ts +0 -43
  618. package/src/runtime/telemetry.ts +0 -55
  619. package/src/runtime/turnStore.ts +0 -30
  620. package/src/scenario/index.ts +0 -1
  621. package/src/scenario/scenario.ts +0 -299
  622. package/src/semantic/canonical.ts +0 -24
  623. package/src/semantic/index.ts +0 -2
  624. package/src/semantic/sha256.ts +0 -82
  625. package/src/storage/appendLog.ts +0 -372
  626. package/src/storage/fileAppendSink.ts +0 -40
  627. package/src/storage/index.ts +0 -2
  628. package/src/store/bindCache.ts +0 -22
  629. package/src/store/cache.ts +0 -191
  630. package/src/store/context.ts +0 -31
  631. package/src/store/handleStore.ts +0 -31
  632. package/src/store/index.ts +0 -9
  633. package/src/store/messageStore.ts +0 -31
  634. package/src/store/objectStore.ts +0 -31
  635. package/src/store/processStore.ts +0 -31
  636. package/src/store/threadStore.ts +0 -31
  637. package/src/store/windowStore.ts +0 -31
  638. package/src/structure/abstractSyntaxTree.ts +0 -493
  639. package/src/structure/avlTree.ts +0 -408
  640. package/src/structure/binarySearchTree.ts +0 -352
  641. package/src/structure/binaryTree.ts +0 -525
  642. package/src/structure/bloomFilter.ts +0 -231
  643. package/src/structure/compare.ts +0 -110
  644. package/src/structure/cuckooFilter.ts +0 -337
  645. package/src/structure/deque.ts +0 -223
  646. package/src/structure/filterKey.ts +0 -36
  647. package/src/structure/hash.ts +0 -279
  648. package/src/structure/heap.ts +0 -268
  649. package/src/structure/index.ts +0 -20
  650. package/src/structure/iterator.ts +0 -22
  651. package/src/structure/lru.ts +0 -619
  652. package/src/structure/ordered.ts +0 -399
  653. package/src/structure/priority.ts +0 -915
  654. package/src/structure/queue.ts +0 -168
  655. package/src/structure/redBlackTree.ts +0 -680
  656. package/src/structure/segmentTree.ts +0 -248
  657. package/src/structure/stack.ts +0 -118
  658. package/src/structure/unordered.ts +0 -407
  659. package/src/style.css +0 -278
  660. package/src/thread/index.ts +0 -2
  661. package/src/thread/thread.ts +0 -176
  662. package/src/thread/transitions.ts +0 -17
  663. package/src/types/common.ts +0 -119
  664. package/src/types/index.ts +0 -23
  665. package/src/warrant/epistemic.ts +0 -154
  666. package/src/warrant/index.ts +0 -3
  667. package/src/warrant/inquiry.ts +0 -166
  668. package/src/warrant/warrant.ts +0 -179
  669. package/src/window/compaction.ts +0 -403
  670. package/src/window/compactionTypes.ts +0 -119
  671. package/src/window/index.ts +0 -7
  672. package/src/window/page.ts +0 -18
  673. package/src/window/window.ts +0 -244
  674. package/tools/_helpers.ts +0 -34
  675. package/tools/a2a-check.ts +0 -110
  676. package/tools/a2a-flow-check.ts +0 -78
  677. package/tools/agent-card-check.ts +0 -76
  678. package/tools/agent-check.ts +0 -236
  679. package/tools/agent-store-check.ts +0 -153
  680. package/tools/agreement-check.ts +0 -27
  681. package/tools/annotation-server-check.ts +0 -48
  682. package/tools/annotation-server.ts +0 -230
  683. package/tools/annotation-status.ts +0 -65
  684. package/tools/approval-access-check.ts +0 -66
  685. package/tools/arch-check.ts +0 -72
  686. package/tools/attention-core-check.ts +0 -48
  687. package/tools/attention-resource-check.ts +0 -169
  688. package/tools/audit-check.ts +0 -207
  689. package/tools/authority-check.ts +0 -124
  690. package/tools/authz-check.ts +0 -138
  691. package/tools/bayes-check.ts +0 -162
  692. package/tools/bayes-probe.ts +0 -154
  693. package/tools/behavior-tuning-check.ts +0 -53
  694. package/tools/board-check.ts +0 -87
  695. package/tools/boundary-check.ts +0 -199
  696. package/tools/bundle-trust-check.ts +0 -96
  697. package/tools/calibrate-imported.ts +0 -108
  698. package/tools/calibration-check.ts +0 -44
  699. package/tools/calibration-registry-check.ts +0 -40
  700. package/tools/calibration-sim.ts +0 -40
  701. package/tools/card-sign-check.ts +0 -71
  702. package/tools/card-trust-check.ts +0 -78
  703. package/tools/channel-check.ts +0 -86
  704. package/tools/checkpoint-check.ts +0 -620
  705. package/tools/cleanup-style-configs.ts +0 -29
  706. package/tools/cognition-check.ts +0 -648
  707. package/tools/cognitive-runtime-check.ts +0 -609
  708. package/tools/collaboration-demo.ts +0 -78
  709. package/tools/compaction-check.ts +0 -266
  710. package/tools/concept-check.ts +0 -260
  711. package/tools/concurrency-check.ts +0 -242
  712. package/tools/conformance-runtime.ts +0 -389
  713. package/tools/conformance.ts +0 -651
  714. package/tools/context-ast-check.ts +0 -90
  715. package/tools/context-ast-deepseek.ts +0 -242
  716. package/tools/context-graph-bench.ts +0 -50
  717. package/tools/context-graph-check.ts +0 -229
  718. package/tools/context-index-check.ts +0 -48
  719. package/tools/context-lifecycle-check.ts +0 -69
  720. package/tools/context-projection-check.ts +0 -73
  721. package/tools/context-scope-check.ts +0 -134
  722. package/tools/context-storage-check.ts +0 -113
  723. package/tools/cooperation-check.ts +0 -517
  724. package/tools/cooperation-http-check.ts +0 -125
  725. package/tools/deepseek-llm-check.ts +0 -147
  726. package/tools/deepseek-probe.ts +0 -494
  727. package/tools/diagnosis-check.ts +0 -109
  728. package/tools/diagnosis-router-check.ts +0 -50
  729. package/tools/document-context-check.ts +0 -237
  730. package/tools/envelope-check.ts +0 -659
  731. package/tools/eslint-rules/index.ts +0 -26
  732. package/tools/eslint-rules/rules/jsdoc-comment-format.ts +0 -105
  733. package/tools/eslint-rules/rules/no-function-keyword.ts +0 -60
  734. package/tools/eslint-rules/rules/no-multiline-parameters.ts +0 -71
  735. package/tools/eslint-rules/rules/no-multiline-type-parameters.ts +0 -57
  736. package/tools/eslint-rules/rules/no-undefined-comparison.ts +0 -115
  737. package/tools/eslint-rules/rules/prefer-template-for-string-conversion.ts +0 -97
  738. package/tools/eslint-rules/rules/require-typeof-parentheses.ts +0 -49
  739. package/tools/eslint-rules/rules/require-variable-type.ts +0 -36
  740. package/tools/eslint-rules/rules/variable-naming.ts +0 -175
  741. package/tools/eval-recorded-replay.ts +0 -215
  742. package/tools/eval-replay.ts +0 -139
  743. package/tools/eval-summary.ts +0 -136
  744. package/tools/evidence-revalidation-check.ts +0 -235
  745. package/tools/exec-check.ts +0 -217
  746. package/tools/federation-check.ts +0 -166
  747. package/tools/federation-demo.ts +0 -45
  748. package/tools/filter-check.ts +0 -243
  749. package/tools/governance-check.ts +0 -680
  750. package/tools/governance-store-check.ts +0 -131
  751. package/tools/governed-task-check.ts +0 -227
  752. package/tools/guard-check.ts +0 -122
  753. package/tools/http-channel-check.ts +0 -125
  754. package/tools/import-campaign.ts +0 -28
  755. package/tools/inquiry-check.ts +0 -126
  756. package/tools/interaction-check.ts +0 -171
  757. package/tools/interrupts-check.ts +0 -173
  758. package/tools/ipc-check.ts +0 -170
  759. package/tools/kernel-async-check.ts +0 -136
  760. package/tools/kernel-check.ts +0 -369
  761. package/tools/kernel-runtime-check.ts +0 -276
  762. package/tools/keys-check.ts +0 -559
  763. package/tools/label-audit.ts +0 -70
  764. package/tools/labels-workflow-check.ts +0 -49
  765. package/tools/linear-structure-check.ts +0 -524
  766. package/tools/live-exec.ts +0 -62
  767. package/tools/live-governance-eval.ts +0 -813
  768. package/tools/live-legacy.ts +0 -50
  769. package/tools/lru-check.ts +0 -182
  770. package/tools/make-annotation-campaign.ts +0 -57
  771. package/tools/mcp-executor-check.ts +0 -33
  772. package/tools/merge-annotations.ts +0 -32
  773. package/tools/merge-check.ts +0 -98
  774. package/tools/metrics-check.ts +0 -84
  775. package/tools/net-probe.ts +0 -107
  776. package/tools/oauth-probe.ts +0 -117
  777. package/tools/outbox-check.ts +0 -50
  778. package/tools/persistence-check.ts +0 -144
  779. package/tools/pin-check.ts +0 -116
  780. package/tools/platform-check.ts +0 -281
  781. package/tools/policy-check.ts +0 -514
  782. package/tools/prettier-plugin/index.mts +0 -84
  783. package/tools/priority-check.ts +0 -627
  784. package/tools/probe-context7.ts +0 -63
  785. package/tools/registry-check.ts +0 -252
  786. package/tools/reliability-check.ts +0 -25
  787. package/tools/retry-check.ts +0 -85
  788. package/tools/rewrite-declaration-extensions.ts +0 -98
  789. package/tools/runtime-check.ts +0 -165
  790. package/tools/runtime-deepseek-check.ts +0 -85
  791. package/tools/runtime-eval.ts +0 -358
  792. package/tools/scenario-check.ts +0 -161
  793. package/tools/scenario-incident.ts +0 -162
  794. package/tools/scoped-priors-check.ts +0 -35
  795. package/tools/scoped-priors-pooling.ts +0 -34
  796. package/tools/segment-ast-check.ts +0 -649
  797. package/tools/session-check.ts +0 -196
  798. package/tools/session-store-check.ts +0 -73
  799. package/tools/share-check.ts +0 -88
  800. package/tools/sign-check.ts +0 -95
  801. package/tools/signing-check.ts +0 -275
  802. package/tools/snapshot-demo.ts +0 -65
  803. package/tools/social-check.ts +0 -146
  804. package/tools/structure-compare-check.ts +0 -95
  805. package/tools/support/documentScanner.ts +0 -577
  806. package/tools/support/texScanner.ts +0 -631
  807. package/tools/support/workspaceScanner.ts +0 -476
  808. package/tools/team-check.ts +0 -163
  809. package/tools/team-demo.ts +0 -36
  810. package/tools/telemetry-check.ts +0 -44
  811. package/tools/tex-context-check.ts +0 -263
  812. package/tools/tool-gate-check.ts +0 -98
  813. package/tools/tree-structure-check.ts +0 -786
  814. package/tools/trust-flow-check.ts +0 -158
  815. package/tools/turn-store-check.ts +0 -57
  816. package/tools/unified-check.ts +0 -83
  817. package/tools/validate.ts +0 -309
  818. package/tools/wire-check.ts +0 -96
  819. package/tools/workspace-agent-check.ts +0 -486
  820. package/tools/workspace-agent-demo.ts +0 -62
  821. package/tools/workspace-agent-server.ts +0 -101
  822. package/tools/workspace-context-check.ts +0 -331
  823. package/tools/workspace-context.ts +0 -131
  824. package/tsconfig.build.json +0 -14
  825. package/tsconfig.json +0 -24
  826. package/tsconfig.tools.json +0 -31
  827. package/viz/context-ast.json +0 -1577
@@ -0,0 +1,2375 @@
1
+ /**
2
+ * Read-only Office document parsing.
3
+ *
4
+ * The agent workspace exposes `outline`, `import`, `export` and `dependency`
5
+ * for files, so an Office container must answer the same questions as a source
6
+ * file: what is inside, what does it point at, and what does it expose? This
7
+ * module does that without a third-party parser or a runtime dependency:
8
+ *
9
+ * - OOXML (`.docx`, `.xlsx`, `.pptx`) is a ZIP of XML parts. `zip.ts` reads the
10
+ * archive and a bounded tag scanner below reads the parts. The scanner is
11
+ * deliberately not a DOM: it records tag spans, attributes and matching close
12
+ * indices, which is enough to walk paragraphs, slides, sheets and formulas
13
+ * with a hard tag/depth limit.
14
+ * - Legacy binaries (`.doc`, `.xls`, `.ppt`, `.pub`) are OLE Compound Files.
15
+ * `cfb.ts` extracts named streams and this module recovers printable
16
+ * UTF-16LE/ASCII text. That recovery is heuristic and reported as such.
17
+ * - Access databases (`.accdb`, `.mdb`) use the Jet/ACE format rather than CFB.
18
+ * The header is recognised and catalog names are recovered on a best-effort
19
+ * basis; no page or column semantics are claimed.
20
+ * - Anything else is `kind: "unknown"` with an explicit warning instead of a
21
+ * guessed parse.
22
+ *
23
+ * Parsing is not semantic validation. Word headers/footnotes/comments,
24
+ * PowerPoint layouts/notes/charts, Excel formula results and Access table
25
+ * definitions are outside the model. Every result therefore carries warnings
26
+ * that name the unsupported surface; `officeImports`, `officeExports` and
27
+ * `officeReferences` expose the facts that were actually recovered.
28
+ */
29
+ import { lstatSync, readFileSync } from "node:fs";
30
+ import { posix } from "node:path";
31
+ import { rawDigest } from "../fingerprint/raw.js";
32
+ import { readCfb } from "../file/cfb.js";
33
+ import { fail, isFilesystemError } from "../file/errors.js";
34
+ import { hasOfficeOutlineParser, registerOfficeOutlineParser } from "./officeBridge.js";
35
+ import { readZipEntries, zipText } from "../file/zip.js";
36
+ /**
37
+ * Largest Office file `parseOffice` will load. Legacy containers and OOXML
38
+ * packages can be much larger than a text outline; `parseOffice` still applies
39
+ * the same 64 MiB ceiling as the rest of the bounded readers.
40
+ */
41
+ export const OfficeMaxDocumentBytes = 64 * 1024 * 1024;
42
+ /**
43
+ * Largest Office file the outline bridge accepts from raw bytes.
44
+ *
45
+ * `outline.ts` owns its own 4 MiB text ceiling and routes Office containers
46
+ * through `readOutlineBytes` before calling the registered parser. Keeping the
47
+ * same value here means a direct `officeOutlineParser` caller gets
48
+ * `quota_exceeded` before any container walk, never `binary_file`, and the two
49
+ * entry points agree on the bound.
50
+ */
51
+ export const OfficeMaxOutlineBytes = 4 * 1024 * 1024;
52
+ const MaxSections = 2000;
53
+ const MaxReferences = 2000;
54
+ const MaxImports = 500;
55
+ const MaxExports = 1000;
56
+ const MaxWarnings = 48;
57
+ const MaxLabelLength = 200;
58
+ const MaxDetailLength = 200;
59
+ const MaxTextFragments = 500;
60
+ const MaxRawFragments = 4096;
61
+ const MaxTextScanBytes = 48 * 1024 * 1024;
62
+ const MaxXmlTags = 200000;
63
+ const MaxXmlDepth = 256;
64
+ const MaxRelationshipTargets = 500;
65
+ /**
66
+ * Extensions recognized as Office documents. The map is lowercase; `isOfficeFile`
67
+ * and `kindFromPath` both fold the caller path before lookup.
68
+ */
69
+ const OfficeExtensions = {
70
+ ".docx": "word",
71
+ ".doc": "word",
72
+ ".xlsx": "excel",
73
+ ".xls": "excel",
74
+ ".pptx": "powerpoint",
75
+ ".ppt": "powerpoint",
76
+ ".pub": "publisher",
77
+ ".accdb": "access",
78
+ ".mdb": "access",
79
+ };
80
+ /**
81
+ * Fold a path to its lowercase extension. A dot-file or a path without an
82
+ * extension yields an empty string.
83
+ *
84
+ * @param path - Caller path.
85
+ * @returns Lowercase extension including the dot, or `""`.
86
+ */
87
+ const extensionOf = (path) => {
88
+ const lower = path.toLowerCase();
89
+ const slash = lower.lastIndexOf("/");
90
+ const name = slash === -1 ? lower : lower.slice(slash + 1);
91
+ const dot = name.lastIndexOf(".");
92
+ return dot <= 0 ? "" : name.slice(dot);
93
+ };
94
+ /**
95
+ * Classify a path by extension.
96
+ *
97
+ * @param path - Caller path.
98
+ * @returns Office family, or `unknown`.
99
+ */
100
+ const kindFromPath = (path) => OfficeExtensions[extensionOf(path)] ?? "unknown";
101
+ /**
102
+ * Whether a path names a supported Office document. The check is purely
103
+ * syntactic: a file with a `.docx` suffix that is not a ZIP is still routed to
104
+ * the Office parser so it can report a structured warning.
105
+ *
106
+ * @param path - Root-relative or absolute path.
107
+ * @returns Whether the extension is one of the supported Office extensions.
108
+ */
109
+ export const isOfficeFile = (path) => kindFromPath(path) !== "unknown";
110
+ /**
111
+ * Collapse whitespace and bound a label.
112
+ *
113
+ * @param value - Raw label.
114
+ * @param fallback - Text used when the label is empty.
115
+ * @returns A compact label.
116
+ */
117
+ const cleanLabel = (value, fallback) => {
118
+ const text = value.replace(/\s+/g, " ").trim();
119
+ const chosen = text.length === 0 ? fallback : text;
120
+ return chosen.slice(0, MaxLabelLength);
121
+ };
122
+ /**
123
+ * Collapse whitespace and bound a detail/target string.
124
+ *
125
+ * @param value - Raw value.
126
+ * @returns A compact value.
127
+ */
128
+ const cleanDetail = (value) => value.replace(/\s+/g, " ").trim().slice(0, MaxDetailLength);
129
+ /**
130
+ * Read ASCII bytes from a buffer without assuming a zero terminator.
131
+ *
132
+ * @param bytes - Source bytes.
133
+ * @param offset - Start offset.
134
+ * @param length - Maximum byte count.
135
+ * @returns Printable ASCII characters only.
136
+ */
137
+ const asciiAt = (bytes, offset, length) => {
138
+ const end = Math.min(bytes.length, offset + length);
139
+ let value = "";
140
+ for (let index = offset; index < end; index += 1) {
141
+ const code = bytes[index];
142
+ if (code < 0x20 || code > 0x7e) {
143
+ break;
144
+ }
145
+ value += String.fromCharCode(code);
146
+ }
147
+ return value;
148
+ };
149
+ /**
150
+ * Extract an errno code from an unknown thrown value without trusting it.
151
+ *
152
+ * @param error - Caught value.
153
+ * @returns The string code, or `""`.
154
+ */
155
+ const errorCode = (error) => {
156
+ if (typeof (error) !== "object" || error === null) {
157
+ return "";
158
+ }
159
+ const code = error.code;
160
+ return typeof (code) === "string" ? code : "";
161
+ };
162
+ /**
163
+ * Translate a host filesystem failure into the closed domain taxonomy.
164
+ *
165
+ * @param error - Caught value.
166
+ * @returns Never; throws a classified error.
167
+ */
168
+ const mapFsError = (error) => {
169
+ if (isFilesystemError(error)) {
170
+ throw error;
171
+ }
172
+ switch (errorCode(error)) {
173
+ case "ENOENT": {
174
+ return fail("not_found");
175
+ }
176
+ case "EACCES":
177
+ case "EPERM": {
178
+ return fail("permission_denied");
179
+ }
180
+ case "EISDIR": {
181
+ return fail("not_file");
182
+ }
183
+ case "ENOTDIR": {
184
+ return fail("not_directory");
185
+ }
186
+ case "ELOOP": {
187
+ return fail("symlink_denied");
188
+ }
189
+ case "ENOSPC":
190
+ case "EDQUOT": {
191
+ return fail("quota_exceeded");
192
+ }
193
+ default: {
194
+ return fail("internal_error");
195
+ }
196
+ }
197
+ };
198
+ /**
199
+ * `lstat` through the domain error taxonomy.
200
+ *
201
+ * @param absolute - Host absolute path.
202
+ * @returns File stats.
203
+ */
204
+ const statOrFail = (absolute) => {
205
+ try {
206
+ return lstatSync(absolute);
207
+ }
208
+ catch (error) {
209
+ return mapFsError(error);
210
+ }
211
+ };
212
+ /**
213
+ * Read whole-file bytes through the domain error taxonomy.
214
+ *
215
+ * @param absolute - Host absolute path.
216
+ * @returns File bytes.
217
+ */
218
+ const readBytesOrFail = (absolute) => {
219
+ try {
220
+ return readFileSync(absolute);
221
+ }
222
+ catch (error) {
223
+ return mapFsError(error);
224
+ }
225
+ };
226
+ /**
227
+ * Decode the five predefined XML entities and numeric character references.
228
+ * Unknown entities are left intact so a caller can see exactly what was not
229
+ * understood.
230
+ *
231
+ * @param value - Encoded text.
232
+ * @returns Decoded text.
233
+ */
234
+ const decodeXmlEntities = (value) => value.replace(/&(#x[0-9A-Fa-f]+|#[0-9]+|[A-Za-z][A-Za-z0-9]*);/g, (match, entity) => {
235
+ if (entity === "amp") {
236
+ return "&";
237
+ }
238
+ if (entity === "lt") {
239
+ return "<";
240
+ }
241
+ if (entity === "gt") {
242
+ return ">";
243
+ }
244
+ if (entity === "quot") {
245
+ return '"';
246
+ }
247
+ if (entity === "apos") {
248
+ return "'";
249
+ }
250
+ if (entity === "nbsp") {
251
+ return "\u00a0";
252
+ }
253
+ if (entity.startsWith("#x") || entity.startsWith("#X")) {
254
+ const code = Number.parseInt(entity.slice(2), 16);
255
+ return Number.isSafeInteger(code) && code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : match;
256
+ }
257
+ if (entity.startsWith("#")) {
258
+ const code = Number.parseInt(entity.slice(1), 10);
259
+ return Number.isSafeInteger(code) && code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : match;
260
+ }
261
+ return match;
262
+ });
263
+ /**
264
+ * Remove comments, CDATA markers and any residual tags from a text fragment,
265
+ * then decode entities. Used for the content of `<w:t>`, `<a:t>`, `<f>` and
266
+ * similar leaf elements where nested markup is not expected.
267
+ *
268
+ * @param value - Leaf content.
269
+ * @returns Plain text.
270
+ */
271
+ const xmlText = (value) => decodeXmlEntities(value
272
+ .replace(/<!--[\s\S]*?-->/g, "")
273
+ .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")
274
+ .replace(/<[^>]*>/g, ""));
275
+ /**
276
+ * Find the `>` that closes a tag, ignoring `>` characters inside quoted
277
+ * attribute values.
278
+ *
279
+ * @param xml - Source XML.
280
+ * @param start - Offset of `<`.
281
+ * @returns Offset of `>`, or `-1`.
282
+ */
283
+ const findTagEnd = (xml, start) => {
284
+ let quote = "";
285
+ for (let index = start + 1; index < xml.length; index += 1) {
286
+ const char = xml[index];
287
+ if (quote.length > 0) {
288
+ if (char === quote) {
289
+ quote = "";
290
+ }
291
+ continue;
292
+ }
293
+ if (char === '"' || char === "'") {
294
+ quote = char;
295
+ continue;
296
+ }
297
+ if (char === ">") {
298
+ return index;
299
+ }
300
+ }
301
+ return -1;
302
+ };
303
+ /**
304
+ * Parse the attribute section of an open tag. Attribute values may be single or
305
+ * double quoted; entity references are decoded.
306
+ *
307
+ * @param body - Tag text between `<` and `>`, trimmed.
308
+ * @param from - Index after the tag name.
309
+ * @returns Attribute map.
310
+ */
311
+ const parseAttributes = (body, from) => {
312
+ const attrs = {};
313
+ let index = from;
314
+ while (index < body.length) {
315
+ while (index < body.length && /\s/.test(body[index])) {
316
+ index += 1;
317
+ }
318
+ if (index >= body.length || body[index] === "/") {
319
+ break;
320
+ }
321
+ const equals = body.indexOf("=", index);
322
+ if (equals === -1) {
323
+ break;
324
+ }
325
+ const name = body.slice(index, equals).trim();
326
+ let valueStart = equals + 1;
327
+ while (valueStart < body.length && /\s/.test(body[valueStart])) {
328
+ valueStart += 1;
329
+ }
330
+ const quote = body[valueStart] ?? "";
331
+ if (quote !== '"' && quote !== "'") {
332
+ break;
333
+ }
334
+ const valueEnd = body.indexOf(quote, valueStart + 1);
335
+ if (valueEnd === -1) {
336
+ break;
337
+ }
338
+ if (name.length > 0) {
339
+ attrs[name] = decodeXmlEntities(body.slice(valueStart + 1, valueEnd));
340
+ }
341
+ index = valueEnd + 1;
342
+ }
343
+ return attrs;
344
+ };
345
+ /**
346
+ * Scan XML into an ordered tag list with bounded nesting and tag count.
347
+ *
348
+ * This is intentionally not a validating parser. It recognises comments,
349
+ * processing instructions, CDATA sections and doctype declarations, handles
350
+ * quoted `>` characters and records matching close indices with a stack. A
351
+ * document that exceeds the tag or depth ceiling throws; Office callers turn
352
+ * that into a warning because a single oversized part should not discard the
353
+ * rest of the package.
354
+ *
355
+ * @param xml - XML text.
356
+ * @returns Tags with matching-close indices.
357
+ */
358
+ const scanXml = (xml) => {
359
+ const tags = [];
360
+ const closeOf = [];
361
+ const stack = [];
362
+ let depth = 0;
363
+ let index = 0;
364
+ while (index < xml.length) {
365
+ const start = xml.indexOf("<", index);
366
+ if (start === -1) {
367
+ break;
368
+ }
369
+ if (xml.startsWith("<!--", start)) {
370
+ const end = xml.indexOf("-->", start + 4);
371
+ if (end === -1) {
372
+ break;
373
+ }
374
+ index = end + 3;
375
+ continue;
376
+ }
377
+ if (xml.startsWith("<![CDATA[", start)) {
378
+ const end = xml.indexOf("]]>", start + 9);
379
+ if (end === -1) {
380
+ break;
381
+ }
382
+ index = end + 3;
383
+ continue;
384
+ }
385
+ const next = xml[start + 1] ?? "";
386
+ if (next === "!" || next === "?") {
387
+ const end = findTagEnd(xml, start);
388
+ if (end === -1) {
389
+ break;
390
+ }
391
+ index = end + 1;
392
+ continue;
393
+ }
394
+ const end = findTagEnd(xml, start);
395
+ if (end === -1) {
396
+ throw new Error("unterminated xml tag");
397
+ }
398
+ const body = xml.slice(start + 1, end).trim();
399
+ index = end + 1;
400
+ if (body.length === 0) {
401
+ continue;
402
+ }
403
+ const closing = body.startsWith("/");
404
+ const content = closing ? body.slice(1).trim() : body;
405
+ const selfClosing = !closing && content.endsWith("/");
406
+ const withoutSlash = selfClosing ? content.slice(0, -1).trimEnd() : content;
407
+ let nameEnd = 0;
408
+ while (nameEnd < withoutSlash.length && !/\s/.test(withoutSlash[nameEnd])) {
409
+ nameEnd += 1;
410
+ }
411
+ const name = withoutSlash.slice(0, nameEnd);
412
+ if (name.length === 0) {
413
+ continue;
414
+ }
415
+ const colon = name.lastIndexOf(":");
416
+ const local = colon === -1 ? name : name.slice(colon + 1);
417
+ const attrs = closing ? {} : parseAttributes(withoutSlash, nameEnd);
418
+ const tagIndex = tags.length;
419
+ tags.push({ name, local, closing, selfClosing, attrs, start, end: end + 1 });
420
+ closeOf.push(-1);
421
+ if (tags.length > MaxXmlTags) {
422
+ throw new Error("xml tag limit exceeded");
423
+ }
424
+ if (closing) {
425
+ depth = Math.max(0, depth - 1);
426
+ const open = stack.pop();
427
+ if (open !== (void 0)) {
428
+ closeOf[open] = tagIndex;
429
+ }
430
+ }
431
+ else if (!selfClosing) {
432
+ depth += 1;
433
+ if (depth > MaxXmlDepth) {
434
+ throw new Error("xml nesting limit exceeded");
435
+ }
436
+ stack.push(tagIndex);
437
+ }
438
+ }
439
+ return { tags, closeOf, unclosed: stack.length };
440
+ };
441
+ /**
442
+ * Attribute lookup with namespace-prefix fallback: an exact `r:id` wins over a
443
+ * bare `id`, and `w:val` is found when the caller only knows the local name.
444
+ *
445
+ * @param tag - Tag to inspect.
446
+ * @param name - Qualified or local attribute name.
447
+ * @returns Attribute value, or `""`.
448
+ */
449
+ const xmlAttr = (tag, name) => {
450
+ const direct = tag.attrs[name];
451
+ if (direct !== (void 0)) {
452
+ return direct;
453
+ }
454
+ const colon = name.lastIndexOf(":");
455
+ const local = colon === -1 ? name : name.slice(colon + 1);
456
+ const keys = Object.keys(tag.attrs);
457
+ for (let index = 0; index < keys.length; index += 1) {
458
+ const key = keys[index];
459
+ const keyColon = key.lastIndexOf(":");
460
+ const keyLocal = keyColon === -1 ? key : key.slice(keyColon + 1);
461
+ if (keyLocal === local) {
462
+ return tag.attrs[key];
463
+ }
464
+ }
465
+ return "";
466
+ };
467
+ /**
468
+ * Text content of one element, using its precomputed matching close.
469
+ *
470
+ * @param xml - Source XML.
471
+ * @param scan - Tag scan for the same source.
472
+ * @param index - Index of the open tag.
473
+ * @returns Decoded inner text, or `""` when the element is self-closing or
474
+ * unclosed.
475
+ */
476
+ const xmlInnerText = (xml, scan, index) => {
477
+ const tag = scan.tags[index];
478
+ if (tag.selfClosing) {
479
+ return "";
480
+ }
481
+ const close = scan.closeOf[index];
482
+ if (close < 0) {
483
+ return "";
484
+ }
485
+ return xmlText(xml.slice(tag.end, scan.tags[close].start));
486
+ };
487
+ /**
488
+ * First child element with a given local name inside an open element.
489
+ *
490
+ * @param scan - Tag scan.
491
+ * @param openIndex - Enclosing open tag.
492
+ * @param local - Local child name.
493
+ * @returns Index of the child, or `-1`.
494
+ */
495
+ const xmlChild = (scan, openIndex, local) => {
496
+ const close = scan.closeOf[openIndex];
497
+ const end = close < 0 ? scan.tags.length : close;
498
+ for (let index = openIndex + 1; index < end; index += 1) {
499
+ const tag = scan.tags[index];
500
+ if (!tag.closing && tag.local === local) {
501
+ return index;
502
+ }
503
+ }
504
+ return -1;
505
+ };
506
+ /**
507
+ * Text of the first matching child element.
508
+ *
509
+ * @param xml - Source XML.
510
+ * @param scan - Tag scan.
511
+ * @param openIndex - Enclosing open tag.
512
+ * @param local - Local child name.
513
+ * @returns Decoded text, or `""`.
514
+ */
515
+ const xmlChildText = (xml, scan, openIndex, local) => {
516
+ const child = xmlChild(scan, openIndex, local);
517
+ return child === -1 ? "" : xmlInnerText(xml, scan, child);
518
+ };
519
+ /**
520
+ * Resolve an internal OOXML relationship target against its source part.
521
+ *
522
+ * @param sourcePart - Part that owns the relationship.
523
+ * @param target - Relationship target as written.
524
+ * @returns Normalized package part name.
525
+ */
526
+ const resolvePart = (sourcePart, target) => {
527
+ if (target.startsWith("/")) {
528
+ return posix.normalize(target.slice(1)).replace(/^\.\//, "");
529
+ }
530
+ return posix.normalize(posix.join(posix.dirname(sourcePart), target)).replace(/^\.\//, "");
531
+ };
532
+ /**
533
+ * Path of the relationships part for an OOXML part.
534
+ *
535
+ * @param part - Source part name.
536
+ * @returns Relationships part name.
537
+ */
538
+ const relationshipsPart = (part) => {
539
+ const directory = posix.dirname(part);
540
+ const file = `${posix.basename(part)}.rels`;
541
+ return directory === "." ? `_rels/${file}` : `${directory}/_rels/${file}`;
542
+ };
543
+ /**
544
+ * Read a relationships part. Missing parts are normal (a minimal fixture or a
545
+ * package without links), so this returns an empty list rather than warning.
546
+ *
547
+ * @param entries - OOXML package parts.
548
+ * @param part - Source part name.
549
+ * @returns Relationships in document order.
550
+ */
551
+ const readRelationships = (entries, part) => {
552
+ const xml = zipText(entries, relationshipsPart(part));
553
+ if (xml === (void 0)) {
554
+ return [];
555
+ }
556
+ const scan = scanXml(xml);
557
+ const relationships = [];
558
+ for (let index = 0; index < scan.tags.length && relationships.length < MaxRelationshipTargets; index += 1) {
559
+ const tag = scan.tags[index];
560
+ if (tag.closing || tag.local !== "Relationship") {
561
+ continue;
562
+ }
563
+ const id = xmlAttr(tag, "Id");
564
+ const type = xmlAttr(tag, "Type");
565
+ const rawTarget = xmlAttr(tag, "Target");
566
+ if (rawTarget.length === 0) {
567
+ continue;
568
+ }
569
+ const external = xmlAttr(tag, "TargetMode") === "External";
570
+ relationships.push({
571
+ id,
572
+ type,
573
+ target: external ? rawTarget : resolvePart(part, rawTarget),
574
+ external,
575
+ });
576
+ }
577
+ return relationships;
578
+ };
579
+ /**
580
+ * Index a relationship list by id.
581
+ *
582
+ * @param relationships - Relationships to index.
583
+ * @returns Id to relationship.
584
+ */
585
+ const relationshipMap = (relationships) => {
586
+ const map = new Map();
587
+ for (let index = 0; index < relationships.length; index += 1) {
588
+ map.set(relationships[index].id, relationships[index]);
589
+ }
590
+ return map;
591
+ };
592
+ /**
593
+ * Create the empty document and its context.
594
+ *
595
+ * @param path - Root-relative path.
596
+ * @param kind - Initial family guess.
597
+ * @param format - Container format.
598
+ * @param bytes - Complete file bytes.
599
+ * @returns Context for one parse.
600
+ */
601
+ const createContext = (path, kind, format, bytes) => {
602
+ const document = {
603
+ path,
604
+ kind,
605
+ format,
606
+ bytes: BigInt(bytes.byteLength),
607
+ fingerprint: rawDigest(bytes),
608
+ sections: [],
609
+ references: [],
610
+ imports: [],
611
+ exports: [],
612
+ metadata: {},
613
+ warnings: [],
614
+ };
615
+ return {
616
+ document,
617
+ sections: document.sections,
618
+ referenceKeys: new Set(),
619
+ exportKeys: new Set(),
620
+ importKeys: new Set(),
621
+ warningKeys: new Set(),
622
+ count: 0,
623
+ truncated: false,
624
+ };
625
+ };
626
+ /**
627
+ * Add a warning, deduplicated and bounded.
628
+ *
629
+ * @param context - Parse context.
630
+ * @param message - Human-readable warning.
631
+ */
632
+ const pushWarning = (context, message) => {
633
+ const clean = message.replace(/\s+/g, " ").trim().slice(0, 240);
634
+ if (clean.length === 0 || context.warningKeys.has(clean) || context.document.warnings.length >= MaxWarnings) {
635
+ return;
636
+ }
637
+ context.warningKeys.add(clean);
638
+ context.document.warnings.push(clean);
639
+ };
640
+ /**
641
+ * Set one metadata value when it is non-empty.
642
+ *
643
+ * @param context - Parse context.
644
+ * @param key - Metadata key.
645
+ * @param value - Raw value.
646
+ */
647
+ const setMetadata = (context, key, value) => {
648
+ const clean = value.replace(/\s+/g, " ").trim().slice(0, 300);
649
+ if (clean.length > 0) {
650
+ context.document.metadata[key] = clean;
651
+ }
652
+ };
653
+ /**
654
+ * Append a section and return it, or `undefined` once the section budget is
655
+ * exhausted. Children are appended pre-order, so `index` matches document
656
+ * order.
657
+ *
658
+ * @param context - Parse context.
659
+ * @param parent - Parent section, or `undefined` for a root section.
660
+ * @param kind - Section kind.
661
+ * @param label - Raw label.
662
+ * @param depth - Hierarchy depth.
663
+ * @returns The new section, or `undefined` when truncated.
664
+ */
665
+ const appendSection = (context, parent, kind, label, depth) => {
666
+ if (context.count >= MaxSections) {
667
+ context.truncated = true;
668
+ return undefined;
669
+ }
670
+ const index = context.count;
671
+ context.count += 1;
672
+ const section = {
673
+ id: `${kind}-${index}`,
674
+ label: cleanLabel(label, `Section ${index + 1}`),
675
+ kind,
676
+ depth: Math.max(0, Math.trunc(depth)),
677
+ index,
678
+ children: [],
679
+ };
680
+ if (parent === (void 0)) {
681
+ context.sections.push(section);
682
+ }
683
+ else {
684
+ parent.children.push(section);
685
+ }
686
+ return section;
687
+ };
688
+ /**
689
+ * Add an outbound reference, deduplicated by kind/from/target.
690
+ *
691
+ * @param context - Parse context.
692
+ * @param reference - Reference to add.
693
+ */
694
+ const addReference = (context, reference) => {
695
+ if (context.document.references.length >= MaxReferences) {
696
+ return;
697
+ }
698
+ const target = cleanDetail(reference.target);
699
+ const from = cleanDetail(reference.from);
700
+ if (target.length === 0) {
701
+ return;
702
+ }
703
+ const key = `${reference.kind}\u0000${from}\u0000${target}`;
704
+ if (context.referenceKeys.has(key)) {
705
+ return;
706
+ }
707
+ context.referenceKeys.add(key);
708
+ const detail = cleanDetail(reference.detail ?? "");
709
+ context.document.references.push({
710
+ kind: reference.kind,
711
+ from,
712
+ target,
713
+ ...(detail.length === 0 ? {} : { detail }),
714
+ });
715
+ };
716
+ /**
717
+ * Add an external import target, deduplicated.
718
+ *
719
+ * @param context - Parse context.
720
+ * @param target - URL or external path.
721
+ */
722
+ const addImport = (context, target) => {
723
+ const clean = cleanDetail(target);
724
+ if (clean.length === 0 || context.importKeys.has(clean) || context.document.imports.length >= MaxImports) {
725
+ return;
726
+ }
727
+ context.importKeys.add(clean);
728
+ context.document.imports.push(clean);
729
+ };
730
+ /**
731
+ * Add an exported name, deduplicated.
732
+ *
733
+ * @param context - Parse context.
734
+ * @param name - Exported symbol or object name.
735
+ */
736
+ const addExport = (context, name) => {
737
+ const clean = cleanLabel(name, "");
738
+ if (clean.length === 0 || context.exportKeys.has(clean) || context.document.exports.length >= MaxExports) {
739
+ return;
740
+ }
741
+ context.exportKeys.add(clean);
742
+ context.document.exports.push(clean);
743
+ };
744
+ /**
745
+ * Read `docProps/core.xml` and `docProps/app.xml` into metadata.
746
+ *
747
+ * @param context - Parse context.
748
+ * @param entries - OOXML package parts.
749
+ */
750
+ const readDocumentProperties = (context, entries) => {
751
+ const core = zipText(entries, "docProps/core.xml");
752
+ if (core !== (void 0)) {
753
+ const scan = scanXml(core);
754
+ const fieldKeys = {
755
+ title: "title",
756
+ creator: "creator",
757
+ lastModifiedBy: "lastModifiedBy",
758
+ created: "created",
759
+ modified: "modified",
760
+ subject: "subject",
761
+ description: "description",
762
+ keywords: "keywords",
763
+ category: "category",
764
+ };
765
+ for (let index = 0; index < scan.tags.length; index += 1) {
766
+ const tag = scan.tags[index];
767
+ if (tag.closing || tag.selfClosing) {
768
+ continue;
769
+ }
770
+ const key = fieldKeys[tag.local];
771
+ if (key === (void 0)) {
772
+ continue;
773
+ }
774
+ setMetadata(context, key, xmlInnerText(core, scan, index));
775
+ }
776
+ }
777
+ const app = zipText(entries, "docProps/app.xml");
778
+ if (app !== (void 0)) {
779
+ const scan = scanXml(app);
780
+ const fieldKeys = {
781
+ Application: "application",
782
+ Company: "company",
783
+ AppVersion: "appVersion",
784
+ Pages: "pages",
785
+ Words: "words",
786
+ Slides: "slides",
787
+ Worksheets: "worksheets",
788
+ };
789
+ for (let index = 0; index < scan.tags.length; index += 1) {
790
+ const tag = scan.tags[index];
791
+ if (tag.closing || tag.selfClosing) {
792
+ continue;
793
+ }
794
+ const key = fieldKeys[tag.local];
795
+ if (key === (void 0)) {
796
+ continue;
797
+ }
798
+ setMetadata(context, key, xmlInnerText(app, scan, index));
799
+ }
800
+ }
801
+ };
802
+ /**
803
+ * Heading level for a Word paragraph, from an explicit outline level (which
804
+ * wins) or a built-in Heading/Title style id.
805
+ *
806
+ * @param style - Style id from `<w:pStyle w:val>`.
807
+ * @param outline - Raw outline level from `<w:outlineLvl w:val>`.
808
+ * @returns One-based heading level, or `0` for body text.
809
+ */
810
+ const wordHeadingLevel = (style, outline) => {
811
+ if (outline.length > 0) {
812
+ const value = Number.parseInt(outline, 10);
813
+ if (Number.isSafeInteger(value) && value >= 0 && value <= 8) {
814
+ return value + 1;
815
+ }
816
+ }
817
+ const heading = /^Heading\s*([1-9])$/i.exec(style.trim());
818
+ if (heading !== null) {
819
+ return Number(heading[1]);
820
+ }
821
+ return /^Title$/i.test(style.trim()) ? 1 : 0;
822
+ };
823
+ /**
824
+ * Parse `word/document.xml` into heading/paragraph sections plus bookmarks.
825
+ *
826
+ * @param context - Parse context.
827
+ * @param entries - OOXML package parts.
828
+ */
829
+ const parseWord = (context, entries) => {
830
+ const xml = zipText(entries, "word/document.xml");
831
+ if (xml === (void 0)) {
832
+ pushWarning(context, "word package has no word/document.xml");
833
+ return;
834
+ }
835
+ const scan = scanXml(xml);
836
+ const paragraphs = [];
837
+ let current;
838
+ let nested = 0;
839
+ for (let index = 0; index < scan.tags.length; index += 1) {
840
+ const tag = scan.tags[index];
841
+ if (tag.local === "p" && !tag.closing) {
842
+ if (tag.selfClosing) {
843
+ continue;
844
+ }
845
+ if (current === (void 0)) {
846
+ current = { texts: [], style: "", outline: "" };
847
+ }
848
+ else {
849
+ nested += 1;
850
+ }
851
+ continue;
852
+ }
853
+ if (tag.local === "p" && tag.closing) {
854
+ if (current === (void 0)) {
855
+ continue;
856
+ }
857
+ if (nested > 0) {
858
+ nested -= 1;
859
+ continue;
860
+ }
861
+ paragraphs.push({ text: current.texts.join(""), style: current.style, outline: current.outline });
862
+ current = undefined;
863
+ continue;
864
+ }
865
+ if (tag.local === "bookmarkStart" && !tag.closing) {
866
+ const bookmark = xmlAttr(tag, "w:name");
867
+ if (bookmark.length > 0 && bookmark !== "_GoBack") {
868
+ addExport(context, bookmark);
869
+ addReference(context, { kind: "bookmark", from: context.document.path, target: bookmark, detail: "bookmark" });
870
+ }
871
+ continue;
872
+ }
873
+ if (current === (void 0)) {
874
+ continue;
875
+ }
876
+ if (tag.local === "t" && !tag.closing) {
877
+ const text = xmlInnerText(xml, scan, index);
878
+ if (text.length > 0) {
879
+ current.texts.push(text);
880
+ }
881
+ }
882
+ else if (tag.local === "pStyle" && !tag.closing) {
883
+ current.style = xmlAttr(tag, "w:val");
884
+ }
885
+ else if (tag.local === "outlineLvl" && !tag.closing) {
886
+ current.outline = xmlAttr(tag, "w:val");
887
+ }
888
+ else if (tag.local === "hyperlink" && !tag.closing) {
889
+ const anchor = xmlAttr(tag, "w:anchor");
890
+ if (anchor.length > 0) {
891
+ addReference(context, { kind: "bookmark", from: context.document.path, target: `#${anchor}`, detail: "internal bookmark" });
892
+ }
893
+ }
894
+ }
895
+ if (current !== (void 0)) {
896
+ paragraphs.push({ text: current.texts.join(""), style: current.style, outline: current.outline });
897
+ }
898
+ const headingStack = [];
899
+ for (let index = 0; index < paragraphs.length; index += 1) {
900
+ const paragraph = paragraphs[index];
901
+ const text = cleanLabel(paragraph.text, "");
902
+ if (text.length === 0) {
903
+ continue;
904
+ }
905
+ const level = wordHeadingLevel(paragraph.style, paragraph.outline);
906
+ if (level > 0) {
907
+ while (headingStack.length > 0 && headingStack[headingStack.length - 1].level >= level) {
908
+ headingStack.pop();
909
+ }
910
+ const parent = headingStack[headingStack.length - 1];
911
+ const section = appendSection(context, parent === (void 0) ? undefined : parent.section, "heading", text, parent === (void 0) ? level - 1 : parent.section.depth + 1);
912
+ if (section !== (void 0)) {
913
+ headingStack.push({ level, section });
914
+ }
915
+ }
916
+ else {
917
+ const parent = headingStack[headingStack.length - 1];
918
+ appendSection(context, parent === (void 0) ? undefined : parent.section, "paragraph", text, parent === (void 0) ? 0 : parent.section.depth + 1);
919
+ }
920
+ }
921
+ const relationships = readRelationships(entries, "word/document.xml");
922
+ for (let index = 0; index < relationships.length; index += 1) {
923
+ const relationship = relationships[index];
924
+ const type = relationship.type.toLowerCase();
925
+ if (type.endsWith("/hyperlink")) {
926
+ addReference(context, {
927
+ kind: "hyperlink",
928
+ from: context.document.path,
929
+ target: relationship.target,
930
+ detail: relationship.external ? "external hyperlink" : "internal hyperlink",
931
+ });
932
+ }
933
+ else if (relationship.external) {
934
+ addReference(context, { kind: "external-link", from: context.document.path, target: relationship.target, detail: "external relationship" });
935
+ }
936
+ else if (type.endsWith("/image") ||
937
+ type.endsWith("/oleobject") ||
938
+ type.endsWith("/package") ||
939
+ type.endsWith("/diagramdata") ||
940
+ type.endsWith("/chart")) {
941
+ addReference(context, { kind: "embedded", from: context.document.path, target: relationship.target, detail: "embedded part" });
942
+ }
943
+ else {
944
+ continue;
945
+ }
946
+ if (relationship.external) {
947
+ addImport(context, relationship.target);
948
+ }
949
+ }
950
+ pushWarning(context, "word: main document body only; headers, footnotes, comments, revisions, text boxes and embedded objects are not expanded");
951
+ };
952
+ /**
953
+ * Read shared strings. The cell content is not projected into the outline, but
954
+ * reading the part validates it and records its size; a malformed shared-string
955
+ * part is downgraded to a warning rather than failing the workbook.
956
+ *
957
+ * @param context - Parse context.
958
+ * @param entries - OOXML package parts.
959
+ * @returns Decoded shared strings.
960
+ */
961
+ const readSharedStrings = (context, entries) => {
962
+ const xml = zipText(entries, "xl/sharedStrings.xml");
963
+ if (xml === (void 0)) {
964
+ return [];
965
+ }
966
+ try {
967
+ const scan = scanXml(xml);
968
+ const strings = [];
969
+ for (let index = 0; index < scan.tags.length && strings.length < 10000; index += 1) {
970
+ const tag = scan.tags[index];
971
+ if (tag.closing || tag.selfClosing || tag.local !== "si") {
972
+ continue;
973
+ }
974
+ const close = scan.closeOf[index];
975
+ const end = close < 0 ? scan.tags.length : close;
976
+ let text = "";
977
+ for (let child = index + 1; child < end; child += 1) {
978
+ const childTag = scan.tags[child];
979
+ if (!childTag.closing && childTag.local === "t") {
980
+ text += xmlInnerText(xml, scan, child);
981
+ }
982
+ }
983
+ strings.push(text);
984
+ }
985
+ return strings;
986
+ }
987
+ catch (error) {
988
+ pushWarning(context, `excel: shared strings ignored (${error instanceof Error ? error.message : "parse failure"})`);
989
+ return [];
990
+ }
991
+ };
992
+ /**
993
+ * Read the workbook sheet list and defined names. Sheet order is the order of
994
+ * `<sheet>` elements; each is resolved through the workbook relationships with
995
+ * a name-based fallback for minimal packages.
996
+ *
997
+ * @param context - Parse context.
998
+ * @param entries - OOXML package parts.
999
+ * @returns Sheets and defined names.
1000
+ */
1001
+ const readWorkbook = (context, entries) => {
1002
+ const xml = zipText(entries, "xl/workbook.xml");
1003
+ if (xml === (void 0)) {
1004
+ pushWarning(context, "excel package has no xl/workbook.xml");
1005
+ return { sheets: [], names: [] };
1006
+ }
1007
+ const scan = scanXml(xml);
1008
+ const relationships = relationshipMap(readRelationships(entries, "xl/workbook.xml"));
1009
+ const sheets = [];
1010
+ const names = [];
1011
+ for (let index = 0; index < scan.tags.length; index += 1) {
1012
+ const tag = scan.tags[index];
1013
+ if (tag.closing) {
1014
+ continue;
1015
+ }
1016
+ if (tag.local === "sheet") {
1017
+ const name = cleanLabel(xmlAttr(tag, "name"), `Sheet ${sheets.length + 1}`);
1018
+ const relationId = xmlAttr(tag, "r:id");
1019
+ const relationship = relationships.get(relationId);
1020
+ const fallback = `xl/worksheets/sheet${sheets.length + 1}.xml`;
1021
+ sheets.push({ name, relationId, part: relationship === (void 0) ? fallback : relationship.target });
1022
+ }
1023
+ else if (tag.local === "definedName") {
1024
+ const name = cleanLabel(xmlAttr(tag, "name"), "");
1025
+ if (name.length === 0) {
1026
+ continue;
1027
+ }
1028
+ const localRaw = xmlAttr(tag, "localSheetId");
1029
+ const localSheetIndex = localRaw.length === 0 ? -1 : Number.parseInt(localRaw, 10);
1030
+ names.push({ name, localSheetIndex: Number.isSafeInteger(localSheetIndex) ? localSheetIndex : -1, formula: xmlInnerText(xml, scan, index) });
1031
+ }
1032
+ }
1033
+ return { sheets, names };
1034
+ };
1035
+ /**
1036
+ * Replace double-quoted Excel string literals with spaces, preserving offsets
1037
+ * so reference positions in the original formula remain meaningful.
1038
+ *
1039
+ * @param formula - Formula text.
1040
+ * @returns Masked character array joined back into a string.
1041
+ */
1042
+ const maskFormulaStrings = (formula) => {
1043
+ const chars = formula.split("");
1044
+ let index = 0;
1045
+ while (index < chars.length) {
1046
+ if (chars[index] !== '"') {
1047
+ index += 1;
1048
+ continue;
1049
+ }
1050
+ chars[index] = " ";
1051
+ index += 1;
1052
+ while (index < chars.length) {
1053
+ if (chars[index] === '"') {
1054
+ if (index + 1 < chars.length && chars[index + 1] === '"') {
1055
+ chars[index] = " ";
1056
+ chars[index + 1] = " ";
1057
+ index += 2;
1058
+ continue;
1059
+ }
1060
+ chars[index] = " ";
1061
+ index += 1;
1062
+ break;
1063
+ }
1064
+ chars[index] = " ";
1065
+ index += 1;
1066
+ }
1067
+ }
1068
+ return chars.join("");
1069
+ };
1070
+ /**
1071
+ * Blank a matched span so a later, less specific regex cannot report the same
1072
+ * token as a different reference kind.
1073
+ *
1074
+ * @param chars - Mutable character array.
1075
+ * @param start - Match start.
1076
+ * @param length - Match length.
1077
+ */
1078
+ const maskSpan = (chars, start, length) => {
1079
+ for (let index = start; index < start + length && index < chars.length; index += 1) {
1080
+ chars[index] = " ";
1081
+ }
1082
+ };
1083
+ /**
1084
+ * Extract A1, Sheet!A1 and [n]Sheet!A1 references from one formula.
1085
+ *
1086
+ * External workbook references are resolved through the package's external
1087
+ * link parts when available; the unresolved `[n]` form is kept otherwise. Only
1088
+ * lexical references are reported: formulas are not evaluated, shared formulas
1089
+ * are not expanded and dynamic references are not inferred.
1090
+ *
1091
+ * @param context - Parse context.
1092
+ * @param formula - Cell formula without the leading `=`.
1093
+ * @param sheetName - Sheet that owns the cell.
1094
+ * @param address - Cell address such as `B4`.
1095
+ * @param externalTargets - External link index to target path.
1096
+ */
1097
+ const addFormulaReferences = (context, formula, sheetName, address, externalTargets) => {
1098
+ if (formula.length === 0) {
1099
+ return;
1100
+ }
1101
+ const masked = maskFormulaStrings(formula);
1102
+ const chars = masked.split("");
1103
+ const from = `${sheetName}!${address}`;
1104
+ const external = /(?:'\[(\d+)\]([^']*)'|\[(\d+)\]([A-Za-z0-9_.]+))!(\$?[A-Z]{1,3}\$?[1-9]\d*)/g;
1105
+ let match = external.exec(masked);
1106
+ while (match !== null) {
1107
+ const linkIndex = Number(match[1] ?? match[3]);
1108
+ const targetSheet = match[2] ?? match[4] ?? "";
1109
+ const targetAddress = match[5];
1110
+ const target = `[${linkIndex}]${targetSheet}!${targetAddress}`;
1111
+ addReference(context, { kind: "formula", from, target, detail: "external workbook reference" });
1112
+ const resolved = externalTargets.get(linkIndex);
1113
+ if (resolved !== (void 0)) {
1114
+ addImport(context, resolved);
1115
+ }
1116
+ maskSpan(chars, match.index, match[0].length);
1117
+ match = external.exec(masked);
1118
+ }
1119
+ const afterExternal = chars.join("");
1120
+ const sheetPattern = /(?<![\w\]'])(?:'((?:[^']|'')+)'|([A-Za-z_][A-Za-z0-9_.]*))!(\$?[A-Z]{1,3}\$?[1-9]\d*)/g;
1121
+ let sheetMatch = sheetPattern.exec(afterExternal);
1122
+ while (sheetMatch !== null) {
1123
+ const targetSheet = (sheetMatch[1] ?? sheetMatch[2] ?? "").replace(/''/g, "'");
1124
+ const targetAddress = sheetMatch[3];
1125
+ if (!targetSheet.startsWith("[")) {
1126
+ addReference(context, { kind: "formula", from, target: `${targetSheet}!${targetAddress}`, detail: "sheet reference" });
1127
+ maskSpan(chars, sheetMatch.index, sheetMatch[0].length);
1128
+ }
1129
+ sheetMatch = sheetPattern.exec(afterExternal);
1130
+ }
1131
+ const rest = chars.join("");
1132
+ const plain = /(?<![A-Za-z0-9_.$])\$?[A-Z]{1,3}\$?[1-9]\d*(?![A-Za-z0-9_(])/g;
1133
+ let plainMatch = plain.exec(rest);
1134
+ while (plainMatch !== null) {
1135
+ addReference(context, { kind: "formula", from, target: plainMatch[0], detail: "cell reference" });
1136
+ plainMatch = plain.exec(rest);
1137
+ }
1138
+ };
1139
+ /**
1140
+ * Read external-link targets. `xl/externalLinks/externalLinkN.xml` has a
1141
+ * relationships part whose external target is the linked workbook.
1142
+ *
1143
+ * @param entries - OOXML package parts.
1144
+ * @returns External link index to target.
1145
+ */
1146
+ const readExternalTargets = (entries) => {
1147
+ const targets = new Map();
1148
+ const names = Array.from(entries.keys()).filter((name) => /^xl\/externalLinks\/externalLink[0-9]+\.xml$/.test(name));
1149
+ names.sort((left, right) => {
1150
+ const leftNumber = Number(/externalLink([0-9]+)\.xml$/.exec(left)?.[1] ?? 0);
1151
+ const rightNumber = Number(/externalLink([0-9]+)\.xml$/.exec(right)?.[1] ?? 0);
1152
+ return leftNumber - rightNumber;
1153
+ });
1154
+ for (let index = 0; index < names.length; index += 1) {
1155
+ const name = names[index];
1156
+ const linkIndex = Number(/externalLink([0-9]+)\.xml$/.exec(name)?.[1] ?? names.length + 1);
1157
+ const relationships = readRelationships(entries, name);
1158
+ const external = relationships.find((relationship) => relationship.external);
1159
+ if (external !== (void 0)) {
1160
+ targets.set(linkIndex, external.target);
1161
+ }
1162
+ }
1163
+ return targets;
1164
+ };
1165
+ /**
1166
+ * Read worksheet tables through the worksheet relationships.
1167
+ *
1168
+ * @param entries - OOXML package parts.
1169
+ * @param sheetPart - Worksheet part.
1170
+ * @returns Table definitions in relationship order.
1171
+ */
1172
+ const readSheetTables = (entries, sheetPart) => {
1173
+ const relationships = readRelationships(entries, sheetPart);
1174
+ const tables = [];
1175
+ for (let index = 0; index < relationships.length; index += 1) {
1176
+ const relationship = relationships[index];
1177
+ if (!relationship.type.toLowerCase().endsWith("/table") || relationship.external) {
1178
+ continue;
1179
+ }
1180
+ const xml = zipText(entries, relationship.target);
1181
+ if (xml === (void 0)) {
1182
+ continue;
1183
+ }
1184
+ try {
1185
+ const scan = scanXml(xml);
1186
+ for (let tagIndex = 0; tagIndex < scan.tags.length; tagIndex += 1) {
1187
+ const tag = scan.tags[tagIndex];
1188
+ if (tag.closing || tag.local !== "table") {
1189
+ continue;
1190
+ }
1191
+ const name = cleanLabel(xmlAttr(tag, "name"), "");
1192
+ const displayName = cleanLabel(xmlAttr(tag, "displayName"), name);
1193
+ const ref = cleanLabel(xmlAttr(tag, "ref"), "");
1194
+ if (displayName.length > 0) {
1195
+ tables.push({ name, displayName, ref });
1196
+ }
1197
+ break;
1198
+ }
1199
+ }
1200
+ catch {
1201
+ continue;
1202
+ }
1203
+ }
1204
+ return tables;
1205
+ };
1206
+ /**
1207
+ * Parse one worksheet: formulas and hyperlinks. Cell values and shared strings
1208
+ * are deliberately not emitted as outline nodes; the sheet/table/named-range
1209
+ * hierarchy is the structural contract for this format.
1210
+ *
1211
+ * @param context - Parse context.
1212
+ * @param entries - OOXML package parts.
1213
+ * @param sheet - Sheet definition.
1214
+ * @param externalTargets - External link index to target.
1215
+ * @returns Whether the worksheet part was readable.
1216
+ */
1217
+ const parseWorksheet = (context, entries, sheet, externalTargets) => {
1218
+ const xml = zipText(entries, sheet.part);
1219
+ if (xml === (void 0)) {
1220
+ pushWarning(context, `excel: worksheet part ${sheet.part} is missing`);
1221
+ return false;
1222
+ }
1223
+ const scan = scanXml(xml);
1224
+ const relationships = relationshipMap(readRelationships(entries, sheet.part));
1225
+ for (let index = 0; index < scan.tags.length; index += 1) {
1226
+ const tag = scan.tags[index];
1227
+ if (tag.closing) {
1228
+ continue;
1229
+ }
1230
+ if (tag.local === "c") {
1231
+ const address = xmlAttr(tag, "r");
1232
+ if (!/^[A-Z]{1,3}[1-9][0-9]*$/.test(address)) {
1233
+ continue;
1234
+ }
1235
+ const formula = xmlChildText(xml, scan, index, "f");
1236
+ if (formula.length > 0) {
1237
+ addFormulaReferences(context, formula, sheet.name, address, externalTargets);
1238
+ }
1239
+ }
1240
+ else if (tag.local === "hyperlink") {
1241
+ const relationshipId = xmlAttr(tag, "r:id");
1242
+ const location = xmlAttr(tag, "location");
1243
+ const relationship = relationships.get(relationshipId);
1244
+ if (relationship !== (void 0)) {
1245
+ addReference(context, {
1246
+ kind: "hyperlink",
1247
+ from: `${sheet.name}!${xmlAttr(tag, "ref")}`,
1248
+ target: relationship.target,
1249
+ detail: relationship.external ? "external hyperlink" : "internal hyperlink",
1250
+ });
1251
+ if (relationship.external) {
1252
+ addImport(context, relationship.target);
1253
+ }
1254
+ }
1255
+ else if (location.length > 0) {
1256
+ addReference(context, {
1257
+ kind: "hyperlink",
1258
+ from: `${sheet.name}!${xmlAttr(tag, "ref")}`,
1259
+ target: `#${location}`,
1260
+ detail: "internal hyperlink",
1261
+ });
1262
+ }
1263
+ }
1264
+ }
1265
+ return true;
1266
+ };
1267
+ /**
1268
+ * Parse an OOXML spreadsheet into sheet sections with table and named-range
1269
+ * children.
1270
+ *
1271
+ * @param context - Parse context.
1272
+ * @param entries - OOXML package parts.
1273
+ */
1274
+ const parseExcel = (context, entries) => {
1275
+ const workbook = readWorkbook(context, entries);
1276
+ const sharedStrings = readSharedStrings(context, entries);
1277
+ const externalTargets = readExternalTargets(entries);
1278
+ for (let index = 0; index < externalTargets.size; index += 1) {
1279
+ const entry = Array.from(externalTargets.entries())[index];
1280
+ if (entry !== (void 0)) {
1281
+ addReference(context, { kind: "external-link", from: context.document.path, target: entry[1], detail: `external workbook [${entry[0]}]` });
1282
+ addImport(context, entry[1]);
1283
+ }
1284
+ }
1285
+ if (workbook.sheets.length === 0) {
1286
+ pushWarning(context, "excel: workbook defines no sheets");
1287
+ }
1288
+ for (let index = 0; index < workbook.sheets.length; index += 1) {
1289
+ const sheet = workbook.sheets[index];
1290
+ const sheetSection = appendSection(context, undefined, "sheet", sheet.name, 0);
1291
+ try {
1292
+ parseWorksheet(context, entries, sheet, externalTargets);
1293
+ }
1294
+ catch (error) {
1295
+ pushWarning(context, `excel: sheet ${sheet.name} could not be parsed (${error instanceof Error ? error.message : "parse failure"})`);
1296
+ }
1297
+ const tables = readSheetTables(entries, sheet.part);
1298
+ for (let tableIndex = 0; tableIndex < tables.length; tableIndex += 1) {
1299
+ const table = tables[tableIndex];
1300
+ if (sheetSection !== (void 0)) {
1301
+ appendSection(context, sheetSection, "table", table.displayName, 1);
1302
+ }
1303
+ addExport(context, table.displayName);
1304
+ }
1305
+ for (let nameIndex = 0; nameIndex < workbook.names.length; nameIndex += 1) {
1306
+ const defined = workbook.names[nameIndex];
1307
+ if (defined.localSheetIndex !== index) {
1308
+ continue;
1309
+ }
1310
+ if (sheetSection !== (void 0)) {
1311
+ appendSection(context, sheetSection, "section", defined.name, 1);
1312
+ }
1313
+ addExport(context, defined.name);
1314
+ addReference(context, { kind: "named-range", from: context.document.path, target: defined.name, detail: defined.formula });
1315
+ }
1316
+ }
1317
+ const globalNames = workbook.names.filter((name) => name.localSheetIndex < 0);
1318
+ if (globalNames.length > 0) {
1319
+ const namedSection = appendSection(context, undefined, "section", "Named ranges", 0);
1320
+ for (let index = 0; index < globalNames.length; index += 1) {
1321
+ const defined = globalNames[index];
1322
+ if (namedSection !== (void 0)) {
1323
+ appendSection(context, namedSection, "section", defined.name, 1);
1324
+ }
1325
+ addExport(context, defined.name);
1326
+ addReference(context, { kind: "named-range", from: context.document.path, target: defined.name, detail: defined.formula });
1327
+ }
1328
+ }
1329
+ setMetadata(context, "excel.sheets", String(workbook.sheets.length));
1330
+ setMetadata(context, "excel.definedNames", String(workbook.names.length));
1331
+ setMetadata(context, "excel.sharedStrings", String(sharedStrings.length));
1332
+ pushWarning(context, "excel: formulas are not evaluated; cell values remain cached; shared/dynamic/external references beyond literal A1 syntax are not expanded");
1333
+ };
1334
+ /**
1335
+ * Read the slide order through presentation relationships, with a numeric
1336
+ * part-name fallback for packages that omit the relationship part.
1337
+ *
1338
+ * @param entries - OOXML package parts.
1339
+ * @returns Slides in presentation order.
1340
+ */
1341
+ const readSlides = (entries) => {
1342
+ const xml = zipText(entries, "ppt/presentation.xml");
1343
+ const relationships = relationshipMap(readRelationships(entries, "ppt/presentation.xml"));
1344
+ const slides = [];
1345
+ if (xml !== (void 0)) {
1346
+ const scan = scanXml(xml);
1347
+ for (let index = 0; index < scan.tags.length; index += 1) {
1348
+ const tag = scan.tags[index];
1349
+ if (tag.closing || tag.local !== "sldId") {
1350
+ continue;
1351
+ }
1352
+ const relationId = xmlAttr(tag, "r:id");
1353
+ const relationship = relationships.get(relationId);
1354
+ if (relationship !== (void 0)) {
1355
+ slides.push({ relationId, part: relationship.target });
1356
+ }
1357
+ }
1358
+ }
1359
+ if (slides.length === 0) {
1360
+ const names = Array.from(entries.keys()).filter((name) => /^ppt\/slides\/slide[0-9]+\.xml$/.test(name));
1361
+ names.sort((left, right) => {
1362
+ const leftNumber = Number(/slide([0-9]+)\.xml$/.exec(left)?.[1] ?? 0);
1363
+ const rightNumber = Number(/slide([0-9]+)\.xml$/.exec(right)?.[1] ?? 0);
1364
+ return leftNumber - rightNumber;
1365
+ });
1366
+ for (let index = 0; index < names.length; index += 1) {
1367
+ slides.push({ relationId: "", part: names[index] });
1368
+ }
1369
+ }
1370
+ return slides;
1371
+ };
1372
+ /**
1373
+ * Extract slide text, title placeholders and slide relationships.
1374
+ *
1375
+ * @param context - Parse context.
1376
+ * @param entries - OOXML package parts.
1377
+ * @param slide - Slide part.
1378
+ * @param ordinal - One-based slide number.
1379
+ * @returns Nothing.
1380
+ */
1381
+ const parseSlide = (context, entries, slide, ordinal) => {
1382
+ const xml = zipText(entries, slide.part);
1383
+ if (xml === (void 0)) {
1384
+ pushWarning(context, `powerpoint: slide part ${slide.part} is missing`);
1385
+ appendSection(context, undefined, "slide", `Slide ${ordinal}`, 0);
1386
+ return;
1387
+ }
1388
+ const scan = scanXml(xml);
1389
+ const shapes = [];
1390
+ const paragraphs = [];
1391
+ let shape;
1392
+ let paragraph;
1393
+ for (let index = 0; index < scan.tags.length; index += 1) {
1394
+ const tag = scan.tags[index];
1395
+ if (tag.local === "sp") {
1396
+ if (tag.closing) {
1397
+ if (shape !== (void 0)) {
1398
+ shapes.push({ title: shape.title, paragraphs: [...shape.paragraphs] });
1399
+ }
1400
+ shape = undefined;
1401
+ paragraph = undefined;
1402
+ }
1403
+ else if (!tag.selfClosing) {
1404
+ shape = { title: false, paragraphs: [] };
1405
+ }
1406
+ continue;
1407
+ }
1408
+ if (shape !== (void 0) && tag.local === "ph" && !tag.closing) {
1409
+ const placeholder = xmlAttr(tag, "type");
1410
+ if (placeholder === "title" || placeholder === "ctrTitle") {
1411
+ shape.title = true;
1412
+ }
1413
+ }
1414
+ if (tag.local === "p" && !tag.name.startsWith("p:") && !tag.name.startsWith("p14:")) {
1415
+ if (tag.closing) {
1416
+ if (paragraph !== (void 0)) {
1417
+ const text = cleanLabel(paragraph.join(""), "");
1418
+ if (text.length > 0) {
1419
+ paragraphs.push(text);
1420
+ if (shape !== (void 0)) {
1421
+ shape.paragraphs.push(text);
1422
+ }
1423
+ }
1424
+ paragraph = undefined;
1425
+ }
1426
+ }
1427
+ else if (paragraph === (void 0) && !tag.selfClosing) {
1428
+ paragraph = [];
1429
+ }
1430
+ continue;
1431
+ }
1432
+ if (paragraph !== (void 0) && tag.local === "t" && !tag.closing && !tag.name.startsWith("p:")) {
1433
+ const text = xmlInnerText(xml, scan, index);
1434
+ if (text.length > 0) {
1435
+ paragraph.push(text);
1436
+ }
1437
+ }
1438
+ }
1439
+ if (paragraph !== (void 0)) {
1440
+ const text = cleanLabel(paragraph.join(""), "");
1441
+ if (text.length > 0) {
1442
+ paragraphs.push(text);
1443
+ }
1444
+ }
1445
+ let title = "";
1446
+ for (let index = 0; index < shapes.length; index += 1) {
1447
+ if (shapes[index].title && shapes[index].paragraphs.length > 0) {
1448
+ title = shapes[index].paragraphs.join(" ");
1449
+ break;
1450
+ }
1451
+ }
1452
+ const slideSection = appendSection(context, undefined, "slide", title.length > 0 ? title : `Slide ${ordinal}`, 0);
1453
+ if (slideSection !== (void 0)) {
1454
+ if (title.length > 0) {
1455
+ appendSection(context, slideSection, "heading", title, 1);
1456
+ }
1457
+ for (let index = 0; index < paragraphs.length; index += 1) {
1458
+ if (title.length > 0 && paragraphs[index] === title) {
1459
+ continue;
1460
+ }
1461
+ appendSection(context, slideSection, "paragraph", paragraphs[index], 1);
1462
+ }
1463
+ }
1464
+ const relationships = readRelationships(entries, slide.part);
1465
+ for (let index = 0; index < relationships.length; index += 1) {
1466
+ const relationship = relationships[index];
1467
+ const type = relationship.type.toLowerCase();
1468
+ if (type.endsWith("/hyperlink")) {
1469
+ addReference(context, {
1470
+ kind: "hyperlink",
1471
+ from: context.document.path,
1472
+ target: relationship.target,
1473
+ detail: relationship.external ? "external hyperlink" : "internal hyperlink",
1474
+ });
1475
+ }
1476
+ else if (type.endsWith("/oleobject") || type.endsWith("/package") || type.endsWith("/embedded")) {
1477
+ addReference(context, { kind: "embedded", from: context.document.path, target: relationship.target, detail: "embedded object" });
1478
+ }
1479
+ else {
1480
+ continue;
1481
+ }
1482
+ if (relationship.external) {
1483
+ addImport(context, relationship.target);
1484
+ }
1485
+ }
1486
+ };
1487
+ /**
1488
+ * Parse an OOXML presentation.
1489
+ *
1490
+ * @param context - Parse context.
1491
+ * @param entries - OOXML package parts.
1492
+ */
1493
+ const parsePowerPoint = (context, entries) => {
1494
+ const slides = readSlides(entries);
1495
+ if (slides.length === 0) {
1496
+ pushWarning(context, "powerpoint: presentation contains no slides");
1497
+ }
1498
+ for (let index = 0; index < slides.length; index += 1) {
1499
+ const fallbackNumber = Number(/slide([0-9]+)\.xml$/.exec(slides[index].part)?.[1] ?? 0);
1500
+ const ordinal = slides[index].relationId.length === 0 && Number.isSafeInteger(fallbackNumber) && fallbackNumber > 0 ? fallbackNumber : index + 1;
1501
+ try {
1502
+ parseSlide(context, entries, slides[index], ordinal);
1503
+ }
1504
+ catch (error) {
1505
+ pushWarning(context, `powerpoint: slide ${ordinal} could not be parsed (${error instanceof Error ? error.message : "parse failure"})`);
1506
+ appendSection(context, undefined, "slide", `Slide ${ordinal}`, 0);
1507
+ }
1508
+ }
1509
+ setMetadata(context, "powerpoint.slides", String(slides.length));
1510
+ pushWarning(context, "powerpoint: slide order and text placeholders only; layouts, masters, notes, charts, SmartArt and images are not expanded");
1511
+ };
1512
+ /**
1513
+ * Whether a UTF-16 code unit can be part of readable text. Surrogates are
1514
+ * excluded because the scanner works on individual code units; unpaired
1515
+ * surrogates are a strong signal of binary data.
1516
+ *
1517
+ * @param code - UTF-16 code unit.
1518
+ * @returns Whether the unit is printable.
1519
+ */
1520
+ const isPrintableCodeUnit = (code) => {
1521
+ if (code === 0x09) {
1522
+ return true;
1523
+ }
1524
+ if (code < 0x20 || code === 0x7f) {
1525
+ return false;
1526
+ }
1527
+ if (code >= 0xd800 && code <= 0xdfff) {
1528
+ return false;
1529
+ }
1530
+ if (code === 0xfeff || code === 0xfffe || code === 0xffff) {
1531
+ return false;
1532
+ }
1533
+ return true;
1534
+ };
1535
+ /**
1536
+ * Count Unicode letters without constructing a code-point array.
1537
+ *
1538
+ * @param text - Candidate text.
1539
+ * @returns Letter count.
1540
+ */
1541
+ const letterCount = (text) => {
1542
+ const matches = text.match(/\p{L}/gu);
1543
+ return matches === null ? 0 : matches.length;
1544
+ };
1545
+ /**
1546
+ * Detect ASCII bytes that were misread as UTF-16LE. When every code unit has
1547
+ * printable ASCII in both byte lanes, the "text" is an ASCII run viewed with
1548
+ * the wrong endianness (for example a Jet header), not real CJK text. Rejecting
1549
+ * this shape removes the biggest false-positive class from whole-file scans
1550
+ * without dropping legitimate UTF-16LE or CJK content.
1551
+ *
1552
+ * @param text - Candidate fragment.
1553
+ * @returns Whether every code unit looks like two printable ASCII bytes.
1554
+ */
1555
+ const looksByteSwappedAscii = (text) => {
1556
+ const chars = Array.from(text);
1557
+ if (chars.length < 4) {
1558
+ return false;
1559
+ }
1560
+ let swapped = 0;
1561
+ for (let index = 0; index < chars.length; index += 1) {
1562
+ const code = chars[index].codePointAt(0);
1563
+ if (code === (void 0) || code > 0xffff) {
1564
+ return false;
1565
+ }
1566
+ const high = code >>> 8;
1567
+ const low = code & 0xff;
1568
+ if (high >= 0x20 && high <= 0x7e && low >= 0x20 && low <= 0x7e) {
1569
+ swapped += 1;
1570
+ }
1571
+ }
1572
+ return swapped === chars.length;
1573
+ };
1574
+ /**
1575
+ * Whether a fragment is likely to be human-readable prose or a name rather
1576
+ * than binary noise. The threshold favours precision: legacy formats contain
1577
+ * many short ASCII runs inside structures, and false paragraphs are worse than
1578
+ * a missed line.
1579
+ *
1580
+ * @param text - Normalized fragment.
1581
+ * @returns Whether to keep it.
1582
+ */
1583
+ const isMeaningfulFragment = (text) => {
1584
+ if (text.length < 3) {
1585
+ return false;
1586
+ }
1587
+ if (looksByteSwappedAscii(text)) {
1588
+ return false;
1589
+ }
1590
+ const visible = text.replace(/\s/g, "").length;
1591
+ if (visible < 3) {
1592
+ return false;
1593
+ }
1594
+ const letters = letterCount(text);
1595
+ if (letters < 2 || letters / visible < 0.35) {
1596
+ return false;
1597
+ }
1598
+ const lower = text.toLowerCase();
1599
+ if (lower === "root entry" ||
1600
+ lower === "compobj" ||
1601
+ lower === "summaryinformation" ||
1602
+ lower === "documentsummaryinformation" ||
1603
+ lower.startsWith("standard jet db") ||
1604
+ lower.startsWith("standard ace db") ||
1605
+ lower.startsWith("microsoft ")) {
1606
+ return false;
1607
+ }
1608
+ return true;
1609
+ };
1610
+ /**
1611
+ * Scan UTF-16LE runs at one alignment.
1612
+ *
1613
+ * @param bytes - Stream bytes.
1614
+ * @param parity - Starting byte offset (0 or 1).
1615
+ * @param sink - Collected fragments.
1616
+ * @returns Nothing; fragments are appended to `sink`.
1617
+ */
1618
+ const scanUtf16 = (bytes, parity, sink) => {
1619
+ let runStart = -1;
1620
+ let run = "";
1621
+ const flush = () => {
1622
+ if (run.length >= 3) {
1623
+ sink.push({ text: run, offset: runStart });
1624
+ }
1625
+ runStart = -1;
1626
+ run = "";
1627
+ };
1628
+ for (let index = parity; index + 1 < bytes.length; index += 2) {
1629
+ if (sink.length >= MaxRawFragments) {
1630
+ flush();
1631
+ return;
1632
+ }
1633
+ const code = bytes[index] | (bytes[index + 1] << 8);
1634
+ if (isPrintableCodeUnit(code)) {
1635
+ if (runStart < 0) {
1636
+ runStart = index;
1637
+ }
1638
+ run += String.fromCharCode(code);
1639
+ }
1640
+ else {
1641
+ flush();
1642
+ }
1643
+ }
1644
+ flush();
1645
+ };
1646
+ /**
1647
+ * Scan printable ASCII runs.
1648
+ *
1649
+ * @param bytes - Stream bytes.
1650
+ * @param sink - Collected fragments.
1651
+ * @returns Nothing; fragments are appended to `sink`.
1652
+ */
1653
+ const scanAscii = (bytes, sink) => {
1654
+ let runStart = -1;
1655
+ let run = "";
1656
+ const flush = () => {
1657
+ if (run.length >= 3) {
1658
+ sink.push({ text: run, offset: runStart });
1659
+ }
1660
+ runStart = -1;
1661
+ run = "";
1662
+ };
1663
+ for (let index = 0; index < bytes.length; index += 1) {
1664
+ if (sink.length >= MaxRawFragments) {
1665
+ flush();
1666
+ return;
1667
+ }
1668
+ const code = bytes[index];
1669
+ if (code >= 0x20 && code <= 0x7e) {
1670
+ if (runStart < 0) {
1671
+ runStart = index;
1672
+ }
1673
+ run += String.fromCharCode(code);
1674
+ }
1675
+ else {
1676
+ flush();
1677
+ }
1678
+ }
1679
+ flush();
1680
+ };
1681
+ /**
1682
+ * Recover printable text from a binary stream. UTF-16LE is tried at both byte
1683
+ * alignments, then ASCII; fragments are ordered by their source offset and
1684
+ * deduplicated after normalization. The 500-fragment ceiling is the
1685
+ * "do not turn a binary file into a wall of noise" bound.
1686
+ *
1687
+ * @param bytes - Stream bytes.
1688
+ * @returns Ordered fragments and whether raw scanning hit its own cap.
1689
+ */
1690
+ const extractTextFragments = (bytes) => {
1691
+ const raw = [];
1692
+ scanUtf16(bytes, 0, raw);
1693
+ /**
1694
+ * The odd alignment is only a fallback. Scanning it unconditionally turns
1695
+ * every UTF-16LE string into a second, byte-swapped "CJK" duplicate that
1696
+ * passes the letter-ratio filter; that is binary noise, not document text.
1697
+ */
1698
+ if (raw.length === 0) {
1699
+ scanUtf16(bytes, 1, raw);
1700
+ }
1701
+ if (raw.length < MaxRawFragments) {
1702
+ scanAscii(bytes, raw);
1703
+ }
1704
+ const truncated = raw.length >= MaxRawFragments;
1705
+ raw.sort((left, right) => left.offset - right.offset);
1706
+ const fragments = [];
1707
+ const seen = new Set();
1708
+ for (let index = 0; index < raw.length && fragments.length < MaxTextFragments; index += 1) {
1709
+ const text = raw[index].text.replace(/\s+/g, " ").trim().slice(0, 300);
1710
+ if (!isMeaningfulFragment(text) || seen.has(text)) {
1711
+ continue;
1712
+ }
1713
+ seen.add(text);
1714
+ fragments.push({ text, offset: raw[index].offset });
1715
+ }
1716
+ return { fragments, truncated };
1717
+ };
1718
+ /**
1719
+ * Recover text from named streams, falling back to every stream when none of
1720
+ * the preferred names exist. Total scanned bytes are bounded independently of
1721
+ * the CFB stream limit.
1722
+ *
1723
+ * @param streams - Stream map from `readCfb`.
1724
+ * @param preferred - Stream names to try first.
1725
+ * @returns Ordered fragments and whether scanning was truncated.
1726
+ */
1727
+ const streamText = (streams, preferred) => {
1728
+ const selected = [];
1729
+ for (let index = 0; index < preferred.length; index += 1) {
1730
+ const stream = streams.get(preferred[index]);
1731
+ if (stream !== (void 0)) {
1732
+ selected.push(stream);
1733
+ }
1734
+ }
1735
+ if (selected.length === 0) {
1736
+ streams.forEach((stream) => {
1737
+ selected.push(stream);
1738
+ });
1739
+ }
1740
+ const fragments = [];
1741
+ let scanned = 0;
1742
+ let truncated = false;
1743
+ for (let index = 0; index < selected.length; index += 1) {
1744
+ const stream = selected[index];
1745
+ if (scanned >= MaxTextScanBytes) {
1746
+ truncated = true;
1747
+ break;
1748
+ }
1749
+ const allowed = stream.length > MaxTextScanBytes - scanned ? stream.subarray(0, MaxTextScanBytes - scanned) : stream;
1750
+ scanned += allowed.length;
1751
+ const extracted = extractTextFragments(allowed);
1752
+ if (extracted.truncated) {
1753
+ truncated = true;
1754
+ }
1755
+ for (let fragmentIndex = 0; fragmentIndex < extracted.fragments.length && fragments.length < MaxTextFragments; fragmentIndex += 1) {
1756
+ const fragment = extracted.fragments[fragmentIndex];
1757
+ if (!fragments.some((candidate) => candidate.text === fragment.text)) {
1758
+ fragments.push(fragment);
1759
+ }
1760
+ }
1761
+ }
1762
+ return { fragments, truncated };
1763
+ };
1764
+ /**
1765
+ * Add URL references recovered from legacy text. A URL is the one structured
1766
+ * relationship that survives text extraction reliably; everything else in a
1767
+ * legacy binary is reported as heuristic.
1768
+ *
1769
+ * @param context - Parse context.
1770
+ * @param fragments - Recovered fragments.
1771
+ */
1772
+ const addUrlReferences = (context, fragments) => {
1773
+ const pattern = /https?:\/\/[^\s"'<>)\]]+/g;
1774
+ for (let index = 0; index < fragments.length; index += 1) {
1775
+ let match = pattern.exec(fragments[index].text);
1776
+ while (match !== null) {
1777
+ addReference(context, { kind: "hyperlink", from: context.document.path, target: match[0], detail: "legacy hyperlink" });
1778
+ addImport(context, match[0]);
1779
+ match = pattern.exec(fragments[index].text);
1780
+ }
1781
+ }
1782
+ };
1783
+ /**
1784
+ * Build one text-oriented section with paragraph children. A section is only
1785
+ * created when there is at least one printable fragment: an empty container or
1786
+ * a stream with no recoverable text must not look like a parsed document
1787
+ * section.
1788
+ *
1789
+ * @param context - Parse context.
1790
+ * @param label - Section label.
1791
+ * @param fragments - Paragraph fragments.
1792
+ * @returns The created section, or `undefined` when there is no text or the
1793
+ * section budget is exhausted.
1794
+ */
1795
+ const appendTextSection = (context, label, fragments) => {
1796
+ if (fragments.length === 0) {
1797
+ pushWarning(context, `${label}: no printable text recovered`);
1798
+ return undefined;
1799
+ }
1800
+ const section = appendSection(context, undefined, "section", label, 0);
1801
+ if (section === (void 0)) {
1802
+ return undefined;
1803
+ }
1804
+ for (let index = 0; index < fragments.length; index += 1) {
1805
+ appendSection(context, section, "paragraph", fragments[index].text, 1);
1806
+ }
1807
+ return section;
1808
+ };
1809
+ /**
1810
+ * Read BIFF `BOUNDSHEET` records (0x0085) from an Excel workbook stream. Sheet
1811
+ * names are the only Excel structure the legacy reader claims; cells and
1812
+ * formulas are not decoded.
1813
+ *
1814
+ * @param stream - Workbook or Book stream.
1815
+ * @returns Sheet names in workbook order.
1816
+ */
1817
+ const biffSheetNames = (stream) => {
1818
+ const names = [];
1819
+ const seen = new Set();
1820
+ if (stream.length < 4) {
1821
+ return names;
1822
+ }
1823
+ const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
1824
+ let offset = 0;
1825
+ let records = 0;
1826
+ while (offset + 4 <= stream.length && records < 20000) {
1827
+ const type = view.getUint16(offset, true);
1828
+ const length = view.getUint16(offset + 2, true);
1829
+ const dataStart = offset + 4;
1830
+ if (dataStart + length > stream.length) {
1831
+ break;
1832
+ }
1833
+ if (type === 0x0085 && length >= 8) {
1834
+ const count = stream[dataStart + 6];
1835
+ const flags = stream[dataStart + 7];
1836
+ if (count > 0 && count <= 255) {
1837
+ const nameStart = dataStart + 8;
1838
+ const wide = (flags & 0x01) !== 0;
1839
+ const byteLength = wide ? count * 2 : count;
1840
+ if (nameStart + byteLength <= stream.length) {
1841
+ let name = "";
1842
+ for (let index = 0; index < count; index += 1) {
1843
+ const code = wide ? stream[nameStart + index * 2] | (stream[nameStart + index * 2 + 1] << 8) : stream[nameStart + index];
1844
+ if (code === 0 || code < 0x20) {
1845
+ break;
1846
+ }
1847
+ name += String.fromCharCode(code);
1848
+ }
1849
+ const clean = cleanLabel(name, "");
1850
+ if (clean.length > 0 && !seen.has(clean)) {
1851
+ seen.add(clean);
1852
+ names.push(clean);
1853
+ }
1854
+ }
1855
+ }
1856
+ }
1857
+ offset = dataStart + length;
1858
+ records += 1;
1859
+ }
1860
+ return names;
1861
+ };
1862
+ /**
1863
+ * Parse a legacy OLE Office document. Text is recovered from the stream that
1864
+ * carries it in each format, then split into one section per meaningful
1865
+ * fragment. No binary field is interpreted as a document object unless the
1866
+ * format's own stream names identify it.
1867
+ *
1868
+ * @param context - Parse context.
1869
+ * @param streams - CFB streams.
1870
+ * @param kind - Document family.
1871
+ */
1872
+ const parseLegacy = (context, streams, kind) => {
1873
+ if (kind === "excel") {
1874
+ const workbook = streams.get("Workbook") ?? streams.get("Book");
1875
+ const sheetNames = workbook === (void 0) ? [] : biffSheetNames(workbook);
1876
+ for (let index = 0; index < sheetNames.length; index += 1) {
1877
+ appendSection(context, undefined, "sheet", sheetNames[index], 0);
1878
+ }
1879
+ const text = streamText(streams, ["Workbook", "Book"]);
1880
+ appendTextSection(context, "Workbook text", text.fragments);
1881
+ addUrlReferences(context, text.fragments);
1882
+ if (text.truncated) {
1883
+ pushWarning(context, "excel: legacy text scan reached its fragment limit");
1884
+ }
1885
+ setMetadata(context, "excel.sheets", String(sheetNames.length));
1886
+ pushWarning(context, "excel: legacy BIFF text extraction is heuristic; cells, formulas and styles are not decoded");
1887
+ return;
1888
+ }
1889
+ if (kind === "word") {
1890
+ const text = streamText(streams, ["WordDocument"]);
1891
+ appendTextSection(context, "Word document", text.fragments);
1892
+ addUrlReferences(context, text.fragments);
1893
+ if (text.truncated) {
1894
+ pushWarning(context, "word: legacy text scan reached its fragment limit");
1895
+ }
1896
+ pushWarning(context, "word: legacy .doc text extraction is heuristic; tables, fields and formatting are not decoded");
1897
+ return;
1898
+ }
1899
+ if (kind === "powerpoint") {
1900
+ const text = streamText(streams, ["PowerPoint Document"]);
1901
+ appendTextSection(context, "PowerPoint presentation", text.fragments);
1902
+ addUrlReferences(context, text.fragments);
1903
+ if (text.truncated) {
1904
+ pushWarning(context, "powerpoint: legacy text scan reached its fragment limit");
1905
+ }
1906
+ pushWarning(context, "powerpoint: legacy .ppt text extraction is heuristic; slides, layouts and shapes are not decoded");
1907
+ return;
1908
+ }
1909
+ if (kind === "publisher") {
1910
+ const text = streamText(streams, ["Contents", "Quill", "QuillSub"]);
1911
+ appendTextSection(context, "Publisher document", text.fragments);
1912
+ addUrlReferences(context, text.fragments);
1913
+ if (text.truncated) {
1914
+ pushWarning(context, "publisher: legacy text scan reached its fragment limit");
1915
+ }
1916
+ pushWarning(context, "publisher: Publisher support is best-effort; text story order, pages, frames and layout are not decoded");
1917
+ return;
1918
+ }
1919
+ const text = streamText(streams, []);
1920
+ appendTextSection(context, "OLE streams", text.fragments);
1921
+ addUrlReferences(context, text.fragments);
1922
+ pushWarning(context, "ole: unrecognized document family; only printable text fragments were recovered");
1923
+ };
1924
+ /**
1925
+ * Access/Jet engine magic strings after the four-byte file header.
1926
+ */
1927
+ const AccessMagic = ["Standard Jet DB", "Standard ACE DB"];
1928
+ /**
1929
+ * Detect a Jet/ACE database by its header. Access files are deliberately not
1930
+ * treated as CFB: `.mdb` and `.accdb` use a page-oriented format with this
1931
+ * signature at offset 4.
1932
+ *
1933
+ * @param bytes - Candidate file bytes.
1934
+ * @returns Engine name, or `undefined` when the header is not an Access header.
1935
+ */
1936
+ const accessEngine = (bytes) => {
1937
+ if (bytes.length < 24 || bytes[0] !== 0x00 || bytes[1] !== 0x01 || bytes[2] !== 0x00 || bytes[3] !== 0x00) {
1938
+ return undefined;
1939
+ }
1940
+ const magic = asciiAt(bytes, 4, 20);
1941
+ for (let index = 0; index < AccessMagic.length; index += 1) {
1942
+ if (magic.startsWith(AccessMagic[index])) {
1943
+ return AccessMagic[index];
1944
+ }
1945
+ }
1946
+ return undefined;
1947
+ };
1948
+ /**
1949
+ * Parse an Access database on a best-effort basis. There is no page parser
1950
+ * here, so catalog names are recovered from UTF-16LE identifier-shaped string
1951
+ * runs and classified only by a conventional prefix. The warning states that
1952
+ * limitation explicitly; no table schema or query SQL is claimed.
1953
+ *
1954
+ * @param context - Parse context.
1955
+ * @param bytes - Database bytes.
1956
+ * @param engine - Detected engine magic.
1957
+ */
1958
+ const parseAccess = (context, bytes, engine) => {
1959
+ pushWarning(context, "access catalog best-effort: catalog names are recovered from string runs, not from table/page parsing");
1960
+ setMetadata(context, "access.engine", engine);
1961
+ /**
1962
+ * Skip the header and the first database page. Its ASCII engine magic and
1963
+ * fixed fields would otherwise be read as UTF-16LE and surface as catalog
1964
+ * candidates in an otherwise empty database.
1965
+ */
1966
+ const scanStart = Math.min(bytes.length, 4096);
1967
+ const scanEnd = Math.min(bytes.length, scanStart + MaxTextScanBytes);
1968
+ const scanned = extractTextFragments(bytes.subarray(scanStart, scanEnd));
1969
+ const reserved = [
1970
+ "select",
1971
+ "from",
1972
+ "where",
1973
+ "table",
1974
+ "index",
1975
+ "primary",
1976
+ "foreign",
1977
+ "database",
1978
+ "access",
1979
+ "admin",
1980
+ "system",
1981
+ "standard",
1982
+ "create",
1983
+ "insert",
1984
+ "update",
1985
+ "delete",
1986
+ "query",
1987
+ "form",
1988
+ "report",
1989
+ ];
1990
+ const candidates = [];
1991
+ const seen = new Set();
1992
+ for (let index = 0; index < scanned.fragments.length && candidates.length < 100; index += 1) {
1993
+ const name = scanned.fragments[index].text;
1994
+ if (!/^[\p{L}_][\p{L}\p{N}_ ]{1,62}$/u.test(name) || name.split(" ").length > 4) {
1995
+ continue;
1996
+ }
1997
+ const lower = name.toLowerCase();
1998
+ if (reserved.includes(lower) || lower.startsWith("msys") || lower.startsWith("~") || seen.has(lower)) {
1999
+ continue;
2000
+ }
2001
+ seen.add(lower);
2002
+ candidates.push(name);
2003
+ }
2004
+ if (candidates.length === 0) {
2005
+ pushWarning(context, "access: no catalog names could be recovered");
2006
+ }
2007
+ else {
2008
+ const catalog = appendSection(context, undefined, "section", "Access catalog (best effort)", 0);
2009
+ for (let index = 0; index < candidates.length; index += 1) {
2010
+ const kind = /^(?:qry|q_)/i.test(candidates[index]) ? "query" : "table";
2011
+ if (catalog !== (void 0)) {
2012
+ appendSection(context, catalog, kind, candidates[index], 1);
2013
+ }
2014
+ }
2015
+ }
2016
+ setMetadata(context, "access.catalogCandidates", String(candidates.length));
2017
+ if (scanned.truncated) {
2018
+ pushWarning(context, "access: catalog scan reached its fragment limit");
2019
+ }
2020
+ };
2021
+ /**
2022
+ * Determine the OOXML family from package part names when the extension is
2023
+ * unknown or absent.
2024
+ *
2025
+ * @param entries - OOXML package parts.
2026
+ * @returns Best family guess.
2027
+ */
2028
+ const kindFromParts = (entries) => {
2029
+ let word = false;
2030
+ let excel = false;
2031
+ let powerpoint = false;
2032
+ const names = Array.from(entries.keys());
2033
+ for (let index = 0; index < names.length; index += 1) {
2034
+ if (names[index].startsWith("word/")) {
2035
+ word = true;
2036
+ }
2037
+ else if (names[index].startsWith("xl/")) {
2038
+ excel = true;
2039
+ }
2040
+ else if (names[index].startsWith("ppt/")) {
2041
+ powerpoint = true;
2042
+ }
2043
+ }
2044
+ if (word) {
2045
+ return "word";
2046
+ }
2047
+ if (excel) {
2048
+ return "excel";
2049
+ }
2050
+ if (powerpoint) {
2051
+ return "powerpoint";
2052
+ }
2053
+ return "unknown";
2054
+ };
2055
+ /**
2056
+ * Determine the legacy family from CFB stream names.
2057
+ *
2058
+ * @param streams - CFB streams.
2059
+ * @returns Best family guess.
2060
+ */
2061
+ const kindFromStreams = (streams) => {
2062
+ if (streams.has("WordDocument")) {
2063
+ return "word";
2064
+ }
2065
+ if (streams.has("Workbook") || streams.has("Book")) {
2066
+ return "excel";
2067
+ }
2068
+ if (streams.has("PowerPoint Document")) {
2069
+ return "powerpoint";
2070
+ }
2071
+ if (streams.has("Contents") || streams.has("Quill") || streams.has("QuillSub")) {
2072
+ return "publisher";
2073
+ }
2074
+ return "unknown";
2075
+ };
2076
+ /**
2077
+ * Whether a byte sequence starts with a ZIP header.
2078
+ *
2079
+ * A package normally starts with a local-file header (`PK\x03\x04`), but an
2080
+ * empty archive starts with its end-of-central-directory record (`PK\x05\x06`)
2081
+ * and a spanned archive with the spanning marker (`PK\x07\x08`). All three are
2082
+ * valid ZIP starts, so all three are routed to the OOXML reader, which then
2083
+ * reports an empty package instead of "unrecognized container".
2084
+ *
2085
+ * @param bytes - Candidate bytes.
2086
+ * @returns Whether the ZIP magic is present.
2087
+ */
2088
+ const hasZipMagic = (bytes) => bytes.length >= 4 &&
2089
+ bytes[0] === 0x50 &&
2090
+ bytes[1] === 0x4b &&
2091
+ ((bytes[2] === 0x03 && bytes[3] === 0x04) || (bytes[2] === 0x05 && bytes[3] === 0x06) || (bytes[2] === 0x07 && bytes[3] === 0x08));
2092
+ /**
2093
+ * Whether a byte sequence starts with the CFB signature.
2094
+ *
2095
+ * @param bytes - Candidate bytes.
2096
+ * @returns Whether the CFB magic is present.
2097
+ */
2098
+ const hasCfbMagic = (bytes) => bytes.length >= 8 &&
2099
+ bytes[0] === 0xd0 &&
2100
+ bytes[1] === 0xcf &&
2101
+ bytes[2] === 0x11 &&
2102
+ bytes[3] === 0xe0 &&
2103
+ bytes[4] === 0xa1 &&
2104
+ bytes[5] === 0xb1 &&
2105
+ bytes[6] === 0x1a &&
2106
+ bytes[7] === 0xe1;
2107
+ /**
2108
+ * Parse Office bytes that have already been read and validated by a caller.
2109
+ *
2110
+ * The container level is decided by magic, not extension: a `.docx` that is
2111
+ * really a CFB file is still treated as a CFB container, and a ZIP with an
2112
+ * unhelpful extension is sniffed from its parts. Extension only disambiguates
2113
+ * the document family inside a valid container.
2114
+ *
2115
+ * @param path - Root-relative path used in references and IDs.
2116
+ * @param bytes - Complete file bytes.
2117
+ * @returns Best-effort document.
2118
+ */
2119
+ const parseOfficeBytes = (path, bytes) => {
2120
+ const extensionKind = kindFromPath(path);
2121
+ const format = hasZipMagic(bytes) ? "ooxml" : hasCfbMagic(bytes) || accessEngine(bytes) !== (void 0) ? "ole" : "unknown";
2122
+ const context = createContext(path, extensionKind, format, bytes);
2123
+ try {
2124
+ if (format === "ooxml") {
2125
+ let entries;
2126
+ try {
2127
+ entries = readZipEntries(bytes);
2128
+ }
2129
+ catch (error) {
2130
+ if (isFilesystemError(error) && error.code === "quota_exceeded") {
2131
+ throw error;
2132
+ }
2133
+ pushWarning(context, `ooxml container rejected: ${error instanceof Error ? error.message : "parse failure"}`);
2134
+ }
2135
+ if (entries !== (void 0)) {
2136
+ const kind = extensionKind === "unknown" ? kindFromParts(entries) : extensionKind;
2137
+ context.document.kind = kind;
2138
+ try {
2139
+ readDocumentProperties(context, entries);
2140
+ }
2141
+ catch (error) {
2142
+ pushWarning(context, `office properties ignored (${error instanceof Error ? error.message : "parse failure"})`);
2143
+ }
2144
+ try {
2145
+ if (kind === "word") {
2146
+ parseWord(context, entries);
2147
+ }
2148
+ else if (kind === "excel") {
2149
+ parseExcel(context, entries);
2150
+ }
2151
+ else if (kind === "powerpoint") {
2152
+ parsePowerPoint(context, entries);
2153
+ }
2154
+ else {
2155
+ pushWarning(context, "ooxml package does not contain word/, xl/ or ppt/ parts");
2156
+ }
2157
+ }
2158
+ catch (error) {
2159
+ pushWarning(context, `ooxml ${kind} structure ignored (${error instanceof Error ? error.message : "parse failure"})`);
2160
+ }
2161
+ }
2162
+ else {
2163
+ context.document.kind = extensionKind;
2164
+ }
2165
+ }
2166
+ else if (format === "ole") {
2167
+ const engine = accessEngine(bytes);
2168
+ if (engine !== (void 0)) {
2169
+ context.document.kind = "access";
2170
+ parseAccess(context, bytes, engine);
2171
+ }
2172
+ else {
2173
+ const cfb = readCfb(bytes);
2174
+ for (let index = 0; index < cfb.warnings.length; index += 1) {
2175
+ pushWarning(context, cfb.warnings[index]);
2176
+ }
2177
+ const kind = extensionKind === "unknown" ? kindFromStreams(cfb.streams) : extensionKind;
2178
+ context.document.kind = kind;
2179
+ if (cfb.streams.size === 0) {
2180
+ pushWarning(context, "ole container has no readable streams");
2181
+ }
2182
+ else {
2183
+ try {
2184
+ parseLegacy(context, cfb.streams, kind);
2185
+ }
2186
+ catch (error) {
2187
+ pushWarning(context, `ole ${kind} structure ignored (${error instanceof Error ? error.message : "parse failure"})`);
2188
+ }
2189
+ }
2190
+ }
2191
+ }
2192
+ else {
2193
+ context.document.kind = "unknown";
2194
+ pushWarning(context, "unrecognized office container; no structure was extracted");
2195
+ }
2196
+ }
2197
+ catch (error) {
2198
+ if (isFilesystemError(error)) {
2199
+ throw error;
2200
+ }
2201
+ pushWarning(context, `office parse stopped: ${error instanceof Error ? error.message : "unknown failure"}`);
2202
+ }
2203
+ if (context.truncated) {
2204
+ pushWarning(context, `office section limit ${MaxSections} reached; remaining structure was not parsed`);
2205
+ }
2206
+ return context.document;
2207
+ };
2208
+ /**
2209
+ * Parse one Office document, reading its bytes through the session sandbox.
2210
+ *
2211
+ * A file larger than 64 MiB is refused with `quota_exceeded` before it is read.
2212
+ * Path resolution, symlink checks and file-type checks are delegated to
2213
+ * `PathSandbox`, so this parser cannot read outside the session roots.
2214
+ *
2215
+ * @param sandbox - Session sandbox that owns path resolution.
2216
+ * @param path - Root-relative POSIX path.
2217
+ * @returns Best-effort document structure and warnings.
2218
+ */
2219
+ export const parseOffice = (sandbox, path) => {
2220
+ const rooted = sandbox.resolve(path, "read");
2221
+ sandbox.revalidate(rooted);
2222
+ const stat = statOrFail(rooted.absolute);
2223
+ if (!stat.isFile()) {
2224
+ return fail("not_file");
2225
+ }
2226
+ if (stat.size > OfficeMaxDocumentBytes) {
2227
+ return fail("quota_exceeded");
2228
+ }
2229
+ sandbox.revalidate(rooted);
2230
+ const bytes = readBytesOrFail(rooted.absolute);
2231
+ if (bytes.byteLength > OfficeMaxDocumentBytes) {
2232
+ return fail("quota_exceeded");
2233
+ }
2234
+ return parseOfficeBytes(rooted.relative, bytes);
2235
+ };
2236
+ /**
2237
+ * Map an Office section kind to the closest existing outline kind. The outline
2238
+ * vocabulary predates Office and has no `sheet`, `slide`, `table`, `query`,
2239
+ * `form` or `report` values, so those become `section` rather than inventing
2240
+ * enum members outside the shared type.
2241
+ *
2242
+ * @param kind - Office section kind.
2243
+ * @returns Outline kind.
2244
+ */
2245
+ const outlineKindOf = (kind) => {
2246
+ if (kind === "heading") {
2247
+ return "heading";
2248
+ }
2249
+ if (kind === "paragraph") {
2250
+ return "paragraph";
2251
+ }
2252
+ return "section";
2253
+ };
2254
+ /**
2255
+ * Number of sections in one subtree, including the subtree root.
2256
+ *
2257
+ * @param section - Office section.
2258
+ * @returns Subtree section count.
2259
+ */
2260
+ const countSections = (section) => {
2261
+ let total = 1;
2262
+ for (let index = 0; index < section.children.length; index += 1) {
2263
+ total += countSections(section.children[index]);
2264
+ }
2265
+ return total;
2266
+ };
2267
+ /**
2268
+ * Highest document ordinal in one subtree. A parent's range ends at the last
2269
+ * ordinal of its deepest descendant, which keeps ranges nested and monotonic.
2270
+ *
2271
+ * @param section - Office section.
2272
+ * @returns Highest ordinal in the subtree.
2273
+ */
2274
+ const lastSectionIndex = (section) => {
2275
+ let last = section.index;
2276
+ for (let index = 0; index < section.children.length; index += 1) {
2277
+ last = Math.max(last, lastSectionIndex(section.children[index]));
2278
+ }
2279
+ return last;
2280
+ };
2281
+ /**
2282
+ * Build outline nodes from Office sections.
2283
+ *
2284
+ * Ranges are synthetic because Office formats expose neither text lines nor
2285
+ * part offsets. Two monotonic projections keep the tree usable:
2286
+ *
2287
+ * - `startLine`/`endLine` are the section's document ordinals: `index + 1` up
2288
+ * to the highest ordinal in its subtree, so a parent covers its children and
2289
+ * siblings never overlap;
2290
+ * - `startByte`/`endByte` project the same ordinal interval onto the container
2291
+ * size, so the sections partition the file exactly once and a parent still
2292
+ * covers its subtree.
2293
+ *
2294
+ * Both are explicitly approximate: they answer "where in document order", not
2295
+ * "at which OOXML part offset". The path is `"<file>#<section-id>"` and every
2296
+ * node is a symbol, not a file anchor.
2297
+ *
2298
+ * The raw-byte entry point enforces `OfficeMaxOutlineBytes` itself so a direct
2299
+ * caller sees `quota_exceeded` rather than handing a container to a text
2300
+ * charset detector (which would report `binary_file`). `buildOutline` applies
2301
+ * the same ceiling before it reads the file.
2302
+ *
2303
+ * @param input - Path and complete container bytes.
2304
+ * @returns Outline nodes in document order.
2305
+ */
2306
+ export const officeOutlineParser = (input) => {
2307
+ if (input.bytes.byteLength > OfficeMaxOutlineBytes) {
2308
+ return fail("quota_exceeded");
2309
+ }
2310
+ const document = parseOfficeBytes(input.path, input.bytes);
2311
+ const totalBytes = input.bytes.byteLength;
2312
+ let sectionCount = 0;
2313
+ for (let index = 0; index < document.sections.length; index += 1) {
2314
+ sectionCount += countSections(document.sections[index]);
2315
+ }
2316
+ const byteAt = (ordinal) => sectionCount === 0 ? 0n : (BigInt(totalBytes) * BigInt(Math.max(0, Math.min(ordinal, sectionCount)))) / BigInt(sectionCount);
2317
+ const build = (section) => {
2318
+ const last = lastSectionIndex(section);
2319
+ const children = [];
2320
+ for (let index = 0; index < section.children.length; index += 1) {
2321
+ children.push(build(section.children[index]));
2322
+ }
2323
+ const node = {
2324
+ isFile: false,
2325
+ isDirectory: false,
2326
+ path: `${input.path}#${section.id}`,
2327
+ type: outlineKindOf(section.kind),
2328
+ range: {
2329
+ startByte: byteAt(section.index),
2330
+ endByte: byteAt(last + 1),
2331
+ startLine: BigInt(section.index + 1),
2332
+ endLine: BigInt(last + 1),
2333
+ },
2334
+ label: section.label,
2335
+ depth: section.depth,
2336
+ children,
2337
+ };
2338
+ return node;
2339
+ };
2340
+ const nodes = [];
2341
+ for (let index = 0; index < document.sections.length; index += 1) {
2342
+ nodes.push(build(document.sections[index]));
2343
+ }
2344
+ return nodes;
2345
+ };
2346
+ /**
2347
+ * Install the Office outline extractor into the shared strategy registry.
2348
+ * Idempotent: repeated calls leave the same parser registered.
2349
+ */
2350
+ export const installOfficeOutlineParser = () => {
2351
+ if (!hasOfficeOutlineParser()) {
2352
+ registerOfficeOutlineParser(officeOutlineParser);
2353
+ }
2354
+ };
2355
+ /**
2356
+ * External targets of a parsed document, for dependency/import consumers.
2357
+ *
2358
+ * @param document - Parsed Office document.
2359
+ * @returns A copy of the import list.
2360
+ */
2361
+ export const officeImports = (document) => [...document.imports];
2362
+ /**
2363
+ * Exported names of a parsed document, for export consumers.
2364
+ *
2365
+ * @param document - Parsed Office document.
2366
+ * @returns A copy of the export list.
2367
+ */
2368
+ export const officeExports = (document) => [...document.exports];
2369
+ /**
2370
+ * Outbound references of a parsed document, for dependency consumers.
2371
+ *
2372
+ * @param document - Parsed Office document.
2373
+ * @returns A copy of the reference list.
2374
+ */
2375
+ export const officeReferences = (document) => document.references.map((reference) => ({ ...reference }));