mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
Field report follow-up: an agent with a stale skill copy couldn't find the gated URL after creating a file. Instructions rot; hook output is computed fresh from the binary every turn. Claude Code's turn-start pull hook now runs 'bdrive sync --hook claude-code', which: - pulls as before, and stamps the session note from the event JSON (replacing the sh/sed pipeline for the pull leg) - emits the project's gated-link formula as UserPromptSubmit additionalContext: whenever the agent mentions a synced file path in prose, it appends the hub link on an emoji — `<path>` [🔗](<url>) — path plain (it's the local path), hyperlink on the emoji only; code blocks stay plain; bdrive share stays explicit-opt-in-public Blind-tested: an agent given only the injected context decorated every path mention correctly, kept the code-block command plain, and checked files were synced before linking. - hooks install now CONVERGES marker-identified groups to the current shape (command/matcher/flags), so improvements reach existing projects on reinstall instead of being frozen by the idempotency marker; hermes same - plugin: UserPromptSubmit → beardrive-pull.sh (stdout passes through); version 0.3.0 - SKILL 'Share what you make' generalized to 'Link what you mention' (URL formula documented for non-Claude platforms); install.md pointer template updated Never fails the turn: every error path in --hook mode is a silent successful exit; offline still emits (links serve online teammates). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
80 lines
3.0 KiB
Go
80 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// `bdrive sync --hook <label>` is the agent-hook flavor of sync, run by the
|
|
// Claude Code UserPromptSubmit hook at every turn start. It does three
|
|
// things: pulls (a normal cycle), stamps the session note so every change
|
|
// this turn is attributed to the agent session, and — the part that keeps
|
|
// agents current no matter how stale their skill copy is — emits the
|
|
// project's gated-link formula as additionalContext, so the agent can
|
|
// append a hub link to any synced file path it mentions.
|
|
//
|
|
// Everything is best-effort: a hook must never fail the turn, so every
|
|
// error path is a silent, successful exit.
|
|
|
|
// hookNoteTTL mirrors `bdrive sync --note-ttl`'s default: the daemon's own
|
|
// scans keep stamping this session's changes for a while.
|
|
const hookNoteTTL = 30 * time.Minute
|
|
|
|
func runHookSync(cmd *cobra.Command, folder, label string) error {
|
|
// The platform pipes its event JSON on stdin; the session id is all we
|
|
// need from it here.
|
|
data, _ := io.ReadAll(io.LimitReader(cmd.InOrStdin(), 1<<20))
|
|
var event struct {
|
|
SessionID string `json:"session_id"`
|
|
}
|
|
_ = json.Unmarshal(data, &event) // malformed input: just sync
|
|
|
|
sess, proj, err := openSession(cmd.Context(), folder, true)
|
|
if err != nil {
|
|
return nil // not a mount / no session: fast no-op
|
|
}
|
|
defer closeSession(sess)
|
|
|
|
if event.SessionID != "" {
|
|
note := label + " session " + event.SessionID
|
|
if err := sess.Store.SaveNote(note, hookNoteTTL); err == nil {
|
|
sess.Note = note
|
|
}
|
|
}
|
|
|
|
// The pull. Offline is fine — the link formula below is still valid
|
|
// for teammates who are online.
|
|
if _, err := sess.Cycle(cmd.Context()); err != nil {
|
|
return nil // never break the turn
|
|
}
|
|
|
|
server, projectID, err := splitHubRemote(proj.Remote)
|
|
if err != nil {
|
|
return nil // non-hub remote: nothing to link to
|
|
}
|
|
base := server + "/" + projectID
|
|
|
|
out := map[string]any{
|
|
"hookSpecificOutput": map[string]any{
|
|
"hookEventName": "UserPromptSubmit",
|
|
"additionalContext": fmt.Sprintf(
|
|
"beardrive: this folder syncs to %s (the project's hub page; files are at %s/<url-encoded path>). "+
|
|
"Link convention: whenever you mention a synced file's path in prose, append its gated hub link on an emoji, formatted exactly as: `<path>` [🔗](%s/<url-encoded path>) — the path stays plain text, the hyperlink goes on the emoji only. "+
|
|
"These links require hub sign-in + project membership, so they are safe to paste anywhere internal. "+
|
|
"Only link files that actually sync (inside the shared scope, not ignored); keep paths inside code blocks or commands plain; give a raw URL only when the user needs to paste it outside this conversation. "+
|
|
"`bdrive share <file>` mints PUBLIC no-account links — use it only when the user explicitly asks for a public link.",
|
|
base, base, base),
|
|
},
|
|
}
|
|
enc, err := json.Marshal(out)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
fmt.Fprintln(cmd.OutOrStdout(), string(enc))
|
|
return nil
|
|
}
|