ancient-fences 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +107 -72
  2. package/package.json +2 -2
  3. package/src/report.mjs +5 -2
package/README.md CHANGED
@@ -3,9 +3,17 @@
3
3
  <img src="https://raw.githubusercontent.com/Marcin1000/ancient-fences/main/assets/readme-banner-light.png" alt="Ancient Fences" width="100%">
4
4
  </picture>
5
5
 
6
+ <p align="center">
7
+ <a href="https://www.npmjs.com/package/ancient-fences"><img alt="npm" src="https://img.shields.io/npm/v/ancient-fences?color=8B5514&labelColor=1E242B&style=flat-square"></a>
8
+ <img alt="MIT licence" src="https://img.shields.io/badge/licence-MIT-8B5514?labelColor=1E242B&style=flat-square">
9
+ <img alt="Node 20 or newer" src="https://img.shields.io/badge/node-%E2%89%A520-8B5514?labelColor=1E242B&style=flat-square">
10
+ <img alt="Works with Claude Code, Cursor and Copilot" src="https://img.shields.io/badge/output-agent%20tasks-8B5514?labelColor=1E242B&style=flat-square">
11
+ </p>
12
+
6
13
  # Ancient Fences
7
14
 
8
- **Find the code you wrote because of someone else's bug. Then check whether that bug is still there.**
15
+ **Find outdated workarounds in a long-lived codebase, and turn the verified ones
16
+ into tasks a coding agent can act on.**
9
17
 
10
18
  Dependabot bumps the version. Nobody removes the workaround you wrote because
11
19
  the old version was broken.
@@ -13,14 +21,23 @@ the old version was broken.
13
21
  ```bash
14
22
  npx ancient-fences . # what is standing in this codebase
15
23
  npx ancient-fences . --check # and whether the reasons still hold
24
+ npx ancient-fences . --tasks # the dead ones, written as work for your agent
16
25
  ```
17
26
 
18
- No installation, no configuration, no account. It reads the repository you
19
- point it at and prints what it found.
27
+ No installation, no configuration, no account. It reads the repository you point
28
+ it at and prints what it found.
29
+
30
+ <picture>
31
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Marcin1000/ancient-fences/main/assets/shot-fences.png">
32
+ <img src="https://raw.githubusercontent.com/Marcin1000/ancient-fences/main/assets/shot-fences-light.png" alt="Ancient Fences output for webpack: 90 fences standing, 39 of them in tests, the oldest untouched for 8.8 years" width="100%">
33
+ </picture>
34
+
35
+ A real run against a full clone of webpack. Nothing here is typed to look good.
20
36
 
21
37
  ## The problem
22
38
 
23
- You hit a bug in a library. You write code around it and, if you are decent, leave a note:
39
+ You hit a bug in a library. You write code around it and, if you are decent,
40
+ leave a note:
24
41
 
