container-superposition 0.1.8 → 0.1.10

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 (184) hide show
  1. package/README.md +3 -0
  2. package/dist/tool/cli/args.d.ts.map +1 -1
  3. package/dist/tool/cli/args.js +1 -1
  4. package/dist/tool/cli/args.js.map +1 -1
  5. package/dist/tool/commands/adopt.d.ts.map +1 -1
  6. package/dist/tool/commands/adopt.js +15 -21
  7. package/dist/tool/commands/adopt.js.map +1 -1
  8. package/dist/tool/commands/doctor.d.ts +1 -0
  9. package/dist/tool/commands/doctor.d.ts.map +1 -1
  10. package/dist/tool/commands/doctor.js +1370 -73
  11. package/dist/tool/commands/doctor.js.map +1 -1
  12. package/dist/tool/questionnaire/composer.d.ts +3 -1
  13. package/dist/tool/questionnaire/composer.d.ts.map +1 -1
  14. package/dist/tool/questionnaire/composer.js +273 -20
  15. package/dist/tool/questionnaire/composer.js.map +1 -1
  16. package/dist/tool/questionnaire/presets.d.ts.map +1 -1
  17. package/dist/tool/questionnaire/presets.js +1 -0
  18. package/dist/tool/questionnaire/presets.js.map +1 -1
  19. package/dist/tool/questionnaire/questionnaire.d.ts.map +1 -1
  20. package/dist/tool/questionnaire/questionnaire.js +3 -1
  21. package/dist/tool/questionnaire/questionnaire.js.map +1 -1
  22. package/dist/tool/schema/project-config.d.ts.map +1 -1
  23. package/dist/tool/schema/project-config.js +174 -1
  24. package/dist/tool/schema/project-config.js.map +1 -1
  25. package/dist/tool/schema/types.d.ts +53 -2
  26. package/dist/tool/schema/types.d.ts.map +1 -1
  27. package/docs/README.md +1 -0
  28. package/docs/overlays.md +188 -147
  29. package/docs/specs/001-verbose-plan-graph/spec.md +5 -12
  30. package/docs/specs/002-superposition-config-file/spec.md +5 -12
  31. package/docs/specs/003-mkdocs2-overlay/spec.md +2 -9
  32. package/docs/specs/004-doctor-fix/spec.md +1 -8
  33. package/docs/specs/005-cuda-overlay/spec.md +2 -9
  34. package/docs/specs/006-rocm-overlay/spec.md +3 -10
  35. package/docs/specs/007-target-aware-generation/spec.md +4 -11
  36. package/docs/specs/008-project-file-canonical/spec.md +7 -8
  37. package/docs/specs/009-project-env/spec.md +3 -10
  38. package/docs/specs/010-compose-env-materialization/spec.md +3 -10
  39. package/docs/specs/011-overlay-parameters/spec.md +2 -9
  40. package/docs/specs/012-ollama-cli-overlay/spec.md +47 -0
  41. package/docs/specs/013-doctor-dependency-check/spec.md +250 -0
  42. package/docs/specs/014-doctor-compose-port-cross-validation/spec.md +276 -0
  43. package/docs/specs/015-doctor-env-example-drift/spec.md +248 -0
  44. package/docs/specs/016-doctor-reproducibility-check/spec.md +276 -0
  45. package/docs/specs/017-doctor-dry-run/spec.md +276 -0
  46. package/docs/specs/{007-init-project-file → 018-init-project-file}/spec.md +2 -9
  47. package/docs/specs/019-project-mounts/spec.md +176 -0
  48. package/docs/specs/taxonomy.md +186 -0
  49. package/docs/superposition-yml.md +467 -0
  50. package/overlays/.presets/full-observability.yml +113 -0
  51. package/overlays/.presets/k8s-dev.yml +174 -0
  52. package/overlays/.presets/local-llm.yml +105 -0
  53. package/overlays/.presets/vector-ai.yml +150 -0
  54. package/overlays/.shared/vscode/js-ts-settings.json +19 -0
  55. package/overlays/.shared/vscode/markdown-extensions.json +8 -0
  56. package/overlays/alertmanager/devcontainer.patch.json +0 -1
  57. package/overlays/alertmanager/docker-compose.yml +8 -0
  58. package/overlays/alertmanager/overlay.yml +1 -0
  59. package/overlays/amp/devcontainer.patch.json +4 -1
  60. package/overlays/ansible/README.md +163 -0
  61. package/overlays/ansible/devcontainer.patch.json +14 -0
  62. package/overlays/ansible/overlay.yml +18 -0
  63. package/overlays/argocd/README.md +158 -0
  64. package/overlays/argocd/devcontainer.patch.json +9 -0
  65. package/overlays/argocd/overlay.yml +17 -0
  66. package/overlays/argocd/setup.sh +29 -0
  67. package/overlays/argocd/verify.sh +14 -0
  68. package/overlays/bun/devcontainer.patch.json +1 -10
  69. package/overlays/bun/overlay.yml +8 -1
  70. package/overlays/claude-code/devcontainer.patch.json +6 -1
  71. package/overlays/codex/devcontainer.patch.json +5 -0
  72. package/overlays/comfyui/docker-compose.yml +1 -0
  73. package/overlays/comfyui/overlay.yml +4 -0
  74. package/overlays/commitlint/devcontainer.patch.json +1 -6
  75. package/overlays/docker-sock/overlay.yml +1 -0
  76. package/overlays/dotnet/overlay.yml +4 -1
  77. package/overlays/fuseki/.env.example +5 -0
  78. package/overlays/fuseki/README.md +173 -0
  79. package/overlays/fuseki/devcontainer.patch.json +18 -0
  80. package/overlays/fuseki/docker-compose.yml +29 -0
  81. package/overlays/fuseki/overlay.yml +42 -0
  82. package/overlays/fuseki/verify.sh +58 -0
  83. package/overlays/gemini-cli/devcontainer.patch.json +4 -1
  84. package/overlays/go/overlay.yml +6 -1
  85. package/overlays/grafana/devcontainer.patch.json +0 -1
  86. package/overlays/grafana/docker-compose.yml +8 -2
  87. package/overlays/grafana/overlay.yml +6 -1
  88. package/overlays/jaeger/.env.example +11 -0
  89. package/overlays/jaeger/README.md +33 -4
  90. package/overlays/jaeger/devcontainer.patch.json +9 -1
  91. package/overlays/jaeger/docker-compose.yml +17 -0
  92. package/overlays/jaeger/overlay.yml +1 -12
  93. package/overlays/java/overlay.yml +6 -1
  94. package/overlays/jupyter/docker-compose.yml +1 -0
  95. package/overlays/jupyter/overlay.yml +1 -0
  96. package/overlays/keycloak/devcontainer.patch.json +0 -1
  97. package/overlays/keycloak/docker-compose.yml +1 -0
  98. package/overlays/keycloak/overlay.yml +15 -0
  99. package/overlays/localstack/docker-compose.yml +1 -0
  100. package/overlays/localstack/overlay.yml +19 -1
  101. package/overlays/loki/devcontainer.patch.json +0 -1
  102. package/overlays/loki/docker-compose.yml +8 -0
  103. package/overlays/loki/overlay.yml +1 -0
  104. package/overlays/mailpit/docker-compose.yml +1 -0
  105. package/overlays/mailpit/overlay.yml +1 -0
  106. package/overlays/minio/devcontainer.patch.json +1 -1
  107. package/overlays/minio/docker-compose.yml +1 -0
  108. package/overlays/minio/overlay.yml +23 -2
  109. package/overlays/mkdocs/devcontainer.patch.json +1 -5
  110. package/overlays/mkdocs/overlay.yml +3 -1
  111. package/overlays/mkdocs2/devcontainer.patch.json +1 -5
  112. package/overlays/mkdocs2/overlay.yml +2 -0
  113. package/overlays/mongodb/docker-compose.yml +2 -0
  114. package/overlays/mongodb/overlay.yml +26 -2
  115. package/overlays/mysql/docker-compose.yml +2 -0
  116. package/overlays/mysql/overlay.yml +36 -2
  117. package/overlays/nats/docker-compose.yml +1 -0
  118. package/overlays/nats/overlay.yml +18 -2
  119. package/overlays/nodejs/devcontainer.patch.json +1 -12
  120. package/overlays/nodejs/overlay.yml +8 -1
  121. package/overlays/ollama/README.md +4 -3
  122. package/overlays/ollama/docker-compose.yml +1 -0
  123. package/overlays/ollama/overlay.yml +6 -1
  124. package/overlays/ollama/verify.sh +5 -28
  125. package/overlays/ollama-cli/README.md +90 -0
  126. package/overlays/ollama-cli/devcontainer.patch.json +3 -0
  127. package/overlays/ollama-cli/overlay.yml +19 -0
  128. package/overlays/{ollama → ollama-cli}/setup.sh +7 -10
  129. package/overlays/ollama-cli/verify.sh +49 -0
  130. package/overlays/open-webui/docker-compose.yml +1 -0
  131. package/overlays/open-webui/overlay.yml +8 -1
  132. package/overlays/opencode/devcontainer.patch.json +4 -1
  133. package/overlays/otel-collector/README.md +4 -0
  134. package/overlays/otel-collector/devcontainer.patch.json +4 -1
  135. package/overlays/otel-collector/docker-compose.yml +8 -4
  136. package/overlays/otel-collector/overlay.yml +1 -0
  137. package/overlays/otel-demo-nodejs/devcontainer.patch.json +0 -1
  138. package/overlays/otel-demo-nodejs/docker-compose.yml +1 -0
  139. package/overlays/otel-demo-nodejs/overlay.yml +9 -1
  140. package/overlays/otel-demo-python/devcontainer.patch.json +0 -1
  141. package/overlays/otel-demo-python/docker-compose.yml +1 -0
  142. package/overlays/otel-demo-python/overlay.yml +6 -1
  143. package/overlays/pandoc/README.md +10 -0
  144. package/overlays/pandoc/devcontainer.patch.json +0 -5
  145. package/overlays/pandoc/overlay.yml +2 -0
  146. package/overlays/pandoc/setup.sh +10 -0
  147. package/overlays/pgvector/devcontainer.patch.json +11 -5
  148. package/overlays/pgvector/docker-compose.yml +1 -0
  149. package/overlays/pgvector/overlay.yml +3 -0
  150. package/overlays/playwright/devcontainer.patch.json +0 -5
  151. package/overlays/playwright/overlay.yml +2 -1
  152. package/overlays/postgres/docker-compose.yml +1 -0
  153. package/overlays/postgres/overlay.yml +4 -1
  154. package/overlays/pre-commit/devcontainer.patch.json +1 -7
  155. package/overlays/prometheus/devcontainer.patch.json +0 -1
  156. package/overlays/prometheus/docker-compose.yml +8 -0
  157. package/overlays/prometheus/overlay.yml +1 -0
  158. package/overlays/promtail/devcontainer.patch.json +1 -2
  159. package/overlays/promtail/docker-compose.yml +8 -0
  160. package/overlays/promtail/overlay.yml +1 -0
  161. package/overlays/qdrant/docker-compose.yml +1 -0
  162. package/overlays/qdrant/overlay.yml +5 -1
  163. package/overlays/rabbitmq/docker-compose.yml +1 -0
  164. package/overlays/rabbitmq/overlay.yml +25 -2
  165. package/overlays/redis/docker-compose.yml +7 -0
  166. package/overlays/redis/overlay.yml +15 -1
  167. package/overlays/redpanda/docker-compose.yml +1 -0
  168. package/overlays/redpanda/overlay.yml +15 -3
  169. package/overlays/rocm/overlay.yml +2 -1
  170. package/overlays/rust/overlay.yml +3 -1
  171. package/overlays/sqlserver/docker-compose.yml +1 -0
  172. package/overlays/sqlserver/overlay.yml +17 -0
  173. package/overlays/task/README.md +47 -0
  174. package/overlays/task/devcontainer.patch.json +9 -0
  175. package/overlays/task/overlay.yml +16 -0
  176. package/overlays/task/setup.sh +29 -0
  177. package/overlays/task/verify.sh +14 -0
  178. package/overlays/tempo/devcontainer.patch.json +0 -1
  179. package/overlays/tempo/docker-compose.yml +8 -0
  180. package/overlays/tempo/overlay.yml +1 -0
  181. package/overlays/windsurf-cli/devcontainer.patch.json +4 -1
  182. package/package.json +1 -1
  183. package/tool/schema/config.schema.json +74 -1
  184. package/overlays/.shared/otel/otel-base-config.yaml +0 -30
