Ports the technique #11 asked for and ships the script #19 asks for: was this tried, what killed it, and is the killer still alive. The RED run did excellent archaeology unaided — fourteen steps, correct verdict, and it split the proposal into a goal worth keeping and a mechanism that had failed, which is the finding. What it never ran was `git log -S`, and what it leaned on hardest was the issue tracker. Both facts shaped the skill. The GREEN run then broke the tool's central claim, live. Asked about a feature this repo removed and argued about at length, `lineage.sh "per repo"` printed: no trace of "per repo" — that is a real finding: it means this is new here Flatly wrong; the term simply had a space in it. Worse, the skill's own anti-pattern table forbids that exact inference — "assuming absence means it was never considered" — so the tool was printing, in its own voice, the mistake the skill warns against. A no-match now says it is one string against one repo, and names the variants to try before anyone concludes novelty. The test asserts the refusal rather than the old message. Three more from the same run: - The tool searched `docs/adr/*` for decision records. This repo has no `docs/`, and this estate keeps decisions in the brain and in gitea comments — so that section silently searched nothing four times, and its null was indistinguishable from a real one. It now names both unsearchable sources on every run, and the skill makes `brain_query` a step: the single best artifact in the GREEN run came from the brain and nowhere else. - Step 1 steered readers toward path-scoped log and away from `-S`. Backwards. A function deleted from a file that still exists is the dominant removal shape, and `-S<identifier>` returns birth and death on one screen while the path search buries it. Rewritten to lead with the identifier. - "Which conditions still hold" had no method, and the run answered it by running commands against the live host in twenty seconds. Now a table: one command per claim, because "the record says X" and "X is true right now" are different answers and only the second is worth giving. Also fixed: the tool defaulted to the cwd and would return a confident null against the wrong repo in a worktree-heavy setup. It now prints which repo it searched, every run. Closes #11 Bump-Type: minor
8.5 KiB
name, surfaces, summary, use_when, description
| name | surfaces | summary | use_when | description | ||
|---|---|---|---|---|---|---|
| lineage |
|
Backward decision archaeology — was this tried before, and does the reason it failed still hold | Before proposing something that feels new; when an idea keeps resurfacing; when you inherit code whose reason is gone | Backward decision archaeology. Find out whether an idea was already tried here, why it was dropped, and whether that reason still applies — using git history, decision records and the issue tracker rather than asking whoever is still around. Use before proposing a change that feels novel, when inheriting unexplained code, or when the same idea keeps being re-litigated. |
Lineage
Overview
Most "new" ideas in an established codebase are revivals. The valuable question is never "is this a good idea" in the abstract — it is "was this tried, what killed it, and is the killer still alive?"
A past failure is not evidence against trying again. It is context for trying again differently, and the difference between those two readings is usually one commit message.
When to Use
- Before proposing something that feels new in a codebase older than you
- When an idea keeps resurfacing and being re-argued from scratch
- When you inherit code and cannot tell whether a constraint is load-bearing or vestigial
- Before writing an ADR — the reason a thing was dropped belongs in your Context section
Do not use for: code you wrote this week, or a question the current code answers. Archaeology on a live construction site is just reading.
The tool
skills/lineage/lineage.sh <term> [repo-path]
Runs the searches people skip because they are fiddly to type. In particular
git log -S<term>, which finds commits where the term's occurrence count changed —
so it surfaces the removal as well as the introduction. A plain --grep never finds a
deletion, and the deletion is the whole point.
It also lists files that once carried the term and no longer exist, and names the two sources it cannot search for you.
A no-match is not a finding. The first version of this tool said a null meant "this is new here"; a pressure test caught it asserting exactly that about the most-discussed removed feature in this repo — the search term merely had a space in it. One string against one repo proves nothing. Try at least three variants (the identifier name, a one-word form, the name it would have had) and the brain and tracker searches before telling anyone an idea is novel. This is the same rule as the anti-patterns table below, and it is there because the tool broke it.
Note also that --grep takes a regex and -S takes a literal string, so a term
containing ., [, * or + matches differently in the two halves of the output.
The sequence
1. Search on the identifier, not the concept. If the thing had a name in the code —
a function, a flag, a config key — git log --all -S<name> typically returns exactly two
commits, its birth and its death, on one screen. That is the fastest path to the answer in
this whole sequence.
Path-scoped log (git log --all -- <file>) is the weaker move and it is the one people
reach for: a function deleted from a file that still exists is the dominant removal
shape, and the path search buries it under every other commit that touched the file.
Concept words are the fallback, and they are lossy — a term with a space in it will miss the record entirely. Try several spellings before you conclude anything.
2. Read the removal commit's full body, not its subject.
git log -1 --format=%B <sha>
git show --stat <sha>
This is where the answer almost always is. A subject line says what; the body says why, and the why is what you came for. Read the diff too — what was removed alongside it tells you how load-bearing it was.
3. Search outside git — that is where this estate keeps its reasoning. Two sources, both routinely decisive, neither reachable from a commit log:
- The brain (
brain_query). Postmortems and generalized rules live there. In the run that shaped this skill, the single best artifact — the rule abstracted from the incident, with detection one-liners — came from the brain and from nowhere else. - The issue tracker, including comments. The commit records that something was removed; the argument for it is usually in a comment. Search closed issues especially — a closed issue is where a decision usually is.
A git-only search finds the what and misses the why.
4. Test whether the failure conditions still hold — empirically, not from the record. This is the step that makes the whole exercise worth doing, and it is the one that gets hand-waved. Write the conditions down as separate claims, then run a command per claim:
| Claim from the record | How you check it today |
|---|---|
| "absolute links break across two path roots" | readlink one, test -e it on this host |
| "it is gitignored, so nobody sees the rot" | grep the ignore file |
| "the detector does not cover it" | grep the detector for the path it scans |
Twenty seconds each, and it converts "the record claims X" into "X is true right now" — which is the only form of the answer worth giving. A constraint that has since been removed turns a dead idea live; one that is still true kills a revival on the spot.
5. Separate the mechanism from the goal. A revival proposal usually bundles both, and they can have opposite verdicts: the goal is still worth wanting, the mechanism is the one that failed. Splitting them is often the whole finding — you end up agreeing with the proposer and rejecting their implementation, which is a far more useful answer than yes or no.
What "it was tried and failed" can mean
| Reading | Tell | What to do |
|---|---|---|
| Structurally impossible | The removal commit says why it cannot work, not that it broke | Believe it. Look for a different mechanism for the same goal |
| Failed under conditions that changed | The blocker names a version, a host, a tool that has since moved | Re-open it, citing what changed |
| Failed on execution | It broke, was reverted, nobody diagnosed it | Genuinely undecided. The revival is legitimate; budget for the diagnosis |
| Never actually tried | Discussed, an issue exists, no commit | There is no evidence either way. Say so rather than inheriting someone's pessimism |
| Still there, in a different shape | The idea returned under another name | The most missable one — check before you build it twice |
Output
Answer the question that was asked, then show your work:
- Verdict — tried / not tried / tried and later restored differently
- The decisive artifact — commit sha, issue number, or ADR, so the reader can check
- What killed it, quoted from the source rather than paraphrased
- Whether that still applies, per condition, not as a single yes or no
If the decisive evidence is one commit message, quote it. A conclusion that makes the reader re-run your search has not saved them anything.
Anti-patterns
| Anti-pattern | Why it fails |
|---|---|
--grep only |
Finds discussion, misses deletions. The deletion is the evidence |
| Reading the commit subject and stopping | Subjects say what; you need why |
| Treating a past failure as a verdict | Conditions change. That is the entire question |
| Git-only search | The argument is usually in the issue tracker |
| "It was tried and it failed" with no sha | Unverifiable, and it will be re-litigated next quarter |
| Assuming absence means it was never considered | It may have been rejected in a conversation. Say what you searched |
Escalation
Read-only. Reports what the record says; changes nothing and revives nothing.
Stop and hand back when: the record is genuinely silent — no commit, no issue, no ADR. State what you searched and that nothing exists, rather than inferring intent from absence. Someone deciding not to build a thing and nobody ever considering it look identical in git, and only one of them is an argument.
Also stop when the answer turns on why a person did something and the record does not say. Ask them. Reconstructing a colleague's reasoning from a diff is how a guess acquires a citation.
Cross-References
adr— what you find here belongs in the Context section of the ADR you are about to write. If the thing was decided before, your ADR supersedes that one; say so.debug— the same discipline aimed at a failure rather than a decision: evidence before hypothesis, and the removal commit is evidence.