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
23 changes: 13 additions & 10 deletions .claude/routines/desk.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The desk is the daily Routine that keeps the conference data complete and curren
## Ground rules

- You orchestrate. Subagents research and return change sets; you alone edit data, commit, push and write to GitHub.
- The data you edit is the three data files and `utils/tidy_conf/data/titles.yml`, where you only add the variations accepted change sets carry (AGENTS.md, "Data mechanics"). Every other file stays with the maintainer, `rejections.yml` and the rest of `utils/` included.
- Start every subagent, researcher and critic alike, as the `desk-research` type: the Agent tool with `subagent_type: desk-research` ([.claude/agents/desk-research.md](../agents/desk-research.md)). That type has only Read, Grep, Glob, WebFetch and WebSearch, so it runs no commands, writes no files, uses no MCP tools and starts no subagents.
- Process every worklist item: a run has no length cap. Commit and push each conference as soon as it is done and keep the ledger current, so an interrupted run loses nothing.
- Push only to `auto/conference-updates` and `desk-queue`, following "Pushing". Files under `.github/` stay with the maintainer.
Expand Down Expand Up @@ -46,7 +47,7 @@ Done when: `QUEUE/queue/` is a directory and you have noted QUEUE's commit, or y

Run `python utils/desk_worklist.py --queue-dir QUEUE > <scratch>/worklist.json` (leave out `--queue-dir` without a queue) with an interpreter that has the dependencies (AGENTS.md, "Data mechanics").

Before any subagent starts, save what the tamper check's status command prints now: `git status --porcelain --ignored --untracked-files=normal -- .claude AGENTS.md REVIEW.md .github utils pixi.toml ':(glob)**/CLAUDE*.md' > <scratch>/tamper.txt`. Step 2 left the tree clean, so it lists only ignored files the checkout already holds, such as `.github/enrichment_review.md` or `utils/tidy_conf/data/.tmp/`, and the tamper check leaves those out.
Before any subagent starts, save what the tamper check's status command prints now: `git status --porcelain --ignored --untracked-files=normal -- .claude AGENTS.md REVIEW.md .github utils pixi.toml ':(glob)**/CLAUDE*.md' ':(exclude)utils/tidy_conf/data/titles.yml' > <scratch>/tamper.txt`. Step 2 left the tree clean, so it lists only ignored files the checkout already holds, such as `.github/enrichment_review.md` or `utils/tidy_conf/data/.tmp/`, and the tamper check leaves those out. It also leaves out `titles.yml`, which you edit as data: the stray-write checks of step 6.3 cover it.

Done when: the file parses as JSON, you have its item count, it holds exactly one `scout` item, and every line of `<scratch>/tamper.txt`, if it has any, starts with `!!` (otherwise stop and report).

