Most of my product work lives in private repositories: LILA, and my employers'. In September 2026 I took the parts of that work that could stand alone and published them as five small packages. Each one is a method I had already used in production or in my own evidence, cleaned up so it runs anywhere, with tests, continuous integration on three operating systems, and a README that says what the tool cannot do.

At a glance

  • product-engineer: a pack of four agent skills for Claude Code, Codex, Cursor and the other agents npx skills add knows about. The main skill carries seven rules: restate the request as a customer outcome before building, end every commit and pull request with a plain-language "For the customer" block, treat "done" as observed rather than as "tests pass", write down what was deliberately not built, never print a number that was not counted, speak the stakeholder's language, propose the smallest change that moves the metric. Three smaller skills install the commit block, the observed "done" and plain-language release notes on their own. A check command refuses a message without the block and, since 0.4.0, resolves the file paths and run URLs the message cites, reporting the ones that lead nowhere while a message that cites nothing still passes; it ships as a commitlint plugin and a pre-commit hook; the plain-language table exists in Turkish, Japanese and Chinese. An evaluation of eight tasks, each run bare and with the skill, is in the repository with every transcript: the customer block went from 0 of 8 commits to 8 of 8, and two of the five metrics did not move and are printed anyway. On 17 October the commit-block and observed-"done" skills were each run on their own against a fresh bare arm, and each moved only the metric its rule is about (the block 0 to 8 of 8, the observation 1 to 6 of 8). npx product-engineer at 0.7.0.
  • proactive-gate: LILA's delivery gate as a zero-dependency library. Twelve checks in a fixed order, a reason on every rejection, the daily budget consumed by an atomic increment at send time, fail-open or fail-closed on store errors, and a replay command that runs a day of candidate messages through a policy and reports the allow rate and the silence reasons. Since 0.2.0 a policy is a JSON file with a schema, seventeen presets carry platform quotas and legal limits with their sources (LINE, WeChat, WhatsApp Business, Kakao, Korean and Japanese law, India's TCCCPR, Brazil's LGPD, TCPA, ePrivacy), four adapters plug into the AI SDK, Mastra, LangChain and OpenAI Agents, a Claude Code hook gates tool calls, and a Python sibling in the same repository passes the same 68 conformance fixtures, as does a Go implementation contributed from outside the project, with nothing declared skipped in any of the three. A bench replays a committed day of 21 candidate messages through the gate and through an honest hand-rolled policy: 11 sent against 13, with each disagreement traced to a daylight-saving boundary, a budget keyed to the wrong day, or two deliveries taking the last slot at once. Quiet hours can differ per weekday, so a Friday-and-Saturday weekend or a Friday-evening rule is a configuration rather than a custom check. Since 0.4.0 explain(decision) renders that same decision as sentences for the person deciding whether the assistant is too chatty rather than for the engineer holding the trace, built from the trace with nothing added and identical word for word in both languages, which a cross-language check in continuous integration compares on every push; it was contributed by someone I had never spoken to, who came back hours later with the India preset: TRAI sets no statutory quiet window, its Schedule-II keeps four of nine time bands off until the subscriber switches one on, and the "9am to 9pm" every secondary source repeats is in no primary text, which is why that preset is opt-in consents rather than a window. Brazil's LGPD, the WhatsApp Business rules and an async SQLite store followed the same evening, from the same person: seven contributions in a day, each checked before merging against the statute, the platform's own documentation or a real driver. The seventh is the one worth the sentence: twenty-one fixtures for the days a clock misbehaves, of which the two that failed turned out to be defects in my code rather than in his fixtures. He filed them and declared the failures rather than adjusting the tests, and reviewing them found a fourth. The shape of it is the part worth keeping: below the year 1000 the two siblings wrote different storage keys for the same instant, so one store serving both kept two sets of counters, the TypeScript weekly key collapsed every week of such a year into one bucket, and the monthly cap bound in Python and never in TypeScript. I reproduced all of that against the published 0.7.0 packages before writing it down, and checked the repair the same way: at 0.7.1 the same instant produces the same nine keys in both, on three dates across two months. Of its twelve defaults exactly one, the deferral constant, rests on a measured study, and the README names the trust ramp, the dismissal cooldown and the daily budget as judgement with no study behind them. Since 0.8.2, CI installs the packed package and runs one decision on Bun and Deno, and a separate check fails if the main entry imports a Node builtin. npm i proactive-gate at 0.9.0 and pip install proactive-gate at the same version, with a docs site and playground.
  • workproof: a verifiable engineering report from a repository you cannot show. Figures from git (tenure, commit share, cadence, footprint, tests and docs, surviving lines, and since 0.2.0 files authored, major-contributor components, commit size, co-authored commits, absence factor, AI-assisted commits and survival by cohort), the exact command behind each and what each cannot show, no code, no paths, no emails by default. Bot commits leave every denominator and generated, vendored and lock files leave every count. The hash covers canonical JSON, check recomputes it, verify compares integrity before figures, and attest writes a signed statement. The README publishes a table of how the figures respond to gaming, including the tool's worst case: an unmarked generated file takes surviving lines from 51.6% to 99.3%, while the same file marked linguist-generated leaves every count. Calendar figures are computed in the author's own weeks rather than in UTC, which changed published numbers and therefore report hashes; the repository says so rather than letting a stranger discover it. Since 0.5.0 one definition decides what a bot is, and it reads the commit address rather than the author name, so an address that merely begins with dependabot belongs to a person again; a bot whose address is outside the recognised forms is now counted as a contributor, which deflates the subject's share rather than inflating it. npx workproof at 0.7.0.
  • surviving-lines: the share of lines still alive at HEAD next to commit share, over a deterministic file sample. The method behind Showing ownership when the repositories are private, and workproof's only dependency. --markdown prints the table for pasting into an issue, contributed by someone I had never spoken to. npx surviving-lines at 0.4.0.
  • ai-slop-linter: a linter for the tells of machine writing in commit messages, pull request descriptions and READMEs. Twenty-one rules, nineteen of them from Wikipedia's guideline on signs of AI writing, each with a line number and a reason; fixes only where a fix cannot change the meaning; an Action, a commit hook and an agent skill. It lists tells and never guesses who wrote the text. The first run on my own READMEs gave one of them a C for bold labels in a rule list; that README was rewritten the same day to show the tool before explaining it, and the table in the linter's README keeps both scores. Precision is measured rather than asserted: five public-domain texts written before any language model grade A or B, five pieces of unedited model output grade F, and the eight findings on the human side are published rather than tuned away. Since 0.1.7 --format sarif feeds GitHub code scanning, so a finding survives the pull request it was found in; it was contributed from outside the project, and its test puts a surrogate pair before the finding so the asserted column pins the UTF-16 convention the output declares. npx ai-slop-linter at 0.2.0, with a browser playground that grades pasted text without installing anything.

