Skip to content

Commit 40c4e68

Browse files
committed
Phase 6: docs
README rewritten for the markdown task-list format: - Folder layout uses .md filenames - Editing rules show standard markdown task list with the [/] and [!] custom states called out as non-standard - "Migrating from pre-0.2.0" section explains migrate_to_markdown() with preview-then-commit flow - Examples updated throughout (.txt -> .md, [X] - text -> - [X] text) - recurring.txt section reframes the workflow ("recurring lives in the manifest, not as inline `*` markers") - Coverage of preview mode adds migrate_to_markdown to the list - .gitignore tip drops `*.md` since .md is now canonical - Troubleshooting adds a mixed-extension hint CLAUDE.md updates: - Modules list adds R/recurring.R and R/migrate.R, calls out the dual-format .parse_task_line() helper in R/parse.R - Preview mode list adds migrate_to_markdown - Task File Format section rewritten as markdown, notes the parser still reads pre-0.2.0 files
1 parent f206a98 commit 40c4e68

2 files changed

Lines changed: 86 additions & 41 deletions

File tree

CLAUDE.md

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,21 @@ r -e 'tinypkgr::check()'
1919
## Architecture
2020

2121
**Core data flow:**
22-
1. `parse_todo()` - Parse .txt file → data.frame with columns: id, parent_id, period, section, name, recur, status, level, order, path
22+
1. `parse_todo()` - Parse .md (or legacy .txt) file → data.frame with columns: id, parent_id, period, section, name, recur, status, level, order, path
2323
2. `inherit_recur_to_parents()` - Bubble `recur=TRUE` up to parent tasks
2424
3. `rollup_status()` - Update parent status based on children (all x → x, any / → /, etc.)
25-
4. `write_todo_txt()` - Write data.frame back to .txt format
25+
4. `write_todo_txt()` - Write data.frame back to .md format
2626

2727
**Key modules:**
28-
- `R/parse.R` - Text parsing to data.frame (internal/full schema)
28+
- `R/parse.R` - Text parsing to data.frame (internal/full schema). `.parse_task_line()` is the dual-format lexer used by `parse_todo`, `roll_day`, `next_day`, and `tasks()`.
2929
- `R/tasks.R` - Agent-facing read API (`tasks()`)
3030
- `R/rollup.R` - Parent status calculation
3131
- `R/advance.R` - Period advancement logic (weekly/monthly/quarterly rollover)
3232
- `R/cli.R` - User-facing functions: `run_monday()`, `fix_parents()`, `sync_from_daily()`, `next_day()`
3333
- `R/roll_day.R` - Day-to-day list rollover (`roll_day()`)
34-
- `R/io.R` - File I/O (txt, markdown, html output)
34+
- `R/recurring.R` - Manifest reader + materializer
35+
- `R/migrate.R` - One-shot legacy `.txt``.md` converter (`migrate_to_markdown()`)
36+
- `R/io.R` - File I/O (markdown writer, html mirror)
3537
- `R/config.R` - Configuration via `config.yaml` or `hacer_config.R`
3638

3739
## Agent-facing read API
@@ -53,23 +55,29 @@ r -e 'tinypkgr::check()'
5355

5456
## Preview mode
5557

56-
Every mutator (`roll_day`, `run_monday`, `fix_parents`, `next_day`, `sync_from_daily`, `instantiate_todo`) accepts `preview = TRUE` and returns a `hacer_preview` describing the would-be change without writing. Set `HACER_PREVIEW=1` to flip the default — useful for one-shot agent invocations that should be inspectable before they touch the user's todo repo. Internals live in `R/preview.R`; each mutator builds a `targets` list of `path -> new_lines` and dispatches via `.write_or_preview()`.
58+
Every mutator (`roll_day`, `run_monday`, `fix_parents`, `next_day`, `sync_from_daily`, `instantiate_todo`, `migrate_to_markdown`) accepts `preview = TRUE` and returns a `hacer_preview` describing the would-be change without writing. Set `HACER_PREVIEW=1` to flip the default — useful for one-shot agent invocations that should be inspectable before they touch the user's todo repo. Internals live in `R/preview.R`; each mutator builds a `targets` list of `path -> new_lines` and dispatches via `.write_or_preview()`.
5759

58-
## Task File Format
60+
## Task File Format (0.2.0+)
5961

60-
```
61-
# Section Header
62+
```markdown
63+
# todo_yymmdd_daily.md
64+
65+
## Monday
6266