Expand Down Expand Up @@ -82,13 +83,13 @@ Take the items in worklist order. Several subagents may research at once, for di
2. **Critic.** Send every change set with result `change`, `new-edition` or `new-series`, and every scouter `lead`, to a fresh `desk-research` subagent (the Agent tool with `subagent_type: desk-research`) with "Read AGENTS.md and .claude/skills/critic/SKILL.md, then review this change set" and the set. When each critic returns, run the tamper check again. Done when each has a verdict and every tamper check found nothing.
3. **Apply.** Subagents still researching can write to the working tree while you apply this conference, so check the tree before and after your edits.
1. Before you edit, run the tamper check, then `git status --porcelain --untracked-files=normal`, which must print nothing: the tree holds none of this conference's edits yet, so anything it prints is a stray write.
2. For each accepted set (without the fields the critic dropped), edit the data and run the sort (AGENTS.md, "Data mechanics").
3. Before you stage and commit, run the tamper check again and then the stray-write check: compare `git status --porcelain --untracked-files=normal` and `git diff HEAD` with the edits you made for this conference. Only `_data/` files may change, and only in the entries and fields of this conference's accepted sets, with the values those sets give, apart from the sort's own work: inside this conference's entries, the fields it drops or tidies and the `sub` and `location` it adds; elsewhere, entries it moves between the three files or reorders without changing them. Anything else is a stray write.
4. Restore a stray write with `git checkout HEAD -- <path>` for a tracked file and `git clean -fd -- <path>` for an untracked one, and note "stray write: <paths>" for this item's ledger row and the final report (step 9). When a stray write after your edits was in one of the three data files, restore all three with `git checkout HEAD -- _data/conferences.yml _data/archive.yml _data/legacy.yml`, apply your edits again, run the sort and check again; a change that comes back is a stray write still being made. Repeat a check that found a stray write up to five times; when it still finds one, stop and report.
5. Commit the three data files: one commit per conference, covering every edition of that series the item touched. Subject: `<conference> <year>: <fields changed>`, or `Add <conference> <year>` for a new entry. Body: per field the old and new value, the URL and the quote, then the critic's flags. Note the commit `git rev-parse HEAD` prints.
2. For each accepted set (without the fields and variations the critic dropped), edit the data, add its `titles` variations and run the sort (AGENTS.md, "Data mechanics").
3. Before you stage and commit, run the tamper check again and then the stray-write check: compare `git status --porcelain --untracked-files=normal` and `git diff HEAD` with the edits you made for this conference. Only the three data files and `utils/tidy_conf/data/titles.yml` may change. The data files change only in the entries and fields of this conference's accepted sets, with the values those sets give, apart from the sort's own work: inside this conference's entries, the fields it drops or tidies and the `sub` and `location` it adds; elsewhere, entries it moves between the three files or reorders without changing them. `titles.yml` gains only the lines that add this conference's accepted variations: one line per variation, plus the key line when the key is new and the `variations:` line when the key has no list yet. Anything else is a stray write, with one exception. When this conference adds variations and the sort set a `conference` or `alt_name` no accepted set gives, a variation may have renamed an existing entry, and a variation must rename nothing. Restore the three data files and `titles.yml` with `git checkout HEAD -- _data/conferences.yml _data/archive.yml _data/legacy.yml utils/tidy_conf/data/titles.yml`, apply the accepted sets again without their variations, run the sort and check again. When the renames are gone, the variations caused them: leave them out of this commit and note "variations deferred: <variations>, would rename <conference> <year>, ..." for the item's ledger row, since folding those entries into the series is the maintainer's call. When the renames come back, they are a stray write.
4. Restore a stray write with `git checkout HEAD -- <path>` for a tracked file and `git clean -fd -- <path>` for an untracked one, and note "stray write: <paths>" for this item's ledger row and the final report (step 9). When a stray write after your edits was in one of the three data files or `titles.yml`, restore all four with `git checkout HEAD -- _data/conferences.yml _data/archive.yml _data/legacy.yml utils/tidy_conf/data/titles.yml`, apply your edits again, run the sort and check again; a change that comes back is a stray write still being made. Repeat a check that found a stray write up to five times; when it still finds one, stop and report.
5. Commit the three data files and `titles.yml`: one commit per conference, covering every edition of that series the item touched and the variations it adds. Subject: `<conference> <year>: <fields changed>`, with `titles` among the fields when it adds variations, or `Add <conference> <year>` for a new entry. Body: per field the old and new value, the URL and the quote, per variation the key, the variation, the URL and the quote, then the critic's flags. Note the commit `git rev-parse HEAD` prints.
6. After the commit, `git show HEAD` must hold only this conference's accepted edits and the sort's own work. When it holds anything else, stop and report without pushing. Anything `git status --short` then prints is a stray write: restore it with `git checkout HEAD -- <path>` or `git clean -fd -- <path>` and note it, without applying your edits again.

Done when the last stray-write check found no stray write, `git show HEAD` holds only this conference's accepted edits and the sort's own work, `git status --short` prints nothing and `git show --stat HEAD` lists only `_data/` files.
Done when the last stray-write check found no stray write, `git show HEAD` holds only this conference's accepted edits and the sort's own work, `git status --short` prints nothing and `git show --stat HEAD` lists only `_data/` files and, when the commit adds variations, `utils/tidy_conf/data/titles.yml`.
4. **Push.** Follow "Pushing". Done when `git rev-parse HEAD` equals `git rev-parse origin/auto/conference-updates`.
5. **Ledger.** Add the item's rows ("Ledger") to your scratch ledger and, once a PR can exist (step 7), to the PR body, opening the PR if none is open ("Rolling PR"). This run's section keeps the previous run's "Feedback read to" value. Done when the rows are in the PR body, or in the scratch ledger while no PR can exist.

