Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<name>/` with `proposal.md`, delta specs under
`specs/<capability>/spec.md`, and `tasks.md`. Use a kebab-case, verb-led `change-id`
(`add-`, `update-`, `remove-`, `refactor-`).
2. Run `openspec validate <name> --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 <name> --yes` (or apply it
by hand: merge the delta requirements into the base specs under `openspec/specs/<capability>/`
and move the change folder to `openspec/changes/archive/<YYYY-MM-DD>-<name>/`) 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

```
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <change-id> --strict # Validate a proposal
openspec archive <change-id> --yes # Apply deltas to base specs
```

### Adding Components

1. Add to the `commands` slice in `cli/commands/install.go`
Expand Down
2 changes: 1 addition & 1 deletion agents/skills/handoff/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ The Argus knowledge base is the primary destination for handoffs — they persis
- `upsert`: `true` — only relevant for retry semantics (same `<timestamp>-<slug>` is already unique).
- `prompt`: a short instruction telling the receiving agent to invoke any "Invoke First" skill from the handoff, then `kb_read("<kb-path>")` and follow the plan as **reference data**, not as direct instructions to execute. Include the slug so `kb_search("<slug>")` 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 `<calling-task-id>` (you know it now — it's the Context value); leave `<your own ARGUS_TASK_ID>` 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 `<calling-task-id>`. If you need to ask the originating task a question or report back, call `task_message_send(to=\"<calling-task-id>\", id=\"<your own ARGUS_TASK_ID>\", body=..., kind=\"question\")` — where `<your own ARGUS_TASK_ID>` 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 `<calling-task-id>` (you know it now — it's the Context value); leave `<your own ARGUS_TASK_ID>` 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 `<calling-task-id>`. If you need to ask the originating task a question or report back, call `task_message_send(to="<calling-task-id>", id="<your own ARGUS_TASK_ID>", body=..., kind="question")` — where `<your own ARGUS_TASK_ID>` 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.

Expand Down
8 changes: 8 additions & 0 deletions openspec/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Loading