pi-harness-runtime 1.1.440 → 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 (349) hide show
  1. package/harness/e2e/auto-test-loop.ts +2 -0
  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/key-loader.ts +2 -0
  7. package/harness/langchain/checkpointer.ts +2 -0
  8. package/harness/langchain/cron-cli.ts +2 -0
  9. package/harness/langchain/graph.ts +2 -0
  10. package/harness/notification-center.ts +2 -0
  11. package/harness/secret-provider.ts +2 -0
  12. package/index.ts +2 -0
  13. package/package.json +7 -1
  14. package/packages/a2a-adapter/package.json +1 -1
  15. package/packages/agent-policy/dist/extension.d.ts.map +1 -1
  16. package/packages/agent-policy/dist/extension.js +2 -0
  17. package/packages/agent-policy/dist/extension.js.map +1 -1
  18. package/packages/agent-policy/package.json +1 -1
  19. package/packages/agent-policy/src/extension.ts +2 -0
  20. package/packages/architecture-generator/package.json +1 -1
  21. package/packages/auth/dist/minimax-browser-auth.d.ts.map +1 -1
  22. package/packages/auth/dist/minimax-browser-auth.js +23 -1
  23. package/packages/auth/dist/minimax-browser-auth.js.map +1 -1
  24. package/packages/auth/package.json +1 -1
  25. package/packages/auth/src/minimax-browser-auth.ts +30 -5
  26. package/packages/autonomous-refactor/package.json +1 -1
  27. package/packages/autonomous-runtime/dist/inbox.d.ts.map +1 -1
  28. package/packages/autonomous-runtime/dist/inbox.js +2 -0
  29. package/packages/autonomous-runtime/dist/inbox.js.map +1 -1
  30. package/packages/autonomous-runtime/package.json +1 -1
  31. package/packages/autonomous-runtime/src/inbox.ts +2 -0
  32. package/packages/cache-strategy/package.json +1 -1
  33. package/packages/capability-registry/package.json +1 -1
  34. package/packages/checkpoint/package.json +1 -1
  35. package/packages/cli-plugin-sdk/package.json +1 -1
  36. package/packages/clipboard/dist/src/clipboard.d.ts.map +1 -1
  37. package/packages/clipboard/dist/src/clipboard.js +2 -0
  38. package/packages/clipboard/dist/src/clipboard.js.map +1 -1
  39. package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -1
  40. package/packages/clipboard/dist/src/copy-sync.js +2 -0
  41. package/packages/clipboard/dist/src/copy-sync.js.map +1 -1
  42. package/packages/clipboard/dist/src/gist-relay.d.ts.map +1 -1
  43. package/packages/clipboard/dist/src/gist-relay.js +2 -0
  44. package/packages/clipboard/dist/src/gist-relay.js.map +1 -1
  45. package/packages/clipboard/package.json +1 -1
  46. package/packages/clipboard/src/clipboard.ts +2 -0
  47. package/packages/clipboard/src/copy-sync.ts +2 -0
  48. package/packages/clipboard/src/gist-relay.ts +2 -0
  49. package/packages/clipboard-plugin/package.json +1 -1
  50. package/packages/code-generation/package.json +1 -1
  51. package/packages/code-review/package.json +1 -1
  52. package/packages/codex-adapter/package.json +1 -1
  53. package/packages/codex-watcher/package.json +1 -1
  54. package/packages/config-capture/package.json +1 -1
  55. package/packages/connection-runtime/package.json +1 -1
  56. package/packages/context-compiler/package.json +1 -1
  57. package/packages/context-discovery/package.json +1 -1
  58. package/packages/context-manager/package.json +1 -1
  59. package/packages/contracts/dist/delegation-store.js +1 -4
  60. package/packages/contracts/dist/workspace-env.js +28 -1
  61. package/packages/contracts/package.json +1 -1
  62. package/packages/contracts/src/delegation-store.ts +9 -4
  63. package/packages/contracts/src/workspace-env.ts +29 -4
  64. package/packages/cookie-sanitizer/package.json +1 -1
  65. package/packages/cost-optimizer/package.json +1 -1
  66. package/packages/dependency-analyzer/package.json +1 -1
  67. package/packages/django-plugin/package.json +1 -1
  68. package/packages/doc-generator/package.json +1 -1
  69. package/packages/durable-review/package.json +1 -1
  70. package/packages/e2e-tester/package.json +1 -1
  71. package/packages/evaluation-engine/package.json +1 -1
  72. package/packages/evaluation-runner/package.json +1 -1
  73. package/packages/event-bus/package.json +1 -1
  74. package/packages/event-sink/package.json +1 -1
  75. package/packages/event-store/dist/src/store.d.ts.map +1 -1
  76. package/packages/event-store/dist/src/store.js +2 -0
  77. package/packages/event-store/dist/src/store.js.map +1 -1
  78. package/packages/event-store/package.json +1 -1
  79. package/packages/event-store/src/store.ts +3 -0
  80. package/packages/experience-replay/package.json +1 -1
  81. package/packages/feedback-collector/package.json +1 -1
  82. package/packages/file-copy-helper/package.json +1 -1
  83. package/packages/framework-detector/package.json +1 -1
  84. package/packages/framework-plugin-sdk/package.json +1 -1
  85. package/packages/frappe-plugin/package.json +1 -1
  86. package/packages/generic-web-plugin/dist/analyzer.d.ts.map +1 -1
  87. package/packages/generic-web-plugin/dist/analyzer.js +2 -0
  88. package/packages/generic-web-plugin/dist/analyzer.js.map +1 -1
  89. package/packages/generic-web-plugin/package.json +1 -1
  90. package/packages/generic-web-plugin/src/analyzer.ts +3 -0
  91. package/packages/health-monitor/package.json +1 -1
  92. package/packages/intent-analyzer/package.json +1 -1
  93. package/packages/jev-judge/package.json +1 -1
  94. package/packages/knowledge-graph/package.json +1 -1
  95. package/packages/knowledge-retrieval/dist/mcp-client.d.ts.map +1 -1
  96. package/packages/knowledge-retrieval/dist/mcp-client.js +0 -1
  97. package/packages/knowledge-retrieval/dist/mcp-client.js.map +1 -1
  98. package/packages/knowledge-retrieval/package.json +1 -4
  99. package/packages/knowledge-retrieval/src/mcp-client.ts +0 -1
  100. package/packages/laravel-plugin/package.json +1 -1
  101. package/packages/learning-engine/package.json +1 -1
  102. package/packages/logging/package.json +1 -1
  103. package/packages/mcp-adapter/dist/server.d.ts.map +1 -1
  104. package/packages/mcp-adapter/dist/server.js +2 -0
  105. package/packages/mcp-adapter/dist/server.js.map +1 -1
  106. package/packages/mcp-adapter/package.json +1 -1
  107. package/packages/mcp-adapter/src/server.ts +3 -0
  108. package/packages/memory-engine/package.json +1 -1
  109. package/packages/milestone-manager/package.json +1 -1
  110. package/packages/model-registry/package.json +1 -1
  111. package/packages/nextjs-plugin/package.json +1 -1
  112. package/packages/notification/package.json +1 -1
  113. package/packages/observability/package.json +1 -1
  114. package/packages/okf-indexer/package.json +1 -1
  115. package/packages/paperclip-integration/dist/index.d.ts.map +1 -1
  116. package/packages/paperclip-integration/dist/index.js +2 -0
  117. package/packages/paperclip-integration/dist/index.js.map +1 -1
  118. package/packages/paperclip-integration/package.json +1 -1
  119. package/packages/paperclip-integration/src/index.ts +2 -0
  120. package/packages/performance-optimizer/package.json +1 -1
  121. package/packages/permission-mode/package.json +1 -1
  122. package/packages/privilege-broker/dist/audit.d.ts.map +1 -1
  123. package/packages/privilege-broker/dist/audit.js +2 -0
  124. package/packages/privilege-broker/dist/audit.js.map +1 -1
  125. package/packages/privilege-broker/package.json +1 -1
  126. package/packages/privilege-broker/src/audit.ts +2 -0
  127. package/packages/project-analyzer/package.json +1 -1
  128. package/packages/project-bootstrap/package.json +1 -1
  129. package/packages/projection-engine/package.json +1 -1
  130. package/packages/prompt-compiler/dist/compiler.d.ts.map +1 -1
  131. package/packages/prompt-compiler/dist/compiler.js +2 -0
  132. package/packages/prompt-compiler/dist/compiler.js.map +1 -1
  133. package/packages/prompt-compiler/package.json +1 -1
  134. package/packages/prompt-compiler/src/compiler.ts +2 -0
  135. package/packages/prompt-versioning/package.json +1 -1
  136. package/packages/provider-adapter-sdk/package.json +1 -1
  137. package/packages/provider-router/package.json +1 -1
  138. package/packages/provider-selector/package.json +1 -1
  139. package/packages/providers/package.json +1 -1
  140. package/packages/qdrant-skills/package.json +1 -1
  141. package/packages/quota-manager/package.json +1 -1
  142. package/packages/rate-limiter/package.json +1 -1
  143. package/packages/react-vite-plugin/package.json +1 -1
  144. package/packages/relay/package.json +1 -1
  145. package/packages/release-manager/package.json +1 -1
  146. package/packages/requirement-compiler/package.json +1 -1
  147. package/packages/runtime/package.json +1 -1
  148. package/packages/scheduler/package.json +1 -1
  149. package/packages/scheduler-adapter/package.json +1 -1
  150. package/packages/session/package.json +1 -1
  151. package/packages/session-api/package.json +1 -1
  152. package/packages/session-export/package.json +1 -1
  153. package/packages/shared-context/package.json +1 -1
  154. package/packages/skill-mcp-client/dist/client.d.ts.map +1 -1
  155. package/packages/skill-mcp-client/dist/client.js +0 -1
  156. package/packages/skill-mcp-client/dist/client.js.map +1 -1
  157. package/packages/skill-mcp-client/package.json +1 -4
  158. package/packages/skill-mcp-client/src/client.ts +0 -1
  159. package/packages/skill-registry/package.json +1 -1
  160. package/packages/skills/dist/parser.d.ts.map +1 -1
  161. package/packages/skills/dist/parser.js +2 -0
  162. package/packages/skills/dist/parser.js.map +1 -1
  163. package/packages/skills/dist/skill-versioning.d.ts.map +1 -1
  164. package/packages/skills/dist/skill-versioning.js +2 -0
  165. package/packages/skills/dist/skill-versioning.js.map +1 -1
  166. package/packages/skills/package.json +1 -1
  167. package/packages/skills/src/parser.ts +3 -0
  168. package/packages/skills/src/skill-versioning.ts +2 -0
  169. package/packages/sprint-planner/package.json +1 -1
  170. package/packages/subscription-engine/package.json +1 -1
  171. package/packages/task-compiler/package.json +1 -1
  172. package/packages/tencentdb-memory/package.json +1 -1
  173. package/packages/tencentdb-sync/package.json +1 -1
  174. package/packages/test-data-generator/package.json +1 -1
  175. package/packages/test-generator/package.json +1 -1
  176. package/packages/todo-bd-sync/package.json +1 -1
  177. package/packages/token-estimation/package.json +1 -1
  178. package/packages/token-optimizer/package.json +1 -1
  179. package/packages/tooling-preference/package.json +1 -1
  180. package/packages/trajectory/package.json +1 -1
  181. package/packages/tui/package.json +1 -1
  182. package/packages/two-phase-review/package.json +1 -1
  183. package/packages/types/package.json +1 -1
  184. package/packages/voice-api/package.json +1 -1
  185. package/packages/webmcp-tester/dist/index.js +0 -2
  186. package/packages/webmcp-tester/package.json +1 -1
  187. package/packages/webmcp-tester/src/index.ts +0 -3
  188. package/packages/wiki/package.json +1 -1
  189. package/packages/workflow-events/package.json +1 -1
  190. package/packages/workspace-scanner/package.json +1 -1
  191. package/packages/worktree/package.json +1 -1
  192. package/packages/write-review/dist/trigger.d.ts.map +1 -1
  193. package/packages/write-review/dist/trigger.js +1 -2
  194. package/packages/write-review/dist/trigger.js.map +1 -1
  195. package/packages/write-review/package.json +1 -1
  196. package/packages/write-review/src/trigger.ts +1 -2
  197. package/scripts/lib/runtime-imports.ts +189 -3
  198. package/scripts/telegram-webhook-server.ts +2 -0
  199. package/scripts/verify-package-contents.ts +148 -1
  200. package/harness/e2e/tools-detector.test.ts +0 -155
  201. package/harness/glm-quota-countdown.test.ts +0 -323
  202. package/harness/langchain/agents.test.ts +0 -39
  203. package/harness/langchain/checkpointer.test.ts +0 -243
  204. package/harness/langchain/daemon.test.ts +0 -459
  205. package/harness/langchain/model-router.test.ts +0 -191
  206. package/harness/langchain/ping-pong-middleware.test.ts +0 -69
  207. package/harness/langchain/scoreboard.test.ts +0 -267
  208. package/harness/langchain/status-line.test.ts +0 -106
  209. package/harness/langchain/surge.test.ts +0 -467
  210. package/harness/langchain/widget.test.ts +0 -251
  211. package/harness/skill-sync.test.ts +0 -57
  212. package/harness/ssh-detach-interceptor.test.ts +0 -263
  213. package/harness/ssh-hang-recovery.test.ts +0 -86
  214. package/packages/a2a-adapter/test/a2a.test.ts +0 -151
  215. package/packages/agent-policy/test/interlock.test.ts +0 -101
  216. package/packages/agent-policy/test/policy-retention.test.ts +0 -380
  217. package/packages/agent-policy/test/policy.test.ts +0 -387
  218. package/packages/auth/test/auth.test.ts +0 -238
  219. package/packages/autonomous-refactor/test/refactor.test.ts +0 -202
  220. package/packages/autonomous-runtime/test/types.test.ts +0 -95
  221. package/packages/cache-strategy/test/cache.test.ts +0 -137
  222. package/packages/capability-registry/test/registry.test.ts +0 -204
  223. package/packages/checkpoint/test/engine.test.ts +0 -265
  224. package/packages/cli-plugin-sdk/test/cli.test.ts +0 -45
  225. package/packages/code-generation/test/generator.test.ts +0 -238
  226. package/packages/code-review/test/engine.test.ts +0 -302
  227. package/packages/codex-adapter/test/provider.test.ts +0 -142
  228. package/packages/codex-watcher/test/watch.test.ts +0 -453
  229. package/packages/context-compiler/test/context-compiler.test.ts +0 -746
  230. package/packages/context-manager/test/context.test.ts +0 -198
  231. package/packages/contracts/test/delegation.test.ts +0 -350
  232. package/packages/contracts/test/workspace-env.test.ts +0 -802
  233. package/packages/cookie-sanitizer/test/infer.test.ts +0 -110
  234. package/packages/cookie-sanitizer/test/normalize.test.ts +0 -113
  235. package/packages/cookie-sanitizer/test/parse.test.ts +0 -204
  236. package/packages/cookie-sanitizer/test/sync.test.ts +0 -237
  237. package/packages/cookie-sanitizer/test/watcher.test.ts +0 -27
  238. package/packages/cost-optimizer/test/cost-optimizer.test.ts +0 -438
  239. package/packages/dependency-analyzer/test/analyzer.test.ts +0 -49
  240. package/packages/django-plugin/test/analyzer.test.ts +0 -195
  241. package/packages/doc-generator/test/docs.test.ts +0 -168
  242. package/packages/durable-review/test/acceptance.test.ts +0 -357
  243. package/packages/durable-review/test/smoke.test.ts +0 -107
  244. package/packages/evaluation-engine/test/engine.test.ts +0 -277
  245. package/packages/evaluation-runner/test/runner.test.ts +0 -168
  246. package/packages/event-bus/test/bus.test.ts +0 -100
  247. package/packages/event-store/test/store.test.ts +0 -115
  248. package/packages/experience-replay/test/replay.test.ts +0 -267
  249. package/packages/feedback-collector/test/collector.test.ts +0 -142
  250. package/packages/framework-detector/test/detector.test.ts +0 -75
  251. package/packages/framework-plugin-sdk/test/manager.test.ts +0 -320
  252. package/packages/frappe-plugin/test/analyzer.test.ts +0 -250
  253. package/packages/generic-web-plugin/test/analyzer.test.ts +0 -134
  254. package/packages/health-monitor/test/health.test.ts +0 -231
  255. package/packages/intent-analyzer/test/analyzer.test.ts +0 -72
  256. package/packages/intent-analyzer/test/ping-pong.test.ts +0 -96
  257. package/packages/jev-judge/tests/auto-continue-context.test.ts +0 -280
  258. package/packages/jev-judge/tests/continuation-controller.test.ts +0 -1396
  259. package/packages/jev-judge/tests/jev-provider-chain.test.ts +0 -536
  260. package/packages/jev-judge/tests/outcome-application.test.ts +0 -132
  261. package/packages/jev-judge/tests/question-risk.test.ts +0 -129
  262. package/packages/jev-judge/tests/typesafe-provider.test.ts +0 -640
  263. package/packages/jev-judge/tests/validation-ledger.test.ts +0 -181
  264. package/packages/jev-judge/tests/validation-plan.test.ts +0 -170
  265. package/packages/jev-judge/tests/validation-tie-breaker.test.ts +0 -88
  266. package/packages/laravel-plugin/test/analyzer.test.ts +0 -220
  267. package/packages/learning-engine/test/engine.test.ts +0 -331
  268. package/packages/logging/test/buffer.test.ts +0 -44
  269. package/packages/logging/test/classification.test.ts +0 -137
  270. package/packages/logging/test/config.test.ts +0 -97
  271. package/packages/logging/test/fanout.test.ts +0 -186
  272. package/packages/logging/test/identity-transport.test.ts +0 -197
  273. package/packages/logging/test/incidents.test.ts +0 -404
  274. package/packages/logging/test/ingest-schema.test.ts +0 -184
  275. package/packages/logging/test/integration.test.ts +0 -140
  276. package/packages/logging/test/log-http.test.ts +0 -246
  277. package/packages/logging/test/redact.test.ts +0 -109
  278. package/packages/logging/test/sampler.test.ts +0 -57
  279. package/packages/logging/test/server.test.ts +0 -444
  280. package/packages/logging/test/shipper.test.ts +0 -152
  281. package/packages/logging/test/spool.test.ts +0 -96
  282. package/packages/mcp-adapter/test/server.test.ts +0 -91
  283. package/packages/memory-engine/test/engine.test.ts +0 -456
  284. package/packages/model-registry/test/registry.test.ts +0 -342
  285. package/packages/nextjs-plugin/test/analyzer.test.ts +0 -149
  286. package/packages/notification/test/approval-payload.test.ts +0 -269
  287. package/packages/notification/test/detail-format.test.ts +0 -106
  288. package/packages/notification/test/notification.test.ts +0 -375
  289. package/packages/notification/test/pending-question-lifecycle.test.ts +0 -195
  290. package/packages/notification/test/pending-question.test.ts +0 -331
  291. package/packages/notification/test/tooling-proposal-payload.test.ts +0 -212
  292. package/packages/observability/test/observability.test.ts +0 -1350
  293. package/packages/performance-optimizer/test/optimizer.test.ts +0 -267
  294. package/packages/project-analyzer/test/index.test.ts +0 -1719
  295. package/packages/prompt-compiler/test/prompt-compiler.test.ts +0 -813
  296. package/packages/prompt-versioning/test/versioning.test.ts +0 -112
  297. package/packages/provider-adapter-sdk/test/sdk.test.ts +0 -220
  298. package/packages/provider-router/test/provider-router.test.ts +0 -405
  299. package/packages/provider-selector/test/selector.test.ts +0 -192
  300. package/packages/providers/test/providers.test.ts +0 -225
  301. package/packages/qdrant-skills/test/collection-info.test.ts +0 -165
  302. package/packages/qdrant-skills/test/embedder-batch.test.ts +0 -184
  303. package/packages/qdrant-skills/test/embedder.test.ts +0 -97
  304. package/packages/quota-manager/test/quota.test.ts +0 -432
  305. package/packages/rate-limiter/test/limiter.test.ts +0 -128
  306. package/packages/react-vite-plugin/test/analyzer.test.ts +0 -139
  307. package/packages/relay/test/client.test.ts +0 -446
  308. package/packages/relay/test/relay-e2e.test.ts +0 -299
  309. package/packages/relay/test/relay-endpoint.test.ts +0 -278
  310. package/packages/release-manager/test/release.test.ts +0 -215
  311. package/packages/requirement-compiler/test/requirement-compiler.test.ts +0 -743
  312. package/packages/runtime/test/runtime-api.test.ts +0 -169
  313. package/packages/runtime/test/runtime.test.ts +0 -297
  314. package/packages/scheduler/test/scheduler.test.ts +0 -125
  315. package/packages/session/test/manager.test.ts +0 -233
  316. package/packages/shared-context/test/shared-context.test.ts +0 -114
  317. package/packages/skill-registry/test/registry.test.ts +0 -508
  318. package/packages/skills/test/init-skills.test.ts +0 -114
  319. package/packages/skills/test/remote-skills.test.ts +0 -360
  320. package/packages/skills/test/remote-source.test.ts +0 -248
  321. package/packages/skills/test/sanitize.test.ts +0 -250
  322. package/packages/skills/test/vector-indexing.test.ts +0 -205
  323. package/packages/skills/test/vector-schema.test.ts +0 -180
  324. package/packages/skills/test/vector-threshold.test.ts +0 -108
  325. package/packages/sprint-planner/test/planner.test.ts +0 -187
  326. package/packages/task-compiler/test/task-compiler.test.ts +0 -628
  327. package/packages/test-data-generator/test/generator.test.ts +0 -265
  328. package/packages/test-generator/test/generator.test.ts +0 -156
  329. package/packages/token-estimation/test/token-estimation.test.ts +0 -207
  330. package/packages/token-optimizer/test/optimizer.test.ts +0 -141
  331. package/packages/tooling-preference/tests/changed-files.test.ts +0 -124
  332. package/packages/tooling-preference/tests/detect.test.ts +0 -188
  333. package/packages/tooling-preference/tests/proposal.test.ts +0 -107
  334. package/packages/tooling-preference/tests/quality-check.test.ts +0 -272
  335. package/packages/tooling-preference/tests/registry.test.ts +0 -51
  336. package/packages/tooling-preference/tests/store.test.ts +0 -99
  337. package/packages/trajectory/test/store.test.ts +0 -250
  338. package/packages/tui/test/harness-footer.test.ts +0 -559
  339. package/packages/tui/test/harness-header.test.ts +0 -590
  340. package/packages/two-phase-review/test/classify.test.ts +0 -102
  341. package/packages/two-phase-review/test/helpers.ts +0 -72
  342. package/packages/two-phase-review/test/rules.test.ts +0 -150
  343. package/packages/two-phase-review/test/runtime.test.ts +0 -362
  344. package/packages/two-phase-review/test/scope.test.ts +0 -186
  345. package/packages/two-phase-review/test/signals.test.ts +0 -183
  346. package/packages/two-phase-review/test/state.test.ts +0 -201
  347. package/packages/wiki/test/basic.test.ts +0 -242
  348. package/packages/workspace-scanner/test/scanner.test.ts +0 -43
  349. 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
- });