> ## Documentation Index
> Fetch the complete documentation index at: https://libops-renovate-github-com-libops-sitectl-isle-0-x.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Development

> Local development workflow for sitectl and its plugins, including Go workspaces, linting, testing, and integration tests.

## Local install

For local development, install all binaries into a directory on your `$PATH` so core sitectl can discover and invoke the plugin binaries. From the core repo:

```bash theme={null}
make install
```

This builds and installs `sitectl`, then changes into `../sitectl-isle` and `../sitectl-drupal` and runs `make install` in each. The plugin `install` targets run `make work` before building so they use the local sitectl checkout.

If you're only working on a single plugin:

```bash theme={null}
cd ../sitectl-isle && make install
cd ../sitectl-drupal && make install
```

## Application plugin template

Use [`sitectl-app-tmpl`](https://github.com/libops/sitectl-app-tmpl) when starting a new application plugin for a Compose-backed app that uses shared services such as Traefik, MariaDB, or Solr. The template includes the standard SDK entrypoint, Compose create metadata, project discovery, an app `exec` helper, debug/validate/healthcheck runners, GitHub Actions workflows, GoReleaser config, Makefile targets, and a local `go.work` helper.

After creating a repo from the template, replace the app constants, module path, binary name, Compose template repository, project discovery rules, and app-specific commands. Core `sitectl compose` owns build, init, up, down, logs, status, and rollout behavior; core service commands own shared MariaDB, Solr, Traefik, Valkey, and Memcached operations. Keep local workspace wiring in `go.work`; do not add sibling-module `replace` directives to `go.mod`.

See [Application plugin template](/contributing/app-template) for the template checklist.

## Developing against a local sitectl checkout

Plugins import sitectl as a Go module dependency. When you need to test plugin changes against an unreleased version of sitectl, use Go workspaces instead of `replace` directives:

```bash theme={null}
make work
```

This runs `scripts/use-go-work.sh`, which creates a `go.work` file that points the plugin at `../sitectl`. The `go.work` file is gitignored — it's local only and does not affect CI or releases.

<Warning>
  Never add a `replace` directive to `go.mod` for local development. Use `make work` instead. Replace directives affect everyone who builds the module; `go.work` files are local.
</Warning>

## Current working directory context detection

sitectl can use the current working directory as the active context when it is inside a supported Compose project. Core sitectl finds the Compose project root, then asks installed plugins whether they claim that project. The plugin owns the discovery rules.

Plugins should register discovery during startup:

```go theme={null}
sdk.SetComposeProjectDiscovery(plugin.ComposeProjectDiscovery{
    RequiredServices:         []string{"drupal"},
    RequiredComposerPackages: []string{"drupal/islandora"},
    Reason:                   "drupal service with drupal/islandora in composer.json",
})
```

The SDK exposes this through the private `__sitectl-rpc` entrypoint using the `project.detect` method. Core sitectl uses that method during lightweight plugin discovery and creates a transient local context named `.` when the project has no matching saved context. This context is in memory only; discovery must not write to `~/.sitectl/config.yaml`.

Discovery is cached for the lifetime of the `sitectl` process by canonical project path and requested plugin. Repeated subcommands in one invocation should not repeatedly shell out to every plugin.

Plugin-specific commands must use the shared SDK context resolver (`sdk.GetContext`) so `--context .` works consistently. Commands that bypass the SDK need an explicit reason and tests for cwd detection.

## Linting and testing

```bash theme={null}
make lint test
```

This runs the same lint and test invocation used in GitHub Actions. Run it before pushing. Lint includes `gofmt` and `golangci-lint`. Tests run with `-race`.

To run a single test:

```bash theme={null}
go test -v -race ./cmd -run TestFcrepoComponent
```

## Integration tests (ISLE plugin)

The ISLE plugin has an end-to-end `create` test that exercises the full site creation flow:

```bash theme={null}
make integration-test FCREPO_STATE=off ISLE_FILE_SYSTEM_URI=public SITECTL_CONTEXT=isle-test
```

This is the same script the GitHub Actions integration workflow runs. It requires Docker and a functioning sitectl installation.

## Key command flags

### `sitectl create isle`

| Flag                | Default                                                  | Description                                                    |
| ------------------- | -------------------------------------------------------- | -------------------------------------------------------------- |
| `--template-repo`   | `https://github.com/islandora-devops/isle-site-template` | Template repository to clone                                   |
| `--template-branch` | `main`                                                   | Branch to clone from                                           |
| `--git-remote-url`  | —                                                        | If set, keeps template as `upstream` and adds this as `origin` |

### `sitectl component describe`

`sitectl component describe` and `sitectl component reconcile` accept `--codebase-rootfs`. The shared RPC contract carries this as `codebase_rootfs` so the same field works for Drupal and non-Drupal plugins. ISLE defaults it to `./drupal` so Drupal-specific paths like `composer.json` and `config/sync` resolve correctly for the site template layout.

Use `--codebase-rootfs` in examples and tests. Compatibility flags may exist in older plugins, but docs should show the canonical rootfs flag.

```bash theme={null}
sitectl component describe --path /path/to/project
```

### `sitectl converge` / `sitectl validate` / `sitectl verify`

These commands use `DisableFlagParsing: true` and forward unclaimed flags to the plugin's registered runner. The ISLE plugin's `ConvergeRunner` and `ValidateRunner` both accept `--codebase-rootfs` to resolve the Drupal web root correctly. The ISLE plugin's `VerifyRunner` accepts expected-state flags such as `--fcrepo`, `--blazegraph`, `--iiif`, `--iiif-topology`, and `--bot-mitigation`.

```bash theme={null}
sitectl converge --report
sitectl validate
sitectl verify --fcrepo off --iiif triplet --bot-mitigation on
```

### `sitectl create`

`create` also uses `DisableFlagParsing: true` and forwards every argument after it to
the selected plugin's create runner. As a result, global `sitectl` flags such as
`--context` and `--log-level` must be placed **before** the `create` subcommand, while
plugin create flags (`--path`, `--type`, `--checkout-source`, `--setup-only`, and so
on) go after the target. Placing `--context` after `create` forwards it to the plugin,
which fails with `unknown flag: --context`.

```bash theme={null}
sitectl --context integration-test create drupal/default --path ./drupal --type local
```

## Why `make install` matters for plugin chaining

Rebuilding `sitectl` locally without reinstalling the plugin binaries means core command dispatch will not see your current plugin builds. The full install chain keeps all three binaries aligned while you work across:

* core command routing in `sitectl`
* stack logic in `sitectl-isle`
* Drupal-specific extensions in `sitectl-drupal`
