package cmd import ( "path/filepath" "git.noodles.cam/claude-skills/marketplace/cli/internal/config" "git.noodles.cam/claude-skills/marketplace/cli/internal/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" ) // commentsSidecarPath is where an issue's comment thread lives — beside it, // under the same slug. // // A path, not a concept the domain needs: a thread is pulled from the tracker // and never pushed back, so `internal/issue` has no reason to learn that the // file exists. It does not have to — the file is named after the issue, and // issue.SlugFiles takes it away when the issue goes. // // It sits here rather than in either command because `pull` writes it and // `comment` rewrites it, and two spellings of one path is how the two come to // disagree about where a thread is. func commentsSidecarPath(root, id string) string { return filepath.Join(root, id+".comments.md") } // The sync commands all start the same way and must fail the same way. // // Every one of them needs a store and a client, and the order matters: a // command that dialled first would report a network problem for a project that // was never initialized, and an operator would go looking at the wrong thing. // So the store is resolved before a socket is opened, and each failure names // the command that fixes it. // syncStart resolves the store and builds a client for it. // // The client is built from the project's own configuration, which is why there // is no --login flag anywhere in this tree: which login a project runs under is // a fact about the project, stated once by `kettle init`, not a thing a caller // gets to differ about per invocation. That the two could disagree is what the // Python version needed a PreToolUse hook to police. func syncStart(out string) (string, *gitea.Client, error) { root, err := storeRoot(out) if err != nil { return "", nil, err } cfg, err := config.Require("") if err != nil { return "", nil, err } client, err := gitea.New(cfg) if err != nil { return "", nil, err } return root, client, nil } // syncStartExisting is syncStart for the commands that read the store rather // than create it: push, comment, close and the sync form of evict all operate // on issues that are already on disk, and a missing store is a mistake to // report, not a directory to conjure. func syncStartExisting(out string) (string, *gitea.Client, error) { root, client, err := syncStart(out) if err != nil { return "", nil, err } if err := issue.RequireStore(root); err != nil { return "", nil, err } return root, client, nil }