25
42
  ```js
26
43
  // Workaround for https://github.com/some/lib/issues/2500 (remove when fixed)
@@ -28,14 +45,47 @@ You hit a bug in a library. You write code around it and, if you are decent, lea
28
45
 
29
46
  Then the bug gets fixed. The issue is closed. The library is replaced. And
30
47
  nothing happens, because there is no link between someone else's tracker and
31
- your code. The workaround stays forever, and after two years nobody dares
32
- touch it: the note says "bug", so maybe the bug is still there.
48
+ your code. The workaround stays forever, and after two years nobody dares touch
49
+ it: the note says "bug", so maybe the bug is still there.
33
50
 
34
51
  This is Chesterton's Fence at industrial scale. The code knows the fence is
35
- there. Git knows how long it has stood. Nobody checks whether the reason
36
- still exists.
52
+ there. Git knows how long it has stood. Nobody checks whether the reason still
53
+ exists.
54
+
55
+ ## From workaround to agent task
56
+
57
+ <picture>
58
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Marcin1000/ancient-fences/main/assets/diagram-fences.png">
59
+ <img src="https://raw.githubusercontent.com/Marcin1000/ancient-fences/main/assets/diagram-fences-light.png" alt="Three stages: detect the workaround, check the reason still holds, hand off a task an agent can act on" width="100%">
60
+ </picture>
37
61
 
38
- ## Real example
62
+ Ancient Fences does not edit your code, and that is deliberate. Knowing that a
63
+ fence is dead is the scarce part; every editor now ships something that can do
64
+ the deleting. So `--tasks` writes the verified findings as work:
65
+
66
+ ```bash
67
+ npx ancient-fences . --check --tasks
68
+ ```
69
+
70
+ You get a markdown file with one entry per dead fence: the file and line, the
71
+ reason originally recorded, what proves it no longer holds, and the instruction.
72
+ Each entry has this shape:
73
+
74
+ ```markdown
75
+ ## src/upload.js:212
76
+
77
+ - Recorded reason: github:some/lib#2500
78
+ - Evidence: the issue was closed on 2021-04-14, and the fix shipped in
79
+ 1.9.0 (milestone). Your lockfile has some-lib 2.3.1.
80
+ - Untouched for 4.2 years.
81
+
82
+ Remove the workaround and the comment that explains it, then run the test suite.
83
+ ```
84
+
85
+ Point Claude Code, Cursor, Copilot or your own script at that file. The tests
86
+ are the safety net, which is why every instruction ends there.
87
+
88
+ ## A real one, in full
39
89
 
40
90
  In `webpack`, `test/configCases/plugins/terser-plugin/extract.js:3`:
41
91
 
@@ -44,31 +94,36 @@ In `webpack`, `test/configCases/plugins/terser-plugin/extract.js:3`:
44
94
  // https://github.com/mishoo/UglifyJS2/issues/2500
45
95
  ```
46
96
 
47
- Written **2017-11-08**. The referenced issue is closed. Webpack dropped
48
- UglifyJS for terser in 2018. The string `uglify` does not appear in its
49
- `package.json` even once. The directory is literally named `terser-plugin`.
50
- The fence has stood untouched for **8.8 years**, guarding a hole in a road
51
- that no longer exists.
97
+ The referenced issue is closed. Webpack dropped UglifyJS for terser in 2018, and
98
+ the string `uglify` does not appear anywhere else in the repository. The
99
+ directory is literally named `terser-plugin`. The line has stood untouched for
100
+ **8.8 years**, guarding a hole in a road that no longer exists.
101
+
102
+ That one lives in a test file, which is exactly why this tool would not ask you
103
+ to delete it. Read on.
104
+
105
+ ## Tests are counted apart, and never listed as work
106
+
107
+ A comment in a test that links to an issue is usually the regression test for
108
+ that bug. It exists because of the bug, exactly like a workaround, but a closed
109
+ issue is the reason to **keep** it.
52
110
 
53
- Two runs, full clones, August 2026:
111
+ Four full clones, measured with this command:
54
112
 
55
113
  | Repository | In source | In tests | Oldest |
56
114
  |---|---|---|---|
57
115
  | puppeteer/puppeteer | 32 | 45 | 9.1 yr |
58
- | webpack/webpack | 54 | 39 | 8.8 yr |
116
+ | webpack/webpack | 51 | 39 | 8.8 yr |
59
117
  | eslint/eslint | 43 | 399 | 8.2 yr |
60
118
  | expressjs/express | 0 | 2 | 1.9 yr |
61
119
 
62
- Both are well-maintained projects by good engineers. That is the point.
120
+ eslint has 399 findings in tests and 43 in its source. One combined number would
121
+ have made it look ten times worse than it is, and would have told an agent to
122
+ delete the tests that guard fixed bugs.
63
123
 