63-
[ ] - Parent Task
64-
[/] - Child in progress
65-
[x] - Child done
66-
[ ] -*Recurring Task
67+
- [ ] Parent Task
68+
- [/] Child in progress
69+
- [x] Child done
70+
- [!] Blocked thing
6771
```
6872

69-
- Two spaces per indent level
70-
- Status: `[ ]` todo, `[/]` in progress, `[x]` done, `[!]` blocked
71-
- `*` prefix = recurring (preserved across rollovers)
72-
- `[!]` is sticky: rollup gives it precedence over all other statuses, and `roll_day()` / `run_monday()` / `next_day()` preserve it verbatim until a human or agent changes it
73+
- Standard markdown task list. Two spaces per indent level.
74+
- Status: `[ ]` todo, `[/]` in progress, `[x]` done, `[!]` blocked. `[/]` and `[!]` are non-standard but render fine in Obsidian.
75+
- Day sections in Daily are `## ` (H2). The first line is the H1 file-name header.
76+
- Recurring tasks are declared in `recurring.txt`, not as inline `*` markers.
77+
78+
The dual-format parser still reads pre-0.2.0 files (`[X] - text` syntax in `.txt` files with `# Section` H1 day headers). The writer never emits the legacy format. `migrate_to_markdown()` does a one-shot conversion of the live dir.
79+
80+
- `[!]` is sticky: rollup gives it precedence over all other statuses, and `roll_day()` / `run_monday()` / `next_day()` preserve it verbatim until a human or agent changes it.
7381

7482
## Dependencies
7583

README.md

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# hacer
22

3-
Plain-text nested ToDo files: parse, roll up, and advance.
3+
Plain-text nested ToDo files in markdown: parse, roll up, and advance.
44

55
Edit in any text editor. Run a couple of small helpers. Keep history in git.
66

@@ -33,20 +33,37 @@ run_monday() # advances week, archives prior
3333
├─ hacer_config.R # edit paths/options here
3434
├─ recurring.txt # recurring tasks + frequencies (optional)
3535
├─ this_week/ # live files you edit daily
36-
│ ├─ todo_yymmdd_daily.txt
37-
│ ├─ todo_yymmdd_week.txt
38-
│ ├─ todo_yymmdd_month.txt
39-
│ └─ todo_yymmdd_quarter.txt
36+
│ ├─ todo_yymmdd_daily.md
37+
│ ├─ todo_yymmdd_week.md
38+
│ ├─ todo_yymmdd_month.md
39+
│ └─ todo_yymmdd_quarter.md
4040
└─ archive/ # prior weeks (commit/push to GitHub)
4141
```
4242

43-
> Pre-0.1.7 repos used capitalized `ToDo_YYMMDD_*.txt` filenames. Readers stay case-insensitive so legacy files keep working — new writes always emit the lowercase form.
43+
> Pre-0.2.0 repos used `[X] - text` syntax in `.txt` files. Readers still understand both formats during transition; new writes always emit standard markdown task lists in `.md`. Run `migrate_to_markdown()` to convert your live files in one shot.
4444
4545
## Editing rules (syntax)
4646

47-
- Two spaces per indent level for sub-tasks.
48-
- Status: `[ ]` = todo, `[/]` = in progress, `[x]` = done, `[!]` = blocked (attention needed).
49-
- Recurring: prefix name with `*` (e.g., `[ ] -*Exercise`) → `recur = TRUE`.
47+
Standard markdown task list. Two spaces per indent level:
48+
49+
```markdown
50+
# todo_260427_daily.md
51+
52+
## Monday
53+
54+
- [ ] House
55+
- [/] Drywall
56+
- [x] Trim
57+
- [!] Permit blocked on city review
58+
59+
## Tuesday
60+
61+
- [ ] CO2
62+
```
63+
64+
- Status: `[ ]` = todo, `[/]` = in progress, `[x]` = done, `[!]` = blocked (attention needed). `[x]` and `[ ]` are vanilla markdown; `[/]` and `[!]` are custom states (Obsidian renders them, GitHub falls back to plain text).
65+
- Day sections in Daily are H2 headers (`## Monday`).
66+
- Recurring tasks live in `recurring.txt`, not as inline `*` markers in the file. The pre-0.2.0 `*` marker is still parsed (so legacy files keep working) but the writer never emits it.
5067
- Parents auto-roll:
5168
- any child `[!]` → parent `[!]` (blocked bubbles up; takes precedence)
5269
- all children `x` → parent `x`
@@ -73,16 +90,16 @@ MTWR cornball.ai > Lil Casey > Countdown # nested path
7390
- `*` is shorthand for `MTWRF`.
7491
- Optional week-of-month prefix `<n>W:` (e.g. `1W:M` = first Monday of the month).
7592
- Nested paths use ` > ` as the separator; ancestors are auto-materialized as recurring containers.
76-
- Non-recurring one-off tasks (e.g. an ad-hoc `[ ] - CO2`) live in Daily as before; they aren't touched by the manifest.
93+
- Non-recurring one-off tasks (e.g. an ad-hoc `- [ ] CO2`) live in Daily as before; they aren't touched by the manifest.
7794

