package.el does not have good support for this use case. You have to re-run
package-install-from-buffer every time you make an update to the package, and also
M-x find-function and friends will not jump to the source code, but rather the copy that
package.el puts in
~/.emacs.d/elpa. As a consequence, I would recommend not using
package.el for this use case (or at all, since it has a number of other problems).
Instead, you can try my package manager
straight.el, which is explicitly designed for this use case. With
straight.el, there is no "local installation" procedure. You just put your repository in
~/.emacs.d/straight/repos/<my-package-repo>, and then load the package in your init-file:
(straight-use-package '(<my-package> :local-repo "<my-package-repo>"))
Anytime you make a change to the source code, autoload generation and byte-compilation is redone (once you restart Emacs). Also, you're running directly from the Git repository, so
M-x find-function and friends will work as expected.
Finally, local and upstream versions are entirely interchangeable.
straight.el lets you make any local changes you want, and they are treated as just as sacred as upstream changes (although
straight.el does let you automatically [and interactively] revert your repositories to their upstream versions, if you wish). Thus, there is no concern with making any changes to your configuration if you want to do local development: just do local development. And if you have a private package that you publish, it's trivial: just push that package somewhere (and probably update the recipe in your init-file to point to that Git repository, so that
straight.el can re-clone it from a different machine).
package.el, there is no difference in the treatment of multi-file and single-file packages.
For verbose documentation about
straight.el, including comparisons to other package managers, see the README.