Adopt standard .plane/ root directory convention for Project-as-Code (Plane Compose / CLI)

(written with help of AI)
First off, huge appreciation to the Plane team for building “Project-as-Code” (Plane Compose / CLI). Having issue tracking, cycles, pages, and workflows version-controlled alongside code is an incredible direction for developer-centric project management.

I wanted to open a discussion and feature proposal regarding the directory structure created by the Plane CLI when cloning or initializing a project inside an existing repository.

Background & Context

About 7 months ago, I had a brief chat with one of the Plane developers and shared initial feedback regarding this directory convention. They kindly mentioned they would pass the thought along to the core team.
I’m bringing it to the community forum now simply to ensure the feedback isn’t lost, to see if other teams feel the same friction, and to offer concrete suggestions on how we could align with standard developer tooling conventions.

The Problem

When running `plane clone` or initializing a project within the root of an existing repository, the CLI currently creates a top-level directory named after the project key/identifier (for example, in our case, an `event/` folder for project key `EVENT`).

Inside this project-named folder, we get:
```text
event/
├── .gitignore
├── .plane/ (internal CLI state: state.json, state.lock)
├── plane.yaml
├── workspace.yaml
├── schema/
└── work/

While this works well if Plane is treated as an isolated standalone repository, it causes significant friction when embedding Project-as-Code into an existing application or monorepo:

  1. Naming Collisions with Domain/Source Code:
    Project keys often match the domain or product name (e.g., event, billing, core, api). Having a folder named event/ at the repository root directly clashes with source code folders, microservice boundaries, or domain libraries.
  2. Root Clutter:
    It adds an un-namespaced, visible top-level folder to the repository root that looks like application code rather than tooling configuration.
  3. Divergence from Ecosystem Conventions:
    Modern developer tooling unanimously follows the dot-folder convention (.<tool>/) at the repository root to encapsulate project-level tool metadata and configurations:
    • .github/ (GitHub Actions, templates)
    • .gitlab/ (GitLab CI/CD)
    • .vscode/ or .idea/ (IDE settings)
    • .claude/, .cursor/, .omo/ (AI agent configs & rules)
    • .devcontainer/, .husky/, .terraform/

Proposed Solution

Encapsulate all Plane Project-as-Code files inside a dedicated dot-directory at the repository root: .plane/.

1. Ideal Structure (Single Project):

When cloning or initializing in the root of an existing project:

my-repo/
├── .plane/
│   ├── plane.yaml
│   ├── workspace.yaml
│   ├── schema/
│   ├── work/
│   └── cache/ (or internal state)
├── src/
├── tests/
└── README.md

2. Monorepo / Multi-Project Support:

If a repository links to multiple Plane projects:

my-repo/
└── .plane/
    ├── workspace.yaml
    └── projects/
        ├── event/
        │   ├── plane.yaml
        │   └── ...
        └── billing/
            ├── plane.yaml
            └── ...

3. CLI Ergonomics:

At minimum, even if the default behavior is preserved for backwards compatibility, providing an explicit target path or flag would solve this immediately:

# Clone directly into .plane in current directory
plane clone <PROJECT_KEY> --path .plane
# or
plane init --dir .plane

Why this matters

Embracing the .plane/ convention makes Plane feel like a native citizen in modern codebases. Developers and teams can commit their Project-as-Code setup without interfering with existing build systems, file trees, or package layouts.

Hello @amirakrari

Really appreciate you bringing this back – sounds like the note from that hallway
conversation 7 months ago never made it into anything trackable, which is exactly the
kind of thing that happens to feedback that only lives in a conversation. Glad it’s
written up properly here now.

On the dot-folder convention

Most of the examples you listed – .github/, .devcontainer/, .husky/, .vscode/ –
actually hold content people author by hand and review in PRs (workflow YAML, hook
scripts, devcontainer configs), so “dot-folder” clearly doesn’t mean “hidden internal
state only” across the ecosystem generally. Fair to say the pattern is really “this is
the tool’s namespace,” not a statement about what’s allowed to live inside it – so we’re
not going to argue the convention itself doesn’t apply here.

Where we’d add a data point of our own: .git/ and .terraform/ specifically (not on
your list, but worth looking at too) follow a stricter version of it – the dot-folder
there holds only derived/internal state (.git/objects, .terraform’s provider
binaries and module cache), and the content a person actually authors – your source
tree, your .tf files – stays in plain view at the root, never inside it. Plane Compose
already mirrors that narrower pattern one level down today: <project>/.plane/ holds
internal state (state.json, the lock file), while plane.yaml/schema//work/ – the
stuff you actually edit and review – sit visibly beside it.

The practical reason we’d lean toward keeping it that way isn’t really a purity argument
about what dot-folders are “for” – plenty of tools put real content in them, as your own
list shows. It’s that dot-directories commonly get treated as hidden-by-default by
tooling that walks a repo: ls without -a, some editor sidebars, some find/linter
invocations. That’s a fine tradeoff for a CI workflow file you configure once and rarely
open. It’s more friction for schema//work/, which is meant to be routinely browsed
and edited by the whole team, closer to source than to one-time config. So the caution
is about discoverability for files people touch often, not a claim that dot-folders are
off-limits for real content in general.

The actual problem is real – but I think it splits into two different setups that want two different answers

event/ colliding with a domain folder called event/ isn’t really about event/
being too visible – it’s about it having no namespace of its own at the repo root.
That’s a different problem than the one dot-folders solve. But “give it a namespace”
means something different depending on what kind of repo you’re actually embedding into,
and your post reads like it could be either:

If this is one repo per project (which is what your concrete example – one event/
folder for project key EVENT – sounds like): the fix is just naming the one folder
something that doesn’t collide, nothing more. plane clone EVENT --directory <name>
already does this today – rename it to plane-event/, .event-plane/, whatever avoids
the clash in your tree, no design work needed on our end. (If you do pick something
dot-prefixed, worth noting given the section above: it’s not hiding anything the way
.git hides its internals – plane.yaml/schema//work/ are still sitting right there
inside it, just under a renamed folder. There’s also a rough edge if you land on .plane
specifically – the internal state folder is also named .plane/ inside whatever you
pick, so that exact combination nests as .plane/.plane/ – we’ll clean that up
regardless.) What this setup should not do is pull in workspace-level config (via
ws clone) alongside it – if every project repo carries its own independent copy of the
workspace schema, edited and pulled on its own schedule, you get config drift across
repos instead of the collision problem you have now. One project, one folder, no
workspace nesting.

If this is a true monorepo – one repo that’s meant to hold every project under a
workspace, code included – namespacing under a workspace folder makes more sense, and
this is where something like ws clone nesting projects underneath it earns its keep:

my-repo/
├── acme-workspace/         ← `plane ws clone acme-workspace`
│   ├── schema/               (workspace-level config, one copy)
│   └── projects/
│       ├── event/            ← `plane clone EVENT`, nested here instead of at root
│       └── billing/          ← same for another project
├── apps/
│   └── event/                 ← your actual "event" domain code -- no longer collides
└── README.md

The projects/ layer isn’t just tidiness – anywhere our own code walks a workspace
folder looking for schema/*.yaml, a project folder sitting as a direct sibling of
schema/ risks getting swept into that scan and having its YAML misread as workspace
config. Keeping projects in their own subtree avoids that ambiguity outright instead of
relying on filename conventions.

This model has a real limit worth naming rather than glossing over: it only holds until
any one project in that workspace lives outside this repo. At that point that project
either doesn’t get a local clone here at all, or gets its own standalone plane clone
elsewhere – which drops it back into the single-project-repo case above, drift risk and
all. We think that’s an acceptable edge rather than something to design around – not
every project in a workspace has to be tracked the same way.

If this direction goes anywhere, one ergonomics question it raises: should
plane clone EVENT in a repo with no workspace folder yet auto-run the equivalent of
ws clone first and nest itself inside, rather than making that a separate manual step?
Leaning toward yes for the monorepo case specifically, but this is very much still an
open question, not a decision.

To be clear on where this stands: the monorepo/nesting piece is a direction we’re
thinking about, not a plan with a date. The single-project-repo fix is closer – mostly
already possible via --directory, and plane init/plane ws clone not having the same
flag yet is a real, fixable gap independent of anything above.