64
- Measured on a full clone, because a shallow one cannot date a line.
65
-
66
- The two columns are the point. A comment in a test that links to an issue is
67
- usually the regression test for that bug: it exists because of the bug, exactly
68
- like a workaround, but a closed issue is the reason to **keep** it. eslint has
69
- 399 of those and 43 in its source. Mixing them into one number would have made
70
- eslint look ten times worse than it is, and would have told an agent to delete
71
- the tests that guard fixed bugs.
124
+ All four are maintained by good engineers. That is the point. The full reports
125
+ are published at
126
+ [ancientcode.net/reports](https://ancientcode.net/reports/).
72
127
 
73
128
  ## What it finds
74
129
 
@@ -82,11 +137,11 @@ the tests that guard fixed bugs.
82
137
  A comment counts as a fence when it links to an external tracker, or says
83
138
  something that only a fence says ("workaround", "kludge", "no longer needed",
84
139
  "do not upgrade"). Words that merely appear in fences ("until", "polyfill",
85
- "temporary", "regression") count only when the comment also names the
86
- condition: a deadline or a version. Phrases that read as prose elsewhere
87
- ("remove this", "blocked by") count next to a `TODO`, `FIXME` or `HACK`, or
88
- next to a named condition. Comment markers are read per language, so `#fff` in
89
- a stylesheet and `a // b` in Python are not comments.
140
+ "temporary", "regression") count only when the comment also names the condition:
141
+ a deadline or a version. Phrases that read as prose elsewhere ("remove this",
142
+ "blocked by") count next to a `TODO`, `FIXME` or `HACK`, or next to a named
143
+ condition. Comment markers are read per language, so `#fff` in a stylesheet and
144
+ `a // b` in Python are not comments.
90
145
 
91
146
  Trackers understood: GitHub issues and pull requests, Chromium (`crbug.com`),
92
147
  Mozilla Bugzilla, WebKit.
@@ -105,8 +160,8 @@ VERDICT: upgrade first (fix shipped in 0.34.0, but sharp is pinned at 0.33.1)
105
160
  ```
106
161
 
107
162
  Those are two different jobs. The first is code you can delete this afternoon.
108
- The second is the more uncomfortable finding: you are still paying to maintain
109
- a workaround for a bug that was fixed years ago, because nobody upgraded.
163
+ The second is the more uncomfortable finding: you are still paying to maintain a
164
+ workaround for a bug that was fixed years ago, because nobody upgraded.
110
165
 
111
166
  `package-lock.json`, `yarn.lock` and `pnpm-lock.yaml` are read when present. No
112
167
  lockfile means the tool says less, never something false.
@@ -117,8 +172,21 @@ was fetched, so the report says age was not measured rather than printing a
117
172
  confident zero.
118
173
 
119
174
  **An unknown issue state never produces `still valid`.** Not knowing is not a
120
- green light. A tool that reassures you without grounds is worse than no tool.
121
- (The test enforcing this caught a real bug on the prototype's first run.)
175
+ green light, and when the tracker cannot answer the report says why. A tool that
176
+ reassures you without grounds is worse than no tool.
177
+
178
+ ## A report you can forward
179
+
180
+ ```bash
181
+ npx ancient-fences . --check --report=fences.html
182
+ ```
183
+
184
+ <picture>
185
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Marcin1000/ancient-fences/main/assets/report-fences.png">
186
+ <img src="https://raw.githubusercontent.com/Marcin1000/ancient-fences/main/assets/report-fences-light.png" alt="The HTML report: counts, then the longest-untouched fences with their recorded reasons" width="100%">
187
+ </picture>
188
+
189
+ One HTML file, no scripts, no external requests.
122
190
 
123
191
  ## Options
124
192
 
@@ -137,45 +205,12 @@ green light. A tool that reassures you without grounds is worse than no tool.
137
205
  (default 7)
138
206
  ```
139
207
 
