@tdk-landscape/tdk-cli-core 1.3.53 → 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 (388) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/cli/dist/generator/extension-fetch.js +16 -9
  3. package/cli/package.json +1 -1
  4. package/package.json +16 -1
  5. package/.claude/skills/layer-autoresearch/SKILL.md +0 -48
  6. package/.claude/skills/tdk-troubleshoot/SKILL.md +0 -222
  7. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -40
  8. package/.github/ISSUE_TEMPLATE/config.yml +0 -11
  9. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -18
  10. package/.github/ISSUE_TEMPLATE/premium_license.yml +0 -43
  11. package/.github/dependabot.yml +0 -33
  12. package/.github/pull_request_template.md +0 -5
  13. package/.github/workflows/ci.yml +0 -200
  14. package/.github/workflows/release-binaries.yml +0 -139
  15. package/.github/zizmor.yml +0 -9
  16. package/.pre-commit-config.yaml +0 -57
  17. package/CODE_OF_CONDUCT.md +0 -9
  18. package/CONTRIBUTING.md +0 -42
  19. package/Makefile +0 -92
  20. package/SECURITY.md +0 -17
  21. package/benchmarks/results/container-scale-2026-09-25T20-54-49-736Z.json +0 -45
  22. package/benchmarks/results/container-scale-2026-09-25T21-01-20-540Z.json +0 -150
  23. package/benchmarks/results/container-scale-2026-09-25T21-15-01-441Z.json +0 -73
  24. package/benchmarks/results/container-scale-2026-09-25T22-09-01-630Z.json +0 -77
  25. package/benchmarks/results/container-scale-2026-09-25T22-18-28-371Z.json +0 -202
  26. package/cli/AGENTS.md +0 -390
  27. package/cli/DESIGN.md +0 -735
  28. package/cli/README.md +0 -292
  29. package/cli/biome.json +0 -40
  30. package/cli/dependency-graph.svg +0 -1135
  31. package/cli/dist/cli.d.ts +0 -3
  32. package/cli/dist/cli.d.ts.map +0 -1
  33. package/cli/dist/cli.js.map +0 -1
  34. package/cli/dist/commands/completion.d.ts +0 -3
  35. package/cli/dist/commands/completion.d.ts.map +0 -1
  36. package/cli/dist/commands/completion.js.map +0 -1
  37. package/cli/dist/commands/config.d.ts +0 -3
  38. package/cli/dist/commands/config.d.ts.map +0 -1
  39. package/cli/dist/commands/config.js.map +0 -1
  40. package/cli/dist/commands/doctor.d.ts +0 -19
  41. package/cli/dist/commands/doctor.d.ts.map +0 -1
  42. package/cli/dist/commands/doctor.js.map +0 -1
  43. package/cli/dist/commands/down.d.ts +0 -3
  44. package/cli/dist/commands/down.d.ts.map +0 -1
  45. package/cli/dist/commands/down.js.map +0 -1
  46. package/cli/dist/commands/help.d.ts +0 -2
  47. package/cli/dist/commands/help.d.ts.map +0 -1
  48. package/cli/dist/commands/help.js.map +0 -1
  49. package/cli/dist/commands/networks.d.ts +0 -3
  50. package/cli/dist/commands/networks.d.ts.map +0 -1
  51. package/cli/dist/commands/networks.js.map +0 -1
  52. package/cli/dist/commands/project.d.ts +0 -9
  53. package/cli/dist/commands/project.d.ts.map +0 -1
  54. package/cli/dist/commands/project.js.map +0 -1
  55. package/cli/dist/commands/projects.d.ts +0 -3
  56. package/cli/dist/commands/projects.d.ts.map +0 -1
  57. package/cli/dist/commands/projects.js.map +0 -1
  58. package/cli/dist/commands/resource.d.ts +0 -69
  59. package/cli/dist/commands/resource.d.ts.map +0 -1
  60. package/cli/dist/commands/resource.js.map +0 -1
  61. package/cli/dist/commands/resources.d.ts +0 -3
  62. package/cli/dist/commands/resources.d.ts.map +0 -1
  63. package/cli/dist/commands/resources.js.map +0 -1
  64. package/cli/dist/commands/stack.d.ts +0 -3
  65. package/cli/dist/commands/stack.d.ts.map +0 -1
  66. package/cli/dist/commands/stack.js.map +0 -1
  67. package/cli/dist/commands/stacks.d.ts +0 -3
  68. package/cli/dist/commands/stacks.d.ts.map +0 -1
  69. package/cli/dist/commands/stacks.js.map +0 -1
  70. package/cli/dist/commands/status.d.ts +0 -3
  71. package/cli/dist/commands/status.d.ts.map +0 -1
  72. package/cli/dist/commands/status.js.map +0 -1
  73. package/cli/dist/commands/ui.d.ts +0 -3
  74. package/cli/dist/commands/ui.d.ts.map +0 -1
  75. package/cli/dist/commands/ui.js.map +0 -1
  76. package/cli/dist/commands/up.d.ts +0 -3
  77. package/cli/dist/commands/up.d.ts.map +0 -1
  78. package/cli/dist/commands/up.js.map +0 -1
  79. package/cli/dist/commands/upgrade.d.ts +0 -14
  80. package/cli/dist/commands/upgrade.d.ts.map +0 -1
  81. package/cli/dist/commands/upgrade.js.map +0 -1
  82. package/cli/dist/commands/version.d.ts +0 -3
  83. package/cli/dist/commands/version.d.ts.map +0 -1
  84. package/cli/dist/commands/version.js.map +0 -1
  85. package/cli/dist/components/Accessible.d.ts +0 -8
  86. package/cli/dist/components/Accessible.d.ts.map +0 -1
  87. package/cli/dist/components/Accessible.js.map +0 -1
  88. package/cli/dist/components/BaseTooltip.d.ts +0 -4
  89. package/cli/dist/components/BaseTooltip.d.ts.map +0 -1
  90. package/cli/dist/components/BaseTooltip.js.map +0 -1
  91. package/cli/dist/components/DetailPanel.d.ts +0 -4
  92. package/cli/dist/components/DetailPanel.d.ts.map +0 -1
  93. package/cli/dist/components/DetailPanel.js.map +0 -1
  94. package/cli/dist/components/FileTree.d.ts +0 -4
  95. package/cli/dist/components/FileTree.d.ts.map +0 -1
  96. package/cli/dist/components/FileTree.js.map +0 -1
  97. package/cli/dist/components/ResourceSelectInput.d.ts +0 -4
  98. package/cli/dist/components/ResourceSelectInput.d.ts.map +0 -1
  99. package/cli/dist/components/ResourceSelectInput.js.map +0 -1
  100. package/cli/dist/components/ResourceTable.d.ts +0 -4
  101. package/cli/dist/components/ResourceTable.d.ts.map +0 -1
  102. package/cli/dist/components/ResourceTable.js.map +0 -1
  103. package/cli/dist/components/TabBar.d.ts +0 -4
  104. package/cli/dist/components/TabBar.d.ts.map +0 -1
  105. package/cli/dist/components/TabBar.js.map +0 -1
  106. package/cli/dist/components/Tooltip.d.ts +0 -12
  107. package/cli/dist/components/Tooltip.d.ts.map +0 -1
  108. package/cli/dist/components/Tooltip.js.map +0 -1
  109. package/cli/dist/components/index.d.ts +0 -8
  110. package/cli/dist/components/index.d.ts.map +0 -1
  111. package/cli/dist/components/index.js.map +0 -1
  112. package/cli/dist/config/platform-standards.d.ts +0 -159
  113. package/cli/dist/config/platform-standards.d.ts.map +0 -1
  114. package/cli/dist/config/platform-standards.js.map +0 -1
  115. package/cli/dist/generator/extension-fetch.d.ts +0 -25
  116. package/cli/dist/generator/extension-fetch.d.ts.map +0 -1
  117. package/cli/dist/generator/extension-fetch.js.map +0 -1
  118. package/cli/dist/generator/template-engine.d.ts +0 -62
  119. package/cli/dist/generator/template-engine.d.ts.map +0 -1
  120. package/cli/dist/generator/template-engine.js.map +0 -1
  121. package/cli/dist/index.d.ts +0 -11
  122. package/cli/dist/index.d.ts.map +0 -1
  123. package/cli/dist/index.js.map +0 -1
  124. package/cli/dist/types/index.d.ts +0 -257
  125. package/cli/dist/types/index.d.ts.map +0 -1
  126. package/cli/dist/types/index.js.map +0 -1
  127. package/cli/dist/utils/cache.d.ts +0 -19
  128. package/cli/dist/utils/cache.d.ts.map +0 -1
  129. package/cli/dist/utils/cache.js.map +0 -1
  130. package/cli/dist/utils/command-helpers.d.ts +0 -17
  131. package/cli/dist/utils/command-helpers.d.ts.map +0 -1
  132. package/cli/dist/utils/command-helpers.js.map +0 -1
  133. package/cli/dist/utils/constants.d.ts +0 -53
  134. package/cli/dist/utils/constants.d.ts.map +0 -1
  135. package/cli/dist/utils/constants.js.map +0 -1
  136. package/cli/dist/utils/discovery-context.d.ts +0 -4
  137. package/cli/dist/utils/discovery-context.d.ts.map +0 -1
  138. package/cli/dist/utils/discovery-context.js.map +0 -1
  139. package/cli/dist/utils/docker.d.ts +0 -4
  140. package/cli/dist/utils/docker.d.ts.map +0 -1
  141. package/cli/dist/utils/docker.js.map +0 -1
  142. package/cli/dist/utils/doctor-runtime.d.ts +0 -81
  143. package/cli/dist/utils/doctor-runtime.d.ts.map +0 -1
  144. package/cli/dist/utils/doctor-runtime.js.map +0 -1
  145. package/cli/dist/utils/env-validator.d.ts +0 -15
  146. package/cli/dist/utils/env-validator.d.ts.map +0 -1
  147. package/cli/dist/utils/env-validator.js.map +0 -1
  148. package/cli/dist/utils/errors.d.ts +0 -31
  149. package/cli/dist/utils/errors.d.ts.map +0 -1
  150. package/cli/dist/utils/errors.js.map +0 -1
  151. package/cli/dist/utils/file-helpers.d.ts +0 -14
  152. package/cli/dist/utils/file-helpers.d.ts.map +0 -1
  153. package/cli/dist/utils/file-helpers.js.map +0 -1
  154. package/cli/dist/utils/formatting.d.ts +0 -30
  155. package/cli/dist/utils/formatting.d.ts.map +0 -1
  156. package/cli/dist/utils/formatting.js.map +0 -1
  157. package/cli/dist/utils/paths.d.ts +0 -3
  158. package/cli/dist/utils/paths.d.ts.map +0 -1
  159. package/cli/dist/utils/paths.js.map +0 -1
  160. package/cli/dist/utils/port-assignment.d.ts +0 -6
  161. package/cli/dist/utils/port-assignment.d.ts.map +0 -1
  162. package/cli/dist/utils/port-assignment.js.map +0 -1
  163. package/cli/dist/utils/project-features.d.ts +0 -23
  164. package/cli/dist/utils/project-features.d.ts.map +0 -1
  165. package/cli/dist/utils/project-features.js.map +0 -1
  166. package/cli/dist/utils/project-templates.d.ts +0 -7
  167. package/cli/dist/utils/project-templates.d.ts.map +0 -1
  168. package/cli/dist/utils/project-templates.js.map +0 -1
  169. package/cli/dist/utils/prompt.d.ts +0 -28
  170. package/cli/dist/utils/prompt.d.ts.map +0 -1
  171. package/cli/dist/utils/prompt.js.map +0 -1
  172. package/cli/dist/utils/resource-features.d.ts +0 -29
  173. package/cli/dist/utils/resource-features.d.ts.map +0 -1
  174. package/cli/dist/utils/resource-features.js.map +0 -1
  175. package/cli/dist/utils/service-urls.d.ts +0 -30
  176. package/cli/dist/utils/service-urls.d.ts.map +0 -1
  177. package/cli/dist/utils/service-urls.js.map +0 -1
  178. package/cli/dist/utils/services.d.ts +0 -18
  179. package/cli/dist/utils/services.d.ts.map +0 -1
  180. package/cli/dist/utils/services.js.map +0 -1
  181. package/cli/dist/utils/stack-features.d.ts +0 -22
  182. package/cli/dist/utils/stack-features.d.ts.map +0 -1
  183. package/cli/dist/utils/stack-features.js.map +0 -1
  184. package/cli/dist/utils/tar.d.ts +0 -7
  185. package/cli/dist/utils/tar.d.ts.map +0 -1
  186. package/cli/dist/utils/tar.js.map +0 -1
  187. package/cli/dist/utils/tilt.d.ts +0 -20
  188. package/cli/dist/utils/tilt.d.ts.map +0 -1
  189. package/cli/dist/utils/tilt.js.map +0 -1
  190. package/cli/dist/utils/validation.d.ts +0 -16
  191. package/cli/dist/utils/validation.d.ts.map +0 -1
  192. package/cli/dist/utils/validation.js.map +0 -1
  193. package/cli/knip.json +0 -6
  194. package/cli/pnpm-lock.yaml +0 -2305
  195. package/cli/pnpm-workspace.yaml +0 -2
  196. package/cli/src/cli.ts +0 -72
  197. package/cli/src/commands/__tests__/always-enabled-infra.test.ts +0 -109
  198. package/cli/src/commands/__tests__/config.test.ts +0 -146
  199. package/cli/src/commands/__tests__/doctor.test.ts +0 -730
  200. package/cli/src/commands/__tests__/error-handling.test.ts +0 -78
  201. package/cli/src/commands/__tests__/phases-model.test.ts +0 -161
  202. package/cli/src/commands/__tests__/project-resource-features-e2e.test.ts +0 -187
  203. package/cli/src/commands/__tests__/project.test.ts +0 -68
  204. package/cli/src/commands/__tests__/resource.test.ts +0 -226
  205. package/cli/src/commands/__tests__/saas-starter-integration.test.ts +0 -194
  206. package/cli/src/commands/__tests__/stack-features-e2e.test.ts +0 -135
  207. package/cli/src/commands/__tests__/upgrade.test.ts +0 -198
  208. package/cli/src/commands/completion.ts +0 -300
  209. package/cli/src/commands/config.ts +0 -299
  210. package/cli/src/commands/doctor.ts +0 -926
  211. package/cli/src/commands/down.ts +0 -37
  212. package/cli/src/commands/help.ts +0 -143
  213. package/cli/src/commands/networks-design.md +0 -80
  214. package/cli/src/commands/networks.ts +0 -332
  215. package/cli/src/commands/project.ts +0 -456
  216. package/cli/src/commands/projects.ts +0 -82
  217. package/cli/src/commands/resource.ts +0 -652
  218. package/cli/src/commands/resources.ts +0 -95
  219. package/cli/src/commands/stack.ts +0 -128
  220. package/cli/src/commands/stacks.ts +0 -52
  221. package/cli/src/commands/status.ts +0 -88
  222. package/cli/src/commands/ui.tsx +0 -843
  223. package/cli/src/commands/up.ts +0 -169
  224. package/cli/src/commands/upgrade.ts +0 -628
  225. package/cli/src/commands/version.ts +0 -9
  226. package/cli/src/components/Accessible.tsx +0 -21
  227. package/cli/src/components/BaseTooltip.tsx +0 -66
  228. package/cli/src/components/DetailPanel.tsx +0 -146
  229. package/cli/src/components/FileTree.tsx +0 -89
  230. package/cli/src/components/ResourceSelectInput.tsx +0 -30
  231. package/cli/src/components/ResourceTable.tsx +0 -78
  232. package/cli/src/components/TabBar.tsx +0 -53
  233. package/cli/src/components/Tooltip.tsx +0 -11
  234. package/cli/src/components/index.ts +0 -7
  235. package/cli/src/config/platform-standards.test.ts +0 -11
  236. package/cli/src/config/platform-standards.ts +0 -196
  237. package/cli/src/generator/__tests__/extension-fetch.test.ts +0 -242
  238. package/cli/src/generator/__tests__/template-engine.test.ts +0 -70
  239. package/cli/src/generator/extension-fetch.ts +0 -286
  240. package/cli/src/generator/template-engine.ts +0 -752
  241. package/cli/src/index.ts +0 -86
  242. package/cli/src/templates.d.ts +0 -4
  243. package/cli/src/types/index.ts +0 -353
  244. package/cli/src/utils/__tests__/cache.test.ts +0 -98
  245. package/cli/src/utils/__tests__/formatting.test.ts +0 -170
  246. package/cli/src/utils/__tests__/paths.test.ts +0 -180
  247. package/cli/src/utils/__tests__/port-assignment.test.ts +0 -121
  248. package/cli/src/utils/__tests__/service-urls.test.ts +0 -174
  249. package/cli/src/utils/__tests__/services.test.ts +0 -531
  250. package/cli/src/utils/__tests__/tilt.test.ts +0 -172
  251. package/cli/src/utils/__tests__/validation.test.ts +0 -148
  252. package/cli/src/utils/cache.ts +0 -76
  253. package/cli/src/utils/command-helpers.ts +0 -54
  254. package/cli/src/utils/constants.ts +0 -130
  255. package/cli/src/utils/discovery-context.ts +0 -49
  256. package/cli/src/utils/docker.ts +0 -20
  257. package/cli/src/utils/doctor-runtime.ts +0 -644
  258. package/cli/src/utils/env-validator.ts +0 -139
  259. package/cli/src/utils/errors.ts +0 -168
  260. package/cli/src/utils/file-helpers.ts +0 -62
  261. package/cli/src/utils/formatting.ts +0 -215
  262. package/cli/src/utils/paths.ts +0 -53
  263. package/cli/src/utils/port-assignment.ts +0 -106
  264. package/cli/src/utils/project-features.ts +0 -118
  265. package/cli/src/utils/project-templates.ts +0 -29
  266. package/cli/src/utils/prompt.ts +0 -128
  267. package/cli/src/utils/resource-features.ts +0 -215
  268. package/cli/src/utils/service-urls.ts +0 -124
  269. package/cli/src/utils/services.ts +0 -364
  270. package/cli/src/utils/stack-features.ts +0 -30
  271. package/cli/src/utils/tar.ts +0 -24
  272. package/cli/src/utils/tilt.ts +0 -133
  273. package/cli/src/utils/validation.ts +0 -67
  274. package/cli/tsconfig.json +0 -25
  275. package/cli/tsconfig.typecheck.json +0 -8
  276. package/docs/FEATURES.md +0 -281
  277. package/docs/demo.svg +0 -217
  278. package/engine/docs/AUTODISCOVERY_TEST.md +0 -182
  279. package/engine/docs/C4-ARCHITECTURE-operations.md +0 -396
  280. package/engine/docs/C4-ARCHITECTURE-overview.md +0 -284
  281. package/engine/docs/C4-ARCHITECTURE.md +0 -17
  282. package/engine/docs/GOLDEN_IMAGE_INTEGRATION.md +0 -349
  283. package/engine/docs/MANIFEST_DRIVEN_ARCHITECTURE.md +0 -357
  284. package/engine/docs/README.md +0 -284
  285. package/install.sh +0 -19
  286. package/knip.json +0 -10
  287. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/.openspec.yaml +0 -2
  288. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/design.md +0 -102
  289. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/proposal.md +0 -31
  290. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-notifications/spec.md +0 -48
  291. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-orchestration/spec.md +0 -63
  292. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-troubleshooting/spec.md +0 -56
  293. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/tasks.md +0 -61
  294. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/.openspec.yaml +0 -2
  295. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/design.md +0 -114
  296. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/proposal.md +0 -32
  297. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/manifest-field-migration/spec.md +0 -20
  298. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/resource-terminology-standard/spec.md +0 -20
  299. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/stack-terminology-standard/spec.md +0 -20
  300. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/tasks.md +0 -194
  301. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/.openspec.yaml +0 -2
  302. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/design.md +0 -111
  303. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/proposal.md +0 -33
  304. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/function-naming-alignment/spec.md +0 -12
  305. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/optional-parameter-patterns/spec.md +0 -18
  306. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-documentation/spec.md +0 -16
  307. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-naming-consistency/spec.md +0 -12
  308. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-ordering-standard/spec.md +0 -12
  309. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/tasks.md +0 -194
  310. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/.openspec.yaml +0 -2
  311. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/design.md +0 -84
  312. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/proposal.md +0 -31
  313. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/codebase-audit/spec.md +0 -27
  314. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/dead-code-cleanup/spec.md +0 -27
  315. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/test-gap-fill/spec.md +0 -23
  316. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/todo-resolution/spec.md +0 -27
  317. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/tasks.md +0 -39
  318. package/openspec/changes/archive/2026-06-25-landscape-consolidation/.openspec.yaml +0 -2
  319. package/openspec/changes/archive/2026-06-25-landscape-consolidation/design.md +0 -80
  320. package/openspec/changes/archive/2026-06-25-landscape-consolidation/proposal.md +0 -40
  321. package/openspec/changes/archive/2026-06-25-landscape-consolidation/spec.md +0 -52
  322. package/openspec/changes/archive/2026-06-25-landscape-consolidation/tasks.md +0 -51
  323. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/.openspec.yaml +0 -2
  324. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/design.md +0 -43
  325. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/proposal.md +0 -29
  326. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/specs/self-contained-generated-projects/spec.md +0 -67
  327. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/tasks.md +0 -27
  328. package/openspec/changes/optimize-runtime-docker-layers/.openspec.yaml +0 -3
  329. package/openspec/changes/optimize-runtime-docker-layers/design.md +0 -81
  330. package/openspec/changes/optimize-runtime-docker-layers/proposal.md +0 -44
  331. package/openspec/changes/optimize-runtime-docker-layers/specs/runtime-container-footprint/spec.md +0 -79
  332. package/openspec/changes/optimize-runtime-docker-layers/tasks.md +0 -36
  333. package/openspec/changes/prioritized-cold-start/.openspec.yaml +0 -4
  334. package/openspec/changes/prioritized-cold-start/design.md +0 -137
  335. package/openspec/changes/prioritized-cold-start/proposal.md +0 -41
  336. package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +0 -98
  337. package/openspec/changes/prioritized-cold-start/tasks.md +0 -69
  338. package/openspec/config.yaml +0 -1
  339. package/openspec/specs/manifest-field-migration/spec.md +0 -24
  340. package/openspec/specs/resource-terminology-standard/spec.md +0 -24
  341. package/openspec/specs/self-contained-generated-projects/spec.md +0 -67
  342. package/openspec/specs/stack-terminology-standard/spec.md +0 -24
  343. package/platform/services/platform/proxy/service.yaml +0 -14
  344. package/scripts/audit-codebase.sh +0 -213
  345. package/scripts/autoresearch/layer-eval.sh +0 -65
  346. package/scripts/benchmark/README.md +0 -25
  347. package/scripts/benchmark/container-scale.ts +0 -503
  348. package/scripts/benchmark/pre-release-gate.sh +0 -9
  349. package/scripts/install-git-hooks.sh +0 -14
  350. package/scripts/measure-idle-footprint.sh +0 -58
  351. package/scripts/record-demo.sh +0 -43
  352. package/scripts/release-binaries.sh +0 -97
  353. package/scripts/setup-pre-commit.sh +0 -33
  354. package/scripts/stage-npm-engine.sh +0 -36
  355. package/tests/tilt-engine/__init__.py +0 -5
  356. package/tests/tilt-engine/conftest.py +0 -182
  357. package/tests/tilt-engine/fixtures/appointment-frontend-service.json +0 -11
  358. package/tests/tilt-engine/fixtures/identity-backend-service.json +0 -12
  359. package/tests/tilt-engine/fixtures/invalid-service.json +0 -4
  360. package/tests/tilt-engine/test_agents_md_generator.py +0 -341
  361. package/tests/tilt-engine/test_agents_md_integration.py +0 -378
  362. package/tests/tilt-engine/test_cache_operations.py +0 -181
  363. package/tests/tilt-engine/test_cache_persistence.py +0 -142
  364. package/tests/tilt-engine/test_code_executor.py +0 -152
  365. package/tests/tilt-engine/test_code_viewer.py +0 -127
  366. package/tests/tilt-engine/test_compose_port_anchors.py +0 -310
  367. package/tests/tilt-engine/test_config_inspector.py +0 -162
  368. package/tests/tilt-engine/test_daemon_focus_mode.py +0 -89
  369. package/tests/tilt-engine/test_daemon_polling.py +0 -122
  370. package/tests/tilt-engine/test_daemon_registration.py +0 -115
  371. package/tests/tilt-engine/test_daemon_validation.py +0 -107
  372. package/tests/tilt-engine/test_docker_generator_bugs.py +0 -493
  373. package/tests/tilt-engine/test_docker_prune.py +0 -119
  374. package/tests/tilt-engine/test_dockerignore_path_bug.py +0 -82
  375. package/tests/tilt-engine/test_file_browser.py +0 -193
  376. package/tests/tilt-engine/test_frontend_basepath_manifest.py +0 -27
  377. package/tests/tilt-engine/test_health_dependencies.py +0 -130
  378. package/tests/tilt-engine/test_health_endpoints.py +0 -118
  379. package/tests/tilt-engine/test_health_tilt_ui.py +0 -85
  380. package/tests/tilt-engine/test_runtime_container_footprint.py +0 -162
  381. package/tests/tilt-engine/test_sablier_defer_start.py +0 -237
  382. package/tests/tilt-engine/test_safety_aliases.py +0 -84
  383. package/tests/tilt-engine/test_snapshot_diff.py +0 -136
  384. package/tests/tilt-engine/test_snapshot_persistence.py +0 -163
  385. package/tests/tilt-engine/test_snapshot_scanning.py +0 -123
  386. package/tests/tilt-engine/test_standalone_project_boot.py +0 -61
  387. package/tests/tilt-engine/test_standalone_traefik.py +0 -160
  388. 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