@polymorphism-tech/morph-spec 8.33.0 → 8.34.2

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 (176) hide show
  1. package/CLAUDE.md +3 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +25 -1
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +2 -2
  9. package/framework/MORPH.md +4 -5
  10. package/framework/agents.json +1 -0
  11. package/framework/docs/CLI.md +5 -2
  12. package/framework/hooks/README.md +11 -1
  13. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
  14. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
  15. package/framework/hooks/claude-code/core/chain.js +27 -7
  16. package/framework/hooks/claude-code/core/worktree-context.js +83 -14
  17. package/framework/hooks/claude-code/core/worktree-create.js +206 -0
  18. package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
  19. package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
  20. package/framework/hooks/claude-code/statusline.sh +11 -11
  21. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  22. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  23. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  24. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  25. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  26. package/framework/hooks/shared/command-tokens.js +23 -0
  27. package/framework/hooks/shared/e2e-check.js +0 -0
  28. package/framework/hooks/shared/git-paths.js +25 -0
  29. package/framework/hooks/shared/hook-response.js +17 -0
  30. package/framework/hooks/shared/morph-cli.js +134 -0
  31. package/framework/hooks/shared/stdin-reader.js +26 -26
  32. package/framework/rules/infrastructure-standards.md +3 -1
  33. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  34. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  35. package/framework/skills/core/morph-apply/SKILL.md +3 -3
  36. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  37. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  38. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  39. package/framework/skills/core/morph-implement/SKILL.md +3 -1
  40. package/framework/skills/core/morph-plan/SKILL.md +6 -3
  41. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  42. package/framework/skills/core/morph-review/SKILL.md +1 -1
  43. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  44. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  45. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  46. package/framework/standards/STANDARDS.json +30 -0
  47. package/framework/standards/ai-agents/agent-session.md +160 -160
  48. package/framework/standards/ai-agents/batching.md +193 -193
  49. package/framework/standards/ai-agents/context-providers.md +188 -188
  50. package/framework/standards/ai-agents/mcp-server.md +250 -250
  51. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  52. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  53. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  54. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  55. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  56. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  57. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  58. package/framework/standards/ai-agents/production.md +231 -231
  59. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  60. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  61. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  62. package/framework/standards/ai-agents/structured-output.md +249 -249
  63. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  64. package/framework/standards/infrastructure/docker/local-compose-isolation.md +88 -0
  65. package/framework/templates/IDE-SNIPPETS.md +262 -262
  66. package/framework/templates/REGISTRY.json +1 -1
  67. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  68. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  69. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  70. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  71. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  72. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  73. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  74. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  75. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  76. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  77. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  78. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  79. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  80. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  81. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  82. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  83. package/framework/templates/code/typescript/contracts.ts +167 -167
  84. package/framework/templates/docs/clarifications.md +253 -253
  85. package/framework/templates/docs/schema-analysis.md +119 -119
  86. package/framework/templates/docs/ui-components.md +52 -52
  87. package/framework/templates/docs/ui-design-system.md +76 -76
  88. package/framework/templates/docs/ui-flows.md +167 -167
  89. package/framework/templates/docs/ui-mockups.md +98 -98
  90. package/framework/templates/feature/decisions.md +122 -122
  91. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  92. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  93. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  94. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  95. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  96. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  97. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  98. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  99. package/framework/templates/infrastructure/docker/docker-compose.template.yml +24 -14
  100. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  101. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  102. package/framework/templates/infrastructure/github/README.md +56 -56
  103. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  104. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  105. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  106. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  107. package/framework/templates/integrations/asaas-client.cs +387 -387
  108. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  109. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  110. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  111. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  112. package/framework/templates/project/validate.js +120 -120
  113. package/framework/templates/state.template.json +17 -17
  114. package/framework/templates/ui/design-system.css +226 -226
  115. package/package.json +1 -1
  116. package/src/commands/dashboard/dashboard.js +53 -53
  117. package/src/commands/e2e/e2e.js +77 -39
  118. package/src/commands/index.js +13 -13
  119. package/src/commands/project/checkpoint.js +209 -209
  120. package/src/commands/project/doctor.js +8 -6
  121. package/src/commands/project/env.js +95 -0
  122. package/src/commands/project/worktree.js +266 -52
  123. package/src/commands/state/delete.js +8 -0
  124. package/src/commands/state/finish.js +29 -34
  125. package/src/core/state/phase-state-machine.js +197 -197
  126. package/src/core/templates/template-renderer.js +477 -477
  127. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  128. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  129. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  130. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  131. package/src/dashboard/app/main.jsx +13 -13
  132. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  133. package/src/dashboard/app/pages/HookInspector.jsx +430 -418
  134. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  135. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  136. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  137. package/src/dashboard/app/pages/Standards.jsx +332 -332
  138. package/src/dashboard/app/styles/index.css +47 -47
  139. package/src/dashboard/index.html +12 -12
  140. package/src/dashboard/package.json +24 -24
  141. package/src/dashboard/scanner/hooks-scanner.js +5 -1
  142. package/src/dashboard/scanner/phases-scanner.js +15 -15
  143. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  144. package/src/dashboard/scanner/rules-scanner.js +37 -37
  145. package/src/dashboard/scanner/skills-scanner.js +43 -43
  146. package/src/dashboard/scanner/squads-scanner.js +13 -13
  147. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  148. package/src/dashboard/vite.config.js +24 -24
  149. package/src/lib/detectors/design-system-detector.js +188 -188
  150. package/src/lib/detectors/index.js +217 -217
  151. package/src/lib/e2e/compose-env.js +6 -11
  152. package/src/lib/e2e/stack-teardown.js +115 -0
  153. package/src/lib/generators/architecture-scanner.js +2 -1
  154. package/src/lib/generators/design-system-generator.js +508 -508
  155. package/src/lib/generators/settings-generator.js +209 -209
  156. package/src/lib/graph/graphifyignore.js +2 -1
  157. package/src/lib/hooks/hook-executor.js +167 -167
  158. package/src/lib/installers/mcp-installer.js +719 -719
  159. package/src/lib/tasks/task-classifier.js +30 -30
  160. package/src/lib/tasks/task-parser.js +209 -209
  161. package/src/lib/tasks/test-runner.js +21 -4
  162. package/src/lib/validators/checkpoint-persistence.js +39 -39
  163. package/src/lib/validators/css/css-validator.js +340 -340
  164. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  165. package/src/lib/validators/shared/issue-counter.js +14 -14
  166. package/src/lib/validators/verify-runner.js +7 -0
  167. package/src/lib/worktree/dirs.js +32 -0
  168. package/src/lib/worktree/env-allocator.js +45 -11
  169. package/src/lib/worktree/worktree-audit.js +40 -2
  170. package/src/utils/agent-id-mapper.js +11 -11
  171. package/src/utils/claude-settings-manager.js +300 -300
  172. package/src/utils/file-copier.js +18 -13
  173. package/src/utils/hooks-installer.js +46 -3
  174. package/src/utils/skills-installer.js +187 -187
  175. package/framework/templates/.idea/morph-templates.xml +0 -92
  176. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