Why these five

Each answers a question I had to answer for myself first. How do I describe my share of a codebase I cannot open, in a way a stranger can check? How does an assistant decide when not to speak, and how does a second team reuse that decision? How do I get a coding agent to explain a change to the person who asked for it? How do I catch the phrases that make a reader stop reading before the text is out the door? The packages are those answers with the private parts removed.

What the first audit found

On the evening of the day they were published I had all five read by an agent whose only instruction was to find where the tools misbehave for someone who is not writing English on an American setup, and to prove each finding by running it rather than by reasoning about it. It came back with six, and every one was real.

Two of them were failures, not rough edges. surviving-lines and workproof both exited with an error and produced no report at all on any repository holding a filename stored in decomposed Unicode, which is routine for Korean and common for French, Turkish, Vietnamese, Portuguese and Spanish names committed from Linux. The cause took some finding: macOS writes core.precomposeunicode=true into every repository git creates on it, and that setting rewrites command-line arguments, so a path read out of the tree no longer matched the tree when it was handed back. The linter's score was meaningless outside the Latin alphabet, because it counted words with a pattern that only matched Latin letters. A Japanese document of three thousand characters counted as one word, hit the fifty-word floor and graded F on a single tell whatever its length; Arabic and Hebrew counted zero.

The other four were quieter and, for a tool whose whole argument is that a stranger can trust the number, worse. workproof counted cadence in UTC weeks rather than the author's, so a developer in Auckland who worked two weeks appeared to have worked three. Author matching missed a name stored in a different Unicode normalisation, and İSMAİL YILMAZ could not match itself, because an uppercase dotted I lowercases to an i with a combining dot rather than to the Turkish dotless one. Fixed-width tables were padded by counting code units, so East Asian names pushed the numbers out of their columns. And the linter read an ordinary nested Markdown list as a spaced hyphen, which gave any README with nested bullets three errors and an F, on the day before it was to be shown to strangers.

A seventh finding came from reading rather than running. proactive-gate attributed its utility threshold to a system called PRISM. The system in the paper it cites is called Priorities, and PRISM appears neither in that paper nor on its author's publication index. The mathematics was never wrong; the name was, and a wrong citation in a README is a claim like any other.

