@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.
- package/CLAUDE.md +3 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +25 -1
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +4 -5
- package/framework/agents.json +1 -0
- package/framework/docs/CLI.md +5 -2
- package/framework/hooks/README.md +11 -1
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
- package/framework/hooks/claude-code/core/chain.js +27 -7
- package/framework/hooks/claude-code/core/worktree-context.js +83 -14
- package/framework/hooks/claude-code/core/worktree-create.js +206 -0
- package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
- package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/command-tokens.js +23 -0
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/git-paths.js +25 -0
- package/framework/hooks/shared/hook-response.js +17 -0
- package/framework/hooks/shared/morph-cli.js +134 -0
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rules/infrastructure-standards.md +3 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +3 -3
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +3 -1
- package/framework/skills/core/morph-plan/SKILL.md +6 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +30 -0
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +231 -231
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +88 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +1 -1
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +24 -14
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/e2e/e2e.js +77 -39
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +8 -6
- package/src/commands/project/env.js +95 -0
- package/src/commands/project/worktree.js +266 -52
- package/src/commands/state/delete.js +8 -0
- package/src/commands/state/finish.js +29 -34
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +430 -418
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +5 -1
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/e2e/compose-env.js +6 -11
- package/src/lib/e2e/stack-teardown.js +115 -0
- package/src/lib/generators/architecture-scanner.js +2 -1
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/graph/graphifyignore.js +2 -1
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +21 -4
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/verify-runner.js +7 -0
- package/src/lib/worktree/dirs.js +32 -0
- package/src/lib/worktree/env-allocator.js +45 -11
- package/src/lib/worktree/worktree-audit.js +40 -2
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +18 -13
- package/src/utils/hooks-installer.js +46 -3
- package/src/utils/skills-installer.js +187 -187
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
package/src/commands/e2e/e2e.js
CHANGED
|
@@ -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
|
-
*
|
|
46
|
-
*
|
|
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
|
|
50
|
-
const
|
|
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
|
-
|
|
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 /
|
|
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(
|
|
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}
|
|
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 {
|
package/src/commands/index.js
CHANGED
|
@@ -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';
|