fix(continuity): wire scratch-commit-before-release and rewrite bootstrap prompt (Phase 4 items 3, 5, 6)
Release now re-verifies every handoff Anchor.Dirty file hash (previously unchecked after the top-level anchor SHA compare), snapshots dirty state onto a per-task scratch branch before uploading, and rewrites the anchor to the new commit so successor pickup collapses to a single HEAD compare. ScratchCommit made idempotent for repeated rotations of the same task. Bootstrap's prompt now points the agent at the scratch-branch commit history instead of vague "read the handoff" prose, and does not claim a GET /v1/artifacts/<ref> endpoint that doesn't exist. MarkdownChanges had zero callers and zero tests; deleted per AUDIT.md's explicit deletion option rather than half-wiring an undesigned feature. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
@@ -200,28 +200,6 @@ func Load(ref string, cas CAS) (Handoff, error) {
|
||||
return Decode(b)
|
||||
}
|
||||
|
||||
type Notice struct {
|
||||
Path string
|
||||
SHA256 string
|
||||
At time.Time
|
||||
}
|
||||
|
||||
func MarkdownChanges(root string, paths []string) ([]Notice, error) {
|
||||
out := []Notice{}
|
||||
for _, p := range paths {
|
||||
if !strings.HasSuffix(strings.ToLower(p), ".md") {
|
||||
continue
|
||||
}
|
||||
b, e := os.ReadFile(filepath.Join(root, p))
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
s := sha256.Sum256(b)
|
||||
out = append(out, Notice{p, hex.EncodeToString(s[:]), time.Now().UTC()})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ScratchCommit records WIP atomically on a dedicated branch before rotation.
|
||||
func ScratchCommit(root, branch, message string) error {
|
||||
if branch == "" || strings.ContainsAny(branch, " \t\n") {
|
||||
@@ -237,15 +215,24 @@ func ScratchCommit(root, branch, message string) error {
|
||||
if len(status) != 0 {
|
||||
return errors.New("TASK.md is immutable")
|
||||
}
|
||||
if strings.TrimSpace(message) == "" {
|
||||
return errors.New("scratch commit message required")
|
||||
}
|
||||
for _, args := range [][]string{{"switch", "-c", branch}, {"add", "-A"}, {"commit", "-m", message}} {
|
||||
if err := exec.Command("git", append([]string{"-C", root}, args...)...).Run(); err != nil {
|
||||
// Reuse the branch across rotations of the same task rather than failing
|
||||
// on "branch already exists" — a task can rotate more than once.
|
||||
if err := exec.Command("git", "-C", root, "switch", branch).Run(); err != nil {
|
||||
if err := exec.Command("git", "-C", root, "switch", "-c", branch).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if err := exec.Command("git", "-C", root, "add", "-A").Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
full, err := exec.Command("git", "-C", root, "status", "--porcelain").Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(full) == 0 {
|
||||
return nil // nothing to snapshot; branch already reflects the worktree
|
||||
}
|
||||
return exec.Command("git", "-C", root, "commit", "-m", message).Run()
|
||||
}
|
||||
|
||||
func ScratchPush(root, branch, remote string) error {
|
||||
|
||||
Reference in New Issue
Block a user