Skip to content

Releases: TGPSKI/catnip

catnip 0.4.0 — windows end on a day GitHub has finished counting

Choose a tag to compare

@TGPSKI TGPSKI released this 09 Aug 20:47
v0.4.0
dc2d759

GitHub keeps adding counts to a day for well over a day after it closes, and
documents none of it. Every window catnip drew ended on days that were still
being counted, and the tannery agent that was supposed to notice reported
success without running anything.

Added

  • CATNIP_SETTLE_HOURS — how long after a day closes GitHub is still
    adding to it. 36 is a measured floor: the variable raises the wait for an
    account that settles slower and cannot lower it, because a short wait
    costs nothing visible and publishes part of a day as all of it.
  • catnip.runfiles — one reader for a run directory's raw payloads,
    shared by the four traffic analyses. Each had its own copy of the
    raw/repo_<slug>_<endpoint>.json convention, and each copy inlined
    config.slug_for without its sanitizing step: a repo whose name carries
    any character outside [A-Za-z0-9_.-] resolved to a filename the fetcher
    never wrote, is_file() returned False, and the repo left anomaly,
    profile, funnel and correlation output with no error and no row. No repo
    on this account triggers it today.
  • meta.json records window_digest, settle_hours and
    store_latest_day.

Changed

  • Windows move back. No window ends inside the settling wait, so at a
    midday cron 1d covers the day before yesterday. The TUI header names the
    day it is showing instead of calling it "last day", and the report's
    provenance table carries a settled through row.
  • Correlation is more conservative. A repo whose third non-zero day was
    an unsettled one no longer qualifies for the pair scan.
  • Upgrading is handled, not waited out. meta.json changed what
    latest_day means: reports written before this recorded the store's
    newest day, which is a fetch day and ahead of what those reports covered.
    Comparing a settled day against it suppresses reports for exactly the
    cycles the store is gaining settled days, so a meta.json without
    settle_hours is recognised as pre-settling and does not gate the next
    report.
  • The tannery needs leather v0.5.3 or newer. Older builds parse
    require_tool: as prompt text instead of enforcing it.

Fixed

  • No window ends on a day GitHub has not finished counting. The last-day
    view printed a column of zeros ranked alphabetically, because the newest
    day in the store is the day the fetch ran and GitHub returns that day as a
    flat zero. Re-reading one day out of four run directories, over the same
    35 repos every time, it carried 33% of its final views and 47% of its
    final clones at 12h after closing and was complete at 30h; days older than
    that were identical in every read. derive.settled_day ends every window
    settle_hours() behind the newest fetch — hours, not a day count, because
    the same "two days back" means different amounts of settling depending on
    what hour the timer fires. docs/metrics.md carries the measurement and
    #5 tracks making it
    self-checking.
  • The edge is never the last day carrying traffic. Trimming trailing
    zeros was the tempting fix and is wrong: this account recorded ten
    genuinely empty days in June, and reading a quiet day as an unfinished one
    slides every window a day left without saying so. The previous code did
    exactly that on the CSV path, where it had been dead for as long as a
    store existed.
  • Trend arrows were biased downward. totals.py summed clones_7d over
    a window ending on the fetch day while clones_prior_7d was complete, so
    the comparison measured the shape of the collection.
  • The per-run analyses no longer score days GitHub is still counting. A
    run filed a minor dip against a day eighteen hours old. The anomaly
    detector, clone profiler and correlation analysis read settled days now,
    which also lifts their baselines onto complete data.
  • catnip report no longer treats a fetch as new data, and no longer
    freezes a day it got wrong.
    The interval guard compared the store's
    newest day, which advances on every run whether or not GitHub counted
    anything. It compares settled days now, and records a window_digest of
    the numbers each report stated so a day revised after publication reopens
    the report. Without it, a report written before a day settled asserted a
    third of that day's traffic forever, because the only question asked was
    whether a new day had appeared.
  • catnip prune keeps runs whose days may still be revised. Ingested
    means a run's numbers are in the store, not that they were the final
    numbers. The run directory is the only record of what one fetch saw, and
    history --rebuild reconstructs from surviving runs alone.
  • A tannery turn that must act can no longer end on prose. The collect
    agent answered "catnip-run call succeeded" without calling it on
    2026-08-08 and 2026-08-09, finished in 1.4s instead of 41 minutes, and
    recorded an invented latest_day: 2024-01-15 over an untouched store, as
    success. Replayed against the same served model the tool was in scope
    every time, and the model answered in prose in 4 of 16 samples. Every
    acting turn declares require_tool:, and record turns write unmeasured
    with action: failed when a {{value}} arrives unsubstituted.
  • The tannery no longer does date arithmetic in the model.
    catnip-store-status emits settled_day, settled_expected and a
    stale: yes|no verdict, and the collect agent's rule is "failed if stale
    is yes". A model comparing dates to "yesterday" is arithmetic nobody
    checks, and it returned a passing verdict on both failed nights.

catnip 0.3.0 — the tannery sizes its own hunt

Choose a tag to compare

@TGPSKI TGPSKI released this 07 Aug 23:20
v0.3.0
9ca0466