Expand All @@ -109,7 +110,7 @@ Done when: the tamper check found nothing; `gh api repos/REPO/pulls/<n> --jq .bo

Skip this step without a `desk-queue` branch (step 3). Otherwise, in QUEUE:

- Remove every queue item whose worklist item ended `actioned` or `dismissed`, or `deferred` as a needs-maintainer result or verdict or a legacy suspect: those wait on the maintainer, whose answer on the PR comes back through step 5. Remove them with `python utils/desk_queue.py remove QUEUE <id> ...`, which also records each id as handled, so the writers do not queue the same finding again.
- Remove every queue item whose worklist item ended `actioned` or `dismissed`, or `deferred` as a needs-maintainer result or verdict, a legacy suspect or deferred variations: those wait on the maintainer, whose answer on the PR comes back through step 5. Remove them with `python utils/desk_queue.py remove QUEUE <id> ...`, which also records each id as handled, so the writers do not queue the same finding again.
- Snooze for seven days the queue items of every worklist item that ended as a lead: `python utils/desk_queue.py snooze QUEUE --until <date> <id> ...`, with `<date>` from `date -u -d '+7 days' +%F`. The worklist skips them until then.
- Items whose subagent failed and items not reached stay queued without a snooze.
- Write `ledger/pending.md` from step 7, or delete it once its text is in the PR.
Expand Down Expand Up @@ -144,7 +145,7 @@ The desk never changes the guard, the procedure or the git directory's config, h

1. `sha256sum "$(git rev-parse --git-common-dir)/config" "$(git rev-parse --git-common-dir)"/hooks/* "$(git rev-parse --git-common-dir)"/info/* | diff <scratch>/git-dir.sha256 -` prints nothing: the git directory still holds the files step 3 hashed.
2. `git rev-parse HEAD` prints the commit you noted last. Note it at the end of step 2 and after each commit, rebase, reset or cherry-pick you run: a commit made through inline code leaves `git status` clean and would go out with your next push.
3. `git status --porcelain --ignored --untracked-files=normal -- .claude AGENTS.md REVIEW.md .github utils pixi.toml ':(glob)**/CLAUDE*.md' | grep -vxF -f <scratch>/tamper.txt -e '!! utils/__pycache__/' -e '!! utils/tidy_conf/__pycache__/'` prints nothing. It lists ignored files too, since Claude Code and Python load some of them, such as `.claude/settings.local.json`, a `CLAUDE.md` in any folder and `.pyc` files. It leaves out the lines step 4 saved and the two `__pycache__` folders the sort creates.
3. `git status --porcelain --ignored --untracked-files=normal -- .claude AGENTS.md REVIEW.md .github utils pixi.toml ':(glob)**/CLAUDE*.md' ':(exclude)utils/tidy_conf/data/titles.yml' | grep -vxF -f <scratch>/tamper.txt -e '!! utils/__pycache__/' -e '!! utils/tidy_conf/__pycache__/'` prints nothing. It lists ignored files too, since Claude Code and Python load some of them, such as `.claude/settings.local.json`, a `CLAUDE.md` in any folder and `.pyc` files. It leaves out the lines step 4 saved, the two `__pycache__` folders the sort creates and `titles.yml`, which step 6.3 checks with the data files.

When a part prints anything else, stop the run there, with nothing more committed, pushed or posted, and report what it printed (step 9).

