@polymorphism-tech/morph-spec 8.47.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +51 -43
- package/README.md +39 -38
- package/bin/morph-spec.js +41 -113
- package/bin/validate.js +1 -147
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +86 -21
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-status.md +4 -4
- package/framework/docs/CLI.md +574 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +3 -17
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
- package/framework/hooks/claude-code/core/gate-guard.js +9 -78
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
- package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
- package/framework/hooks/claude-code/core/state-sync.js +15 -85
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
- package/framework/hooks/claude-code/core/validate-completion.js +37 -147
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
- package/framework/hooks/claude-code/core/worktree-context.js +15 -114
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +11 -76
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +168 -44
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +11 -43
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +59 -0
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +4 -0
- package/framework/hooks/shared/gate-question.js +12 -0
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +14 -72
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +99 -0
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +51 -247
- package/framework/hooks/shared/state-writer.js +9 -54
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- 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 +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +94 -152
- package/framework/standards/STANDARDS.json +109 -1219
- package/framework/standards/STANDARDS.meta.json +1319 -0
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/evals-with-cache.md +1 -2
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +3 -3
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +7 -7
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +15 -179
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +18 -105
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +17 -111
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +53 -307
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +6 -35
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +8 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +65 -72
- package/src/commands/project/status.js +28 -121
- package/src/commands/project/update.js +31 -223
- package/src/commands/project/worktree.js +109 -883
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +65 -120
- package/src/commands/state/approve.js +87 -229
- package/src/commands/state/archive.js +23 -139
- package/src/commands/state/auto-approval.js +65 -0
- package/src/commands/state/create.js +49 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +105 -494
- package/src/commands/state/gate-check.js +74 -183
- package/src/commands/state/gate-decision.js +8 -42
- package/src/commands/state/handoff.js +4 -31
- package/src/commands/state/list.js +21 -91
- package/src/commands/state/report-check.js +134 -338
- package/src/commands/state/retype.js +12 -28
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +79 -19
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +52 -216
- package/src/commands/validation/verify.js +45 -160
- package/src/core/paths/output-schema.js +9 -155
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +2 -30
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +32 -395
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +21 -0
- package/src/core/work-types.js +10 -45
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
- package/src/dashboard/app/components/CommandPalette.jsx +4 -11
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +3 -9
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
- package/src/dashboard/app/pages/Features.jsx +9 -16
- package/src/dashboard/app/pages/Guide.jsx +2 -3
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +14 -83
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +1 -7
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +92 -113
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +4 -7
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +14 -13
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +8 -59
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +37 -112
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +11 -122
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +2 -1
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +17 -17
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +2 -6
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +8 -33
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/mutation/dotnet-introspection.js +281 -0
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +47 -13
- package/src/lib/plan/artifact-vocabulary.js +40 -0
- package/src/lib/plan/checks.js +208 -0
- package/src/lib/plan/plan-parser.js +204 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +204 -0
- package/src/lib/risk-detector.js +131 -274
- package/src/lib/specs/adr-index.js +38 -85
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +31 -0
- package/src/lib/specs/gfm-table.js +11 -0
- package/src/lib/specs/revocations.js +31 -102
- package/src/lib/specs/screen-checklist.js +56 -132
- package/src/lib/stack/dotnet-build-ignores.js +43 -0
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +260 -0
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +37 -0
- package/src/lib/standards/sections.js +47 -0
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +23 -179
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -313
- package/src/lib/tasks/test-runner.js +107 -447
- package/src/lib/telemetry/metrics.js +13 -103
- package/src/lib/telemetry/telemetry-reader.js +13 -238
- package/src/lib/telemetry/verify-event.js +4 -34
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +24 -137
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +54 -156
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +152 -575
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -137
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +31 -126
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -138
- package/src/utils/agents-installer.js +92 -272
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -87
- package/src/utils/file-copier.js +32 -90
- package/src/utils/hooks-installer.js +26 -296
- package/src/utils/skills-installer.js +4 -102
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -57
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/commands/utils/index.js +0 -3
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/dotnet-test-introspection.js +0 -632
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,32 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Project Detector
|
|
3
|
-
*
|
|
4
|
-
* Scans a project directory for stack, architecture, UI library, and integrations.
|
|
5
|
-
* Used by `morph-spec init`, `mcp setup` and the test-node fallback (test-nodes.js).
|
|
6
|
-
*
|
|
7
|
-
* @module detectors/index
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
2
|
import { join } from 'path';
|
|
12
3
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* PR B) scans the SAME candidate list this detector already uses — one
|
|
18
|
-
* spelling of "where a frontend package might live", not two that drift.
|
|
19
|
-
*/
|
|
20
|
-
export const PACKAGE_LOCATIONS = ['.', 'src/frontend', 'src/web', 'frontend', 'apps/web', 'packages/app'];
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Detect project structure from filesystem evidence.
|
|
24
|
-
*
|
|
25
|
-
* @param {string} targetPath - Root path of the project
|
|
26
|
-
* @param {Object} [opts] - Options
|
|
27
|
-
* @param {boolean} [opts.generateStandards=false] - Whether to generate inferred markdown
|
|
28
|
-
* @returns {{ structure: { stack: string, architecture: string|null, uiLibrary: string|null, integrations: string[], hasVercel: boolean }, inferred: { markdown: string } }}
|
|
29
|
-
*/
|
|
4
|
+
import { PACKAGE_LOCATIONS } from '../../../framework/hooks/shared/frontend-locations.js';
|
|
5
|
+
|
|
6
|
+
export { PACKAGE_LOCATIONS };
|
|
7
|
+
|
|
30
8
|
export function detectProject(targetPath, opts = {}) {
|
|
31
9
|
const structure = {
|
|
32
10
|
stack: 'unknown',
|
|
@@ -37,8 +15,6 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
37
15
|
};
|
|
38
16
|
|
|
39
17
|
try {
|
|
40
|
-
// ── Package.json analysis ──────────────────────────────────────────────
|
|
41
|
-
// Collect deps from root and common monorepo subfolders
|
|
42
18
|
const allDeps = {};
|
|
43
19
|
let rootPkg = null;
|
|
44
20
|
let scripts = [];
|
|
@@ -50,17 +26,15 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
50
26
|
if (loc === '.') rootPkg = pkg;
|
|
51
27
|
Object.assign(allDeps, pkg.dependencies, pkg.devDependencies);
|
|
52
28
|
scripts.push(...Object.keys(pkg.scripts || {}));
|
|
53
|
-
// Track frontend path for config
|
|
54
29
|
if (loc !== '.' && (pkg.dependencies?.next || pkg.dependencies?.react || pkg.dependencies?.vue)) {
|
|
55
30
|
structure.frontendPath = loc;
|
|
56
31
|
}
|
|
57
|
-
} catch {
|
|
32
|
+
} catch {}
|
|
58
33
|
}
|
|
59
34
|
const depNames = Object.keys(allDeps);
|
|
60
35
|
|
|
61
36
|
if (depNames.length > 0) {
|
|
62
37
|
|
|
63
|
-
// Stack detection from deps
|
|
64
38
|
if (depNames.includes('next')) {
|
|
65
39
|
structure.stack = 'nextjs';
|
|
66
40
|
} else if (depNames.includes('react')) {
|
|
@@ -73,12 +47,10 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
73
47
|
structure.stack = 'nodejs';
|
|
74
48
|
}
|
|
75
49
|
|
|
76
|
-
// UI library detection
|
|
77
50
|
if (existsSync(join(targetPath, 'components.json')) || existsSync(join(targetPath, structure.frontendPath || '.', 'components.json')) || depNames.includes('@shadcn/ui')) {
|
|
78
51
|
structure.uiLibrary = 'shadcn';
|
|
79
52
|
}
|
|
80
53
|
|
|
81
|
-
// Integrations
|
|
82
54
|
if (depNames.includes('@neondatabase/serverless') || depNames.includes('postgres') || depNames.includes('pg')) {
|
|
83
55
|
structure.integrations.push('neon');
|
|
84
56
|
}
|
|
@@ -89,13 +61,11 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
89
61
|
structure.integrations.push('stripe');
|
|
90
62
|
}
|
|
91
63
|
|
|
92
|
-
// Vercel detection
|
|
93
64
|
if (depNames.includes('vercel') || scripts.includes('vercel')) {
|
|
94
65
|
structure.hasVercel = true;
|
|
95
66
|
}
|
|
96
67
|
}
|
|
97
68
|
|
|
98
|
-
// ── .csproj detection ──────────────────────────────────────────────────
|
|
99
69
|
let hasDotnet = false;
|
|
100
70
|
const csprojLocations = ['.', 'src', 'src/Api', 'src/Web', 'src/backend'];
|
|
101
71
|
|
|
@@ -110,7 +80,7 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
110
80
|
if (content.includes('Npgsql')) {
|
|
111
81
|
if (!structure.integrations.includes('neon')) structure.integrations.push('neon');
|
|
112
82
|
}
|
|
113
|
-
} catch {
|
|
83
|
+
} catch {}
|
|
114
84
|
}
|
|
115
85
|
|
|
116
86
|
for (const loc of csprojLocations) {
|
|
@@ -118,41 +88,34 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
118
88
|
const dir = join(targetPath, loc);
|
|
119
89
|
if (!existsSync(dir)) continue;
|
|
120
90
|
const entries = readdirSyncSafe(dir);
|
|
121
|
-
// Direct .csproj files
|
|
122
91
|
for (const e of entries.filter(f => f.endsWith('.csproj'))) {
|
|
123
92
|
checkCsproj(join(dir, e));
|
|
124
93
|
}
|
|
125
|
-
// One level deeper (e.g., src/backend/MyProject/*.csproj)
|
|
126
94
|
for (const sub of entries) {
|
|
127
95
|
const subDir = join(dir, sub);
|
|
128
96
|
for (const e of readdirSyncSafe(subDir).filter(f => f.endsWith('.csproj'))) {
|
|
129
97
|
checkCsproj(join(subDir, e));
|
|
130
98
|
}
|
|
131
99
|
}
|
|
132
|
-
} catch {
|
|
100
|
+
} catch {}
|
|
133
101
|
}
|
|
134
102
|
|
|
135
|
-
// Determine stack: monorepo (nextjs+dotnet), dotnet, or keep frontend stack
|
|
136
103
|
if (hasDotnet) {
|
|
137
104
|
if (structure.stack === 'nextjs' || structure.stack === 'react' || structure.stack === 'vue') {
|
|
138
|
-
// Monorepo: frontend + .NET backend
|
|
139
105
|
structure.stack = `${structure.stack}+dotnet`;
|
|
140
106
|
} else if (structure.stack === 'unknown') {
|
|
141
107
|
structure.stack = 'dotnet';
|
|
142
108
|
}
|
|
143
109
|
}
|
|
144
110
|
|
|
145
|
-
// ── vercel.json detection ──────────────────────────────────────────────
|
|
146
111
|
if (existsSync(join(targetPath, 'vercel.json'))) {
|
|
147
112
|
structure.hasVercel = true;
|
|
148
113
|
}
|
|
149
114
|
|
|
150
|
-
// ── tsconfig.json detection (fallback for stack) ────────────────────────
|
|
151
115
|
if (structure.stack === 'unknown' && existsSync(join(targetPath, 'tsconfig.json'))) {
|
|
152
116
|
structure.stack = 'nodejs';
|
|
153
117
|
}
|
|
154
118
|
|
|
155
|
-
// ── Architecture detection ─────────────────────────────────────────────
|
|
156
119
|
try {
|
|
157
120
|
const configPath = join(targetPath, '.morph/config/config.json');
|
|
158
121
|
if (existsSync(configPath)) {
|
|
@@ -161,16 +124,14 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
161
124
|
structure.architecture = config.architecture.style;
|
|
162
125
|
}
|
|
163
126
|
}
|
|
164
|
-
} catch {
|
|
127
|
+
} catch {}
|
|
165
128
|
|
|
166
|
-
// ── Docker detection ───────────────────────────────────────────────────
|
|
167
129
|
if (existsSync(join(targetPath, 'docker-compose.yml')) ||
|
|
168
130
|
existsSync(join(targetPath, 'docker-compose.yaml')) ||
|
|
169
131
|
existsSync(join(targetPath, 'Dockerfile'))) {
|
|
170
132
|
if (!structure.integrations.includes('docker')) structure.integrations.push('docker');
|
|
171
133
|
}
|
|
172
134
|
|
|
173
|
-
// ── Azure detection ────────────────────────────────────────────────────
|
|
174
135
|
const azureSignals = [
|
|
175
136
|
'.azure',
|
|
176
137
|
'azure-pipelines.yml',
|
|
@@ -181,7 +142,6 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
181
142
|
const p = join(targetPath, signal);
|
|
182
143
|
if (!existsSync(p)) return false;
|
|
183
144
|
if (signal === 'infra') {
|
|
184
|
-
// Check for .bicep files inside infra/
|
|
185
145
|
return readdirSyncSafe(p).some(f => f.endsWith('.bicep'));
|
|
186
146
|
}
|
|
187
147
|
return true;
|
|
@@ -190,7 +150,6 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
190
150
|
structure.integrations.push('azure');
|
|
191
151
|
}
|
|
192
152
|
} catch {
|
|
193
|
-
// Fail-safe — return defaults
|
|
194
153
|
}
|
|
195
154
|
|
|
196
155
|
return {
|
|
@@ -201,11 +160,6 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
201
160
|
};
|
|
202
161
|
}
|
|
203
162
|
|
|
204
|
-
/**
|
|
205
|
-
* Safe readdir that returns [] on error.
|
|
206
|
-
* @param {string} dir
|
|
207
|
-
* @returns {string[]}
|
|
208
|
-
*/
|
|
209
163
|
function readdirSyncSafe(dir) {
|
|
210
164
|
try {
|
|
211
165
|
return readdirSync(dir);
|
|
@@ -214,11 +168,6 @@ function readdirSyncSafe(dir) {
|
|
|
214
168
|
}
|
|
215
169
|
}
|
|
216
170
|
|
|
217
|
-
/**
|
|
218
|
-
* Generate a markdown summary from detected structure.
|
|
219
|
-
* @param {Object} structure
|
|
220
|
-
* @returns {string}
|
|
221
|
-
*/
|
|
222
171
|
function generateInferredMarkdown(structure) {
|
|
223
172
|
const lines = [`## Detected Stack: ${structure.stack}`];
|
|
224
173
|
if (structure.architecture) lines.push(`Architecture: ${structure.architecture}`);
|
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* the credentials its auth setup needs.
|
|
4
|
-
*
|
|
5
|
-
* This lives in ONE place because it has two callers that must never disagree:
|
|
6
|
-
* `morph-spec e2e up` (manual) and the `e2e` node inside `morph-spec verify`
|
|
7
|
-
* (the one that actually runs in the loop). When only the manual path built the
|
|
8
|
-
* port block, the isolation was real in the command nobody runs and absent in
|
|
9
|
-
* the one everybody runs — two worktrees would get distinct container names via
|
|
10
|
-
* COMPOSE_PROJECT_NAME and then collide on host port 3000 anyway, surfacing as
|
|
11
|
-
* "port is already allocated" and pausing a gate over an infrastructure clash
|
|
12
|
-
* rather than a defect.
|
|
13
|
-
*
|
|
14
|
-
* It sits in src/lib rather than framework/hooks/shared because it depends on
|
|
15
|
-
* env-allocator.js: hooks are installed standalone into target projects, where
|
|
16
|
-
* `src/` does not exist, so the dependency may only point this way.
|
|
17
|
-
*
|
|
18
|
-
* @module lib/e2e/compose-env
|
|
19
|
-
*/
|
|
1
|
+
// One place for `e2e up` and the `e2e` node of `verify`, which must never disagree on ports. Lives in
|
|
2
|
+
// src/lib, not hooks/shared, because it depends on env-allocator.js and hooks run without `src/`.
|
|
20
3
|
|
|
21
4
|
import { join } from 'path';
|
|
22
5
|
|
|
@@ -25,43 +8,11 @@ import chalk from 'chalk';
|
|
|
25
8
|
import { loadEnvFile } from '../../../framework/hooks/shared/e2e-check.js';
|
|
26
9
|
import { allocateEnvBlock, portLayout, resolveFleetConfig, resolvePortAliases } from '../worktree/env-allocator.js';
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* Port variables come from `portLayout()` in env-allocator.js — the SAME
|
|
32
|
-
* function that renders `.morph/worktree.env` — so a compose file and the
|
|
33
|
-
* worktree manifest describe the same world by construction (`PORT`,
|
|
34
|
-
* `API_PORT`/`ASPNETCORE_URLS`, `POSTGRES_PORT`, `COMPOSE_PROJECT_NAME`).
|
|
35
|
-
*
|
|
36
|
-
* `PLAYWRIGHT_BASE_URL` is derived here rather than left to the spec config —
|
|
37
|
-
* the suite has to be told where the app actually landed, otherwise a feature
|
|
38
|
-
* on port 4020 gets tested against a hardcoded localhost:3000 and fails with
|
|
39
|
-
* ECONNREFUSED that looks exactly like a broken app.
|
|
40
|
-
*
|
|
41
|
-
* The credentials env file loads AFTER the layout but cannot outrank it: a
|
|
42
|
-
* `.env.e2e` copied from a sibling worktree carries THAT worktree's block, and
|
|
43
|
-
* letting it win points this stack at the neighbour's postgres while every
|
|
44
|
-
* other surface still reports the allocated port. The protected keys are the
|
|
45
|
-
* ones actually assigned above — never a second literal list, which is exactly
|
|
46
|
-
* how an offset lands in one writer and not the other. A key that gets ignored
|
|
47
|
-
* is announced on stderr (never stdout: `morph-spec env` writes shell-eval
|
|
48
|
-
* output there), because swapping a silent override for a silent discard just
|
|
49
|
-
* moves the debugging session.
|
|
50
|
-
*
|
|
51
|
-
* Fail-open throughout: with no git dir (or a corrupt registry) the caller gets
|
|
52
|
-
* default ports, which only matters when two stacks run at once. With no layout
|
|
53
|
-
* applied nothing is filtered — there the env file is the only port source.
|
|
54
|
-
*
|
|
55
|
-
* @param {object} params
|
|
56
|
-
* @param {string} params.cwd - project root
|
|
57
|
-
* @param {object|null} [params.config] - .morph/config/config.json contents
|
|
58
|
-
* @param {string} params.featureName
|
|
59
|
-
* @param {string|null} [params.gitCommonDir]
|
|
60
|
-
* @returns {Record<string,string>}
|
|
61
|
-
*/
|
|
11
|
+
// Ports come from `portLayout()`, the same function that renders `.morph/worktree.env`. The credentials
|
|
12
|
+
// file loads after the layout but cannot outrank it: a `.env.e2e` copied from a sibling worktree carries
|
|
13
|
+
// that worktree's block. Ignored keys are announced on stderr; stdout carries `morph-spec env` output.
|
|
62
14
|
export function buildComposeEnv({ cwd, config, featureName, gitCommonDir }) {
|
|
63
15
|
const env = {};
|
|
64
|
-
/** @type {Set<string>|null} - null while no layout has been applied */
|
|
65
16
|
let layoutKeys = null;
|
|
66
17
|
|
|
67
18
|
if (gitCommonDir) {
|
|
@@ -97,18 +48,6 @@ export function buildComposeEnv({ cwd, config, featureName, gitCommonDir }) {
|
|
|
97
48
|
return env;
|
|
98
49
|
}
|
|
99
50
|
|
|
100
|
-
/**
|
|
101
|
-
* Tell the caller — `e2e up`, the `e2e` node of `verify`, `stack-teardown` and
|
|
102
|
-
* `morph-spec env` all reach this through `buildComposeEnv` — which keys of the
|
|
103
|
-
* credentials file were dropped and what won instead.
|
|
104
|
-
*
|
|
105
|
-
* stderr, always: `morph-spec env` prints `export K=v` on stdout for a
|
|
106
|
-
* shell to eval, and `verify` prints JSON there.
|
|
107
|
-
*
|
|
108
|
-
* @param {string} envFile
|
|
109
|
-
* @param {string[]} ignored
|
|
110
|
-
* @param {Record<string,string>} env - the layout that won
|
|
111
|
-
*/
|
|
112
51
|
function reportIgnoredLayoutKeys(envFile, ignored, env) {
|
|
113
52
|
const shown = ignored.map((key) => `${key}=${env[key]}`).join(', ');
|
|
114
53
|
console.warn(chalk.yellow(
|
|
@@ -1,27 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* `morph-spec verify` leaves the stack UP on purpose (cold-starting a
|
|
6
|
-
* container per task is what makes the loop unusable), so something has to be
|
|
7
|
-
* the one place that brings it down when the feature closes. That place is
|
|
8
|
-
* this module, shared by `finish` and `delete --with-worktree`.
|
|
9
|
-
*
|
|
10
|
-
* Three things the old root-side teardown got wrong, each pinned by a test:
|
|
11
|
-
* 1. It ran from the ROOT. A compose that exists only in the worktree was
|
|
12
|
-
* never found, and a bind mount resolved to the wrong checkout.
|
|
13
|
-
* 2. It passed a bare `COMPOSE_PROJECT_NAME`. Without the port block
|
|
14
|
-
* (`PORT`, `POSTGRES_PORT`, …) compose resolves a different config than
|
|
15
|
-
* the one that came up, and `down` misses the containers.
|
|
16
|
-
* 3. It never removed volumes or the stamp. Postgres data and a stamp saying
|
|
17
|
-
* "source unchanged since build" outlived the feature.
|
|
18
|
-
*
|
|
19
|
-
* Entirely fail-open: no compose, no docker, or a stubborn container are all
|
|
20
|
-
* non-events for the caller — a stack that refuses to come down must never
|
|
21
|
-
* fail a finish that already merged.
|
|
22
|
-
*
|
|
23
|
-
* @module lib/e2e/stack-teardown
|
|
24
|
-
*/
|
|
1
|
+
// `verify` leaves the stack up on purpose, so this is the one place that brings it down. Runs from the
|
|
2
|
+
// owning tree with the full port block, or compose resolves a different config and `down` misses it.
|
|
3
|
+
// Fail-open: a stack that refuses to come down must never fail a finish that already merged.
|
|
25
4
|
|
|
26
5
|
import { execFileSync } from 'child_process';
|
|
27
6
|
import { existsSync, readFileSync } from 'fs';
|
|
@@ -36,15 +15,6 @@ import {
|
|
|
36
15
|
} from '../../../framework/hooks/shared/e2e-check.js';
|
|
37
16
|
import { buildComposeEnv } from './compose-env.js';
|
|
38
17
|
|
|
39
|
-
/**
|
|
40
|
-
* @param {string} feature
|
|
41
|
-
* @param {object} params
|
|
42
|
-
* @param {string} params.root - primary root (absolute)
|
|
43
|
-
* @param {string|null} [params.worktreePath] - the feature's live worktree, if any
|
|
44
|
-
* @param {boolean} [params.volumes=true] - also remove named volumes (`down -v`)
|
|
45
|
-
* @param {Function} [params._exec] - injectable executor (testing)
|
|
46
|
-
* @returns {{ attempted: boolean, cwd: string, status: 'pass'|'fail'|'skip', stampDeleted: boolean, output?: string }}
|
|
47
|
-
*/
|
|
48
18
|
export function tearDownFeatureStack(feature, { root, worktreePath = null, volumes = true, _exec = defaultExec } = {}) {
|
|
49
19
|
const cwd = worktreePath && existsSync(worktreePath) ? worktreePath : root;
|
|
50
20
|
const projectName = composeProjectName(feature);
|
|
@@ -53,8 +23,7 @@ export function tearDownFeatureStack(feature, { root, worktreePath = null, volum
|
|
|
53
23
|
try {
|
|
54
24
|
const config = readConfig(cwd) ?? readConfig(root);
|
|
55
25
|
|
|
56
|
-
// The owning tree's compose first; the root's copy is the fallback for a
|
|
57
|
-
// worktree already half torn down (versioned file, so equivalent).
|
|
26
|
+
// The owning tree's compose first; the root's copy is the fallback for a worktree already half torn down.
|
|
58
27
|
let composeCwd = cwd;
|
|
59
28
|
let composeFile = resolveComposeFile(cwd, config);
|
|
60
29
|
if (!composeFile && cwd !== root) {
|
|
@@ -63,8 +32,7 @@ export function tearDownFeatureStack(feature, { root, worktreePath = null, volum
|
|
|
63
32
|
}
|
|
64
33
|
|
|
65
34
|
if (!composeFile) {
|
|
66
|
-
// Nothing to bring down
|
|
67
|
-
// `up` that a stack it cannot see is still fresh.
|
|
35
|
+
// Nothing to bring down, but a stamp left behind would tell the next `up` a stack it cannot see is fresh.
|
|
68
36
|
return { attempted: false, cwd, status: 'skip', stampDeleted: deleteStamp(gitCommonDir, projectName) };
|
|
69
37
|
}
|
|
70
38
|
|
|
@@ -82,13 +50,11 @@ export function tearDownFeatureStack(feature, { root, worktreePath = null, volum
|
|
|
82
50
|
...(result.status === 'fail' && result.output ? { output: result.output } : {}),
|
|
83
51
|
};
|
|
84
52
|
} catch {
|
|
85
|
-
//
|
|
86
|
-
// outlive the stack it described.
|
|
53
|
+
// The stamp must not outlive the stack it described, even when the teardown itself blew up.
|
|
87
54
|
return { attempted: false, cwd, status: 'fail', stampDeleted: deleteStamp(gitCommonDir, projectName) };
|
|
88
55
|
}
|
|
89
56
|
}
|
|
90
57
|
|
|
91
|
-
/** BOM-tolerant config read; null when absent or malformed. */
|
|
92
58
|
function readConfig(dir) {
|
|
93
59
|
try {
|
|
94
60
|
const path = join(dir, '.morph', 'config', 'config.json');
|