@@ -0,0 +1,276 @@
1
+ # Feature Specification: Doctor Reproducibility Check
2
+
3
+ **Spec ID**: `016-doctor-reproducibility-check`
4
+ **Taxonomy**: `CLI-UX`
5
+ **Created**: 2026-04-24
6
+ **Author**: PM Agent
7
+ **Status**: Approved
8
+ **Input**: Feature assessment — `cs doctor` cannot tell whether the generated `.devcontainer/` output is up-to-date with the project file; a developer may have edited generated files by hand or added overlays without re-running `cs regen`.
9
+
10
+ ## Problem Statement
11
+
12
+ After `cs regen` runs, the generated `.devcontainer/` directory is derived deterministically from
13
+ the project file and the overlay registry. If a developer then manually edits a generated file,
14
+ adds a new overlay without re-running `cs regen`, or changes a parameter value, the generated
15
+ output becomes inconsistent with the project file — but nothing in the current toolchain detects
16
+ this. Doctor should detect "regen needed" situations and prompt the developer to run `cs regen`
17
+ (or do so automatically with `--fix`).
18
+
19
+ ## Goals
20
+
21
+ - Detect when the generated output no longer matches what `cs regen` would produce from the
22
+ current project file and overlay registry.
23
+ - Allow `doctor --fix` to resolve the inconsistency by running a fresh `cs regen`.
24
+ - Produce a clear, actionable message pointing to which file(s) differ.
25
+
26
+ ## Non-Goals
27
+
28
+ - Storing a cryptographic content-addressable hash of every generated byte (too brittle when
29
+ line endings or file ordering varies by OS).
30
+ - Detecting manual edits in non-generated files (files that `cs regen` does not own).
31
+ - Replacing `cs regen` — the fix action simply calls it.
32
+ - Validating overlay registry changes (a changed overlay that breaks composition is a different
33
+ problem).
34
+
35
+ ## Design
36
+
37
+ ### Reproducibility fingerprint approach
38
+
39
+ The check operates by performing a **dry regen** — composing the devcontainer in memory into a
40
+ temporary directory, then comparing the result file-by-file against the current output directory.
41
+ This is more reliable than a stored hash because it reflects the current overlay registry state
42
+ and always computes the ground truth.
43
+
44
+ `checkReproducibility(overlaysConfig, outputPath, overlaysDir, workingDir)` is a new async
45
+ function in `tool/commands/doctor.ts`.
46
+
47
+ **Early return**: if no project file is present, return empty (no noise).
48
+
49
+ **Step 1 — Dry compose**
50
+
51
+ 1. Load project config via `loadProjectConfig(workingDir)`.
52
+ 2. Rebuild answers via `buildAnswersFromProjectConfig()` + `applyPresetSelections()`.
53
+ 3. Create a temporary directory (use `os.mkdtemp`).
54
+ 4. Call `composeDevContainer(answers, overlaysDir, { isRegen: false, outputPath: tmpDir })`.
55
+ 5. The temporary directory now contains what regen would produce.
56
+
57
+ **Step 2 — File comparison**
58
+
59
+ List all files in `outputPath` (recursively) that are owned by `cs regen` — i.e., those present
60
+ in the temp directory or that have the `# Generated by container-superposition` header comment.
61
+
62
+ For each file in the temp directory:
63
+
64
+ - If absent from `outputPath` → **fail** ("File `<path>` would be created by `cs regen` but
65
+ does not exist — run `cs regen` to synchronise").
66
+ - If content differs → **fail** ("File `<path>` differs from what `cs regen` would produce —
67
+ it may have been manually edited or is out of date").
68
+
69
+ For each file in `outputPath` with the generation header that is absent from the temp directory:
70
+
71
+ - **warn** ("File `<path>` exists but `cs regen` would not produce it — it may be stale").
72
+
73
+ **Step 3 — Cleanup**
74
+
75
+ Remove the temporary directory unconditionally (inside a `finally` block).
76
+
77
+ Pass check message: "Generated output matches current project configuration."
78
+
79
+ ### Generated file identification
80
+
81
+ A file is "owned by regen" if it satisfies **any** of:
82
+
83
+ 1. Its first line matches `# Generated by container-superposition` (shell/yaml convention).
84
+ 2. Its first line matches `// Generated by container-superposition` (JSON, ts convention).
85
+ 3. It appears in the temp directory output (ground truth).
86
+
87
+ This avoids false positives on user-created files in `.devcontainer/` that regen does not touch.
88
+
89
+ ### Fix action: `reproducibility-regen`
90
+
91
+ Registered in `REMEDIATION_REGISTRY`:
92
+
93
+ - **Safety class**: `safe-unattended`
94
+ - **Execution kind**: `regeneration`
95
+ - **Planned changes**:
96
+ - "Regenerate devcontainer configuration from current project file"
97
+
98
+ `executeReproducibilityRegen(outputPath, overlaysConfig, overlaysDir, workingDir, silent)`:
99
+
100
+ 1. Load project config.
101
+ 2. Rebuild answers and call `composeDevContainer(answers, overlaysDir, { isRegen: true })`.
102
+ 3. Re-check: run `checkReproducibility()` and verify it returns no failures.
103
+
104
+ Note: since this is a destructive overwrite of generated files, the fix is safe only because
105
+ regen is idempotent and the files are derived outputs, not user data.
106
+
107
+ ### Performance note
108
+
109
+ The dry compose is the same operation as `cs regen` but writes to a temp dir. On typical projects
110
+ (< 20 overlays) this takes under 500 ms. If the operation is too slow for CI use, a future
111
+ optimisation could introduce a stored hash file (`.devcontainer/.superposition-hash`) as a
112
+ shortcut — but this is out of scope for the initial implementation.
113
+
114
+ ### DoctorReport changes
115
+
116
+ `DoctorReport` gains a `reproducibility: CheckResult[]` field.
117
+
118
+ `generateReport()` gains a `reproducibilityChecks` parameter.
119
+
120
+ `formatAsText()` gains a "Reproducibility" section; suppressed if all pass.
121
+
122
+ `reportToFindings()` adds:
123
+
124
+ ```typescript
125
+ ...checksToFindings(report.reproducibility, 'manifest', 'full'),
126
+ ```
127
+
128
+ `executeFixRun()` calls `checkReproducibility()` in the re-check pass.
129
+
130
+ `doctorCommand()` calls `await checkReproducibility(...)` and passes results to `generateReport()`.
131
+
132
+ Note: because `checkReproducibility()` is async, `doctorCommand()` and the section of
133
+ `generateReport()` that invokes checks must await it. The existing doctor checks are synchronous;
134
+ this will be the first async check. The wiring pattern must be adjusted to `await` the async
135
+ check before building the report.
136
+
137
+ ### Affected files
138
+
139
+ | File | Change |
140
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
141
+ | `tool/commands/doctor.ts` | Add `checkReproducibility()` (async), `executeReproducibilityRegen()`, wire into report infrastructure, `REMEDIATION_REGISTRY`, `PRIORITY` map; adjust `doctorCommand()` to `await` the new check |
142
+ | `tool/__tests__/commands.test.ts` | Tests for: clean output (pass), missing file (fail), differing file (fail), extra generated file (warn), fix action |
143
+ | `CHANGELOG.md` | Entry under `### Added` |
144
+
145
+ ### User-visible behaviour
146
+
147
+ ```
148
+ Reproducibility:
149
+ ✗ File devcontainer.json differs from what cs regen would produce
150
+ → It may have been manually edited or is out of date
151
+ → Run cs regen or use --fix to synchronise
152
+ ✗ File scripts/setup.sh would be created by cs regen but does not exist
153
+ → Run cs regen to synchronise
154
+ ⚠ File devcontainer.json.bak exists but cs regen would not produce it — it may be stale
155
+ ✓ Generated output matches current project configuration
156
+ ```
157
+
158
+ ### Backward compatibility
159
+
160
+ No changes to generated files or project file format. The dry compose uses the same code paths
161
+ as `cs regen` — if regen works, the check works.
162
+
163
+ ## User Scenarios & Testing
164
+
165
+ ### User Story 1 — Manual edit detected (P1)
166
+
167
+ A developer tweaked `devcontainer.json` to add a custom extension, then forgot to re-run
168
+ `cs regen` after changing an overlay parameter. Doctor reports that the file differs from what
169
+ regen would produce.
170
+
171
+ **Why this priority**: Manual edits to generated files are a common source of "it works on my
172
+ machine" bugs. Detecting them immediately is the highest-value use of the reproducibility check.
173
+
174
+ **Independent Test**: Write a `devcontainer.json` that differs from what `composeDevContainer`
175
+ would produce for the given project file. Run `doctorCommand`. Assert `fail` finding for
176
+ `devcontainer.json`.
177
+
178
+ **Acceptance Scenarios**:
179
+
180
+ 1. **Given** `devcontainer.json` has been manually modified after the last regen, **When**
181
+ `cs doctor` runs, **Then** a `fail` finding reports "`devcontainer.json` differs from what
182
+ `cs regen` would produce".
183
+ 2. **Given** `--fix` is used, **When** doctor runs, **Then** `devcontainer.json` is regenerated
184
+ and the re-check passes.
185
+ 3. **Given** the output exactly matches what regen would produce, **When** `cs doctor` runs,
186
+ **Then** the Reproducibility section is suppressed.
187
+
188
+ ---
189
+
190
+ ### User Story 2 — Missing generated file detected (P2)
191
+
192
+ A developer deleted `scripts/setup.sh` accidentally. Doctor flags the missing file.
193
+
194
+ **Acceptance Scenarios**:
195
+
196
+ 1. **Given** `scripts/setup.sh` would be produced by regen but is absent, **When** `cs doctor`
197
+ runs, **Then** a `fail` finding reports that the file would be created by regen but does not
198
+ exist.
199
+ 2. **Given** `--fix` is used, **When** doctor runs, **Then** the file is restored.
200
+
201
+ ---
202
+
203
+ ### Edge Cases
204
+
205
+ - Output directory does not exist: return a single `fail` ("Output directory `<path>` not found
206
+ — run `cs regen` to initialise").
207
+ - Temp directory creation fails (disk full): return a single `fail` with the OS error message.
208
+ - Compose call in dry mode throws: propagate the error as a `fail` finding; cleanup temp dir
209
+ in `finally`.
210
+ - Files owned by regen that differ only in line endings (CRLF vs LF): normalise to LF before
211
+ comparison so Windows checkouts do not generate false positives.
212
+
213
+ ## Requirements
214
+
215
+ ### Functional Requirements
216
+
217
+ - **FR-001**: `checkReproducibility()` MUST return `fail` for each file that would be produced
218
+ by `cs regen` but is absent from the output directory.
219
+ - **FR-002**: `checkReproducibility()` MUST return `fail` for each generated file whose content
220
+ differs from what `cs regen` would produce (after LF normalisation).
221
+ - **FR-003**: `checkReproducibility()` MUST return `warn` for each file in the output directory
222
+ with a generation header that `cs regen` would not produce.
223
+ - **FR-004**: `checkReproducibility()` MUST NOT report findings for files that are not owned by
224
+ regen (user-created files without the generation header).
225
+ - **FR-005**: The temporary directory used for dry compose MUST be cleaned up in a `finally`
226
+ block regardless of whether the check succeeds or fails.
227
+ - **FR-006**: `executeReproducibilityRegen()` MUST call `composeDevContainer` with `isRegen: true`
228
+ and verify the re-check returns no failures.
229
+ - **FR-007**: When no project file is present, the check MUST return an empty result.
230
+
231
+ ### Key Entities
232
+
233
+ - **`dryOutput`**: the set of files produced by `composeDevContainer` into a temp directory for the current project configuration.
234
+ - **Generation header**: the comment `# Generated by container-superposition` or `// Generated by container-superposition` on the first line of a file.
235
+
236
+ ## Dependencies & Impact
237
+
238
+ - **Affected Areas**: `tool/commands/doctor.ts`, `tool/__tests__/commands.test.ts`, `CHANGELOG.md`
239
+ - **Compatibility Impact**: None — purely additive. `composeDevContainer` gains an optional
240
+ `outputPath` parameter override if it does not already have one; otherwise a wrapper is used.
241
+ - **Required Documentation Updates**: `CHANGELOG.md`
242
+ - **Verification Plan**: Unit tests with mock filesystem; integration test with a real project file.
243
+
244
+ ## Success Criteria
245
+
246
+ ### Measurable Outcomes
247
+
248
+ - **SC-001**: `cs doctor` on a project where a generated file has been manually edited reports a
249
+ `fail` in the Reproducibility section within the normal doctor runtime budget.
250
+ - **SC-002**: `cs doctor --fix` on the same setup restores the file and the re-check passes.
251
+ - **SC-003**: `npm test` passes with at least 4 new test cases: clean output, missing file,
252
+ differing content, fix action.
253
+ - **SC-004**: No existing doctor tests regress.
254
+
255
+ ## Open Questions
256
+
257
+ | # | Question | Owner | Resolution |
258
+ | --- | ------------------------------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------ |
259
+ | 1 | Does `composeDevContainer` currently support an `outputPath` override, or does it always write to | Dev | Pending — needs code read of composer.ts |
260
+ | | a fixed location derived from `workingDir`? | | |
261
+ | 2 | Should the dry compose run be cached between doctor check runs to avoid double work when | PM | Pending — defer; optimise only if latency is measured as a problem |
262
+ | | both `checkReproducibility` and other checks need the composed output? | | |
263
+ | 3 | Line-ending normalisation: is CRLF a realistic concern in this repo's CI matrix? | Dev | Pending — check .gitattributes |
264
+
265
+ ## Out of Scope
266
+
267
+ - Storing a persistent hash file between runs (future optimisation only).
268
+ - Detecting which specific overlay or parameter change caused the drift.
269
+ - Tracking changes to overlay registry files as a trigger for "regen needed".
270
+
271
+ ## Implementation Notes
272
+
273
+ - `checkReproducibility` returns early when `outputPath` or `outputPath/devcontainer.json` does not exist — this prevents false positives in fresh environments where regen has never been run.
274
+ - `composeDevContainer` was called with a temp directory for the dry compose; the temp dir is cleaned up in a `finally` block unconditionally.
275
+ - The `os` module was added as a new import to `doctor.ts` for `os.tmpdir()`.
276
+ - Open question 1 (re: `composeDevContainer` `outputPath` override) was resolved by passing the temp directory as the `outputPath` option.
@@ -0,0 +1,276 @@
1
+ # Feature Specification: Doctor `--fix --dry-run` Flag
2
+
3
+ **Spec ID**: `017-doctor-dry-run`
4
+ **Taxonomy**: `CLI-FLAG`
5
+ **Created**: 2026-04-24
6
+ **Author**: PM Agent
7
+ **Status**: Approved
8
+ **Input**: Feature assessment — `cs doctor --fix` applies changes immediately with no preview; users have no way to understand what would change before committing to auto-repair.
9
+
10
+ ## Problem Statement
11
+
12
+ `cs doctor --fix` is a powerful command that modifies the project file and regenerates the
13
+ devcontainer in one step. Users — especially those new to the project — have no way to preview
14
+ what changes `--fix` would make before applying them. This makes `--fix` feel risky and
15
+ discourages adoption. A `--dry-run` modifier should show exactly what would change without
16
+ writing anything, enabling confident use of `--fix`.
17
+
18
+ ## Goals
19
+
20
+ - Add a `--dry-run` flag (usable only in combination with `--fix`) that prints a human-readable
21
+ plan of what each fix action would do without executing any file writes.
22
+ - Show the planned changes for every auto-fixable finding: which files would be written, which
23
+ keys would be added to the project file, and what regen would regenerate.
24
+ - Exit with a non-zero exit code when there are findings that would be fixed (so CI can detect
25
+ "fix needed" without applying the fix).
26
+
27
+ ## Non-Goals
28
+
29
+ - Implementing a diff of generated file content (file-level diff is an enhancement, not
30
+ required for the initial implementation).
31
+ - Applying partial fixes (dry-run is all-or-nothing: show the plan for all auto-fixable findings
32
+ or none).
33
+ - Changing any existing `--fix` behaviour (dry-run is a separate flag, not a modifier to the
34
+ existing logic).
35
+ - Supporting `--dry-run` without `--fix` (without `--fix`, there is nothing to dry-run; the
36
+ combination is invalid).
37
+
38
+ ## Design
39
+
40
+ ### CLI flag
41
+
42
+ `doctor` gains a new option: `--dry-run` (boolean, default `false`).
43
+
44
+ The Commander declaration:
45
+
46
+ ```typescript
47
+ .option('--dry-run', 'Show what --fix would change without writing anything')
48
+ ```
49
+
50
+ Validation: if `--dry-run` is set but `--fix` is not set, print an error and exit:
51
+
52
+ ```
53
+ Error: --dry-run requires --fix. Use: cs doctor --fix --dry-run
54
+ ```
55
+
56
+ ### Dry-run execution flow
57
+
58
+ When `--fix --dry-run` is used:
59
+
60
+ 1. Run all doctor checks as normal (same as `--fix`).
61
+ 2. Collect all auto-fixable findings by consulting `REMEDIATION_REGISTRY` for each finding's
62
+ remediation key.
63
+ 3. For each auto-fixable finding, look up the `RemediationAction.plannedChanges` array in
64
+ `REMEDIATION_REGISTRY`. These are already stored as human-readable strings per the existing
65
+ spec 004 design.
66
+ 4. Print the dry-run plan (see "User-visible behaviour" below).
67
+ 5. **Do not call `executeSingleFix()` or `executeFixRun()`** — no writes occur.
68
+ 6. Exit with code `1` if there are auto-fixable findings (to support CI use); exit with `0` if
69
+ all findings are already clean.
70
+
71
+ ### Integration with `executeFixRun`
72
+
73
+ `executeFixRun(findings, options)` gains a `dryRun: boolean` parameter (default `false`).
74
+
75
+ When `dryRun` is `true`:
76
+
77
+ - Skip all `executeSingleFix()` calls.
78
+ - Return a summary marked as `{ dryRun: true, plannedActions: RemediationPlan[] }`.
79
+
80
+ `RemediationPlan` (new type):
81
+
82
+ ```typescript
83
+ interface RemediationPlan {
84
+ findingName: string;
85
+ remediationKey: string;
86
+ plannedChanges: string[];
87
+ safetyClass: string;
88
+ }
89
+ ```
90
+
91
+ ### Output format
92
+
93
+ ```
94
+ Doctor dry-run — changes that --fix would apply:
95
+ ══════════════════════════════════════════════════
96
+
97
+ [1] parameters-regen (safe-unattended)
98
+ Finding: "Missing required parameter: POSTGRES_PASSWORD"
99
+ Would:
100
+ • Add missing parameters with overlay defaults to project file
101
+ • Regenerate devcontainer configuration from project file
102
+
103
+ [2] dependency-fix (safe-unattended)
104
+ Finding: "Overlay grafana requires prometheus which is not in your project file"
105
+ Would:
106
+ • Add missing required overlay(s) to project file
107
+ • Regenerate devcontainer configuration from updated project file
108
+
109
+ [3] env-example-regen (safe-unattended)
110
+ Finding: "Parameter POSTGRES_PASSWORD is missing from .env.example"
111
+ Would:
112
+ • Regenerate .env.example from current overlay selection
113
+
114
+ ──────────────────────────────────────────────────
115
+ 3 fix action(s) would be applied. Run without --dry-run to apply.
116
+
117
+ Findings that require manual action (not auto-fixable):
118
+ ✗ Overlay nodjs not found in overlay registry — check for typos
119
+ → Edit .superposition.yml to correct the overlay ID
120
+ ```
121
+
122
+ When there are no auto-fixable findings:
123
+
124
+ ```
125
+ Doctor dry-run — no auto-fixable findings. Nothing to apply.
126
+ ```
127
+
128
+ ### JSON output (--format json)
129
+
130
+ When `--format json` is used with `--fix --dry-run`, the JSON output gains a `dryRun` key:
131
+
132
+ ```json
133
+ {
134
+ "dryRun": true,
135
+ "plannedActions": [
136
+ {
137
+ "findingName": "Missing required parameter: POSTGRES_PASSWORD",
138
+ "remediationKey": "parameters-regen",
139
+ "plannedChanges": ["Add missing parameters with overlay defaults to project file", "Regenerate devcontainer configuration from project file"],
140
+ "safetyClass": "safe-unattended"
141
+ }
142
+ ],
143
+ "manualFindings": [...]
144
+ }
145
+ ```
146
+
147
+ ### Exit codes
148
+
149
+ | Scenario | Exit code |
150
+ | --------------------------------------------------- | --------- |
151
+ | Dry-run with auto-fixable findings | `1` |
152
+ | Dry-run with only manual findings (no auto-fixable) | `1` |
153
+ | Dry-run with no findings at all (everything passes) | `0` |
154
+ | `--dry-run` without `--fix` (invalid combination) | `1` |
155
+
156
+ ### Affected files
157
+
158
+ | File | Change |
159
+ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
160
+ | `tool/commands/doctor.ts` | Add `--dry-run` option, `RemediationPlan` type, dry-run branch in `executeFixRun`, dry-run output section in `formatAsText` |
161
+ | `tool/__tests__/commands.test.ts` | Tests for: dry-run output lists planned actions, no writes occur, exit code 1 with findings, exit code 0 clean, invalid `--dry-run` without `--fix` |
162
+ | `CHANGELOG.md` | Entry under `### Added` |
163
+
164
+ ### User-visible behaviour
165
+
166
+ See the output format section above. The key constraint: no files are modified when `--dry-run`
167
+ is active — not the project file, not any generated file, not `.env.example`.
168
+
169
+ ### Backward compatibility
170
+
171
+ `--dry-run` is a new flag — no existing behaviour changes. `--fix` without `--dry-run` continues
172
+ to work exactly as before. The new `dryRun` parameter on `executeFixRun` defaults to `false`.
173
+
174
+ ## User Scenarios & Testing
175
+
176
+ ### User Story 1 — Safe preview before first `--fix` use (P1)
177
+
178
+ A developer is onboarding to a project and sees 3 doctor failures. Before running `--fix`, they
179
+ run `cs doctor --fix --dry-run` to understand what will change.
180
+
181
+ **Why this priority**: `--fix` modifies the project file and regenerates. Without a preview,
182
+ users are reluctant to use it. Dry-run is the single biggest adoption unblock for `--fix`.
183
+
184
+ **Independent Test**: Set up a project with a missing required parameter and a missing required
185
+ overlay dependency. Run `doctorCommand --fix --dry-run`. Assert no files were written and output
186
+ contains both planned actions.
187
+
188
+ **Acceptance Scenarios**:
189
+
190
+ 1. **Given** a project with a missing required parameter, **When** `cs doctor --fix --dry-run`
191
+ runs, **Then** the output lists the `parameters-regen` planned action and the project file is
192
+ not modified.
193
+ 2. **Given** `--dry-run` without `--fix`, **When** the command runs, **Then** an error is printed
194
+ and the command exits with code 1.
195
+ 3. **Given** no fixable findings, **When** `cs doctor --fix --dry-run` runs, **Then** output
196
+ reports "no auto-fixable findings" and exits with code 0.
197
+
198
+ ---
199
+
200
+ ### User Story 2 — CI "fix needed" detection (P2)
201
+
202
+ A CI pipeline runs `cs doctor --fix --dry-run` and fails the build when doctor would make
203
+ changes, forcing the developer to run `cs doctor --fix` locally before merging.
204
+
205
+ **Acceptance Scenarios**:
206
+
207
+ 1. **Given** auto-fixable findings exist, **When** `cs doctor --fix --dry-run` runs in CI,
208
+ **Then** exit code is `1`.
209
+ 2. **Given** all checks pass, **When** `cs doctor --fix --dry-run` runs in CI, **Then** exit
210
+ code is `0`.
211
+
212
+ ---
213
+
214
+ ### Edge Cases
215
+
216
+ - Manual-only findings with no auto-fixable ones: still exit `1` (there are problems), still
217
+ show the manual section, show "0 auto-fixable actions".
218
+ - All findings are manual-only: dry-run output should clarify that `--fix` would not resolve
219
+ them automatically.
220
+ - `--dry-run` used with `--format json`: produce JSON with the `dryRun` structure above.
221
+
222
+ ## Requirements
223
+
224
+ ### Functional Requirements
225
+
226
+ - **FR-001**: When `--fix --dry-run` is specified, `cs doctor` MUST NOT write any files.
227
+ - **FR-002**: The dry-run output MUST list every auto-fixable finding with its `remediationKey`
228
+ and `plannedChanges`.
229
+ - **FR-003**: The dry-run output MUST list manual-only findings separately.
230
+ - **FR-004**: `cs doctor --fix --dry-run` MUST exit with code `1` when any findings exist (auto-
231
+ fixable or manual).
232
+ - **FR-005**: `cs doctor --fix --dry-run` MUST exit with code `0` when all checks pass.
233
+ - **FR-006**: `--dry-run` without `--fix` MUST print an error message and exit with code `1`.
234
+ - **FR-007**: `--fix --dry-run` with `--format json` MUST include a `dryRun: true` key and
235
+ `plannedActions` array in the JSON output.
236
+
237
+ ## Dependencies & Impact
238
+
239
+ - **Affected Areas**: `tool/commands/doctor.ts`, `tool/__tests__/commands.test.ts`, `CHANGELOG.md`
240
+ - **Compatibility Impact**: None — `--dry-run` is a new flag; no existing behaviour changes.
241
+ - **Required Documentation Updates**: `CHANGELOG.md`; update `cs doctor --help` output implicitly
242
+ via Commander option declaration.
243
+ - **Verification Plan**: Unit tests verifying no writes occur; exit code assertions.
244
+
245
+ ## Success Criteria
246
+
247
+ ### Measurable Outcomes
248
+
249
+ - **SC-001**: `cs doctor --fix --dry-run` on a project with fixable findings produces output
250
+ listing all planned actions and exits with code 1.
251
+ - **SC-002**: No files in the project directory or output directory are modified when `--dry-run`
252
+ is active.
253
+ - **SC-003**: `npm test` passes with at least 4 new test cases: dry-run output, no-write
254
+ guarantee, exit codes, invalid flag combination.
255
+ - **SC-004**: No existing doctor tests regress.
256
+
257
+ ## Open Questions
258
+
259
+ | # | Question | Owner | Resolution |
260
+ | --- | ---------------------------------------------------------------------------------------- | ----- | ----------------------------------------------------------------------------------------- |
261
+ | 1 | Should `--dry-run` show a file-level diff of generated output (e.g. what regen would add | PM | Pending — defer; full diff requires dry compose which is expensive. Leave for a follow-up |
262
+ | | to devcontainer.json)? | | |
263
+ | 2 | Should `--dry-run` without `--fix` be silently ignored instead of an error? | PM | Pending — lean toward error: the combination is meaningless and should be caught early |
264
+
265
+ ## Out of Scope
266
+
267
+ - File-content diffing (showing before/after for generated files).
268
+ - Partial dry-run (dry-running only some fix actions).
269
+ - Storing the dry-run plan to a file for later application.
270
+
271
+ ## Implementation Notes
272
+
273
+ - `executeFixRun` gains a `dryRun: boolean` parameter (default `false`); when true it skips all `executeSingleFix` calls and returns the planned actions without writing anything.
274
+ - The `RemediationPlan` interface is defined in `doctor.ts` with fields `findingName`, `remediationKey`, `plannedChanges`, and `safetyClass`.
275
+ - The dry-run branch in `doctorCommand` runs before the normal fix branch; `--dry-run` without `--fix` exits immediately with code 1 and an error message.
276
+ - Exit code for dry-run with any findings (auto-fixable or manual) is `1`; exit code `0` only when all checks pass.
@@ -1,17 +1,10 @@
1
1
  # Feature Specification: `init --project-file`
2
2
 
3
- **Feature Branch**: `copilot/sub-pr-121`
3
+ **Spec ID**: `018-init-project-file`
4
4
  **Created**: 2026-03-23
5
5
  **Status**: Final
6
6
  **Input**: Extend the `init` command with a `--project-file` flag that persists the chosen init configuration into a repository-root project config file (creating `.superposition.yml` by default or updating an existing supported project config), aligning init runs with the project-config workflow.
7
7
 
8
- ## Review & Approval _(mandatory before implementation)_
9
-
10
- - **Spec Path**: `docs/specs/007-init-project-file/spec.md`
11
- - **Commit Status**: Committed
12
- - **Review Status**: Approved
13
- - **Implementation Gate**: No implementation code may begin until this spec is committed and reviewed.
14
-
15
8
  ## Summary
16
9
 
17
10
  Allow `container-superposition init` to optionally write a repository-root
@@ -34,7 +27,7 @@ project config path when one already exists) alongside the normal init output.
34
27
  current `init` runs do.
35
28
  - Project config write errors MUST NOT suppress devcontainer generation success; they MUST be reported separately.
36
29
 
37
- ## User Scenarios & Testing _(mandatory)_
30
+ ## User Scenarios & Testing
38
31
 
39
32
  ### User Story 1 - Write project config alongside devcontainer generation (Priority: P1)
40
33