From 98385b1302044a773d3dce785b18baa8b2cf6444 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 13 Jun 2026 11:35:22 -0600 Subject: [PATCH 01/15] chore(agenda): add ORCHESTRATE implementation plan Working artifact for feature/agenda. Implement in a fresh session from this worktree. See docs/specs/SPEC-agenda-schedule-2026-06-13.md. Delete this file during dev merge cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) --- ORCHESTRATE-agenda.md | 99 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 ORCHESTRATE-agenda.md diff --git a/ORCHESTRATE-agenda.md b/ORCHESTRATE-agenda.md new file mode 100644 index 000000000..0697698ec --- /dev/null +++ b/ORCHESTRATE-agenda.md @@ -0,0 +1,99 @@ +# ORCHESTRATE: agenda + forward-looking schedule layer + +> **Feature branch:** `feature/agenda` · **Base:** `dev` · **Worktree:** `~/.git-worktrees/flow-cli/agenda` +> **Spec:** `docs/specs/SPEC-agenda-schedule-2026-06-13.md` (committed on `dev`) +> **Plan:** `~/.claude/plans/eventual-bouncing-phoenix.md` +> **Target version:** v7.10.0 (minor) + +## ⛔ Session boundary + +This file was created by the **planning session on `dev`**. Implementation happens **here, in a +NEW `claude` session started from this worktree**: + +```bash +cd ~/.git-worktrees/flow-cli/agenda && claude +``` + +Do NOT implement from the dev/planning session. + +--- + +## Goal + +Add a forward-looking schedule layer: new `agenda` command + UPCOMING section in `dash` + +dated enrichment of `morning`/`today`/`week`, driven by one shared engine `lib/schedule.zsh`. +Works fully without atlas and without `yq`. See the spec for the full rationale and data model. + +## Reuse (read-only — do NOT reinvent) + +- `lib/date-parser.zsh` → `_date_load_config` (teaching dates: `week_N`/`exam_*`/`deadline_*`/`holiday_*`), `_date_add_days`, `_date_normalize`, `_date_compute_from_week`. +- `lib/atlas-bridge.zsh` → `_flow_has_atlas`, `_flow_atlas_async`, `_flow_list_projects`, `_flow_timestamp`. +- `commands/dash.zsh` → `_dash_find_project_path`, `_dash_get_status_field`, `_dash_detect_category`, `_dash_get_urgency`. +- `lib/core.zsh` → `FLOW_COLORS`. Cache pattern → `_dash_quick_health_check`. + +## Tasks (TDD: write/extend the test, then implement; `./tests/run-all.sh` + `source flow.plugin.zsh` green before each commit) + +### 1. Engine — `lib/schedule.zsh` (new) → `tests/test-schedule.zsh` +- [ ] Module guard `_FLOW_SCHEDULE_LOADED`; `typeset -g SCHEDULE_DEFAULT_WINDOW=7`. +- [ ] `_schedule_classify [window]` → `overdue|today|soon|later`. +- [ ] `_schedule_relative_days ` → "today"/"in 3d"/"overdue 2d". +- [ ] `_schedule_parse_status ` → records from `## Schedule:` (no external cmds); infers `project=${status_file:h:t}`. +- [ ] `_schedule_teach_items [window]` → `local -A CONFIG_DATES; eval "$(_date_load_config ...)"`; map week/exam/deadline; holidays typed `holiday` (filtered unless `--all`); guarded by `command -v yq` + file exists. +- [ ] `_schedule_expand_recurring ` → concrete dates (`strftime '%u'` + `_date_add_days`). +- [ ] `_schedule_collect [window] [category]` → orchestrate over `_flow_list_projects`; emit record stream; session cache. +- [ ] `_schedule_filter_window ` (stdin) → in-window + always overdue. +- [ ] `_schedule_sort` (stdin) → `sort -t'|' -k1,1`. +- [ ] `_schedule_render_line ` → type icon + urgency color + relative-day + label + dim project. +- [ ] `_flow_schedule_to_atlas ` → `_flow_has_atlas || return 0`; cap-probe `_FLOW_ATLAS_HAS_SCHEDULE`; `_flow_atlas_async`; no-op if absent. +- **Tests:** parse block; empty/malformed → no crash; classify boundaries (frozen "today" arg); expand across **month+year** boundary; window filter; teach parse (fixture w/ `start_date`); **no-yq fallback**; **atlas-absent no-op** (mock `_flow_atlas_async` asserts not called). +- **Footguns:** no `local path=` / `local status=`; `local -A CONFIG_DATES`; split with `${(f)...}`. + +### 2. Wiring — `flow.plugin.zsh` +- [ ] Source `lib/date-parser.zsh` (if not already global) **then** `lib/schedule.zsh` in the core library block, after `atlas-bridge.zsh`. + +### 3. Command — `commands/agenda.zsh` (new) → `tests/test-agenda.zsh` +- [ ] `agenda [today|-w/--week|-m/--month||--all|--overdue|-h/--help]`; default = 7d. +- [ ] Pipeline `_schedule_collect | _schedule_filter_window | _schedule_sort` → bucketed render (OVERDUE/TODAY/THIS WEEK/LATER) + calm empty state; then async atlas push. +- [ ] `_agenda_help` (dash-style, `_C_*` locals). Aliases `agt`/`agw`/`agm` (avoid `ag`). +- **Tests:** `-h`; default/`--overdue`/category/`--all`/`today` vs `-m`; empty state; `run_isolated` + `FLOW_ATLAS_ENABLED=no` + temp `FLOW_PROJECTS_ROOT`. + +### 4. dash — `commands/dash.zsh` → extend `tests/test-dash.zsh` +- [ ] `_dash_upcoming` (top 3–4, 7d + overdue); insert in `dash()` **after `_dash_quick_wins`, before `_dash_quick_access`**; self-suppress when empty. +- [ ] Date-keyed session cache (~600s TTL) shared with agenda/morning. + +### 5. Cadence — `commands/morning.zsh` +- [ ] `_flow_morning_agenda` in `morning` (top 5, 7d + overdue) between projects and wins; quick mode one-liner. +- [ ] `today` → "📅 Due today" (0d) + overdue. `week` → "📅 This week's deadlines" (7d, by weekday). + +### 6. Packaging +- [ ] `completions/_agenda` (model `completions/_dash`): flags + category states. +- [ ] `man/man1/agenda.1` (model `man/man1/g.1`); `.TH` = `flow-cli `. +- [ ] **Patch `tests/test-manpage-version-sync.zsh`** orphan check: add `[[ "$base" == "agenda" ]] && continue` next to the `flow` skip (~line 223). REQUIRED or CI fails. +- [ ] Register `tests/test-schedule.zsh` + `tests/test-agenda.zsh` in `tests/run-all.sh`. +- [ ] Add a fixture `.flow/teach-config.yml` with `weeks[].start_date` (demo uses `weeks[].date` → yields no `week_N`). + +### 7. Docs +- [ ] `CLAUDE.md` (command list + Quick Reference), `docs/help/QUICK-REFERENCE.md` (`agenda` + aliases + `## Schedule:` snippet). +- [ ] `docs/reference/MASTER-DISPATCHER-GUIDE.md` (commands section; note dash UPCOMING + cadence enrichment). +- [ ] New `docs/guides/AGENDA-SCHEDULE-GUIDE.md`; add to `mkdocs.yml` nav (strict build). +- [ ] `docs/ATLAS-CONTRACT.md` — document opportunistic `atlas schedule push --format=json` contract. + +## Verification (before PR) + +1. `source flow.plugin.zsh` clean (no errors / leaked vars). +2. `./tests/run-all.sh` green (new + touched suites; 1 expected interactive timeout). +3. Manual: temp `FLOW_PROJECTS_ROOT` with a `## Schedule:` block + teach project w/ `start_date`; run + `agenda`, `agenda --overdue`, `agenda research`, `agenda -m`, `agenda -h`; verify buckets/overdue/filter/empty. +4. `dash` shows UPCOMING after QUICK WINS; suppresses when none. `morning`/`today`/`week` show dated blocks. +5. `FLOW_ATLAS_ENABLED=no agenda` fully works; atlas-without-`schedule` → silent no-op. +6. `mkdocs build --strict` passes. + +## Integrate + +- [ ] `git fetch origin dev && git rebase origin/dev` → `./tests/run-all.sh` → `gh pr create --base dev`. +- [ ] Bump version (v7.10.0) per release flow when merging dev→main. +- [ ] **Delete this `ORCHESTRATE-agenda.md` as part of the merge cleanup** (working artifact — belongs on the feature branch, not on `dev`). + +## Out of scope (v1) + +Atlas-side `schedule` command (separate atlas PR); `monthly:`/multi-day recurrence; ICS export; interactive `agenda add`. From 0e86cbcb54187fd56aa53b78faeefd57dd5f71e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 20:05:20 +0000 Subject: [PATCH 02/15] feat(schedule): add forward-looking schedule engine Add lib/schedule.zsh, the shared engine for the new agenda layer. It aggregates dated activity from two greenfield sources and normalizes them to a pipe-delimited record stream (date|label|type|project| recurrence|source) so every surface renders identically: - .STATUS `## Schedule:` blocks (ZSH-parseable, no yq) - .flow/teach-config.yml teaching dates (via _date_load_config, yq-guarded) Functions: _schedule_classify, _schedule_relative_days, _schedule_parse_status, _schedule_teach_items, _schedule_expand_recurring (weekly:, crosses month/year boundaries), _schedule_collect (session cached), _schedule_filter_window, _schedule_sort, _schedule_render_line, and _flow_schedule_to_atlas (opportunistic, capability-detected, silent no-op when atlas/schedule absent). Reuses date-parser + atlas-bridge + dash helpers + FLOW_COLORS; pure ZSH, works without atlas and without yq. Wire date-parser.zsh then schedule.zsh into the core library block (after atlas-bridge.zsh). tests/test-schedule.zsh (32 cases) covers classification boundaries, relative-day labels, .STATUS parsing (empty/malformed safe), recurrence expansion across month+year boundaries, window filtering, the no-yq fallback, and the atlas-absent no-op. Adds tests/fixtures/ teach-config-scheduled.yml (weeks[].start_date, which _date_load_config requires; the demo fixture uses weeks[].date and yields no week_N). Verified: test-schedule (32/32), source flow.plugin.zsh clean. Full run-all.sh has 17 pre-existing environmental failures (atlas/git/chezmoi/ himalaya/network-dependent), unchanged by this commit (confirmed against a stashed baseline). https://claude.ai/code/session_014YpEdtSqYMRdcuoMQFgLXW --- flow.plugin.zsh | 2 + lib/schedule.zsh | 501 ++++++++++++++++++++++ tests/fixtures/teach-config-scheduled.yml | 44 ++ tests/test-schedule.zsh | 405 +++++++++++++++++ 4 files changed, 952 insertions(+) create mode 100644 lib/schedule.zsh create mode 100644 tests/fixtures/teach-config-scheduled.yml create mode 100644 tests/test-schedule.zsh diff --git a/flow.plugin.zsh b/flow.plugin.zsh index 0c8cd9896..17626f2b2 100644 --- a/flow.plugin.zsh +++ b/flow.plugin.zsh @@ -30,6 +30,8 @@ FLOW_PLUGIN_DIR=${0:A:h} source "$FLOW_PLUGIN_DIR/lib/core.zsh" source "$FLOW_PLUGIN_DIR/lib/config.zsh" source "$FLOW_PLUGIN_DIR/lib/atlas-bridge.zsh" +source "$FLOW_PLUGIN_DIR/lib/date-parser.zsh" +source "$FLOW_PLUGIN_DIR/lib/schedule.zsh" source "$FLOW_PLUGIN_DIR/lib/dotfile-helpers.zsh" source "$FLOW_PLUGIN_DIR/lib/project-detector.zsh" source "$FLOW_PLUGIN_DIR/lib/project-cache.zsh" diff --git a/lib/schedule.zsh b/lib/schedule.zsh new file mode 100644 index 000000000..8b5869ec9 --- /dev/null +++ b/lib/schedule.zsh @@ -0,0 +1,501 @@ +# lib/schedule.zsh - Forward-looking schedule engine for flow-cli +# +# Shared engine powering the `agenda` command, the dash UPCOMING section, and +# the dated enrichment of morning/today/week. Aggregates forward-looking dated +# activity from two greenfield sources: +# +# 1. `.STATUS` `## Schedule:` blocks (per project) — ZSH-parseable, no yq. +# 2. `.flow/teach-config.yml` teaching dates (per teaching project) — via +# _date_load_config (lib/date-parser.zsh); guarded by yq + file existence. +# +# Records are normalized to a pipe-delimited stream so every surface renders +# identically: +# +# date|label|type|project|recurrence|source +# +# date ISO YYYY-MM-DD (concrete; recurring tokens are expanded) +# label human text (must not contain `|`) +# type teaching|research|general|recurring|holiday +# project inferred from ${status_file:h:t} +# recurrence `none` or `weekly:` +# source status|teach-config +# +# Pure ZSH. Works fully without atlas and without yq (the teaching path is the +# only one that needs yq; the research/general path needs none). Atlas is an +# opportunistic, capability-detected sync target. +# +# Reuses (read-only): _date_load_config / _date_add_days (lib/date-parser.zsh); +# _flow_has_atlas / _flow_atlas_async / _flow_list_projects (lib/atlas-bridge.zsh); +# _dash_find_project_path / _dash_detect_category (commands/dash.zsh); +# FLOW_COLORS (lib/core.zsh). + +# Module guard +[[ -n "$_FLOW_SCHEDULE_LOADED" ]] && return +typeset -g _FLOW_SCHEDULE_LOADED=1 + +zmodload zsh/datetime 2>/dev/null + +# ============================================================================ +# CONSTANTS +# ============================================================================ + +# Default look-ahead window (days) for agenda / dash / cadence. +typeset -g SCHEDULE_DEFAULT_WINDOW=7 + +# Session cache (date+window+category keyed, ~600s TTL) shared across surfaces. +typeset -g SCHEDULE_CACHE_TTL=600 +typeset -g _SCHEDULE_CACHE_RECORDS="" +typeset -g _SCHEDULE_CACHE_KEY="" +typeset -g _SCHEDULE_CACHE_TIME=0 + +# Atlas `schedule` subcommand capability probe (cached per session). +typeset -g _FLOW_ATLAS_HAS_SCHEDULE="" + +# ============================================================================ +# DATE HELPERS +# ============================================================================ + +# Today as ISO YYYY-MM-DD (ZSH-native). +_schedule_today() { + strftime '%Y-%m-%d' $EPOCHSECONDS +} + +# ============================================================================= +# Function: _schedule_classify +# Purpose: Classify an ISO date relative to today + a window +# Arguments: +# $1 - ISO date (YYYY-MM-DD) +# $2 - window in days [default: SCHEDULE_DEFAULT_WINDOW] +# Output: +# stdout - overdue|today|soon|later +# Notes: +# - Pure string comparison (ISO dates sort lexically) + _date_add_days. +# - "soon" = strictly future but within the window; "later" = beyond it. +# ============================================================================= +_schedule_classify() { + local iso="$1" + local window="${2:-$SCHEDULE_DEFAULT_WINDOW}" + [[ -z "$iso" ]] && return 1 + + local today=$(_schedule_today) + + if [[ "$iso" < "$today" ]]; then + echo "overdue" + elif [[ "$iso" == "$today" ]]; then + echo "today" + else + local window_end=$(_date_add_days "$today" "$window") + if [[ -n "$window_end" && "$iso" > "$window_end" ]]; then + echo "later" + else + echo "soon" + fi + fi +} + +# ============================================================================= +# Function: _schedule_relative_days +# Purpose: Human relative-day label for an ISO date +# Output: +# stdout - "today" | "in Nd" | "overdue Nd" +# Notes: +# - Parses at local noon (strftime -r) to avoid DST off-by-one. +# ============================================================================= +_schedule_relative_days() { + local iso="$1" + [[ -z "$iso" ]] && return 1 + + local today=$(_schedule_today) + local e t + e=$(strftime -r '%Y-%m-%d %H:%M:%S' "$iso 12:00:00" 2>/dev/null) || return 1 + t=$(strftime -r '%Y-%m-%d %H:%M:%S' "$today 12:00:00" 2>/dev/null) || return 1 + + local diff=$(( (e - t) / 86400 )) + if (( diff == 0 )); then + echo "today" + elif (( diff > 0 )); then + echo "in ${diff}d" + else + echo "overdue $(( -diff ))d" + fi +} + +# ============================================================================= +# Function: _schedule_type_icon +# Purpose: Map a record type to its display icon +# ============================================================================= +_schedule_type_icon() { + case "$1" in + teaching) echo "🎓" ;; + research) echo "🔬" ;; + recurring) echo "🔁" ;; + holiday) echo "🏖️" ;; + general|*) echo "📌" ;; + esac +} + +# ============================================================================ +# PARSING — .STATUS `## Schedule:` section (no external commands) +# ============================================================================ + +# ============================================================================= +# Function: _schedule_parse_status +# Purpose: Extract schedule records from a `.STATUS` `## Schedule:` block +# Arguments: +# $1 - path to a .STATUS file +# Output: +# stdout - records (date|label|type|project|recurrence|source) +# ISO entries carry a concrete date; `weekly:` entries carry an +# empty date field + recurrence token (expanded later by collect). +# Grammar (one list item per line): +# - |