game-ci / game-ci/cli

Formalize the apps/plugins/packages dependency-direction rule

Open
#122 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
16
Forks
6
Avg merge
1h 51m
Merged PRs (30d)
167

Description

## The rule

From internal discussion, the intended layering for this monorepo:

```
/apps can rely on /packages
/plugins can rely on /packages
/packages can rely on other /packages (but only 1 way, never circular)

/apps can not rely on /plugins (plugins are self contained, and are loaded using
a mechanism, not direct dependency)
/plugins can not rely on /apps, if it needs to then that part has to be moved
into a /packages module
```

Nothing in the repo currently documents this explicitly (not in `AGENTS.md`, `CONTRIBUTING.md`, or `DEVELOPMENT.md`). This issue is to (a) record the rule somewhere real, and (b) track where the actual repo layout does/doesn't hold up against it, since it was never checked mechanically before.

## Current state (audited 2026-08-24)

The repo is two-tier today: `src/` (the `game-ci` CLI — the app) + `plugins/*` (two workspace packages: `@game-ci/orchestrator`, `@game-ci/unity-engine-core`). **There is no `/packages` tier at all.**

Checked against each rule literally:

- **`/apps` can rely on `/packages`** — N/A, no packages exist.
- **`/plugins` can rely on `/packages`** — N/A, same.
- **`/packages` can rely on other `/packages` (one-way)** — N/A, no packages tier.
- **`/apps` can not rely on `/plugins`** — was **violated**: `src/cli.ts` statically imported `orchestratorPlugin` directly from `plugins/orchestrator/src/cli-plugin/index.ts` (a relative path into a plugin's private internals) to make `orchestrate` work without requiring `--plugin @game-ci/orchestrator-plugin`. Fixed in #121 — orchestrator is now loaded through `PluginLoader.load('@game-ci/orchestrator/cli-plugin')` (its public package export), same mechanism as any other plugin; it's just always in the default load list. Two things had to be fixed to make that actually work, not just compile — see #121's description for the full writeup: (1) the root `package.json` never declared a dependency on `@game-ci/orchestrator` at all, so there was no workspace symlink to resolve the package name through; (2) `@game-ci/orchestrator`'s `exports` map only pointed at compiled `dist/`, which would have introduced a new, currently-unwired build-order dependency (orchestrator's own `tsc` build would need to run before the root CLI's `bun build`) — solved with a `"bun"` export condition pointing at TypeScript source directly, so bundling stays byte-identical to before (1756 modules, 10.52MB, verified with zero pre-built orchestrator `dist/` present).
- **`/plugins` can not rely on `/apps`** — **held**. Neither `orchestrator` nor `unity-engine-core` imports anything from `src/`.

## Open question: is "plugin" even the right classification for these two?

`@game-ci/orchestrator` and `@game-ci/unity-engine-core` don't depend on `src/` or on each other, so they technically satisfy the plugin rules as written. But they don't share a `/packages` tier for common needs either — each independently vendors its own copies of overlapping dependencies (`@actions/core`, `@actions/exec`, `nanoid`, `semver`, `yaml`, etc.) rather than depending on one shared module. That's not a rule violation (there's no cross-plugin reliance happening to route through `/packages`), but it's exactly the situation the `/packages` tier exists to solve once it's needed.

Separately: `@game-ci/orchestrator` ships its own standalone `bin` entry (`"game-ci": "./dist/cli.js"`) and can also be loaded as a fully out-of-process executable plugin via the `executable:`/CLI-protocol path (`CliProtocolPlugin`) — i.e. it has real independent-app characteristics, not just "extends the host app" characteristics. `@game-ci/unity-engine-core` has no `bin` entry and no standalone identity — it's closer to a pure library. Worth deciding explicitly whether "plugin" here means "a thing dynamically loaded into `game-ci` to extend it" (a *consumption mode*, which a package can have alongside also being runnable standalone) or a stricter category that excludes anything with independent app identity. Current lean: the two aren't mutually exclusive — a single workspace package can be both "an app" (has its own entrypoint, runs standalone) and "a plugin" (also exposes a `GameCIPlugin`-shaped export the host can dynamically load) — but this should be a deliberate call, not an accident of how things were built.

## Follow-ups this issue should track

- [ ] Document the apps/plugins/packages rule somewhere real (`AGENTS.md` or a new `ARCHITECTURE.md`)
- [ ] Decide the open classification question above and record the decision
- [ ] If/when orchestrator and unity-engine-core end up needing to share code, factor it into a real `/packages` tier rather than duplicating it further
- [ ] Consider a lightweight CI check (e.g. an import-boundary lint rule) that would have caught the `src/cli.ts` violation automatically, so this class of drift doesn't require a manual audit to notice again

Contributor guide

Open the contributing guide

Research direction

Read AGENTS.md, CONTRIBUTING.md, and DEVELOPMENT.md to choose where the dependency-direction rule belongs, then review src/cli.ts and the workspace layout alongside issue #121. Document the rule and the classification decision, record the current audit and follow-ups, and determine whether a lightweight CI boundary check can be specified without expanding the scope.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
cli, documentation, tooling
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.