Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Checks

on:
push:
branches: [main, master]
pull_request:

jobs:
integrity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.x"
- name: Repo integrity — links resolve, everything reachable from README
run: python scripts/check_repo.py
63 changes: 63 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# AGENTS.md — Dispatches From Reality

Cold-start map for any agent (Claude Code, Cursor, Codex, cloud) working in this
repository. This is a **writing** repo — professional essays, research packets,
lessons, and creative dispatches by Sean Campbell — so the checks are about the
prose and its wiring, not a test suite.

## First move (every session)

```bash
git fetch origin
git status -sb
python scripts/check_repo.py # links resolve + everything reachable from README
```

`scripts/check_repo.py` is the one gate, and it is stdlib-only (no install). CI
(`.github/workflows/checks.yml`) runs it on every push and pull request. Run it
before you push; a red check is a broken link or a stranded file, and both are
cheap to fix once you can see them.

## The two invariants the check enforces

1. **Every relative Markdown link resolves.** A link that names a file the reader
cannot open is a broken promise. External (`http`/`mailto`) links are not
checked — those live outside the repo. Files that belong to another repo (the
`Emerging-Rule/community` lessons and showcases) are linked by full URL on
purpose, not by a relative path that only resolves in that tree.
2. **Every Markdown file is reachable from `README.md`.** The front door reaches
everything; nothing is stranded where no reader will find it. A new file must
earn a link from the index (directly or transitively) — or an entry in the
`ALLOWLIST` at the top of `scripts/check_repo.py`, **with a reason**. Silence
is not a valid state: an unindexed file is either a bug or a documented choice.

## House conventions (not gate-enforced, but expected)

These are the habits the research work already keeps; hold them because a reader
trusts the prose only as far as it earns it.

- **Research packages come in pairs.** `research/<topic>/DRAFT.md` is the full
prose; `research/<topic>/README.md` is the packet that summarises it. **Keep
their figures identical** — a number that drifts between the draft and its
packet is an internal contradiction a reader will catch.
- **Sources named inline, no URLs in the research drafts.** The drafts name their
sources in the sentence; they do not carry a link section. (Lessons and indexes
may link out; the no-URL rule is specific to the research prose.)
- **Provenance is a state, and it is honest.** Where a piece tags evidence, use
the `assumed` / `fitted` / `measured` ordering from *Nobody Counts the Ad
Breaks*: `measured` means read at first hand, `fitted` means corroborated by a
stated rule, `assumed` means a single source or none. Do **not** call a
secondary summary `measured` — that is the exact conflation that paper is about.
- **Do not overclaim.** No "peer-reviewed", "verified against", or "proven" unless
it is true and traceable. Say what was checked and what was not.

## What is where

| Path | What |
|------|------|
| `README.md` | The index — the front door the check measures reachability from |
| `research/<topic>/` | A draft + packet pair per research thread |
| `essays/`, `education/`, `professional/`, `lessons/`, `creative/` | The writing, all indexed from `README.md` |
| `drafts/` | Work in progress — staging, allowlisted out of the reachability check |
| `scripts/check_repo.py` | The gate: links resolve, everything reachable |
| `.github/workflows/checks.yml` | Runs the gate in CI on push and pull request |
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Agent instructions

**Read first:** [`AGENTS.md`](AGENTS.md) — cold-start map and the house
conventions for this writing repo.

The one gate, run before you push (stdlib only, no install):

```bash
python scripts/check_repo.py # links resolve + everything reachable from README
```
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ Lessons that teach something real through a story. No devices required for the c

## Lessons — Social Studies (Grades 6–8)

Start with the **[Teacher Overview](lessons/socialstudies-6-8-teacher-overview.md)** — the series is about what the record does to you: whose account got kept, and how to think with what's missing.

| Lesson | |
|--------|-|
| [The Long Account](lessons/socialstudies-6-8-the-long-account.md) | |
Expand All @@ -167,3 +169,15 @@ Lessons that teach something real through a story. No devices required for the c
| Document | |
|----------|-|
| [Lesson Proposal — The Scribe](education/emerging-rule/emerging-rule-presentation-scribe-lesson.md) | Pitch to Emerging Rule community |

---

## Creative

Short creative dispatches — fiction and comic pieces written in-session.

| Piece | |
|-------|-|
| [The Grand Trine of Vega](creative/doctor-who-the-grand-trine-of-vega.md) | A *Doctor Who* closing scene |
| [Downstream](creative/downstream.md) | A session dispatch |
| [On the Structural Failure of Diner French Toast](creative/oakenscroll-structural-failure-of-diner-french-toast.md) | Prof. Archimedes Oakenscroll, at length |
2 changes: 1 addition & 1 deletion lessons/ai-calibration-6-10-index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The Calibration Series — Teacher Overview