78-
The manifest is **opt-in**. If `recurring.txt` is absent, `run_monday()` behaves exactly as it did pre-0.1.8.
95+
The manifest is **opt-in**. If `recurring.txt` is absent, `run_monday()` carries last week forward without materializing anything new.
7996

8097
## Period & carry-over logic
8198

8299
- Weekly rollover (`run_monday()`):
83-
- **Daily/Week**: drop items that are `x` and **not** recurring; keep `/`; keep all `*`.
84-
- **Month/Quarter**: keep `x` until period changes; at new month/quarter, non-recurring `x` are cleared; recurring `x` reset to blank.
85-
- Recurring `*` **bubble up to parents** so containers (projects) stick around.
100+
- **Daily/Week**: drop items that are `x` and not in `recurring.txt`; keep `/` and `[!]`.
101+
- **Month/Quarter**: keep `x` until period changes; at new month/quarter, non-recurring `x` are cleared.
102+
- Recurring items are materialized fresh from `recurring.txt`.
86103
- **Strict subset**: Week ⊆ Month ⊆ Quarter. If you add an ad-hoc task in **Daily**, you can sync it upward.
87104

88105
## Common commands
@@ -91,7 +108,7 @@ The manifest is **opt-in**. If `recurring.txt` is absent, `run_monday()` behaves
91108
# set the active repo for the session
92109
hacer::use_repo("~/todo")
93110

94-
# weekly rollover (creates new todo_yymmdd_* in this_week/, archives prior)
111+
# weekly rollover (creates new todo_yymmdd_*.md in this_week/, archives prior)
95112
hacer::run_monday()
96113

97114
# advance to tomorrow's daily section (preserves blank-line groups)
@@ -101,25 +118,45 @@ hacer::next_day()
101118
hacer::sync_from_daily()
102119

103120
# fix parent statuses in a file you're editing (rolls parents to / or x)
104-
hacer::fix_parents(file_name = "~/todo/this_week/todo_250915_daily.txt")
121+
hacer::fix_parents(file_name = "~/todo/this_week/todo_260427_daily.md")
105122

106123
# day-to-day: copy yesterday forward, drop done non-recurring, log to done.log
107124
hacer::roll_day()
108125

109126
# read everything as a data.frame (parsed from this_week/)
110127
hacer::tasks()
128+
129+
# convert legacy .txt files to .md (one-shot)
130+
hacer::migrate_to_markdown()
111131
```
112132

113133
> Tip: add `use_repo("~/todo")` to `~/.Rprofile` so you don't need to call it each session.
114134
135+
## Migrating from pre-0.2.0
136+
137+
Older repos used `[X] - text` syntax in `.txt` files. The dual-format parser keeps reading them, but the writer no longer emits that style. To convert in one shot:
138+
139+
```r
140+
library(hacer)
141+
use_repo("~/todo")
142+
143+
# Inspect first — preview reports the would-be conversions
144+
hacer::migrate_to_markdown(preview = TRUE)
145+
146+
# Then commit
147+
hacer::migrate_to_markdown()
148+
```
149+
150+
`migrate_to_markdown()` walks `this_week/` only (archive files stay legacy unless you decide they need attention), rewrites each `.txt` as `.md` in the markdown task-list syntax, and removes the `.txt` source. It also surfaces a list of any tasks that carried the legacy `*` recurring marker — copy those paths into `recurring.txt` so they keep recurring, otherwise they become one-offs.
151+
115152
## For LLM CLI agents
116153

117154
hacer's functions work fine as one-shot R calls from any agent that can spawn `Rscript` or `r` (Claude Code, Codex, etc.). Point hacer at a repo with the `HACER_REPO` environment variable — no `use_repo()` required:
118155

119156
```bash
120157
HACER_REPO=~/todo r -e 'hacer::run_monday()'
121158
HACER_REPO=~/todo r -e 'hacer::next_day()'
122-
HACER_REPO=~/todo r -e 'hacer::fix_parents("~/todo/this_week/todo_250915_daily.txt")'
159+
HACER_REPO=~/todo r -e 'hacer::fix_parents("~/todo/this_week/todo_260427_daily.md")'
123160
```
124161

125162
Resolution order for the repo path is: `repo_dir` argument → `options("hacer.repo")``HACER_REPO` env var → `tools::R_user_dir("hacer", "data")`. The env var makes the "stateless one-shot" case ergonomic. The `R_user_dir()` fallback is CRAN-safe but ugly (`~/.local/share/R/hacer/` on Linux), so most users `instantiate_todo("~/todo")` and persist `use_repo("~/todo")` in `~/.Rprofile`.
@@ -135,10 +172,10 @@ pv <- hacer::roll_day(preview = TRUE)
135172
print(pv)
136173
#> hacer preview
137174
#> created (1):
138-
#> + ~/todo/this_week/todo_250916_daily.txt
175+
#> + ~/todo/this_week/todo_260428_daily.md
139176
#> done.log appends (2):
140-
#> > 2025-09-16 [x] - Some finished task
141-
#> > 2025-09-16 [x] - A nested done item
177+
#> > 2026-04-28 - [x] Some finished task
178+
#> > 2026-04-28 - [x] A nested done item
142179
```
143180

