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,802 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import {
6
- buildAllowanceReason,
7
- canShowModeValue,
8
- collectDotenvVars,
9
- collectProcessEnvVars,
10
- describeCwdCommand,
11
- describeOs,
12
- describeServerState,
13
- describeWorkspaceMode,
14
- detectModeHint,
15
- filterOwnPids,
16
- formatEnvForAgentResult,
17
- formatEnvStatusLine,
18
- formatWorkspacePath,
19
- isLauncherCommand,
20
- isLowRamWarning,
21
- isSessionVariableKey,
22
- MASKED_MODE_VALUE,
23
- modeFromRepositoryFacts,
24
- modeFromValue,
25
- orderDotenvFileNames,
26
- parseDotenvVars,
27
- resolveWorkspaceMode,
28
- } from "../src/workspace-env-format.ts";
29
- import {
30
- DOTENV_MAX_BYTES,
31
- isTemplateDotenvName,
32
- readDotenvFiles,
33
- } from "../src/workspace-dotenv.ts";
34
- import type { EnvEvidence, WorkspaceEnvResult } from "../src/workspace-env-format.ts";
35
-
36
- function makeEnv(overrides: Partial<WorkspaceEnvResult> = {}): WorkspaceEnvResult {
37
- return {
38
- workspacePath: "/home/tester/pi-harness-runtime",
39
- mode: "unknown",
40
- evidence: [],
41
- hasDevScript: false,
42
- hasBuildScript: false,
43
- packageManager: "bun",
44
- devServerRunning: false,
45
- buildServerRunning: false,
46
- ramTotalGb: 15,
47
- ramAvailableGb: 7.7,
48
- ramEffectiveLimitGb: null,
49
- lowRamWarning: true,
50
- buildAllowed: false,
51
- detectedAt: 0,
52
- ...overrides,
53
- };
54
- }
55
-
56
- describe("workspace mode wording", () => {
57
- test("an unknown mode carries its reason", () => {
58
- expect(describeWorkspaceMode(makeEnv())).toBe(
59
- "UNKNOWN (no NODE_ENV or environment variable found)",
60
- );
61
- });
62
-
63
- test("a known mode names the variable it came from", () => {
64
- const evidence: EnvEvidence[] = [
65
- {
66
- source: ".env file",
67
- key: "AMOS_ENV",
68
- value: "development",
69
- timestamp: 0,
70
- file: ".env",
71
- },
72
- ];
73
-
74
- expect(describeWorkspaceMode(makeEnv({ mode: "development", evidence }))).toBe(
75
- "DEVELOPMENT (from AMOS_ENV in .env)",
76
- );
77
- });
78
-
79
- test("an empty *_ENV variable names itself", () => {
80
- const evidence: EnvEvidence[] = [
81
- { source: ".env file", key: "AMOS_ENV", value: "", timestamp: 0, file: ".env" },
82
- ];
83
-
84
- expect(describeWorkspaceMode(makeEnv({ evidence }))).toBe(
85
- "UNKNOWN (AMOS_ENV is empty in .env)",
86
- );
87
- });
88
-
89
- test("an unrecognized NODE_ENV names the value that could not be read", () => {
90
- const evidence: EnvEvidence[] = [
91
- { source: "process.env", key: "NODE_ENV", value: "staging", timestamp: 0 },
92
- ];
93
-
94
- expect(describeWorkspaceMode(makeEnv({ evidence }))).toBe(
95
- "UNKNOWN (NODE_ENV=staging is not a known mode)",
96
- );
97
- });
98
-
99
- test("a known mode is reported plainly", () => {
100
- expect(describeWorkspaceMode(makeEnv({ mode: "development" }))).toBe("DEVELOPMENT");
101
- expect(describeWorkspaceMode(makeEnv({ mode: "production" }))).toBe("PRODUCTION");
102
- expect(describeWorkspaceMode(makeEnv({ mode: "test" }))).toBe("TEST");
103
- });
104
-
105
- test("a dev script is a capability, not a state", () => {
106
- expect(describeServerState("running")).toBe("running");
107
- expect(describeServerState("not-detected")).toBe("not detected");
108
- expect(describeServerState("not-checked")).toBe("not checked");
109
- });
110
- });
111
-
112
- describe("the operating system", () => {
113
- test("the OS is named in a readable form", () => {
114
- expect(describeOs("linux")).toBe("Linux");
115
- expect(describeOs("darwin")).toBe("macOS");
116
- expect(describeOs("win32")).toBe("Windows");
117
- expect(describeOs("aix")).toBe("aix");
118
- });
119
-
120
- test("the command that prints the working directory depends on the OS", () => {
121
- expect(describeCwdCommand("linux")).toBe("pwd");
122
- expect(describeCwdCommand("darwin")).toBe("pwd");
123
- expect(describeCwdCommand("win32")).toContain("echo %cd%");
124
- expect(describeCwdCommand("win32")).toContain("PowerShell");
125
- });
126
-
127
- test("the home directory is shortened on a path boundary only", () => {
128
- expect(formatWorkspacePath("/home/tester/app", "/home/tester")).toBe("~/app");
129
- expect(formatWorkspacePath("/home/tester", "/home/tester")).toBe("~");
130
- expect(formatWorkspacePath("/home/testerX/app", "/home/tester")).toBe("/home/testerX/app");
131
- expect(formatWorkspacePath("/home/tester/app/", "/home/tester")).toBe("~/app");
132
- expect(formatWorkspacePath("/app", "/")).toBe("/app");
133
- });
134
- });
135
-
136
- describe("the process check", () => {
137
- test("our own processes are not counted as a running server", () => {
138
- // The shell that runs pgrep carries the pattern in its own command
139
- // line, so it always matches.
140
- expect(filterOwnPids(["4242", "4243"], ["4242"])).toEqual(["4243"]);
141
- expect(filterOwnPids(["", " "], [])).toEqual([]);
142
- expect(filterOwnPids(["4242"], [])).toEqual(["4242"]);
143
- });
144
- });
145
-
146
- describe("the server detection", () => {
147
- test("a launcher is told apart from a server", () => {
148
- // "npm run dev" only STARTS a server - the server is its child.
149
- expect(isLauncherCommand("bun run dev")).toBe(true);
150
- expect(isLauncherCommand("npm run start")).toBe(true);
151
- expect(isLauncherCommand("yarn dev")).toBe(true);
152
- // A real server is not a launcher.
153
- expect(isLauncherCommand("node /srv/app/vite/bin/vite.js")).toBe(false);
154
- expect(isLauncherCommand("next dev")).toBe(false);
155
- });
156
- });
157
-
158
- describe("the mode detection of a workspace", () => {
159
- test("the mode is read from a dotenv file of the workspace", () => {
160
- const vars = collectDotenvVars([{ name: ".env", content: "AMOS_ENV=development\n" }]);
161
- const resolved = resolveWorkspaceMode([], vars);
162
-
163
- expect(resolved.mode).toBe("development");
164
- expect(resolved.hint?.key).toBe("AMOS_ENV");
165
- expect(resolved.hint?.file).toBe(".env");
166
- expect(resolved.source).toBe(".env file");
167
- });
168
-
169
- test("a .env.<name> file is read too", () => {
170
- // The real reading of the files: a variant file of the workspace
171
- // must be found on disk, not only in a hand made list.
172
- const dir = mkdtempSync(join(tmpdir(), "wstest-"));
173
- try {
174
- writeFileSync(join(dir, ".env.production"), "NODE_ENV=production\n");
175
- const vars = collectDotenvVars(readDotenvFiles(dir));
176
-
177
- expect(resolveWorkspaceMode([], vars).mode).toBe("production");
178
- } finally {
179
- rmSync(dir, { recursive: true, force: true });
180
- }
181
- });
182
-
183
- test("the base .env wins over a variant", () => {
184
- const vars = collectDotenvVars([
185
- { name: ".env.production", content: "NODE_ENV=production\n" },
186
- { name: ".env", content: "NODE_ENV=development\n" },
187
- ]);
188
-
189
- expect(vars.find((v) => v.key === "NODE_ENV")?.value).toBe("development");
190
- expect(resolveWorkspaceMode([], vars).mode).toBe("development");
191
- });
192
-
193
- test("a workspace without a variable keeps the mode unknown", () => {
194
- const vars = collectDotenvVars([
195
- { name: ".env", content: "DATABASE_URL=postgres://localhost\n" },
196
- ]);
197
- const resolved = resolveWorkspaceMode([], vars);
198
-
199
- expect(resolved.mode).toBe("unknown");
200
- expect(resolved.hint).toBeNull();
201
- });
202
-
203
- test("the process env wins over the dotenv files", () => {
204
- const vars = collectDotenvVars([{ name: ".env", content: "NODE_ENV=production\n" }]);
205
- const resolved = resolveWorkspaceMode(
206
- collectProcessEnvVars({ NODE_ENV: "development" }),
207
- vars,
208
- );
209
-
210
- expect(resolved.mode).toBe("development");
211
- expect(resolved.source).toBe("process.env");
212
- });
213
-
214
- test("an explicit process NODE_ENV decides even when it names no known mode", () => {
215
- const vars = collectDotenvVars([{ name: ".env", content: "AMOS_ENV=production\n" }]);
216
- const resolved = resolveWorkspaceMode(collectProcessEnvVars({ NODE_ENV: "staging" }), vars);
217
-
218
- // The explicit "staging" is not overwritten by a file: the mode stays
219
- // unknown and the label names the value.
220
- expect(resolved.mode).toBe("unknown");
221
- expect(resolved.source).toBe("process.env");
222
- expect(resolved.hint?.value).toBe("staging");
223
- });
224
-
225
- test("an unrelated environment variable of the process env does not outrank the workspace", () => {
226
- const vars = collectDotenvVars([{ name: ".env", content: "NODE_ENV=production\n" }]);
227
- const resolved = resolveWorkspaceMode(
228
- collectProcessEnvVars({ FOO_ENV: "development" }),
229
- vars,
230
- );
231
-
232
- expect(resolved.mode).toBe("production");
233
- expect(resolved.source).toBe(".env file");
234
- });
235
-
236
- test("an environment variable of the process env is the last resort", () => {
237
- const resolved = resolveWorkspaceMode(
238
- collectProcessEnvVars({ AMOS_ENV: "production" }),
239
- [],
240
- );
241
-
242
- expect(resolved.mode).toBe("production");
243
- expect(resolved.source).toBe("process.env");
244
- });
245
-
246
- test("an unusable environment variable does not shadow a usable one", () => {
247
- // The scenario of the user: an early "APP_MODE=" in the same .env
248
- // must not hide the "AMOS_ENV=development" of line 46.
249
- const vars = collectDotenvVars([
250
- { name: ".env", content: "APP_MODE=staging\nAPP_ENV=\nAMOS_ENV=development\n" },
251
- ]);
252
- const resolved = resolveWorkspaceMode([], vars);
253
-
254
- expect(resolved.mode).toBe("development");
255
- expect(resolved.hint?.key).toBe("AMOS_ENV");
256
- expect(resolved.source).toBe(".env file");
257
- });
258
-
259
- test("the dotenv file names are ordered", () => {
260
- expect(
261
- orderDotenvFileNames([".env.production", ".env", ".env.local", ".env.zzz", "other"]),
262
- ).toEqual([".env", ".env.local", ".env.production", ".env.zzz"]);
263
- });
264
- });
265
-
266
- describe("the mode detection helpers", () => {
267
- test("a dotenv value is read with its quotes and trailing comment removed", () => {
268
- const vars = parseDotenvVars(
269
- [
270
- "# a comment",
271
- "",
272
- "NODE_ENV=production",
273
- 'APP_ENV="development"',
274
- "export AMOS_ENV=staging",
275
- "RAILS_ENV: test",
276
- "BARE",
277
- "QUOTED_WITH_HASH=abc # trailing",
278
- ].join("\n"),
279
- );
280
-
281
- expect(vars).toEqual([
282
- { key: "NODE_ENV", value: "production" },
283
- { key: "APP_ENV", value: "development" },
284
- { key: "AMOS_ENV", value: "staging" },
285
- { key: "RAILS_ENV", value: "test" },
286
- { key: "QUOTED_WITH_HASH", value: "abc" },
287
- ]);
288
- });
289
-
290
- test("a known mode is read from a value", () => {
291
- expect(modeFromValue("development")).toBe("development");
292
- expect(modeFromValue("DEV")).toBe("development");
293
- expect(modeFromValue(" prod ")).toBe("production");
294
- expect(modeFromValue("testing")).toBe("test");
295
- expect(modeFromValue("staging")).toBeNull();
296
- expect(modeFromValue("")).toBeNull();
297
- });
298
-
299
- test("NODE_ENV wins over another *_ENV key", () => {
300
- const hint = detectModeHint([
301
- { key: "AMOS_ENV", value: "production" },
302
- { key: "NODE_ENV", value: "development" },
303
- ]);
304
-
305
- expect(hint?.key).toBe("NODE_ENV");
306
- });
307
-
308
- test("another *_ENV key is used when there is no NODE_ENV", () => {
309
- const hint = detectModeHint([
310
- { key: "DATABASE_URL", value: "postgres://localhost" },
311
- { key: "AMOS_ENV", value: "production" },
312
- ]);
313
-
314
- expect(hint?.key).toBe("AMOS_ENV");
315
- });
316
-
317
- test("a value that names a mode is only used in the dotenv files", () => {
318
- // The process env never decides by a value: an unrelated variable
319
- // that holds "production" says nothing about the environment.
320
- const resolved = resolveWorkspaceMode(
321
- collectProcessEnvVars({ BRANCH: "production" }),
322
- [],
323
- );
324
-
325
- expect(resolved.mode).toBe("unknown");
326
- expect(resolved.hint).toBeNull();
327
- });
328
-
329
- test("the workspace file wins over the process env", () => {
330
- // The workspace says "APP_MODE=staging": that is explicit and it
331
- // must not be replaced by an unrelated variable of the process env.
332
- const resolved = resolveWorkspaceMode(
333
- collectProcessEnvVars({ AMOS_ENV: "development" }),
334
- collectDotenvVars([{ name: ".env", content: "APP_MODE=staging\n" }]),
335
- );
336
-
337
- expect(resolved.mode).toBe("unknown");
338
- expect(resolved.hint?.key).toBe("APP_MODE");
339
- expect(resolved.source).toBe(".env file");
340
- });
341
-
342
- test("a usable environment shaped key wins over an unusable one", () => {
343
- const vars = [
344
- { key: "APP_MODE", value: "staging" },
345
- { key: "AMOS_ENV", value: "development" },
346
- ];
347
-
348
- // The value match is opt-in: a caller has to ask for it.
349
- expect(detectModeHint(vars, { allowValueMatch: true })?.key).toBe("AMOS_ENV");
350
- expect(detectModeHint(vars)?.key).toBe("APP_MODE");
351
- });
352
-
353
- test("only an environment shaped key may decide the mode by its value", () => {
354
- // BRANCH=production is a branch name, not the environment.
355
- expect(detectModeHint([{ key: "BRANCH", value: "production" }], { allowValueMatch: true })).toBeNull();
356
- expect(detectModeHint([{ key: "DEBUG", value: "dev" }], { allowValueMatch: true })).toBeNull();
357
- expect(detectModeHint([{ key: "APP_ENV", value: "production" }], { allowValueMatch: true })?.key).toBe("APP_ENV");
358
- });
359
- });
360
-
361
- describe("the masking of the mode value", () => {
362
- test("a secret like key is never shown", () => {
363
- expect(canShowModeValue("PASSWORD", "production", ".env file")).toBe(false);
364
- expect(canShowModeValue("API_KEY", "production", ".env file")).toBe(false);
365
- });
366
-
367
- test("the NODE_ENV of the process env is shown", () => {
368
- expect(canShowModeValue("NODE_ENV", "staging", "process.env")).toBe(true);
369
- });
370
-
371
- test("a dotenv value that names no known mode is not shown", () => {
372
- expect(canShowModeValue("NODE_ENV", "staging", ".env file")).toBe(false);
373
- expect(canShowModeValue("AMOS_ENV", "production", ".env file")).toBe(true);
374
- });
375
-
376
- test("an evidence value that names a mode is never called unknown", () => {
377
- // A stale cache row (the stored mode and the evidence disagree) must
378
- // not produce "NODE_ENV=development is not a known mode".
379
- const env = makeEnv({
380
- mode: "unknown",
381
- evidence: [
382
- {
383
- source: ".env file",
384
- key: "NODE_ENV",
385
- value: "development",
386
- timestamp: 0,
387
- file: ".env",
388
- },
389
- ],
390
- });
391
-
392
- expect(describeWorkspaceMode(env)).toBe("DEVELOPMENT (from NODE_ENV in .env)");
393
- });
394
-
395
- test("a masked value is named by its key only", () => {
396
- const env = makeEnv({
397
- mode: "unknown",
398
- evidence: [
399
- {
400
- source: ".env file",
401
- key: "NODE_ENV",
402
- value: MASKED_MODE_VALUE,
403
- timestamp: 0,
404
- file: ".env",
405
- },
406
- ],
407
- });
408
-
409
- expect(describeWorkspaceMode(env)).toBe("UNKNOWN (NODE_ENV in .env names no known mode)");
410
- });
411
- });
412
-
413
- describe("reading the dotenv files of a workspace", () => {
414
- test("a template file is not an environment file", () => {
415
- expect(isTemplateDotenvName(".env.example")).toBe(true);
416
- expect(isTemplateDotenvName(".env.sample")).toBe(true);
417
- expect(isTemplateDotenvName(".env.template")).toBe(true);
418
- expect(isTemplateDotenvName(".env")).toBe(false);
419
- expect(isTemplateDotenvName(".env.production")).toBe(false);
420
- });
421
-
422
- test("a template marker in any segment makes the file a template", () => {
423
- // ".env.example.local" documents a template: it must never decide
424
- // the mode.
425
- expect(isTemplateDotenvName(".env.example.local")).toBe(true);
426
- expect(isTemplateDotenvName(".env.local.sample")).toBe(true);
427
- expect(isTemplateDotenvName(".env.tpl")).toBe(true);
428
- expect(isTemplateDotenvName(".env.local.development")).toBe(false);
429
- });
430
-
431
- test("a symlink out of the workspace is not read", () => {
432
- const outside = mkdtempSync(join(tmpdir(), "wstest-outside-"));
433
- const dir = mkdtempSync(join(tmpdir(), "wstest-"));
434
- try {
435
- writeFileSync(join(outside, "secret.env"), "AMOS_ENV=production\n");
436
- // A ".env" that points outside the workspace is not the
437
- // configuration of the workspace.
438
- symlinkSync(join(outside, "secret.env"), join(dir, ".env"));
439
- expect(readDotenvFiles(dir)).toEqual([]);
440
-
441
- // A link that stays inside the workspace is fine.
442
- writeFileSync(join(dir, "real.env"), "AMOS_ENV=development\n");
443
- symlinkSync(join(dir, "real.env"), join(dir, ".env.local"));
444
- const files = readDotenvFiles(dir);
445
- expect(files.map((f) => f.name)).toEqual([".env.local"]);
446
- } finally {
447
- rmSync(dir, { recursive: true, force: true });
448
- rmSync(outside, { recursive: true, force: true });
449
- }
450
- });
451
-
452
- test("the files are read from the workspace, not from the working directory", () => {
453
- const dir = mkdtempSync(join(tmpdir(), "wstest-"));
454
- try {
455
- writeFileSync(join(dir, ".env"), "AMOS_ENV=development\n");
456
- writeFileSync(join(dir, ".env.example"), "BRANCH=production\n");
457
-
458
- const files = readDotenvFiles(dir);
459
- expect(files.map((f) => f.name)).toEqual([".env"]);
460
- expect(files[0].content).toContain("AMOS_ENV=development");
461
- } finally {
462
- rmSync(dir, { recursive: true, force: true });
463
- }
464
- });
465
-
466
- test("a missing workspace has no environment files", () => {
467
- expect(readDotenvFiles(join(tmpdir(), "wstest-does-not-exist-12345"))).toEqual([]);
468
- });
469
-
470
- test("a file of exactly the maximum size is read", () => {
471
- const dir = mkdtempSync(join(tmpdir(), "wstest-"));
472
- try {
473
- // The limit is a limit, not an off by one: "X=" + N + "\n" is
474
- // exactly DOTENV_MAX_BYTES.
475
- writeFileSync(join(dir, ".env"), "X=" + "a".repeat(DOTENV_MAX_BYTES - 3) + "\n");
476
- const files = readDotenvFiles(dir);
477
- expect(files).toHaveLength(1);
478
- expect(files[0].content).toContain("X=aaa");
479
- } finally {
480
- rmSync(dir, { recursive: true, force: true });
481
- }
482
- });
483
-
484
- test("a file that is too big is not read", () => {
485
- const dir = mkdtempSync(join(tmpdir(), "wstest-"));
486
- try {
487
- writeFileSync(join(dir, ".env"), `X=${"a".repeat(DOTENV_MAX_BYTES + 10)}\n`);
488
- expect(readDotenvFiles(dir)).toEqual([]);
489
- } finally {
490
- rmSync(dir, { recursive: true, force: true });
491
- }
492
- });
493
- });
494
-
495
- describe("formatEnvStatusLine", () => {
496
- test("does not show a bare UNKNOWN next to an available dev script", () => {
497
- const line = formatEnvStatusLine(makeEnv({ hasDevScript: true, hasBuildScript: true }));
498
-
499
- expect(line).toContain("mode: UNKNOWN (no NODE_ENV or environment variable found)");
500
- expect(line).not.toContain("Workspace: UNKNOWN |");
501
- // Without a stored state the honest answer is "not checked" - the
502
- // old cache cannot tell a missing server from a platform where no
503
- // check was possible.
504
- expect(line).toContain("dev server: not checked");
505
- expect(line).toContain("build server: not checked");
506
- expect(line).toContain("dev script: available");
507
- expect(line).toContain("build script: available");
508
- expect(line).toContain("RAM: 7.7/15");
509
- });
510
-
511
- test("a stored server state is reported as it is", () => {
512
- const line = formatEnvStatusLine(
513
- makeEnv({ devServerState: "running", buildServerState: "not-detected" }),
514
- );
515
-
516
- expect(line).toContain("dev server: running");
517
- expect(line).toContain("build server: not detected");
518
- });
519
-
520
- test("the legacy server state signature still works", () => {
521
- expect(describeServerState(true, false)).toBe("running");
522
- expect(describeServerState(false, true)).toBe("not running (script available)");
523
- expect(describeServerState(false, false)).toBe("no script");
524
- });
525
-
526
- test("the line names the workspace path and the operating system", () => {
527
- const line = formatEnvStatusLine(makeEnv());
528
-
529
- expect(line).toContain(`Workspace: ${formatWorkspacePath("/home/tester/pi-harness-runtime")}`);
530
- expect(line).toContain(`OS: ${describeOs()}`);
531
- });
532
-
533
- test("an unchecked server is not reported as not running", () => {
534
- const line = formatEnvStatusLine(
535
- makeEnv({ devServerState: "not-checked", buildServerState: "not-checked" }),
536
- );
537
-
538
- expect(line).toContain("dev server: not checked");
539
- expect(line).not.toContain("dev server: not detected");
540
- expect(line).not.toContain("not running");
541
- });
542
-
543
- test("a running dev server is reported as running", () => {
544
- const line = formatEnvStatusLine(makeEnv({ mode: "development", hasDevScript: true, devServerRunning: true }));
545
-
546
- expect(line).toContain("mode: DEVELOPMENT");
547
- expect(line).toContain("dev server: running");
548
- });
549
-
550
- test("a missing RAM reading omits the RAM segment", () => {
551
- const line = formatEnvStatusLine(makeEnv({ ramTotalGb: null, ramAvailableGb: null }));
552
-
553
- expect(line).not.toContain("RAM:");
554
- });
555
-
556
- test("a partial RAM reading never renders a null value", () => {
557
- const available = formatEnvStatusLine(makeEnv({ ramTotalGb: null, ramAvailableGb: 7.7 }));
558
- const total = formatEnvStatusLine(makeEnv({ ramTotalGb: 15, ramAvailableGb: null }));
559
-
560
- expect(available).toContain("RAM: 7.7GB available");
561
- expect(available).not.toContain("null");
562
- expect(total).toContain("RAM: 15GB total");
563
- expect(total).not.toContain("null");
564
- });
565
- });
566
-
567
- describe("export surface of workspace-env.ts", () => {
568
- // The module itself needs node:sqlite, which bun 1.3.x does not provide.
569
- // Assert the re-export surface on the source text instead.
570
- const source = readFileSync(
571
- join(import.meta.dir, "..", "src", "workspace-env.ts"),
572
- "utf8",
573
- );
574
-
575
- test("keeps re-exporting the formatting helpers", () => {
576
- for (const name of [
577
- "describeServerState",
578
- "describeWorkspaceMode",
579
- "formatEnvForAgentResult",
580
- ]) {
581
- // The name must be part of an export statement, not only a word
582
- // somewhere in the file.
583
- expect(source).toMatch(new RegExp(`export\\s*\\{[^}]*\\b${name}\\b`, "s"));
584
- }
585
- });
586
-
587
- test("keeps exporting the detector functions", () => {
588
- for (const name of [
589
- "detectWorkspaceEnv",
590
- "loadWorkspaceEnv",
591
- "getWorkspaceEnv",
592
- "revalidateWorkspaceEnv",
593
- "formatEnvForAgent",
594
- "formatEnvStatusLine",
595
- ]) {
596
- expect(source).toContain(`export function ${name}`);
597
- }
598
- });
599
- });
600
-
601
- describe("formatEnvForAgentResult", () => {
602
- test("explains that a dev script does not establish the mode", () => {
603
- const text = formatEnvForAgentResult(makeEnv({ hasDevScript: true }));
604
-
605
- expect(text).toContain("Mode: UNKNOWN (no NODE_ENV or environment variable found)");
606
- expect(text).toContain("Dev script: yes");
607
- expect(text).toContain("Build allowed: no - requires explicit request");
608
- expect(text).toContain("a dev/build script only means a server CAN be started");
609
- });
610
-
611
- test("a known mode drops the unknown-mode note", () => {
612
- const text = formatEnvForAgentResult(makeEnv({ mode: "production", buildAllowed: true }));
613
-
614
- expect(text).toContain("Mode: PRODUCTION");
615
- expect(text).toContain("Build allowed: yes");
616
- expect(text).not.toContain("Environment unknown");
617
- });
618
- });
619
-
620
- describe("the template files of a workspace", () => {
621
- test("a plural template name is a template too", () => {
622
- expect(isTemplateDotenvName(".env.examples")).toBe(true);
623
- expect(isTemplateDotenvName(".env.samples")).toBe(true);
624
- expect(isTemplateDotenvName(".env.templates")).toBe(true);
625
- expect(isTemplateDotenvName(".env.example")).toBe(true);
626
- expect(isTemplateDotenvName(".env.local")).toBe(false);
627
- expect(isTemplateDotenvName(".env.production")).toBe(false);
628
- });
629
- });
630
-
631
- describe("the session variables of a desktop", () => {
632
- test("a session variable is not an environment selector", () => {
633
- expect(isSessionVariableKey("GNOME_SHELL_SESSION_MODE")).toBe(true);
634
- expect(isSessionVariableKey("XDG_SESSION_TYPE")).toBe(true);
635
- expect(isSessionVariableKey("DESKTOP_SESSION")).toBe(true);
636
- expect(isSessionVariableKey("DBUS_SESSION_BUS_ADDRESS")).toBe(true);
637
- });
638
-
639
- test("a real environment selector is not a session variable", () => {
640
- expect(isSessionVariableKey("NODE_ENV")).toBe(false);
641
- expect(isSessionVariableKey("AMOS_ENV")).toBe(false);
642
- expect(isSessionVariableKey("APP_MODE")).toBe(false);
643
- expect(isSessionVariableKey("RAILS_ENV")).toBe(false);
644
- });
645
-
646
- test("a session variable never decides the mode of a workspace", () => {
647
- // The scenario of the user: GNOME_SHELL_SESSION_MODE=ubuntu is set in
648
- // every GNOME shell and must not make a source repository unknown.
649
- const processVars = collectProcessEnvVars({
650
- GNOME_SHELL_SESSION_MODE: "ubuntu",
651
- });
652
-
653
- expect(processVars).toEqual([]);
654
- expect(resolveWorkspaceMode(processVars, []).mode).toBe("unknown");
655
- expect(resolveWorkspaceMode(processVars, []).hint).toBeNull();
656
- });
657
-
658
- test("a session variable in a dotenv file does not decide the mode either", () => {
659
- const vars = collectDotenvVars([
660
- { name: ".env", content: "XDG_SESSION_TYPE=x11\n" },
661
- ]);
662
-
663
- expect(resolveWorkspaceMode([], vars).hint).toBeNull();
664
- });
665
-
666
- test("a session variable does not shadow a real environment selector", () => {
667
- const vars = collectDotenvVars([
668
- { name: ".env", content: "GNOME_SHELL_SESSION_MODE=ubuntu\nAMOS_ENV=production\n" },
669
- ]);
670
- const resolved = resolveWorkspaceMode([], vars);
671
-
672
- expect(resolved.mode).toBe("production");
673
- expect(resolved.hint?.key).toBe("AMOS_ENV");
674
- });
675
- });
676
-
677
- describe("the mode detection of a source repository", () => {
678
- const published = {
679
- isGitRepository: true,
680
- branch: "main",
681
- isPublishedPackage: true,
682
- hasDeploymentMarker: false,
683
- hasRunningServer: false,
684
- };
685
-
686
- test("a published package without a runtime is a development checkout", () => {
687
- expect(modeFromRepositoryFacts(published)).toEqual({
688
- mode: "development",
689
- key: "source-only repository",
690
- });
691
- });
692
-
693
- test("a deployment configuration keeps the mode unknown", () => {
694
- const detection = modeFromRepositoryFacts({
695
- ...published,
696
- hasDeploymentMarker: true,
697
- });
698
-
699
- expect(detection).toBeNull();
700
- });
701
-
702
- test("a development branch is the fallback", () => {
703
- expect(
704
- modeFromRepositoryFacts({
705
- ...published,
706
- branch: "develop",
707
- isPublishedPackage: false,
708
- }),
709
- ).toEqual({ mode: "development", key: "git branch develop" });
710
- expect(
711
- modeFromRepositoryFacts({
712
- ...published,
713
- branch: "development",
714
- isPublishedPackage: false,
715
- }),
716
- ).toEqual({ mode: "development", key: "git branch development" });
717
- });
718
-
719
- test("a production branch is never a development checkout", () => {
720
- expect(
721
- modeFromRepositoryFacts({
722
- ...published,
723
- branch: "main",
724
- isPublishedPackage: false,
725
- }),
726
- ).toBeNull();
727
- expect(
728
- modeFromRepositoryFacts({
729
- ...published,
730
- branch: "production",
731
- isPublishedPackage: false,
732
- }),
733
- ).toBeNull();
734
- });
735
-
736
- test("a directory that is not a repository names no mode", () => {
737
- expect(
738
- modeFromRepositoryFacts({
739
- ...published,
740
- isGitRepository: false,
741
- }),
742
- ).toBeNull();
743
- });
744
-
745
- test("the label names the reason of the detection", () => {
746
- const env = makeEnv({
747
- mode: "development",
748
- buildAllowed: true,
749
- evidence: [
750
- { source: "detection", key: "source-only repository", value: "development", timestamp: 0 },
751
- ],
752
- });
753
-
754
- expect(describeWorkspaceMode(env)).toBe("DEVELOPMENT (source-only repository)");
755
- });
756
- });
757
-
758
- describe("the low memory rule", () => {
759
- test("flags a machine with almost no free memory", () => {
760
- expect(isLowRamWarning(64, 2)).toBe(true);
761
- expect(isLowRamWarning(16, 3.9)).toBe(true);
762
- });
763
-
764
- test("does not flag a small machine with real headroom", () => {
765
- expect(isLowRamWarning(15, 6.6)).toBe(false);
766
- expect(isLowRamWarning(8, 8)).toBe(false);
767
- });
768
-
769
- test("flags a big machine that ran out of memory", () => {
770
- expect(isLowRamWarning(64, 3)).toBe(true);
771
- });
772
-
773
- test("does not flag an unknown memory state", () => {
774
- expect(isLowRamWarning(null, null)).toBe(false);
775
- expect(isLowRamWarning(null, 12)).toBe(false);
776
- });
777
- });
778
-
779
- describe("the build permission", () => {
780
- test("allows a build when the environment allows it", () => {
781
- expect(buildAllowanceReason(makeEnv({ mode: "development", buildAllowed: true }))).toBe("yes");
782
- });
783
-
784
- test("asks for an explicit request when the mode is unknown", () => {
785
- expect(
786
- buildAllowanceReason(makeEnv({ mode: "unknown", buildAllowed: false, lowRamWarning: false })),
787
- ).toBe("no - requires explicit request");
788
- });
789
-
790
- test("names the memory shortage when the memory is the reason", () => {
791
- expect(
792
- buildAllowanceReason(makeEnv({ mode: "development", buildAllowed: false, lowRamWarning: true })),
793
- ).toBe("no - low memory");
794
- });
795
-
796
- test("appears in the agent context", () => {
797
- const message = formatEnvForAgentResult(
798
- makeEnv({ mode: "development", buildAllowed: false, lowRamWarning: true }),
799
- );
800
- expect(message).toContain("Build allowed: no - low memory");
801
- });
802
- });