pi-harness-runtime 1.1.439 → 1.1.441

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 (359) hide show
  1. package/harness/e2e/auto-test-loop.ts +8 -2
  2. package/harness/e2e/browser-scrapers/base-browser-scraper.ts +2 -0
  3. package/harness/e2e/browser-scrapers/chrome-profile-manager.ts +2 -0
  4. package/harness/e2e/browser-scrapers/codex-browser-scraper.ts +2 -0
  5. package/harness/e2e/tools-detector.ts +2 -0
  6. package/harness/index.ts +2 -1
  7. package/harness/key-loader.ts +2 -0
  8. package/harness/langchain/checkpointer.ts +2 -0
  9. package/harness/langchain/cron-cli.ts +2 -0
  10. package/harness/langchain/graph.ts +2 -0
  11. package/harness/master-planner.ts +1 -1
  12. package/harness/notification-center.ts +2 -0
  13. package/harness/output-limit-handler.ts +1 -1
  14. package/harness/project-detector/detector.ts +2 -4
  15. package/harness/secret-provider.ts +2 -0
  16. package/harness/task-graph.ts +460 -113
  17. package/index.ts +16 -7
  18. package/package.json +7 -1
  19. package/packages/a2a-adapter/package.json +1 -1
  20. package/packages/agent-policy/dist/extension.d.ts.map +1 -1
  21. package/packages/agent-policy/dist/extension.js +2 -0
  22. package/packages/agent-policy/dist/extension.js.map +1 -1
  23. package/packages/agent-policy/package.json +1 -1
  24. package/packages/agent-policy/src/extension.ts +2 -0
  25. package/packages/architecture-generator/package.json +1 -1
  26. package/packages/auth/dist/minimax-browser-auth.d.ts.map +1 -1
  27. package/packages/auth/dist/minimax-browser-auth.js +23 -1
  28. package/packages/auth/dist/minimax-browser-auth.js.map +1 -1
  29. package/packages/auth/package.json +1 -1
  30. package/packages/auth/src/minimax-browser-auth.ts +30 -5
  31. package/packages/autonomous-refactor/package.json +1 -1
  32. package/packages/autonomous-runtime/dist/inbox.d.ts.map +1 -1
  33. package/packages/autonomous-runtime/dist/inbox.js +2 -0
  34. package/packages/autonomous-runtime/dist/inbox.js.map +1 -1
  35. package/packages/autonomous-runtime/package.json +1 -1
  36. package/packages/autonomous-runtime/src/inbox.ts +2 -0
  37. package/packages/cache-strategy/package.json +1 -1
  38. package/packages/capability-registry/package.json +1 -1
  39. package/packages/checkpoint/package.json +1 -1
  40. package/packages/cli-plugin-sdk/package.json +1 -1
  41. package/packages/clipboard/dist/src/clipboard.d.ts.map +1 -1
  42. package/packages/clipboard/dist/src/clipboard.js +2 -0
  43. package/packages/clipboard/dist/src/clipboard.js.map +1 -1
  44. package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -1
  45. package/packages/clipboard/dist/src/copy-sync.js +2 -0
  46. package/packages/clipboard/dist/src/copy-sync.js.map +1 -1
  47. package/packages/clipboard/dist/src/gist-relay.d.ts.map +1 -1
  48. package/packages/clipboard/dist/src/gist-relay.js +2 -0
  49. package/packages/clipboard/dist/src/gist-relay.js.map +1 -1
  50. package/packages/clipboard/package.json +1 -1
  51. package/packages/clipboard/src/clipboard.ts +2 -0
  52. package/packages/clipboard/src/copy-sync.ts +2 -0
  53. package/packages/clipboard/src/gist-relay.ts +2 -0
  54. package/packages/clipboard-plugin/dist/types.d.ts +9 -2
  55. package/packages/clipboard-plugin/dist/types.d.ts.map +1 -1
  56. package/packages/clipboard-plugin/package.json +1 -1
  57. package/packages/clipboard-plugin/src/types.ts +9 -2
  58. package/packages/code-generation/package.json +1 -1
  59. package/packages/code-review/package.json +1 -1
  60. package/packages/codex-adapter/package.json +1 -1
  61. package/packages/codex-watcher/package.json +1 -1
  62. package/packages/config-capture/package.json +1 -1
  63. package/packages/connection-runtime/package.json +1 -1
  64. package/packages/context-compiler/package.json +1 -1
  65. package/packages/context-discovery/package.json +1 -1
  66. package/packages/context-manager/package.json +1 -1
  67. package/packages/contracts/dist/delegation-store.js +1 -4
  68. package/packages/contracts/dist/workspace-env.js +28 -1
  69. package/packages/contracts/package.json +1 -1
  70. package/packages/contracts/src/delegation-store.ts +9 -4
  71. package/packages/contracts/src/workspace-env.ts +29 -4
  72. package/packages/cookie-sanitizer/package.json +1 -1
  73. package/packages/cost-optimizer/package.json +1 -1
  74. package/packages/dependency-analyzer/package.json +1 -1
  75. package/packages/django-plugin/package.json +1 -1
  76. package/packages/doc-generator/package.json +1 -1
  77. package/packages/durable-review/package.json +1 -1
  78. package/packages/e2e-tester/package.json +1 -1
  79. package/packages/evaluation-engine/package.json +1 -1
  80. package/packages/evaluation-runner/package.json +1 -1
  81. package/packages/event-bus/package.json +1 -1
  82. package/packages/event-sink/package.json +1 -1
  83. package/packages/event-store/dist/src/store.d.ts.map +1 -1
  84. package/packages/event-store/dist/src/store.js +2 -0
  85. package/packages/event-store/dist/src/store.js.map +1 -1
  86. package/packages/event-store/package.json +1 -1
  87. package/packages/event-store/src/store.ts +3 -0
  88. package/packages/experience-replay/package.json +1 -1
  89. package/packages/feedback-collector/package.json +1 -1
  90. package/packages/file-copy-helper/package.json +1 -1
  91. package/packages/framework-detector/package.json +1 -1
  92. package/packages/framework-plugin-sdk/package.json +1 -1
  93. package/packages/frappe-plugin/package.json +1 -1
  94. package/packages/generic-web-plugin/dist/analyzer.d.ts.map +1 -1
  95. package/packages/generic-web-plugin/dist/analyzer.js +2 -0
  96. package/packages/generic-web-plugin/dist/analyzer.js.map +1 -1
  97. package/packages/generic-web-plugin/package.json +1 -1
  98. package/packages/generic-web-plugin/src/analyzer.ts +3 -0
  99. package/packages/health-monitor/package.json +1 -1
  100. package/packages/intent-analyzer/package.json +1 -1
  101. package/packages/jev-judge/package.json +1 -1
  102. package/packages/knowledge-graph/package.json +1 -1
  103. package/packages/knowledge-retrieval/dist/mcp-client.d.ts.map +1 -1
  104. package/packages/knowledge-retrieval/dist/mcp-client.js +0 -1
  105. package/packages/knowledge-retrieval/dist/mcp-client.js.map +1 -1
  106. package/packages/knowledge-retrieval/package.json +1 -4
  107. package/packages/knowledge-retrieval/src/mcp-client.ts +0 -1
  108. package/packages/laravel-plugin/package.json +1 -1
  109. package/packages/learning-engine/package.json +1 -1
  110. package/packages/logging/package.json +1 -1
  111. package/packages/mcp-adapter/dist/server.d.ts.map +1 -1
  112. package/packages/mcp-adapter/dist/server.js +2 -0
  113. package/packages/mcp-adapter/dist/server.js.map +1 -1
  114. package/packages/mcp-adapter/package.json +1 -1
  115. package/packages/mcp-adapter/src/server.ts +3 -0
  116. package/packages/memory-engine/package.json +1 -1
  117. package/packages/milestone-manager/package.json +1 -1
  118. package/packages/model-registry/package.json +1 -1
  119. package/packages/nextjs-plugin/package.json +1 -1
  120. package/packages/notification/package.json +1 -1
  121. package/packages/observability/package.json +1 -1
  122. package/packages/okf-indexer/package.json +1 -1
  123. package/packages/paperclip-integration/dist/index.d.ts.map +1 -1
  124. package/packages/paperclip-integration/dist/index.js +2 -0
  125. package/packages/paperclip-integration/dist/index.js.map +1 -1
  126. package/packages/paperclip-integration/package.json +1 -1
  127. package/packages/paperclip-integration/src/index.ts +2 -0
  128. package/packages/performance-optimizer/package.json +1 -1
  129. package/packages/permission-mode/package.json +1 -1
  130. package/packages/privilege-broker/dist/audit.d.ts.map +1 -1
  131. package/packages/privilege-broker/dist/audit.js +2 -0
  132. package/packages/privilege-broker/dist/audit.js.map +1 -1
  133. package/packages/privilege-broker/package.json +1 -1
  134. package/packages/privilege-broker/src/audit.ts +2 -0
  135. package/packages/project-analyzer/package.json +1 -1
  136. package/packages/project-bootstrap/package.json +1 -1
  137. package/packages/projection-engine/package.json +1 -1
  138. package/packages/prompt-compiler/dist/compiler.d.ts.map +1 -1
  139. package/packages/prompt-compiler/dist/compiler.js +2 -0
  140. package/packages/prompt-compiler/dist/compiler.js.map +1 -1
  141. package/packages/prompt-compiler/package.json +1 -1
  142. package/packages/prompt-compiler/src/compiler.ts +2 -0
  143. package/packages/prompt-versioning/package.json +1 -1
  144. package/packages/provider-adapter-sdk/package.json +1 -1
  145. package/packages/provider-router/package.json +1 -1
  146. package/packages/provider-selector/package.json +1 -1
  147. package/packages/providers/package.json +1 -1
  148. package/packages/qdrant-skills/package.json +1 -1
  149. package/packages/quota-manager/package.json +1 -1
  150. package/packages/rate-limiter/package.json +1 -1
  151. package/packages/react-vite-plugin/package.json +1 -1
  152. package/packages/relay/package.json +1 -1
  153. package/packages/release-manager/package.json +1 -1
  154. package/packages/requirement-compiler/package.json +1 -1
  155. package/packages/runtime/package.json +1 -1
  156. package/packages/scheduler/package.json +1 -1
  157. package/packages/scheduler-adapter/package.json +1 -1
  158. package/packages/session/package.json +1 -1
  159. package/packages/session-api/package.json +1 -1
  160. package/packages/session-export/package.json +1 -1
  161. package/packages/shared-context/package.json +1 -1
  162. package/packages/skill-mcp-client/dist/client.d.ts.map +1 -1
  163. package/packages/skill-mcp-client/dist/client.js +0 -1
  164. package/packages/skill-mcp-client/dist/client.js.map +1 -1
  165. package/packages/skill-mcp-client/package.json +1 -4
  166. package/packages/skill-mcp-client/src/client.ts +0 -1
  167. package/packages/skill-registry/package.json +1 -1
  168. package/packages/skills/dist/parser.d.ts.map +1 -1
  169. package/packages/skills/dist/parser.js +2 -0
  170. package/packages/skills/dist/parser.js.map +1 -1
  171. package/packages/skills/dist/skill-versioning.d.ts.map +1 -1
  172. package/packages/skills/dist/skill-versioning.js +2 -0
  173. package/packages/skills/dist/skill-versioning.js.map +1 -1
  174. package/packages/skills/package.json +1 -1
  175. package/packages/skills/src/parser.ts +3 -0
  176. package/packages/skills/src/skill-versioning.ts +2 -0
  177. package/packages/sprint-planner/package.json +1 -1
  178. package/packages/subscription-engine/package.json +1 -1
  179. package/packages/task-compiler/package.json +1 -1
  180. package/packages/tencentdb-memory/package.json +1 -1
  181. package/packages/tencentdb-sync/package.json +1 -1
  182. package/packages/test-data-generator/package.json +1 -1
  183. package/packages/test-generator/package.json +1 -1
  184. package/packages/todo-bd-sync/package.json +1 -1
  185. package/packages/token-estimation/package.json +1 -1
  186. package/packages/token-optimizer/package.json +1 -1
  187. package/packages/tooling-preference/package.json +1 -1
  188. package/packages/trajectory/package.json +1 -1
  189. package/packages/tui/package.json +1 -1
  190. package/packages/two-phase-review/package.json +1 -1
  191. package/packages/types/package.json +1 -1
  192. package/packages/voice-api/package.json +1 -1
  193. package/packages/webmcp-tester/dist/index.js +0 -2
  194. package/packages/webmcp-tester/package.json +1 -1
  195. package/packages/webmcp-tester/src/index.ts +0 -3
  196. package/packages/wiki/package.json +1 -1
  197. package/packages/workflow-events/package.json +1 -1
  198. package/packages/workspace-scanner/package.json +1 -1
  199. package/packages/worktree/package.json +1 -1
  200. package/packages/write-review/dist/trigger.d.ts.map +1 -1
  201. package/packages/write-review/dist/trigger.js +1 -2
  202. package/packages/write-review/dist/trigger.js.map +1 -1
  203. package/packages/write-review/package.json +1 -1
  204. package/packages/write-review/src/trigger.ts +1 -2
  205. package/scripts/lib/runtime-imports.ts +189 -3
  206. package/scripts/release-all.ts +5 -3
  207. package/scripts/telegram-webhook-server.ts +2 -0
  208. package/scripts/typecheck-packages.mjs +7 -4
  209. package/scripts/verify-package-contents.ts +161 -2
  210. package/harness/e2e/tools-detector.test.ts +0 -156
  211. package/harness/glm-quota-countdown.test.ts +0 -323
  212. package/harness/langchain/agents.test.ts +0 -39
  213. package/harness/langchain/checkpointer.test.ts +0 -243
  214. package/harness/langchain/daemon.test.ts +0 -459
  215. package/harness/langchain/model-router.test.ts +0 -191
  216. package/harness/langchain/ping-pong-middleware.test.ts +0 -69
  217. package/harness/langchain/scoreboard.test.ts +0 -245
  218. package/harness/langchain/status-line.test.ts +0 -106
  219. package/harness/langchain/surge.test.ts +0 -467
  220. package/harness/langchain/widget.test.ts +0 -251
  221. package/harness/skill-sync.test.ts +0 -57
  222. package/harness/ssh-detach-interceptor.test.ts +0 -263
  223. package/harness/ssh-hang-recovery.test.ts +0 -86
  224. package/packages/a2a-adapter/test/a2a.test.ts +0 -151
  225. package/packages/agent-policy/test/interlock.test.ts +0 -101
  226. package/packages/agent-policy/test/policy-retention.test.ts +0 -380
  227. package/packages/agent-policy/test/policy.test.ts +0 -387
  228. package/packages/auth/test/auth.test.ts +0 -238
  229. package/packages/autonomous-refactor/test/refactor.test.ts +0 -202
  230. package/packages/autonomous-runtime/test/types.test.ts +0 -95
  231. package/packages/cache-strategy/test/cache.test.ts +0 -137
  232. package/packages/capability-registry/test/registry.test.ts +0 -204
  233. package/packages/checkpoint/test/engine.test.ts +0 -265
  234. package/packages/cli-plugin-sdk/test/cli.test.ts +0 -45
  235. package/packages/code-generation/test/generator.test.ts +0 -238
  236. package/packages/code-review/test/engine.test.ts +0 -302
  237. package/packages/codex-adapter/test/provider.test.ts +0 -142
  238. package/packages/codex-watcher/test/watch.test.ts +0 -453
  239. package/packages/context-compiler/test/context-compiler.test.ts +0 -746
  240. package/packages/context-manager/test/context.test.ts +0 -198
  241. package/packages/contracts/test/delegation.test.ts +0 -350
  242. package/packages/contracts/test/workspace-env.test.ts +0 -802
  243. package/packages/cookie-sanitizer/test/infer.test.ts +0 -110
  244. package/packages/cookie-sanitizer/test/normalize.test.ts +0 -113
  245. package/packages/cookie-sanitizer/test/parse.test.ts +0 -204
  246. package/packages/cookie-sanitizer/test/sync.test.ts +0 -237
  247. package/packages/cookie-sanitizer/test/watcher.test.ts +0 -27
  248. package/packages/cost-optimizer/test/cost-optimizer.test.ts +0 -438
  249. package/packages/dependency-analyzer/test/analyzer.test.ts +0 -49
  250. package/packages/django-plugin/test/analyzer.test.ts +0 -195
  251. package/packages/doc-generator/test/docs.test.ts +0 -168
  252. package/packages/durable-review/test/acceptance.test.ts +0 -357
  253. package/packages/durable-review/test/smoke.test.ts +0 -107
  254. package/packages/evaluation-engine/test/engine.test.ts +0 -277
  255. package/packages/evaluation-runner/test/runner.test.ts +0 -168
  256. package/packages/event-bus/test/bus.test.ts +0 -100
  257. package/packages/event-store/test/store.test.ts +0 -115
  258. package/packages/experience-replay/test/replay.test.ts +0 -267
  259. package/packages/feedback-collector/test/collector.test.ts +0 -142
  260. package/packages/framework-detector/test/detector.test.ts +0 -75
  261. package/packages/framework-plugin-sdk/test/manager.test.ts +0 -320
  262. package/packages/frappe-plugin/test/analyzer.test.ts +0 -250
  263. package/packages/generic-web-plugin/test/analyzer.test.ts +0 -134
  264. package/packages/health-monitor/test/health.test.ts +0 -231
  265. package/packages/intent-analyzer/test/analyzer.test.ts +0 -72
  266. package/packages/intent-analyzer/test/ping-pong.test.ts +0 -96
  267. package/packages/jev-judge/tests/auto-continue-context.test.ts +0 -280
  268. package/packages/jev-judge/tests/continuation-controller.test.ts +0 -1396
  269. package/packages/jev-judge/tests/jev-provider-chain.test.ts +0 -536
  270. package/packages/jev-judge/tests/outcome-application.test.ts +0 -132
  271. package/packages/jev-judge/tests/question-risk.test.ts +0 -129
  272. package/packages/jev-judge/tests/typesafe-provider.test.ts +0 -640
  273. package/packages/jev-judge/tests/validation-ledger.test.ts +0 -181
  274. package/packages/jev-judge/tests/validation-plan.test.ts +0 -170
  275. package/packages/jev-judge/tests/validation-tie-breaker.test.ts +0 -88
  276. package/packages/laravel-plugin/test/analyzer.test.ts +0 -220
  277. package/packages/learning-engine/test/engine.test.ts +0 -331
  278. package/packages/logging/test/buffer.test.ts +0 -44
  279. package/packages/logging/test/classification.test.ts +0 -137
  280. package/packages/logging/test/config.test.ts +0 -97
  281. package/packages/logging/test/fanout.test.ts +0 -186
  282. package/packages/logging/test/identity-transport.test.ts +0 -197
  283. package/packages/logging/test/incidents.test.ts +0 -404
  284. package/packages/logging/test/ingest-schema.test.ts +0 -184
  285. package/packages/logging/test/integration.test.ts +0 -140
  286. package/packages/logging/test/log-http.test.ts +0 -246
  287. package/packages/logging/test/redact.test.ts +0 -109
  288. package/packages/logging/test/sampler.test.ts +0 -57
  289. package/packages/logging/test/server.test.ts +0 -444
  290. package/packages/logging/test/shipper.test.ts +0 -152
  291. package/packages/logging/test/spool.test.ts +0 -96
  292. package/packages/mcp-adapter/test/server.test.ts +0 -91
  293. package/packages/memory-engine/test/engine.test.ts +0 -456
  294. package/packages/model-registry/test/registry.test.ts +0 -342
  295. package/packages/nextjs-plugin/test/analyzer.test.ts +0 -149
  296. package/packages/notification/test/approval-payload.test.ts +0 -269
  297. package/packages/notification/test/detail-format.test.ts +0 -106
  298. package/packages/notification/test/notification.test.ts +0 -375
  299. package/packages/notification/test/pending-question-lifecycle.test.ts +0 -195
  300. package/packages/notification/test/pending-question.test.ts +0 -331
  301. package/packages/notification/test/tooling-proposal-payload.test.ts +0 -212
  302. package/packages/observability/test/observability.test.ts +0 -1350
  303. package/packages/performance-optimizer/test/optimizer.test.ts +0 -267
  304. package/packages/project-analyzer/test/index.test.ts +0 -1719
  305. package/packages/prompt-compiler/test/prompt-compiler.test.ts +0 -813
  306. package/packages/prompt-versioning/test/versioning.test.ts +0 -112
  307. package/packages/provider-adapter-sdk/test/sdk.test.ts +0 -220
  308. package/packages/provider-router/test/provider-router.test.ts +0 -405
  309. package/packages/provider-selector/test/selector.test.ts +0 -192
  310. package/packages/providers/test/providers.test.ts +0 -225
  311. package/packages/qdrant-skills/test/collection-info.test.ts +0 -165
  312. package/packages/qdrant-skills/test/embedder-batch.test.ts +0 -184
  313. package/packages/qdrant-skills/test/embedder.test.ts +0 -97
  314. package/packages/quota-manager/test/quota.test.ts +0 -432
  315. package/packages/rate-limiter/test/limiter.test.ts +0 -128
  316. package/packages/react-vite-plugin/test/analyzer.test.ts +0 -139
  317. package/packages/relay/test/client.test.ts +0 -446
  318. package/packages/relay/test/relay-e2e.test.ts +0 -299
  319. package/packages/relay/test/relay-endpoint.test.ts +0 -278
  320. package/packages/release-manager/test/release.test.ts +0 -215
  321. package/packages/requirement-compiler/test/requirement-compiler.test.ts +0 -743
  322. package/packages/runtime/test/runtime-api.test.ts +0 -169
  323. package/packages/runtime/test/runtime.test.ts +0 -297
  324. package/packages/scheduler/test/scheduler.test.ts +0 -125
  325. package/packages/session/test/manager.test.ts +0 -233
  326. package/packages/shared-context/test/shared-context.test.ts +0 -114
  327. package/packages/skill-registry/test/registry.test.ts +0 -508
  328. package/packages/skills/test/init-skills.test.ts +0 -114
  329. package/packages/skills/test/remote-skills.test.ts +0 -360
  330. package/packages/skills/test/remote-source.test.ts +0 -248
  331. package/packages/skills/test/sanitize.test.ts +0 -250
  332. package/packages/skills/test/vector-indexing.test.ts +0 -205
  333. package/packages/skills/test/vector-schema.test.ts +0 -180
  334. package/packages/skills/test/vector-threshold.test.ts +0 -108
  335. package/packages/sprint-planner/test/planner.test.ts +0 -187
  336. package/packages/task-compiler/test/task-compiler.test.ts +0 -628
  337. package/packages/test-data-generator/test/generator.test.ts +0 -265
  338. package/packages/test-generator/test/generator.test.ts +0 -156
  339. package/packages/token-estimation/test/token-estimation.test.ts +0 -207
  340. package/packages/token-optimizer/test/optimizer.test.ts +0 -141
  341. package/packages/tooling-preference/tests/changed-files.test.ts +0 -124
  342. package/packages/tooling-preference/tests/detect.test.ts +0 -188
  343. package/packages/tooling-preference/tests/proposal.test.ts +0 -107
  344. package/packages/tooling-preference/tests/quality-check.test.ts +0 -272
  345. package/packages/tooling-preference/tests/registry.test.ts +0 -51
  346. package/packages/tooling-preference/tests/store.test.ts +0 -99
  347. package/packages/trajectory/test/store.test.ts +0 -250
  348. package/packages/tui/test/harness-footer.test.ts +0 -559
  349. package/packages/tui/test/harness-header.test.ts +0 -590
  350. package/packages/two-phase-review/test/classify.test.ts +0 -102
  351. package/packages/two-phase-review/test/helpers.ts +0 -72
  352. package/packages/two-phase-review/test/rules.test.ts +0 -150
  353. package/packages/two-phase-review/test/runtime.test.ts +0 -362
  354. package/packages/two-phase-review/test/scope.test.ts +0 -186
  355. package/packages/two-phase-review/test/signals.test.ts +0 -183
  356. package/packages/two-phase-review/test/state.test.ts +0 -201
  357. package/packages/wiki/test/basic.test.ts +0 -242
  358. package/packages/workspace-scanner/test/scanner.test.ts +0 -43
  359. package/packages/worktree/test/worktree.test.ts +0 -163
