aboutsummaryrefslogtreecommitdiff
path: root/.claude/commands/lint-org.md
blob: 953629c617afd96d59a54557054aa869a0025261 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
---
description: Run org-lint over a tracked .org file, apply mechanical auto-fixes silently, then walk judgment items with the user. Mechanical categories — item-number (add [@N] counters to drifted bullets), missing-language-in-src-block (convert bare #+begin_src to #+begin_example), misplaced-planning-info (merge multi-line CLOSED:/DEADLINE:/SCHEDULED: onto one line), misplaced-heading markdown-bold case (**X.** → *X.*). Judgment categories — broken file: links, invalid fuzzy links, verbatim-asterisk inside body prose (=*** Foo=), unknown source-block languages. Modes — `interactive` (default) walks each judgment with inline numbered options and applies user-chosen resolutions; `mechanical-only` applies mechanical, appends remaining judgment items to a carry-forward file for next-morning review. Defaults FILE to `todo.org` in cwd. Backs up the file to /tmp before any modification. Use to clean up accumulated org-lint warnings on a tracked file. Do NOT use for markdown files (wrong tool), for org files outside the project (cross-project boundary), or to enforce style rules beyond what org-lint flags.
disable-model-invocation: true
---

# /lint-org — Sweep an org file's lint warnings

Apply mechanical fixes silently, walk judgment items with the user.

## Usage

```
/lint-org [FILE] [--mode=interactive|mechanical-only]
```

- `FILE` — path to the .org file. Defaults to `todo.org` in the current working directory.
- `--mode` — `interactive` (default) or `mechanical-only`.
- Backs up the file to `/tmp/<basename>.before-lint-pass.<timestamp>` before any modification.

## Scope

In scope:

- `todo.org` and other tracked org files at the project root or under `.ai/`.
- Mechanical fixers listed in **Categorization** below.
- Judgment items in the four documented categories, plus a generic "unhandled" fallback.

Out of scope (refuse, don't try to lint):

- Markdown files — use a markdownlint tool.
- Org files outside the current project — flag a cross-project boundary per `protocols.org`.
- Style rules beyond what `org-lint` flags (line length, sentence reflow, etc.).

## Categorization

**Mechanical (auto-fixed in non-`--check` modes):**

| Checker | Fix |
|---------|-----|
| `item-number` | Add `[@N]` directive: `4. content` → `4. [@4] content`. |
| `missing-language-in-src-block` | Convert bare `#+begin_src` ... `#+end_src` to `#+begin_example` ... `#+end_example`. |
| `misplaced-planning-info` | Merge multi-line `CLOSED:`/`DEADLINE:`/`SCHEDULED:` onto a single canonical line (CLOSED → DEADLINE → SCHEDULED order). |
| `misplaced-heading` *(markdown-bold case)* | `**X.**` at start of line → `*X.*`. The verbatim-asterisk case (`=*** Foo=` in body prose) stays judgment. |

**Judgment (walked inline in `interactive` mode, deferred in `mechanical-only`):**

| Checker | Resolutions to offer |
|---------|----------------------|
| `link-to-local-file` | (1) Repair to a renamed/moved file by heuristic search. (2) Drop to `=verbatim=` text with a "(file never landed)" annotation. (3) Leave as-is and file a TODO entry. (4) Skip. |
| `invalid-fuzzy-link` | (1) Repair to a `[[*Heading]]` ref if a similar heading exists. (2) Drop to `=verbatim label=` text. (3) Skip. |
| `misplaced-heading` *(verbatim-asterisk case)* | (1) Strip asterisks and rephrase to preserve semantics. (2) Convert surrounding markup to `~code~` style. (3) Skip. |
| `suspicious-language-in-src-block` | (1) Emit an Emacs init one-liner that registers the language. (2) Change the block label to `text` or `example`. (3) Skip. |
| anything else | Surface the raw `org-lint` message and ask the user how to proceed. |

## Phase A — Run the script

1. Resolve `FILE` — argument if given, else `todo.org` in cwd. If the file doesn't exist, abort with a clear error.
2. Confirm `FILE` is inside the current project (no cross-project boundary). If not, follow `protocols.org` — stop and ask.
3. Invoke the script:

   ```bash
   emacs --batch -q -l .ai/scripts/lint-org.el FILE
   ```

   The script applies every mechanical fix, then emits structured stdout. First line is a summary; each subsequent line is a plist describing one issue:

   ```
   ;; lint-org: file=todo.org mechanical=4 judgment=11
   (:kind mechanical-fixed :line 23 :checker item-number :msg "...")
   (:kind judgment :line 41 :checker link-to-local-file :msg "...")
   ...
   ```

4. Parse the output into two lists: `mechanical-fixed` and `judgment`. Save the counts.

## Phase B — Handle judgments

### `interactive` mode (default)

Walk each judgment item one at a time. For each:

1. Read the file at the reported line plus a few lines of surrounding context (use the Read tool with `offset` and `limit`).
2. Present the item with inline numbered resolutions per `interaction.md` (no popup menu). Shape:

   ```
   ### Judgment 3/11 — line 87, link-to-local-file

   Context (lines 85-89):
       ...
       See [[file:notes/2025-old-plan.org][the original plan]].
       ...

   Resolutions:
   1. Repair — closest match in the repo: `notes/2026-old-plan.org`. Update the link.
   2. Drop to verbatim — `=2025-old-plan.org=` with a "(file never landed)" annotation.
   3. Leave as-is and add a TODO to create `notes/2025-old-plan.org`.
   4. Skip — leave the warning in place.

   Pick a number.
   ```

3. Apply the chosen resolution with `Edit`. If the user picks "Skip," do nothing.
4. Move to the next judgment.

After the walk:

5. Re-run `emacs --batch -q -l .ai/scripts/lint-org.el --check FILE` to get post-pass counts.
6. Report: pre-pass total, mechanical applied, judgments resolved (per resolution), judgments skipped, post-pass total.

### `mechanical-only` mode

No interactive walk. Append remaining judgment items to a carry-forward file so the next morning's review picks them up.

1. Determine the carry-forward path:
   - `$LINT_ORG_FOLLOWUPS` if set in the environment.
   - Else `~/projects/work/inbox/lint-followups.org` if `~/projects/work/inbox/` exists.
   - Else `<project-root>/.ai/lint-followups.org`.
2. Append a dated heading and one entry per judgment:

   ```org
   * 2026-05-14 lint-org follow-ups — todo.org
   ** TODO line 87 — link-to-local-file — Link to non-existent local file "notes/2025-old-plan.org"
   ** TODO line 132 — invalid-fuzzy-link — Unknown fuzzy location "Architecture Spike"
   ...
   ```

   Use absolute today's date (run `date "+%Y-%m-%d"`) — no relative dates.
3. Re-run lint in `--check` mode for post-pass counts.
4. Report: pre-pass total, mechanical applied, judgments deferred to `<carry-forward path>`, post-pass total. Don't block.

## Phase C — Final report

Print a concise summary in either mode:

```
## /lint-org — todo.org

Pre-pass: 15 issues across 5 categories.
Mechanical applied: 4 (item-number ×2, missing-language ×1, misplaced-planning ×1).
Judgments resolved: 8 (repaired ×3, dropped to verbatim ×4, todo-filed ×1).
Judgments skipped: 3.
Post-pass: 3 issues remain.
```

The file is left uncommitted — the user reviews the diff and commits if it looks right. Never auto-commit.

## Edge cases

- **File doesn't exist** — surface the error from the script; don't continue.
- **Zero lint issues** — report `Pre-pass: 0 issues. Nothing to do.` and exit. `mechanical-only` mode must not write an empty section to the carry-forward file in this case.
- **Script fails** — `org-lint` errors, malformed file, emacs missing. Surface the raw stderr, don't claim success.
- **Mechanical fixer declines** — the script emits the item as `judgment` if a fixer's preconditions don't hold (already fixed, unexpected shape). Treat as a normal judgment.
- **User cancels mid-walk** — leave already-applied resolutions in place. The file is uncommitted, so a `git checkout -- FILE` reverts cleanly. Surface that option if asked.
- **Judgment item depends on context the model can't access** — always offer "Skip" so the user can defer.

## Anti-patterns

- Auto-applying judgment resolutions without user input. Every judgment requires explicit selection.
- Modifying lines outside the flagged issue. Each fix touches one site.
- Committing the changes on the user's behalf. The file is left uncommitted by design.
- Editing the carry-forward file's prior entries. Append a new dated section per run; old sections stay.
- Adding new categories silently. New mechanical categories need a test in `tests/test-lint-org.el` and a docs update here.