# Spec: `/lint-org` skill + wrap-up integration Drafted 2026-05-14 after a one-off pass cleared todo.org from 55 → 1 org-lint warnings. The pass surfaced enough recurring patterns to justify codifying them into a skill instead of relying on ad-hoc runs. ## Problem `todo.org` (and other tracked org files in `~/projects/work/`) accumulate lint warnings as the file gets edited. Categories observed in the 2026-05-14 baseline: - Stale `[[file:...]]` refs when target files get moved/renamed/deleted. - Stale `[[todo.org:NNN]]` line-number fuzzy links when headings shift. - `CLOSED:` and `DEADLINE:` / `SCHEDULED:` on separate lines instead of one. - Numbered lists fragmented across paragraph breaks (`4. … blank … 5.` looks like a new list starting at 5). - Bare `#+begin_src` blocks (prose drafts, no language slug). - Markdown-style `**Bold.**` at start of paragraphs (org reads it as a possible level-2 heading). - Heading-pattern characters (`***`, `*`) inside `=verbatim=` markup, which trips the misplaced-heading detector even though the markup is technically correct. - Src-blocks tagged with languages org doesn't know natively (`markdown`). Without a regular sweep these compound. The 2026-05-14 baseline accumulated 55 issues over months of editing. A nightly sweep keeps the count near zero forever, because each day's drift is small. ## Design Two pieces, depending on each other but not coupled: 1. **`/lint-org` skill** — does the work. Invocable ad-hoc on any org file. Runs in two modes: `interactive` (default; presents judgment items as numbered options) and `mechanical-only` (auto-fixes safe categories, logs judgment items elsewhere for follow-up). 2. **Wrap-up workflow integration** — `wrap-it-up.org` calls `/lint-org todo.org --mode=mechanical-only` after the existing `todo-cleanup.el --archive-done` pass. Judgment items get logged into the next day's daily-prep so they surface in the morning. The skill is the implementation; the workflow is the cadence. The skill works standalone if Craig wants to run it manually mid-day or against another file (`session-context.org`, prep docs, etc.). ## Skill: `/lint-org` ### Usage ``` /lint-org [FILE_PATH] [--mode=interactive|mechanical-only] ``` - `FILE_PATH` defaults to the project's primary org file (heuristic: look for `todo.org` in the cwd's `.ai/` root, fall back to asking). - `--mode` defaults to `interactive`. ### Modes **`interactive`** (default — ad-hoc invocation, Craig at the keyboard): 1. Run org-lint via `emacs --batch`. 2. Categorize issues into *mechanical* and *judgment* buckets. 3. Auto-apply the mechanical fixes silently. 4. Walk each judgment item with Craig — inline numbered options per the no-popup rule in `interaction.md`. 5. Re-run lint; report before/after totals. **`mechanical-only`** (called by wrap-it-up at end of day): 1. Run org-lint. 2. Auto-apply only the mechanical fixes. 3. Append a "Lint follow-ups" section to tomorrow's daily-prep (or a known carry-forward inbox) listing each judgment item with its line number, category, and proposed resolutions. 4. Re-run lint; report before/after totals + count of items deferred to tomorrow's prep. 5. Never block the workflow on a judgment item — defer. ### Issue categorization #### Mechanical (always-safe auto-fixes) | Category | Fix | |----------|-----| | `item-number` | Add `[@N]` directive: `4. content` → `4. [@4] content`. The bullet number stays, the directive tells org to treat it as item N regardless of position in the parsed list. | | `missing-language-in-src-block` | Convert bare `#+begin_src ... #+end_src` to `#+begin_example ... #+end_example`. Caveat: only when the block contains prose; if a future case has code without a language slug, the right fix is to add the language (defer to judgment mode). | | `misplaced-planning-info` | Merge two-line `CLOSED:` / `DEADLINE:` / `SCHEDULED:` blocks onto a single line. Use the file's existing convention (probe with grep — typically `CLOSED: [...] DEADLINE: <...>` order). If no existing convention, default to `CLOSED:` first, then `DEADLINE:`/`SCHEDULED:`. | | `misplaced-heading` (markdown-bold case) | `**X**` at the start of a paragraph (where X is short prose followed by `.` or `,`) → `*X*` (org-mode single-asterisk bold). Distinguish from real heading-misplacement by: line is in body of a heading, the `**...**` is short (under ~50 chars), and there's no following blank line + body that would imply a real heading. | #### Judgment (interactive prompt or defer to prep) | Category | Resolutions to offer | |----------|----------------------| | `link-to-local-file` | 1. Repair to a renamed/moved file (heuristic search for matching basename or normalized name). 2. Drop to `=verbatim=` text with a "(file never landed)" annotation. 3. Leave as-is + file a TODO entry to create the file. 4. Skip (leave the warning). | | `invalid-fuzzy-link` | 1. Repair to a `[[*Heading name]]` ref if a heading with similar title exists. 2. Drop to `=verbatim label=` text. 3. Skip. | | `misplaced-heading` (verbatim-asterisk case — `=*** Foo=` inside body prose) | 1. Strip the asterisks and rephrase to preserve the semantic context (e.g. add "level-3" before `=Foo=`). 2. Convert the surrounding markup to `~Foo~` (code style) which doesn't trip the detector. 3. Skip. | | `suspicious-language-in-src-block` | 1. Surface an Emacs-init one-liner to register the language (the right fix when the language label is accurate but unknown to org-babel — e.g. `markdown`). 2. Change the block label to `text` or `example`. 3. Skip. | ### Implementation notes - **Lint runner.** Use `emacs --batch -Q --eval` to load `org-lint` and emit a structured report. The skill should NOT depend on Craig's interactive Emacs config — `-Q` ensures clean lint behavior across machines. - **Result parsing.** `org-lint` returns a list of `(id [marker trust msg checker])`. The marker carries the line number as a string with a text property. The checker is a struct whose name field identifies the category. - **Bulk transforms.** Mechanical fixes that span many lines (e.g. `[@N]` directives across a flagged range) are cleaner via an `awk` pipeline than 20 separate `Edit` calls. Use `awk` for: per-line bullet-number directives, bulk `#+begin_src` → `#+begin_example` conversions when ALL flagged bare blocks share the exact pattern (probe-count first). - **Per-site transforms.** Markdown-bold conversions and verbatim-asterisk fixes need the surrounding sentence for context; use `Edit` per site rather than `awk`. - **Backup.** Before applying any transform, copy the file to `/tmp/.before-lint-pass.` so an unexpected change is recoverable without git. - **Re-run cadence.** After each fix batch, re-run lint and report deltas. If a fix raised a new warning (rare but possible — e.g. converting `*X*` could change paragraph parsing), surface it before continuing. ### Output format For interactive mode: ``` ## /lint-org — todo.org Pre-pass: issues across categories. ### Mechanical fixes applied (auto) - : fixed at lines - ... ### Judgment items (resolve inline) 1. line : Resolutions: 1.1