All seven are fixed and released. Each fix carries a test built from the audit's own reproduction, and the two that changed published numbers say so in the changelog rather than leaving it to be discovered.

Fixes merged in other people's projects

The packages are mine. These are the other half: four fixes in projects I do not maintain, each reviewed and merged by that project's own maintainers, in three organisations.

  • langchain-ai/openwiki #635 — bundled skills could not sync from read-only installations such as Nix or immutable containers: the copy kept read-only mode bits, the atomic rename failed, and a staging directory leaked on every run. Merged on 12 August 2026 and released in v0.3.3, whose changelog credits me by name.
  • langchain-ai/openwiki #703 — on Windows, the onboarding tests wrote into the developer's real configuration directory, because os.homedir() reads USERPROFILE and ignores the test's HOME override. Merged on 24 August 2026; test-only, so no release applies.
  • mastra-ai/mastra #22903 — a scorer declared as a trajectory scorer was handed raw messages instead of a trajectory, and the resulting error was swallowed as a silent score of 0: a gate that always failed and never said why. Merged on 2 September 2026 and shipped in @mastra/core 1.64.0.
  • redis/ioredis #2214 — in Redis Cluster, a pipelined command answered MOVED, ASK or TRYAGAIN during a slot migration was retried only with the whole batch, and only if no write had succeeded; otherwise it never ran. The maintainer agreed the design, benchmarked it and added a commit before merging it on 20 October 2026. It is not yet in a published release.

One more pull request, to comet-ml/opik, is open and is not counted here.

Benchmarks, 24 October 2026

Two of the five tools were measured against other tools on 24 October 2026, each in its own write-up on dev.to with the protocol committed before the first run. I have not run this for workproof, product-engineer or proactive-gate, and nothing here says anything about them.

  • ai-slop-linter, write-up: 11 arms (the linter as a Claude Code plugin, six other skills that claim to remove AI-sounding prose, three of them also run with the prompt naming the skill, and Claude with no plugin), each task run three times on Sonnet 5.5, with a blind grader. On 12 public tasks the linter scored 58.67 of 60 and the best other arm, Humanizer called by name, scored 56.00; the difference is 2.67 with a 95% bootstrap interval of [1.00, 4.33]. It did not win every task: it trailed the best rival on two and tied on a third. On 6 held-out tasks, written by an author who had seen no code of the repository, it scored 29.67 of 30 against 30.00 for unslop, and four arms scored 29.67 or more, so that set does not separate them and the claim of first place does not hold there.
  • surviving-lines, write-up: six arms (the plugin, project-pilot 1.0.0 with and without the prompt naming its command, mcp-git-historian, busfactor, and Claude with no plugin), each task run three times on Sonnet 5.5. On 5 sealed tasks, written by an agent that had not seen the repository, it scored 9.67 of 25 against 7.33 for the best other arm; the difference is +2.33 with an interval of [+0.33, +4.33], which stays above zero only just, and 9.67 of 25 means every arm found these tasks hard. On 11 public tasks, which I had read while developing the tool, it scored 47.67 of 51 against 41.00, which is weaker evidence for that reason. It trailed a rival on 2 of the 5 sealed tasks and on 3 public ones, so the stricter per-task rule fails.

Both results apply to those task sets only and rank nothing in general. The tasks, transcripts and scripts are in each repository.

Where this stands

All five were published on 5 September 2026 and all five install from npm; each has shipped several releases since. This page used to say the version numbers lived on the npm pages rather than here, because here they would rot — and the five it still carried had rotted anyway, every one of them wrong by the time anybody looked. They are now compared against what npm and PyPI actually serve, along with the contribution counts, by a script that runs before a commit can be made; the honest fix for a number that goes stale is not to stop writing it down, it is to stop writing it from memory. proactive-gate's Python sibling is on PyPI at the same version, published by the same workflow. Every release is also archived on Zenodo with a permanent identifier, so a paper or a report can cite the exact code it ran and still resolve to it years later. Each is published by its own release workflow rather than from my machine, so npm carries build provenance linking the tarball to the commit and the workflow that built it.

