hammerkit 1.6.0 → 1.8.0

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 (277) hide show
  1. package/README.md +7 -7
  2. package/build.schema.json +59 -15
  3. package/dist/cache/backends/local-cache-backend.js +58 -1
  4. package/dist/cache/backends/registry/registry-client.js +264 -0
  5. package/dist/cache/backends/registry/registry-credentials.js +81 -0
  6. package/dist/cache/backends/registry/registry-reference.js +36 -0
  7. package/dist/cache/backends/registry-cache-backend.js +145 -0
  8. package/dist/cache/backends/s3-cache-backend.js +99 -46
  9. package/dist/cache/cache-backend-registry.js +2 -0
  10. package/dist/cache/cache-inventory.js +84 -0
  11. package/dist/cache/cache-sync.js +77 -0
  12. package/dist/cache/explain.js +214 -0
  13. package/dist/cache/last-resolved.js +48 -0
  14. package/dist/cache/read-only.js +15 -0
  15. package/dist/cache/retention.js +61 -0
  16. package/dist/cli.js +43 -0
  17. package/dist/docker/using-container.js +17 -1
  18. package/dist/executer/archive-task-entry.js +21 -0
  19. package/dist/executer/await-completed-dependencies.js +32 -14
  20. package/dist/executer/cache-metadata.js +9 -2
  21. package/dist/executer/docker-service.js +2 -1
  22. package/dist/executer/docker-task.js +18 -3
  23. package/dist/executer/dry-run.js +73 -0
  24. package/dist/executer/event-cache.js +6 -5
  25. package/dist/executer/execute-command.js +21 -3
  26. package/dist/executer/execute-docker.js +18 -2
  27. package/dist/executer/execute-work-service.js +10 -1
  28. package/dist/executer/execute-work-task.js +111 -26
  29. package/dist/executer/execute-work-tree.js +26 -1
  30. package/dist/executer/execution-steps.js +3 -8
  31. package/dist/executer/get-container-binds.js +13 -1
  32. package/dist/executer/get-scheduler-execute-result.js +4 -1
  33. package/dist/executer/lazy-resolver.js +15 -3
  34. package/dist/executer/reset-work-tree.js +2 -0
  35. package/dist/executer/run-summary.js +102 -0
  36. package/dist/executer/scheduler/enqueue-next.js +15 -60
  37. package/dist/executer/scheduler/state-key.js +78 -0
  38. package/dist/index.js +14 -1
  39. package/dist/kubernetes/apply.js +6 -7
  40. package/dist/kubernetes/await-running-state.js +100 -60
  41. package/dist/kubernetes/ensure-http-route.js +7 -10
  42. package/dist/kubernetes/ensure-namespace.js +9 -10
  43. package/dist/kubernetes/ensure-persistent-data.js +6 -5
  44. package/dist/kubernetes/get-service-ip.js +2 -2
  45. package/dist/kubernetes/kubernetes-instance.js +0 -4
  46. package/dist/kubernetes/remove-persistent-data.js +16 -6
  47. package/dist/kubernetes/resolve-pod-name.js +7 -7
  48. package/dist/log.js +16 -6
  49. package/dist/logging/live-logger.js +2 -1
  50. package/dist/optimizer/calculate-checksum.js +8 -2
  51. package/dist/optimizer/get-work-cache-stats.js +66 -16
  52. package/dist/optimizer/work-task-cache-description.js +34 -5
  53. package/dist/planner/project-root.js +26 -0
  54. package/dist/planner/utils/append-work-environment.js +1 -1
  55. package/dist/planner/utils/append-work-service.js +2 -1
  56. package/dist/planner/utils/append-work-task.js +7 -1
  57. package/dist/planner/utils/get-outputs-to-reset.js +20 -0
  58. package/dist/planner/utils/parse-work-generate.js +2 -2
  59. package/dist/planner/utils/parse-work-source.js +35 -30
  60. package/dist/planner/utils/portable-path.js +30 -0
  61. package/dist/planner/work-cache-id.js +2 -2
  62. package/dist/planner/work-graph-serializer.js +103 -0
  63. package/dist/planner/work-instance-id.js +15 -0
  64. package/dist/planner/work-runtime-docker.js +58 -15
  65. package/dist/planner/work-runtime-kubernetes.js +95 -45
  66. package/dist/planner/work-runtime-local.js +31 -8
  67. package/dist/planner/work-service-id.js +16 -4
  68. package/dist/program.js +235 -1
  69. package/dist/schema/build-file-base-task-schema.js +4 -0
  70. package/dist/schema/build-file-environment-schema-http-route.js +15 -0
  71. package/dist/schema/build-file-environment-schema.js +8 -2
  72. package/dist/schema/build-file-schema.js +1 -1
  73. package/dist/schema/cache-schema.js +21 -2
  74. package/dist/schema/duration-schema.js +18 -0
  75. package/dist/schema/envs-schema.js +1 -1
  76. package/dist/schema/reference-parser.js +3 -0
  77. package/dist/schema/size-schema.js +18 -0
  78. package/dist/schema/work-scope-parser.js +15 -2
  79. package/dist/utils/units.js +87 -0
  80. package/package.json +20 -9
  81. package/.agents/FEEDBACK.md +0 -74
  82. package/.agents/skills/before-commit/SKILL.md +0 -101
  83. package/.agents/skills/do-the-work/SKILL.md +0 -64
  84. package/.agents/skills/fix-ci/SKILL.md +0 -98
  85. package/.agents/skills/speckit-git-commit/SKILL.md +0 -53
  86. package/.agents/skills/speckit-git-feature/SKILL.md +0 -72
  87. package/.agents/skills/speckit-git-initialize/SKILL.md +0 -54
  88. package/.agents/skills/speckit-git-remote/SKILL.md +0 -50
  89. package/.agents/skills/speckit-git-validate/SKILL.md +0 -54
  90. package/.devcontainer/Dockerfile +0 -5
  91. package/.devcontainer/devcontainer.json +0 -40
  92. package/.gitbook.yaml +0 -5
  93. package/.specify/extensions/.registry +0 -37
  94. package/.specify/extensions/git/README.md +0 -100
  95. package/.specify/extensions/git/commands/speckit.git.commit.md +0 -48
  96. package/.specify/extensions/git/commands/speckit.git.feature.md +0 -67
  97. package/.specify/extensions/git/commands/speckit.git.initialize.md +0 -49
  98. package/.specify/extensions/git/commands/speckit.git.remote.md +0 -45
  99. package/.specify/extensions/git/commands/speckit.git.validate.md +0 -49
  100. package/.specify/extensions/git/config-template.yml +0 -62
  101. package/.specify/extensions/git/extension.yml +0 -140
  102. package/.specify/extensions/git/git-config.yml +0 -62
  103. package/.specify/extensions.yml +0 -149
  104. package/.specify/feature.json +0 -3
  105. package/.specify/init-options.json +0 -10
  106. package/.specify/integration.json +0 -15
  107. package/.specify/integrations/claude.manifest.json +0 -16
  108. package/.specify/integrations/speckit.manifest.json +0 -17
  109. package/.specify/memory/constitution.md +0 -162
  110. package/.specify/templates/checklist-template.md +0 -40
  111. package/.specify/templates/constitution-template.md +0 -50
  112. package/.specify/templates/plan-template.md +0 -113
  113. package/.specify/templates/spec-template.md +0 -131
  114. package/.specify/templates/tasks-template.md +0 -252
  115. package/.specify/workflows/speckit/workflow.yml +0 -77
  116. package/.specify/workflows/workflow-registry.json +0 -13
  117. package/AGENTS.md +0 -94
  118. package/Dockerfile +0 -32
  119. package/FOLLOWUPS.md +0 -34
  120. package/SUMMARY.md +0 -68
  121. package/VALUES.md +0 -53
  122. package/dist/cache/auto-pull.spec.js +0 -123
  123. package/dist/cache/backends/local-cache-backend.spec.js +0 -72
  124. package/dist/cache/backends/s3-cache-backend.spec.js +0 -118
  125. package/dist/cache/cache-backend-registry.spec.js +0 -22
  126. package/dist/cache/resolve-cache.spec.js +0 -52
  127. package/dist/cli.spec.js +0 -23
  128. package/dist/console/get-logger.spec.js +0 -23
  129. package/dist/docker/get-default-platform.spec.js +0 -29
  130. package/dist/docker/longest-common-prefix.spec.js +0 -26
  131. package/dist/docker/package.spec.js +0 -145
  132. package/dist/docker/remove-container.spec.js +0 -32
  133. package/dist/docker/using-container.spec.js +0 -90
  134. package/dist/environment/merge-environment-variables.spec.js +0 -24
  135. package/dist/environment/replace-env-variables.spec.js +0 -47
  136. package/dist/executer/abort.spec.js +0 -18
  137. package/dist/executer/environment-mock.spec.js +0 -22
  138. package/dist/executer/execute.spec.js +0 -51
  139. package/dist/executer/get-service-containers.spec.js +0 -59
  140. package/dist/executer/get-service-env-hints.spec.js +0 -65
  141. package/dist/executer/label-values.spec.js +0 -61
  142. package/dist/executer/lazy-resolver.spec.js +0 -20
  143. package/dist/executer/process-manager.spec.js +0 -54
  144. package/dist/executer/scheduler/enqueue-next.spec.js +0 -136
  145. package/dist/executer/state-resolver.spec.js +0 -37
  146. package/dist/executer/state.spec.js +0 -66
  147. package/dist/executer/states.spec.js +0 -14
  148. package/dist/file/get-file-context.spec.js +0 -178
  149. package/dist/file/move-files.spec.js +0 -48
  150. package/dist/kubernetes/ensure-http-route.spec.js +0 -70
  151. package/dist/kubernetes/ensure-ingress.js +0 -54
  152. package/dist/kubernetes/ensure-namespace.spec.js +0 -39
  153. package/dist/kubernetes/resolve-pod-name.spec.js +0 -81
  154. package/dist/log.spec.js +0 -131
  155. package/dist/logging/grouped-logger.spec.js +0 -84
  156. package/dist/logging/interactive-logger.spec.js +0 -57
  157. package/dist/logging/live-logger.spec.js +0 -37
  158. package/dist/logging/loggers.spec.js +0 -154
  159. package/dist/optimizer/calculate-checksum.spec.js +0 -34
  160. package/dist/optimizer/get-cache-directory.spec.js +0 -59
  161. package/dist/optimizer/get-work-cache-stats.spec.js +0 -79
  162. package/dist/optimizer/work-task-cache-description.spec.js +0 -45
  163. package/dist/parser/default-build-file.spec.js +0 -48
  164. package/dist/parser/parse-label-arguments.spec.js +0 -27
  165. package/dist/parser/read-build-file.spec.js +0 -89
  166. package/dist/parser/read-env-file.spec.js +0 -42
  167. package/dist/planner/utils/append-work-service.spec.js +0 -71
  168. package/dist/planner/utils/get-container-user.spec.js +0 -30
  169. package/dist/planner/utils/get-default-kube-config.spec.js +0 -11
  170. package/dist/planner/utils/normalize-path.spec.js +0 -24
  171. package/dist/planner/utils/parse-work-command.spec.js +0 -21
  172. package/dist/planner/utils/parse-work-generate.spec.js +0 -45
  173. package/dist/planner/utils/parse-work-mount.spec.js +0 -62
  174. package/dist/planner/utils/parse-work-mounts.spec.js +0 -19
  175. package/dist/planner/utils/parse-work-port.spec.js +0 -24
  176. package/dist/planner/utils/parse-work-ports.spec.js +0 -24
  177. package/dist/planner/utils/parse-work-source.spec.js +0 -58
  178. package/dist/planner/utils/parse-work-volume.spec.js +0 -18
  179. package/dist/planner/utils/plan-work-tasks.spec.js +0 -30
  180. package/dist/planner/utils/plan-work-volume.spec.js +0 -33
  181. package/dist/planner/utils/split-name.spec.js +0 -16
  182. package/dist/planner/utils/template-value.spec.js +0 -14
  183. package/dist/planner/validate.spec.js +0 -251
  184. package/dist/planner/work-cache-id.spec.js +0 -173
  185. package/dist/planner/work-runtime-local.spec.js +0 -193
  186. package/dist/program-commands.spec.js +0 -84
  187. package/dist/program.spec.js +0 -43
  188. package/dist/schema/build-file-environment-schema-ingress.js +0 -14
  189. package/dist/schema/build-file-schema.spec.js +0 -61
  190. package/dist/schema/doc-build-files.spec.js +0 -223
  191. package/dist/schema/parse-error.spec.js +0 -18
  192. package/dist/schema/reference-parser.spec.js +0 -83
  193. package/dist/schema/schema-parser.spec.js +0 -80
  194. package/dist/schema/type-guards.spec.js +0 -23
  195. package/dist/schema/work-scope-parser.spec.js +0 -66
  196. package/dist/service/update-service-status.spec.js +0 -27
  197. package/dist/testing/ensure-local-registry.js +0 -134
  198. package/dist/testing/example-test-suite.js +0 -63
  199. package/dist/testing/expect.js +0 -82
  200. package/dist/testing/get-test-suite.js +0 -9
  201. package/dist/testing/http-get.js +0 -21
  202. package/dist/testing/integration/cache.spec.js +0 -49
  203. package/dist/testing/integration/cancellation.spec.js +0 -45
  204. package/dist/testing/integration/clean.spec.js +0 -43
  205. package/dist/testing/integration/cmd.spec.js +0 -15
  206. package/dist/testing/integration/concurrency.spec.js +0 -19
  207. package/dist/testing/integration/docker-package-registry.spec.js +0 -44
  208. package/dist/testing/integration/docker-service-crash.spec.js +0 -36
  209. package/dist/testing/integration/docker.spec.js +0 -18
  210. package/dist/testing/integration/env.spec.js +0 -48
  211. package/dist/testing/integration/environment.spec.js +0 -97
  212. package/dist/testing/integration/error.spec.js +0 -19
  213. package/dist/testing/integration/export.spec.js +0 -22
  214. package/dist/testing/integration/extend.spec.js +0 -29
  215. package/dist/testing/integration/glob.spec.js +0 -40
  216. package/dist/testing/integration/include.spec.js +0 -31
  217. package/dist/testing/integration/invalid.spec.js +0 -17
  218. package/dist/testing/integration/invalid_loop.spec.js +0 -56
  219. package/dist/testing/integration/k8s-state-cache.spec.js +0 -41
  220. package/dist/testing/integration/kubernetes-service.spec.js +0 -148
  221. package/dist/testing/integration/kubernetes.spec.js +0 -99
  222. package/dist/testing/integration/labels.spec.js +0 -42
  223. package/dist/testing/integration/local.spec.js +0 -14
  224. package/dist/testing/integration/monorepo.spec.js +0 -15
  225. package/dist/testing/integration/multi-line.spec.js +0 -17
  226. package/dist/testing/integration/reference.spec.js +0 -21
  227. package/dist/testing/integration/services.spec.js +0 -38
  228. package/dist/testing/integration/store.spec.js +0 -52
  229. package/dist/testing/integration/unknown.spec.js +0 -28
  230. package/dist/testing/integration/validate.spec.js +0 -160
  231. package/dist/testing/integration/watch-dependency.spec.js +0 -33
  232. package/dist/testing/integration/watch.spec.js +0 -45
  233. package/dist/testing/requires-kubernetes.js +0 -18
  234. package/dist/testing/requires-linux-containers.js +0 -18
  235. package/dist/testing/requires-windows-containers.js +0 -18
  236. package/dist/testing/test-case.js +0 -75
  237. package/dist/testing/test-streams.js +0 -28
  238. package/dist/testing/test-suite-setup.js +0 -3
  239. package/dist/testing/test-suite.js +0 -3
  240. package/dist/testing/testing-timeout.js +0 -32
  241. package/dist/utils/abort-event.spec.js +0 -46
  242. package/dist/utils/buffer-context.spec.js +0 -38
  243. package/dist/utils/ci.spec.js +0 -53
  244. package/dist/utils/debouncer.spec.js +0 -58
  245. package/dist/utils/emitter.spec.js +0 -34
  246. package/dist/utils/empty-writable.spec.js +0 -18
  247. package/dist/utils/environment-config.spec.js +0 -26
  248. package/dist/utils/fail-never.spec.js +0 -9
  249. package/dist/utils/sleep.spec.js +0 -22
  250. package/dist/version.spec.js +0 -12
  251. package/specs/build-file/caches/spec.md +0 -114
  252. package/specs/build-file/environment-variables/spec.md +0 -82
  253. package/specs/build-file/includes/spec.md +0 -60
  254. package/specs/build-file/references/spec.md +0 -56
  255. package/specs/build-file/spec.md +0 -62
  256. package/specs/cli/clean/spec.md +0 -85
  257. package/specs/cli/down/spec.md +0 -85
  258. package/specs/cli/execute/spec.md +0 -101
  259. package/specs/cli/init/spec.md +0 -64
  260. package/specs/cli/ls/spec.md +0 -66
  261. package/specs/cli/package/spec.md +0 -110
  262. package/specs/cli/spec.md +0 -73
  263. package/specs/cli/store-restore/spec.md +0 -74
  264. package/specs/cli/up/spec.md +0 -80
  265. package/specs/cli/validate/spec.md +0 -70
  266. package/specs/labels/spec.md +0 -91
  267. package/specs/service/container/spec.md +0 -120
  268. package/specs/service/kubernetes/spec.md +0 -97
  269. package/specs/service/spec.md +0 -105
  270. package/specs/task/caching/spec.md +0 -107
  271. package/specs/task/container/spec.md +0 -118
  272. package/specs/task/dependencies/spec.md +0 -70
  273. package/specs/task/extending/spec.md +0 -74
  274. package/specs/task/kubernetes/spec.md +0 -125
  275. package/specs/task/needs/spec.md +0 -59
  276. package/specs/task/spec.md +0 -87
  277. package/specs/task/watching/spec.md +0 -69
