From 814b2a635fb04480b8c561d101f7fc656dd42ccd Mon Sep 17 00:00:00 2001 From: Tink bot Date: Fri, 15 May 2026 14:34:13 +0000 Subject: [PATCH] feat(veans): install agent hooks during init instead of just printing Adds a final step to bootstrap.Init that offers to wire `veans prime` into Claude Code and OpenCode automatically. Per-agent yes/no prompts default to "yes" for Claude Code and "no" for OpenCode; --install-claude / --install-opencode flags skip the prompt for scripted contexts; --no-hooks falls back to the previous behaviour of just printing the snippets. Claude Code: - Writes/merges .claude/settings.json - JSON merge preserves existing keys (model, permissions, other hooks) and only appends a `veans prime` command entry under SessionStart and PreCompact if one isn't already there - Idempotent: re-running reports "Already configured" without duplicating entries OpenCode: - Writes .opencode/plugin/veans-prime.ts with the standard handler skeleton - Existing files are left alone (no TS-merge story for v0) Failures during hook install are non-fatal: the repo is already configured, so the user gets a warning + the printed snippets as a fallback path. Unit tests cover the merge logic (fresh file, idempotent rerun, preserving user's other hooks/keys), the install actions ("Wrote"/"Updated"/"Already configured"), and the offer flow (flags-bypass-prompt vs prompt-when-unset vs no-hooks). --- veans/.golangci.yml | 6 +- veans/AGENTS.md | 44 +++- veans/README.md | 4 +- veans/e2e/claim_test.go | 2 +- veans/e2e/helpers.go | 39 ++-- veans/e2e/init_test.go | 14 +- veans/e2e/shared_test.go | 4 +- veans/e2e/tasks_test.go | 10 +- veans/go.mod | 1 + veans/go.sum | 3 + veans/internal/auth/auth.go | 50 ++--- veans/internal/auth/browser.go | 48 ----- veans/internal/auth/oauth.go | 16 +- veans/internal/bootstrap/bootstrap.go | 61 +++--- veans/internal/bootstrap/hooks.go | 269 ++++++++++++++++++++++++ veans/internal/bootstrap/hooks_test.go | 260 +++++++++++++++++++++++ veans/internal/client/assignees.go | 5 - veans/internal/client/tasks.go | 8 - veans/internal/client/tokens.go | 41 +--- veans/internal/client/types.go | 17 +- veans/internal/client/users.go | 6 - veans/internal/commands/claim.go | 9 +- veans/internal/commands/create.go | 8 +- veans/internal/commands/init.go | 52 +++-- veans/internal/commands/list.go | 27 +-- veans/internal/commands/prime.go | 4 - veans/internal/commands/prime_test.go | 7 + veans/internal/commands/prompt.tmpl | 111 ++++++++-- veans/internal/commands/root.go | 21 +- veans/internal/commands/show.go | 43 +--- veans/internal/commands/update.go | 9 +- veans/internal/config/config.go | 18 +- veans/internal/credentials/env.go | 14 +- veans/internal/credentials/file.go | 34 +-- veans/internal/credentials/file_test.go | 18 -- veans/internal/credentials/keyring.go | 10 - veans/internal/credentials/store.go | 15 -- veans/internal/output/errors.go | 15 +- veans/magefile.go | 8 - 39 files changed, 836 insertions(+), 495 deletions(-) delete mode 100644 veans/internal/auth/browser.go create mode 100644 veans/internal/bootstrap/hooks.go create mode 100644 veans/internal/bootstrap/hooks_test.go diff --git a/veans/.golangci.yml b/veans/.golangci.yml index 2528088aa..c14dab5ab 100644 --- a/veans/.golangci.yml +++ b/veans/.golangci.yml @@ -92,11 +92,11 @@ linters: - gosec path: e2e/ text: 'G(204|306|703):' - # .veans.yml is committed to the repo and intentionally world- - # readable; 0o644 is correct. + # .veans.yml + agent hook config files are committed to the repo + # and intentionally world-readable; 0o644 is correct. - linters: - gosec - path: internal/config/config\.go + path: internal/(config|bootstrap)/.*\.go text: 'G306:' formatters: enable: diff --git a/veans/AGENTS.md b/veans/AGENTS.md index b5ab906c0..3545fa055 100644 --- a/veans/AGENTS.md +++ b/veans/AGENTS.md @@ -60,6 +60,16 @@ what's bitten me; if a new endpoint behaves oddly, suspect one of these: `valid:"required"` upstream; sending it omitted or zero fails validation. Use `client.FarFuture` (year 9999) when you mean "no expiry" — the frontend does the same. +- **Task descriptions and comments are HTML, not markdown.** The + Vikunja web UI uses TipTap, which calls `getHTML()` on save. The + stored field is therefore HTML. The agent prompt template + (`internal/commands/prompt.tmpl`) teaches agents the canonical + TipTap shapes — most importantly `'` +- For surgical edits, prefer `--description-replace-old` / + `--description-replace-new`. To check off a task item, replace + `data-checked="false">

step 1

` with `data-checked="true">

step 1

` + (errors if the old text isn't unique — same semantics as the Edit tool). - Post a comment on significant decisions, discoveries, or course-changes: - `veans update {{ .TaskIDExample }} --comment "Discovered Y; pivoting to Z because …"` + `veans update {{ .TaskIDExample }} --comment '

Discovered Y; pivoting to Z because …

'` - For sub-work that could be assigned separately, create real subtasks - via `--parent`. For incremental check-off lists, use markdown checkboxes + via `--parent`. For incremental check-off lists, use task-list items in the description instead. ## AFTER you finish work - Move to `in-review` and post a summary comment. **Never close tasks yourself** — the human (or the merge hook) closes them. ``` - veans update {{ .TaskIDExample }} -s in-review --comment "## Summary of Changes - - … - - …" + veans update {{ .TaskIDExample }} -s in-review \ + --comment '

Summary of Changes

' ``` - If you abandon work, scrap the task with a reason: `veans update {{ .TaskIDExample }} -s scrapped --reason "obsolete: "` @@ -49,6 +50,83 @@ fix: handle empty project identifiers Refs: {{ .TaskIDExample }} ``` +# Description format + +**Descriptions and comments are HTML, not markdown.** Vikunja's web UI +renders them through the TipTap rich-text editor, which interprets the +stored field as HTML. Plain markdown saves as literal text and shows up +ugly in the UI; write HTML directly. Tokens are cheap, conversion bugs +aren't. + +**Titles are plaintext — not HTML, not markdown.** They show up in +list views, breadcrumbs, and notification subjects; tags would leak +through as `<p>…</p>` everywhere. Write +`fix the bug` not `

fix the bug

` or `**fix** the bug`. + +Canonical TipTap shapes the web UI renders cleanly: + +```html +

Summary

+

Two short paragraphs explaining the task.

+ +

Steps

+ + +

Notes

+ + +

Inline code, bold, italic, +and a link.

+ +
if err != nil { return err }
+ +

A quote, e.g. from a linked issue.

+ +
+``` + +Important details: +- Task-list items use `