#!/usr/bin/env python3
"""handoff — living working memory for a workstream.

Maintained DURING a session by tiny targeted mutations, instead of reconstructed
afterwards by a six-minute archaeology pass.

    handoff init <slug>
    handoff add-discovery  "<fact>" ["<how it was found>"]
    handoff add-failure    "<what was tried>" ["<literal error / what to do instead>"]
    handoff add-decision   "<decision>" ["<reason>"]
    handoff add-instruction "<verbatim human words>" [standing|one-off|workstream]
    handoff add-question   "<question asked of the human>"
    handoff add-location   "<path>" "<what it is / why you go there>"
    handoff resolve        "<substring of the question>" "<resolution>"
    handoff supersede      "<substring of the old entry>" "<what replaced it, and why>"
    handoff prune          # move SUPERSEDED entries to ARCHIVE-<slug>.md
    handoff set-state      "<current state>"
    handoff set-next       "<next action / continuation point>"
    handoff show [section]
    handoff session <claude-session-id>   # record a contributing session

WHY IT IS SHAPED LIKE THIS
--------------------------
Cost. A session that just wrote a line already HAS that line in context — it does
not need to re-read the document to know what it says. So:

  append operations   -> zero reads. Just seek to a marker and insert.
  set operations      -> rewrite one delimited section, not the file.
  resolve             -> appends a RESOLVED line; never deletes the original.

Never deleting is deliberate. A contradiction between an early line and a later
one IS the information: it says the earlier belief was superseded. Measured on
2026-09-12, an agent reported a retracted figure whose correction sat six lines
away — so corrections must be loud and late, not silent edits.

Slug resolution: --slug, then $HANDOFF_SLUG, then .handoff-slug in cwd.
File: ~/session-handoffs/LIVE-<slug>.md
"""
import re
import os, re, sys, time

HOME = os.path.expanduser('~')
DIR = os.path.join(HOME, 'session-handoffs')

SECTIONS = [
    ('CONTINUATION', 'Continuation point', 'set'),
    ('STATE',        'Current state',      'set'),
    ('LOCATIONS',    'Working locations',  'append'),
    ('INSTRUCTIONS', 'Human instructions', 'append'),
    ('DECISIONS',    'Decisions',          'append'),
    ('DISCOVERIES',  'Expensive discoveries', 'append'),
    ('FAILURES',     'Tried and failed (do NOT re-attempt)', 'append'),
    ('QUESTIONS',    'Open questions / blocked on human', 'append'),
]

def path_for(slug):
    return os.path.join(DIR, f'LIVE-{slug}.md')

def resolve_slug(argv):
    if '--slug' in argv:
        i = argv.index('--slug'); s = argv[i+1]; del argv[i:i+2]; return s
    if os.environ.get('HANDOFF_SLUG'): return os.environ['HANDOFF_SLUG']
    for d in (os.getcwd(), HOME):
        f = os.path.join(d, '.handoff-slug')
        if os.path.isfile(f):
            v = open(f).read().strip()
            if v: return v
    return None

def record_session(slug, sid):
    """Append a contributing session id to the frontmatter, idempotently.

    A workstream OUTLIVES any single Claude session: every rollover (/clear +
    /session-start) mints a new session id while the handoff continues. So a single
    session_id field would name only the session that happened to create the file and
    would be stale from the first rollover onward.

    The lineage is what makes a handoff auditable — given this file you can find every
    transcript that fed it, in order, and go read the raw record of any claim in it.
    """
    if not sid: return
    p = path_for(slug)
    if not os.path.exists(p): return
    s = open(p).read()
    if sid in s: return                      # already recorded
    m = re.match(r'(---\n.*?)(\n---\n)', s, re.S)
    if not m: return
    head, close = m.group(1), m.group(2)
    if 'sessions:' not in head:
        head += '\nsessions:'
    head += f'\n  - {sid}          # {time.strftime("%Y-%m-%d %H:%M")}'
    open(p, 'w').write(head + close + s[m.end():])

def init(slug):
    os.makedirs(DIR, exist_ok=True)
    p = path_for(slug)
    if os.path.exists(p): return p
    out = [f'---\nslug: {slug}\nkind: living-handoff\nstarted: {time.strftime("%Y-%m-%d %H:%M")}\n---\n',
           f'\n# Living handoff — {slug}\n',
           '\n_Maintained during the session by `handoff` operations. Append-only by design:\n'
           'a later entry that contradicts an earlier one means the earlier one was superseded._\n']
    for key, title, _ in SECTIONS:
        out.append(f'\n## {title}\n<!-- {key}_START -->\n<!-- {key}_END -->\n')
    open(p, 'w').write(''.join(out))
    return p

def load(slug):
    p = path_for(slug)
    if not os.path.exists(p): init(slug)
    return p, open(p).read()