@@ -7,6 +7,11 @@
7
7
  * running still matches the working tree, and `down` releases it — which
8
8
  * `morph-spec finish` also does as part of teardown.
9
9
  *
10
+ * Every subcommand acts in the TREE THAT OWNS THE FEATURE: run from the
11
+ * primary root for a feature that lives in a worktree, the compose file, the
12
+ * bind mount and the stamp are all the worktree's — the root's copy is a
13
+ * different checkout of different code.
14
+ *
10
15
  * All the real logic lives in framework/hooks/shared/e2e-check.js, shared with
11
16
  * the `verify` e2e node so the two can never drift.
12
17
  *
@@ -22,16 +27,21 @@ import {
22
27
  bringUpStack,
23
28
  composeProjectName,
24
29
  computeSourceStamp,
30
+ decideIsolation,
25
31
  defaultExec,
32
+ deleteStamp,
26
33
  getStackStatus,
27
34
  isDockerAvailable,
35
+ lintComposeIsolation,
28
36
  prepareAuth,
29
37
  readStamp,
30
38
  resolveComposeFile,
31
39
  resolveSpecCommand,
32
40
  tearDownStack,
33
41
  } from '../../../framework/hooks/shared/e2e-check.js';
42
+ import { isSecondaryWorktree } from '../../../framework/hooks/shared/git-paths.js';
34
43
  import { buildComposeEnv } from '../../lib/e2e/compose-env.js';