> **Example submission** — queued pending human review on Lesson 02. See [`showcases/ai-calibration-6-10/README.md`](../showcases/ai-calibration-6-10/README.md).
> **Example submission** — queued pending human review on Lesson 02. See [`showcases/ai-calibration-6-10/README.md`](https://github.com/Emerging-Rule/community/blob/main/showcases/ai-calibration-6-10/README.md).

### Series-Level Guide for Educators

Expand Down
2 changes: 1 addition & 1 deletion lessons/ai-literacy-9-12-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

**Tags:** `ai-literacy`, `grades-9-12`, `llms`, `bias`, `consent`, `student-agency`, `device-optional`

> **Example submission** — posted for Emerging Rule community review. See [`showcases/ai-literacy-9-12/README.md`](../showcases/ai-literacy-9-12/README.md).
> **Example submission** — posted for Emerging Rule community review. See [`showcases/ai-literacy-9-12/README.md`](https://github.com/Emerging-Rule/community/blob/main/showcases/ai-literacy-9-12/README.md).

> *"The unexamined tool is not worth using."*

Expand Down
2 changes: 1 addition & 1 deletion lessons/cs-k12-the-scribe-who-forgot-his-dreams.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Students will understand — through story, not jargon — that many AI systems

**Co-authorship:** Story developed by Sean Campbell with Hanz Christain Anderthon (Professor of Computational Kindness, UTETY). AI tools assisted drafting; **human direction and edit are authoritative.**

**Planning / presentation thread:** [`research/emerging-rule-scribe-talkthrough.md`](../research/emerging-rule-scribe-talkthrough.md) · [`education/emerging-rule/emerging-rule-presentation-scribe-lesson.md`](../education/emerging-rule/emerging-rule-presentation-scribe-lesson.md)
**Planning / presentation thread:** [`research/emerging-rule-scribe-talkthrough.md`](https://github.com/Emerging-Rule/community/blob/main/research/emerging-rule-scribe-talkthrough.md) · [`education/emerging-rule/emerging-rule-presentation-scribe-lesson.md`](../education/emerging-rule/emerging-rule-presentation-scribe-lesson.md)

---

Expand Down
2 changes: 1 addition & 1 deletion lessons/socialstudies-6-8-teacher-overview.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The Calibration Series (Social Studies) — Teacher Overview

> **Example submission** — see [`showcases/socialstudies-6-8/README.md`](../showcases/socialstudies-6-8/README.md).
> **Example submission** — see [`showcases/socialstudies-6-8/README.md`](https://github.com/Emerging-Rule/community/blob/main/showcases/socialstudies-6-8/README.md).

### Series-Level Guide for Educators

Expand Down
90 changes: 90 additions & 0 deletions scripts/check_repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""Repo integrity checks for Dispatches From Reality.

Two invariants, enforced in CI (.github/workflows/checks.yml) and runnable by
hand. Modelled on Nestor's docs gate: the front door reaches everything, and a
link that names a file means that file exists.

1. **Every relative Markdown link resolves.** A link to a path that is not there
is a broken promise; external (http/mailto) links are not checked.
2. **Every Markdown file is reachable from README.md** by following relative
links — nothing is stranded where a reader can never find it — except an
explicit, reasoned allowlist. A new file must be linked from the index
(directly or transitively) or added here on purpose; silence is not allowed.

Run: `python scripts/check_repo.py` (stdlib only; exits non-zero on any failure).
"""
from __future__ import annotations

import pathlib
import re
import sys

ROOT = pathlib.Path(__file__).resolve().parent.parent
LINK = re.compile(r"\[[^\]]*\]\(([^)]+)\)")

# Files deliberately NOT linked from the front door, each with a reason. This is
# the one place an exception may live, and it must say why — an unindexed file
# is either a bug or a documented choice, never an accident nobody noticed.
ALLOWLIST = {
"drafts/applied-governance-of-autonomous-systems.md":
"work-in-progress draft; drafts/ is staging, not the published index",
"professional/HANDOFF-2026-06-10.md":
"internal session handoff, not reader-facing portfolio content",
"AGENTS.md":
"agent cold-start map, not reader-facing portfolio content",
"CLAUDE.md":
"thin pointer to AGENTS.md for Claude Code; not reader-facing",
}


def rel_link_targets(md: pathlib.Path):
"""Yield the relative link targets in a Markdown file (external links skipped)."""
for m in LINK.finditer(md.read_text(encoding="utf-8", errors="replace")):
target = m.group(1).strip()
if target.startswith(("http://", "https://", "mailto:", "tel:", "#")):
continue
path = target.split("#", 1)[0].split("?", 1)[0]
if path:
yield path


def main() -> int:
mds = sorted(p for p in ROOT.rglob("*.md") if ".git" not in p.parts)
failures: list[str] = []

# 1. Broken relative links.
for md in mds:
for path in rel_link_targets(md):
if not (md.parent / path).resolve().exists():
failures.append(f"broken link: {md.relative_to(ROOT)} -> {path}")

# 2. Reachability from README.md (transitive closure over relative .md links).
readme = ROOT / "README.md"
reached = {readme.resolve()}
stack = [readme]
while stack:
cur = stack.pop()
for path in rel_link_targets(cur):
tgt = (cur.parent / path).resolve()
if tgt.suffix == ".md" and tgt.exists() and tgt not in reached:
reached.add(tgt)
stack.append(tgt)
allowed = {(ROOT / a).resolve() for a in ALLOWLIST}
for md in mds:
r = md.resolve()
if r not in reached and r not in allowed:
failures.append(f"orphan (unreachable from README.md): {md.relative_to(ROOT)}")

if failures:
print(f"FAIL — {len(failures)} issue(s):")
for f in sorted(failures):
print(f" {f}")
return 1
print(f"OK — {len(mds)} markdown files: all relative links resolve, all reachable "
f"from README.md ({len(ALLOWLIST)} allowlisted).")
return 0


if __name__ == "__main__":
sys.exit(main())