os-big-picture
ALWAYS invoke this skill when the user asks where the project as a whole stands - "where are we", "what's the big picture", "what have we built", "what is in this project", "map the project", "what does this thing even do", "what is stale", "what can we delete", "update the roadm
Install
npx skills add https://github.com/kharmanskyi/open-steps/tree/main/skills/os-big-picture
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install kharmanskyi-open-steps@llmmart
git clone https://github.com/kharmanskyi/open-steps.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole kharmanskyi/open-steps collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
os-big-picture
One file, BIG-PICTURE.md, where os-whats-next already looks for a backlog.
It answers "where are we now" - not a plan, nothing in it a promise. Write it
in the user's language; paths stay English.
When to use it. The user asks where the project stands, or there is no
BIG-PICTURE.md yet → the full pass: describe, census, signals, backlog. A
session report was just written → the fold-in: only the rows that session
touched. Both re-measure, because a session that moves no row is the one after
which the dates go quietly stale.
Step 1 - find the file, and never clobber it
BIG-PICTURE.md in the project root, and it may already be somebody's own
work. Everything this skill writes lives between <!-- open-steps:begin -->
and <!-- open-steps:end -->; no markers → append at the end; no file → create
it, and add it to this clone's ignore list, never to .gitignore:
f=BIG-PICTURE.md; e="$(git rev-parse --git-dir)/info/exclude"
git check-ignore -q "$f" || git ls-files --error-unmatch "$f" >/dev/null 2>&1 \
|| grep -qxF "$f" "$e" 2>/dev/null || printf '%s\n' "$f" >> "$e"
Each of the three is a reason to leave it alone. Say in one line that you did
it; git add -f BIG-PICTURE.md shares it instead. Never read Last worked on
or Signal back out of the file - only Stage and the queue carry forward.
Step 2 - the census, measured by the script
bash "${CLAUDE_SKILL_DIR}/scripts/census.sh" .
An AGE line, then one measured PART row per part: path, last worked on,
commits in six months, whether anything outside reaches it, the signal.
Step 3 - reading what it printed
| Signal | What it means | What to do |
|---|---|---|
active |
Being worked on | Nothing |
stable |
Quiet, and something still reaches it | Leave it alone - finished and in use |
unused - N months |
Quiet, and nothing reaches it | The only real retire candidate |
When the AGE line says young, write one line directly above the table,
before the header row: "This project is N days old. Nothing here can be quiet
for six months yet, so the Signal column will only start to mean something from
the date the script gave." Every active is then a fact about the
calendar, not the code. On an ok line, write no such note.
Step 4 - the backlog, sourced and never invented
Three sources, each item naming its own: session reports in
~/.claude/open-steps/reports/<project>/ (⏳ deferred rows, "Anything needed
from you", recorded debt); Step 3 (each unused part, one retire candidate);
the user, by hand. Nothing else - a gap you noticed while reading the code is
not a task, so say it in the chat and let the user decide.
Step 5 - the tracker, if there is one
An offer, never a quiet action, and only for the "What is next" rows.
- Find it, never install it.
gh repo view --json hasIssuesEnabled -q .hasIssuesEnabled, then any tracker already connected. Nothing connected is a normal answer: say so under "What is next", then Step 6. - Search before you propose:
gh issue list --search '<a few words>' --state all --json number,title,state. A closed ticket is still a match; where the search did not run, say so. - Ask, then create. Plain lines through
os-ask-simple; on a yes create those and nothing else, and only where this pass measured what the item rests on. Write each number back into its row, same pass.
Step 6 - plain words, before it is written
Put every sentence in the block through the os-say-simple rules first: one
sentence one idea, active voice, one word for one thing. That covers the prose,
the "What it does" column and the backlog items - never a measured cell.
The shape
<!-- open-steps:begin -->
_Measured <date>. Stages are dated where they stand; anything undated in this
file is not measured._
## What this is
<Three or four sentences. What the product does, who runs it, what it runs
on. No jargon - a reader who has never seen the code.>
## What is in it
| Feature | What it does | Stage | Last worked on | Signal |
|---|---|---|---|---|
| <plain name> | <one line> | live (1 Sep) | 1 Sep | active |
| <plain name> | <one line> | not checked | 28 May | stable |
## Worth retiring
<One line each: what it is, how long unused, what would break. Empty is a
result - write "nothing found" and keep the heading.>
## What is next
- <item> - <source>
- <item> - <source> - #<ticket number, once one exists>
_Age and wiring measured <date>, fresh this pass. Stage comes from session
reports and is only as current as the date beside it._
<!-- open-steps:end -->
Stage is one of building, built - not shipped, live, retired or not checked, and it carries the date it came from, always - live (12 Jul),
never bare live, and not checked never dated. When the newest date in that
column is months behind the newest commit, the reports have stopped: say so.
Hard rules - these rules are the skill
- Measured and assumed never mix.
Last worked onandSignalare script output every time.Stagehas no measurement. - Enumerate, never guess. The parts come from the script.
- Quiet is not dead. No retire candidate on age alone: quiet plus wired
in is
stable, which is what stops the map recommending you delete a working product. - Never invent work. Every backlog item names its source.
- Never clobber. Only between the markers. Their file stays theirs.
- Recommend, never act. No deleting code, moving files, opening a pull request. "Worth retiring" is a sentence, not an action.
- Plain words. A feature is named as the user says it, not as the folder.
- One screen per section. Twelve features is a wall - group and say so.
- A ticket only after an explicit yes. Rule 6 is the code, this is everyone else's inbox.
- The plain-words pass never touches a measured cell.
- Re-measure, never quote. A number read out of the file is a number about the past wearing today's date.
- Every claim carries its date or says it has none. A map that cannot go visibly stale will go invisibly stale.
Why the rules are there, the traps the script cannot catch, and a worked
example: references/.
Files (open-steps)
-
references
-
01-quiet-is-not-dead.md 3.6 KB
# Example 1 - the map that recommended deleting a working product Synthetic. A small parcel-tracking product, two years old, one founder and two contractors. No confidential or third-party data. The user asked: *"What have we actually built here? Half of it feels dead."* ## What a first pass produced It listed the directories it could think of, sorted them by date, and called everything old a candidate for deletion. > **Stale, consider removing:** `notifier/` (7 months), `geocode/` (9 months), > `pdf/` (11 months), `retry/` (7 months), `csvimport/` (10 months), > `webhooks/` (8 months). > > Six of eleven modules are unmaintained. Recommend a cleanup sprint. Confident, tidy, and wrong in a way that costs a weekend. Every one of those six is imported by the running product. `geocode/` had not changed in nine months because it was finished - it turns an address into coordinates, and addresses have not changed. "Unmaintained" was never measured; it was inferred from a date. ## What the skill produces Two signals instead of one, and the parts enumerated from `git ls-files` rather than from memory. > _Measured 14 Aug. Stages are dated where they stand; anything undated in this > file is not measured._ > > ## What this is > > A parcel-tracking service for small shops. Sellers paste a tracking number, > customers get a page that updates itself and an email when the parcel moves. > It runs as one web service with a background worker. > > ## What is in it > > | Feature | What it does | Stage | Last worked on | Signal | > |---|---|---|---|---| > | Tracking page | The page customers watch | live (12 Aug) | 12 Aug | active | > | Carrier polling | Asks carriers where a parcel is | live (9 Aug) | 9 Aug | active | > | Emails | Tells the customer it moved | live (6 Jan) | 6 Jan | stable | > | Address lookup | Turns an address into a map point | live (3 Nov) | 3 Nov | stable | > | Receipts | Makes the PDF receipt | live (21 Sep) | 21 Sep | stable | > | Spreadsheet import | Bulk-adds parcels from a file | not checked | 4 Dec | stable | > | Partner callbacks | Tells a shop's own system | live (2 Feb) | 2 Feb | stable | > | **Old SMS sender** | Sent texts before we moved to email | not checked | 18 Mar | **unused - 11 months** | > > ## Worth retiring > > - **Old SMS sender.** Nothing has called it since the switch to email in > March, and no build or deploy script mentions it. It still holds the > Twilio credentials. Removing it is about an hour. > > ## What is next > > - Finish the spreadsheet import - never confirmed working (report, 4 Dec) > - Decide on the old SMS sender - retire candidate above > > _Age and wiring measured 14 Aug, fresh this pass. Stage comes from session > reports and is only as current as the date beside it._ ## What changed, and why each change is in the rules | Change | Rule | |---|---| | Six "stale" became `stable` | Each is imported by the live product. Quiet plus wired in is finished, not dead - Step 3 | | The real dead one appeared | `sms/` was never in the hand-written list. `git ls-files` found it - rule 2 | | "Recommend a cleanup sprint" became one named item | The skill recommends, it does not act - rule 6 | | Two rows say `not checked` | Nobody could prove those shipped. A guess would have read the same as a fact - rule 1 | | Both next items name a source | Neither was the agent's idea - rule 4 | The lesson that cost the most: **the retire list got shorter and became useful.** Six confident candidates were noise a person has to disprove one by one. One candidate, with the reason it is dead and the credential it still holds, is a decision they can make in a minute. -
rationale.md 6.5 KB
# Why the rules are there, and the traps the script cannot catch `SKILL.md` holds the rules. This file holds the reasons, and the failures each rule was written after. Read it when a rule looks like ceremony, or when the map you are about to write disagrees with one. ## Why the file exists at all Every other skill in the pack is one session wide. `os-whats-next` is told to read the backlog **always**, and until this skill landed nothing in the pack ever wrote one. The map is the standing half: the part that outlives the session that produced it, and the part an owner can open without an agent present. That is also why it is a file and not a query. A census generated on demand would be fresher, and useless to the person this pack is for - they would need an agent running to see what they own. ## Why quiet code is not dead code (rules 1 and 3) Age alone says nothing. Finished code is quiet; so is abandoned code. The two read identically in `git log`, which is why a map built on dates alone recommends deleting a working product - see [`01-quiet-is-not-dead.md`](01-quiet-is-not-dead.md), where six of eight "stale" modules were load-bearing. **`stable` is the row that protects the user.** Most quiet code is still imported by dozens of files. Only quiet *and* unreached is a retire candidate, and even then it is a sentence for the user, never an action (rule 6). ## Why nothing measured is ever read back out of the file (rule 11) A map that already exists was true on the day it was written and has been decaying since. `Last worked on` and `Signal` cost seconds to measure, so there is no case where quoting the stored value is worth being wrong. The only cells carried forward are the ones nothing can measure: `Stage`, and the queue. The same reason drives the fold-in rule. A session that fixed a bug in a finished feature moves no row - and it is exactly the session after which the dates would silently be a day older than they claim. Measuring needs no reason and no permission, so the fold-in measures whether or not it has news. ## Why `Stage` carries a date (rule 12) `Stage` is the one column nothing can measure. It comes from the session reports, from the user, or it says `not checked`. The failure this file is most likely to have is being **half fresh, and therefore trusted whole**: the measured columns keep refreshing themselves and look healthy while `Stage` quietly ages behind them. The date beside each stage is the cure, because it puts the decay in the row rather than in a footnote nobody reads. A `Stage` column that is all `not checked` is itself a finding - it means the reports have stopped being written, not that the product is unknowable. Say so. ## Why the map stays out of git The map is a working note about the project, not part of the product, and a `git add -A` should never sweep it into somebody's commit. So the skill puts it in that clone's own `.git/info/exclude`, never in the shared `.gitignore`, which belongs to the whole team. Three conditions stop it, and each is somebody's decision to respect: the file is already ignored, it is tracked because somebody chose to commit it, or the line is already there. **A tracked map stays tracked** - never remove it from git, and say once that its edits will show up in diffs. ## Why the tracker step is an offer (rule 9) A team that runs on a tracker will not read the map, so the queue has to reach them where they already look. But rule 6 protects the code and rule 9 protects everyone else's inbox: searching a tracker is free, filling one is not. - **The same item proposed twice** is how this step fails. A ticket whose number never made it back into its row will be offered again next session, and the team gets two of it. - **A closed ticket is still a match.** Search `--state all`. An item somebody already did should leave the map, not reopen as a new ticket. - **A stale row must never become a ticket.** A retire candidate needs the wiring check run today, not the one in the file. The tracker reaches people who never open this map, and that is exactly who a stale row would mislead. - **No tracker is not a fault.** Plenty of projects run on the map alone. Say it in one line and never turn it into a recommendation to adopt one. ## Traps the script cannot catch `scripts/census.sh` measures dates and wiring. These four it cannot see, and they are yours: - **A renamed folder looks new.** A whole subsystem that moved reads as three months old on the day it moved. `git log --follow` on a single file inside it when a date looks wrong. - **A monorepo is not one product.** Several deployables → group the map by deployable, or "What this is" becomes a lie of averages. - **git says last *touched*, not last *used*.** A part with no commits may run every day. That is why wiring is a separate signal and not a tiebreaker. - **Vendored and generated code reads as abandoned.** The script drops the usual folder names, but it cannot know yours. A copied-in dependency has one commit, years old, and is not yours to retire. ## The young repository A project younger than the quiet window has no quiet code yet - by definition, not by luck. Every part comes back `active`, and the map would report a sweeping clean bill of health it did not earn. Measured on a real five-month-old project with 1245 commits: 51 parts, all `active`, nothing to retire. Not one of those was a finding. So the script prints the repository's age, and on a young one the map says in a line above the table that the liveness column cannot mean anything yet, and from when it will. The reader then knows the column is a fact about the calendar. ## Plain words are not fewer facts (rule 10) The plain-words pass rewrites sentences, never facts. A shorter "Worth retiring" line that drops what would break is worse than the long one, and `os-say-simple`'s own hard rules carry over whole: add nothing, drop no bad news, numbers and dates stay exact, a claim from a report stays a claim. ## Options that were considered and rejected So a later session does not re-run the choice: | Rejected | Why | |---|---| | A hook that warns on a stale map | A threshold to tune, and it nags every session | | Store only the non-perishable half, generate the census on demand | Kills the reason the file exists: an owner opening it without an agent | | The tracker as the source of truth | No truth at all for a solo owner with no tracker | | Dropping `Stage` | It is the only column that says how far a feature got | | Keeping the map in `~/.claude` | The team could never see it |
-
-
scripts
-
census.sh 3.9 KB
#!/usr/bin/env bash # The measured half of os-big-picture. Nothing here is a guess: every number # comes out of git. Prose, stages and the queue stay the skill's job. # # bash scripts/census.sh [repo-path] # # It prints three kinds of line, tab separated: # # MEASURED <date> # AGE <days> <young|ok> <date liveness starts meaning something> # PART <path> <last worked on> <commits in the window> <wired|orphan> <signal> # # The signal is one of `active`, `stable`, `unused - N months`. It is the # combination of two things and never the date alone: quiet code that is still # reached by something is finished, not dead. # # OS_QUIET_MONTHS moves the window; the tests use it, people should not. set -u cd "${1:-.}" 2>/dev/null || { echo "no such directory: ${1:-.}" >&2; exit 1; } git rev-parse --git-dir >/dev/null 2>&1 || { echo "not a git repository" >&2; exit 1; } MONTHS="${OS_QUIET_MONTHS:-6}" SINCE="$MONTHS months ago" NOW="$(date +%s)" # 30-day months and a 183-day half-year, on purpose: this is a staleness # signal, not an anniversary. Exactness here would buy nothing. WINDOW=$((MONTHS * 2592000)) # A copied-in dependency has one commit, years old, and is not ours to retire. # Build output is not a feature either. Both come off the map before anything # is measured, so they can never appear as a retire candidate. SKIP='^(node_modules|vendor|third_party|third-party|dist|build|target|\.git|\.github)$' # date(1) splits down the middle here: BSD wants -r, GNU wants -d @. on_day() { date -r "$1" +%Y-%m-%d 2>/dev/null || date -d "@$1" +%Y-%m-%d; } printf 'MEASURED\t%s\n' "$(on_day "$NOW")" # How old the repository itself is. A project younger than the quiet window has # no quiet code yet - by definition, not by luck - so the liveness column # cannot mean anything until it is old enough, and the map has to say so. BORN="$(git log --max-parents=0 --format=%ct 2>/dev/null | sort -n | head -1)" if [ -n "$BORN" ]; then DAYS=$(((NOW - BORN) / 86400)) if [ "$((NOW - BORN))" -lt "$WINDOW" ]; then printf 'AGE\t%s days\tyoung\t%s\n' "$DAYS" "$(on_day "$((BORN + WINDOW))")" else printf 'AGE\t%s days\tok\t-\n' "$DAYS" fi fi # Enumerated, never typed from memory: a hand-written list of directories is # the most common way this skill produces a confident, wrong map. parts() { git ls-files | awk -F/ 'NF>1 {print $1}' | sort -u git ls-files | awk -F/ 'NF==1 {print}' | sort } parts | while IFS= read -r p; do [ -n "$p" ] || continue printf '%s' "$p" | grep -Eq "$SKIP" && continue # Repository housekeeping is not a feature: a licence, a readme, an ignore # file. Nothing names them, so they would read as unused on any repository # older than the window, and "retire .gitignore" is the confident, wrong map. case "$p" in .*|LICENSE*|LICENCE*|README*|CHANGELOG*|CONTRIBUTING*|CODE_OF_CONDUCT*|SECURITY*) [ -f "$p" ] && continue ;; esac last="$(git log -1 --format=%ad --date=short -- "$p" 2>/dev/null)" [ -n "$last" ] || continue lastct="$(git log -1 --format=%ct -- "$p" 2>/dev/null)" busy="$(git log --oneline --since="$SINCE" -- "$p" 2>/dev/null | grep -c . || true)" : "${busy:=0}" # Wiring: does anything OUTSIDE this part name it. One search covers both # kinds of part - code somebody imports, and a standalone thing nothing # imports but something builds, ships or documents. A directory is searched # by its name, a loose file by its name without the extension. name="${p%/}" case "$name" in */*) name="${name##*/}" ;; esac if [ -f "$p" ]; then base="${name%.*}"; [ -n "$base" ] && name="$base"; fi wired=orphan if [ -n "$name" ] && git grep -l -F -e "$name" -- . ":(exclude)$p" >/dev/null 2>&1; then wired=wired fi if [ "$busy" -gt 0 ]; then signal="active" elif [ "$wired" = wired ]; then signal="stable" else signal="unused - $(((NOW - lastct) / 2592000)) months" fi printf 'PART\t%s\t%s\t%s\t%s\t%s\n' "$p" "$last" "$busy" "$wired" "$signal" done
-
-
SKILL.md 7.2 KB
--- name: os-big-picture description: >- ALWAYS invoke this skill when the user asks where the project as a whole stands - "where are we", "what's the big picture", "what have we built", "what is in this project", "map the project", "what does this thing even do", "what is stale", "what can we delete", "update the roadmap" - in any language, and whenever a session report has just been written. It keeps one file, BIG-PICTURE.md: what the product is, every feature with how far it got, which parts nobody has touched, and what is queued next. Age and wiring are measured from git; how far a feature got comes from the reports or says "not checked". Where a tracker is connected it offers to open the queue as tickets, after a yes and never before. Never invents work, never deletes code. allowed-tools: - "Read(~/.claude/open-steps/**)" - "Edit(BIG-PICTURE.md)" - "Bash(${CLAUDE_SKILL_DIR}/scripts/census.sh *)" - "Bash(git rev-parse --git-dir)" - "Bash(gh repo view *)" - "Bash(gh issue list *)" - "Bash(gh issue create *)" --- # os-big-picture One file, `BIG-PICTURE.md`, where `os-whats-next` already looks for a backlog. It answers "where are we now" - not a plan, nothing in it a promise. Write it in the user's language; paths stay English. **When to use it.** The user asks where the project stands, or there is no `BIG-PICTURE.md` yet → the full pass: describe, census, signals, backlog. A session report was just written → the fold-in: only the rows that session touched. Both re-measure, because a session that moves no row is the one after which the dates go quietly stale. ## Step 1 - find the file, and never clobber it `BIG-PICTURE.md` in the project root, and it may already be somebody's own work. Everything this skill writes lives between `<!-- open-steps:begin -->` and `<!-- open-steps:end -->`; no markers → append at the end; no file → create it, and add it to this clone's ignore list, never to `.gitignore`: ```bash f=BIG-PICTURE.md; e="$(git rev-parse --git-dir)/info/exclude" git check-ignore -q "$f" || git ls-files --error-unmatch "$f" >/dev/null 2>&1 \ || grep -qxF "$f" "$e" 2>/dev/null || printf '%s\n' "$f" >> "$e" ``` Each of the three is a reason to leave it alone. Say in one line that you did it; `git add -f BIG-PICTURE.md` shares it instead. Never read `Last worked on` or `Signal` back out of the file - only `Stage` and the queue carry forward. ## Step 2 - the census, measured by the script ```bash bash "${CLAUDE_SKILL_DIR}/scripts/census.sh" . ``` An `AGE` line, then one measured `PART` row per part: path, last worked on, commits in six months, whether anything outside reaches it, the signal. ## Step 3 - reading what it printed | Signal | What it means | What to do | |---|---|---| | `active` | Being worked on | Nothing | | `stable` | Quiet, and something still reaches it | **Leave it alone** - finished and in use | | `unused - N months` | Quiet, and nothing reaches it | The only real retire candidate | **When the `AGE` line says `young`**, write one line directly above the table, before the header row: *"This project is N days old. Nothing here can be quiet for six months yet, so the Signal column will only start to mean something from the date the script gave."* Every `active` is then a fact about the calendar, not the code. On an `ok` line, write no such note. ## Step 4 - the backlog, sourced and never invented Three sources, each item naming its own: session reports in `~/.claude/open-steps/reports/<project>/` (⏳ deferred rows, "Anything needed from you", recorded debt); Step 3 (each `unused` part, one retire candidate); the user, by hand. Nothing else - a gap you noticed while reading the code is not a task, so say it in the chat and let the user decide. ## Step 5 - the tracker, if there is one An offer, never a quiet action, and only for the "What is next" rows. 1. **Find it, never install it.** `gh repo view --json hasIssuesEnabled -q .hasIssuesEnabled`, then any tracker already connected. Nothing connected is a normal answer: say so under "What is next", then Step 6. 2. **Search before you propose:** `gh issue list --search '<a few words>' --state all --json number,title,state`. A closed ticket is still a match; where the search did not run, say so. 3. **Ask, then create.** Plain lines through `os-ask-simple`; on a yes create those and nothing else, and only where this pass measured what the item rests on. Write each number back into its row, same pass. ## Step 6 - plain words, before it is written Put every sentence in the block through the `os-say-simple` rules first: one sentence one idea, active voice, one word for one thing. That covers the prose, the "What it does" column and the backlog items - never a measured cell. ## The shape ``` <!-- open-steps:begin --> _Measured <date>. Stages are dated where they stand; anything undated in this file is not measured._ ## What this is <Three or four sentences. What the product does, who runs it, what it runs on. No jargon - a reader who has never seen the code.> ## What is in it | Feature | What it does | Stage | Last worked on | Signal | |---|---|---|---|---| | <plain name> | <one line> | live (1 Sep) | 1 Sep | active | | <plain name> | <one line> | not checked | 28 May | stable | ## Worth retiring <One line each: what it is, how long unused, what would break. Empty is a result - write "nothing found" and keep the heading.> ## What is next - <item> - <source> - <item> - <source> - #<ticket number, once one exists> _Age and wiring measured <date>, fresh this pass. Stage comes from session reports and is only as current as the date beside it._ <!-- open-steps:end --> ``` `Stage` is one of `building`, `built - not shipped`, `live`, `retired` or `not checked`, and **it carries the date it came from, always** - `live (12 Jul)`, never bare `live`, and `not checked` never dated. When the newest date in that column is months behind the newest commit, the reports have stopped: say so. ## Hard rules - these rules *are* the skill 1. **Measured and assumed never mix.** `Last worked on` and `Signal` are script output every time. `Stage` has no measurement. 2. **Enumerate, never guess.** The parts come from the script. 3. **Quiet is not dead.** No retire candidate on age alone: quiet plus wired in is `stable`, which is what stops the map recommending you delete a working product. 4. **Never invent work.** Every backlog item names its source. 5. **Never clobber.** Only between the markers. Their file stays theirs. 6. **Recommend, never act.** No deleting code, moving files, opening a pull request. "Worth retiring" is a sentence, not an action. 7. **Plain words.** A feature is named as the user says it, not as the folder. 8. **One screen per section.** Twelve features is a wall - group and say so. 9. **A ticket only after an explicit yes.** Rule 6 is the code, this is everyone else's inbox. 10. **The plain-words pass never touches a measured cell.** 11. **Re-measure, never quote.** A number read out of the file is a number about the past wearing today's date. 12. **Every claim carries its date or says it has none.** A map that cannot go visibly stale will go invisibly stale. Why the rules are there, the traps the script cannot catch, and a worked example: [`references/`](references/).
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.