@@ -1,120 +0,0 @@
1
- # Feature Specification: Service: Container
2
-
3
- **Feature Branch**: `001-current-state-specs`
4
-
5
- **Created**: 2026-05-30
6
-
7
- **Status**: Current State (reverse-specified from docs, hammerkit 1.6.0)
8
-
9
- **Input**: Existing behavior documented in `docs/service/container.md`
10
-
11
- ## User Scenarios & Testing *(mandatory)*
12
-
13
- ### User Story 1 - Declaring a container service with a healthcheck (Priority: P1)
14
-
15
- A developer needs a postgres database that is fully ready before any dependent task starts. They declare a container service with an `image`, optional environment variables, and a `healthcheck` command. Hammerkit starts the container and repeatedly executes the healthcheck inside it; when the command exits 0, dependent tasks are unblocked.
16
-
17
- **Why this priority**: The healthcheck is the central correctness guarantee for container services — without it tasks may fail due to a not-yet-ready service.
18
-
19
- **Independent Test**: Declare a postgres service with `healthcheck.cmd: "pg_isready -U postgres"`. Run a task that needs it and confirm the task only begins after `pg_isready` returns 0.
20
-
21
- **Acceptance Scenarios**:
22
-
23
- 1. **Given** a container service with a `healthcheck` configured, **When** a task that needs the service is triggered, **Then** hammerkit does not start the task until the healthcheck command exits with code 0 inside the service container.
24
-
25
- 2. **Given** a container service with no `healthcheck`, **When** a task that needs it is triggered, **Then** hammerkit starts the task as soon as the container is running (without waiting for application readiness).
26
-
27
- ### User Story 2 - Exposing service ports to the host for debugging (Priority: P2)
28
-
29
- A developer wants to connect a local SQL client to a running postgres service for inspection. They declare a `ports` mapping on the service so the container port is published to the host machine.
30
-
31
- **Why this priority**: Port exposure is optional for task connectivity but commonly needed during development for direct access.
32
-
33
- **Independent Test**: Declare a postgres service with `ports: [5432:5432]`. Start the service and verify that a connection to `localhost:5432` from the host succeeds.
34
-
35
- **Acceptance Scenarios**:
36
-
37
- 1. **Given** a service declares `ports: - 5432:5432`, **When** the service is running, **Then** port 5432 on the host machine is forwarded to the service container.
38
-
39
- 2. **Given** no `ports` are declared on a service, **When** a container task that needs it runs, **Then** the task can still reach the service on all its container ports via the container network without any published ports.
40
-
41
- ### User Story 3 - Persisting service data across restarts with volumes (Priority: P2)
42
-
43
- A developer wants database data to survive service restarts. They add a `volumes` entry mapping a named volume to the data directory inside the container.
44
-
45
- **Why this priority**: Without volumes, container services lose all state on every stop/start cycle, which breaks local development workflows.
46
-
47
- **Independent Test**: Declare a postgres service with a volume, seed it with data, stop and restart the service, and verify the data is still present.
48
-
49
- **Acceptance Scenarios**:
50
-
51
- 1. **Given** a service declares a named volume (e.g. `postgres-db:/var/lib/postgresql/data`), **When** the service is stopped and restarted, **Then** data written during the first run is still available after restart.
52
-
53
- ### User Story 4 - Mounting local config files into a service container (Priority: P3)
54
-
55
- A developer needs to supply a custom postgres configuration file. They declare a `mounts` entry to bind-mount the local file into the container at the expected path.
56
-
57
- **Why this priority**: Mounts are an advanced configuration option; most services do not require them.
58
-
59
- **Independent Test**: Declare a service with a mount pointing to a local config file. Start the service and verify the file is accessible at the mounted path inside the container.
60
-
61
- **Acceptance Scenarios**:
62
-
63
- 1. **Given** a service declares `mounts: - "./postgres.conf:/etc/postgresql/postgresql.conf"`, **When** the service container starts, **Then** the file at `./postgres.conf` on the host is readable at `/etc/postgresql/postgresql.conf` inside the container.
64
-
65
- ### User Story 5 - Exporting and importing service volume data (Priority: P3)
66
-
67
- A developer seeds a local database from a known-good snapshot using `hammerkit store` / `hammerkit restore` to export and import the service's volume data.
68
-
69
- **Why this priority**: Store/restore for services is a power feature enabling reproducible environments and data sharing between teammates.
70
-
71
- **Independent Test**: Run a service, write data, run `hammerkit store`, wipe the volume, run `hammerkit restore`, verify data is back.
72
-
73
- **Acceptance Scenarios**:
74
-
75
- 1. **Given** a running service with volume data, **When** `hammerkit store` is executed for that service, **Then** the volume data is exported to a transferable archive.
76
-
77
- 2. **Given** an exported archive, **When** `hammerkit restore` is executed, **Then** the service volume is populated with the archived data.
78
-
79
- ### Edge Cases
80
-
81
- - A service without a healthcheck may start a dependent task before the service application is ready to accept connections.
82
- - Mounts of large or frequently changing directories on macOS and Windows incur significant CPU overhead due to filesystem sync; volumes are preferred for data.
83
- - A service is stopped automatically when no further tasks in the current run require it (unless kept alive via `hammerkit up`).
84
-
85
- ## Requirements *(mandatory)*
86
-
87
- ### Functional Requirements
88
-
89
- - **FR-001**: System MUST start container services using the specified `image`.
90
- - **FR-002**: System MUST pass declared `envs` as environment variables into the service container.
91
- - **FR-003**: System MUST publish declared `ports` mappings from the service container to the host machine.
92
- - **FR-004**: System MUST execute the `healthcheck.cmd` inside the service container and only unblock dependent tasks once it exits with code 0.
93
- - **FR-005**: System MUST mount declared `volumes` (named volumes and bind paths) into the service container to persist data across restarts.
94
- - **FR-006**: System MUST bind-mount declared `mounts` (host path to container path) into the service container.
95
- - **FR-007**: System MUST make all container ports of a service reachable from other container tasks in the same network, regardless of whether `ports` are published to the host.
96
- - **FR-008**: System MUST support store/restore operations on service volumes via the `hammerkit store` and `hammerkit restore` CLI commands.
97
-
98
- ### Key Entities
99
-
100
- - **Container Service**: A service with an `image`, optional `envs`, `ports`, `healthcheck`, `volumes`, and `mounts`.
101
- - **Healthcheck**: Contains a `cmd` string executed inside the service container. A zero exit code signals readiness.
102
- - **Volume**: A named or path-based Docker volume entry mapping a volume identifier to a container path.
103
- - **Mount**: A bind-mount entry mapping a host file/directory path to a container path.
104
-
105
- ## Success Criteria *(mandatory)*
106
-
107
- ### Measurable Outcomes
108
-
109
- - **SC-001**: A task dependent on a service with a healthcheck does not start until the healthcheck exits 0, verified by log timestamps.
110
- - **SC-002**: A declared port mapping results in a reachable port on the host machine while the service is running.
111
- - **SC-003**: Data written to a volume-backed service directory is present after the service container is stopped and restarted.
112
- - **SC-004**: A bind-mounted file is readable at its declared container path when the service container is inspected.
113
- - **SC-005**: `hammerkit store` produces a restorable artifact and `hammerkit restore` repopulates the volume to a matching state.
114
-
115
- ## Assumptions
116
-
117
- - Container services run within the same Docker network as container tasks; no additional network configuration is needed for inter-container communication.
118
- - The `healthcheck.cmd` is a shell command string executed inside the service container (not on the host).
119
- - Services sharing volumes across multiple hammerkit projects run as a single container instance, not duplicated per project.
120
- - `ports` are not required for container tasks to reach the service — they are solely for host-side access.
@@ -1,97 +0,0 @@
1
- # Feature Specification: Service: Kubernetes
2
-
3
- **Feature Branch**: `001-current-state-specs`
4
-
5
- **Created**: 2026-05-30
6
-
7
- **Status**: Current State (reverse-specified from docs, hammerkit 1.6.0)
8
-
9
- **Input**: Existing behavior documented in `docs/service/kubernetes.md`
10
-
11
- ## User Scenarios & Testing *(mandatory)*
12
-
13
- ### User Story 1 - Forwarding a Kubernetes deployment port to a local task (Priority: P1)
14
-
15
- A developer wants to run a local task against a staging database that lives in a Kubernetes cluster. They declare an environment with the cluster context, declare a Kubernetes service with a `selector` pointing to the deployment and the ports to forward, then run their task with `--env staging`. Hammerkit establishes the port-forward and the task reaches the database on localhost.
16
-
17
- **Why this priority**: This is the primary use case of Kubernetes services — accessing remote cluster resources locally without leaving the hammerkit workflow.
18
-
19
- **Independent Test**: Declare a `prod` environment with a valid kubeconfig context and a service selecting a known deployment. Run `hammerkit <task> --env prod` and confirm the task can connect on the forwarded port.
20
-
21
- **Acceptance Scenarios**:
22
-
23
- 1. **Given** an environment declares a Kubernetes `context` and a service declares a `selector` with `type: deployment` and a `name`, **When** a task needing the service is run with `--env <env>`, **Then** hammerkit forwards the specified ports from the matching deployment to localhost.
24
-
25
- 2. **Given** an environment named `default` exists in the build file, **When** a task is run without `--env`, **Then** hammerkit uses the `default` environment automatically.
26
-
27
- 3. **Given** the same service definition, **When** the task is run with `--env local` versus `--env prod`, **Then** the port-forward targets the cluster configured in the respective environment.
28
-
29
- ### User Story 2 - Overriding the namespace or context per service (Priority: P2)
30
-
31
- A developer has a `prod` environment pointing to a GKE cluster with a default namespace of `databases`, but one service lives in the `orders` namespace. They add `namespace: orders` on that service to override only the namespace while inheriting everything else from the environment.
32
-
33
- **Why this priority**: Per-service overrides allow a single build file to target resources spread across multiple namespaces without duplicating environment definitions.
34
-
35
- **Independent Test**: Declare an environment with `namespace: databases` and a service that overrides `namespace: orders`. Verify the port-forward targets the `orders` namespace.
36
-
37
- **Acceptance Scenarios**:
38
-
39
- 1. **Given** an environment declares `namespace: databases` and a service declares `namespace: orders`, **When** the service is activated, **Then** the port-forward targets the `orders` namespace, not `databases`.
40
-
41
- 2. **Given** a service declares neither `namespace`, `context`, nor `kubeconfig`, **When** it is activated, **Then** it inherits all three from the selected environment.
42
-
43
- ### User Story 3 - Connecting to different cloud providers (Priority: P3)
44
-
45
- A developer's team uses GKE for prod and EKS for staging. They configure two environments with the appropriate context names written by the cloud CLI tools. Services are declared once and are provider-agnostic.
46
-
47
- **Why this priority**: Multi-provider support broadens the tool's applicability but is a configuration concern, not a behavioral distinction.
48
-
49
- **Independent Test**: Declare two environments with provider-specific context names and run the same service-needing task against each; verify the correct cluster is targeted each time.
50
-
51
- **Acceptance Scenarios**:
52
-
53
- 1. **Given** a GKE environment with context `gke_<project>_<location>_<cluster>`, **When** a task is run with that environment, **Then** hammerkit connects to the GKE cluster via the matching kubeconfig entry.
54
-
55
- 2. **Given** an AKS environment with a dedicated `kubeconfig` file, **When** a task is run with that environment, **Then** hammerkit uses the specified kubeconfig file rather than the default `~/.kube/config`.
56
-
57
- ### Edge Cases
58
-
59
- - If no matching deployment, service, or pod exists in the target cluster namespace, the port-forward fails and the task does not start.
60
- - If neither the service nor the environment specifies a `namespace`, hammerkit defaults to the `default` Kubernetes namespace.
61
- - The `kubectl` binary is not required; hammerkit performs port-forwarding directly through the Kubernetes client library.
62
- - A service can override `context` or `kubeconfig` individually while still inheriting the other fields from the environment.
63
-
64
- ## Requirements *(mandatory)*
65
-
66
- ### Functional Requirements
67
-
68
- - **FR-001**: System MUST forward ports from a Kubernetes resource to localhost using the `selector` (type and name) and `ports` declared on the service.
69
- - **FR-002**: System MUST resolve the cluster connection (`context`, `kubeconfig`, `namespace`) from the environment selected via `--env`.
70
- - **FR-003**: System MUST use the environment named `default` when no `--env` flag is provided and such an environment exists.
71
- - **FR-004**: System MUST allow a service to override `namespace`, `context`, and/or `kubeconfig` independently, inheriting unoverridden fields from the environment.
72
- - **FR-005**: System MUST support `selector.type` values of `deployment`, `service`, and `pod`.
73
- - **FR-006**: System MUST establish port-forwarding without requiring the `kubectl` binary on the host.
74
- - **FR-007**: System MUST default to the environment's `kubeconfig`, falling back to `$HOME/.kube/config` when neither the service nor the environment specifies one.
75
- - **FR-008**: System MUST default to the environment's `namespace`, falling back to `default` when neither specifies one.
76
-
77
- ### Key Entities
78
-
79
- - **Kubernetes Service**: A service entry with `selector` (resource `type` and `name`), `ports` (host:container mappings), and optional `namespace`, `context`, `kubeconfig` overrides.
80
- - **Environment**: A named block under `environments` containing a `kubernetes` sub-key with `context`, optional `kubeconfig`, and optional `namespace`.
81
- - **Selector**: Specifies the Kubernetes resource to forward from. Fields: `type` (deployment | service | pod) and `name`.
82
-
83
- ## Success Criteria *(mandatory)*
84
-
85
- ### Measurable Outcomes
86
-
87
- - **SC-001**: A task that needs a Kubernetes service can connect to the forwarded port on localhost while the task runs, verified by a successful network call.
88
- - **SC-002**: Switching `--env` between two environments with different cluster contexts results in the port-forward targeting the correct cluster, confirmed by which database responds.
89
- - **SC-003**: A per-service `namespace` override causes the forward to target the overriding namespace, not the environment's default namespace.
90
- - **SC-004**: Port-forwarding succeeds on a machine that does not have `kubectl` installed.
91
-
92
- ## Assumptions
93
-
94
- - Each port mapping in `ports` follows `hostPort:containerPort` notation; hammerkit binds `hostPort` on localhost and forwards to `containerPort` on the selected Kubernetes resource.
95
- - The `--env` flag is the only mechanism for selecting a Kubernetes environment at runtime; there is no interactive prompt.
96
- - Kubernetes services in hammerkit are for port-forwarding only — they do not schedule workloads on the cluster (see task/kubernetes.md for running tasks on a cluster).
97
- - A Kubernetes service without an explicit `image` field is distinguished from a container service by the presence of a `selector` field.
@@ -1,105 +0,0 @@
1
- # Feature Specification: Service: Overview
2
-
3
- **Feature Branch**: `001-current-state-specs`
4
-
5
- **Created**: 2026-05-30
6
-
7
- **Status**: Current State (reverse-specified from docs, hammerkit 1.6.0)
8
-
9
- **Input**: Existing behavior documented in `docs/service/README.md`
10
-
11
- ## User Scenarios & Testing *(mandatory)*
12
-
13
- ### User Story 1 - Running a dependency service alongside a container task (Priority: P1)
14
-
15
- A developer needs a database available before their application task starts. They declare a `postgres` service in the build file and add it to the task's `needs` list. When they run the task, hammerkit starts the service first (waiting for its healthcheck to pass if one is configured), then starts the task container — both share a container network so the task can reach the service by its name (e.g. `postgres:5432`).
16
-
17
- **Why this priority**: Services are the primary mechanism for wiring runtime dependencies to tasks; this is the core use case documented first.
18
-
19
- **Independent Test**: Create a build file with a postgres service and a container task that `needs` it. Run the task and confirm it connects to postgres using the service name as the hostname.
20
-
21
- **Acceptance Scenarios**:
22
-
23
- 1. **Given** a build file declares a `postgres` container service and a container task with `needs: [postgres]`, **When** the task is executed, **Then** hammerkit starts the postgres container before the task and the task can reach it at hostname `postgres`.
24
-
25
- 2. **Given** the postgres service has a `healthcheck` configured, **When** the task is about to start, **Then** hammerkit waits until the healthcheck exits with code 0 before launching the task container.
26
-
27
- ### User Story 2 - Using a service from a local (non-container) task (Priority: P2)
28
-
29
- A developer runs a local task (no `image`) that needs a running service. Because the local task is not part of the container network, hammerkit injects connection details as environment variables so the process can still reach the service.
30
-
31
- **Why this priority**: Local tasks are common for development workflows and the env-variable injection is the only way they can reach services.
32
-
33
- **Independent Test**: Declare a container service and a local task that `needs` it. Run the local task and verify it receives `HAMMERKIT_<NAME>_HOST`, `HAMMERKIT_<NAME>_PORT`, and `HAMMERKIT_<NAME>_PORT_<containerPort>` environment variables.
34
-
35
- **Acceptance Scenarios**:
36
-
37
- 1. **Given** a local task (no `image`) declares `needs: [postgres]` where postgres exposes port `5432`, **When** the task is executed, **Then** the process environment contains `HAMMERKIT_POSTGRES_HOST`, `HAMMERKIT_POSTGRES_PORT`, and `HAMMERKIT_POSTGRES_PORT_5432`.
38
-
39
- 2. **Given** a service exposes multiple ports, **When** a local task needing it runs, **Then** a `HAMMERKIT_<NAME>_PORT_<containerPort>` variable is injected for each exposed container port.
40
-
41
- ### User Story 3 - Manually controlling service lifetime with `up` / `down` (Priority: P2)
42
-
43
- A developer wants to keep a shared database running across several task runs without restarting it each time. They start the service with `hammerkit up` and stop it later with `hammerkit down`.
44
-
45
- **Why this priority**: Manual lifecycle control is important for developer-experience workflows where repeated task runs reuse a warm service.
46
-
47
- **Independent Test**: Run `hammerkit up`, verify the service container is running, then execute a task that needs it (it should skip starting the service), then run `hammerkit down` and verify the container stops.
48
-
49
- **Acceptance Scenarios**:
50
-
51
- 1. **Given** a service is defined, **When** `hammerkit up` is executed, **Then** the service container starts and remains running.
52
-
53
- 2. **Given** a running service started via `hammerkit up`, **When** `hammerkit down` is executed, **Then** the service container stops.
54
-
55
- ### User Story 4 - Service-to-service and service-to-task dependencies (Priority: P3)
56
-
57
- A developer has a service that depends on another service (via `needs`) and/or requires a task to run first (via `deps`). Hammerkit resolves the dependency chain before starting the dependent service.
58
-
59
- **Why this priority**: Dependency chaining for services mirrors task dependencies and is a secondary but documented capability.
60
-
61
- **Independent Test**: Declare two services where one lists the other in `needs`, and the first also lists a task in `deps`. Verify hammerkit runs the task first, then starts the dependency service, then the dependent service.
62
-
63
- **Acceptance Scenarios**:
64
-
65
- 1. **Given** service `api` declares `deps: [install]` and `needs: [postgres]`, **When** a task needing `api` is run, **Then** hammerkit runs `install` first, then starts `postgres`, then starts `api`.
66
-
67
- ### Edge Cases
68
-
69
- - A local task that `needs` a Kubernetes-forwarded service receives the same `HAMMERKIT_<NAME>_*` environment variables, but hammerkit does not provide DNS resolution for it.
70
- - If no healthcheck is defined, hammerkit may start a dependent task before the service is fully ready.
71
- - A service name is uppercased when constructing environment variable names (e.g. service `my-db` becomes `HAMMERKIT_MY_DB_HOST`).
72
-
73
- ## Requirements *(mandatory)*
74
-
75
- ### Functional Requirements
76
-
77
- - **FR-001**: System MUST start all services listed in a task's `needs` before executing that task.
78
- - **FR-002**: System MUST expose services to container tasks via the container network using the service name as the DNS hostname.
79
- - **FR-003**: System MUST inject `HAMMERKIT_<NAME>_HOST`, `HAMMERKIT_<NAME>_PORT`, and `HAMMERKIT_<NAME>_PORT_<containerPort>` environment variables into local tasks for each needed service, where `<NAME>` is the uppercased service name.
80
- - **FR-004**: System MUST wait for a service's healthcheck command to exit with code 0 before launching any task that needs it, when a healthcheck is configured.
81
- - **FR-005**: System MUST support `hammerkit up` to start services independently of task execution.
82
- - **FR-006**: System MUST support `hammerkit down` to stop running services.
83
- - **FR-007**: System MUST resolve service `deps` (tasks) and `needs` (other services) before starting a service, applying the same dependency-ordering rules as for tasks.
84
- - **FR-008**: System MUST support both container services (image-based) and Kubernetes port-forwarding services within the same build file.
85
-
86
- ### Key Entities
87
-
88
- - **Service**: A continuous, long-running process declared under the `services` key in a build file. Has a `name`, optional `needs` (other services), optional `deps` (tasks), and is either container-based or Kubernetes-based.
89
- - **Healthcheck**: An optional check attached to a container service. Contains a `cmd` executed inside the service container; exit code 0 means ready.
90
-
91
- ## Success Criteria *(mandatory)*
92
-
93
- ### Measurable Outcomes
94
-
95
- - **SC-001**: A container task that `needs` a service starts only after that service is running (and its healthcheck passes, if present), verified across at least one postgres and one redis-style service.
96
- - **SC-002**: A local task that `needs` a service receives the correct host/port environment variables, confirmed by the task process reading them and connecting successfully.
97
- - **SC-003**: `hammerkit up` / `hammerkit down` start and stop services within a reasonable time, with no task execution triggered.
98
- - **SC-004**: Service dependency chains (service needing another service, service needing a task) resolve in the correct order with zero manual intervention.
99
-
100
- ## Assumptions
101
-
102
- - The service name used as the DNS hostname is exactly as declared in the build file (case-sensitive for the network alias; uppercased only for the environment variable injection).
103
- - `HAMMERKIT_<NAME>_PORT` refers to the first/primary published port when a service exposes multiple ports.
104
- - Services are automatically stopped when they are no longer needed by any remaining task in the current run, unless started with `hammerkit up`.
105
- - The `--env` flag for selecting Kubernetes environments applies equally to Kubernetes-type services; container services are not environment-scoped.
@@ -1,107 +0,0 @@
1
- # Feature Specification: Task: Caching
2
-
3
- **Feature Branch**: `001-current-state-specs`
4
-
5
- **Created**: 2026-05-30
6
-
7
- **Status**: Current State (reverse-specified from docs, hammerkit 1.6.0)
8
-
9
- **Input**: Existing behavior documented in `docs/task/caching.md`
10
-
11
- ## User Scenarios & Testing *(mandatory)*
12
-
13
- ### User Story 1 - Skip unchanged tasks automatically (Priority: P1)
14
-
15
- A developer runs their build repeatedly. Tasks whose source files have not changed since the last successful run are detected as up-to-date and skipped, saving time.
16
-
17
- **Why this priority**: Caching is the primary mechanism that makes hammerkit practical for incremental development workflows.
18
-
19
- **Independent Test**: Run a task with `src` defined twice with no file changes; confirm the second run reports the task as skipped and no commands execute.
20
-
21
- **Acceptance Scenarios**:
22
-
23
- 1. **Given** a task with `src` files defined, **When** those files have not changed since the last successful run, **Then** the task is skipped.
24
- 2. **Given** a task with `src` files defined, **When** at least one source file has changed, **Then** the task re-executes.
25
- 3. **Given** a task whose dependency has no `src` defined (always runs), **When** the downstream task has `src` defined, **Then** the downstream task is also executed (cannot be skipped when an upstream task was not skipped).
26
-
27
- ### User Story 2 - Choose a cache method per task (Priority: P2)
28
-
29
- A developer wants finer control over how staleness is detected. They set `cache: modify-date` on a task to use file modification timestamps instead of content checksums, or `cache: none` to disable caching for a specific task.
30
-
31
- **Why this priority**: Different tasks have different performance/correctness trade-offs for cache detection.
32
-
33
- **Independent Test**: Set `cache: none` on a task with `src` defined and verify it always re-executes regardless of source changes.
34
-
35
- **Acceptance Scenarios**:
36
-
37
- 1. **Given** `cache: checksum` on a task, **When** source file content is identical but the modification timestamp has changed, **Then** the task is skipped.
38
- 2. **Given** `cache: modify-date` on a task, **When** a source file's modification timestamp has changed, **Then** the task re-executes even if content is the same.
39
- 3. **Given** `cache: none` on a task, **When** the task is invoked, **Then** it always re-executes regardless of source state.
40
-
41
- ### User Story 3 - Override the default cache method globally at runtime (Priority: P2)
42
-
43
- A CI engineer wants to run all tasks with checksum-based caching regardless of the per-task defaults. They pass `--cache checksum` on the CLI to override the default for all tasks that did not set an explicit cache method.
44
-
45
- **Why this priority**: CI environments benefit from a reliable, content-based cache independent of local developer machine defaults.
46
-
47
- **Independent Test**: Run with `--cache none` and verify all tasks without an explicit `cache` field re-execute.
48
-
49
- **Acceptance Scenarios**:
50
-
51
- 1. **Given** tasks with no explicit `cache` field, **When** `hammerkit build --cache modify-date` is run, **Then** all those tasks use modify-date caching.
52
- 2. **Given** a task with an explicit `cache: checksum`, **When** `hammerkit build --cache modify-date` is run, **Then** that task continues to use checksum caching (explicit value wins).
53
-
54
- ### User Story 4 - Use a remote cache backend to share results across machines (Priority: P3)
55
-
56
- A team wants CI and developer machines to share build results. They declare a named cache in the build file with an S3 backend and reference it from their tasks. Hammerkit pulls a cached result before running the task and pushes a new result after a successful run.
57
-
58
- **Why this priority**: Remote caching eliminates redundant work in distributed teams and CI/CD pipelines.
59
-
60
- **Independent Test**: Configure an S3-backed cache, run the task on one machine, then run on a second machine that has not built locally and confirm the task is skipped via the remote cache.
61
-
62
- **Acceptance Scenarios**:
63
-
64
- 1. **Given** a task with a named cache pointing to a remote backend, **When** a matching cache entry exists remotely, **Then** hammerkit pulls the result and skips task execution.
65
- 2. **Given** a task with a named cache pointing to a remote backend, **When** no matching entry exists remotely and the task runs successfully, **Then** hammerkit pushes the result to the remote backend.
66
-
67
- ### Edge Cases
68
-
69
- - Source glob patterns using `**` do not traverse symlinked directories.
70
- - Using a large directory such as `node_modules` as a source is explicitly discouraged because recursive traversal is expensive.
71
- - A task can only be skipped if all of its dependencies before it were also skipped; a single non-skipped ancestor forces the task to re-execute.
72
- - The default cache method is `modify-date` locally and `checksum` in CI (environment-dependent).
73
-
74
- ## Requirements *(mandatory)*
75
-
76
- ### Functional Requirements
77
-
78
- - **FR-001**: The system MUST accept a `src` list of files, directories, and glob patterns on a task and use them to determine skip eligibility.
79
- - **FR-002**: The system MUST skip a task when all its sources are unchanged and all its dependency tasks were also skipped.
80
- - **FR-003**: The system MUST support three cache methods: `checksum` (content hash), `modify-date` (file mtime), and `none` (always execute).
81
- - **FR-004**: The system MUST support a `cache` field on a task accepting either a shorthand method string or an object with `name` and optional `method` fields.
82
- - **FR-005**: The system MUST support a `--cache <method>` CLI flag that sets the default method for all tasks that did not declare an explicit `cache` field.
83
- - **FR-006**: An explicit `cache` value on a task MUST take precedence over the `--cache` flag.
84
- - **FR-007**: The system MUST support named caches with remote backends (at minimum S3 and S3-compatible stores).
85
- - **FR-008**: When a remote cache backend is configured and a cache hit exists remotely, the system MUST pull and restore the result without executing the task.
86
- - **FR-009**: After a successful task run with a remote backend, the system MUST push the result to the remote backend.
87
- - **FR-010**: Glob patterns in `src` MUST support the node-glob pattern set including `*`, `?`, `[...]`, `!(…)`, `?(…)`, `+(…)`, `*(…)`, `@(…)`, and `**`.
88
-
89
- ### Key Entities
90
-
91
- - **Cache entry**: A record of a successful task run keyed by source fingerprint, associated with a cache method and optionally a named backend.
92
- - **Named cache**: A top-level `caches` declaration with an optional remote backend (`local` or `s3`).
93
-
94
- ## Success Criteria *(mandatory)*
95
-
96
- ### Measurable Outcomes
97
-
98
- - **SC-001**: A task with unchanged sources is skipped on the second consecutive run with zero commands executed.
99
- - **SC-002**: Changing a single source file causes exactly the affected task (and its dependents) to re-execute on the next run.
100
- - **SC-003**: `--cache none` causes all tasks without an explicit `cache` field to execute unconditionally.
101
- - **SC-004**: With an S3 remote backend, a task result produced on machine A is reused on machine B without re-executing the task commands.
102
-
103
- ## Assumptions
104
-
105
- - The environment-dependent default (modify-date locally, checksum in CI) is determined by whether hammerkit detects a CI environment variable (e.g., `CI=true`).
106
- - Redeclaring the built-in `default` cache to use a remote backend affects all tasks that reference the default cache, as described in the build-file caches documentation.
107
- - The `s3` backend covers S3-compatible stores (MinIO, Cloudflare R2, Google Cloud Storage); no additional backend types are documented for 1.6.0.
@@ -1,118 +0,0 @@
1
- # Feature Specification: Task: Container
2
-
3
- **Feature Branch**: `001-current-state-specs`
4
-
5
- **Created**: 2026-05-30
6
-
7
- **Status**: Current State (reverse-specified from docs, hammerkit 1.6.0)
8
-
9
- **Input**: Existing behavior documented in `docs/task/container.md`
10
-
11
- ## User Scenarios & Testing *(mandatory)*
12
-
13
- ### User Story 1 - Run a task inside a container image (Priority: P1)
14
-
15
- A developer wants to build their project without installing Node.js locally. They add `image: node:14.16.0` to the task definition and hammerkit executes the task's commands inside a container using that image.
16
-
17
- **Why this priority**: Container execution is the core cross-platform isolation feature of hammerkit.
18
-
19
- **Independent Test**: Define a task with `image` set and a command that prints the Node version; verify the output matches the version in the image, not any locally installed Node.
20
-
21
- **Acceptance Scenarios**:
22
-
23
- 1. **Given** a task with an `image` property, **When** the task is run, **Then** all commands execute inside a container created from that image.
24
- 2. **Given** a task with `image` but no `src` or `mounts`, **When** the task runs, **Then** the container has no access to host files.
25
-
26
- ### User Story 2 - Make source files available inside the container (Priority: P1)
27
-
28
- A developer's container task needs to read project files. They declare those files and folders in `src`; hammerkit mounts them into the container automatically so commands can access them.
29
-
30
- **Why this priority**: Without file access the container task cannot read inputs and is not useful.
31
-
32
- **Independent Test**: Add `src` files to a container task and verify a command inside the container can read those files.
33
-
34
- **Acceptance Scenarios**:
35
-
36
- 1. **Given** a container task with `src` files listed, **When** the task executes, **Then** those files are accessible at their expected paths inside the container.
37
- 2. **Given** a container task whose dependency task declares `src` and `generates`, **When** the dependent task executes, **Then** those dependency paths are also mounted automatically without being listed again.
38
-
39
- ### User Story 3 - Persist output files across runs using generates (Priority: P2)
40
-
41
- A developer's container task produces `node_modules`. They declare `node_modules` under `generates` so hammerkit persists it in a container volume between runs, enabling caching of the installed packages.
42
-
43
- **Why this priority**: Without `generates`, outputs are lost when the container exits; re-runs would redo all work.
44
-
45
- **Independent Test**: Run an install task in a container with `node_modules` in `generates`; stop hammerkit and run again — the second run should be cached and `node_modules` should still be present.
46
-
47
- **Acceptance Scenarios**:
48
-
49
- 1. **Given** a container task with a path in `generates`, **When** the task completes, **Then** the output is preserved in a container volume for the next run.
50
- 2. **Given** a container task with `generates` and `export: true`, **When** the task completes, **Then** the generated files are also copied to the host workspace.
51
-
52
- ### User Story 4 - Mount additional files and directories (Priority: P2)
53
-
54
- A developer needs to give the container access to files that are neither source inputs nor generated outputs (e.g., a credential file or a shared cache directory). They use `mounts` with relative paths, absolute paths, or `$PWD`-relative expressions.
55
-
56
- **Why this priority**: Not all container inputs fit the source/generate model; mounts fill that gap.
57
-
58
- **Independent Test**: Add a mount to a container task and verify the mounted path is accessible inside the container.
59
-
60
- **Acceptance Scenarios**:
61
-
62
- 1. **Given** a container task with a relative `mounts` entry, **When** the task runs, **Then** the relative path is mounted into the container.
63
- 2. **Given** a container task with an absolute `mounts` entry, **When** the task runs, **Then** the absolute path is mounted into the container.
64
- 3. **Given** a container task with a `$PWD`-style `mounts` entry (e.g., `$PWD/.npm:/.npm`), **When** the task runs, **Then** the path is expanded and mounted at the specified target path.
65
-
66
- ### User Story 5 - Override the execution shell (Priority: P3)
67
-
68
- A developer's commands require bash-specific syntax. They set `shell: bash` on the task and commands are invoked with bash instead of the default `sh`.
69
-
70
- **Why this priority**: Some commands or scripts require a specific shell; the default `sh` is too restrictive for some use cases.
71
-
72
- **Independent Test**: Set `shell: bash` and use a bash-only feature (`$RANDOM`); verify the command succeeds inside the container.
73
-
74
- **Acceptance Scenarios**:
75
-
76
- 1. **Given** a container task with `shell: bash`, **When** a command uses bash-specific syntax, **Then** it executes successfully.
77
- 2. **Given** a container task without a `shell` property, **When** commands run, **Then** they are executed with `sh`.
78
-
79
- ### Edge Cases
80
-
81
- - A container task with no `src`, `generates`, or `mounts` can still run but has no access to host files.
82
- - File permissions: commands in a container run with the same UID/GID as the local user; hammerkit sets the owner of the working directory and all mounts to that UID/GID before each task run.
83
- - Sources and generates of all dependency tasks are automatically mounted; this reduces the need for explicit `mounts` entries.
84
-
85
- ## Requirements *(mandatory)*
86
-
87
- ### Functional Requirements
88
-
89
- - **FR-001**: The system MUST accept an `image` property on a task and execute all `cmds` inside a container created from that image.
90
- - **FR-002**: The system MUST mount all `src` files and directories into the container at task startup so they are accessible inside the container.
91
- - **FR-003**: The system MUST persist paths listed in `generates` in a container volume across runs.
92
- - **FR-004**: The system MUST automatically mount the `src` and `generates` paths of all dependency tasks into a dependent container task.
93
- - **FR-005**: The system MUST accept a `mounts` list supporting relative paths, absolute paths, and `$PWD`-prefixed expressions with optional target path suffixes.
94
- - **FR-006**: The system MUST default to `sh` as the command execution shell for container tasks.
95
- - **FR-007**: The system MUST accept a `shell` property on a task to override the execution shell.
96
- - **FR-008**: The system MUST run container commands with the same UID/GID as the host user and set ownership of the working directory and mounts accordingly before each run.
97
-
98
- ### Key Entities
99
-
100
- - **Container task**: A task with an `image` property. Additional attributes: `shell` (optional), `mounts` (optional list), `src` (optional), `generates` (optional).
101
- - **Mount entry**: A string specifying a host path (relative, absolute, or `$PWD`-prefixed) optionally followed by `:` and a container target path.
102
-
103
- ## Success Criteria *(mandatory)*
104
-
105
- ### Measurable Outcomes
106
-
107
- - **SC-001**: A container task command that prints the runtime version outputs the version from the declared image, not the host.
108
- - **SC-002**: Files listed in `src` are readable inside the container during task execution.
109
- - **SC-003**: A path listed in `generates` persists between separate hammerkit invocations without requiring re-installation.
110
- - **SC-004**: A container task with `export: true` on a generate path results in that directory appearing on the host filesystem after the run.
111
- - **SC-005**: Files created inside the container are owned by the host user's UID/GID on the host filesystem.
112
-
113
- ## Assumptions
114
-
115
- - Container execution uses the local Docker daemon unless an environment is selected (see kubernetes.md).
116
- - The working directory inside the container mirrors the host project directory structure.
117
- - `$PWD` in mount expressions is expanded to the host working directory at task execution time.
118
- - The `export` and `resetOnChange` flags on generate entries are described on the Task Overview page and apply equally to container tasks.
@@ -1,70 +0,0 @@
1
- # Feature Specification: Task: Dependencies
2
-
3
- **Feature Branch**: `001-current-state-specs`
4
-
5
- **Created**: 2026-05-30
6
-
7
- **Status**: Current State (reverse-specified from docs, hammerkit 1.6.0)
8
-
9
- **Input**: Existing behavior documented in `docs/task/dependencies.md`
10
-
11
- ## User Scenarios & Testing *(mandatory)*
12
-
13
- ### User Story 1 - Chain tasks with dependencies (Priority: P1)
14
-
15
- A developer has three tasks — `install`, `build`, and `publish` — where each step must complete before the next starts. They declare `deps` on each downstream task. When running `hammerkit publish`, hammerkit resolves the full chain and executes each task in dependency order.
16
-
17
- **Why this priority**: Dependency ordering is the foundational orchestration primitive; without it users must run tasks manually in sequence.
18
-
19
- **Independent Test**: Define a three-task chain and run the last one; observe that all three execute in correct order.
20
-
21
- **Acceptance Scenarios**:
22
-
23
- 1. **Given** tasks A, B, C where B depends on A and C depends on B, **When** the user runs `hammerkit C`, **Then** A executes first, then B, then C.
24
- 2. **Given** a dependency chain where task A fails, **When** the user runs `hammerkit C`, **Then** B and C are not executed and the run aborts with a failure.
25
- 3. **Given** a dependency is already cached (sources unchanged), **When** the user runs a downstream task, **Then** the cached dependency is skipped and the downstream task still runs.
26
-
27
- ### User Story 2 - Deep dependency chains (Priority: P2)
28
-
29
- A developer in a monorepo builds deeply nested dependency graphs. Hammerkit resolves chains of arbitrary depth as long as there are no cycles.
30
-
31
- **Why this priority**: Monorepos commonly have multi-level dependency trees.
32
-
33
- **Independent Test**: Create a 5-level dependency chain and confirm all levels execute in the correct order.
34
-
35
- **Acceptance Scenarios**:
36
-
37
- 1. **Given** a dependency graph N levels deep with no cycles, **When** the leaf task is invoked, **Then** all ancestors execute in topological order.
38
- 2. **Given** a build file where task A depends on task B and task B depends on task A, **When** any task in the cycle is invoked, **Then** hammerkit reports an error and does not execute any task.
39
-
40
- ### Edge Cases
41
-
42
- - A task with an empty `deps: []` list is treated the same as a task with no `deps` field.
43
- - If any dependency in the chain fails, all pending downstream tasks are aborted.
44
- - Circular dependency graphs (loops) are explicitly prohibited and must be detected at parse/plan time.
45
-
46
- ## Requirements *(mandatory)*
47
-
48
- ### Functional Requirements
49
-
50
- - **FR-001**: The system MUST accept a `deps` list of task names on any task.
51
- - **FR-002**: The system MUST execute all tasks listed in `deps` before executing the dependent task.
52
- - **FR-003**: The system MUST abort execution of all pending tasks when any dependency task fails.
53
- - **FR-004**: The system MUST support arbitrarily deep dependency chains.
54
- - **FR-005**: The system MUST detect and reject cyclic dependency graphs (loops).
55
- - **FR-006**: A dependency task that is cached (all sources unchanged) MUST be skipped without re-executing, and downstream tasks that still need to run MUST continue normally.
56
-
57
- ## Success Criteria *(mandatory)*
58
-
59
- ### Measurable Outcomes
60
-
61
- - **SC-001**: Running a task with a two-level dependency chain results in the dependency executing first, followed by the requested task.
62
- - **SC-002**: When a dependency fails, no tasks that depend on it (directly or transitively) are executed.
63
- - **SC-003**: Cyclic dependencies cause hammerkit to exit with an error before executing any command.
64
- - **SC-004**: A cached dependency does not re-execute; its downstream task still executes if needed.
65
-
66
- ## Assumptions
67
-
68
- - `deps` values must reference tasks defined within the same build file or an included build file; cross-build-file references follow the include/namespace conventions described elsewhere.
69
- - Dependency resolution is eager: all dependencies of a task are resolved before that task starts, not lazily during execution.
70
- - Parallel execution of independent siblings within a dependency graph is outside the scope of this page and is not specified here.