@polymorphism-tech/morph-spec 8.32.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 +4 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +26 -2
- 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 +71 -68
- 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 +218 -39
- 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 +37 -4
- 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
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stub: validation dispatch was driven by tier-4 agents in v4.
|
|
3
|
-
* In v5, validation is handled by morph-eval skill invoked directly
|
|
4
|
-
* by the Evaluator role. This function is kept for call-site compatibility.
|
|
5
|
-
*
|
|
6
|
-
* @module emit-validator-dispatch
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* No-op in v5. Validation dispatch is now handled by morph-eval skill.
|
|
11
|
-
*/
|
|
12
|
-
export async function emitValidatorDispatch(_featureName, _phase, _cwd) {
|
|
13
|
-
// v5: evaluation driven by morph-eval skill, not CLI dispatch
|
|
14
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Stub: validation dispatch was driven by tier-4 agents in v4.
|
|
3
|
+
* In v5, validation is handled by morph-eval skill invoked directly
|
|
4
|
+
* by the Evaluator role. This function is kept for call-site compatibility.
|
|
5
|
+
*
|
|
6
|
+
* @module emit-validator-dispatch
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* No-op in v5. Validation dispatch is now handled by morph-eval skill.
|
|
11
|
+
*/
|
|
12
|
+
export async function emitValidatorDispatch(_featureName, _phase, _cwd) {
|
|
13
|
+
// v5: evaluation driven by morph-eval skill, not CLI dispatch
|
|
14
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared issue counting utility for validators
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @param {Array} issues - Array of validation issues with type property
|
|
6
|
-
* @returns {{errors: number, warnings: number, infos: number}} Count by type
|
|
7
|
-
*/
|
|
8
|
-
export function countIssues(issues) {
|
|
9
|
-
return {
|
|
10
|
-
errors: issues.filter((i) => i.type === 'error').length,
|
|
11
|
-
warnings: issues.filter((i) => i.type === 'warning').length,
|
|
12
|
-
infos: issues.filter((i) => i.type === 'info').length,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared issue counting utility for validators
|
|
3
|
+
*
|
|
4
|
+
*
|
|
5
|
+
* @param {Array} issues - Array of validation issues with type property
|
|
6
|
+
* @returns {{errors: number, warnings: number, infos: number}} Count by type
|
|
7
|
+
*/
|
|
8
|
+
export function countIssues(issues) {
|
|
9
|
+
return {
|
|
10
|
+
errors: issues.filter((i) => i.type === 'error').length,
|
|
11
|
+
warnings: issues.filter((i) => i.type === 'warning').length,
|
|
12
|
+
infos: issues.filter((i) => i.type === 'info').length,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -48,7 +48,8 @@ const OUTPUT_TAIL_LINES = 20;
|
|
|
48
48
|
* @param {string|null} [taskId] - Task ID for task-scoped verify, or null/undefined for feature scope
|
|
49
49
|
* @param {Object} [options]
|
|
50
50
|
* @param {string} [options.rubric] - Force a rubric id (highest precedence)
|
|
51
|
-
* @param {number} [options.timeout] -
|
|
51
|
+
* @param {number} [options.timeout] - Timeout in ms for the build AND tests nodes
|
|
52
|
+
* (default: 120000 for build; for tests, see resolveTestTimeout())
|
|
52
53
|
* @param {boolean} [options.skipTests] - Skip the tests node
|
|
53
54
|
* @param {boolean} [options.skipBuild] - Skip the build node
|
|
54
55
|
* @param {boolean} [options.skipE2e] - Skip the e2e node
|
|
@@ -86,7 +87,18 @@ export async function runVerify(projectPath, featureName, taskId, options = {})
|
|
|
86
87
|
appendBuildFeedback(feedback, nodes.build);
|
|
87
88
|
|
|
88
89
|
// 2. tests — skipped entirely (no invocation) when build already failed.
|
|
89
|
-
|
|
90
|
+
// The tests node gets the SAME --timeout the user typed, but only when they
|
|
91
|
+
// typed one: `timeout` above is defaulted for the build, and passing that
|
|
92
|
+
// default down would silently outrank config.project.testTimeoutMs and the
|
|
93
|
+
// per-project scaling (see resolveTestTimeout()).
|
|
94
|
+
const explicitTimeout = Number.isFinite(options.timeout) ? options.timeout : undefined;
|
|
95
|
+
nodes.tests = await runTestsNode({
|
|
96
|
+
projectPath,
|
|
97
|
+
skipTests,
|
|
98
|
+
buildNode: nodes.build,
|
|
99
|
+
timeout: explicitTimeout,
|
|
100
|
+
_runTestSuite,
|
|
101
|
+
});
|
|
90
102
|
appendTestsFeedback(feedback, nodes.tests);
|
|
91
103
|
|
|
92
104
|
// 3. validators — ALWAYS runs, even on a red build (free static feedback).
|
|
@@ -174,7 +186,7 @@ function appendBuildFeedback(feedback, buildNode) {
|
|
|
174
186
|
// Tests node
|
|
175
187
|
// ============================================================================
|
|
176
188
|
|
|
177
|
-
async function runTestsNode({ projectPath, skipTests, buildNode, _runTestSuite }) {
|
|
189
|
+
async function runTestsNode({ projectPath, skipTests, buildNode, timeout, _runTestSuite }) {
|
|
178
190
|
const command = resolveTestCommand(projectPath);
|
|
179
191
|
|
|
180
192
|
if (skipTests) {
|
|
@@ -192,7 +204,7 @@ async function runTestsNode({ projectPath, skipTests, buildNode, _runTestSuite }
|
|
|
192
204
|
}
|
|
193
205
|
|
|
194
206
|
const start = Date.now();
|
|
195
|
-
const result = await _runTestSuite(projectPath);
|
|
207
|
+
const result = await _runTestSuite(projectPath, { timeout });
|
|
196
208
|
const durationMs = Date.now() - start;
|
|
197
209
|
|
|
198
210
|
if (result.skipped) {
|
|
@@ -205,6 +217,20 @@ async function runTestsNode({ projectPath, skipTests, buildNode, _runTestSuite }
|
|
|
205
217
|
};
|
|
206
218
|
}
|
|
207
219
|
|
|
220
|
+
// A timeout has no verdict to report: the tail of a truncated log reads like
|
|
221
|
+
// a passing run cut mid-sentence. Lead with what actually happened.
|
|
222
|
+
if (result.timedOut) {
|
|
223
|
+
const tail = tailLines(result.output, OUTPUT_TAIL_LINES);
|
|
224
|
+
return {
|
|
225
|
+
status: 'fail',
|
|
226
|
+
command,
|
|
227
|
+
durationMs,
|
|
228
|
+
timedOut: true,
|
|
229
|
+
reason: result.message,
|
|
230
|
+
output: tail ? `${result.message}\n${tail}` : result.message,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
208
234
|
return {
|
|
209
235
|
status: result.passed ? 'pass' : 'fail',
|
|
210
236
|
command,
|
|
@@ -328,10 +354,17 @@ async function runE2eNode({ projectPath, featureName, taskId, skipE2e, freshE2e,
|
|
|
328
354
|
durationMs,
|
|
329
355
|
reason: result.reason ?? null,
|
|
330
356
|
output: result.output || '',
|
|
357
|
+
// Non-fatal findings (e.g. a compose with fixed ports at the ROOT, where
|
|
358
|
+
// nothing collides yet). Surfaced so the same compose does not fail
|
|
359
|
+
// unannounced the day the feature moves into a worktree.
|
|
360
|
+
warnings: Array.isArray(result.warnings) ? result.warnings : [],
|
|
331
361
|
};
|
|
332
362
|
}
|
|
333
363
|
|
|
334
364
|
function appendE2eFeedback(feedback, e2eNode) {
|
|
365
|
+
for (const warning of e2eNode.warnings || []) {
|
|
366
|
+
feedback.push(`[e2e] warning: ${warning}`);
|
|
367
|
+
}
|
|
335
368
|
if (e2eNode.status !== 'fail') return;
|
|
336
369
|
if (e2eNode.reason) feedback.push(`[e2e] ${e2eNode.reason}`);
|
|
337
370
|
for (const line of linesOf(e2eNode.output)) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dirs.js — the ONE place the worktree container directory is named.
|
|
3
|
+
*
|
|
4
|
+
* Every reader that resolves worktrees BY CONVENTION (test discovery, graph
|
|
5
|
+
* ignore list, the installed .gitignore, the architecture scanner, the
|
|
6
|
+
* dashboard scanner) imports from here. Duplicating the string is how the
|
|
7
|
+
* C1 bug comes back: a reader keeps the old name and silently goes blind.
|
|
8
|
+
*
|
|
9
|
+
* Readers that go through `git worktree list` (fleet, finish, worktree list)
|
|
10
|
+
* never depended on this — git records absolute paths.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The directory new worktrees are created under, relative to the primary root.
|
|
15
|
+
*
|
|
16
|
+
* NO leading dot, deliberately (C1). Jest escapes the `.` when it interpolates
|
|
17
|
+
* an absolute `rootDir` into the `testMatch` glob, so `<root>/.worktrees/...`
|
|
18
|
+
* stops matching and an entire frontend suite reports **0 tests without an
|
|
19
|
+
* error** — the suite "passes" having tested nothing. The dot bought nothing:
|
|
20
|
+
* the directory is gitignored either way.
|
|
21
|
+
*/
|
|
22
|
+
export const WORKTREES_DIR = 'worktrees';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The pre-C1 directory name. New worktrees never land here, but every reader
|
|
26
|
+
* that resolves worktrees BY CONVENTION must still accept it, so a machine
|
|
27
|
+
* that already had `.worktrees/{f}` keeps working with no manual migration.
|
|
28
|
+
*/
|
|
29
|
+
export const LEGACY_WORKTREES_DIR = '.worktrees';
|
|
30
|
+
|
|
31
|
+
/** Both names, new first — the read order for convention-based lookups. */
|
|
32
|
+
export const WORKTREE_DIR_NAMES = [WORKTREES_DIR, LEGACY_WORKTREES_DIR];
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs';
|
|
23
23
|
import { join } from 'path';
|
|
24
|
+
// src/ → hooks/shared/ is the allowed direction (hooks are installed standalone).
|
|
25
|
+
import { composeProjectName } from '../../../framework/hooks/shared/e2e-check.js';
|
|
24
26
|
|
|
25
27
|
const DEFAULT_BASE_PORT = 4000;
|
|
26
28
|
const DEFAULT_BLOCK_SIZE = 10;
|
|
@@ -124,9 +126,46 @@ export function releaseEnvBlock(feature, { gitCommonDir }) {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
129
|
+
* Fixed offsets inside a block. The block has 10 ports; four are named, the
|
|
130
|
+
* rest (+4..+9) are the project's to use. A new offset added here MUST also be
|
|
131
|
+
* published by the compose template (`${VAR:-default}:container`), or it is a
|
|
132
|
+
* port nobody listens on.
|
|
133
|
+
*/
|
|
134
|
+
export const PORT_OFFSETS = Object.freeze({ web: 0, api: 1, postgres: 2, redis: 3 });
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The ONE port-layout contract. Two writers derive from it — the
|
|
138
|
+
* `.morph/worktree.env` manifest (`buildEnvFileContent`) and the env handed to
|
|
139
|
+
* `docker compose` (`src/lib/e2e/compose-env.js`) — so a worktree's file and
|
|
140
|
+
* its running stack can never describe different worlds.
|
|
141
|
+
*
|
|
142
|
+
* `COMPOSE_PROJECT_NAME` is part of the layout on purpose: a hand-run
|
|
143
|
+
* `docker compose up` inside the worktree must land in the isolated project,
|
|
144
|
+
* not in the root's. Every value is a string (env-safe); key order is stable.
|
|
145
|
+
*
|
|
146
|
+
* @param {{ base: number, range: [number, number] }} block
|
|
147
|
+
* @param {{ stack?: string, feature?: string }} [opts]
|
|
148
|
+
* @returns {Record<string,string>}
|
|
149
|
+
*/
|
|
150
|
+
export function portLayout(block, { stack = '', feature = '' } = {}) {
|
|
151
|
+
const env = {};
|
|
152
|
+
if (feature) env.COMPOSE_PROJECT_NAME = composeProjectName(feature);
|
|
153
|
+
env.MORPH_PORT_BASE = String(block.base);
|
|
154
|
+
env.MORPH_PORT_RANGE = `${block.range[0]}-${block.range[1]}`;
|
|
155
|
+
env.PORT = String(block.base + PORT_OFFSETS.web);
|
|
156
|
+
env.API_PORT = String(block.base + PORT_OFFSETS.api);
|
|
157
|
+
env.POSTGRES_PORT = String(block.base + PORT_OFFSETS.postgres);
|
|
158
|
+
env.REDIS_PORT = String(block.base + PORT_OFFSETS.redis);
|
|
159
|
+
if (/\bdotnet\b/i.test(String(stack))) {
|
|
160
|
+
env.ASPNETCORE_URLS = `http://localhost:${block.base + PORT_OFFSETS.api}`;
|
|
161
|
+
}
|
|
162
|
+
return env;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Render the `.morph/worktree.env` contents for a block: a header, the
|
|
167
|
+
* feature, and exactly the `portLayout()` pairs — nothing else, so the file
|
|
168
|
+
* cannot drift from what compose receives.
|
|
130
169
|
*
|
|
131
170
|
* @param {string} feature
|
|
132
171
|
* @param {{ base: number, range: [number, number] }} block
|
|
@@ -135,15 +174,10 @@ export function releaseEnvBlock(feature, { gitCommonDir }) {
|
|
|
135
174
|
*/
|
|
136
175
|
export function buildEnvFileContent(feature, block, stack = '') {
|
|
137
176
|
const lines = [
|
|
138
|
-
`# Managed by morph-spec —
|
|
139
|
-
'# Derivado
|
|
177
|
+
`# Managed by morph-spec — ambiente dedicado desta worktree (feature: ${feature}).`,
|
|
178
|
+
'# Derivado, git-invisível, regenerado por `worktree setup|link`; nunca editar nem commitar.',
|
|
140
179
|
`MORPH_FEATURE=${feature}`,
|
|
141
|
-
|
|
142
|
-
`MORPH_PORT_RANGE=${block.range[0]}-${block.range[1]}`,
|
|
143
|
-
`PORT=${block.base}`,
|
|
180
|
+
...Object.entries(portLayout(block, { stack, feature })).map(([k, v]) => `${k}=${v}`),
|
|
144
181
|
];
|
|
145
|
-
if (/\bdotnet\b/i.test(String(stack))) {
|
|
146
|
-
lines.push(`ASPNETCORE_URLS=http://localhost:${block.base + 1}`);
|
|
147
|
-
}
|
|
148
182
|
return `${lines.join('\n')}\n`;
|
|
149
183
|
}
|
|
@@ -247,9 +247,47 @@ export function auditWorktrees(cwd = process.cwd()) {
|
|
|
247
247
|
|
|
248
248
|
const feature = entry.branch?.match(/^refs\/heads\/morph\/(.+)$/)?.[1] || null;
|
|
249
249
|
if (!feature) {
|
|
250
|
-
// Only worth reporting when it sits in morph's own worktree container —
|
|
251
|
-
// a worktree the user keeps elsewhere is none of morph's business.
|
|
252
250
|
const inContainer = WORKTREE_DIR_NAMES.some((d) => path.startsWith(`${rootNorm}/${d}/`));
|
|
251
|
+
const task = entry.branch?.match(/^refs\/heads\/morph-task\/(.+)$/)?.[1] || null;
|
|
252
|
+
|
|
253
|
+
if (!inContainer) {
|
|
254
|
+
// A worktree OUTSIDE morph's container was created behind morph's back
|
|
255
|
+
// (a native Claude Code run before the WorktreeCreate hook, a raw
|
|
256
|
+
// `git worktree add ../x`, another skill). It has no junctions, no port
|
|
257
|
+
// block, and nothing morph runs there — the exact shape that let a
|
|
258
|
+
// root's `.claude/` be emptied. Reported as `warn` (never a failure,
|
|
259
|
+
// NEVER pruned): the work inside may be the only copy.
|
|
260
|
+
const work = unintegratedWork(root, entry.branch, prIndex);
|
|
261
|
+
issues.push({
|
|
262
|
+
type: 'out-of-container',
|
|
263
|
+
severity: 'warn',
|
|
264
|
+
path,
|
|
265
|
+
detail: `${path} é um worktree fora de worktrees/ (${entry.branch || 'HEAD destacado'}) — criado sem o morph: sem junctions, sem portas, invisível ao \`fleet\`${work.unintegrated ? `; carrega trabalho NÃO integrado (${work.evidence})` : ''}`,
|
|
266
|
+
fix: work.unintegrated
|
|
267
|
+
? `integre a branch primeiro; depois \`morph-spec worktree remove --target "${path}"\`. Novos worktrees: \`morph-spec worktree setup <f>\` ou \`worktree provision <n>\``
|
|
268
|
+
: `morph-spec worktree remove --target "${path}" — e crie os próximos por \`morph-spec worktree setup <f>\` / \`worktree provision <n>\``,
|
|
269
|
+
});
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (task) {
|
|
274
|
+
// `morph-task/*`: a task-isolation tree provisioned by the WorktreeCreate
|
|
275
|
+
// hook. Clean → nothing to say (the orchestrator removes it after the
|
|
276
|
+
// merge). With commits the default does not have → report, never
|
|
277
|
+
// suggest removal.
|
|
278
|
+
const work = unintegratedWork(root, entry.branch, prIndex);
|
|
279
|
+
if (work.unintegrated) {
|
|
280
|
+
issues.push({
|
|
281
|
+
type: 'unintegrated-work',
|
|
282
|
+
severity: 'info',
|
|
283
|
+
path,
|
|
284
|
+
detail: `${path} é um worktree de isolamento de task (${entry.branch}) com trabalho NÃO integrado (${work.evidence})`,
|
|
285
|
+
fix: `integre a branch (\`git merge --no-ff morph-task/${task}\` no orquestrador) e depois \`morph-spec worktree remove --target "${path}"\``,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
253
291
|
if (inContainer) {
|
|
254
292
|
// Same hazard as `no-feature` below, and it needs the same guard:
|
|
255
293
|
// "invisible to fleet" says nothing about whether the WORK is safe
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Map an agents.json ID to the Claude Code subagent_type.
|
|
3
|
-
*
|
|
4
|
-
* @param {string} id - Agent ID from agents.json (e.g., "nextjs-expert")
|
|
5
|
-
* @param {number} [tier] - Agent tier (1-4). Defaults to non-validator.
|
|
6
|
-
* @returns {string} subagent_type matching .claude/agents/ filename stem
|
|
7
|
-
*/
|
|
8
|
-
export function agentIdToSubagentType(id, tier) {
|
|
9
|
-
const prefix = tier === 4 ? 'morph-validator-' : 'morph-';
|
|
10
|
-
return `${prefix}${id}`;
|
|
11
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Map an agents.json ID to the Claude Code subagent_type.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} id - Agent ID from agents.json (e.g., "nextjs-expert")
|
|
5
|
+
* @param {number} [tier] - Agent tier (1-4). Defaults to non-validator.
|
|
6
|
+
* @returns {string} subagent_type matching .claude/agents/ filename stem
|
|
7
|
+
*/
|
|
8
|
+
export function agentIdToSubagentType(id, tier) {
|
|
9
|
+
const prefix = tier === 4 ? 'morph-validator-' : 'morph-';
|
|
10
|
+
return `${prefix}${id}`;
|
|
11
|
+
}
|