140
- An option this tool does not recognise stops the run. A mistyped `--chek`
141
- used to print a normal report, and the reader believed the trackers had been
142
- consulted.
143
-
144
- You can also point it at a single file: `npx ancient-fences src/thing.js`.
145
-
146
- Bundles committed into a repository (`vendor.js`, a browserify or webpack
147
- build, anything minified) are skipped, and the summary says how many were left
148
- out. The fences inside them belong to the libraries they were built from, so
149
- listing them buries the ones your team can actually act on. `dist`, `build`,
150
- `node_modules`, `vendor` and `third_party` are skipped for the same reason.
151
-
152
- Issue states are cached in your user cache directory (`$XDG_CACHE_HOME`,
153
- `%LOCALAPPDATA%`, or `~/.cache`), never inside the repository being scanned.
154
- Every entry records when it was read, entries older than a week are re-checked,
155
- and if the tracker cannot be reached the report says which day the answer is
156
- from:
157
-
158
- ```
159
- VERDICT: remove (reason disappeared 2021-04-02 (state as of 2026-08-18))
160
- ```
161
-
162
- An issue state is a snapshot, not a fact. Closed issues get reopened.
163
-
164
- ## Working with an agent
208
+ An unknown option, or a number where a number cannot go, stops the run with exit
209
+ code 2 rather than being ignored. A mistyped flag that still prints a confident
210
+ report is worse than no report.
165
211
 
166
- Ancient Fences does not edit your code, and that is deliberate. Knowing that a
167
- fence is dead is the scarce part; every editor now ships something that can do
168
- the deleting. So `--tasks` writes the verified findings as work:
169
-
170
- ```bash
171
- npx ancient-fences . --check --tasks
172
- ```
173
-
174
- You get a markdown file with one entry per dead fence: the file and line, the
175
- reason originally recorded, what proves it no longer holds, and the instruction
176
- ("remove the workaround and the comment, then run the test suite"). Point Claude
177
- Code, Cursor, Copilot or your own script at it. The tests are the safety net,
178
- which is why the instruction always ends there.
212
+ An issue state is a snapshot, not a fact. Closed issues get reopened, which is
213
+ why every cached state carries the date it was read.
179
214
 
180
215
  ## Tests
181
216
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ancient-fences",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Finds the code you wrote because of someone else's bug, and checks whether that bug is still there.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "license": "MIT",
27
27
  "scripts": {
28
- "test": "node test/detect.test.mjs && node test/tracker.test.mjs && node test/versions.test.mjs && node test/lockfile.test.mjs && node test/paths.test.mjs && node test/walk.test.mjs && node test/cli.test.mjs"
28
+ "test": "node test/detect.test.mjs && node test/tracker.test.mjs && node test/versions.test.mjs && node test/lockfile.test.mjs && node test/paths.test.mjs && node test/walk.test.mjs && node test/stylesheet.test.mjs && node test/cli.test.mjs"
29
29
  },
30
30
  "repository": {
31
31
  "type": "git",
package/src/report.mjs CHANGED
@@ -265,12 +265,15 @@ h2{font:300 1.6rem/1.1 ui-serif,Georgia,serif;margin:2.5rem 0 1rem}
265
265
  .brand b{color:var(--gold);font-weight:300}
266
266
  .scroll{overflow-x:auto;border:1px solid var(--line);background:var(--surface)}
267
267
  table{border-collapse:collapse;width:100%;min-width:40rem;font-size:.88rem;table-layout:fixed}
268
- }
269
268
  th,td{text-align:left;padding:.8rem 1rem;border-bottom:1px solid var(--line);vertical-align:top}
270
269
  thead th{font-family:ui-monospace,monospace;font-size:.64rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);font-weight:400;background:var(--surface2);white-space:nowrap}
271
270
  td p{margin:.35rem 0 0;color:var(--dim);font-size:.82rem}
272
271
  td.num{color:var(--dim);font-variant-numeric:tabular-nums;font-size:.8rem;overflow-wrap:anywhere}
273
- col.age{width:6rem}col.reason{width:14rem}col.state{width:12rem}
272
+ /* github:microsoft/TypeScript#41697 is 30 characters, and at 14rem it broke in
273
+ the middle of the word. A tracker id split across two lines is not an id
274
+ anybody can search for. Measured on webpack: 29 of 40 broke at 14rem, 2 at
275
+ 19rem, and widening further changed nothing. */
276
+ col.age{width:6rem}col.reason{width:19rem}col.state{width:11rem}
274
277
  code{overflow-wrap:anywhere}
275
278
  code{font-size:.82rem;color:var(--ink)}
276
279
  .v{font-family:ui-monospace,monospace;font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}