Added

  • An editor stands between assembly and the published prowl.md. Analyst
    packages accumulate into a staged assembly; each recorded package hands
    the assembly to an editor that merges same-phenomenon blocks, orders the
    document to argue account-wide first, and cuts restatements. Its publish
    is the only writer of prowl.md and is guarded twice: a document whose
    tier counts differ from its own stamped counts is refused (the editor
    arranges, it does not drop or invent findings), and one whose stamp
    trails the cycle's record is skipped as superseded — a fresher edit is
    already queued behind it.
  • Self-graded analysis depth. A meta-analyst reads the deterministic
    report every third day, judges how much investigation the cycle deserves,
    and seeds N analyst briefs — one per distinct phenomenon, N being its
    call. Each brief runs as its own analyst; packages fan back in through the
    writer, whose per-cycle files accumulate and whose publish dedupes, so
    collation needs no join queue and a failed angle costs only itself. The
    dispatch and recording ends are both single deterministic calls that parse
    blocks themselves — no model ever counts its own fan-out.

Changed

  • The served chain is event-driven; only the fetch and the analysis are on
    cron.
    0.2.1 scheduled the report at 06:52 and sized the gap so a
    41-minute collect would probably have finished — clock arithmetic standing
    in for a dependency. The report now runs from a curing fed by collect's
    output, and the prowl writer from a curing fed by the analysis, so a slow
    fetch delays the report instead of losing it. Routing is one fact per
    stage: the producer's intake URL and the consuming curing name the same
    queue. The leather ingest targets remain for one-shot testing and agent
    validation; the served chain never uses them.

  • The prowl analysis is recorded by one deterministic call. The 0.2.1
    writer made one tool call per FINDING/REFUTED block, which put a
    data-dependent number of calls on the model: three calls against four
    findings drops one silently — no error, just a shorter report.
    catnip-prowl-record replaces catnip_prowl_finding, catnip_prowl_refuted
    and catnip_prowl_publish: it takes the whole analysis, parses the blocks
    itself, files them all or fails naming the block it refused, and publishes.
    Validation is unchanged — tier enum, evidence opening with the catnip tool
    the claim rests on, publish refusing an empty cycle.

  • Every measured value in a state file is now carried by an extract rule.
    Coverage, repo count, schema version, the report's meta fields and the
    recorder's counts reach their recording turns as {{...}} verbatim instead
    of as a model's transcription — 0.2.1 wrote a Python repr of the coverage
    ranges where the store's own JSON belonged. The model writes only judgment:
    action, reason, a finding's prose.

  • catnip-report's verdict rules moved into the turn that owns the
    writer.
    The agent frontmatter is the system prompt and is resent every
    turn, so rules about catnip-report-write fired on turns that could not
    reach the tool. Output rules now live with the tools that produce them.

  • tannery/README.md rewritten for someone deciding whether to run this,
    in the shape of leather's example READMEs.

Fixed

  • The prowl writer dead-lettered every scheduled cycle. The 0.2.1
    lifecycle routed the analysis with output: type: queue, which builds a
    queue item with no hide behind it; the curing loads the item's hide
    unconditionally and failed each attempt. The route is now type: http to
    leather's /intake, which stores the hide first and enqueues against it.
    This tannery had never successfully recorded a prowl analysis until now.

catnip 0.2.1 — a leather tannery, and a fix it surfaced

Choose a tag to compare

@TGPSKI TGPSKI released this 07 Aug 23:26
v0.2.1
e2c6349

Added

  • tannery/ — a leather tannery. catnip-collect runs the fetch daily
    at 05:07, catnip-report writes the deterministic report an hour and
    forty-five minutes behind it, and catnip-prowl hunts every third day.
    The gaps are headroom, not estimates: the fetch is serial and paginated so
    its duration tracks repo count. Overrunning one is safe by construction —
    the store has not advanced, the interval guard refuses, and the report
    records skipped rather than describing yesterday as today.

  • Per-turn tool scoping, which is the reason to use leather here at all.
    Each agent is multi-turn and a turn replaces its tool scope rather than
    extending it, so an agent reaches only what its current turn declares:

    catnip-collect   catnip-pipeline -> catnip-inspect -> catnip-record
    catnip-report    catnip-inspect  -> catnip-report-write -> catnip-record
    catnip-prowl     catnip-evidence -> catnip-file -> catnip-publish
    

    catnip-collect cannot write state while the pipeline turn runs.
    catnip-report cannot reach the writer before reading the store's newest
    day. catnip-prowl cannot file during the turn it gathers evidence and
    cannot gather more once filing starts, so everything it files came from
    evidence already in context. That replaced a paragraph asking the agent to
    test before filing: there is now no turn in which it can do otherwise.

  • Provenance as argument validation. catnip_prowl_finding rejects any
    tier outside measured|inferred|speculative and any evidence that does
    not name the catnip tool the claim rests on. No path through the toolset
    records an untagged or unsourced claim. Refutations have their own tool,
    because a cycle with none tested nothing, and publishing fails on an empty
    cycle rather than writing an empty file.

  • make smoke-tools execs every read-only tool's real argv straight from
    shell-tools.json, so an argv or quoting regression surfaces before the
    scheduler hits it at 05:07 with nobody watching. It is what caught
    catnip view why.

  • --why-view, and catnip view why <view> in the dispatcher.