def stamp():
    """Timestamp, plus a short session tag when one is known.

    Multiple agents can work the same worktree concurrently, and they all resolve to
    the same slug — which is correct, it IS one workstream — but their entries would
    otherwise be indistinguishable. Four characters is enough to disentangle two or
    three concurrent sessions, and cheap enough to put on every line. The full ids are
    in the frontmatter lineage.
    """
    sid = os.environ.get('HANDOFF_SESSION', '')[:4]
    return time.strftime('%m-%d %H:%M') + (f' {sid}' if sid else '')

def append(slug, key, line):
    p, s = load(slug)
    end = f'<!-- {key}_END -->'
    if end not in s:
        s = s.rstrip() + f'\n\n## {key.title()}\n<!-- {key}_START -->\n{end}\n'
    s = s.replace(end, f'{line}\n{end}', 1)
    open(p, 'w').write(s)
    return p

def upsert_location(slug, path_, what):
    """Locations are keyed by path, not by time: re-stating one REPLACES its line.

    Appending here would grow a list of near-duplicates of the same file, which is
    the opposite of what a reader wants from a navigation section. Everything else
    in this format is append-only because a later contradicting entry IS the record
    that a belief changed; a path is not a belief, it is an address."""
    p, s = load(slug)
    line = f'- `{path_}` — {what} _{stamp()}_'
    pat = re.compile(r'^- `' + re.escape(path_) + r'` —.*$', re.M)
    if pat.search(s):
        s = pat.sub(lambda _m: line, s, count=1)
        open(p, 'w').write(s)
        return p
    return append(slug, 'LOCATIONS', line)

def set_section(slug, key, body):
    p, s = load(slug)
    a, b = f'<!-- {key}_START -->', f'<!-- {key}_END -->'
    if a in s and b in s:
        s = re.sub(re.escape(a) + r'.*?' + re.escape(b),
                   f'{a}\n_{stamp()}_\n{body}\n{b}', s, flags=re.S)
    else:
        s = s.rstrip() + f'\n\n## {key.title()}\n{a}\n{body}\n{b}\n'
    open(p, 'w').write(s)
    return p