@@ -1,813 +0,0 @@
1
- /**
2
- * Prompt Compiler - Tests
3
- *
4
- * Tests cover all acceptance criteria from RFC-0041:
5
- * 1. Identical inputs produce identical prompt content and hash.
6
- * 2. Required sections are never removed by compaction.
7
- * 3. Provider formatting does not alter task semantics.
8
- * 4. Secrets are redacted before persistence.
9
- * 5. Invalid output contracts fail compilation.
10
- * 6. Continuation prompts do not repeat completed work.
11
- * 7. Unit tests cover all error codes.
12
- * 8. Tests pass without network access.
13
- */
14
-
15
- import assert from "node:assert";
16
- import { describe, it } from "node:test";
17
-
18
- import type { CompiledRequirement } from "@pi-harness/requirement-compiler";
19
- import type { CompiledTask } from "@pi-harness/task-compiler";
20
- import { compactToBudget, estimateTokens } from "../src/budget.js";
21
- // Import directly from source (TypeScript modules)
22
- import { compilePrompt, createSyncHasher } from "../src/compiler.js";
23
- import {
24
- deduplicateSections,
25
- normalizeForDeduplication,
26
- } from "../src/deduplicate.js";
27
- import { normalizeRequest } from "../src/normalize.js";
28
- import { renderForProvider } from "../src/render.js";
29
- import { buildSections } from "../src/section-builder.js";
30
- import { PromptCompileError } from "../src/types.js";
31
- import { PROVIDER_PROFILES } from "../src/types.js";
32
- import type { PromptCompileRequest } from "../src/types.js";
33
- import { validateSections, validateTokenBudget } from "../src/validate.js";
34
-
35
- // --- Test fixtures -------------------------------------------------------
36
-
37
- function makeRequest(
38
- overrides?: Partial<PromptCompileRequest>,
39
- ): PromptCompileRequest {
40
- const requirement: CompiledRequirement = {
41
- id: "REQ-001",
42
- title: "Build login feature",
43
- problemStatement: "Implement user login with JWT",
44
- goals: [{ id: "g1", description: "Build login" }],
45
- constraints: [
46
- { id: "c1", description: "Use HTTPS only", blocking: true },
47
- { id: "c2", description: "Passwords hashed with bcrypt", blocking: true },
48
- ],
49
- acceptanceCriteria: [
50
- { id: "ac1", outcome: ["User can log in with valid credentials"] },
51
- { id: "ac2", outcome: ["Invalid credentials show error"] },
52
- ],
53
- riskTags: [],
54
- };
55
-
56
- const task: CompiledTask = {
57
- id: "task-001",
58
- type: "implementation",
59
- title: "Implement login",
60
- objective: "Implement user login feature with JWT authentication",
61
- dependencies: [],
62
- priority: 1,
63
- fileOwnership: {
64
- taskId: "task-001",
65
- mode: "exclusive",
66
- include: ["/project/src/auth/**"],
67
- exclude: [],
68
- },
69
- filesInScope: [
70
- { path: "/project/src/auth/login.ts", description: "Login handler" },
71
- { path: "/project/src/auth/jwt.ts", description: "JWT utilities" },
72
- ],
73
- estimatedComplexity: 3 as const,
74
- expectedOutputs: [
75
- {
76
- kind: "file",
77
- description: "login.ts implementation",
78
- path: "/project/src/auth/login.ts",
79
- required: true,
80
- },
81
- { kind: "test_result", description: "login tests", required: true },
82
- ],
83
- prohibitedCommands: ["rm -rf /", "yarn build"],
84
- permittedCommands: ["tsc", "jest", "node"],
85
- requiredCapabilities: ["typescript", "node"],
86
- acceptanceCriteria: ["ac1", "ac2"],
87
- jobId: "JOB-001",
88
- };
89
-
90
- return {
91
- task,
92
- requirement,
93
- context: { entries: [] },
94
- provider: "codex",
95
- attempt: 1,
96
- ...overrides,
97
- };
98
- }
99
-
100
- function makeDeps(overrides?: {
101
- projectRules?: string[];
102
- redactor?: (content: string) => string;
103
- }) {
104
- const hasher = createSyncHasher();
105
- return {
106
- hasher,
107
- clock: { now: () => new Date("2026-01-01T00:00:00.000Z") },
108
- redactor: {
109
- redact: overrides?.redactor ?? ((s: string) => s),
110
- },
111
- projectRules: overrides?.projectRules ?? [
112
- "Use TypeScript strict mode",
113
- "Write unit tests for all public functions",
114
- ],
115
- };
116
- }
117
-
118
- // --- Test 1: Deterministic hash --------------------------------------
119
-
120
- describe("Deterministic hash", () => {
121
- it("identical inputs produce identical prompt content and hash", async () => {
122
- const request = makeRequest();
123
- const deps = makeDeps();
124
-
125
- const pkg1 = await compilePrompt(request, deps);
126
- const pkg2 = await compilePrompt(request, deps);
127
-
128
- assert.strictEqual(pkg1.hash, pkg2.hash, "Hash should be deterministic");
129
- assert.strictEqual(pkg1.system, pkg2.system, "System prompt should match");
130
- assert.strictEqual(pkg1.user, pkg2.user, "User prompt should match");
131
- assert.strictEqual(pkg1.taskId, pkg2.taskId);
132
- assert.strictEqual(pkg1.provider, pkg2.provider);
133
- assert.strictEqual(pkg1.version, "1");
134
- });
135
-
136
- it("different content produces different hash", async () => {
137
- const request1 = makeRequest();
138
- const request2 = makeRequest({
139
- task: {
140
- ...request1.task,
141
- objective: "Different objective",
142
- } as CompiledTask,
143
- });
144
- const deps = makeDeps();
145
-
146
- const pkg1 = await compilePrompt(request1, deps);
147
- const pkg2 = await compilePrompt(request2, deps);
148
-
149
- assert.notStrictEqual(
150
- pkg1.hash,
151
- pkg2.hash,
152
- "Different content should produce different hash",
153
- );
154
- });
155
-
156
- it("hash includes both system and user content", async () => {
157
- const request = makeRequest();
158
- const deps = makeDeps();
159
-
160
- const pkg = await compilePrompt(request, deps);
161
-
162
- assert.ok(pkg.hash.length > 0, "Hash should not be empty");
163
- assert.ok(
164
- pkg.hash === deps.hasher.sha256(`${pkg.system}\n${pkg.user}`),
165
- "Hash should equal SHA-256 of system+user",
166
- );
167
- });
168
- });
169
-
170
- // --- Test 2: Required sections never removed -----------------------------
171
-
172
- describe("Required sections survive compaction", () => {
173
- it("objective section is required and never compacted", async () => {
174
- const request = makeRequest({
175
- context: {
176
- entries: Array.from({ length: 50 }, (_, i) => ({
177
- id: `ctx-${i}`,
178
- content: `This is supplemental context entry number ${i}. It provides background information that could be safely removed if needed.`,
179
- priority: 7, // supplemental priority
180
- source: `file-${i}.md`,
181
- })),
182
- },
183
- });
184
- const deps = makeDeps();
185
-
186
- const pkg = await compilePrompt(request, deps);
187
-
188
- assert.ok(
189
- pkg.user.includes("Implement user login feature"),
190
- "Objective should be in user prompt",
191
- );
192
- assert.ok(
193
- pkg.user.includes("User can log in with valid credentials"),
194
- "Acceptance criteria should be preserved",
195
- );
196
- assert.ok(
197
- pkg.user.includes("/project/src/auth/login.ts"),
198
- "Files in scope should be preserved",
199
- );
200
- });
201
-
202
- it("project rules section is required", async () => {
203
- const request = makeRequest();
204
- const deps = makeDeps({
205
- projectRules: ["Use TypeScript", "No console.log"],
206
- });
207
-
208
- const pkg = await compilePrompt(request, deps);
209
-
210
- assert.ok(
211
- pkg.system.includes("TypeScript") || pkg.user.includes("TypeScript"),
212
- "Project rules should be in prompt",
213
- );
214
- });
215
-
216
- it("acceptance criteria are never removed", async () => {
217
- const request = makeRequest();
218
- const deps = makeDeps();
219
-
220
- const pkg = await compilePrompt(request, deps);
221
-
222
- const acSection = pkg.sections.find(
223
- (s) => s.kind === "acceptance_criteria",
224
- );
225
- assert.ok(acSection, "AC section should exist");
226
- assert.strictEqual(
227
- acSection?.required,
228
- true,
229
- "AC section should be required",
230
- );
231
- assert.ok(
232
- pkg.user.includes("User can log in with valid credentials"),
233
- "AC text should be in prompt",
234
- );
235
- });
236
- });
237
-
238
- // --- Test 3: Provider formatting ---------------------------------------
239
-
240
- describe("Provider formatting", () => {
241
- it("anthropic uses XML formatting", async () => {
242
- const request = makeRequest({ provider: "anthropic" });
243
- const deps = makeDeps();
244
-
245
- const pkg = await compilePrompt(request, deps);
246
- const profile = PROVIDER_PROFILES.anthropic;
247
-
248
- const sections = buildSections(
249
- normalizeRequest(request),
250
- deps.projectRules,
251
- );
252
- const rendered = renderForProvider(sections, profile);
253
-
254
- // Anthropic's rules go in system
255
- if (profile.supportsSystemPrompt) {
256
- assert.ok(
257
- rendered.system.length > 0 || rendered.user.length > 0,
258
- "Should have some output",
259
- );
260
- }
261
- });
262
-
263
- it("all supported providers produce valid output", async () => {
264
- const request = makeRequest();
265
- const providers: Array<
266
- "codex" | "minimax" | "glm" | "openai" | "anthropic" | "gemini"
267
- > = ["codex", "minimax", "glm", "openai", "anthropic", "gemini"];
268
-
269
- for (const provider of providers) {
270
- const req = makeRequest({ provider });
271
- const deps = makeDeps();
272
- const pkg = await compilePrompt(req, deps);
273
-
274
- assert.ok(
275
- pkg.system.length > 0 || pkg.user.length > 0,
276
- `Provider ${provider} should produce output`,
277
- );
278
- assert.strictEqual(pkg.provider, provider);
279
- }
280
- });
281
-
282
- it("different styles produce different formatting but same content", async () => {
283
- const request = makeRequest({ provider: "openai" });
284
- const deps = makeDeps();
285
-
286
- const pkg1 = await compilePrompt(request, deps);
287
- const req2 = makeRequest({ provider: "anthropic" });
288
- const pkg2 = await compilePrompt(req2, deps);
289
-
290
- // Both should contain the same key content
291
- assert.ok(
292
- pkg1.user.includes("Implement user login"),
293
- "OpenAI should have objective",
294
- );
295
- assert.ok(
296
- pkg2.user.includes("Implement user login"),
297
- "Anthropic should have objective",
298
- );
299
- });
300
- });
301
-
302
- // --- Test 4: Secret redaction -----------------------------------------
303
-
304
- describe("Secret redaction", () => {
305
- it("API keys are redacted before persistence", async () => {
306
- const request = makeRequest();
307
- const deps = makeDeps({
308
- redactor: (content: string) =>
309
- content
310
- .replace(/sk-[a-zA-Z0-9]{20,}/g, "[API_KEY_REDACTED]")
311
- .replace(/password\s*=\s*["'][^"']+["']/gi, 'password="[REDACTED]"'),
312
- });
313
-
314
- const secretRequest = makeRequest({
315
- task: {
316
- ...request.task,
317
- objective: "Use API key sk-1234567890abcdefghij for authentication",
318
- permittedCommands: [
319
- "curl -H 'Authorization: Bearer sk-abcdefghijklmnop12345' https://api.example.com",
320
- ],
321
- } as CompiledTask,
322
- });
323
-
324
- const pkg = await compilePrompt(secretRequest, deps);
325
-
326
- assert.ok(
327
- !pkg.user.includes("sk-1234567890abcdefghij"),
328
- "API key should be redacted",
329
- );
330
- assert.ok(
331
- !pkg.user.includes("sk-abcdefghijklmnop12345"),
332
- "Bearer token should be redacted",
333
- );
334
- });
335
-
336
- it("identity section preserves task ID", async () => {
337
- const request = makeRequest();
338
- const deps = makeDeps();
339
-
340
- const pkg = await compilePrompt(request, deps);
341
-
342
- assert.ok(pkg.user.includes("task-001"), "Task ID should be preserved");
343
- assert.ok(
344
- pkg.user.includes("REQ-001"),
345
- "Requirement ID should be preserved",
346
- );
347
- });
348
-
349
- it("hash is computed over redacted content", async () => {
350
- const request = makeRequest();
351
- const deps = makeDeps({
352
- redactor: (content: string) =>
353
- content.replace(/sk-[a-zA-Z0-9]+/g, "[REDACTED]"),
354
- });
355
-
356
- const pkg = await compilePrompt(request, deps);
357
-
358
- assert.ok(pkg.hash.length > 0, "Hash should be computed");
359
- assert.ok(!pkg.user.includes("sk-"), "No API keys in prompt");
360
- });
361
- });
362
-
363
- // --- Test 5: Error codes -----------------------------------------------
364
-
365
- describe("Error codes", () => {
366
- it("INVALID_TASK when task ID is empty", async () => {
367
- const base = makeRequest();
368
- const request = makeRequest({
369
- task: { ...base.task, id: "" },
370
- });
371
- const deps = makeDeps();
372
-
373
- await assert.rejects(
374
- async () => compilePrompt(request, deps),
375
- (err: unknown) => {
376
- if (err instanceof PromptCompileError) {
377
- assert.strictEqual(err.code, "INVALID_TASK");
378
- return true;
379
- }
380
- return false;
381
- },
382
- );
383
- });
384
-
385
- it("MISSING_OBJECTIVE when objective is empty", async () => {
386
- const base = makeRequest();
387
- const request = makeRequest({
388
- task: { ...base.task, objective: "" },
389
- });
390
- const deps = makeDeps();
391
-
392
- await assert.rejects(
393
- async () => compilePrompt(request, deps),
394
- (err: unknown) => {
395
- if (err instanceof PromptCompileError) {
396
- assert.strictEqual(err.code, "MISSING_OBJECTIVE");
397
- return true;
398
- }
399
- return false;
400
- },
401
- );
402
- });
403
-
404
- it("MISSING_OUTPUT_CONTRACT when no outputs defined", async () => {
405
- const base = makeRequest();
406
- const request = makeRequest({
407
- task: { ...base.task, expectedOutputs: [] },
408
- });
409
- const deps = makeDeps();
410
-
411
- await assert.rejects(
412
- async () => compilePrompt(request, deps),
413
- (err: unknown) => {
414
- if (err instanceof PromptCompileError) {
415
- assert.strictEqual(err.code, "MISSING_OUTPUT_CONTRACT");
416
- return true;
417
- }
418
- return false;
419
- },
420
- );
421
- });
422
-
423
- it("TOKEN_BUDGET_EXCEEDED when estimated tokens exceed budget", async () => {
424
- // Test validation directly: compactToBudget removes all compactable content,
425
- // so the full compile cannot produce TOKEN_BUDGET_EXCEEDED.
426
- // We test validateTokenBudget directly.
427
- const profile = PROVIDER_PROFILES.codex;
428
- const available = profile.maximumInputTokens - profile.reservedOutputTokens;
429
-
430
- assert.throws(
431
- () => validateTokenBudget(available + 1, profile),
432
- (err: unknown) => {
433
- if (err instanceof PromptCompileError) {
434
- assert.strictEqual(err.code, "TOKEN_BUDGET_EXCEEDED");
435
- return true;
436
- }
437
- return false;
438
- },
439
- );
440
- });
441
- it("POLICY_CONFLICT for prohibited tools", async () => {
442
- const base = makeRequest();
443
- const request = makeRequest({
444
- task: {
445
- ...base.task,
446
- permittedCommands: ["rm -rf /", "tsc", "jest"],
447
- },
448
- });
449
- const deps = makeDeps();
450
-
451
- await assert.rejects(
452
- async () => compilePrompt(request, deps),
453
- (err: unknown) => {
454
- if (err instanceof PromptCompileError) {
455
- assert.strictEqual(err.code, "POLICY_CONFLICT");
456
- return true;
457
- }
458
- return false;
459
- },
460
- );
461
- });
462
-
463
- it("INVALID_TASK for unknown provider", async () => {
464
- const request = makeRequest({ provider: "unknown-provider" as "codex" });
465
- const deps = makeDeps();
466
-
467
- await assert.rejects(
468
- async () => compilePrompt(request, deps),
469
- (err: unknown) => {
470
- if (err instanceof PromptCompileError) {
471
- assert.strictEqual(err.code, "INVALID_TASK");
472
- return true;
473
- }
474
- return false;
475
- },
476
- );
477
- });
478
- });
479
-
480
- // --- Test 6: Continuation without repetition ---------------------------
481
-
482
- describe("Continuation without repetition", () => {
483
- it("completed items are marked as done, not repeated", async () => {
484
- const request = makeRequest({
485
- continuation: {
486
- previousResponsePath: "/path/to/previous/response.json",
487
- completedItems: ["Step 1: Login form", "Step 2: JWT utility"],
488
- incompleteItems: ["Step 3: Error handling"],
489
- instruction: "continue_without_repeating",
490
- },
491
- });
492
- const deps = makeDeps();
493
-
494
- const pkg = await compilePrompt(request, deps);
495
-
496
- assert.ok(
497
- pkg.user.includes("Step 1: Login form") || pkg.user.includes("DONE"),
498
- "Completed items should be marked",
499
- );
500
- assert.ok(
501
- pkg.user.includes("Step 3: Error handling"),
502
- "Incomplete items should be included",
503
- );
504
- assert.ok(
505
- pkg.user.includes("Continue from where the previous attempt stopped"),
506
- "Continuation instruction should be present",
507
- );
508
- });
509
-
510
- it("continuation section has required=true so it survives compaction", async () => {
511
- const request = makeRequest({
512
- continuation: {
513
- previousResponsePath: "/tmp/response.json",
514
- completedItems: ["A", "B"],
515
- incompleteItems: ["C"],
516
- instruction: "continue_without_repeating",
517
- },
518
- });
519
- const deps = makeDeps();
520
-
521
- const pkg = await compilePrompt(request, deps);
522
-
523
- const contSection = pkg.sections.find(
524
- (s) => s.kind === "continuation_instructions",
525
- );
526
- assert.ok(contSection, "Continuation section should exist");
527
- assert.strictEqual(
528
- contSection?.required,
529
- true,
530
- "Continuation should be required",
531
- );
532
- });
533
-
534
- it("previous response path is referenced", async () => {
535
- const request = makeRequest({
536
- continuation: {
537
- previousResponsePath: "/tmp/attempt-1-response.json",
538
- completedItems: [],
539
- incompleteItems: ["Remaining work"],
540
- instruction: "continue_without_repeating",
541
- },
542
- });
543
- const deps = makeDeps();
544
-
545
- const pkg = await compilePrompt(request, deps);
546
-
547
- assert.ok(
548
- pkg.user.includes("attempt-1-response.json") ||
549
- pkg.sections.some((s) =>
550
- s.sourceRefs.some((ref) => ref.source.includes("attempt-1")),
551
- ),
552
- "Previous response path should be referenced",
553
- );
554
- });
555
- });
556
-
557
- // --- Test 7: Token budget ----------------------------------------------
558
-
559
- describe("Token budget", () => {
560
- it("estimateTokens returns 0 for empty string", () => {
561
- assert.strictEqual(estimateTokens(""), 0);
562
- assert.strictEqual(estimateTokens(" "), 0);
563
- });
564
-
565
- it("estimateTokens scales with content length", () => {
566
- const short = "hello";
567
- const long = "hello world this is a much longer string";
568
-
569
- assert.ok(estimateTokens(long) > estimateTokens(short));
570
- });
571
-
572
- it("compactToBudget preserves required sections", () => {
573
- const sections = [
574
- {
575
- id: "objective",
576
- kind: "objective" as const,
577
- content: "This is the critical objective",
578
- required: true,
579
- compactable: false,
580
- sourceRefs: [],
581
- },
582
- {
583
- id: "context-1",
584
- kind: "supplemental" as const,
585
- content: "This is supplemental content that can be removed",
586
- required: false,
587
- compactable: true,
588
- sourceRefs: [],
589
- },
590
- ];
591
-
592
- const result = compactToBudget(sections, 100, estimateTokens);
593
-
594
- const objectiveSection = result.sections.find((s) => s.id === "objective");
595
- assert.ok(
596
- objectiveSection?.content.includes("critical objective"),
597
- "Required section should be preserved",
598
- );
599
- });
600
-
601
- it("validateTokenBudget throws when exceeded", () => {
602
- const profile = PROVIDER_PROFILES.codex;
603
- const available = profile.maximumInputTokens - profile.reservedOutputTokens;
604
-
605
- assert.throws(
606
- () => validateTokenBudget(available + 1, profile),
607
- (err: unknown) => {
608
- if (err instanceof PromptCompileError) {
609
- assert.strictEqual(err.code, "TOKEN_BUDGET_EXCEEDED");
610
- return true;
611
- }
612
- return false;
613
- },
614
- );
615
- });
616
-
617
- it("validateTokenBudget passes when within budget", () => {
618
- const profile = PROVIDER_PROFILES.codex;
619
- const available = profile.maximumInputTokens - profile.reservedOutputTokens;
620
-
621
- assert.doesNotThrow(() => validateTokenBudget(available, profile));
622
- assert.doesNotThrow(() => validateTokenBudget(100, profile));
623
- });
624
- });
625
-
626
- // --- Test 8: Deduplication ---------------------------------------------
627
-
628
- describe("Deduplication", () => {
629
- it("normalizeForDeduplication strips markdown and lowercases", () => {
630
- assert.strictEqual(normalizeForDeduplication("# Header"), "header");
631
- assert.strictEqual(normalizeForDeduplication("**bold** text"), "bold text");
632
- assert.strictEqual(
633
- normalizeForDeduplication("[link](http://example.com)"),
634
- "link",
635
- );
636
- assert.strictEqual(
637
- normalizeForDeduplication(" spaces collapse "),
638
- "spaces collapse",
639
- );
640
- });
641
-
642
- it("duplicate lines are removed from compactable sections", () => {
643
- const sections = [
644
- {
645
- id: "context-1",
646
- kind: "relevant_context" as const,
647
- content:
648
- "Important note\nThis is repeated\nThis is repeated\nAnother line",
649
- required: false,
650
- compactable: true,
651
- sourceRefs: [],
652
- },
653
- ];
654
-
655
- const result = deduplicateSections(sections);
656
-
657
- assert.ok(
658
- result[0]?.content.split("\n").filter(Boolean).length < 4,
659
- "Duplicates should be removed",
660
- );
661
- });
662
-
663
- it("required sections are not deduplicated", () => {
664
- const sections = [
665
- {
666
- id: "objective",
667
- kind: "objective" as const,
668
- content: "Same objective",
669
- required: true,
670
- compactable: false,
671
- sourceRefs: [],
672
- },
673
- {
674
- id: "objective-2",
675
- kind: "objective" as const,
676
- content: "Same objective",
677
- required: true,
678
- compactable: false,
679
- sourceRefs: [],
680
- },
681
- ];
682
-
683
- const result = deduplicateSections(sections);
684
-
685
- // Both required sections should be preserved
686
- assert.strictEqual(
687
- result.length,
688
- 2,
689
- "Required sections should not be deduplicated",
690
- );
691
- });
692
- });
693
-
694
- // --- Test 9: Metadata completeness ------------------------------------
695
-
696
- describe("Metadata completeness", () => {
697
- it("PromptPackage includes token estimate and source refs", async () => {
698
- const request = makeRequest({
699
- context: {
700
- entries: [
701
- {
702
- id: "ctx-1",
703
- content: "Important context about the codebase",
704
- priority: 1,
705
- source: "repo/readme.md",
706
- },
707
- ],
708
- },
709
- });
710
- const deps = makeDeps();
711
-
712
- const pkg = await compilePrompt(request, deps);
713
-
714
- assert.ok(
715
- pkg.estimatedInputTokens > 0,
716
- "Token estimate should be positive",
717
- );
718
- assert.ok(pkg.sourceRefs.length > 0, "Should have source references");
719
- assert.strictEqual(pkg.version, "1");
720
- assert.strictEqual(pkg.createdAt, "2026-01-01T00:00:00.000Z");
721
- assert.ok(pkg.hash.length > 0, "Hash should be present");
722
- });
723
-
724
- it("sections are ordered deterministically", async () => {
725
- const request = makeRequest();
726
- const deps = makeDeps();
727
-
728
- const pkg = await compilePrompt(request, deps);
729
-
730
- const kinds = pkg.sections.map((s) => s.kind);
731
- // identity should come first
732
- assert.strictEqual(kinds[0], "identity");
733
- // objective should be early
734
- assert.ok(
735
- kinds.indexOf("objective") < kinds.indexOf("supplemental") ||
736
- !kinds.includes("supplemental"),
737
- );
738
- });
739
- });
740
-
741
- // --- Test 10: PromptCompileError -------------------------------------
742
-
743
- describe("PromptCompileError", () => {
744
- it("has correct name, code, and message", () => {
745
- const error = new PromptCompileError(
746
- "TOKEN_BUDGET_EXCEEDED",
747
- "Budget exceeded",
748
- { estimated: 1000, available: 500 },
749
- );
750
-
751
- assert.strictEqual(error.name, "PromptCompileError");
752
- assert.strictEqual(error.code, "TOKEN_BUDGET_EXCEEDED");
753
- assert.strictEqual(error.message, "Budget exceeded");
754
- assert.deepStrictEqual(error.details, { estimated: 1000, available: 500 });
755
- });
756
-
757
- it("is instance of Error", () => {
758
- const error = new PromptCompileError("INVALID_TASK", "Bad task");
759
- assert.ok(error instanceof Error);
760
- assert.ok(error instanceof PromptCompileError);
761
- });
762
- });
763
-
764
- // --- Test 11: normalizeRequest ----------------------------------------
765
-
766
- describe("normalizeRequest", () => {
767
- it("trims strings", () => {
768
- const req = makeRequest({
769
- task: { ...makeRequest().task, id: " task-001 " } as CompiledTask,
770
- });
771
- const normalized = normalizeRequest(req);
772
- assert.strictEqual(normalized.taskId, "task-001");
773
- });
774
-
775
- it("sorts context entries by priority", () => {
776
- const req = makeRequest({
777
- context: {
778
- entries: [
779
- { id: "a", content: "Low priority", priority: 5, source: "x" },
780
- { id: "b", content: "High priority", priority: 1, source: "y" },
781
- { id: "c", content: "Medium priority", priority: 3, source: "z" },
782
- ],
783
- },
784
- });
785
- const normalized = normalizeRequest(req);
786
-
787
- assert.strictEqual(normalized.contextEntries[0]?.id, "b");
788
- assert.strictEqual(normalized.contextEntries[1]?.id, "c");
789
- assert.strictEqual(normalized.contextEntries[2]?.id, "a");
790
- });
791
-
792
- it("removes duplicates from arrays", () => {
793
- const req = makeRequest();
794
- const withDupes = normalizeRequest({
795
- ...req,
796
- context: {
797
- entries: [
798
- { id: "a", content: "Content A", priority: 1, source: "x" },
799
- { id: "b", content: "Content A", priority: 1, source: "y" }, // duplicate content
800
- ],
801
- },
802
- });
803
-
804
- const uniqueContents = new Set(
805
- withDupes.contextEntries.map((e) => e.content),
806
- );
807
- assert.strictEqual(
808
- uniqueContents.size,
809
- 1,
810
- "Duplicate content should be removed",
811
- );
812
- });
813
- });