Changed

  • Funnel views/uniq renamed to pgviews/pguniq. They sum GitHub's
    top ten paths and are not repo traffic, but wore names every other view
    uses for something else. One repo read 10/10 where its real figures were 19
    views from 1 unique visitor: pguniq counted one person once per page, and
    pgviews caught ten of nineteen because ten paths is all the API returns.
    Re-sourcing them from the store would have been worse — the store's window
    and the paths snapshot genuinely disagree, so real windowed traffic beside
    path-derived depth trades a naming problem for an arithmetic one.
  • bin/catnip runs inside main(). bash reads a script incrementally,
    and catnip run is a 41-minute job; rewriting the file in place mid-run
    moves the bytes under bash's stored offset. Measured: an unwrapped script
    loses the rest of its body, a wrapped one always completes. Not a total
    fix — bash may still read garbage after main returns and exit non-zero
    having done all the work. Treat a trailing syntax error from a run that
    otherwise reported success as this, and check the store, not the exit code.

Fixed

  • Tannery timeouts are sized from a measurement instead of the first
    draft's guess.
    The draft of this release bounded catnip_run at 2100
    seconds without measuring anything; a real run took 2459. Shipped as
    drafted, shell-mcp would have SIGKILLed the pipeline 35 minutes into its
    first scheduled cycle and the agent would have reported a timeout. Now
    5400s/6000s/6600s, innermost-first, roughly twice the measurement. The
    same draft told the agent to expect "10–20 minutes", which would have made
    a normal 41-minute run read as anomalous; the prompt now states the
    measured duration.
  • catnip view why <view> had never worked. It took the view name from
    --timeframe, which argparse restricts to 1d/1w/2w/all/epoch, so the
    name was rejected and a bare view why asked derive for the derivation of
    "2w". It is how both the README and the catnip-prowl skill say to read
    a derivation from a shell — the third instance of the same defect after
    catnip report shipping undispatched and catnip config --json colliding
    with its own mutually-exclusive group.
  • Printing a derivation no longer requires a populated store. A
    derivation is documentation, not data; it rode the same path as the views
    and so failed on a fresh install — exactly when someone asks what a number
    will mean before collecting any.
  • attribution() returned different keys depending on whether the window
    was empty
    , and the view read the missing one unconditionally, so an empty
    window raised KeyError mid-render inside curses. The test asserts the two
    returns carry identical key sets rather than the one key that was missing.
  • Config tests compared a resolved path to an unresolved tmpdir, which
    fails only on macOS, where /var is a symlink to /private/var. Caught by
    the cross-platform matrix, which runs on main and on any PR carrying the
    full-test label.

catnip 0.2.0 — every number from the durable store

Choose a tag to compare

@TGPSKI TGPSKI released this 07 Aug 05:37
v0.2.0
5905003

GitHub keeps fourteen days of your traffic data. catnip keeps all of it.

Stdlib Python 3.10+ and gh. No service, no build step, no data leaving your machine.

git clone git@github.com:TGPSKI/catnip.git && cd catnip && make install
gh auth login --scopes repo
catnip init && catnip run && catnip tui

The change

Every windowed number is now computed from the permanent daily store rather than GitHub's rolling 14-day totals. The old views differenced two snapshots of a window that loses its oldest day every night, so they reported window artifacts as change — large negative clone deltas for repos where nothing had happened, and a timeframe selector several views ignored outright.

One derive.py holds every formula, shared by the TUI, catnip view and catnip report, so the three cannot disagree about the same day.

Added

  • Ten views, eight new or rebuilt: traffic, audience, table, lang, attribution, deltas, anomaly, profile, correlation, funnel.
  • Five detail screens behind [space] — repo drilldown, attribution finding, momentum, account event, correlated pair, and a repo's actual pages.
  • [?] derivation overlay on every view — formula, thresholds, inputs, and which components were withheld for want of evidence.
  • catnip report — deterministic markdown from the store. Same store and timeframe produce byte-identical output; every figure tagged measured.
  • Three agent skillscatnip-onboarding, catnip-triage, and catnip-prowl, which runs the deterministic report and then forms and tests its own hypotheses, tagging each claim measured, inferred or speculative in a separate file.
  • Store schema 3 ingests popular paths. /traffic/popular/* rolls like the traffic endpoints, so paths that lived only in run directories were destroyed by catnip prune and could never be refetched.

The rule underneath

Absence is never rendered as zero. Withheld audience components are dropped and the weights renormalized. A repo too thin to classify is low-signal, not mixed. A day with no recorded push says no cause recorded in the store, which is not the claim that nothing shipped. unexplained is a first-class attribution tier, because most traffic has no visible cause and a tool that always names one is fitting noise.

Notes

The drawing layer is pane 0.2.0, vendored byte-identically. The demo recording uses generated data — only the API responses are synthetic; analyze, history, totals and derive all run for real on top of them.

226 tests, stdlib only.