From 124f8ac398def11fcf5fd524614ea60e952c6514 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 14:43:05 +0000 Subject: [PATCH 1/2] docs: rename CLAUDE.md to AGENTS.md, follow ddev/ddev agent setup, drop OpenSpec - Move the contributor guidance from CLAUDE.md to AGENTS.md, replacing the OpenSpec-managed AGENTS.md. Link DDEV's organization-wide AGENTS.md and adopt ddev/ddev's git workflow: commit locally, push only with approval, Conventional Commits, the /ddev:commit skill. - Add a committed .claude/settings.json that asks before every git push and denies git push --no-verify and docker push. Un-ignore that one file and ignore settings.local.json and CLAUDE.local.md. - Remove openspec/ and the .agent/ OpenSpec workflows, which are unused. Keep the one design point the ci-lock template referenced in its header comment. - Point README.md and docs/ at AGENTS.md. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UrTUMychMpBAhoSgdEuKQZ --- .agent/workflows/openspec-apply.md | 20 - .agent/workflows/openspec-archive.md | 24 - .agent/workflows/openspec-proposal.md | 25 - .claude/settings.json | 11 + .gitignore | 8 +- AGENTS.md | 391 ++++++++++++++- CLAUDE.md | 376 --------------- README.md | 2 +- ci-lock/template.tf | 7 +- docs/README.md | 4 +- docs/admin/operations-guide.md | 2 +- docs/admin/server-setup.md | 2 +- openspec/AGENTS.md | 456 ------------------ .../changes/add-ci-staging-lock/design.md | 30 -- .../changes/add-ci-staging-lock/proposal.md | 19 - .../specs/ci-staging-concurrency/spec.md | 21 - openspec/changes/add-ci-staging-lock/tasks.md | 21 - .../add-claude-self-hosted-runner/proposal.md | 18 - .../specs/claude-self-hosted-runner/spec.md | 21 - .../add-claude-self-hosted-runner/tasks.md | 10 - .../changes/add-ssh-agent-relay/proposal.md | 23 - .../specs/workspace-ssh-agent/spec.md | 31 -- openspec/changes/add-ssh-agent-relay/tasks.md | 12 - .../changes/add-user-startup-hook/proposal.md | 19 - .../specs/workspace-lifecycle/spec.md | 18 - .../changes/add-user-startup-hook/tasks.md | 16 - .../changes/deploy-and-verify/proposal.md | 12 - .../specs/verification/spec.md | 10 - openspec/changes/deploy-and-verify/tasks.md | 13 - openspec/changes/update-readme/proposal.md | 14 - .../update-readme/specs/documentation/spec.md | 8 - openspec/changes/update-readme/tasks.md | 7 - openspec/project.md | 42 -- 33 files changed, 405 insertions(+), 1288 deletions(-) delete mode 100644 .agent/workflows/openspec-apply.md delete mode 100644 .agent/workflows/openspec-archive.md delete mode 100644 .agent/workflows/openspec-proposal.md create mode 100644 .claude/settings.json delete mode 100644 CLAUDE.md delete mode 100644 openspec/AGENTS.md delete mode 100644 openspec/changes/add-ci-staging-lock/design.md delete mode 100644 openspec/changes/add-ci-staging-lock/proposal.md delete mode 100644 openspec/changes/add-ci-staging-lock/specs/ci-staging-concurrency/spec.md delete mode 100644 openspec/changes/add-ci-staging-lock/tasks.md delete mode 100644 openspec/changes/add-claude-self-hosted-runner/proposal.md delete mode 100644 openspec/changes/add-claude-self-hosted-runner/specs/claude-self-hosted-runner/spec.md delete mode 100644 openspec/changes/add-claude-self-hosted-runner/tasks.md delete mode 100644 openspec/changes/add-ssh-agent-relay/proposal.md delete mode 100644 openspec/changes/add-ssh-agent-relay/specs/workspace-ssh-agent/spec.md delete mode 100644 openspec/changes/add-ssh-agent-relay/tasks.md delete mode 100644 openspec/changes/add-user-startup-hook/proposal.md delete mode 100644 openspec/changes/add-user-startup-hook/specs/workspace-lifecycle/spec.md delete mode 100644 openspec/changes/add-user-startup-hook/tasks.md delete mode 100644 openspec/changes/deploy-and-verify/proposal.md delete mode 100644 openspec/changes/deploy-and-verify/specs/verification/spec.md delete mode 100644 openspec/changes/deploy-and-verify/tasks.md delete mode 100644 openspec/changes/update-readme/proposal.md delete mode 100644 openspec/changes/update-readme/specs/documentation/spec.md delete mode 100644 openspec/changes/update-readme/tasks.md delete mode 100644 openspec/project.md diff --git a/.agent/workflows/openspec-apply.md b/.agent/workflows/openspec-apply.md deleted file mode 100644 index b0154dd..0000000 --- a/.agent/workflows/openspec-apply.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -description: Implement an approved OpenSpec change and keep tasks in sync. ---- - -**Guardrails** -- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. -- Keep changes tightly scoped to the requested outcome. -- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. - -**Steps** -Track these steps as TODOs and complete them one by one. -1. Read `changes//proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria. -2. Work through tasks sequentially, keeping edits minimal and focused on the requested change. -3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished. -4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality. -5. Reference `openspec list` or `openspec show ` when additional context is required. - -**Reference** -- Use `openspec show --json --deltas-only` if you need additional context from the proposal while implementing. - diff --git a/.agent/workflows/openspec-archive.md b/.agent/workflows/openspec-archive.md deleted file mode 100644 index 38dcc81..0000000 --- a/.agent/workflows/openspec-archive.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -description: Archive a deployed OpenSpec change and update specs. ---- - -**Guardrails** -- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. -- Keep changes tightly scoped to the requested outcome. -- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. - -**Steps** -1. Determine the change ID to archive: - - If this prompt already includes a specific change ID (for example inside a `` block populated by slash-command arguments), use that value after trimming whitespace. - - If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends. - - Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding. - - If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet. -2. Validate the change ID by running `openspec list` (or `openspec show `) and stop if the change is missing, already archived, or otherwise not ready to archive. -3. Run `openspec archive --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work). -4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`. -5. Validate with `openspec validate --strict` and inspect with `openspec show ` if anything looks off. - -**Reference** -- Use `openspec list` to confirm change IDs before archiving. -- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off. - diff --git a/.agent/workflows/openspec-proposal.md b/.agent/workflows/openspec-proposal.md deleted file mode 100644 index db8a95a..0000000 --- a/.agent/workflows/openspec-proposal.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -description: Scaffold a new OpenSpec change and validate strictly. ---- - -**Guardrails** -- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. -- Keep changes tightly scoped to the requested outcome. -- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. -- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files. -- Do not write any code during the proposal stage. Only create design documents (proposal.md, tasks.md, design.md, and spec deltas). Implementation happens in the apply stage after approval. - -**Steps** -1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification. -2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes//`. -3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing. -4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs. -5. Draft spec deltas in `changes//specs//spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant. -6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work. -7. Validate with `openspec validate --strict` and resolve every issue before sharing the proposal. - -**Reference** -- Use `openspec show --json --deltas-only` or `openspec show --type spec` to inspect details when validation fails. -- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones. -- Explore the codebase with `rg `, `ls`, or direct file reads so proposals align with current implementation realities. - diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..f33e400 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,11 @@ +{ + "permissions": { + "ask": [ + "Bash(git push *)" + ], + "deny": [ + "Bash(git push *--no-verify*)", + "Bash(docker push *)" + ] + } +} diff --git a/.gitignore b/.gitignore index ba14d7f..a791b14 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,14 @@ __pycache__/ # Temp files composer.json.tmp +# .claude/settings.json is shared team configuration and is committed; only +# personal overrides stay private. https://code.claude.com/docs/en/claude-directory +**/.claude/settings.local.json +CLAUDE.local.md + # Added by Spec Kitty CLI (auto-managed) -.claude/ +.claude/* +!.claude/settings.json .codex/ .vibe/ .opencode/ diff --git a/AGENTS.md b/AGENTS.md index 0669699..2fb8a35 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,18 +1,383 @@ - -# OpenSpec Instructions +# AGENTS.md -These instructions are for AI assistants working in this project. +Guidance for AI agents working on the coder-ddev codebase. Claude Code reads +this file directly; there is no `CLAUDE.md`. -Always open `@/openspec/AGENTS.md` when the request: -- Mentions planning or proposals (words like proposal, spec, change, plan) -- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work -- Sounds ambiguous and you need the authoritative spec before coding +DDEV's [organization-wide patterns](https://raw.githubusercontent.com/ddev/.github/main/AGENTS.md) +(boundaries, writing style, branches, commits, PRs and issues) apply here too. +This file **wins where they differ**. Fetch GitHub files through +`raw.githubusercontent.com`, not `github.com/.../blob/...` pages, which wrap +the file in markup. -Use `@/openspec/AGENTS.md` to learn: -- How to create and apply change proposals -- Spec format and conventions -- Project structure and guidelines +## Claude Code automation -Keep this managed block so 'openspec update' can refresh the instructions. +`.claude/settings.json` applies only to Claude Code; other agents follow the +rules below by hand. With it, `git push` asks for approval every time, in +every permission mode, and `git push --no-verify` and `docker push` are +denied. Personal overrides go in `.claude/settings.local.json` or +`CLAUDE.local.md`, both gitignored; a `CLAUDE.local.md` can stop Claude Code +from loading this file, so add an `@AGENTS.md` import to it if you use one. - \ No newline at end of file +## Overview + +This project provides a Coder v2+ template for DDEV-based development environments using Docker-in-Docker via Sysbox runtime. It creates isolated workspaces with full Docker daemon support for running DDEV projects. + +**Key Technologies:** +- **Terraform (HCL)** - Infrastructure as Code for Coder templates +- **Docker + Sysbox** - Nested containerization without privileged mode +- **DDEV** - PHP/Node/Python development environment tool (supports 20+ project types) +- **VS Code for Web** - Browser-based IDE via official Coder module +- **Ubuntu 24.04** - Base container OS + +**Note:** User-facing documentation lives in `/docs/`. This file focuses on developer/contributor guidance. + +## Coder CLI Commands in Docs + +When writing or reviewing `coder` CLI commands in documentation, **always verify against the actual CLI** (`coder --help`) and the [Coder CLI reference docs](https://coder.com/docs/reference/cli). Common past mistakes to avoid: + +- `coder delete` takes only **one** workspace argument — use a `for` loop for multiple +- `coder stop` / `coder start` take only **one** workspace argument +- `coder scp` does **not exist** — use `scp` after `coder config-ssh` +- `coder templates show` does **not exist** — use `coder templates list -o json | jq ...` +- `coder publickey` has no `add`/`list`/`remove` subcommands — it only outputs your key +- `coder list` has no `--user` flag — use `--search "owner:"` with `-a` +- `coder tokens` uses `remove` not `revoke`; `create` takes `--name`, not a positional arg +- `coder templates list` has no `--organization` flag — use the global `--org` +- `coder sharing add` requires `--user :` (role required) +- `coder update --parameter X=Y` / `coder restart --parameter X=Y` **silently no-op** on the parameter unless `--always-prompt` is also passed — without it, Coder just reuses the workspace's existing value and (for `update`) may skip the rebuild entirely if the template version hasn't changed. With `--always-prompt`, every *mutable* parameter must be supplied via `--parameter` or it falls back to the template default via `--use-parameter-defaults` (not the workspace's current value) — and any *immutable* parameter (e.g. drupal-contrib's `project_name`) must be **omitted entirely**, since `--always-prompt` errors trying to "change" it even to its current value. Verified live against staging-coder.ddev.com while building the sharing feature (see PR for #204). +- `coder create`/`coder update`/`coder restart --parameter name=value` **splits the value on commas** (it's a `string-array` flag) — a parameter whose value legitimately contains a comma (e.g. freeform's `project_names="a,b"`) fails with `unable to parse rich parameters: format key=value expected, but got b`. Use `--rich-parameter-file ` instead for any comma-containing value; this bit CI on the first attempt at the freeform sharing test (PR #205). + +## DDEV Command Gotchas + +- `ddev composer create` is **obsolete** — always use `ddev composer create-project .` (note the trailing `.` for current directory) +- Do **not** pin packages to `:dev-main` (e.g. `package:dev-main`) — omit the version constraint and let Composer resolve it +- All templates omit `ddev-router` via `ddev config global --omit-containers=ddev-router`; each template uses direct port binding instead + +## Coder Parameter Gotchas + +- **Never change an existing `coder_parameter`'s `type` in place — always give it a new `name`.** The `coder_parameter` data source validates the incoming value against the declared `type` *before* any of the template's own `locals` ever run, so a `try(tobool(...), false)`-style fallback in a `locals` block cannot rescue a stale value — the plan fails hard first, with an opaque `Error: Parameter value is not of type "bool"`. Any client still holding an old-typed value for that parameter name (a bookmarked workspace-creation deep link, a cached copy of a `docs/*.html` page, an already-open browser tab) will hit this on every future `terraform plan`, including the Coder UI's live dynamic-parameter preview during workspace creation. Renaming the parameter is the only fix — `terraform` code cannot intercept the provider's own type validation. Root-caused and fixed for `share_drupal_site` → `drupal_site_public` in #206 (`drupal-core`/`drupal-contrib`; both changed the parameter from a 3-way `owner`/`authenticated`/`public` string to a `bool` switch in #205, one PR earlier — the type change and the deep-link doc pages that still emitted the old string values shipped in the same PR and nothing caught the mismatch). +- **`terraform test`'s `mock_provider "coder"` cannot catch the above class of bug.** The mock replaces the real `coder_parameter` data source entirely (including its type validation), so a run block asserting behavior for a stale/incompatible parameter value will pass locally and in CI even when the real provider would hard-fail. There is no terraform-test-layer way to close this gap; the only defense is the naming discipline above. +- **`docs/drupal-core.html`, `docs/drupal-issue.html`, and `docs/coder-workspace-name.js` build Coder workspace-creation deep links (`?param.=`) and are not covered by any CI check against the live `coder_parameter` definitions in `template.tf`.** Whenever a template's parameter names, types, or allowed values change, grep these docs files for the old name/values and update them in the same PR — nothing currently fails CI if they drift out of sync. + +## Template Architecture Overview + +Three templates exist, each with a distinct routing model: + +| Template | Web port | Routing model | +| -------------------- | -------- | ----------------------------------------------------- | +| `drupal-core` | 80 | Direct bind, no ddev-router | +| `drupal-contrib` | 8080 | Direct bind, no ddev-router | +| `freeform` | 8080 | ddev-router on 8080, Host-header dispatch per project | + +The `freeform` template is unique: it keeps ddev-router running so multiple DDEV projects can coexist in one workspace, distinguished by Host header. + +## Tool Preferences + +- Use `jq` (not `python3 -m json.tool`) for JSON pretty-printing and querying +- For `git commit -m` and `gh pr create --body`, write the message to a file first (e.g. under the scratchpad directory) and pass it via `git commit -F ` / `gh pr create --body-file `, rather than inlining a heredoc on the command line. Multi-paragraph messages containing backticks, apostrophes, or other shell-special characters reliably break inline heredoc quoting. + +## Before Pushing / Pre-push Checklist + +Run these before every push to avoid CI failures: + +```bash +# Terraform formatting (CI runs terraform fmt -check -recursive) +terraform fmt -recursive + +# Vendor shared assets, then validate + test every template +make validate +make test-templates +``` + +`terraform fmt -recursive` must be run from the repo root. It is non-destructive (rewrites in place) and the CI check fails with exit code 3 if any file is not formatted. + +**Always use `make validate` / `make test-templates`, not raw `terraform validate`/`terraform test` run directly in a template directory.** Both targets depend on `make sync-shared`, which vendors `modules/claude-remote-control` and `shared/vscode-extensions.tf` into each template directory first (see "Shared Terraform Assets" below). Running `terraform validate`/`test` directly against a template dir will happily validate against a stale vendored copy and miss the fact that it's out of sync with the canonical source. + +## Shared Terraform Assets + +Some Terraform config (currently `modules/claude-remote-control`, `modules/docker-daemon`, `shared/vscode-extensions.tf`, `shared/bashrc-symlink-guard.sh` and `shared/ssh-agent-setup.sh`) is shared across all three templates but must exist as a **physical copy inside each template directory** — `coder templates push` and CI's `terraform validate`/`test` only ever operate on a single template directory, so a relative `../modules` or `../shared` reference would not survive the push and would not be visible to CI. + +`modules/claude-self-hosted-runner` follows the same rules but is vendored into `freeform` only, the one template that offers the runner. + +- **Edit only the canonical source**: `modules/claude-remote-control/` or `shared/vscode-extensions.tf` at the repo root. Never hand-edit the vendored copies (`