Within hours of the good-first-issues going up, two people I had never spoken to sent four pull requests: a SQLite store and a weekly budget for proactive-gate, more languages and a --format flag for workproof. A third sent the Markdown output for surviving-lines later the same day. The next day one of them came back: told that his SQLite store had shipped and asking to be pointed at more work, he took the issue for exporting proactive-gate's store contract as a suite anyone can run against their own store, and had it merged the same day. A week later a fourth person arrived from the opposite direction. proactive-gate had filed an issue against itself, admitting that its SQLite store deleted an expired row only when a read touched it, and that the keys it writes most carry the date, so nobody ever reads yesterday's key and nothing ever deleted it. He took that issue and sent the fix twelve minutes later, in TypeScript and Python together, with the test whose absence had hidden the bug: advance the clock, do not read the dead keys, count the rows. It was verified before it merged rather than taken on trust — with only his four new calls removed, exactly his two tests fail and nothing else does — and the measurement he had not supplied was taken here: the sweep costs about one percent on the path the library actually runs. Released as 0.3.1 the same morning.

The fourteenth is the one the project needed most and the one I had least to do with. An issue had been open since the beginning asking for an implementation of the specification in another language, written by somebody who is not us — the only way to find out whether a specification is a specification or a description of one implementation's habits. The contributor who had sent the clock fixtures took it and opened a Go module six hours later: a conformance runner that reads the fixtures as data and imports nothing from the library, plus a list of nine places where the fixtures left something ambiguous and he had to guess. I installed Go and ran it here rather than reading the pull request and believing it — every fixture in the repository passes, none skipped, and the stub target shipped alongside fails every one of them with each mismatch named and a non-zero exit, which is how you can tell the runner would say so if it had something to say. The tell that it is not a port is small: his week numbering delegates to Go's own ISOWeek, where the TypeScript hand-rolls the Thursday pivot. Reviewing it found something the two existing implementations had agreed on for months without either being checked — no fixture had ever exercised the allowed-window check at all, which is in all three policy vocabularies and which eight presets are built on, the TCPA calling hours among them. An independent implementation could have had the closing minute wrong and still reported full conformance. There is a fixture for it now, which took the suite from 57 to 58, and 0.7.2 shipped it — so the gap that had gone unexercised for months is now pinned in the package people install, in all three implementations.

The fifteenth was the oldest thing open. A Postgres store had been asked for two days after the launch, and the first attempt at it went wrong in the way pull requests usually do — a rebase that took the branch somewhere neither of us wanted. He came back and redid it, and the second version is not the first with the conflicts cleaned up: the sweep that a later contribution had taught the SQLite store runs here on both writes, the counter increments and resets across a time-to-live boundary inside a single INSERT ... ON CONFLICT, so it stays atomic in one round trip rather than a read and a write that can interleave, the queries are parameterised, and creating the table takes an advisory lock. He had been waiting eleven days to see it merged. Two things arrived in the branch that were not his and were reverted — a pinned action rolled back because a dependency update had landed while he worked, and a formatter run against a style this repository does not have — and one index was added rather than asked of him for a fourth round.

The sixteenth is the one where the answer was no. I had asked whether the linter's English word lists could be matched in another language, and somebody took the question seriously enough to go and measure it: six families of candidate pattern tested against a published corpus of 7,043 Hindi news articles and the machine-written responses to them, every candidate rejected, with the reason written down for each. One phrase ran fifteen times more often in generated text and is still ordinary Hindi, which is the whole trap in one example. Another pattern was overwhelming in two models and absent from a third, which makes it a fact about those models rather than about the language. What arrived was a document saying no rule should ship, and a section on what evidence would change that. It ships as research, not as a rule, because the alternative was a rule that fires on people writing their own language correctly. The sentence I keep coming back to is his: where a measurement was not kept, the candidate is described as rejected rather than given a reconstructed number.

35 merged pull requests from seven contributors; each was reviewed, run through continuous integration and credited by name in the changelog. Twenty-seven were merged the day they arrived; the other eight took between 8 and 124 hours. The five repositories have fifteen stars between them: one is my own, two are from contributors, and five come from a single account that starred all five.

Two rules make that repeatable, and they are written into every contributing guide now rather than left as a habit. A contribution is checked rather than admired: the contract suite was run against three stores each broken one way before it was merged, and the throwaway code that did the checking was handed to its author. And the person is told when their work ships, with the version and the install command, because a merge is a promise and a release is the thing they can show somebody.

All five run the same maintenance setup: actions pinned to commits, least-privilege tokens, a package gate that installs the tarball before a release exists, workflow static analysis, an OpenSSF Scorecard, dependency updates that merge themselves when green, and a nightly job that records stars and downloads. One release command bumps the versions, dates the changelog and pushes the tag; the workflow publishes and then installs the published package from the registry on Linux, macOS and Windows to check it runs. Every figure in the READMEs comes from a real run on a named public repository at a named commit, so the examples can be reproduced. Adoption, if it comes, will be recorded on the stats page with the same scope-and-method rule as everything else on this site, and not before.