144181
Set `HACER_PREVIEW=1` to flip the default for a one-shot CLI agent so it never accidentally writes:
@@ -148,7 +185,7 @@ HACER_REPO=~/todo HACER_PREVIEW=1 r -e 'hacer::run_monday()'
148185
HACER_REPO=~/todo HACER_PREVIEW=1 r -e 'hacer::roll_day()'
149186
```
150187

151-
Covers `roll_day()`, `run_monday()`, `fix_parents()`, `next_day()`, `sync_from_daily()`, and `instantiate_todo()`. The preview lists `files_created`, `files_modified`, line-level diffs, and any `done.log` lines that would be appended.
188+
Covers `roll_day()`, `run_monday()`, `fix_parents()`, `next_day()`, `sync_from_daily()`, `instantiate_todo()`, and `migrate_to_markdown()`. The preview lists `files_created`, `files_modified`, line-level diffs, and any `done.log` lines that would be appended.
152189

153190
### Reading: `tasks()` is the structured API
154191

@@ -157,8 +194,8 @@ Covers `roll_day()`, `run_monday()`, `fix_parents()`, `next_day()`, `sync_from_d
157194
```r
158195
tasks() # everything across all four cadences
159196
tasks(status = "in_progress") # just [/] tasks
160-
tasks(recurring = TRUE) # just *-prefixed tasks
161-
tasks(file = "~/todo/this_week/todo_250915_daily.txt")
197+
tasks(recurring = TRUE) # just tasks materialized from recurring.txt
198+
tasks(file = "~/todo/this_week/todo_260427_daily.md")
162199
```
163200

164201
Columns: `id`, `file`, `line`, `depth`, `status`, `recurring`, `text`, `parent_id`. `status` normalizes the bracket symbols to `"todo"`, `"in_progress"`, `"done"`, and `"blocked"`.
@@ -177,7 +214,7 @@ To get your repos tracked: add them to your todo files (any cadence) and ensure
177214

178215
## Weekly routine (Mon AM)
179216

180-
1. Open `~/todo/this_week/todo_yymmdd_daily.txt` and plan the week/day.
217+
1. Open `~/todo/this_week/todo_yymmdd_daily.md` and plan the week/day.
181218
2. `run_monday()` to advance periods and archive last week.
182219
3. `git -C ~/todo add archive && git -C ~/todo commit -m "Archive week" && git -C ~/todo push`.
183220

@@ -204,7 +241,6 @@ Add live artifacts to `.gitignore` (archive is what you push):
204241
```
205242
this_week/
206243
*.html
207-
*.md
208244
```
209245

210246
## Troubleshooting
@@ -213,6 +249,7 @@ this_week/
213249
- **Wrong repo**: call `use_repo("~/todo")` again (session-scoped).
214250
- **No new files**: check `hacer_config.R` paths; verify `this_week/` exists.
215251
- **Parent status didn't change**: run `fix_parents("<file>")`.
252+
- **Mixed `.txt` and `.md` in `this_week/`**: run `migrate_to_markdown()` to consolidate.
216253

217254
## License
218255

0 commit comments

Comments
 (0)