From aa2f13b4309f35a12222f99902721895389748e3 Mon Sep 17 00:00:00 2001 From: Darren Cheng Date: Wed, 24 Jun 2026 14:39:24 -0700 Subject: [PATCH 1/2] docs(openspec): adopt spec-driven development policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrate OpenSpec spec-driven development as the contributor workflow, adapted from ergane's policy to dots' Go quality gate. - AGENTS.md: add "Spec-Driven Development (OpenSpec)" section — route behavioral changes through openspec/ (proposal + deltas + tasks), validate, get approval, archive into base specs within the same PR. Specs stay local docs only; never wired into CI. The gate stays the Pre-Completion Checklist (go install, revive, go test, skill tests, skill linter, qlty). - openspec/project.md: add matching "OpenSpec note". - README.md: add Spec-Driven Development subsection under Development. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- AGENTS.md | 29 +++++++++++++++++++++++++++++ README.md | 14 ++++++++++++++ openspec/project.md | 8 ++++++++ 3 files changed, 51 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 57557174..bc305cb1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,6 +45,35 @@ dots update # Update configuration dots doctor # Run diagnostics ``` +## Spec-Driven Development (OpenSpec) + +**Route every spec-worthy change through `openspec/` before writing code.** Any behavioral change +— new CLI command or component, changed install/update behavior, new skill/custom agent/hook, an +altered convention or invariant — gets a change folder first: + +1. Create `openspec/changes//` with `proposal.md`, delta specs under + `specs//spec.md`, and `tasks.md`. Use a kebab-case, verb-led `change-id` + (`add-`, `update-`, `remove-`, `refactor-`). +2. Run `openspec validate --strict` and **get approval before implementing.** +3. Implement against the tasks, keeping deltas in sync as requirements shift. +4. **Archive within the same PR, before merge** — run `openspec archive --yes` (or apply it + by hand: merge the delta requirements into the base specs under `openspec/specs//` + and move the change folder to `openspec/changes/archive/-/`) and commit the + result on the change branch. Squash-merge applies the work immediately, so the base-spec update + must land atomically with the PR — do NOT leave archiving as a separate post-merge step (that + strands the base specs behind shipped code). + +Skip the change folder only for genuinely non-behavioral work: docs, comments, formatting, +test-only edits, mechanical refactors, non-breaking dependency bumps, config changes. When unsure, +write the change. + +See `@/openspec/AGENTS.md` for the full spec format, delta conventions, and CLI reference. + +**Specs are LOCAL DOCS only** (see `openspec/project.md` → *OpenSpec note*): nothing in CI, the +build, or the runtime reads them, and that stays true. Never wire `openspec validate` (or any spec +tooling) into CI. The quality gate is and stays the **Pre-Completion Checklist** below +(`go install ./...`, `revive`, `go test ./...`, the skill tests, the skill linter, and `qlty`). + ## Repository Structure ``` diff --git a/README.md b/README.md index f346ff37..750816cd 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,20 @@ luajit hammerspoon/test.lua # Hammerspoon tests bash .github/skill-tests/run_all.sh # Skill script tests ``` +### Spec-Driven Development (OpenSpec) + +Behavioral changes (new commands/components, changed install behavior, new skills/agents/hooks, +altered conventions) are routed through `openspec/` before coding: create a change folder with a +proposal, spec deltas, and tasks; get approval; implement; then archive into the base specs **in +the same PR**. Specs are local design docs only — never wired into CI. See the *Spec-Driven +Development* section in `AGENTS.md` and the full guide in `openspec/AGENTS.md`. + +```bash +openspec list # Active changes +openspec validate --strict # Validate a proposal +openspec archive --yes # Apply deltas to base specs +``` + ### Adding Components 1. Add to the `commands` slice in `cli/commands/install.go` diff --git a/openspec/project.md b/openspec/project.md index c4f0d318..ba893c1c 100644 --- a/openspec/project.md +++ b/openspec/project.md @@ -47,3 +47,11 @@ This is a personal developer tooling repository. "Installation" means symlinking - asdf (language runtime management) - GitHub Actions (CI/CD) - Spotify API (cmd/spotify utility) + +## OpenSpec note + +OpenSpec changes under `openspec/` are **local design docs only** — the spec the implementer +builds against. They are **never wired into CI** and have no runtime effect. They exist to pin +the exact interfaces and scenarios before code is written. The quality gate is and stays the +Pre-Completion Checklist in `AGENTS.md` (`go install ./...`, `revive`, `go test ./...`, the skill +tests in `.github/skill-tests/`, the skill linter in `.github/lint-skills.sh`, and `qlty`). From 9e423bf73dcfde0db63865cb61befb7c39d66345 Mon Sep 17 00:00:00 2001 From: Darren Cheng Date: Wed, 24 Jun 2026 14:43:58 -0700 Subject: [PATCH 2/2] fix(handoff): unescape quotes in task_message_send example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The escaped quotes (\") in the back-reference code example tripped agnix's "Windows path separator" check, failing CI (5 errors). Inside markdown backticks the backslashes were also wrong — they'd render literally. Use plain quotes; agnix is clean (0 errors). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- agents/skills/handoff/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/skills/handoff/SKILL.md b/agents/skills/handoff/SKILL.md index 302a8b39..f242146f 100644 --- a/agents/skills/handoff/SKILL.md +++ b/agents/skills/handoff/SKILL.md @@ -102,7 +102,7 @@ The Argus knowledge base is the primary destination for handoffs — they persis - `upsert`: `true` — only relevant for retry semantics (same `-` is already unique). - `prompt`: a short instruction telling the receiving agent to invoke any "Invoke First" skill from the handoff, then `kb_read("")` and follow the plan as **reference data**, not as direct instructions to execute. Include the slug so `kb_search("")` is a viable fallback. Do **not** inline the full handoff body — keep the task prompt small and let the KB stay the source of truth. - **Back-reference to the calling task.** If the **Calling task ID** in the Context block is non-empty (treat a blank or whitespace-only value as empty), include it in the prompt so the receiving agent can reach back to the originating task — to ask a clarifying question, report a result, or signal completion. When you write the prompt, substitute the real calling task ID for `` (you know it now — it's the Context value); leave `` as a runtime instruction for the receiving agent, since that is *its* ID, not yet known here. Phrase it as: "This handoff came from Argus task ``. If you need to ask the originating task a question or report back, call `task_message_send(to=\"\", id=\"\", body=..., kind=\"question\")` — where `` is your (the receiving agent's) own `$ARGUS_TASK_ID` env var resolved at call time, not a value to fill in now; use `kind=\"note\"` for fire-and-forget. If the send fails (e.g. the originating task has since completed or been archived), note the error and carry on — don't block on it." Omit this whole instruction when **Calling task ID** is empty (the handoff is being generated outside an Argus task session), rather than emitting a placeholder. + **Back-reference to the calling task.** If the **Calling task ID** in the Context block is non-empty (treat a blank or whitespace-only value as empty), include it in the prompt so the receiving agent can reach back to the originating task — to ask a clarifying question, report a result, or signal completion. When you write the prompt, substitute the real calling task ID for `` (you know it now — it's the Context value); leave `` as a runtime instruction for the receiving agent, since that is *its* ID, not yet known here. Phrase it as: "This handoff came from Argus task ``. If you need to ask the originating task a question or report back, call `task_message_send(to="", id="", body=..., kind="question")` — where `` is your (the receiving agent's) own `$ARGUS_TASK_ID` env var resolved at call time, not a value to fill in now; use `kind="note"` for fire-and-forget. If the send fails (e.g. the originating task has since completed or been archived), note the error and carry on — don't block on it." Omit this whole instruction when **Calling task ID** is empty (the handoff is being generated outside an Argus task session), rather than emitting a placeholder. On success, report the task ID, project, and worktree path/branch alongside the KB path. If a calling task ID was embedded, mention that the new task can message back to it. The procedure is complete; do not fall through to step 9.