Expand Down Expand Up @@ -184,7 +185,7 @@ Feedback read to: <cut-off, YYYY-MM-DDTHH:MM:SSZ>
<!-- END_UPDATES -->
```

- N counts the worklist items processed this run, M the conferences committed. "Feedback read to" carries the previous run's value until step 7 settles it; leave the line out while it has no value. Evidence has one row per changed field, grouped by conference; escape `|` inside cells as `\|`.
- N counts the worklist items processed this run, M the conferences committed. "Feedback read to" carries the previous run's value until step 7 settles it; leave the line out while it has no value. Evidence has one row per changed field and one per added variation (Field `titles`, Old empty, New the variation), grouped by conference; escape `|` inside cells as `\|`.
- On this run's first update, move the previous run's section, and the `ledger/pending.md` text from step 3, into `<details>`. When the body nears GitHub's 65,536-character limit, drop the oldest runs.
- Everything from `<!-- DESK_END -->` on belongs to the GitHub Actions writers, who add rows before `<!-- END_UPDATES -->`: `desk_pr_body.py` keeps it byte for byte, and keeps a body without the marker (a writer opened the PR) whole below the desk's part and the marker. A PR the desk opens ends with the writers' empty table shown above.

Expand All @@ -194,4 +195,6 @@ Each worklist item gets one Candidates row, except the `scout` item, which gets

- `actioned`: a change was committed.
- `dismissed`: checked with nothing to change, out of scope, already tracked, or rejected by the critic (give its reason).
- `deferred`: a needs-maintainer result or verdict, a lead without exact dates, a legacy suspect, or a subagent that failed. For the first and the third, the detail says what the maintainer must decide.
- `deferred`: a needs-maintainer result or verdict, a lead without exact dates, a legacy suspect, variations that would rename existing entries (step 6.3) with nothing else of the item committed, or a subagent that failed. For a needs-maintainer result or verdict, a legacy suspect and deferred variations, the detail says what the maintainer must decide.

Deferred variations go in the detail of their item's row whatever its outcome, with the entries they would rename.
12 changes: 9 additions & 3 deletions .claude/skills/critic/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ For each entry in `changes`, check:
- The name follows AGENTS.md "Names": spelled as the data spells the series, (`conference`, `year`) unique, the series not tracked under another name.
- A carried-over field matches the edition it cites.

Done when: every change has pass or fail with a one-line reason.
For each entry in `titles`, check:

- `url`, fetched again, carries the quote verbatim, and the quote uses the variation for this series: the same event, site or organiser.
- `key` is the series' `conference` as the data, or the new-series set, spells it.
- The variation follows AGENTS.md "Names": no other series shares it, and, ignoring case and accents, it is not a key, `global` or variation in `utils/tidy_conf/data/titles.yml` yet, not the `conference` of any entry or another series' `alt_name` in the three data files, and not listed against the key, or the key against it, in `utils/tidy_conf/data/rejections.yml`. Check each of these again for the forms `load_title_mappings` in `utils/tidy_conf/yaml.py` also matches: the variation without years, without "Conference" or "Conf", without spaces and without punctuation. "PyDay Chile 2026" fails, since without its year it is the shared name "PyDay Chile".

Done when: every change and every variation has pass or fail with a one-line reason.

## B. Screen every new series

Expand All @@ -43,7 +49,7 @@ Done when: every signal above has a noted outcome.

A change set flagged `denylist-review` gets `needs-maintainer`, whatever its checks show.

- `accept`: the changes pass and, for a new series, no signal against was found and at least one signal for was. A failing change goes in `drop` with its reason and the rest stand, as long as a new entry keeps every field `utils/tidy_conf/schema.py` requires.
- `accept`: the changes pass and, for a new series, no signal against was found and at least one signal for was. A failing change or variation goes in `drop` with its reason and the rest stand, as long as a new entry keeps every field `utils/tidy_conf/schema.py` requires.
- `reject`: the domain is on the denylist's `reject` list; the find shows pay-to-present fees, paid publication or no Python content; or nothing usable passes. Give the reason.
- `needs-maintainer`: everything in between, such as a page that did not render, the denylist's `review` list, mixed signals, or sources that disagree beyond the precedence rule in AGENTS.md.

Expand All @@ -53,7 +59,7 @@ Return:
item: <worklist item id>
verdict: accept # accept | reject | needs-maintainer
reason: <one line>
drop: [] # fields to leave out, each with its reason
drop: [] # fields and variations to leave out, each with its reason
checks:
- check: <what was checked>
result: pass # pass | fail | could-not-check
Expand Down
1 change: 1 addition & 0 deletions .claude/skills/enricher/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The desk hands you one worklist item about an edition the data holds in any of t
- `cfp_link`, `sponsor`, `finaid` and `coc`: each lands on the page it names.
- `twitter`, `mastodon`, `bluesky` and `youtube`: the conference's own accounts, as its site links them.
- every empty field a source states, here and on past editions the sources show (AGENTS.md "Complete data").
- the names the sources give the series: one that gives a variation (AGENTS.md "Names") goes in `titles`.
Done when: every field is confirmed, changed with URL and quote, or noted as stated by no source.
4. **Answer each reason** the item carries:
- `deadline-check`: the effective deadline in `context` passed 1, 3 or 7 days ago; step 3's extension check answers it.
Expand Down
Loading
Loading