def main():
    argv = sys.argv[1:]
    if not argv or argv[0] in ('-h', '--help'):
        print(__doc__); return 0
    cmd = argv.pop(0)
    slug = resolve_slug(argv)

    if cmd == 'session':
        # handoff session <session-id> [--slug X] — record a contributing session.
        if not slug: print('no slug'); return 1
        record_session(slug, argv[0] if argv else '')
        return 0

    if cmd == 'init':
        slug = argv[0] if argv else slug
        if not slug: print('handoff init <slug>'); return 1
        p = init(slug)
        # NEVER pin at $HOME. `live_slug` falls back to ~/.handoff-slug, so a pin there
        # is not one workstream's pin — it is every home-rooted session's pin. Written
        # by accident on 2026-09-16 while testing from ~, it was then made permanent in
        # 8 unrelated sessions by bind_slug, against a handoff that had been deleted.
        # This is the second time this exact file has hijacked unrelated sessions.
        cwd = os.getcwd()
        if os.path.realpath(cwd) == os.path.realpath(HOME):
            print(f'living handoff: {p}')
            print('NOT pinning: ~/.handoff-slug would claim every session rooted at $HOME.')
            print(f'Use it explicitly instead:  handoff <cmd> --slug {slug}')
            print('or run `handoff init` from the project directory it belongs to.')
            return 0
        open(os.path.join(cwd, '.handoff-slug'), 'w').write(slug + '\n')
        print(f'living handoff: {p}\nslug pinned in ./.handoff-slug'); return 0

    if not slug:
        print('no slug — run `handoff init <slug>` or set HANDOFF_SLUG'); return 1

    a = lambda i, d='': argv[i] if len(argv) > i else d

    if cmd == 'add-discovery':
        # the writer often phrases b as "found via X" itself; don't say it twice
        _h = re.sub(r'^\s*(found\s+via|discovered\s+via|via)\s+', '', a(1), flags=re.I) if a(1) else ''
        how = f' — _found via {_h}_' if _h else ''
        p = append(slug, 'DISCOVERIES', f'- `{stamp()}` {a(0)}{how}')
    elif cmd == 'add-failure':
        fix = f'\n  - → {a(1)}' if a(1) else ''
        p = append(slug, 'FAILURES', f'- `{stamp()}` {a(0)}{fix}')
    elif cmd == 'add-decision':
        why = f' — _because {a(1)}_' if a(1) else ''
        p = append(slug, 'DECISIONS', f'- `{stamp()}` {a(0)}{why}')
    elif cmd == 'add-instruction':
        scope = a(1, 'standing')
        p = append(slug, 'INSTRUCTIONS', f'- `{stamp()}` **{scope}** — "{a(0)}"')
    elif cmd == 'add-location':
        p = upsert_location(slug, a(0), a(1, 'no description given'))
    elif cmd == 'add-question':
        p = append(slug, 'QUESTIONS', f'- [ ] `{stamp()}` {a(0)}')
    elif cmd == 'prune':
        # Move SUPERSEDED entries out of the live file into a sibling archive.
        #
        # Safe precisely BECAUSE supersede exists: every entry moved has a newer
        # entry that already replaced it and says so. Nothing is deleted — a
        # superseded decision still carries "we tried this and moved away", which
        # is what stops the next session re-proposing it.
        #
        # The live file is what /session-start loads and what the worker re-reads
        # every run, so this is the lever that keeps an ageing workstream cheap.
        p, s = load(slug)
        arch = p.replace('LIVE-', 'ARCHIVE-')
        lines = s.splitlines()
        keep, moved, i = [], [], 0
        while i < len(lines):
            ln = lines[i]
            if ln.startswith('- '):
                block = [ln]; j = i + 1
                while j < len(lines) and lines[j].startswith('  '):
                    block.append(lines[j]); j += 1
                if any('SUPERSEDED' in b for b in block):
                    moved.append('\n'.join(block))
                else:
                    keep.extend(block)
                i = j; continue
            keep.append(ln); i += 1
        if not moved:
            print('nothing superseded to prune'); return 0
        header = '' if os.path.exists(arch) else \
            f'# Archive — {slug}\n\n_Superseded entries moved out of the living handoff.\n' \
            f'Each was replaced by a newer entry that names it. Kept because "we tried this\n' \
            f'and moved away" is itself worth knowing._\n'
        with open(arch, 'a') as f:
            f.write(header + f'\n## pruned {stamp()}\n' + '\n'.join(moved) + '\n')
        out = '\n'.join(keep) + '\n'
        note = f'\n_{len(moved)} superseded entries archived to `{os.path.basename(arch)}`._\n'
        if '_superseded entries archived' not in out.lower():
            out = out.replace('\n## Continuation point', note + '\n## Continuation point', 1)
        open(p, 'w').write(out)
        print(f'pruned {len(moved)} superseded entries -> {os.path.basename(arch)}')
        return 0

    elif cmd == 'supersede':
        # MOVE the superseded entry out of the live file, into ARCHIVE-<slug>.md.
        #
        # This used to append a strikethrough line under the old entry and leave it
        # in place, with a separate `prune` step to archive it later. Measured
        # 2026-09-15: pruning a 33,306-byte handoff reclaimed 862 bytes — 2.6%.
        # Marking in place is not a size lever, it is just a third copy of the fact.
        #
        # Nothing is lost: the block is appended to the archive with the reason it
        # was replaced, so "we believed this and moved away" is still recoverable —
        # it is simply not carried by every worker run and every rollover, which is
        # the cost that actually recurs. The live file holds what is TRUE NOW.
        p, s = load(slug)
        needle, why = a(0), a(1)
        lines = s.splitlines()
        keep, moved, i2, hit = [], [], 0, False
        while i2 < len(lines):
            ln = lines[i2]
            if not hit and ln.startswith('- ') and needle.lower() in ln.lower():
                block = [ln]; j2 = i2 + 1
                while j2 < len(lines) and lines[j2].startswith('  '):
                    block.append(lines[j2]); j2 += 1
                moved.append('\n'.join(block) + f'\n  - _superseded {stamp()}: {why}_')
                hit = True; i2 = j2; continue
            keep.append(ln); i2 += 1
        if not hit:
            print(f'no entry matching "{needle}"'); return 1
        arch = p.replace('LIVE-', 'ARCHIVE-')
        header = '' if os.path.exists(arch) else \
            f'# Archive — {slug}\n\n_Entries that were replaced by later ones. Each says what\n' \
            f'superseded it and when. Kept because "we believed this and moved away" stops\n' \
            f'a future session re-proposing it._\n'
        with open(arch, 'a') as f:
            f.write(header + '\n' + '\n'.join(moved) + '\n')
        open(p, 'w').write('\n'.join(keep) + '\n')
        print(f'superseded 1 entry -> {os.path.basename(arch)}')
        return 0

    elif cmd == 'resolve':
        p, s = load(slug)
        needle, res = a(0), a(1)
        hit = False
        out = []
        for ln in s.splitlines():
            if not hit and needle.lower() in ln.lower() and ln.strip().startswith('- [ ]'):
                out.append(ln.replace('- [ ]', '- [x]', 1))
                out.append(f'  - **RESOLVED** `{stamp()}` — {res}')
                hit = True
            else:
                out.append(ln)
        open(p, 'w').write('\n'.join(out) + '\n')
        if not hit: print(f'no open question matching "{needle}"'); return 1
    elif cmd == 'set-state':
        p = set_section(slug, 'STATE', a(0))
    elif cmd == 'set-next':
        p = set_section(slug, 'CONTINUATION', a(0))
    elif cmd == 'show':
        p, s = load(slug)
        if argv:
            k = argv[0].upper()
            m = re.search(re.escape(f'<!-- {k}_START -->') + r'(.*?)' + re.escape(f'<!-- {k}_END -->'), s, re.S)
            print(m.group(1).strip() if m else f'no section {k}')
        else:
            print(s)
        return 0
    else:
        print(f'unknown command: {cmd}'); print(__doc__); return 1

    print(f'ok → {os.path.basename(p)}')
    return 0

if __name__ == '__main__':
    sys.exit(main())
