buildCloseoutSweepPrompt composes the headless prompt that drives the post-done close-out sweep. The prompt is passed as a single positional arg to `claude -p` via exec.Command — no shell interpolation, so any characters are safe. Two responsibilities, executed in order by the same headless session
(slug, projectSlug string)
| 126 | // to the LLM — empty or purely-mechanical sessions yield no new |
| 127 | // entries and no project update. |
| 128 | func buildCloseoutSweepPrompt(slug, projectSlug string) string { |
| 129 | // Substitute the real flow root so the headless sweep doesn't get |
| 130 | // pointed at ~/.flow when the user has $FLOW_ROOT set elsewhere. |
| 131 | root := "~/.flow" |
| 132 | if r, err := flowRoot(); err == nil { |
| 133 | root = r |
| 134 | } |
| 135 | |
| 136 | mindset := "## How to think about this sweep\n\n" + |
| 137 | "**KB entries** (in " + root + "/kb/*.md) are forever — they sit at the top of every future task brief. Bar: very strict. Default: write nothing.\n\n" |
| 138 | if projectSlug != "" { |
| 139 | mindset = "## How to think about this sweep\n\n" + |
| 140 | "Two things happen here, with deliberately DIFFERENT bars:\n" + |
| 141 | " - **KB entries** (in " + root + "/kb/*.md) are forever — they sit at the top of every future task brief. Bar: very strict. Default: write nothing.\n" + |
| 142 | " - The **project log entry** is local to the project and a sibling task may benefit from a richer narrative. Bar: looser. A bit rich is fine. Default: write something if the session moved the project forward.\n\n" |
| 143 | } |
| 144 | |
| 145 | preamble := fmt.Sprintf( |
| 146 | "You are running an automated close-out sweep for completed flow task %q.\n\n"+ |
| 147 | "%s"+ |
| 148 | "## Steps\n\n"+ |
| 149 | "1. Invoke the flow skill via the Skill tool. This loads §4.10 (KB rules) and §4.5 (update-file shape).\n\n"+ |
| 150 | "2. Run: flow transcript %s\n"+ |
| 151 | " This prints the conversation transcript from the task's Claude session. Read it carefully end to end.\n\n"+ |
| 152 | "3. KB sweep — strict bar, distill the essence.\n\n"+ |
| 153 | " For each of these five files, ask: across the WHOLE transcript, is there a durable fact about the user, their org, products, processes, or business that belongs there per §4.10's bucket table AND meets ALL three bars below?\n"+ |
| 154 | " - %s/kb/user.md\n"+ |
| 155 | " - %s/kb/org.md\n"+ |
| 156 | " - %s/kb/products.md\n"+ |
| 157 | " - %s/kb/processes.md\n"+ |
| 158 | " - %s/kb/business.md\n\n"+ |
| 159 | " The three bars (ALL must be met):\n"+ |
| 160 | " a. **Durable** — still true / still relevant in three months. Not 'today I felt X', not 'we tried approach Y for this one PR'.\n"+ |
| 161 | " b. **Surprising or non-obvious** — not derivable from the code, the README, or what a sibling task would already know.\n"+ |
| 162 | " c. **Future-relevant** — a future Claude session would change a decision because of it. If you can't picture that, skip.\n\n"+ |
| 163 | " Most task transcripts contribute nothing to the KB. Mechanical work, narrow bug fixes, local refactors, routine debugging — these almost never produce KB entries. The expected answer for most files on most tasks is 'no'. Don't reach.\n\n"+ |
| 164 | "4. Writing KB entries — INTERPRET the essence; do not transcribe.\n\n"+ |
| 165 | " This is the close-out mode of §4.10 and is DIFFERENT from real-time scoop. In real-time scoop you capture what the user just said, mostly verbatim, because it's a single fresh fact. Here you've read the whole conversation — your job is to SYNTHESIZE: pull out the durable insight in compact paraphrase, in your own words, capturing the essence and (where helpful) the why. Avoid quote dumps. One concise dated bullet per insight.\n\n"+ |
| 166 | " For each KB file you decide needs an entry, Read it first to check for duplicates (in any form — paraphrase, near-duplicate, superset). If something similar already exists, skip; do not append. Append using the §4.10 entry format: one dated bullet per insight, your own paraphrase capturing the essence, never invent or embellish beyond what the transcript supports.\n\n", |
| 167 | slug, mindset, slug, root, root, root, root, root, |
| 168 | ) |
| 169 | |
| 170 | tailNum := "5" |
| 171 | projectStep := "" |
| 172 | if projectSlug != "" { |
| 173 | projectStep = fmt.Sprintf( |
| 174 | "5. Project update — looser bar, narrative OK.\n\n"+ |
| 175 | " This task is attached to project %q. The project log is local and lives next to the work, so a richer entry is fine — capture what got decided, what shipped, what was tried, what's now open. Sibling-task sessions will read this to catch up.\n\n"+ |
| 176 | " Write ONE file at:\n"+ |
| 177 | " %s/projects/%s/updates/YYYY-MM-DD-<kebab-title>.md\n"+ |
| 178 | " Shape per skill §4.5: roughly two paragraphs (can stretch a little if there's real substance). Paragraph 1: what got decided / learned / shipped at the project level. Paragraph 2: what is next or now open. Optional trailing 'Blocked on: <X>' line.\n\n"+ |
| 179 | " The (still real, but looser) bar: write ONLY if the session moved the project forward — a decision was made, something shipped, a learning emerged, a blocker was added/removed, an approach was chosen, etc. Skip when the work was purely mechanical with no project-level narrative (e.g. a single typo fix). Do NOT write a template or a 'task X was marked done' summary. The goal is something a sibling-task session would actually want to read; if you can't picture that, skip.\n\n", |
| 180 | projectSlug, root, projectSlug, |
| 181 | ) |
| 182 | tailNum = "6" |
| 183 | } |
| 184 | |
| 185 | tail := fmt.Sprintf( |