44
+ import { findWorktreeForFeature, resolvePrimaryRoot } from '../project/worktree.js';
35
45
 
36
46
  /** Exit code for a usage error, matching `verify`'s convention. */
37
47
  const EXIT_USAGE = 2;
@@ -41,20 +51,26 @@ const EXIT_USAGE = 2;
41
51
  // ============================================================================
42
52
 
43
53
  /**
44
- * Everything the three subcommands need, resolved once.
45
- * Returns null (after printing) when the project has no compose file — that is
46
- * a usage error here, unlike in the verify node where it is a graded skip.
54
+ * Everything the three subcommands need, resolved once — against the tree
55
+ * that owns the feature (its live worktree when it has one, the root
56
+ * otherwise).
57
+ *
58
+ * Returns null (after printing) when that tree has no compose file — a usage
59
+ * error here, unlike in the verify node where it is a graded skip.
47
60
  */
48
61
  function resolveContext(featureName, { json = false } = {}) {
49
- const cwd = process.cwd();
50
- const config = readConfig(cwd);
62
+ const root = resolvePrimaryRoot(process.cwd());
63
+ const found = findWorktreeForFeature(featureName, root);
64
+ const cwd = found?.path ?? root;
65
+
66
+ const config = readConfig(cwd) ?? readConfig(root);
51
67
  const composeFile = resolveComposeFile(cwd, config);
52
68
 
53
69
  if (!composeFile) {
54
70
  const message =
55
- 'No compose file found (compose.yaml, docker-compose.yml, …). ' +
71
+ `No compose file found in ${cwd} (compose.yaml, docker-compose.yml, …). ` +
56
72
  'Set project.e2e.composeFile in .morph/config/config.json if it lives elsewhere.';
57
- if (json) console.log(JSON.stringify({ error: message }, null, 2));
73
+ if (json) console.log(JSON.stringify({ error: message, tree: cwd }, null, 2));
58
74
  else console.error(chalk.red(`\n❌ ${message}\n`));
59
75
  return null;
60
76
  }
@@ -64,6 +80,9 @@ function resolveContext(featureName, { json = false } = {}) {
64
80
 
65
81
  return {
66
82
  cwd,
83
+ root,
84
+ tree: cwd,
85
+ insideWorktree: isSecondaryWorktree(cwd),
67
86
  config,
68
87
  composeFile,
69
88
  projectName,
@@ -79,39 +98,17 @@ function resolveContext(featureName, { json = false } = {}) {
79
98
  * The port block comes from `buildComposeEnv` rather than being assembled here
80
99
  * so this command and the `e2e` node inside `verify` agree by construction —
81
100
  * isolation that exists only in the command nobody runs is no isolation at all.
82
- * `COMPOSE_PROJECT_NAME` isolates container NAMES but not host ports; a compose
83
- * that hardcodes ports instead of reading `${PORT:-3000}` cannot be isolated at
84
- * all, which is surfaced as a warning rather than silently accepted.
85
101
  */
86
102
  function buildEnv({ cwd, config, featureName, projectName, gitCommonDir }) {
103
+ // COMPOSE_PROJECT_NAME is part of the port layout now; the explicit key stays
104
+ // as the fail-open floor for a repo with no git common dir (no block → no
105
+ // layout), where the project name is still what isolates the containers.
87
106
  return {
88
107
  COMPOSE_PROJECT_NAME: projectName,
89
108
  ...buildComposeEnv({ cwd, config, featureName, gitCommonDir }),
90
109
  };
91
110
  }
92
111
 
93
- /**
94
- * Warn once when the compose file publishes fixed host ports: with a hardcoded
95
- * `"3000:3000"` the fleet port block cannot take effect, so two features
96
- * running simultaneously will collide no matter what we inject.
97
- */
98
- function warnOnFixedPorts(cwd, composeFile) {
99
- try {
100
- const raw = readFileSync(join(cwd, composeFile), 'utf8');
101
- const publishesFixed = /^\s*-\s*["']?\d+:\d+["']?\s*$/m.test(raw);
102
- if (publishesFixed) {
103
- console.log(
104
- chalk.yellow(
105
- '⚠️ Compose publishes fixed host ports. Parallel worktrees will collide — ' +
106
- 'use ${PORT:-3000}:3000 so the fleet port block applies.'
107
- )
108
- );
109
- }
110
- } catch {
111
- // Unreadable compose is the caller's problem, not this warning's.
112
- }
113
- }
114
-
115
112
  // ============================================================================
116
113
  // up
117
114
  // ============================================================================
@@ -119,20 +116,42 @@ function warnOnFixedPorts(cwd, composeFile) {
119
116
  /**
120
117
  * `morph-spec e2e up <feature> [--fresh]`
121
118
  *
122
- * Exit codes: 0 = stack healthy · 1 = failed to come up (or auth broke) ·
123
- * 2 = usage (no compose file / no docker).
119
+ * Exit codes: 0 = stack healthy · 1 = failed to come up (or auth broke, or the
120
+ * compose cannot be isolated inside a worktree) · 2 = usage (no compose file /
121
+ * no docker).
124
122
  */
125
123
  export async function e2eUpCommand(featureName, options = {}) {
126
124
  const ctx = resolveContext(featureName, options);
127
125
  if (!ctx) process.exit(EXIT_USAGE);
128
126
 
127
+ // Isolation BEFORE the docker check: `COMPOSE_PROJECT_NAME` isolates
128
+ // container names but not host ports, and a compose that hardcodes them (or
129
+ // names its containers) collides with the sibling worktree no matter what we
130
+ // inject. Same rule as the verify node — see decideIsolation().
131
+ const lint = lintComposeIsolation(readComposeText(ctx.cwd, ctx.composeFile));
132
+ const isolation = decideIsolation({ findings: lint.findings, insideWorktree: ctx.insideWorktree });
133
+ if (isolation.status === 'fail') {
134
+ if (options.json) {
135
+ console.log(JSON.stringify(
136
+ { error: 'compose-not-isolable', feature: featureName, tree: ctx.tree, composeFile: ctx.composeFile, reason: isolation.reason, findings: lint.findings },
137
+ null, 2,
138
+ ));
139
+ } else {
140
+ console.error(chalk.red(`\n❌ ${isolation.reason}`));
141
+ for (const f of lint.findings) console.error(chalk.gray(` ${ctx.composeFile}:${f.line}: ${f.text}`));
142
+ console.error('');
143
+ }
144
+ process.exit(1);
145
+ }
146
+ if (isolation.status === 'warn' && !options.json) {
147
+ console.log(chalk.yellow(`⚠️ ${isolation.reason}`));
148
+ }
149
+
129
150
  if (!isDockerAvailable(ctx.cwd)) {
130
151
  console.error(chalk.red('\n❌ Docker is not available in this environment.\n'));
131
152
  process.exit(EXIT_USAGE);
132
153
  }
133
154
 
134
- if (!options.json) warnOnFixedPorts(ctx.cwd, ctx.composeFile);
135
-
136
155
  const result = bringUpStack(ctx.cwd, {
137
156
  composeFile: ctx.composeFile,
138
157
  projectName: ctx.projectName,
@@ -150,11 +169,13 @@ export async function e2eUpCommand(featureName, options = {}) {
150
169
  const payload = {
151
170
  feature: featureName,
152
171
  project: ctx.projectName,
172
+ tree: ctx.tree,
153
173
  composeFile: ctx.composeFile,
154
174
  status: failed ? 'fail' : 'pass',
155
175
  reused: result.reused,
156
176
  reason: result.reason,
157
177
  auth: { status: auth.status, reason: auth.reason },
178
+ ...(isolation.status === 'warn' ? { warnings: [isolation.reason] } : {}),
158
179
  };
159
180
 
160
181
  if (options.json) {
@@ -165,6 +186,7 @@ export async function e2eUpCommand(featureName, options = {}) {
165
186
  } else {
166
187
  const how = result.reused ? chalk.gray(`(${result.reason})`) : chalk.gray('(built)');
167
188
  console.log(chalk.green(`\n✓ Stack up — ${ctx.projectName} ${how}`));
189
+ console.log(chalk.gray(` tree: ${ctx.tree}`));
168
190
  if (auth.reason) console.log(chalk.gray(` auth: ${auth.reason}`));
169
191
  console.log('');
170
192
  }
@@ -217,6 +239,7 @@ export async function e2eStatusCommand(featureName, options = {}) {
217
239
  const payload = {
218
240
  feature: featureName,
219
241
  project: ctx.projectName,
242
+ tree: ctx.tree,
220
243
  composeFile: ctx.composeFile,
221
244
  up: status.up,
222
245
  healthy: status.healthy,
@@ -236,6 +259,7 @@ export async function e2eStatusCommand(featureName, options = {}) {
236
259
  } else {
237
260
  console.log('');
238
261
  console.log(chalk.bold(`E2E stack — ${ctx.projectName}`));
262
+ console.log(chalk.gray(`tree: ${ctx.tree}`));
239
263
  console.log(chalk.gray(`compose: ${ctx.composeFile}`));
240
264
  console.log(chalk.gray(`url: ${payload.baseUrl}`));
241
265
  console.log('');
@@ -262,7 +286,8 @@ export async function e2eStatusCommand(featureName, options = {}) {
262
286
  * `morph-spec e2e down <feature> [--volumes]`
263
287
  *
264
288
  * Teardown is best-effort by design — a stack that will not come down must
265
- * never block `finish`.
289
+ * never block `finish`. The freshness stamp goes regardless: a stamp that
290
+ * outlives its containers would let the next `up` reuse a stack that is gone.
266
291
  */
267
292
  export async function e2eDownCommand(featureName, options = {}) {
268
293
  const ctx = resolveContext(featureName, options);
@@ -274,11 +299,16 @@ export async function e2eDownCommand(featureName, options = {}) {
274
299
  env: ctx.env,
275
300
  volumes: Boolean(options.volumes),
276
301
  });
302
+ const stampDeleted = deleteStamp(ctx.gitCommonDir, ctx.projectName);
277
303
 
278
304
  if (options.json) {
279
- console.log(JSON.stringify({ feature: featureName, project: ctx.projectName, status: result.status }, null, 2));
305
+ console.log(JSON.stringify(
306
+ { feature: featureName, project: ctx.projectName, tree: ctx.tree, status: result.status, stampDeleted },
307
+ null, 2,
308
+ ));
280
309
  } else if (result.status === 'pass') {
281
- console.log(chalk.green(`\n✓ Stack down — ${ctx.projectName}\n`));
310
+ console.log(chalk.green(`\n✓ Stack down — ${ctx.projectName}`));
311
+ console.log(chalk.gray(` tree: ${ctx.tree}\n`));
282
312
  } else {
283
313
  console.log(chalk.yellow(`\n⚠️ Teardown reported a problem (not fatal):\n${result.output}\n`));
284
314
  }
@@ -300,6 +330,14 @@ function readConfig(cwd) {
300
330
  }
301
331
  }
302
332
 
333
+ function readComposeText(cwd, composeFile) {
334
+ try {
335
+ return readFileSync(join(cwd, composeFile), 'utf8');
336
+ } catch {
337
+ return '';
338
+ }
339
+ }
340
+
303
341
  /** Same resolution (and same fail-open) as verify-runner's. */
304
342
  function resolveGitCommonDir(cwd) {
305
343
  try {
@@ -1,13 +1,13 @@
1
- /**
2
- * All Commands - Category Exports
3
- *
4
- * Commands organized by functional category.
5
- */
6
-
7
- export * from './state/index.js';
8
- export * from './validation/index.js';
9
- export * from './templates/index.js';
10
- export * from './project/index.js';
11
- export * from './tasks/index.js';
12
- export * from './agents/index.js';
13
- export * from './utils/index.js';
1
+ /**
2
+ * All Commands - Category Exports
3
+ *
4
+ * Commands organized by functional category.
5
+ */
6
+
7
+ export * from './state/index.js';
8
+ export * from './validation/index.js';
9
+ export * from './templates/index.js';
10
+ export * from './project/index.js';
11
+ export * from './tasks/index.js';
12
+ export * from './agents/index.js';
13
+ export * from './utils/index.js';