@tdk-landscape/tdk-cli-core 1.3.52 → 1.3.54

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 (390) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +1 -1
  3. package/cli/dist/commands/config.js +1 -1
  4. package/cli/dist/commands/resource.js +1 -1
  5. package/cli/dist/generator/extension-fetch.js +16 -9
  6. package/cli/package.json +1 -1
  7. package/package.json +16 -1
  8. package/.claude/skills/layer-autoresearch/SKILL.md +0 -48
  9. package/.claude/skills/tdk-troubleshoot/SKILL.md +0 -222
  10. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -40
  11. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  12. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -18
  13. package/.github/dependabot.yml +0 -33
  14. package/.github/pull_request_template.md +0 -5
  15. package/.github/workflows/ci.yml +0 -200
  16. package/.github/workflows/release-binaries.yml +0 -139
  17. package/.github/zizmor.yml +0 -9
  18. package/.pre-commit-config.yaml +0 -57
  19. package/CODE_OF_CONDUCT.md +0 -9
  20. package/CONTRIBUTING.md +0 -42
  21. package/Makefile +0 -92
  22. package/SECURITY.md +0 -17
  23. package/benchmarks/results/container-scale-2026-09-25T20-54-49-736Z.json +0 -45
  24. package/benchmarks/results/container-scale-2026-09-25T21-01-20-540Z.json +0 -150
  25. package/benchmarks/results/container-scale-2026-09-25T21-15-01-441Z.json +0 -73
  26. package/benchmarks/results/container-scale-2026-09-25T22-09-01-630Z.json +0 -77
  27. package/benchmarks/results/container-scale-2026-09-25T22-18-28-371Z.json +0 -202
  28. package/cli/AGENTS.md +0 -390
  29. package/cli/DESIGN.md +0 -735
  30. package/cli/README.md +0 -292
  31. package/cli/biome.json +0 -40
  32. package/cli/dependency-graph.svg +0 -1135
  33. package/cli/dist/cli.d.ts +0 -3
  34. package/cli/dist/cli.d.ts.map +0 -1
  35. package/cli/dist/cli.js.map +0 -1
  36. package/cli/dist/commands/completion.d.ts +0 -3
  37. package/cli/dist/commands/completion.d.ts.map +0 -1
  38. package/cli/dist/commands/completion.js.map +0 -1
  39. package/cli/dist/commands/config.d.ts +0 -3
  40. package/cli/dist/commands/config.d.ts.map +0 -1
  41. package/cli/dist/commands/config.js.map +0 -1
  42. package/cli/dist/commands/doctor.d.ts +0 -19
  43. package/cli/dist/commands/doctor.d.ts.map +0 -1
  44. package/cli/dist/commands/doctor.js.map +0 -1
  45. package/cli/dist/commands/down.d.ts +0 -3
  46. package/cli/dist/commands/down.d.ts.map +0 -1
  47. package/cli/dist/commands/down.js.map +0 -1
  48. package/cli/dist/commands/help.d.ts +0 -2
  49. package/cli/dist/commands/help.d.ts.map +0 -1
  50. package/cli/dist/commands/help.js.map +0 -1
  51. package/cli/dist/commands/networks.d.ts +0 -3
  52. package/cli/dist/commands/networks.d.ts.map +0 -1
  53. package/cli/dist/commands/networks.js.map +0 -1
  54. package/cli/dist/commands/project.d.ts +0 -9
  55. package/cli/dist/commands/project.d.ts.map +0 -1
  56. package/cli/dist/commands/project.js.map +0 -1
  57. package/cli/dist/commands/projects.d.ts +0 -3
  58. package/cli/dist/commands/projects.d.ts.map +0 -1
  59. package/cli/dist/commands/projects.js.map +0 -1
  60. package/cli/dist/commands/resource.d.ts +0 -69
  61. package/cli/dist/commands/resource.d.ts.map +0 -1
  62. package/cli/dist/commands/resource.js.map +0 -1
  63. package/cli/dist/commands/resources.d.ts +0 -3
  64. package/cli/dist/commands/resources.d.ts.map +0 -1
  65. package/cli/dist/commands/resources.js.map +0 -1
  66. package/cli/dist/commands/stack.d.ts +0 -3
  67. package/cli/dist/commands/stack.d.ts.map +0 -1
  68. package/cli/dist/commands/stack.js.map +0 -1
  69. package/cli/dist/commands/stacks.d.ts +0 -3
  70. package/cli/dist/commands/stacks.d.ts.map +0 -1
  71. package/cli/dist/commands/stacks.js.map +0 -1
  72. package/cli/dist/commands/status.d.ts +0 -3
  73. package/cli/dist/commands/status.d.ts.map +0 -1
  74. package/cli/dist/commands/status.js.map +0 -1
  75. package/cli/dist/commands/ui.d.ts +0 -3
  76. package/cli/dist/commands/ui.d.ts.map +0 -1
  77. package/cli/dist/commands/ui.js.map +0 -1
  78. package/cli/dist/commands/up.d.ts +0 -3
  79. package/cli/dist/commands/up.d.ts.map +0 -1
  80. package/cli/dist/commands/up.js.map +0 -1
  81. package/cli/dist/commands/upgrade.d.ts +0 -14
  82. package/cli/dist/commands/upgrade.d.ts.map +0 -1
  83. package/cli/dist/commands/upgrade.js.map +0 -1
  84. package/cli/dist/commands/version.d.ts +0 -3
  85. package/cli/dist/commands/version.d.ts.map +0 -1
  86. package/cli/dist/commands/version.js.map +0 -1
  87. package/cli/dist/components/Accessible.d.ts +0 -8
  88. package/cli/dist/components/Accessible.d.ts.map +0 -1
  89. package/cli/dist/components/Accessible.js.map +0 -1
  90. package/cli/dist/components/BaseTooltip.d.ts +0 -4
  91. package/cli/dist/components/BaseTooltip.d.ts.map +0 -1
  92. package/cli/dist/components/BaseTooltip.js.map +0 -1
  93. package/cli/dist/components/DetailPanel.d.ts +0 -4
  94. package/cli/dist/components/DetailPanel.d.ts.map +0 -1
  95. package/cli/dist/components/DetailPanel.js.map +0 -1
  96. package/cli/dist/components/FileTree.d.ts +0 -4
  97. package/cli/dist/components/FileTree.d.ts.map +0 -1
  98. package/cli/dist/components/FileTree.js.map +0 -1
  99. package/cli/dist/components/ResourceSelectInput.d.ts +0 -4
  100. package/cli/dist/components/ResourceSelectInput.d.ts.map +0 -1
  101. package/cli/dist/components/ResourceSelectInput.js.map +0 -1
  102. package/cli/dist/components/ResourceTable.d.ts +0 -4
  103. package/cli/dist/components/ResourceTable.d.ts.map +0 -1
  104. package/cli/dist/components/ResourceTable.js.map +0 -1
  105. package/cli/dist/components/TabBar.d.ts +0 -4
  106. package/cli/dist/components/TabBar.d.ts.map +0 -1
  107. package/cli/dist/components/TabBar.js.map +0 -1
  108. package/cli/dist/components/Tooltip.d.ts +0 -12
  109. package/cli/dist/components/Tooltip.d.ts.map +0 -1
  110. package/cli/dist/components/Tooltip.js.map +0 -1
  111. package/cli/dist/components/index.d.ts +0 -8
  112. package/cli/dist/components/index.d.ts.map +0 -1
  113. package/cli/dist/components/index.js.map +0 -1
  114. package/cli/dist/config/platform-standards.d.ts +0 -159
  115. package/cli/dist/config/platform-standards.d.ts.map +0 -1
  116. package/cli/dist/config/platform-standards.js.map +0 -1
  117. package/cli/dist/generator/extension-fetch.d.ts +0 -25
  118. package/cli/dist/generator/extension-fetch.d.ts.map +0 -1
  119. package/cli/dist/generator/extension-fetch.js.map +0 -1
  120. package/cli/dist/generator/template-engine.d.ts +0 -62
  121. package/cli/dist/generator/template-engine.d.ts.map +0 -1
  122. package/cli/dist/generator/template-engine.js.map +0 -1
  123. package/cli/dist/index.d.ts +0 -11
  124. package/cli/dist/index.d.ts.map +0 -1
  125. package/cli/dist/index.js.map +0 -1
  126. package/cli/dist/types/index.d.ts +0 -257
  127. package/cli/dist/types/index.d.ts.map +0 -1
  128. package/cli/dist/types/index.js.map +0 -1
  129. package/cli/dist/utils/cache.d.ts +0 -19
  130. package/cli/dist/utils/cache.d.ts.map +0 -1
  131. package/cli/dist/utils/cache.js.map +0 -1
  132. package/cli/dist/utils/command-helpers.d.ts +0 -17
  133. package/cli/dist/utils/command-helpers.d.ts.map +0 -1
  134. package/cli/dist/utils/command-helpers.js.map +0 -1
  135. package/cli/dist/utils/constants.d.ts +0 -53
  136. package/cli/dist/utils/constants.d.ts.map +0 -1
  137. package/cli/dist/utils/constants.js.map +0 -1
  138. package/cli/dist/utils/discovery-context.d.ts +0 -4
  139. package/cli/dist/utils/discovery-context.d.ts.map +0 -1
  140. package/cli/dist/utils/discovery-context.js.map +0 -1
  141. package/cli/dist/utils/docker.d.ts +0 -4
  142. package/cli/dist/utils/docker.d.ts.map +0 -1
  143. package/cli/dist/utils/docker.js.map +0 -1
  144. package/cli/dist/utils/doctor-runtime.d.ts +0 -81
  145. package/cli/dist/utils/doctor-runtime.d.ts.map +0 -1
  146. package/cli/dist/utils/doctor-runtime.js.map +0 -1
  147. package/cli/dist/utils/env-validator.d.ts +0 -15
  148. package/cli/dist/utils/env-validator.d.ts.map +0 -1
  149. package/cli/dist/utils/env-validator.js.map +0 -1
  150. package/cli/dist/utils/errors.d.ts +0 -31
  151. package/cli/dist/utils/errors.d.ts.map +0 -1
  152. package/cli/dist/utils/errors.js.map +0 -1
  153. package/cli/dist/utils/file-helpers.d.ts +0 -14
  154. package/cli/dist/utils/file-helpers.d.ts.map +0 -1
  155. package/cli/dist/utils/file-helpers.js.map +0 -1
  156. package/cli/dist/utils/formatting.d.ts +0 -30
  157. package/cli/dist/utils/formatting.d.ts.map +0 -1
  158. package/cli/dist/utils/formatting.js.map +0 -1
  159. package/cli/dist/utils/paths.d.ts +0 -3
  160. package/cli/dist/utils/paths.d.ts.map +0 -1
  161. package/cli/dist/utils/paths.js.map +0 -1
  162. package/cli/dist/utils/port-assignment.d.ts +0 -6
  163. package/cli/dist/utils/port-assignment.d.ts.map +0 -1
  164. package/cli/dist/utils/port-assignment.js.map +0 -1
  165. package/cli/dist/utils/project-features.d.ts +0 -23
  166. package/cli/dist/utils/project-features.d.ts.map +0 -1
  167. package/cli/dist/utils/project-features.js.map +0 -1
  168. package/cli/dist/utils/project-templates.d.ts +0 -7
  169. package/cli/dist/utils/project-templates.d.ts.map +0 -1
  170. package/cli/dist/utils/project-templates.js.map +0 -1
  171. package/cli/dist/utils/prompt.d.ts +0 -28
  172. package/cli/dist/utils/prompt.d.ts.map +0 -1
  173. package/cli/dist/utils/prompt.js.map +0 -1
  174. package/cli/dist/utils/resource-features.d.ts +0 -29
  175. package/cli/dist/utils/resource-features.d.ts.map +0 -1
  176. package/cli/dist/utils/resource-features.js.map +0 -1
  177. package/cli/dist/utils/service-urls.d.ts +0 -30
  178. package/cli/dist/utils/service-urls.d.ts.map +0 -1
  179. package/cli/dist/utils/service-urls.js.map +0 -1
  180. package/cli/dist/utils/services.d.ts +0 -18
  181. package/cli/dist/utils/services.d.ts.map +0 -1
  182. package/cli/dist/utils/services.js.map +0 -1
  183. package/cli/dist/utils/stack-features.d.ts +0 -22
  184. package/cli/dist/utils/stack-features.d.ts.map +0 -1
  185. package/cli/dist/utils/stack-features.js.map +0 -1
  186. package/cli/dist/utils/tar.d.ts +0 -7
  187. package/cli/dist/utils/tar.d.ts.map +0 -1
  188. package/cli/dist/utils/tar.js.map +0 -1
  189. package/cli/dist/utils/tilt.d.ts +0 -20
  190. package/cli/dist/utils/tilt.d.ts.map +0 -1
  191. package/cli/dist/utils/tilt.js.map +0 -1
  192. package/cli/dist/utils/validation.d.ts +0 -16
  193. package/cli/dist/utils/validation.d.ts.map +0 -1
  194. package/cli/dist/utils/validation.js.map +0 -1
  195. package/cli/knip.json +0 -6
  196. package/cli/pnpm-lock.yaml +0 -2305
  197. package/cli/pnpm-workspace.yaml +0 -2
  198. package/cli/src/cli.ts +0 -72
  199. package/cli/src/commands/__tests__/always-enabled-infra.test.ts +0 -109
  200. package/cli/src/commands/__tests__/config.test.ts +0 -146
  201. package/cli/src/commands/__tests__/doctor.test.ts +0 -730
  202. package/cli/src/commands/__tests__/error-handling.test.ts +0 -78
  203. package/cli/src/commands/__tests__/phases-model.test.ts +0 -161
  204. package/cli/src/commands/__tests__/project-resource-features-e2e.test.ts +0 -187
  205. package/cli/src/commands/__tests__/project.test.ts +0 -68
  206. package/cli/src/commands/__tests__/resource.test.ts +0 -226
  207. package/cli/src/commands/__tests__/saas-starter-integration.test.ts +0 -194
  208. package/cli/src/commands/__tests__/stack-features-e2e.test.ts +0 -135
  209. package/cli/src/commands/__tests__/upgrade.test.ts +0 -198
  210. package/cli/src/commands/completion.ts +0 -300
  211. package/cli/src/commands/config.ts +0 -299
  212. package/cli/src/commands/doctor.ts +0 -926
  213. package/cli/src/commands/down.ts +0 -37
  214. package/cli/src/commands/help.ts +0 -143
  215. package/cli/src/commands/networks-design.md +0 -80
  216. package/cli/src/commands/networks.ts +0 -332
  217. package/cli/src/commands/project.ts +0 -456
  218. package/cli/src/commands/projects.ts +0 -82
  219. package/cli/src/commands/resource.ts +0 -652
  220. package/cli/src/commands/resources.ts +0 -95
  221. package/cli/src/commands/stack.ts +0 -128
  222. package/cli/src/commands/stacks.ts +0 -52
  223. package/cli/src/commands/status.ts +0 -88
  224. package/cli/src/commands/ui.tsx +0 -843
  225. package/cli/src/commands/up.ts +0 -169
  226. package/cli/src/commands/upgrade.ts +0 -628
  227. package/cli/src/commands/version.ts +0 -9
  228. package/cli/src/components/Accessible.tsx +0 -21
  229. package/cli/src/components/BaseTooltip.tsx +0 -66
  230. package/cli/src/components/DetailPanel.tsx +0 -146
  231. package/cli/src/components/FileTree.tsx +0 -89
  232. package/cli/src/components/ResourceSelectInput.tsx +0 -30
  233. package/cli/src/components/ResourceTable.tsx +0 -78
  234. package/cli/src/components/TabBar.tsx +0 -53
  235. package/cli/src/components/Tooltip.tsx +0 -11
  236. package/cli/src/components/index.ts +0 -7
  237. package/cli/src/config/platform-standards.test.ts +0 -11
  238. package/cli/src/config/platform-standards.ts +0 -196
  239. package/cli/src/generator/__tests__/extension-fetch.test.ts +0 -242
  240. package/cli/src/generator/__tests__/template-engine.test.ts +0 -70
  241. package/cli/src/generator/extension-fetch.ts +0 -286
  242. package/cli/src/generator/template-engine.ts +0 -752
  243. package/cli/src/index.ts +0 -86
  244. package/cli/src/templates.d.ts +0 -4
  245. package/cli/src/types/index.ts +0 -353
  246. package/cli/src/utils/__tests__/cache.test.ts +0 -98
  247. package/cli/src/utils/__tests__/formatting.test.ts +0 -170
  248. package/cli/src/utils/__tests__/paths.test.ts +0 -180
  249. package/cli/src/utils/__tests__/port-assignment.test.ts +0 -121
  250. package/cli/src/utils/__tests__/service-urls.test.ts +0 -174
  251. package/cli/src/utils/__tests__/services.test.ts +0 -531
  252. package/cli/src/utils/__tests__/tilt.test.ts +0 -172
  253. package/cli/src/utils/__tests__/validation.test.ts +0 -148
  254. package/cli/src/utils/cache.ts +0 -76
  255. package/cli/src/utils/command-helpers.ts +0 -54
  256. package/cli/src/utils/constants.ts +0 -130
  257. package/cli/src/utils/discovery-context.ts +0 -49
  258. package/cli/src/utils/docker.ts +0 -20
  259. package/cli/src/utils/doctor-runtime.ts +0 -644
  260. package/cli/src/utils/env-validator.ts +0 -139
  261. package/cli/src/utils/errors.ts +0 -168
  262. package/cli/src/utils/file-helpers.ts +0 -62
  263. package/cli/src/utils/formatting.ts +0 -215
  264. package/cli/src/utils/paths.ts +0 -53
  265. package/cli/src/utils/port-assignment.ts +0 -106
  266. package/cli/src/utils/project-features.ts +0 -118
  267. package/cli/src/utils/project-templates.ts +0 -29
  268. package/cli/src/utils/prompt.ts +0 -128
  269. package/cli/src/utils/resource-features.ts +0 -215
  270. package/cli/src/utils/service-urls.ts +0 -124
  271. package/cli/src/utils/services.ts +0 -364
  272. package/cli/src/utils/stack-features.ts +0 -30
  273. package/cli/src/utils/tar.ts +0 -24
  274. package/cli/src/utils/tilt.ts +0 -133
  275. package/cli/src/utils/validation.ts +0 -67
  276. package/cli/tsconfig.json +0 -25
  277. package/cli/tsconfig.typecheck.json +0 -8
  278. package/docs/FEATURES.md +0 -279
  279. package/docs/demo.svg +0 -217
  280. package/engine/docs/AUTODISCOVERY_TEST.md +0 -182
  281. package/engine/docs/C4-ARCHITECTURE-operations.md +0 -396
  282. package/engine/docs/C4-ARCHITECTURE-overview.md +0 -284
  283. package/engine/docs/C4-ARCHITECTURE.md +0 -17
  284. package/engine/docs/GOLDEN_IMAGE_INTEGRATION.md +0 -349
  285. package/engine/docs/MANIFEST_DRIVEN_ARCHITECTURE.md +0 -357
  286. package/engine/docs/README.md +0 -284
  287. package/install.sh +0 -19
  288. package/knip.json +0 -10
  289. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/.openspec.yaml +0 -2
  290. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/design.md +0 -102
  291. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/proposal.md +0 -31
  292. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-notifications/spec.md +0 -48
  293. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-orchestration/spec.md +0 -63
  294. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-troubleshooting/spec.md +0 -56
  295. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/tasks.md +0 -61
  296. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/.openspec.yaml +0 -2
  297. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/design.md +0 -114
  298. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/proposal.md +0 -32
  299. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/manifest-field-migration/spec.md +0 -20
  300. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/resource-terminology-standard/spec.md +0 -20
  301. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/stack-terminology-standard/spec.md +0 -20
  302. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/tasks.md +0 -194
  303. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/.openspec.yaml +0 -2
  304. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/design.md +0 -111
  305. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/proposal.md +0 -33
  306. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/function-naming-alignment/spec.md +0 -12
  307. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/optional-parameter-patterns/spec.md +0 -18
  308. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-documentation/spec.md +0 -16
  309. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-naming-consistency/spec.md +0 -12
  310. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-ordering-standard/spec.md +0 -12
  311. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/tasks.md +0 -194
  312. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/.openspec.yaml +0 -2
  313. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/design.md +0 -84
  314. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/proposal.md +0 -31
  315. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/codebase-audit/spec.md +0 -27
  316. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/dead-code-cleanup/spec.md +0 -27
  317. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/test-gap-fill/spec.md +0 -23
  318. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/todo-resolution/spec.md +0 -27
  319. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/tasks.md +0 -39
  320. package/openspec/changes/archive/2026-06-25-landscape-consolidation/.openspec.yaml +0 -2
  321. package/openspec/changes/archive/2026-06-25-landscape-consolidation/design.md +0 -80
  322. package/openspec/changes/archive/2026-06-25-landscape-consolidation/proposal.md +0 -40
  323. package/openspec/changes/archive/2026-06-25-landscape-consolidation/spec.md +0 -52
  324. package/openspec/changes/archive/2026-06-25-landscape-consolidation/tasks.md +0 -51
  325. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/.openspec.yaml +0 -2
  326. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/design.md +0 -43
  327. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/proposal.md +0 -29
  328. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/specs/self-contained-generated-projects/spec.md +0 -67
  329. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/tasks.md +0 -27
  330. package/openspec/changes/optimize-runtime-docker-layers/.openspec.yaml +0 -3
  331. package/openspec/changes/optimize-runtime-docker-layers/design.md +0 -81
  332. package/openspec/changes/optimize-runtime-docker-layers/proposal.md +0 -44
  333. package/openspec/changes/optimize-runtime-docker-layers/specs/runtime-container-footprint/spec.md +0 -79
  334. package/openspec/changes/optimize-runtime-docker-layers/tasks.md +0 -36
  335. package/openspec/changes/prioritized-cold-start/.openspec.yaml +0 -4
  336. package/openspec/changes/prioritized-cold-start/design.md +0 -137
  337. package/openspec/changes/prioritized-cold-start/proposal.md +0 -41
  338. package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +0 -98
  339. package/openspec/changes/prioritized-cold-start/tasks.md +0 -69
  340. package/openspec/config.yaml +0 -1
  341. package/openspec/specs/manifest-field-migration/spec.md +0 -24
  342. package/openspec/specs/resource-terminology-standard/spec.md +0 -24
  343. package/openspec/specs/self-contained-generated-projects/spec.md +0 -67
  344. package/openspec/specs/stack-terminology-standard/spec.md +0 -24
  345. package/platform/services/platform/proxy/service.yaml +0 -14
  346. package/scripts/audit-codebase.sh +0 -213
  347. package/scripts/autoresearch/layer-eval.sh +0 -65
  348. package/scripts/benchmark/README.md +0 -25
  349. package/scripts/benchmark/container-scale.ts +0 -503
  350. package/scripts/benchmark/pre-release-gate.sh +0 -9
  351. package/scripts/install-git-hooks.sh +0 -14
  352. package/scripts/measure-idle-footprint.sh +0 -58
  353. package/scripts/record-demo.sh +0 -43
  354. package/scripts/release-binaries.sh +0 -97
  355. package/scripts/setup-pre-commit.sh +0 -33
  356. package/scripts/stage-npm-engine.sh +0 -36
  357. package/tests/tilt-engine/__init__.py +0 -5
  358. package/tests/tilt-engine/conftest.py +0 -182
  359. package/tests/tilt-engine/fixtures/appointment-frontend-service.json +0 -11
  360. package/tests/tilt-engine/fixtures/identity-backend-service.json +0 -12
  361. package/tests/tilt-engine/fixtures/invalid-service.json +0 -4
  362. package/tests/tilt-engine/test_agents_md_generator.py +0 -341
  363. package/tests/tilt-engine/test_agents_md_integration.py +0 -378
  364. package/tests/tilt-engine/test_cache_operations.py +0 -181
  365. package/tests/tilt-engine/test_cache_persistence.py +0 -142
  366. package/tests/tilt-engine/test_code_executor.py +0 -152
  367. package/tests/tilt-engine/test_code_viewer.py +0 -127
  368. package/tests/tilt-engine/test_compose_port_anchors.py +0 -310
  369. package/tests/tilt-engine/test_config_inspector.py +0 -162
  370. package/tests/tilt-engine/test_daemon_focus_mode.py +0 -89
  371. package/tests/tilt-engine/test_daemon_polling.py +0 -122
  372. package/tests/tilt-engine/test_daemon_registration.py +0 -115
  373. package/tests/tilt-engine/test_daemon_validation.py +0 -107
  374. package/tests/tilt-engine/test_docker_generator_bugs.py +0 -493
  375. package/tests/tilt-engine/test_docker_prune.py +0 -119
  376. package/tests/tilt-engine/test_dockerignore_path_bug.py +0 -82
  377. package/tests/tilt-engine/test_file_browser.py +0 -193
  378. package/tests/tilt-engine/test_frontend_basepath_manifest.py +0 -27
  379. package/tests/tilt-engine/test_health_dependencies.py +0 -130
  380. package/tests/tilt-engine/test_health_endpoints.py +0 -118
  381. package/tests/tilt-engine/test_health_tilt_ui.py +0 -85
  382. package/tests/tilt-engine/test_runtime_container_footprint.py +0 -162
  383. package/tests/tilt-engine/test_sablier_defer_start.py +0 -237
  384. package/tests/tilt-engine/test_safety_aliases.py +0 -84
  385. package/tests/tilt-engine/test_snapshot_diff.py +0 -136
  386. package/tests/tilt-engine/test_snapshot_persistence.py +0 -163
  387. package/tests/tilt-engine/test_snapshot_scanning.py +0 -123
  388. package/tests/tilt-engine/test_standalone_project_boot.py +0 -61
  389. package/tests/tilt-engine/test_standalone_traefik.py +0 -160
  390. package/tests/tilt-engine/test_traefik_static_wake_route.py +0 -187
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Ground-truth evaluation for L1-L4 layer experiments. DO NOT EDIT during an autoresearch run.
3
- # Re-vendors this engine into the ERP project, regenerates Dockerfiles through Tilt, rebuilds the
4
- # golden layers and a fixed sample of 20 real ERP services, runs the container benchmark and the
5
- # feature tests, then prints one summary block.
6
- set -uo pipefail
7
- CORE="$(cd "$(dirname "$0")/../.." && pwd)"
8
- ERP="${TDK_BENCH_PROJECT:-$CORE/../tdk-erp-system}"
9
- SAMPLE="${LAYER_EVAL_SAMPLE:-20}"
10
- TDK_CLI="${TDK_CLI:-$CORE/cli/bin/tdk.js}"
11
- PREFIX="$(python3 -c "import json;print(json.load(open('$ERP/.tdk/project.json'))['project']['name'])")"
12
- t() { date +%s; }
13
-
14
- cd "$ERP"
15
- TDK_EXTENSION_SOURCE="$CORE" node "$TDK_CLI" project --yes >/dev/null 2>&1 || { echo "status: crash (tdk project)"; exit 1; }
16
- tilt alpha tiltfile-result -f .tdk/.tdk-out/Tiltfile >/dev/null 2>&1 || { echo "status: crash (Tiltfile evaluation)"; exit 1; }
17
-
18
- s=$(t)
19
- for target in l1_golden:l1 l2_golden:l2 l3_backend_golden:l3-backend l3_frontend_golden:l3-frontend l4_backend_bun:l4-backend l4_frontend_golden:l4-frontend; do
20
- docker build -q -f .tdk/.tdk-out/golden-layers.Dockerfile --target "${target%%:*}" -t "$PREFIX-${target##*:}:latest" . >/dev/null 2>&1 \
21
- || { echo "status: crash (golden ${target%%:*})"; exit 1; }
22
- done
23
- golden_build_s=$(( $(t) - s ))
24
-
25
- s=$(t)
26
- ls services/*/*/service.json | sort | head -n "$SAMPLE" | while read -r manifest; do
27
- dir="$(dirname "$manifest")"; svc="$(basename "$dir")"; stack="$(basename "$(dirname "$dir")")"
28
- echo "docker build -q -f $dir/.autogenerated/Dockerfile.app.autogenerated --target production -t ${stack}_${svc}:dev . >/dev/null 2>&1 || echo FAIL $svc"
29
- done | xargs -P 4 -I{} sh -c '{}' | tee /tmp/layer-eval-build.log
30
- grep -q FAIL /tmp/layer-eval-build.log && { echo "status: crash (service build: $(grep FAIL /tmp/layer-eval-build.log | head -3 | tr '\n' ' '))"; exit 1; }
31
- service_build_s=$(( $(t) - s ))
32
-
33
- cd "$CORE"
34
- bun scripts/benchmark/container-scale.ts --synthetic --tiers "$SAMPLE" --gate "$SAMPLE" --settle 15 > /tmp/layer-eval-bench.log 2>&1
35
- result="$(ls -t benchmarks/results/container-scale-*.json | head -1)"
36
- tests=pass
37
- python3 -m pytest tests/tilt-engine/test_runtime_container_footprint.py -q -p no:cacheprovider >/tmp/layer-eval-tests.log 2>&1 || tests=fail
38
-
39
- python3 - "$result" "$ERP" "$SAMPLE" "$PREFIX" "$golden_build_s" "$service_build_s" "$tests" <<'PY'
40
- import json, subprocess, sys, glob, os
41
- result, erp, sample, prefix, gb, sb, tests = sys.argv[1:]
42
- r = json.load(open(result))["results"][0]
43
- def size(img):
44
- out = subprocess.run(["docker", "image", "inspect", img, "--format", "{{.Size}}"], capture_output=True, text=True).stdout.strip()
45
- return int(out) / 1048576 if out else 0
46
- base = size(f"{prefix}-l4-backend:latest")
47
- svcs = sorted(glob.glob(f"{erp}/services/*/*/service.json"))[: int(sample)]
48
- backend = [f"{os.path.basename(os.path.dirname(os.path.dirname(p)))}_{os.path.basename(os.path.dirname(p))}:dev" for p in svcs
49
- if json.load(open(p)).get("appType") != "frontend"]
50
- sizes = [size(i) for i in backend]
51
- ok = r["allHealthySeconds"] is not None and r["crashed"] == 0 and tests == "pass"
52
- print("---")
53
- print(f"mem_per_service_mib: {r['avgMemMiB']}")
54
- print(f"service_image_mib: {sum(sizes)/len(sizes):.1f}")
55
- print(f"unique_per_service_mib: {sum(sizes)/len(sizes) - base:.1f}")
56
- print(f"l4_backend_mib: {base:.1f}")
57
- print(f"all_healthy_s: {r['allHealthySeconds']}")
58
- print(f"crashed: {r['crashed']}")
59
- print(f"docker_ps_ms: {r['dockerPsMs']}")
60
- print(f"cpu_total_pct: {r['totalCpuPct']}")
61
- print(f"golden_build_s: {gb}")
62
- print(f"service_build_s: {sb}")
63
- print(f"tests: {tests}")
64
- print(f"status: {'ok' if ok else 'invalid'}")
65
- PY
@@ -1,25 +0,0 @@
1
- # Container scale benchmark
2
-
3
- Release gate for the north star: **100 ERP services on a 16 GB machine without removing features**.
4
-
5
- ```bash
6
- bun scripts/benchmark/container-scale.ts # tiers 5,10,20,50,80,100, gate on 100
7
- bun scripts/benchmark/container-scale.ts --tiers 5,10 --gate 10
8
- bun scripts/benchmark/container-scale.ts --legacy-healthcheck # old 10s probes, for comparison
9
- ```
10
-
11
- It uses the ERP images that `tdk up` already built in `../tdk-erp-system` (override with `--project` or `TDK_BENCH_PROJECT`). For each tier it starts N real service containers with the generated runtime settings (`--init`, 512 MiB / 0.5 CPU limits, healthchecks against `/health`). It then records:
12
-
13
- - launch time, first-healthy and all-healthy time
14
- - crashes and OOM kills, with each container's exit code and last log line
15
- - total and average memory, CPU
16
- - `docker ps` latency (`tdk doctor` fails above 10 s)
17
- - p50 `/health` latency and host load
18
-
19
- Containers are removed after each tier. Results are printed as a table and saved to `benchmarks/results/*.json`.
20
-
21
- **Gate:** at the gate tier, every container must be healthy within the timeout with no crashes. Total memory must fit `(Docker VM memory - 512 MiB) / 100` per service, and `docker ps` must answer in under 10 s. The exit code is 1 when the gate fails.
22
-
23
- **Git hook (opt-in):** call `scripts/benchmark/pre-release-gate.sh` from `pre-push` or `pre-commit`. It does nothing unless `TDK_BENCH_GATE=1`.
24
-
25
- Rebuild the ERP images after engine changes (`tdk project --yes && tdk up` in the ERP project) so the benchmark measures the current layers. The header line reports what share of images use a single-process `CMD`.
@@ -1,503 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Container scale benchmark / release gate.
4
- *
5
- * Starts N real ERP service containers (images built by `tdk up` in the ERP
6
- * project) for each tier, measures time-to-healthy, crashes, memory, CPU and
7
- * Docker responsiveness, tears them down, and gates on the north star:
8
- * 100 services on a 16 GB machine.
9
- *
10
- * bun scripts/benchmark/container-scale.ts # 5,10,20,50,80,100
11
- * bun scripts/benchmark/container-scale.ts --tiers 5,10 --gate 10
12
- * bun scripts/benchmark/container-scale.ts --legacy-healthcheck # 10s probes, for comparison
13
- *
14
- * Exit code 1 when the gate tier fails.
15
- */
16
- import { execSync, spawnSync } from "node:child_process";
17
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
18
- import { basename, dirname, join, relative, resolve } from "node:path";
19
-
20
- const REPO_ROOT = resolve(import.meta.dir, "..", "..");
21
- const PREFIX = "tdkbench-";
22
-
23
- interface Options {
24
- mode: "compose" | "synthetic";
25
- project: string;
26
- tiers: number[];
27
- gate: number;
28
- timeoutSeconds: number;
29
- interval: number;
30
- startInterval: number;
31
- settleSeconds: number;
32
- }
33
-
34
- function parseArgs(): Options {
35
- const args = process.argv.slice(2);
36
- const get = (flag: string, fallback: string): string => {
37
- const i = args.indexOf(flag);
38
- return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
39
- };
40
- const legacy = args.includes("--legacy-healthcheck");
41
- const tiers = get("--tiers", "5,10,20,50,80,100").split(",").map(Number).filter((n) => n > 0);
42
- return {
43
- mode: args.includes("--synthetic") ? "synthetic" : "compose",
44
- project: resolve(get("--project", process.env.TDK_BENCH_PROJECT ?? join(REPO_ROOT, "..", "tdk-erp-system"))),
45
- tiers,
46
- gate: Number(get("--gate", String(Math.max(...tiers)))),
47
- timeoutSeconds: Number(get("--timeout", "240")),
48
- interval: legacy ? 10 : Number(get("--interval", "30")),
49
- startInterval: legacy ? 0 : Number(get("--start-interval", "2")),
50
- settleSeconds: Number(get("--settle", "15")),
51
- };
52
- }
53
-
54
- function sh(command: string, timeoutMs = 120_000): string {
55
- return execSync(command, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: timeoutMs }).trim();
56
- }
57
-
58
- function shOk(command: string, timeoutMs = 120_000): string {
59
- try {
60
- return sh(command, timeoutMs);
61
- } catch {
62
- return "";
63
- }
64
- }
65
-
66
- interface Service {
67
- name: string;
68
- stack: string;
69
- image: string;
70
- port: number;
71
- isFrontend: boolean;
72
- singleProcess: boolean;
73
- imageMiB: number;
74
- }
75
-
76
- function discoverServices(project: string): Service[] {
77
- const images = new Set(shOk("docker images --format '{{.Repository}}:{{.Tag}}'").split("\n"));
78
- const services: Service[] = [];
79
- const manifests = [...new Bun.Glob("services/*/*/service.json").scanSync({ cwd: project })].sort();
80
- for (const relative of manifests) {
81
- const manifestPath = join(project, relative);
82
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
83
- const name = basename(dirname(manifestPath));
84
- const stack = manifest.stack ?? basename(dirname(dirname(manifestPath)));
85
- const image = `${stack}_${name}:dev`;
86
- if (!images.has(image)) continue;
87
- const [cmd, bytes] = shOk(`docker image inspect ${image} --format '{{json .Config.Cmd}}|{{.Size}}'`).split("|");
88
- services.push({
89
- name,
90
- stack,
91
- image,
92
- port: Number(manifest.port ?? 4000),
93
- isFrontend: manifest.appType === "frontend",
94
- singleProcess: !(cmd ?? "").includes('"run","start"'),
95
- imageMiB: Math.round(Number(bytes ?? 0) / 1048576),
96
- });
97
- }
98
- return services;
99
- }
100
-
101
- function dbPassword(project: string): string {
102
- const envPath = join(project, ".env");
103
- if (!existsSync(envPath)) return "";
104
- const line = readFileSync(envPath, "utf-8").split("\n").find((l) => l.startsWith("DB_PASSWORD="));
105
- return line ? line.slice("DB_PASSWORD=".length).trim() : "";
106
- }
107
-
108
- function projectName(project: string): string {
109
- const pj = join(project, ".tdk", "project.json");
110
- const name = existsSync(pj) ? JSON.parse(readFileSync(pj, "utf-8")).project?.name : basename(project);
111
- return String(name).replace(/-/g, "_");
112
- }
113
-
114
- function toMiB(raw: string): number {
115
- const n = Number.parseFloat(raw);
116
- if (/GiB$/.test(raw)) return n * 1024;
117
- if (/KiB$/.test(raw)) return n / 1024;
118
- if (/MiB$/.test(raw)) return n;
119
- if (/[0-9]B$/.test(raw)) return n / 1048576;
120
- return n;
121
- }
122
-
123
- function timed<T>(fn: () => T): [T, number] {
124
- const start = performance.now();
125
- const value = fn();
126
- return [value, Math.round(performance.now() - start)];
127
- }
128
-
129
- function hostLoad(): number {
130
- return Number.parseFloat(shOk("sysctl -n vm.loadavg").replace(/[{}]/g, "").trim().split(/\s+/)[0] ?? "0");
131
- }
132
-
133
- function hostFreeMiB(): number {
134
- const out = shOk("vm_stat");
135
- const page = Number(out.match(/page size of (\d+)/)?.[1] ?? 16384);
136
- const pages = (key: string) => Number(out.match(new RegExp(`${key}:\\s+(\\d+)`))?.[1] ?? 0);
137
- return Math.round(((pages("Pages free") + pages("Pages speculative")) * page) / 1048576);
138
- }
139
-
140
- function removeBenchContainers(): void {
141
- const ids = shOk(`docker ps -aq --filter name=${PREFIX}`).split("\n").filter(Boolean);
142
- if (ids.length) shOk(`docker rm -f ${ids.join(" ")}`, 300_000);
143
- }
144
-
145
- interface TierResult {
146
- tier: number;
147
- launchSeconds: number;
148
- firstHealthySeconds: number | null;
149
- allHealthySeconds: number | null;
150
- healthy: number;
151
- unhealthy: number;
152
- starting: number;
153
- crashed: number;
154
- oomKilled: number;
155
- crashes: { name: string; exitCode: string; oom: boolean; lastLog: string }[];
156
- totalMemMiB: number;
157
- avgMemMiB: number;
158
- totalCpuPct: number;
159
- dockerPsMs: number;
160
- requestMsP50: number | null;
161
- loadAvg1m: number;
162
- hostFreeMiB: number;
163
- singleProcessShare: number;
164
- }
165
-
166
- function runTier(n: number, services: Service[], opts: Options, network: string, password: string, project: string): TierResult {
167
- removeBenchContainers();
168
- const picked = Array.from({ length: n }, (_, i) => ({ svc: services[i % services.length], name: `${PREFIX}${i}` }));
169
- const healthFlags = (probe: string) =>
170
- [
171
- `--health-cmd '${probe}'`,
172
- `--health-interval ${opts.interval}s`,
173
- opts.startInterval > 0 ? `--health-start-interval ${opts.startInterval}s` : "",
174
- "--health-timeout 5s --health-start-period 30s --health-retries 3",
175
- ].join(" ");
176
-
177
- const t0 = performance.now();
178
- for (const { svc, name } of picked) {
179
- const probe = svc.isFrontend ? "curl -fs http://localhost/ || exit 1" : `curl -fs http://localhost:${svc.port}/health || exit 1`;
180
- const env = svc.isFrontend
181
- ? ""
182
- : `-e PORT=${svc.port} -e NODE_ENV=development -e DATABASE_URL=postgresql://${project}:${password}@${project}_postgres:5432/${project}_${svc.stack} -e TILT_DATABASE_URL=postgresql://${project}:${password}@${project}_postgres:5432/${project}_${svc.stack} -e AUTO_MIGRATE=false`;
183
- const res = spawnSync(
184
- "sh",
185
- ["-c", `docker run -d --name ${name} --init --memory 512m --cpus 0.5 --network ${network} ${env} ${healthFlags(probe)} ${svc.image}`],
186
- { encoding: "utf-8" },
187
- );
188
- if (res.status !== 0) console.error(` ! ${name} (${svc.name}) failed to start: ${res.stderr.trim().split("\n").pop()}`);
189
- }
190
- const launchSeconds = (performance.now() - t0) / 1000;
191
-
192
- let firstHealthy: number | null = null;
193
- let allHealthy: number | null = null;
194
- const deadline = t0 + opts.timeoutSeconds * 1000;
195
- let states: string[] = [];
196
- while (performance.now() < deadline) {
197
- states = shOk(`docker ps -a --filter name=${PREFIX} --format '{{.Status}}'`, 60_000).split("\n").filter(Boolean);
198
- const healthyCount = states.filter((s) => s.includes("(healthy)")).length;
199
- const elapsed = (performance.now() - t0) / 1000;
200
- if (healthyCount > 0 && firstHealthy === null) firstHealthy = elapsed;
201
- if (healthyCount === n) {
202
- allHealthy = elapsed;
203
- break;
204
- }
205
- if (states.length === n && states.every((s) => !s.includes("health: starting") && !s.startsWith("Created"))) break;
206
- Bun.sleepSync(1000);
207
- }
208
-
209
- Bun.sleepSync(opts.settleSeconds * 1000);
210
- states = shOk(`docker ps -a --filter name=${PREFIX} --format '{{.Names}}\t{{.Status}}'`, 60_000).split("\n").filter(Boolean);
211
- const exitedNames = states.filter((s) => /\tExited|\tRestarting|\tDead/.test(s)).map((s) => s.split("\t")[0]);
212
- const crashes = exitedNames.map((name) => {
213
- const inspect = shOk(`docker inspect ${name} --format '{{.State.ExitCode}}|{{.State.OOMKilled}}'`).split("|");
214
- const lastLog = shOk(`docker logs --tail 3 ${name} 2>&1`).split("\n").filter(Boolean).pop() ?? "";
215
- const svc = picked.find((p) => p.name === name)?.svc.name ?? name;
216
- return { name: `${name} (${svc})`, exitCode: inspect[0] ?? "?", oom: inspect[1] === "true", lastLog: lastLog.slice(0, 160) };
217
- });
218
-
219
- const stats = shOk(`docker stats --no-stream --format '{{.Name}} {{.CPUPerc}} {{.MemUsage}}' $(docker ps -q --filter name=${PREFIX})`, 180_000)
220
- .split("\n")
221
- .filter(Boolean)
222
- .map((l) => l.split(/\s+/))
223
- .map(([, cpu, mem]) => ({ cpu: Number.parseFloat(cpu ?? "0"), mem: toMiB(mem ?? "0") }));
224
- const totalMem = stats.reduce((a, s) => a + s.mem, 0);
225
- const [, dockerPsMs] = timed(() => shOk("docker ps -q", 60_000));
226
-
227
- const running = picked.filter((p) => !exitedNames.includes(p.name)).slice(0, 10);
228
- const latencies = running
229
- .map(({ svc, name }) => {
230
- const url = svc.isFrontend ? "http://localhost/" : `http://localhost:${svc.port}/health`;
231
- const out = shOk(`docker exec ${name} curl -s -o /dev/null -w '%{http_code} %{time_total}' ${url}`, 30_000).split(" ");
232
- return out[0] === "200" ? Number.parseFloat(out[1] ?? "0") * 1000 : null;
233
- })
234
- .filter((v): v is number => v !== null)
235
- .sort((a, b) => a - b);
236
-
237
- const result: TierResult = {
238
- tier: n,
239
- launchSeconds: round(launchSeconds),
240
- firstHealthySeconds: firstHealthy === null ? null : round(firstHealthy),
241
- allHealthySeconds: allHealthy === null ? null : round(allHealthy),
242
- healthy: states.filter((s) => s.includes("(healthy)")).length,
243
- unhealthy: states.filter((s) => s.includes("(unhealthy)")).length,
244
- starting: states.filter((s) => s.includes("health: starting")).length,
245
- crashed: crashes.length,
246
- oomKilled: crashes.filter((c) => c.oom).length,
247
- crashes,
248
- totalMemMiB: Math.round(totalMem),
249
- avgMemMiB: stats.length ? round(totalMem / stats.length) : 0,
250
- totalCpuPct: round(stats.reduce((a, s) => a + s.cpu, 0)),
251
- dockerPsMs,
252
- requestMsP50: latencies.length ? round(latencies[Math.floor(latencies.length / 2)]) : null,
253
- loadAvg1m: hostLoad(),
254
- hostFreeMiB: hostFreeMiB(),
255
- singleProcessShare: round(picked.filter((p) => p.svc.singleProcess).length / n),
256
- };
257
- removeBenchContainers();
258
- return result;
259
- }
260
-
261
- // ---- compose mode: start services exactly like Tilt (generated compose, real infra, Traefik) ----
262
-
263
- function ensureExternalNetworks(composeArgs: string): void {
264
- const config = shOk(`docker compose ${composeArgs} config --format json`, 120_000);
265
- if (!config) return;
266
- for (const net of Object.values(JSON.parse(config).networks ?? {}) as { name?: string; external?: boolean }[]) {
267
- if (net.external && net.name && !shOk(`docker network inspect ${net.name} --format '{{.Name}}'`)) shOk(`docker network create ${net.name}`);
268
- }
269
- }
270
-
271
- function startInfra(project: string, root: string): string[] {
272
- const started: string[] = [];
273
- const envFile = join(root, ".env");
274
- const infra = [
275
- { container: `${project}_postgres`, file: join(root, "services", "platform", "database-management", "docker-compose.yml") },
276
- { container: `${project}_traefik`, file: join(root, ".tdk", ".tdk-out", "docker-compose.traefik.yml") },
277
- ];
278
- for (const { container, file } of infra) {
279
- if (shOk(`docker ps -q --filter name=^${container}$`)) continue;
280
- if (shOk(`docker ps -aq --filter name=^${container}$`)) shOk(`docker start ${container}`);
281
- else {
282
- const args = `-f ${file} --env-file ${envFile}`;
283
- ensureExternalNetworks(args);
284
- shOk(`docker compose ${args} up -d`, 300_000);
285
- }
286
- started.push(container);
287
- }
288
- for (let i = 0; i < 60 && !shOk(`docker exec ${project}_postgres pg_isready -U ${project}`); i++) Bun.sleepSync(1000);
289
- return started;
290
- }
291
-
292
- function composeArgs(root: string, stack: string): string {
293
- return `-p ${stack}-autogenerated -f ${join(root, "services", stack, ".autogenerated", "docker-compose.app.autogenerated.yml")} --env-file ${join(root, ".env")}`;
294
- }
295
-
296
- function routeRequest(container: string): { code: string; ms: number } | null {
297
- const labels = JSON.parse(shOk(`docker inspect ${container} --format '{{json .Config.Labels}}'`) || "{}") as Record<string, string>;
298
- const ruleKey = Object.keys(labels).find((k) => /^traefik\.http\.routers\.[^.]+\.rule$/.test(k));
299
- if (!ruleKey) return null;
300
- const rule = labels[ruleKey] ?? "";
301
- const host = rule.match(/Host\(`([^`]+)`\)/)?.[1];
302
- const prefix = rule.match(/PathPrefix\(`([^`]+)`\)/)?.[1] ?? "";
303
- const healthKey = Object.keys(labels).find((k) => k.endsWith(".loadbalancer.healthcheck.path"));
304
- const path = `${prefix.replace(/\/$/, "")}${healthKey ? labels[healthKey] : "/"}`;
305
- if (!host) return null;
306
- const out = shOk(`curl -s -o /dev/null -m 10 -w '%{http_code} %{time_total}' -H 'Host: ${host}' 'http://127.0.0.1:80${path}'`, 20_000).split(" ");
307
- return { code: out[0] ?? "000", ms: round(Number.parseFloat(out[1] ?? "0") * 1000) };
308
- }
309
-
310
- function runTierCompose(n: number, services: Service[], opts: Options, project: string): TierResult & { routed: Record<string, number>; restarts: number } {
311
- const picked = services.slice(0, Math.min(n, services.length));
312
- const byStack = new Map<string, string[]>();
313
- for (const svc of picked) byStack.set(svc.stack, [...(byStack.get(svc.stack) ?? []), svc.name]);
314
- for (const stack of byStack.keys()) {
315
- shOk(`docker exec ${project}_postgres createdb -U ${project} -O ${project} ${project}_${stack}`);
316
- ensureExternalNetworks(composeArgs(opts.project, stack));
317
- }
318
- const names = picked.map((svc) => `${svc.stack}-autogenerated-${svc.name}-1`);
319
-
320
- // Launch every stack's `compose up` in parallel, same as Tilt does across resources.
321
- // NOTE: do not use Bun.spawn() + a Bun.sleepSync() poll loop here - sleepSync blocks
322
- // Bun's event loop, so a spawned child's exit is never observed and the poll spins
323
- // forever even after the real process has finished. Shelling out one blocking
324
- // spawnSync (with the stack commands backgrounded and `wait`ed inside the shell)
325
- // avoids that: the parent shell, not our JS, is what waits for real completion.
326
- const launchScript = [...byStack]
327
- .map(([stack, svcs]) => `docker compose ${composeArgs(opts.project, stack)} up -d --no-build ${svcs.join(" ")} &`)
328
- .join("\n") + "\nwait\n";
329
- const t0 = performance.now();
330
- const launch = spawnSync("sh", ["-c", launchScript], { encoding: "utf-8", timeout: opts.timeoutSeconds * 1000 });
331
- const launchSeconds = (performance.now() - t0) / 1000;
332
- if (launch.status !== 0) console.error(` ! compose up failed (exit ${launch.status}): ${(launch.stderr ?? "").slice(0, 500)}`);
333
-
334
- const status = () => {
335
- const rows = shOk(`docker ps -a --format '{{.Names}}\t{{.Status}}'`, 60_000).split("\n").map((l) => l.split("\t"));
336
- return new Map(rows.filter(([name]) => names.includes(name ?? "")).map(([name, st]) => [name ?? "", st ?? ""]));
337
- };
338
- let firstHealthy: number | null = null;
339
- let allHealthy: number | null = null;
340
- const deadline = t0 + opts.timeoutSeconds * 1000;
341
- while (performance.now() < deadline) {
342
- const st = [...status().values()];
343
- const healthyCount = st.filter((s) => s.includes("(healthy)") || (s.startsWith("Up") && !s.includes("health"))).length;
344
- const elapsed = (performance.now() - t0) / 1000;
345
- if (healthyCount > 0 && firstHealthy === null) firstHealthy = elapsed;
346
- if (healthyCount === picked.length) {
347
- allHealthy = elapsed;
348
- break;
349
- }
350
- Bun.sleepSync(1000);
351
- }
352
- Bun.sleepSync(opts.settleSeconds * 1000);
353
-
354
- const final = status();
355
- const states = [...final.values()];
356
- const crashes = [...final]
357
- .filter(([name, st]) => /^(Exited|Restarting|Dead)/.test(st) || Number(shOk(`docker inspect ${name} --format '{{.RestartCount}}'`)) > 0)
358
- .map(([name]) => {
359
- const [exitCode, oom, restarts] = shOk(`docker inspect ${name} --format '{{.State.ExitCode}}|{{.State.OOMKilled}}|{{.RestartCount}}'`).split("|");
360
- const lastLog = shOk(`docker logs --tail 3 ${name} 2>&1`).split("\n").filter(Boolean).pop() ?? "";
361
- return { name: `${name} restarts=${restarts}`, exitCode: exitCode ?? "?", oom: oom === "true", lastLog: lastLog.slice(0, 160) };
362
- });
363
- const running = [...final].filter(([, st]) => st.startsWith("Up")).map(([name]) => name);
364
- const stats = shOk(`docker stats --no-stream --format '{{.CPUPerc}} {{.MemUsage}}' ${running.join(" ")}`, 180_000)
365
- .split("\n")
366
- .filter(Boolean)
367
- .map((l) => l.split(/\s+/))
368
- .map(([cpu, mem]) => ({ cpu: Number.parseFloat(cpu ?? "0"), mem: toMiB(mem ?? "0") }));
369
- const totalMem = stats.reduce((a, x) => a + x.mem, 0);
370
- const [, dockerPsMs] = timed(() => shOk("docker ps -q", 60_000));
371
-
372
- const routed: Record<string, number> = {};
373
- const latencies: number[] = [];
374
- for (const name of running) {
375
- const r = routeRequest(name);
376
- const code = r?.code ?? "no-route";
377
- routed[code] = (routed[code] ?? 0) + 1;
378
- if (r && r.code === "200") latencies.push(r.ms);
379
- }
380
- latencies.sort((a, b) => a - b);
381
-
382
- const result = {
383
- tier: n,
384
- launchSeconds: round(launchSeconds),
385
- firstHealthySeconds: firstHealthy === null ? null : round(firstHealthy),
386
- allHealthySeconds: allHealthy === null ? null : round(allHealthy),
387
- healthy: states.filter((st) => st.includes("(healthy)")).length,
388
- unhealthy: states.filter((st) => st.includes("(unhealthy)")).length,
389
- starting: states.filter((st) => st.includes("health: starting")).length,
390
- crashed: crashes.length,
391
- oomKilled: crashes.filter((c) => c.oom).length,
392
- crashes,
393
- totalMemMiB: Math.round(totalMem),
394
- avgMemMiB: stats.length ? round(totalMem / stats.length) : 0,
395
- totalCpuPct: round(stats.reduce((a, x) => a + x.cpu, 0)),
396
- dockerPsMs,
397
- requestMsP50: latencies.length ? latencies[Math.floor(latencies.length / 2)] ?? null : null,
398
- loadAvg1m: hostLoad(),
399
- hostFreeMiB: hostFreeMiB(),
400
- singleProcessShare: round(picked.filter((svc) => svc.singleProcess).length / picked.length),
401
- routed,
402
- restarts: crashes.length,
403
- };
404
- for (const [stack, svcs] of byStack) shOk(`docker compose ${composeArgs(opts.project, stack)} rm -sf ${svcs.join(" ")}`, 300_000);
405
- return result;
406
- }
407
-
408
- function round(v: number): number {
409
- return Math.round(v * 10) / 10;
410
- }
411
-
412
- function main(): void {
413
- const opts = parseArgs();
414
- const project = projectName(opts.project);
415
- const network = `${project}_database`;
416
- const dockerMemMiB = Math.round(Number(shOk("docker info --format '{{.MemTotal}}'")) / 1048576);
417
- const hostMemGiB = Math.round(Number(shOk("sysctl -n hw.memsize")) / 1073741824);
418
-
419
- if (shOk("pgrep -f 'tilt up'")) console.warn("⚠ Tilt is running - builds in the background will skew results.");
420
- shOk(`docker network inspect ${network} || docker network create ${network}`);
421
- const startedInfra = opts.mode === "compose" ? startInfra(project, opts.project) : [];
422
- if (opts.mode === "synthetic") shOk(`docker start ${project}_postgres`);
423
- const services = discoverServices(opts.project);
424
- if (services.length === 0) {
425
- console.error(`No built ERP images found for ${opts.project}. Run \`tdk up\` there once to build them.`);
426
- process.exit(2);
427
- }
428
- const baselineContainers = shOk("docker ps -q").split("\n").filter(Boolean).length;
429
- const singleShare = services.filter((s) => s.singleProcess).length / services.length;
430
-
431
- // North star math: 100 services must fit in the Docker VM with ~512 MiB left for infra.
432
- const imageSizes = (list: Service[]) => {
433
- const sizes = list.map((s) => s.imageMiB).sort((a, b) => a - b);
434
- return sizes.length ? { count: sizes.length, min: sizes[0], avg: Math.round(sizes.reduce((a, b) => a + b, 0) / sizes.length), max: sizes[sizes.length - 1] } : null;
435
- };
436
- const imageStats = {
437
- backend: imageSizes(services.filter((s) => !s.isFrontend)),
438
- frontend: imageSizes(services.filter((s) => s.isFrontend)),
439
- backendSingleProcess: imageSizes(services.filter((s) => !s.isFrontend && s.singleProcess)),
440
- backendWrapper: imageSizes(services.filter((s) => !s.isFrontend && !s.singleProcess)),
441
- };
442
- const diskUsage = shOk("docker system df --format '{{.Type}} {{.Size}}'").split("\n")[0] ?? "";
443
- const perServiceBudgetMiB = Math.floor((dockerMemMiB - 512) / 100);
444
- console.log(`Host ${hostMemGiB} GB, Docker VM ${dockerMemMiB} MiB, ${services.length} distinct ERP images (${Math.round(singleShare * 100)}% single-process CMD), ${baselineContainers} other containers running`);
445
- console.log(`North star budget: (${dockerMemMiB} - 512 infra) / 100 = ${perServiceBudgetMiB} MiB per service`);
446
- const fmt = (x: ReturnType<typeof imageSizes>) => (x ? `${x.count} × ${x.min}/${x.avg}/${x.max} MiB (min/avg/max)` : "none");
447
- console.log(`Images - backend new layers: ${fmt(imageStats.backendSingleProcess)}, backend old layers: ${fmt(imageStats.backendWrapper)}, frontend: ${fmt(imageStats.frontend)}; Docker disk: ${diskUsage}`);
448
- console.log(`Mode: ${opts.mode === "compose" ? "compose (generated compose files, real Postgres + Traefik, requests routed through Traefik)" : "synthetic (docker run, /health only)"}`);
449
- console.log(`Healthcheck: interval ${opts.interval}s, start-interval ${opts.startInterval || "off"}\n`);
450
-
451
- const results: TierResult[] = [];
452
- for (const tier of opts.tiers) {
453
- process.stdout.write(`▶ tier ${tier}... `);
454
- const r = opts.mode === "compose" ? runTierCompose(tier, services, opts, project) : runTier(tier, services, opts, network, dbPassword(opts.project), project);
455
- results.push(r);
456
- const routed = "routed" in r ? ` | via Traefik: ${JSON.stringify(r.routed)}` : "";
457
- console.log(`all healthy: ${r.allHealthySeconds ?? "NO"}s, crashed ${r.crashed}, mem ${r.totalMemMiB} MiB, docker ps ${r.dockerPsMs} ms${routed}`);
458
- }
459
-
460
- const header = "| tier | launch s | 1st healthy s | all healthy s | healthy | unhealthy | starting | crashed | OOM | mem MiB | avg MiB | CPU % | docker ps ms | req p50 ms | load 1m | host free MiB |";
461
- const rows = results.map(
462
- (r) =>
463
- `| ${r.tier} | ${r.launchSeconds} | ${r.firstHealthySeconds ?? "-"} | ${r.allHealthySeconds ?? "TIMEOUT"} | ${r.healthy} | ${r.unhealthy} | ${r.starting} | ${r.crashed} | ${r.oomKilled} | ${r.totalMemMiB} | ${r.avgMemMiB} | ${r.totalCpuPct} | ${r.dockerPsMs} | ${r.requestMsP50 ?? "-"} | ${r.loadAvg1m} | ${r.hostFreeMiB} |`,
464
- );
465
- console.log(`\n${header}\n|${"---|".repeat(16)}\n${rows.join("\n")}`);
466
- for (const r of results.filter((x) => x.crashes.length)) {
467
- console.log(`\nCrashes at tier ${r.tier}:`);
468
- for (const c of r.crashes.slice(0, 15)) console.log(` - ${c.name} exit=${c.exitCode}${c.oom ? " OOMKilled" : ""} :: ${c.lastLog}`);
469
- }
470
-
471
- const gate = results.find((r) => r.tier === opts.gate);
472
- const checks = gate
473
- ? [
474
- [`all ${gate.tier} healthy within ${opts.timeoutSeconds}s`, gate.allHealthySeconds !== null],
475
- ["no crashes / OOM kills", gate.crashed === 0],
476
- [`memory ${gate.totalMemMiB} MiB <= ${perServiceBudgetMiB * gate.tier} MiB budget`, gate.totalMemMiB <= perServiceBudgetMiB * gate.tier],
477
- [`docker ps ${gate.dockerPsMs} ms < 10000 ms (tdk doctor timeout)`, gate.dockerPsMs < 10_000],
478
- ]
479
- : [];
480
- if (gate && "routed" in gate) {
481
- const routed = gate.routed as Record<string, number>;
482
- const total = Object.values(routed).reduce((a, b) => a + b, 0);
483
- checks.push([`requests through Traefik: ${routed["200"] ?? 0}/${total} returned 200`, (routed["200"] ?? 0) === total]);
484
- }
485
- const smallest = results[0];
486
- console.log(`\nWebsite promise "full boot in under 5 seconds": tier ${smallest?.tier} all healthy in ${smallest?.allHealthySeconds ?? "TIMEOUT"}s (info only)`);
487
- console.log(`Gate (tier ${opts.gate}):`);
488
- for (const [label, ok] of checks) console.log(` ${ok ? "✓" : "✗"} ${label}`);
489
- const passed = checks.length > 0 && checks.every(([, ok]) => ok);
490
- for (const container of startedInfra) shOk(`docker stop ${container}`);
491
-
492
- const outDir = join(REPO_ROOT, "benchmarks", "results");
493
- mkdirSync(outDir, { recursive: true });
494
- const outFile = join(outDir, `container-scale-${new Date().toISOString().replace(/[:.]/g, "-")}.json`);
495
- writeFileSync(
496
- outFile,
497
- JSON.stringify({ date: new Date().toISOString(), hostMemGiB, dockerMemMiB, perServiceBudgetMiB, options: { ...opts, project: relative(REPO_ROOT, opts.project) }, images: services.length, imageStats, diskUsage, singleProcessImageShare: singleShare, results, gate: { tier: opts.gate, passed } }, null, 2),
498
- );
499
- console.log(`\n${passed ? "PASS" : "FAIL"} - results saved to ${outFile}`);
500
- process.exit(passed ? 0 : 1);
501
- }
502
-
503
- main();
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Opt-in container scale gate for git hooks. No-op unless TDK_BENCH_GATE=1.
3
- # TDK_BENCH_GATE=1 git push # run full gate (tiers up to 100)
4
- # TDK_BENCH_GATE=1 TDK_BENCH_TIERS=5,10,20 git commit ...
5
- set -euo pipefail
6
- [ "${TDK_BENCH_GATE:-0}" = "1" ] || exit 0
7
- cd "$(dirname "$0")/../.."
8
- tiers="${TDK_BENCH_TIERS:-5,10,20,50,80,100}"
9
- exec bun scripts/benchmark/container-scale.ts --tiers "$tiers" --gate "${tiers##*,}"
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- set -euo pipefail
4
-
5
- readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
-
7
- # npm runs `prepare` when installing from a GitHub URL, but its temporary
8
- # checkout does not include the .git directory. Hooks are only needed in a
9
- # developer checkout, so leave package installation untouched.
10
- if ! git -C "${ROOT_DIR}" rev-parse --git-dir >/dev/null 2>&1; then
11
- exit 0
12
- fi
13
-
14
- git -C "${ROOT_DIR}" config core.hooksPath .githooks