9b78ba71b6f74e60436e336c20187534f01c52a6 diff --git a/goals/migrate-historical-goals.md b/goals/migrate-historical-goals.md new file mode 100644 index 0000000000000000000000000000000000000000..eedc460f8f468d7b820c0b2e6d8f08e418d65e00 --- /dev/null +++ b/goals/migrate-historical-goals.md @@ -0,0 +1,84 @@ +--- +slug: migrate-historical-goals +title: Migrate historical /goals + lock down the authoring workflow +date: 2026-05-25 +branch: migrate-historical-goals +pr_number: null +merge_sha: null +status: pending +related_posts: [sama-v2-goal-chain-gap] +--- + +Goal: Recover every /goal command this session and earlier sessions produced from the PR descriptions, commit bodies, and conversation context that captured them; populate goals/ with one .md per recoverable goal classified honestly by recovery fidelity; mark unrecoverable goals as status: lost in the registry without polluting /goals with stub files; and lock down the authoring workflow so future /goals automatically land as committed files BEFORE any code is written AND get embedded verbatim into the PR body so they survive even if goals/ is later corrupted. Closes the "we lose /goals" gap with eyes open about historical losses. + +Extend GoalStatus first (one-line type change): +- src/a31_goals.ts: `type GoalStatus = "pending" | "shipped" | "lossy" | "lost" | "abandoned"` +- Semantics: + * shipped — /goal text recovered VERBATIM from the PR body or conversation. The goals/.md file contains the exact text the user typed. Audit-grade. + * lossy — /goal text recovered from conversation context that was itself paraphrased (e.g. the prior-session summary at the start of this conversation). File exists with the recovered text PLUS an explicit note: "> ⚠ Recovered from conversation summary, not verbatim from PR body. Original text may differ in wording from what the user typed." + * lost — no recoverable text. Entry in ALL_GOALS, NO file on disk. The detail page renders metadata-only (PR link, commit link, related posts) with an honest "the /goal text could not be recovered from any source" message. + * pending — in-flight goal, not yet merged. mergeSha and prNumber are null. + * abandoned — the work was started but the PR was closed without merging. Used for future cases, no historical entries expected. + +Detail handler change (src/d21_handlers_goals.ts): +- For status: "lost", DO NOT call Bun.file().exists() → skip the file-read entirely. Render the badges header + a "Source could not be recovered" callout block + the related_posts links. Return 200, not 404. +- For status: "shipped" and "lossy", keep the existing file-read path. For "lossy", prepend the warning block to the rendered body. + +Done when: +- gh pr list --state merged --limit 30 --json number,title,body,headRefName,mergedAt,mergeCommit fetched and inspected. Every PR classified as one of: + a. /goal-driven AND verbatim /goal text in body → shipped + b. /goal-driven AND only summary in body BUT recoverable from conversation context → lossy + c. /goal-driven AND no recoverable text → lost + d. NOT /goal-driven (Containerfile hotfix, image redesigns, blog-post-only PRs, typo fixes) → excluded from migration entirely +- Classification rule for "is this PR /goal-driven?": the conversation-or-PR-body source text MUST start with literal "Goal:" AND contain "Done when:" AND contain "Constraints" or "Constraints (anti-fudge):" AND contain "Load-bearing files" or "Load-bearing files to read FIRST:". All four markers required — anything missing → not a /goal-driven PR, excluded. +- For every shipped + lossy entry: a goals/.md file exists with full frontmatter (slug, title, date from mergedAt, branch from headRefName, pr_number, merge_sha as 7-char short, status, related_posts). +- For every lost entry: a registry-only entry in ALL_GOALS with status: lost, NO file on disk. Detail page renders metadata-only at 200. +- ALL_GOALS in src/a31_goals.ts contains every classified entry, sorted by date descending. The git-url-drop-owner entry already in ALL_GOALS stays. +- /goals index shows every entry with its status badge (✓ shipped / ⚠ lossy / ✗ lost / ⏳ pending / ✗ abandoned). +- /goals/ for a lost goal returns 200 with metadata-only content, not 404. +- /goals/ for a lossy goal returns 200 with the warning banner prepended to the recovered body. +- src/b32_goals_meta.test.ts gains 2 new test cases: status: "lossy" parses correctly; status: "lost" parses correctly. + +Workflow lock-in (defense-in-depth): +- Write a NEW feedback memory file at /var/home/scri/.claude/projects/-var-home-scri-Documents-tdd-md/memory/feedback_goal_authoring_workflow.md with this content (paraphrased — the memory file itself should be in your normal feedback-memory style with **Why:** + **How to apply:** sections): + Rule: When the user fires a /goal slash command, the agent's FIRST tool call (before any Read, Bash, or other Edit) is to write the verbatim /goal body to goals/.md with frontmatter status: pending, merge_sha: null, pr_number: null, date: , branch: , title: , related_posts: []. Commit this on a new branch as the FIRST commit of the PR. + Additionally, when creating the PR via `gh pr create`, the --body MUST include the verbatim /goal text as the first section (under a "## /goal" heading), followed by the existing "## Summary" + "## Test plan" sections. This is the defense-in-depth: even if goals/ is corrupted, the PR body always has the verbatim text. + After merge, the agent's FINAL commit before deploy updates merge_sha + flips status to shipped in the same goals/.md file. + Why: the empirical chain has historically had a hole the shape of goal.md (see /blog/sama-v2-goal-chain-gap). The two redundant captures — goals/ file AND PR body — close the hole twice. + How to apply: triggered on the literal token "/goal" in user message OR when user pastes a "Goal: ... Done when: ... Constraints: ... Load-bearing files:"-shaped block. Skip if user is asking ABOUT a /goal rather than firing one (e.g. "should I fire this /goal?", "look at this /goal" — those are questions not invocations). +- Add a one-line index entry in MEMORY.md pointing at the new file, between feedback_jolo_mode and feedback_flatpak_host_tools (the related JOLO pacing memory + the github-flow memory are this rule's neighbors). +- Add `/goal.md` (the repo-root scratch file) to .gitignore so it can never accidentally land in a commit. Do NOT delete goal.md — it may have in-flight content; just ignore it. + +Containerfile anti-fudge (lessons-learned from PR #46): +- Verify that no new top-level directory was added during this /goal's work. If one was (e.g. an inadvertent goals_archive/ or similar), it MUST have a corresponding `COPY ./` line in Containerfile and a live-verify step that fetches a file from that directory after deploy. +- The existing goals/ COPY line from PR #46 stays. We don't need a second one; this clause is forward-looking for any future migration. + +Anti-fudge constraints: +- Recovered text is what it is. Don't paraphrase to look better. lossy means lossy; mark it with the warning banner. +- Don't invent /goal text for PRs that don't have any. lost is lost. +- The 4-marker classification rule (Goal: + Done when: + Constraints + Load-bearing files) is the SINGLE filter. Don't add fuzzy "this feels like a /goal" judgments. +- New feedback memory is ADDITIVE — does not edit feedback_jolo_mode or feedback_bypass_permissions_pacing. +- goal.md at repo root: .gitignore'd, not deleted. +- Site language English-only. +- GitHub flow via flatpak-spawn (branch → PR → merge → push p620 → deploy). +- /sama/v2/verify still 7/7 ✓ after deploy (anti-fudge). +- All 400+ tests still pass; new tests for lossy/lost status parsing. + +Live-verify after deploy: +- curl https://tdd.md/goals → 200 with multiple rows, each with a status badge +- For at least one lost entry: curl https://tdd.md/goals/ → 200 with "Source could not be recovered" message +- For at least one lossy entry: curl https://tdd.md/goals/ → 200 with the "Recovered from conversation summary" warning visible +- curl https://tdd.md/sitemap.xml | grep -c /goals/ → matches the count of shipped + lossy + lost entries (lost entries DO get sitemap URLs — they're still indexable pages) +- /sama/v2/verify → 7/7 ✓ + +Load-bearing files to read FIRST: +- src/a31_goals.ts (GoalStatus type — extend with lossy + lost) +- src/b32_goals_meta.ts (frontmatter parser — verify it accepts the two new status values mechanically once STATUS_VALUES is extended) +- src/b32_goals_meta.test.ts (extend with two new status-parsing cases) +- src/d21_handlers_goals.ts (detail handler — branch on status: "lost" before file-read) +- The output of: flatpak-spawn --host gh pr list --state merged --limit 30 --json number,title,body,headRefName,mergedAt,mergeCommit +- The output of: flatpak-spawn --host gh pr view --json body for any PR whose `body` field in the list output looks /goal-shaped (saves a second fetch for the long body text) +- /var/home/scri/.claude/projects/-var-home-scri-Documents-tdd-md/memory/MEMORY.md (the auto-memory index) +- /var/home/scri/.claude/projects/-var-home-scri-Documents-tdd-md/memory/feedback_jolo_mode.md (existing pacing memory — new workflow memory's neighbor; cite it as related) +- .gitignore at repo root (add /goal.md line) +- content/blog/sama-v2-goal-chain-gap.md (the drama post that motivates this — re-read so the recovery narrative stays consistent)