feat(brain): ListPending + PromoteNote — raw→wiki curation primitives (#38)
Closes the human curation loop: list brain/raw/ notes awaiting review (oldest-first, with excerpt) and promote one into brain/wiki/<wing>/<hall>/. PromoteNote rewrites frontmatter (sets wing/hall/promoted_at, preserves created_at + custom fields via the existing frontmatter editor), rebuilds the wing _index, and runs auto-tunnel. Atomic from the caller's view: hall/wing/slug/collision validation happens before any fs change, and the source is deleted only after the destination write succeeds (write-then- delete, never move) — a collision or write failure leaves raw/ intact. Default slug = filename minus the YYYY-MM-DD- prefix. Also exposes GET /pending + POST /promote for shell scripts (bad hall/collision/missing-source → 400, not 500). Scope note: operates on raw/ (the retrospective-skill review queue) per this issue's spec. The knowledge/ legacy pile is #22's bulk-migration concern, not this ongoing queue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -483,6 +483,40 @@ func (h *Handler) BackfillRefs(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, map[string]int{"updated": n})
|
||||
}
|
||||
|
||||
// Pending handles GET /pending — list raw/ notes awaiting promotion.
|
||||
func (h *Handler) Pending(w http.ResponseWriter, _ *http.Request) {
|
||||
pending, err := ListPending(h.brainDir)
|
||||
if err != nil {
|
||||
h.logger.Error("pending failed", "err", err)
|
||||
writeError(w, http.StatusInternalServerError, "pending error")
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"pending": pending})
|
||||
}
|
||||
|
||||
type promoteRequest struct {
|
||||
Filename string `json:"filename"`
|
||||
Wing string `json:"wing"`
|
||||
Hall string `json:"hall"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
}
|
||||
|
||||
// Promote handles POST /promote — move a raw/ note into the wiki. A bad
|
||||
// hall / collision / missing source is a 400 (caller error), not a 500.
|
||||
func (h *Handler) Promote(w http.ResponseWriter, r *http.Request) {
|
||||
var req promoteRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid JSON")
|
||||
return
|
||||
}
|
||||
rel, err := PromoteNote(h.brainDir, PromoteOptions(req))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]string{"path": rel})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(v) //nolint:errcheck
|
||||
|
||||
Reference in New Issue
Block a user