From 101c672ca0fb184f2a4e560440cb4e8503b1fe98 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 30 Jun 2026 15:38:57 -0600 Subject: [PATCH 01/24] chore: remove stale worktrees (at-dispatcher-surface, flow-claude, tok-mint) Co-Authored-By: Claude Sonnet 4.6 --- .STATUS | 1 + 1 file changed, 1 insertion(+) diff --git a/.STATUS b/.STATUS index 307def03a..d248af4ca 100644 --- a/.STATUS +++ b/.STATUS @@ -19,6 +19,7 @@ - **Updated** `CONTRIBUTING.md` — fixed `mkdocs gh-deploy --force` step; added `not_in_nav` guidance - **Updated** `DOCUMENTATION-STYLE-GUIDE.md` — new MkDocs Configuration section - **Merged** PR #482 to main; docs auto-deploy triggered +- **Cleaned** 3 stale worktrees: `at-dispatcher-surface`, `feature-flow-claude`, `feature-tok-mint` (all merged) ## Previous Session (2026-06-30) — housekeeping + tok mint ✅ From e0b227ed5f58db2dcd616f9b5b493153819a3814 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 30 Jun 2026 15:59:50 -0600 Subject: [PATCH 02/24] =?UTF-8?q?chore:=20session=20wrap-up=20(2026-06-30)?= =?UTF-8?q?=20=E2=80=94=20Dependabot=20maintenance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Merged Dependabot PRs #477, #478, #481 (actions/checkout, lint-staged, prettier) - Synced dev with main (6 files) - Cleaned all worktrees (3 stale entries removed) - Removed stale "CI smoke tests only" pending item (full suite live since PRs #465+#466) - Updated status footer to v7.13.0 Co-Authored-By: Claude Sonnet 4.6 --- .STATUS | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.STATUS b/.STATUS index d248af4ca..57ed683ef 100644 --- a/.STATUS +++ b/.STATUS @@ -9,7 +9,7 @@ ## Priority: 2 ## Progress: 100 -## Current Session (2026-06-30) — mkdocs improvements + contributing docs ✅ +## Current Session (2026-06-30) — mkdocs improvements + Dependabot maintenance ✅ **Session activity:** - **Added** breadcrumbs (`navigation.path`), instant prefetch, and validation block to `mkdocs.yml` @@ -20,6 +20,8 @@ - **Updated** `DOCUMENTATION-STYLE-GUIDE.md` — new MkDocs Configuration section - **Merged** PR #482 to main; docs auto-deploy triggered - **Cleaned** 3 stale worktrees: `at-dispatcher-surface`, `feature-flow-claude`, `feature-tok-mint` (all merged) +- **Merged** 3 Dependabot PRs: #477 (actions/checkout 6→7), #478 (lint-staged 17.0.7→17.0.8), #481 (prettier 3.8.4→3.9.4) +- **Synced** dev with main (6 files: 4 workflow files + package.json + package-lock.json) ## Previous Session (2026-06-30) — housekeeping + tok mint ✅ @@ -440,7 +442,7 @@ | Worktree | Branch | Status | |----------|--------|--------| -| `~/.git-worktrees/flow-cli/feature-flow-claude-v2` | `feature/flow-claude-v2` | ORCHESTRATE written — awaiting impl in new session | +| — | — | All worktrees clean (as of 2026-06-30) | --- @@ -450,14 +452,6 @@ - Current coverage: ~50% (348 functions documented) - Target: 80% -### CI runs smoke tests only — full suite not gated (filed 2026-05-13) -- `.github/workflows/test.yml:39-44` runs only `test-flow.zsh` + `test-install.sh` -- Comment is explicit: "Smoke tests only - run full suite locally with: `./tests/run-all.sh`" -- The required status check "ZSH Plugin Tests" on `main` therefore does NOT verify the 205-file suite -- Surfaced during PR #446: the test-doctor regression from commit `0880f924` would have passed CI green despite test-doctor exiting 124 under the harness -- Impact: relies on developer discipline to run `./tests/run-all.sh` locally before opening PRs -- Options to consider: (B) add `test-doctor.zsh` specifically (~30s job time), or (C) add full `./tests/run-all.sh` as a separate slower job (~4min). Either is a separate workflow PR, not bundled with feature work - ### Future Enhancements - Token automation Phases 2-4 (multi-token, gamification) - Config → concept graph integration @@ -473,8 +467,8 @@ --- -**Last Updated:** 2026-06-18 -**Status:** v7.10.2 SHIPPED (main + Homebrew tap) — docs polish + dep maintenance | full suite REQUIRED on main — 64 passed / 0 failed / 1 skipped | 14 dispatchers + at bridge | 216 test files | 12000+ test functions | 0 lint errors | 0 broken links +**Last Updated:** 2026-06-30 +**Status:** v7.13.0 active | mkdocs improvements (breadcrumbs, validation, not_in_nav) + Dependabot deps current | full suite REQUIRED on main — 64 passed / 0 failed / 1 skipped | 14 dispatchers + at bridge | 219 test files | 12000+ test functions | 0 lint errors | 0 broken links ## wins: Fixed the regression bug (2026-06-19), --category fix squashed the bug (2026-06-19), fixed the bug (2026-06-19), Fixed the regression bug (2026-06-19), --category fix squashed the bug (2026-06-19) ## streak: 1 ## last_active: 2026-06-19 10:30 From 9ff13649226b57b07e9c634a3e489a1b4c71f7e5 Mon Sep 17 00:00:00 2001 From: Test User Date: Wed, 1 Jul 2026 19:46:05 -0600 Subject: [PATCH 03/24] docs(specs): add planning-coordination brainstorm + spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ecosystem proposal + flow-cli SPEC for daily-planning coordination across flow-cli, atlas, and obsidian-cli-ops. atlas owns the planning brain (Task entity + agenda aggregator); flow-cli refactors planning-command duplication (incl. $path->project_path bug fix) and consumes atlas agenda via a new schedule-engine source. First-class testing (§5) and documentation (§6) plans. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BvJR434JKwmpq8LD458Aum --- ...NSTORM-planning-coordination-2026-07-01.md | 153 ++++++++++++++++++ .../SPEC-planning-coordination-2026-07-01.md | 143 ++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 docs/specs/BRAINSTORM-planning-coordination-2026-07-01.md create mode 100644 docs/specs/SPEC-planning-coordination-2026-07-01.md diff --git a/docs/specs/BRAINSTORM-planning-coordination-2026-07-01.md b/docs/specs/BRAINSTORM-planning-coordination-2026-07-01.md new file mode 100644 index 000000000..718942c7e --- /dev/null +++ b/docs/specs/BRAINSTORM-planning-coordination-2026-07-01.md @@ -0,0 +1,153 @@ +# BRAINSTORM — flow-cli ⇄ atlas ⇄ obsidian-cli-ops: Daily-Planning Coordination + +**Date:** 2026-07-01 · **Depth:** deep · **Focus:** architecture / ops +**Decisions locked (via brainstorm grill):** planning brain = **atlas**; sequencing = **both tracks in parallel**; canonical task/deadline store = **atlas `Task` entity**; deliverable = **proposal + SPEC + `--orch` handoff**. + +--- + +## Context — why this, why now + +Three tools already touch daily planning, but each is blind to part of the picture: + +- **flow-cli v7.13.0** has a mature schedule engine (`lib/schedule.zsh`, shipped v7.10.0) that renders a forward-looking `agenda`. It reads *only* `.STATUS ## Schedule:` blocks + `teach-config.yml`. It is **blind to atlas's `Task.dueDate`** and has **no `obs` bridge**. +- **atlas v0.12.2** owns the richest task model — `Task.js` already has `dueDate` / `isOverdue` / `isDueSoon`, plus `atlas plan` (morning ritual) and iCal/JSON export — but exposes **no `atlas task` CLI and no agenda aggregator**. +- **obsidian-cli-ops v4.3.0** already consumes `atlas project list --format json` to render vault boards (`obs board`, `obs research board`; PRs #80–82 automated this) — but has **no task-by-due-date query** and the vault's **task/daily layer is empty** (TaskNotes installed, `TaskNotes/Tasks/` empty; kanban/agenda/calendar `.base` views unused). + +**The gap:** there is no single daily agenda that merges *research deadlines* (atlas tasks / `.STATUS` cadence gates) with *dev schedule* (flow-cli), and no populated task store the vault can render. atlas has the data model; flow-cli has the fast surface; obs has the vault renderer. They just aren't wired end-to-end. + +**Intended outcome:** atlas becomes the planning brain (canonical tasks + a daily-agenda aggregator with JSON/iCal output); flow-cli becomes the <10ms daily driver that *displays* that agenda merged with its existing schedule sources; obs renders the same agenda into the vault and seeds the empty TaskNotes store. Meanwhile flow-cli's planning-command internals get de-duplicated so the new surface is built on a clean base. + +--- + +## Target architecture + +```mermaid +flowchart LR + subgraph sources[Sources of truth] + ST[".STATUS cadence gates
+ ## Schedule: blocks"] + TASK["atlas Task store
dueDate / priority / project"] + TEACH[".flow/teach-config.yml
teaching dates"] + end + + subgraph atlas["atlas — planning brain (Node)"] + AGG["atlas agenda (NEW)
merges tasks + deadlines + sessions
→ normalized dated records"] + TCLI["atlas task (NEW CLI)
add / list / done --format json"] + ICAL["session/agenda export
iCal + JSON (exists, extend)"] + end + + subgraph flow["flow-cli — fast daily driver (ZSH)"] + SE["schedule engine
_schedule_collect (+ atlas source)"] + AG["agenda / dash / morning
merged research + dev view"] + end + + subgraph obs["obsidian-cli-ops — vault renderer (Py)"] + BRD["obs board / research board
render agenda into vault"] + TN["seed TaskNotes/Tasks/
(empty → populated)"] + end + + VAULT[("Obsidian Research vault
dashboard + .base views")] + + ST --> AGG + TASK --> AGG + TASK --> TCLI + TEACH --> SE + AGG -->|"JSON"| SE + AGG -->|"JSON"| BRD + TCLI -->|"JSON"| BRD + BRD --> VAULT + TN --> VAULT + SE --> AG + AGG --> ICAL +``` + +**Contract principle (reuse the existing pattern):** flow-cli already models "atlas commands that don't exist yet" as *proposed contract* entries (`schedule push` is capability-probed via `_FLOW_ATLAS_HAS_SCHEDULE`, ships a silent no-op until atlas implements it). The new `atlas task list` / `atlas agenda` follow the same discipline — flow-cli codes against the proposed contract in `docs/ATLAS-CONTRACT.md`, degrades gracefully when absent, and lights up when atlas ships it. + +--- + +## Work, by track (parallel) + +### Track A — flow-cli internal refactor *(no cross-repo dependency; ship first & independently)* + +The planning commands carry duplication that makes any new feature fragile: + +| Debt | Where | Fix | +|---|---|---| +| `.STATUS` field parsing reimplemented 4+ ways | `dash.zsh:1325`, `atlas-bridge.zsh:836`, inline `grep` in `morning.zsh:84`, `adhd.zsh:104`, `capture.zsh:406` | one `_flow_status_field ` accessor in `lib/core.zsh` | +| **Latent bug**: `$path` vs `project_path` | `morning.zsh:83`, `adhd.zsh:103` read `$path` (ZSH's PATH array); fallback emits `project_path=` (`atlas-bridge.zsh:397`) | use `project_path`; focus/progress/icon lookups there are currently silently broken | +| Two divergent project-path resolvers | `_dash_find_project_path` (`dash.zsh:1284`) vs `_flow_get_project_fallback` (`atlas-bridge.zsh:370`) | single resolver covering apps + quarto/manuscripts + presentations | +| "Suggest a project" reimplemented 5× | `dash.zsh:181`, `:1139`, `morning.zsh:144`, `adhd.zsh` (`next`), `js` | one `_flow_suggest_project` | +| Today-stats recomputed twice | `_dash_header` (`dash.zsh:98`), `_dash_right_now` (`:181`) | shared helper | +| `agenda` pipeline inlined, diverges from shared surfaces | `agenda.zsh:62` vs `_schedule_window_records` (`schedule.zsh:549`) | route `agenda` through the shared engine | + +### Track B — atlas planning brain *(atlas repo; critical path for coordination)* + +1. **`atlas task` CLI** — promote the existing `Task` entity to a first-class command: `add`, `list`, `done`, `rm`, with `--format json`, `--due`, `--priority`, `--project`, `--overdue`. The data model (`dueDate`/`isOverdue`/`isDueSoon`) already exists. +2. **`atlas agenda [today|week|--all]`** — the aggregator: merge open tasks (by `dueDate`), project deadlines (parsed from `.STATUS` cadence gates in the research registry), and active sessions into a **normalized dated record set** matching flow-cli's existing `date|label|type|project|recurrence|source` shape (so flow-cli ingests it without translation). +3. **Wire into `atlas plan`** (morning ritual already exists) and **extend iCal export** (`session export` → `agenda export --format ics`). + +### Track C — flow-cli consumes the atlas agenda *(depends on B's contract; can start against proposed contract)* + +1. Add `_schedule_atlas_items` as a new source inside `_schedule_collect` (`schedule.zsh:378`) — capability-probed via the existing `_flow_atlas_json` helper; emits the same normalized records; **graceful no-op when atlas/agenda absent** (mirrors `_FLOW_ATLAS_HAS_SCHEDULE`). +2. `agenda` / `dash` UPCOMING / `morning` now show **research deadlines + dev schedule in one view**. +3. Extend `docs/ATLAS-CONTRACT.md` — add `atlas task list --format json` and `atlas agenda --format json` (proposed → pinned once atlas ships), with contract tests (`tests/test-atlas-contract.zsh`). + +### Track D — obs renders + seeds the vault *(depends on B; obs already speaks atlas JSON)* + +1. Extend `obs board` / `obs research board` to render the **daily agenda + tasks** (not just project registry state) into the marker-bounded vault board. +2. **Seed `TaskNotes/Tasks/`** from `atlas task list --format json` — populate the empty store so the installed kanban/agenda/calendar `.base` views finally have data (this is the vault's missing daily/calendar layer). +3. Reconcile with the existing `PROPOSAL-unified-research-board.md` (four overlapping boards → one). + +--- + +## Quick Wins (< 30 min each) +1. **Fix the `$path` bug** (`morning.zsh:83`, `adhd.zsh:103`) — one-word change each, restores broken focus/progress display. *(Track A)* +2. **Mark `SPEC-agenda-schedule-2026-06-13.md` implemented** — stale draft for a shipped feature. *(hygiene)* +3. **Draft the two proposed ATLAS-CONTRACT entries** (`atlas task list`, `atlas agenda`) so Tracks C/D can code against them immediately. *(Track C prep)* + +## Medium Effort (1–2 hrs each) +- [ ] `_flow_status_field` accessor + migrate the 4 call sites *(Track A)* +- [ ] `_flow_suggest_project` + `_flow_resolve_project_path` unification *(Track A)* +- [ ] `_schedule_atlas_items` source wired into `_schedule_collect` against the proposed contract *(Track C)* +- [ ] `atlas task` CLI thin wrapper over existing `Task` entity *(Track B)* + +## Long-term (multi-session) +- [ ] `atlas agenda` aggregator + iCal export *(Track B)* +- [ ] obs agenda rendering + TaskNotes seeding *(Track D)* +- [ ] Board consolidation per `PROPOSAL-unified-research-board.md` *(Track D)* + +## Recommended Next Step +→ **Ship Track A first** (pure flow-cli, no cross-repo dependency, includes the confirmed bug fix), *in parallel with* drafting the Track B `atlas task`/`atlas agenda` contract. Track A gives immediate value and a clean base; the contract unblocks C and D. This is exactly what the SPEC + `--orch` handoff sequences. + +--- + +## Test-Plan Scaffold *(default-on; tiers inferred from change shape)* + +flow-cli changes touch parsers/helpers **and** cross-command data flow with an external dependency (atlas) → tiers: `unit`, `integration`, `e2e`, `dogfood`, `dependency`. + +| Tier | Coverage | Status | +|---|---|---| +| `unit` | `_flow_status_field`, `_flow_suggest_project`, `_flow_resolve_project_path`, `_schedule_atlas_items` record normalization | RED stub — `tests/test-schedule-atlas-source.zsh`, `tests/test-status-field-accessor.zsh` | +| `integration` | agenda pipeline merges `.STATUS` + teach + atlas sources without double-counting; `dash`/`morning`/`agenda` share one engine | RED stub — `tests/integration/agenda-merged-sources.zsh` | +| `dependency` | ATLAS-CONTRACT: `atlas task list`/`atlas agenda` JSON shape pinned; graceful no-op when absent | extend `tests/test-atlas-contract.zsh` | +| `e2e` | `agenda` renders merged view in a sandbox with a stub atlas returning tasks | RED stub — `tests/e2e-agenda-atlas.zsh` | +| `dogfood` | run `agenda` / `dash` against real repo; confirm research deadlines appear | manual, documented in PR | +| `count-cascade` | `N/A` — no new command/skill/agent (extends existing `agenda`) | — | + +> Stubs land **red-first**; each carries `# TODO(author): delete if not contract-bearing` until confirmed. + +## Documentation Scaffold *(default-on; doc-scorer ≥3)* + +- [x] **Guide** — update `MASTER-DISPATCHER-GUIDE.md` + `AGENDA-SCHEDULE-GUIDE.md` (new atlas source, merged view) +- [x] **Refcard** — `docs/help/QUICK-REFERENCE.md` (agenda now spans research + dev) +- [x] **Contract** — `docs/ATLAS-CONTRACT.md` (new `atlas task`/`atlas agenda` entries) + bump version +- [x] **CHANGELOG** — `[Unreleased]` mirror (root + `docs/`) +- [ ] **Demo** — `N/A — score 2` (no new user-facing command surface) +- [ ] **Mermaid** — `[x]` included above (architecture diagram) + +*(Auto-docs touch semantic docs only — version/count lines stay in `bump-version.sh`.)* + +--- + +## Cross-repo note + +Tracks B (atlas) and D (obs) live in sibling repos and need their own specs. The companion **`SPEC-planning-coordination-2026-07-01.md`** in this repo scopes Tracks A + C (what flow-cli owns) and documents B as a contract dependency. The `--orch` handoff (`/craft:orchestrate:plan`) can detect the cross-repo shape and fan out B/D specs. diff --git a/docs/specs/SPEC-planning-coordination-2026-07-01.md b/docs/specs/SPEC-planning-coordination-2026-07-01.md new file mode 100644 index 000000000..4873d445a --- /dev/null +++ b/docs/specs/SPEC-planning-coordination-2026-07-01.md @@ -0,0 +1,143 @@ +# SPEC — flow-cli planning-command refactor + atlas-agenda coordination + +**Status:** Draft (awaiting approval) +**Date:** 2026-07-01 · **Target version:** v7.14.0 · **Focus:** architecture / ops +**Companion:** [`BRAINSTORM-planning-coordination-2026-07-01.md`](BRAINSTORM-planning-coordination-2026-07-01.md) +**Scope in this repo:** Track A (flow-cli internal refactor) + Track C (consume atlas agenda). Tracks B (atlas `task`/`agenda` CLI) and D (obs render/seed) are cross-repo dependencies, documented here as contract only. + +--- + +## 1. Context & motivation + +flow-cli's planning surface (`agenda`, `dash`, `morning`, `next`, `js`) works but (a) duplicates core logic across 5 files, (b) carries a confirmed latent bug, and (c) is blind to atlas's task/deadline data. The brainstorm locked an **atlas-owns-the-brain** architecture: atlas aggregates tasks + deadlines into a daily agenda (JSON/iCal); flow-cli displays it fast, merged with its existing `.STATUS`/teaching sources; obs renders it into the vault. This SPEC delivers the flow-cli half on a de-duplicated base. + +## 2. Goals / non-goals + +**Goals** +- G1 — Single accessor for `.STATUS` fields, project-path resolution, and project suggestion (kill 4×/2×/5× duplication). +- G2 — Fix the `$path` → `project_path` bug in `morning.zsh` + `adhd.zsh`. +- G3 — Route `agenda` through the shared `_schedule_window_records` engine (no inlined pipeline). +- G4 — New `_schedule_atlas_items` source: merge `atlas agenda`/`atlas task` records into the schedule engine, capability-probed, graceful-degrading. +- G5 — Pin `atlas task list --format json` + `atlas agenda --format json` in `docs/ATLAS-CONTRACT.md` as proposed contract. + +**Non-goals** +- Implementing the atlas CLI (Track B) or obs rendering (Track D) — separate repos/specs. +- Changing the `.STATUS ## Schedule:` grammar or teach-config parsing. +- New top-level command (extends existing `agenda`; no count-cascade). + +## 3. Design + +### 3.1 Shared accessors (`lib/core.zsh`) +- `_flow_status_field ` — replaces `_dash_get_status_field` (`dash.zsh:1325`), the inline `grep`s in `morning.zsh:84` / `adhd.zsh:104` / `capture.zsh:406`, and `_flow_where_fallback` field reads (`atlas-bridge.zsh:836`). Handles both `## Field:` and `field:` dialects (superset of current behavior). +- `_flow_resolve_project_path ` — single resolver merging `_dash_find_project_path` (`dash.zsh:1284`, has apps + quarto/manuscripts + presentations) and `_flow_get_project_fallback` (`atlas-bridge.zsh:370`). **Always emits `project_path=` never `path=`.** +- `_flow_suggest_project` — single active/priority scan replacing the 5 reimplementations (`dash.zsh:181`, `:1139`, `morning.zsh:144`, `adhd.zsh` `next`, `js`). + +### 3.2 Bug fix (G2) +`morning.zsh:83` and `adhd.zsh:103` do `eval "$info"` then read `$path`. The fallback emits `project_path=` (`atlas-bridge.zsh:397` — deliberately, per its comment "Avoid 'path' — conflicts with ZSH's PATH-tied variable"). Result: `$path` = the shell's PATH array, so `.STATUS` focus/progress + `_flow_detect_project_type "$path"` operate on garbage. Fix: read `$project_path` (routed through `_flow_resolve_project_path`). + +### 3.3 agenda unification (G3) +`agenda.zsh:62` runs an inline collect→filter→classify variant. Re-point it at `_schedule_window_records` (`schedule.zsh:549`) so bucketing + holiday-drop logic lives once. `_schedule_classify` stays for OVERDUE/TODAY/THIS-WEEK/LATER labelling. + +### 3.4 atlas agenda source (G4) +New `_schedule_atlas_items ` invoked inside `_schedule_collect` (`schedule.zsh:378`, alongside `_schedule_parse_status` + `_schedule_teach_items`): +- Probe capability once (session-cached, e.g. `_FLOW_ATLAS_HAS_AGENDA`), mirroring `_FLOW_ATLAS_HAS_SCHEDULE`. +- Call `_flow_atlas_json agenda "$window"`; map each record to the engine's `date|label|type|project|recurrence|source` shape (`source=atlas`). +- **Absent/older atlas → silent no-op**, engine falls back to `.STATUS` + teach only. No behavior change without atlas. + +### 3.5 Contract (G5) +Add to `docs/ATLAS-CONTRACT.md` (bump contract version): `atlas task list --format json`, `atlas agenda [today|week|--all] --format json`, marked **proposed** until atlas ships them (same status `schedule push` carries today). + +## 4. Files touched + +- `lib/core.zsh` — new shared accessors. +- `lib/schedule.zsh` — `_schedule_atlas_items` + call site in `_schedule_collect`. +- `commands/agenda.zsh` — route through shared engine. +- `commands/morning.zsh`, `commands/adhd.zsh` — bug fix + use shared accessors. +- `commands/dash.zsh`, `commands/capture.zsh` — migrate to shared accessors. +- `lib/atlas-bridge.zsh` — capability probe + `agenda` JSON passthrough for `at`. +- `docs/ATLAS-CONTRACT.md` — proposed contract entries. + +--- + +## 5. Testing plan *(refined — first-class, red-first, in-tree)* + +Run everything in the worktree via `./tests/run-all.sh`; new suites registered there. Judge failures against the `dev` baseline. + +### 5.1 Unit +| Suite (new/extended) | Asserts | +|---|---| +| `tests/test-status-field-accessor.zsh` (new) | `_flow_status_field` reads both `## Focus:` and `focus:` dialects; missing field → empty; strips `%` on progress; no leak to `$path`/`$PATH` | +| `tests/test-project-path-resolver.zsh` (new) | `_flow_resolve_project_path` finds apps, quarto/manuscripts, presentations; emits `project_path=` never `path=`; unknown project → empty | +| `tests/test-suggest-project.zsh` (new) | `_flow_suggest_project` prefers active + high-priority; deterministic tie-break; empty registry → graceful | +| `tests/test-schedule-atlas-source.zsh` (new) | `_schedule_atlas_items` maps stub JSON → normalized records; malformed JSON → no-op not crash; capability-absent → 0 records | + +### 5.2 Integration (cross-command data flow) +| Suite | Asserts | +|---|---| +| `tests/integration/agenda-merged-sources.zsh` (new) | `.STATUS` + teach + atlas records merge with **no double-count**; dedupe on `date|label|project`; `dash` UPCOMING, `morning`, `agenda` all render the same merged set (shared engine) | +| `tests/integration/atlas-flow-integration.zsh` (extend) | agenda path with atlas present vs absent yields identical non-atlas rows | + +### 5.3 Dependency / contract +| Suite | Asserts | +|---|---| +| `tests/test-atlas-contract.zsh` (extend) | `atlas task list --format json` + `atlas agenda --format json` shapes pinned; flow-cli degrades to no-op when atlas lacks them (proven against a stub that omits the commands) | + +### 5.4 E2E + dogfood +| Suite | Asserts | +|---|---| +| `tests/e2e-agenda-atlas.zsh` (new) | sandbox + stub `atlas agenda` returning 2 tasks + `_flow_has_atlas` override → merged view shows research deadlines alongside `.STATUS` schedule (capture real output per memory `capture-real-agenda-output-for-docs`) | +| dogfood (manual, quoted in PR) | run `agenda` / `dash` / `morning` against the real repo set; confirm the `$path` fix restores focus/progress; confirm no regression when atlas agenda unavailable | + +### 5.5 Regression guards +- Terminal-hygiene guard unaffected (no new fzf picker) — confirm `tests/test-terminal-hygiene-regression.zsh` still green. +- Man-page version-sync guard — bump `.TH` on release only. + +**Baseline discipline:** any failure must be reproduced on `dev` before dismissal; PR body reports exact command + pass/fail/skip counts + baseline mapping. + +--- + +## 6. Documentation plan *(refined — first-class, currency-checked)* + +Inventory current state before editing (per doc-currency rule); update only genuine deltas. + +| Doc | Change | +|---|---| +| `docs/reference/MASTER-DISPATCHER-GUIDE.md` | agenda now merges an atlas source; note graceful degradation | +| `docs/guides/AGENDA-SCHEDULE-GUIDE.md` | new data source diagram; merged research+dev example with real captured output | +| `docs/reference/MASTER-API-REFERENCE.md` | document `_flow_status_field`, `_flow_resolve_project_path`, `_flow_suggest_project`, `_schedule_atlas_items` | +| `docs/help/QUICK-REFERENCE.md` | one-line: agenda spans research + dev | +| `docs/ATLAS-CONTRACT.md` | proposed `atlas task`/`atlas agenda` entries + contract version bump | +| `CHANGELOG.md` + `docs/CHANGELOG.md` | `[Unreleased]` — mirrored (both files) | +| `CLAUDE.md` | refresh test-file/suite counts after new suites land | +| `mkdocs.yml` | no nav change (extends existing pages); `mkdocs build --strict` must pass | +| stale-spec hygiene | mark `SPEC-agenda-schedule-2026-06-13.md` **Implemented** | + +Doc-scorer (`commands/docs/sync.md`, threshold ≥3): guide ✅, refcard ✅, contract ✅; demo `N/A — score 2` (no new command surface); mermaid ✅ (in brainstorm). + +--- + +## 7. Cross-repo dependencies (contract only — not built here) + +- **atlas (Track B):** `atlas task {add,list,done} --format json`; `atlas agenda [today|week|--all] --format json` emitting `date|label|type|project|recurrence|source`. → own spec in `atlas/docs/specs/`. +- **obsidian-cli-ops (Track D):** extend `obs board`/`research board` to render agenda + seed `TaskNotes/Tasks/` from `atlas task list --format json`. → own spec in `obsidian-cli-ops/docs/`. + +## 8. Verification (end-to-end) + +1. `source flow.plugin.zsh` — clean load, no errors. +2. `./tests/run-all.sh` — all suites green (1 expected interactive/tmux timeout); new suites pass; counts updated in CLAUDE.md. +3. Dogfood with **no atlas**: `agenda`, `dash`, `morning` identical to pre-change (graceful degradation proven). +4. Dogfood with **stub atlas agenda**: research deadlines appear merged; `$path` fix restores focus/progress in `morning`/`next`. +5. `mkdocs build --strict` — 0 warnings. +6. `/craft:docs:lint` — clean. + +## 9. Rollout + +- Branch: `feature/planning-coordination` (worktree off `dev`). +- Ship Track A independently if Track C's atlas dependency isn't ready — the refactor + bug fix stand alone. +- Release as v7.14.0 once C's contract is pinned (C degrades safely even before atlas ships). + +--- + +## Next step + +`--orch` handoff: `/craft:orchestrate:plan docs/specs/SPEC-planning-coordination-2026-07-01.md` → generates `ORCHESTRATE-planning-coordination.md` (+ optional worktree), detects the cross-repo shape, and fans out the atlas/obs companion specs. From 0023c57e7e830290c6540b64ea084b2acac12211 Mon Sep 17 00:00:00 2001 From: Test User Date: Wed, 1 Jul 2026 19:48:54 -0600 Subject: [PATCH 04/24] chore(status): record planning-coordination worktree Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BvJR434JKwmpq8LD458Aum --- .STATUS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.STATUS b/.STATUS index 57ed683ef..64d317261 100644 --- a/.STATUS +++ b/.STATUS @@ -442,7 +442,7 @@ | Worktree | Branch | Status | |----------|--------|--------| -| — | — | All worktrees clean (as of 2026-06-30) | +| `~/.git-worktrees/flow-cli/feature-planning-coordination` | `feature/planning-coordination` | Planning done (2026-07-01) — SPEC + ORCHESTRATE committed; awaiting impl in a new session (Tracks A+C; atlas/obs contract-only) | --- From 8012aaeac73a244fa719561ebfc2afa504a70783 Mon Sep 17 00:00:00 2001 From: Davood Tofighi Date: Wed, 1 Jul 2026 23:43:18 -0600 Subject: [PATCH 05/24] Planning-coordination: shared accessors refactor + dark-ready atlas agenda source + .STATUS enforcer (#483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(orchestrate): planning-coordination phase plan 3-phase plan (Track A refactor + $path fix / Track C atlas agenda source + contract / docs), TDD red-first, graceful-degradation gate. Tracks B (atlas) and D (obs) are contract-only, out of scope for this worktree. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BvJR434JKwmpq8LD458Aum * docs(spec+orchestrate): apply grill decisions D1-D16 Adversarial grill of the planning-coordination SPEC + ORCHESTRATE surfaced 16 load-bearing decisions: - $path bug fix isolated as its own pre-step (Phase 0), red-test-first - Consolidation of 4x .STATUS readers / 2x path resolvers / 5x project suggesters guarded by characterization/parity tests landed BEFORE the refactor (silent-behavior-loss guard) - Track C (atlas agenda source) ships dark-ready/capability-probed, mirrors the existing 'schedule push' no-op pattern; zero payoff until atlas Track B ships, zero rework when it does - .STATUS-ingestion mechanism left as atlas's own open design question, not prescribed here (respects the Track B contract-only boundary) - New flow-cli-scoped .STATUS template + warn-only enforcer (check-status.zsh) - Execution re-modeled as phase-gated (stop+report each phase, reviewer runs the FULL suite independently) instead of one-shot, with a 2-attempt fix-forward cap and explicit escalation on repeated failure - Atlas-absent/present test simulation via capability-flag override + a PATH stub shim, with a fixture whose shape is asserted against the documented contract to prevent silent stub/contract drift Full ledger + board summary + dispatch-suitability debate: see the grilled plan (paths differ by session). Hard-scoped to flow-cli only this cycle per the grill's reversibility/scope-creep finding — atlas Track B and obs Track D remain contract-only, deferred to their own specs+grills. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BvJR434JKwmpq8LD458Aum * fix(planning): read $project_path not $path in morning/next _flow_get_project_fallback deliberately emits `project_path=` (never `path=`) to avoid colliding with ZSH's PATH-tied `$path` array. But morning.zsh and adhd.zsh read `$path` after `eval "$info"`, so the .STATUS lookup silently fails and focus/progress render blank while project-type detection falls back to generic even for typed projects. Red test (tests/test-path-bug-fix.zsh) proves the current broken output, then the two-file fix restores it. Isolated pre-step ahead of the Phase 1 shared-accessor refactor per SPEC §3.0 / ORCHESTRATE Phase 0. * docs(orchestrate): check off Phase 0 Phase 0 (the isolated $path -> $project_path fix) is implemented, tested, and approved at the review gate. Check off 0.1-0.5 to keep the tracking checklist current before Phase 1. * test(planning): characterize .STATUS field readers before refactor Snapshots the current byte-exact output of _dash_get_status_field (+ its Status/Focus/Progress wrappers), the inline greps in morning/next/ capture, and _flow_where_fallback across dialect, colon-in-value, %- in-non-progress-field, missing-field, and status-synonym cases. Green on today's code (17/17) — the parity guard required before any _flow_status_field accessor exists, per SPEC §3.1 / ORCHESTRATE 1.0. Also documents a pre-existing bug found while characterizing: _flow_- where_fallback's `local status=...` crashes (status is a zsh read-only special variable, even function-local), so it never actually prints Status:/Focus: today. Captured explicitly as the one assertion that is expected to flip after the Phase 1 migration fixes it as an unavoidable side effect of touching that line. * refactor(planning): shared .STATUS/project accessors Adds three shared accessors to lib/core.zsh (SPEC-planning-coordination -2026-07-01 §3.1), each pinned by new unit tests: - _flow_status_field : replaces the 4 divergent .STATUS field readers (_dash_get_status_field, and the inline greps in morning/next/capture). Handles both "## Field:" and "field:" dialects; does NOT strip '%' (kept at call sites — a universal strip would corrupt any other field containing '%', e.g. "## Focus: hit 80% coverage"; also dash's own _dash_get_project_progress '%'-strip is a pre-existing zsh pattern-matching no-op, confirmed via characterization and left untouched, out of migration scope). - _flow_resolve_project_path : merges _dash_find_project_path's taxonomy (dev-tools/apps/r-packages/research/teaching/quarto- manuscripts/quarto-presentations) with _flow_get_project_fallback's exact-root-match + generic quarto/ fallback. Always emits `project_path=`, never `path=`. - _flow_suggest_project : replaces the 5 project-suggestion reimplementations (dash "right now"/footer, morning priority scan, adhd next/js) with one active/priority/random scan. NOT byte-parity guarded like the field reader (the 5 sites genuinely differ in selection logic) — each strategy is pinned by its own test instead. _dash_get_status_field, _dash_find_project_path, and _flow_get_project_fallback now delegate to the shared accessors as thin wrappers, so their ~25 combined internal call sites needed no changes. Also routes `agenda` (agenda.zsh) through the shared _schedule_window_records pipeline (schedule.zsh), which gained two additive, backward-compatible parameters (category, keep_holidays) so agenda's --all/category-filter behavior is preserved while the collect->filter->sort->holiday-drop logic now lives in one place. _schedule_classify stays for OVERDUE/TODAY/WEEK/LATER bucketing. Bug fixes discovered via characterization (tests/test-status-field- parity.zsh, task 1.0) and fixed as an unavoidable side effect of the mandated atlas-bridge.zsh:836 migration: _flow_where_fallback declared `local status=...`, but `status` is a zsh read-only special variable even as a function-local, so it crashed before ever printing Status:/ Focus: — the characterization test that captured this crash was updated (not silently adjusted) to assert the now-fixed behavior, with the reasoning documented inline. Also fixed in this pass: a genuine zsh gotcha found while developing _flow_suggest_project — redeclaring `local var` on an already-local variable on every iteration of a `while read ... done <<< ...` loop causes zsh to echo the previous iteration's value to stdout. Fixed by declaring loop-scoped locals once, outside the loop. Scope calls made along the way (flagged per the phase-gate review process): - capture.zsh's daily_goal read drops its `grep -i` case-insensitivity; every real .STATUS uses lowercase "## daily_goal:" (templates/docs), so this is a narrowing with no real-world effect. - _flow_resolve_project_path checks the project-root exact match LAST (matching _dash_find_project_path's original, better-tested order) rather than FIRST (_flow_get_project_fallback's original order) — the two disagreed; dash's order wins as the fuller, more-tested taxonomy for this now-shared function. - _flow_suggest_project's wiring into all 5 original call sites was done (not left as unwired dead code), backstopped by existing dash/adhd/morning test coverage plus the new strategy-level tests and a manual dogfood pass (dash/morning/next/js/agenda all producing correct, unchanged-looking output against a live fixture). tests/test-status-field-parity.zsh (task 1.0, characterization) stays green post-refactor except the one flagged, deliberate exception above. Full ./tests/run-all.sh: 71 passed, 0 failed, 0 timeout, 1 skipped (pre-existing, tool-absence skip — up from 67/0/0/1 pre-Phase-1 by the 4 new suites registered here). * docs(contract): pin proposed atlas agenda read contract (dark-ready) Adds the atlas agenda --format=json section (Track C, SPEC-planning-coordination-2026-07-01 §3.4) alongside the existing atlas schedule push section, bumps contract to v1.2.0. Pins only the read INTERFACE flow-cli calls (_flow_atlas_json agenda "$window"); how atlas populates Task.dueDate — including whether it ever reads .STATUS itself — is explicitly left as atlas's own open design question (D4), not prescribed here. No atlas release implements `agenda` yet; this is the proposed contract ahead of the implementing code landing in the same commit range. * feat(agenda): merge atlas agenda source into schedule engine (dark-ready) Adds a third, capability-probed source to _schedule_collect alongside .STATUS Schedule blocks and teach-config: atlas-tracked deadlines (Task.dueDate). Ships as tested, INERT code — no atlas release implements `agenda` yet (Track B, tracked separately) — same posture `schedule push` shipped with before atlas B existed. - _schedule_atlas_items (lib/schedule.zsh): probes `atlas agenda --help` once per session (cached in _FLOW_ATLAS_HAS_AGENDA, mirroring _FLOW_ATLAS_HAS_SCHEDULE), calls _flow_atlas_json agenda "$window", maps the JSON array to the engine's date|label|type|project|recurrence|source shape with source hardcoded to `atlas`. Requires jq to parse the array response — jq absence is just another silent-no-op path, same as atlas absence; the engine adds no hard dependency. - Wired into _schedule_collect as a third source after the per-project loop (one call per collect, not per-project — atlas returns cross-project results in one shot). Category filtering resolves each atlas record's project to a local path the same way the other two sources do. - Added a dedupe pass on (date, label, project) — the first duplicate- possible scenario in this engine, since .STATUS + teach-config could never structurally collide before. First occurrence wins, so a local .STATUS record always takes priority over an atlas duplicate of it. - `at` dispatcher (2.5): verified, not implemented — the existing generic `atlas "$@"` passthrough already forwards `at agenda ... --format=json` correctly when atlas is present; confirmed via a stub-atlas dogfood check, no code change needed. Tests (all new, registered in tests/run-all.sh): - tests/test-schedule-atlas-source.zsh (6 unit tests): function exists; absent-via-capability-flag is empty (D15 — not FLOW_ATLAS_ENABLED=no alone); present-but-incapable (older atlas) is empty; capable atlas maps the fixture correctly; capability probe is cached; jq-absent is a graceful no-op. - tests/e2e-agenda-atlas.zsh (5 e2e tests, stub atlas shim on PATH per D16 — real atlas can't be used, it lacks the command): atlas-absent output unaffected; atlas-present merges fixture items; atlas items pass the category filter via _schedule_collect directly (agenda's CLI hardcodes window=7 for category filters, which would make a date-window assertion here flaky against the fixture's fixed dates as real time passes — verified `_schedule_filter_window` always retains "overdue" regardless of window, then used a wide window directly against _schedule_collect to make the category-match assertion itself window-independent); dedupe — an atlas record identical to a local one on (date,label,project) does not double-count. - tests/test-atlas-contract.zsh: 2 new assertions pinning atlas-agenda-stub.json's shape to the contract doc's documented example — verified with a planted-defect check (removed a field from the fixture, confirmed the test fails; restored, confirmed it passes again) that this guard actually catches contract/fixture drift, not just a tautology. - tests/fixtures/atlas-agenda-stub.json (new): the canned `atlas agenda --format=json` response used by the above. Dogfood (manual, against a live multi-project fixture): - No atlas: dash "right now"/footer suggestion, morning priority suggestion, and agenda output are byte-identical to the Phase 1 dogfood pass — confirmed degradation is truly a no-op. - Atlas stub present: both fixture deadlines merge into `agenda -m` alongside the local project's own item, no duplication, correct THIS WEEK / LATER bucketing. Full ./tests/run-all.sh: 73 passed, 0 failed, 0 timeout, 1 skipped (pre-existing tool-absence skip; up from 71 by the 3 new suites registered here — test-schedule-atlas-source, e2e-agenda-atlas, plus test-atlas-contract's 2 new assertions inside the existing suite). Scope note: ORCHESTRATE's Key Files list referenced tests/integration/agenda-merged-sources.zsh as a file to "extend" for 3-source dedup coverage; it did not exist in this repo. Dedup coverage was added as e2e-agenda-atlas.zsh Section 3 instead (flagged for review, same as the Phase 0/1 scope calls). * feat(status): add .STATUS template + warn-only schema enforcer flow-cli-scoped only (SPEC-planning-coordination-2026-07-01 §3.6, D9) — not an ecosystem-wide standard. Reuses the scripts/check-math.zsh pattern (standalone, lint-staged-wired, callable directly). - templates/.STATUS.template: canonical structure with inline comments — header fields (Project/Type/Status/Focus/Phase/Priority/Progress), the ## Schedule: grammar, ## daily_goal:, ## Active Worktrees, session-log convention. Documents the dual dialect ("## Field:" / "field:") _flow_status_field supports. Priority/Progress/Status use concrete starter values (2/0/active), not bracketed placeholders, so the template itself validates clean — a placeholder like "" would otherwise fail the Progress format check. - scripts/check-status.zsh: validates required fields present, Progress is an integer 0-100, Status is in the allowed set (active|paused|archived|blocked, or a recognized _flow_status_field synonym), and ## Schedule: lines match the grammar _schedule_parse_status (lib/schedule.zsh) actually expects — a line the real parser can't match is silently dropped there (invisible, not fatal), which is exactly the mistake worth surfacing here. D11 HARD CONSTRAINT: exits 0 always, no matter how malformed the input — prints violations, never blocks. Verified directly against the maximally-broken-input case, not just asserted. - Wired into lint-staged as a literal ".STATUS" key (not a glob), alongside the existing "*.qmd" -> check-math.zsh entry. Two zsh footguns found and fixed while writing check-status.zsh (both first discovered in Phase 1, recurring here in new code — same lib/core.zsh `_flow_status_field` root+filename convention doesn't apply to templates/.STATUS.template, whose filename isn't literally ".STATUS": staged a throwaway copy under that exact name per validation call so the accessor's contract is met without duplicating its parsing logic): - `local status=...` crashes (status is a zsh read-only special variable, even function-local) -> renamed to `proj_status`. - redeclaring `local var` inside a loop echoes the previous iteration's value to stdout -> all loop-scoped locals declared once, outside the loops. tests/test-status-schema.zsh (14 tests, registered in tests/run-all.sh): checker/template exist; a fully valid fixture produces zero warnings and exit 0; the template and flow-cli's own .STATUS both pass clean; missing-field / bad-Progress (out-of-range and non-numeric) / bad-Status / malformed-Schedule-line all warn but still exit 0; a Status synonym is NOT flagged; a maximally-broken fixture (not even STATUS-shaped) still exits 0; no-args exits 0; lint-staged wiring is present. Full ./tests/run-all.sh: 74 passed, 0 failed, 0 timeout, 1 skipped (pre-existing tool-absence skip; up from 73 by this one new suite). * docs(planning): agenda/dispatcher/API guides + CHANGELOG Phase 4 (Documentation & Discoverability) — SPEC-planning-coordination -2026-07-01 §6, ORCHESTRATE final phase. - docs/reference/MASTER-API-REFERENCE.md: documents the 4 new/changed functions (_flow_status_field, _flow_resolve_project_path, _flow_suggest_project in a new "Project Utilities" subsection; _schedule_atlas_items in a new "Schedule Engine" top-level section — lib/schedule.zsh had no section here before). Coverage count bumped 178 -> 182. - docs/guides/AGENDA-SCHEDULE-GUIDE.md: adds a mermaid data-source diagram (.STATUS + teach-config + atlas -> collect -> dedupe -> surfaces), a new "3. Atlas-tracked deadlines" source entry, and rewrites "Atlas integration" into push (shipped) + read (dark-ready) halves. The read half includes a REAL captured `agenda -m` run (memory capture-real-agenda-output-for-docs — built a live 3-source fixture with a stub atlas, ran it, transcribed the actual output; not fabricated) showing a .STATUS item, a teach-config item, and an atlas-sourced item merged, correctly typed and bucketed. - docs/reference/MASTER-DISPATCHER-GUIDE.md: notes the engine now merges three sources and that the atlas one degrades to a silent no-op without atlas. - docs/help/QUICK-REFERENCE.md: tip clarifying `agenda` spans every project category (dev/r/quarto/apps), not just research/teach. - docs/ATLAS-CONTRACT.md: verified already current from Phase 2 (v1.2.0, `atlas agenda` documented) — no further change needed. - CONTRIBUTING.md: new ".STATUS Files" subsection pointing at templates/.STATUS.template and scripts/check-status.zsh (warn-only). Inserted narrowly — this file is otherwise significantly stale (describes a structure/toolchain that doesn't match flow-cli's actual pure-ZSH layout); that pre-existing staleness is out of scope here and is flagged in the phase report, not fixed. - CHANGELOG.md + docs/CHANGELOG.md: mirrored [Unreleased] entries for all of Phases 0-3 (shared accessors, atlas agenda source + dedupe, .STATUS template/enforcer, the $path and _flow_where_fallback bug fixes). The two files were already drifted before this commit (an extra "tok mint" entry only in docs/CHANGELOG.md, a missing blank line before the 7.13.0 heading) — not reconciled here, flagged in the phase report as a separate pre-existing issue. - CLAUDE.md: test-file count 219 -> 245 (already stale beyond just this phase's +8 new files — pre-existing drift); suite-run wording corrected from "67/67 passing, 1 expected interactive/tmux timeout" to "74/74 passing, 1 skipped — required external tool absent" to match what run-all.sh actually reports today (a skip, not a timeout); added scripts/check-status.zsh and templates/ to the structure tree. - docs/specs/SPEC-agenda-schedule-2026-06-13.md: Status draft -> Implemented, with a pointer to this SPEC's §3.4 for the atlas-source addition. Verification: - `mkdocs build --strict`: 0 warnings (clean). - markdownlint (craft's config — flow-cli has no markdownlint config or CI step of its own; runs prettier instead): 0 findings in every region this commit touched. 20 pre-existing findings remain in CHANGELOG.md (historical entries, lines 128-1065) and the SPEC file (lines 66-114, pre-existing body) — untouched by this commit, flagged rather than fixed (out of scope). - Full ./tests/run-all.sh: 74 passed, 0 failed, 0 timeout, 1 skipped (unchanged from Phase 3 — this phase touches docs only). * docs(orchestrate): check off final acceptance criteria (reviewer-verified) Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BvJR434JKwmpq8LD458Aum * fix(test): hide jq via controlled PATH, not /bin assumption (Linux CI) test_no_jq_is_graceful_noop used PATH="$STUB_BIN:/bin", assuming /bin is jq-free. True on macOS (a real, separate /bin). False on Ubuntu's ubuntu-latest CI runner: Ubuntu has been usr-merged since 19.10, so /bin is a symlink to /usr/bin, which ships jq preinstalled. /bin/jq resolved on CI, the no-op branch never triggered, and the test failed with a non-empty assert_empty. Same bug class as the documented macos-only-shell-isms-break-linux-ci gotcha, new instance: a PATH- based "hide this tool" assumption that only holds on macOS's non-merged /bin. Fix: PATH="$STUB_BIN" alone, no /bin. $STUB_BIN is a fresh mktemp dir under the test's exclusive control containing only the atlas stub, so it's guaranteed jq-free on every platform — no reliance on any real system directory's contents. Verified nothing else was needed from /bin: _schedule_atlas_items only consults `atlas` (found — the stub) and `jq` (not found, by construction) on PATH before its early return; confirmed `command -v jq` fails under PATH="$STUB_BIN" alone. Local verification: tests/test-schedule-atlas-source.zsh 6/6 passing; full ./tests/run-all.sh 74 passed, 0 failed, 0 timeout, 1 skipped (unchanged pre-existing tool-absence skip). --------- Co-authored-by: Test User Co-authored-by: Claude Sonnet 5 --- CHANGELOG.md | 6 + CLAUDE.md | 11 +- CONTRIBUTING.md | 13 + ORCHESTRATE-planning-coordination.md | 283 ++++++++++++++++ commands/adhd.zsh | 10 +- commands/agenda.zsh | 11 +- commands/capture.zsh | 8 +- commands/dash.zsh | 148 +++------ commands/morning.zsh | 23 +- docs/ATLAS-CONTRACT.md | 62 +++- docs/CHANGELOG.md | 7 + docs/guides/AGENDA-SCHEDULE-GUIDE.md | 82 ++++- docs/help/QUICK-REFERENCE.md | 6 + docs/reference/MASTER-API-REFERENCE.md | 200 +++++++++++- docs/reference/MASTER-DISPATCHER-GUIDE.md | 8 + docs/specs/SPEC-agenda-schedule-2026-06-13.md | 3 +- .../SPEC-planning-coordination-2026-07-01.md | 91 ++++-- lib/atlas-bridge.zsh | 61 ++-- lib/core.zsh | 232 +++++++++++++ lib/schedule.zsh | 119 ++++++- package.json | 3 + scripts/check-status.zsh | 164 ++++++++++ templates/.STATUS.template | 70 ++++ tests/e2e-agenda-atlas.zsh | 211 ++++++++++++ tests/fixtures/atlas-agenda-stub.json | 16 + tests/run-all.sh | 8 + tests/test-atlas-contract.zsh | 43 +++ tests/test-path-bug-fix.zsh | 139 ++++++++ tests/test-project-path-resolver.zsh | 144 +++++++++ tests/test-schedule-atlas-source.zsh | 183 +++++++++++ tests/test-status-field-accessor.zsh | 107 ++++++ tests/test-status-field-parity.zsh | 305 ++++++++++++++++++ tests/test-status-schema.zsh | 259 +++++++++++++++ tests/test-suggest-project.zsh | 147 +++++++++ 34 files changed, 2961 insertions(+), 222 deletions(-) create mode 100644 ORCHESTRATE-planning-coordination.md create mode 100755 scripts/check-status.zsh create mode 100644 templates/.STATUS.template create mode 100755 tests/e2e-agenda-atlas.zsh create mode 100644 tests/fixtures/atlas-agenda-stub.json create mode 100755 tests/test-path-bug-fix.zsh create mode 100755 tests/test-project-path-resolver.zsh create mode 100755 tests/test-schedule-atlas-source.zsh create mode 100755 tests/test-status-field-accessor.zsh create mode 100755 tests/test-status-field-parity.zsh create mode 100755 tests/test-status-schema.zsh create mode 100755 tests/test-suggest-project.zsh diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a036a784..d788ca234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Instant prefetch** (`mkdocs.yml`): enabled `navigation.instant.prefetch` — prefetches pages on link hover for faster perceived navigation - **MkDocs validation block** (`mkdocs.yml`): `validation.nav.omitted_files: warn` and `validation.links.anchors: warn` now surface silent issues on `mkdocs build` - **`not_in_nav` block** (`mkdocs.yml`): marks `ATLAS-CONTRACT.md` and six adhd template files as intentionally out of nav (accessible via links, suppresses omitted_files warnings) +- **Shared `.STATUS`/project accessors** (`lib/core.zsh`): `_flow_status_field`, `_flow_resolve_project_path`, `_flow_suggest_project` — replace 4 divergent `.STATUS` field readers, 2 project-path resolvers, and 5 project-suggestion scans with one implementation each, guarded by a byte-parity characterization suite. `dash`, `morning`, `next`, `capture`, and `agenda` now share the same accessors instead of reimplementing them. +- **Atlas agenda source, dark-ready** (`lib/schedule.zsh`): `_schedule_atlas_items` — a third, capability-probed schedule source (alongside `.STATUS` `## Schedule:` blocks and teach-config) that will surface atlas-tracked deadlines (`Task.dueDate`) once atlas ships an `agenda` command. Ships as tested, inert code — no atlas release implements it yet, so this is a silent no-op today. See `docs/ATLAS-CONTRACT.md` (`atlas agenda`, proposed). +- **`agenda` routed through the shared schedule pipeline** (`lib/schedule.zsh`, `commands/agenda.zsh`): `_schedule_window_records` gained `category`/`keep_holidays` parameters so `agenda`'s collect→filter→sort→holiday-drop chain is now the same shared pipeline `dash`/`morning`/`today`/`week` already use. Added a `(date, label, project)` dedupe pass — the first duplicate-possible scenario now that a third source exists. +- **`.STATUS` template + warn-only schema checker**: `templates/.STATUS.template` documents the canonical `.STATUS` shape (header fields, `## Schedule:` grammar, `## daily_goal:`, `## Active Worktrees`, session-log convention); `scripts/check-status.zsh` validates it as a warn-only `lint-staged` pre-commit check (prints violations, never blocks — flow-cli-scoped, not an ecosystem standard). ### Fixed @@ -22,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Broken link** (`docs/guides/EMAIL-DISPATCHER-GUIDE.md`): removed References entry linking to `internal/EM-V2-ARCHITECTURE.md` which is excluded from the site - **CONTRIBUTING.md deploy instruction**: replaced incorrect `mkdocs gh-deploy --force` step with note that docs auto-deploy via CI (manual deploy is blocked by branch guard) - **DOCUMENTATION-STYLE-GUIDE.md**: updated `--strict` link-check reference to `mkdocs build` (validation block replaces `--strict`); added MkDocs Configuration section documenting breadcrumbs, `not_in_nav`, `exclude` plugin, and the fnmatch footgun +- **`$path` → `$project_path` bug** (`commands/morning.zsh`, `commands/adhd.zsh`): `next`/`morning` read ZSH's PATH-tied `$path` array instead of the `project_path` field the project resolver actually emits — `.STATUS` focus/progress and the project-type icon were silently blank or wrong for every project with nonzero progress. Landed as an isolated pre-step ahead of the shared-accessor refactor above. +- **`_flow_where_fallback` crash** (`lib/atlas-bridge.zsh`): `local status=...` crashed (`status` is a zsh read-only special variable, even as a function-local) — `at where` never actually printed `Status:`/`Focus:`. Fixed as an unavoidable side effect of the shared-accessor migration above. ## [7.13.0] — 2026-06-19 — flow claude: C7-C11 checks + watch daemon diff --git a/CLAUDE.md b/CLAUDE.md index 49af2c46a..7a0af781d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -136,8 +136,9 @@ flow-cli/ ├── hooks/ # ZSH hooks ├── docs/ # Documentation (MkDocs) │ └── internal/ # Internal conventions & contributor templates -├── scripts/ # Standalone validators (check-math.zsh) -├── tests/ # 219 test files, 12000+ test functions +├── scripts/ # Standalone validators (check-math.zsh, check-status.zsh) +├── templates/ # .STATUS.template (canonical shape reference) +├── tests/ # 245 test files, 12000+ test functions │ └── fixtures/demo-course/ # STAT-101 demo course for E2E └── .archive/ # Archived Node.js CLI ``` @@ -182,7 +183,7 @@ flow-cli/ ## Testing -**219 test files, 12000+ test functions.** Run: `./tests/run-all.sh` (67/67 passing, 1 expected interactive/tmux timeout) or individual suites in `tests/`. +**245 test files, 12000+ test functions.** Run: `./tests/run-all.sh` (74/74 passing, 1 skipped — required external tool absent, e.g. himalaya) or individual suites in `tests/`. See `docs/guides/TESTING.md` for patterns, mocks, assertions, TDD workflow. @@ -216,8 +217,8 @@ export FLOW_FORCE_DISPATCHER_OBS=1 # Force-keep one dispatcher (FLOW_F ## Current Status -**Version:** v7.13.0 | **Tests:** 12000+ (67/67 suite, 1 interactive timeout) | **Docs:** https://Data-Wise.github.io/flow-cli/ +**Version:** v7.13.0 | **Tests:** 12000+ (74/74 suite, 1 skipped — tool absence) | **Docs:** https://Data-Wise.github.io/flow-cli/ --- -**Last Updated:** 2026-06-19 (v7.13.0) +**Last Updated:** 2026-07-01 (test counts refreshed — planning-coordination work) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7de90f7d..8476442f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,6 +81,19 @@ flow-cli/ **Important:** The actual ZSH configuration files live in `~/.config/zsh/` (not in this repo). +### `.STATUS` Files + +Every flow-cli-tracked project (including this repo's own root `.STATUS`) follows a +documented shape: `templates/.STATUS.template` is the canonical reference — header +fields (`## Project/Type/Status/Focus/Phase/Priority/Progress`), the `## Schedule:` +grammar `agenda` reads, `## daily_goal:`, and the session-log convention. + +`scripts/check-status.zsh` validates `.STATUS` files against this shape and runs +automatically via `lint-staged` on every commit that touches a `.STATUS` file. It is +**warn-only** — it prints violations but never blocks a commit (flow-cli-scoped +convention, not an ecosystem-wide standard). Run it manually against any file with +`zsh scripts/check-status.zsh path/to/.STATUS`. + --- ## Development Workflow diff --git a/ORCHESTRATE-planning-coordination.md b/ORCHESTRATE-planning-coordination.md new file mode 100644 index 000000000..9cc0a74f7 --- /dev/null +++ b/ORCHESTRATE-planning-coordination.md @@ -0,0 +1,283 @@ +# planning-coordination — Orchestration Plan + +> **Branch:** `feature/planning-coordination` +> **Base:** `dev` +> **Worktree:** `~/.git-worktrees/flow-cli/feature-planning-coordination` +> **Spec:** `docs/specs/SPEC-planning-coordination-2026-07-01.md` (grilled 2026-07-01, D1–D16) +> **Brainstorm:** `docs/specs/BRAINSTORM-planning-coordination-2026-07-01.md` +> **Version Target:** v7.14.0 + +## Objective + +Refactor flow-cli's planning-command duplication (single `.STATUS` accessor, +project-path resolver, project-suggester; fix the `$path`→`project_path` bug +as an isolated pre-step) and add a **dark-ready** atlas-agenda source to the +schedule engine so `agenda`/`dash`/`morning` will surface research deadlines +(atlas `Task.dueDate`) merged with the existing `.STATUS`/teaching schedule +once atlas ships the command — graceful-degrading (silent no-op) until then. +Also add a flow-cli-scoped `.STATUS` template + warn-only enforcer. + +**Cross-repo:** atlas (Track B) and obsidian-cli-ops (Track D) are +**contract-only** here (SPEC §7, hard-scoped by grill decision D7). Do NOT +implement them in this worktree; they get their own specs + grills. This +worktree ships Tracks A + C + the `.STATUS` template/enforcer only. + +**Execution model (grill D10):** this is **phase-gated, not one-shot.** Stop +after each phase (including the pre-step) and wait for the reviewer to run the +full suite independently before proceeding — do not chain phases yourself +even if you believe tests pass. + +## Phase Overview + +| Phase | Increment | Priority | Effort | Status | +| ----- | ------------------------------------------------------ | -------- | ------ | ------ | +| 0 | Pre-step — `$path` bug fix (isolated) | High | ~20min | | +| 1 | Track A — internal refactor (characterization-guarded) | High | ~2-3h | | +| 2 | Track C — atlas agenda source + contract (dark-ready) | High | ~2-3h | | +| 3 | `.STATUS` template + enforcer (warn-only) | Medium | ~1h | | +| 4 | Documentation & Discoverability | High | ~1-2h | | + +## Phase 0: Pre-step — `$path` → `project_path` bug fix (D2, isolated) + +**Scope:** ONLY this bug. Do not touch the shared accessors yet — that's Phase 1. + +- [x] 0.1 **Red test first:** `tests/test-path-bug-fix.zsh` — proves `next`/ + `morning` focus/progress display is blank or wrong today for a project + with nonzero progress (the actual broken behavior, not a synthetic case) +- [x] 0.2 Fix: `morning.zsh:83`, `adhd.zsh:103` — read `$project_path` (the + field the fallback actually emits, `atlas-bridge.zsh:397`) instead of + `$path` (ZSH's PATH array) +- [x] 0.3 Green: `test-path-bug-fix.zsh` passes; full `./tests/run-all.sh` +- [x] 0.4 **Isolated commit** — `fix(planning): read $project_path not $path in morning/next` +- [x] 0.5 **STOP.** Report: test output, diff, full-suite result. Wait for the + reviewer's go-ahead before Phase 1. + +**Key files:** `commands/morning.zsh`, `commands/adhd.zsh` (2-line fix each), `tests/test-path-bug-fix.zsh` (NEW) + +## Phase 1: Track A — internal refactor (characterization-guarded, Axis 2) + +**Scope:** De-duplicate planning helpers onto shared accessors in `lib/core.zsh`; +route `agenda` through the shared schedule engine. TDD — characterization tests +BEFORE any consolidation, then red unit tests for the new accessors. + +- [x] 1.0 **Characterization tests first (Axis 2 — parity guard):** + `tests/test-status-field-parity.zsh` — snapshot the CURRENT output of + `_dash_get_status_field` (`dash.zsh:1325`), the inline greps + (`morning.zsh:84`, `adhd.zsh:104`, `capture.zsh:406`), and + `_flow_where_fallback` (`atlas-bridge.zsh:836`) across 4 `.STATUS` + dialect + missing-field fixtures. Land GREEN on today's code (register + in `run-all.sh`) BEFORE writing any new accessor. +- [x] 1.1 **Red tests:** `tests/test-status-field-accessor.zsh`, + `tests/test-project-path-resolver.zsh`, `tests/test-suggest-project.zsh` + (register in `tests/run-all.sh`) +- [x] 1.2 Add `_flow_status_field ` to `lib/core.zsh` + (handles `## Field:` + `field:` dialects; strips `%`) +- [x] 1.3 Add `_flow_resolve_project_path ` — merges + `_dash_find_project_path` (`dash.zsh:1284`) + `_flow_get_project_fallback` + (`atlas-bridge.zsh:370`); **always emits `project_path=`** +- [x] 1.4 Add `_flow_suggest_project` — one active/priority scan replacing the 5 + reimplementations (`dash.zsh:181`, `:1139`, `morning.zsh:144`, `adhd.zsh` + `next`, `js`) +- [x] 1.5 Migrate call sites: `dash.zsh:1325`, `capture.zsh:406`, + `atlas-bridge.zsh:836`, `morning.zsh:84`, `adhd.zsh:104` → `_flow_status_field` +- [x] 1.6 Route `agenda` (`agenda.zsh:62`) through `_schedule_window_records` + (`schedule.zsh:549`); keep `_schedule_classify` for bucketing +- [x] 1.7 **Parity check:** `test-status-field-parity.zsh` still GREEN + post-refactor — any diff is a silent-behavior-loss regression, not an + acceptable "improvement"; investigate before proceeding +- [x] 1.8 Green: all new/parity unit suites + full `./tests/run-all.sh` +- [x] 1.9 Commit — `refactor(planning): shared .STATUS/project accessors` (+ separate `test:` commit for the characterization/red suites if helpful for review) +- [x] 1.10 **STOP.** Report: files changed, tests added, pass/fail/skip counts, + parity-suite diff (should be none). Wait for the reviewer's go-ahead. + +**Key files:** `lib/core.zsh` (update), `commands/{morning,adhd,dash,capture,agenda}.zsh` (update), `lib/atlas-bridge.zsh` (update), `tests/test-status-field-parity.zsh` / `tests/test-status-field-accessor.zsh` / `tests/test-project-path-resolver.zsh` / `tests/test-suggest-project.zsh` (NEW) + +## Phase 2: Track C — atlas agenda source + contract (dark-ready, D1) + +**Scope:** New capability-probed schedule source that merges atlas records — +ships as tested, inert code (real atlas has no `agenda` command yet). Pin the +proposed contract; leave `.STATUS`-ingestion mechanism as **atlas's own open +question** (D4) — do not design it here. + +- [x] 2.1 **Red tests first:** `tests/test-schedule-atlas-source.zsh`, + `tests/e2e-agenda-atlas.zsh` (register in `run-all.sh`) +- [x] 2.2 Create `tests/fixtures/atlas-agenda-stub.json` — the atlas-present + test fixture (canned `agenda --format json` response) +- [x] 2.3 Add `_schedule_atlas_items ` in `lib/schedule.zsh`; + capability cache `_FLOW_ATLAS_HAS_AGENDA` (mirror `_FLOW_ATLAS_HAS_SCHEDULE`); + call `_flow_atlas_json agenda "$window"`; map to + `date|label|type|project|recurrence|source` with `source=atlas` +- [x] 2.4 Wire into `_schedule_collect` (`schedule.zsh:378`) alongside + `_schedule_parse_status` + `_schedule_teach_items`; **no-op when absent** +- [x] 2.5 `at` dispatcher: `agenda` JSON passthrough in `lib/atlas-bridge.zsh` + — verified the generic `atlas "$@"` passthrough (already existing) + already handles `at agenda ... --format=json` correctly; no code change + needed, confirmed via a stub-atlas dogfood check +- [x] 2.6 Extend `docs/ATLAS-CONTRACT.md`: proposed + `atlas agenda --format json`; bump contract version; + explicitly note `.STATUS`-ingestion as **atlas's open design question**, + not prescribed here +- [x] 2.7 Extend `tests/test-atlas-contract.zsh`: assert the fixture + (2.2)'s shape matches what the contract doc (2.6) documents — a + contract edit without a fixture update must fail this test (D16) +- [x] 2.8 **Test both atlas states (D15):** absent via capability-flag + override (`_FLOW_ATLAS_HAS_AGENDA` / `_flow_has_atlas`, NOT + `FLOW_ATLAS_ENABLED=no` alone — env var is known-insufficient per memory + `capture-real-agenda-output-for-docs`); present via a stub `atlas` shim + on `PATH` returning the fixture +- [x] 2.9 Green: dependency + e2e + full integration (dedupe on + `date|label|project`, no double-count — new: `tests/integration/ +agenda-merged-sources.zsh` didn't exist yet, so dedup coverage was + added as e2e-agenda-atlas.zsh Section 3 instead); full `./tests/run-all.sh` +- [x] 2.10 Commit — `feat(agenda): merge atlas agenda source into schedule engine (dark-ready)` (+ `docs(contract):` for ATLAS-CONTRACT) +- [x] 2.11 **STOP.** Report: files changed, tests added, pass/fail/skip counts, + dogfood confirmation that no-atlas behavior is unchanged. Wait for the + reviewer's go-ahead. + +**Key files:** `lib/schedule.zsh` (update), `lib/atlas-bridge.zsh` (update), `docs/ATLAS-CONTRACT.md` (update), `tests/test-schedule-atlas-source.zsh` / `tests/e2e-agenda-atlas.zsh` / `tests/fixtures/atlas-agenda-stub.json` (NEW), `tests/test-atlas-contract.zsh` / `tests/integration/agenda-merged-sources.zsh` (extend) + +## Phase 3: `.STATUS` template + enforcer (D9, warn-only per D11) + +**Scope:** flow-cli-scoped only — NOT an ecosystem-wide standard. + +- [x] 3.1 **Red test first:** `tests/test-status-schema.zsh` +- [x] 3.2 Write `templates/.STATUS.template` — canonical structure + inline + comments (header fields, `## Schedule:` grammar, `## daily_goal:`, + `## Active Worktrees`, session-log convention; documents the dual + dialect `_flow_status_field` supports) +- [x] 3.3 Write `scripts/check-status.zsh` (model: `scripts/check-math.zsh`) — + validates required fields, `Progress` int 0–100, `Schedule` grammar, + `Status` in allowed set. **exit 0 always (warn-only, D11)** — print + violations, never block +- [x] 3.4 Wire into `lint-staged`: extensionless `.STATUS` filename match + (not a glob) alongside the `check-math.zsh` entry +- [x] 3.5 Green: `check-status.zsh` rejects (prints, doesn't block) a + malformed fixture; passes clean on the template and on flow-cli's own + `.STATUS`; full `./tests/run-all.sh` +- [x] 3.6 Commit — `feat(status): add .STATUS template + warn-only schema enforcer` +- [x] 3.7 **STOP.** Report results. Wait for the reviewer's go-ahead. + +**Key files:** `templates/.STATUS.template` (NEW), `scripts/check-status.zsh` (NEW), `package.json` (lint-staged entry), `tests/test-status-schema.zsh` (NEW) + +## Phase 4: Documentation & Discoverability (REQUIRED — final phase) + +Per SPEC §6 — currency-check each file before editing; update genuine deltas only. + +- [x] Guide — `docs/reference/MASTER-DISPATCHER-GUIDE.md` (atlas source + degradation) +- [x] Guide — `docs/guides/AGENDA-SCHEDULE-GUIDE.md` (data-source diagram + merged example w/ real captured output — memory `capture-real-agenda-output-for-docs`) +- [x] API ref — `docs/reference/MASTER-API-REFERENCE.md` (`_flow_status_field`, `_flow_resolve_project_path`, `_flow_suggest_project`, `_schedule_atlas_items`) +- [x] REFCARD — `docs/help/QUICK-REFERENCE.md` (agenda spans research + dev) +- [x] Contract — `docs/ATLAS-CONTRACT.md` version bump (done in Phase 2; verified — v1.2.0, `atlas agenda` documented) +- [x] `CONTRIBUTING.md` — note `.STATUS` template + warn-only `check-status.zsh` +- [x] CHANGELOG — `CHANGELOG.md` + `docs/CHANGELOG.md` `[Unreleased]`, **mirrored** +- [x] CLAUDE.md — refresh test-file/suite counts after new suites +- [x] Website — `mkdocs build --strict` 0 warnings (no nav change expected) +- [x] Hygiene — mark `docs/specs/SPEC-agenda-schedule-2026-06-13.md` **Implemented** +- [x] `/craft:docs:lint` clean (scoped to files touched this phase — see report) +- Demo — N/A (no new user-facing command surface; extends existing `agenda`) +- Catalog/hub — N/A (no new command/skill/agent) +- [x] **STOP.** Report results. Wait for the reviewer's final go-ahead before any PR is opened (agent does NOT open the PR). + +## Friction Prevention + +- **Context first:** read this ORCHESTRATE file + the SPEC before any edit. +- **Verify location:** `git worktree list` + `pwd` — confirm you are in the + worktree, not the main repo. Branch must be `feature/planning-coordination`. +- **No new code files on dev:** all code lands here on the feature branch. +- **Red-first:** every new test starts failing; carry + `# TODO(author): delete if not contract-bearing` until the contract is confirmed. +- **Characterization before consolidation (Axis 2):** never refactor a + duplicated call site before its current behavior is snapshotted — silent + behavior-loss is the #1 risk this plan guards against. +- **Graceful degradation is a hard requirement:** Track C must be a silent + no-op with no/older atlas — prove it via capability-flag override, not + `FLOW_ATLAS_ENABLED=no` alone (D15). +- **Warn-only means warn-only (D11):** `check-status.zsh` must exit 0 always + this cycle — do not make it blocking. +- **`.STATUS` ingestion is NOT your design problem (D4):** if you find + yourself designing how atlas parses `.STATUS`, stop — that belongs in + atlas's own spec. +- **STOP after EVERY phase (including Phase 0)** and wait for the reviewer's + explicit go-ahead before proceeding — this is phase-gated execution (D10), + not autonomous multi-phase execution. +- **On a failed review gate:** you'll receive the exact failing output via a + follow-up message. Fix forward and re-commit in the same phase — do not + reset, do not skip ahead. If told this is the 2nd failed attempt, stop and + wait; do not attempt a 3rd fix unprompted (D13/D14). +- Portability (memory `macos-only-shell-isms-break-linux-ci`): no `sed -i ''`, + `stat -f`, `date -j`, `exec {var}>`; POSIX-portable for Linux CI. + +## Acceptance Criteria (SPEC §8) + +- [x] `source flow.plugin.zsh` — clean load, no errors +- [x] `./tests/run-all.sh` — **reviewer-run independently**, green (1 expected interactive/tmux timeout); new suites pass; CLAUDE.md counts updated +- [x] Characterization suite unchanged pre/post-refactor (parity proven) +- [x] `check-status.zsh` warn-only: exit 0 always, prints violations on a bad fixture, clean on template + real `.STATUS` +- [x] Dogfood **no atlas** (capability-flag override): `agenda`/`dash`/`morning` identical to pre-change (degradation proven) +- [x] Dogfood **atlas stub on PATH**: research deadlines merged; `$path` fix restores focus/progress +- [x] `mkdocs build --strict` — 0 warnings +- [x] markdownlint (flow-cli's own `.markdownlint.yaml`, reviewer-run) — 20 findings, all confirmed pre-existing/untouched by this branch; zero new findings +- [x] Documentation & Discoverability phase complete + +## Commit Strategy + +- Phase 0: `fix(planning): read $project_path not $path in morning/next` +- Phase 1: `refactor(planning): shared .STATUS/project accessors` (+ `test:` for characterization/red suites) +- Phase 2: `feat(agenda): merge atlas agenda source into schedule engine (dark-ready)` (+ `docs(contract):` for ATLAS-CONTRACT) +- Phase 3: `feat(status): add .STATUS template + warn-only schema enforcer` +- Phase 4: `docs(planning): agenda/dispatcher/API guides + CHANGELOG` + +## Verification (after each phase — run by the REVIEWER, not self-reported) + +```bash +cd ~/.git-worktrees/flow-cli/feature-planning-coordination +source flow.plugin.zsh # clean load +./tests/run-all.sh # FULL suite, every phase gate (Axis 5) +mkdocs build --strict # docs (Phase 4) +``` + +## Integrate (after ALL phases verified — reviewer does this, not the agent) + +```bash +git fetch origin dev && git rebase origin/dev +./tests/run-all.sh +gh pr create --base dev +# after merge: git worktree remove ~/.git-worktrees/flow-cli/feature-planning-coordination +# delete ORCHESTRATE-planning-coordination.md as part of merge cleanup +``` + +## Session Instructions + +### Context + +You are in the **flow-cli repo worktree** for the `planning-coordination` +feature. The SPEC (`docs/specs/SPEC-planning-coordination-2026-07-01.md`, +grilled — decisions D1–D16) has full design details; the BRAINSTORM has the +ecosystem rationale. Tracks B/D are out of scope — contract-only. + +### How to Start + +```bash +cd ~/.git-worktrees/flow-cli/feature-planning-coordination +claude +``` + +On session start, paste: + +> Read `ORCHESTRATE-planning-coordination.md` and the spec at +> `docs/specs/SPEC-planning-coordination-2026-07-01.md`. Confirm you are in the +> worktree (`git worktree list`, `pwd`, branch = `feature/planning-coordination`). +> Then start **Phase 0** (the isolated `$path` fix) using TDD. **STOP after +> Phase 0** and report — do not proceed to Phase 1 without an explicit +> go-ahead. This plan is phase-gated: every phase stops for independent +> review, not just a final summary. + +### Phase-by-Phase + +1. Read the current state of each file listed in the phase. +2. Write the red/characterization tests first, then implement per the SPEC design. +3. Run `./tests/run-all.sh` before stopping. +4. Commit per the Commit Strategy. +5. **STOP and report — wait for the reviewer's go-ahead before the next phase.** +6. On a failed gate: fix forward per the follow-up instruction, re-commit, + re-report. Cap 2 attempts per phase (D13/D14). diff --git a/commands/adhd.zsh b/commands/adhd.zsh index 7261981a2..1d9906159 100644 --- a/commands/adhd.zsh +++ b/commands/adhd.zsh @@ -33,8 +33,8 @@ js() { fi if [[ -z "$project" ]]; then - # Fallback: pick randomly from active projects - project=$(echo "$projects" | sort -R | head -1) + # Fallback: pick randomly from active projects (shared scan, lib/core.zsh) + project=$(_flow_suggest_project random-active) fi fi @@ -100,12 +100,12 @@ next() { if [[ -n "$info" ]]; then eval "$info" - if [[ -n "$path" ]] && [[ -f "$path/.STATUS" ]]; then - focus=$(command grep -m1 "^## Focus:" "$path/.STATUS" 2>/dev/null | command cut -d: -f2- | sed 's/^ *//') + if [[ -n "$project_path" ]] && [[ -f "$project_path/.STATUS" ]]; then + focus=$(_flow_status_field "$project_path" "Focus") fi fi - local icon=$(_flow_project_icon "$(_flow_detect_project_type "$path" 2>/dev/null)") + local icon=$(_flow_project_icon "$(_flow_detect_project_type "$project_path" 2>/dev/null)") printf " %s %-15s" "$icon" "$project" [[ -n "$focus" ]] && printf " → %s" "$focus" echo "" diff --git a/commands/agenda.zsh b/commands/agenda.zsh index b3da0c681..d424e0861 100644 --- a/commands/agenda.zsh +++ b/commands/agenda.zsh @@ -57,14 +57,11 @@ agenda() { ;; esac - # Pipeline: collect -> window filter -> sort + # Pipeline: routed through the shared surface pipeline (_schedule_window_records, + # schedule.zsh) so collect -> window-filter -> sort -> holiday-drop logic + # lives once (SPEC §3.3). Holidays are noise unless explicitly requested (--all). local records - records=$(_schedule_collect "$window" "$category" | _schedule_filter_window "$window" | _schedule_sort) - - # Holidays are noise unless explicitly requested (--all) - if (( ! show_all )) && [[ -n "$records" ]]; then - records=$(print -r -- "$records" | _schedule_drop_holidays) - fi + records=$(_schedule_window_records "$window" "$category" "$show_all") # Bucketize (THIS WEEK vs LATER split on the fixed 7-day horizon) local -a b_overdue=() b_today=() b_week=() b_later=() diff --git a/commands/capture.zsh b/commands/capture.zsh index 81290407a..d4b4bde23 100644 --- a/commands/capture.zsh +++ b/commands/capture.zsh @@ -399,11 +399,13 @@ _flow_read_goal() { local today=$(_flow_today) # First: Check project .STATUS for daily_goal (v3.5.0) + # Reads via the shared accessor (_flow_status_field, lib/core.zsh); note + # this drops the old case-insensitive grep -i — every real .STATUS uses + # lowercase "## daily_goal:" (templates/docs), so this is not load-bearing. if _flow_in_project; then local root=$(_flow_find_project_root) - local status_file="$root/.STATUS" - if [[ -f "$status_file" ]]; then - local project_goal=$(command grep -i "^## daily_goal:" "$status_file" 2>/dev/null | head -1 | sed 's/^## [^:]*: *//') + if [[ -f "$root/.STATUS" ]]; then + local project_goal=$(_flow_status_field "$root" "daily_goal") if [[ -n "$project_goal" ]] && [[ "$project_goal" =~ ^[0-9]+$ ]]; then echo "$project_goal" return diff --git a/commands/dash.zsh b/commands/dash.zsh index 980fabbf0..4fd3904aa 100644 --- a/commands/dash.zsh +++ b/commands/dash.zsh @@ -228,51 +228,30 @@ _dash_right_now() { suggestion="Keep going on" suggested_project="$current_project" else - # Not working - suggest starting - # Find first active project with a focus/next item - local projects=$(_flow_list_projects) + # Not working - suggest starting. Shared scan (_flow_suggest_project, + # lib/core.zsh): prefer an active project that has a Focus, falling back + # to any active project. local found=0 - - while IFS= read -r project; do - [[ -z "$project" ]] && continue - - local proj_path=$(_dash_find_project_path "$project") - if [[ -n "$proj_path" ]] && [[ -f "$proj_path/.STATUS" ]]; then - local proj_status=$(_dash_get_project_status "$proj_path/.STATUS") - if [[ "$proj_status" == "active" ]]; then - local focus=$(_dash_get_project_focus "$proj_path/.STATUS") - if [[ -n "$focus" ]]; then - suggested_project="$project" - next_action="$focus" - found=1 - break - fi - fi + local suggested=$(_flow_suggest_project active-with-focus) + if [[ -n "$suggested" ]]; then + suggested_project="$suggested" + local resolved project_path + resolved=$(_flow_resolve_project_path "$suggested_project") + eval "$resolved" + next_action=$(_dash_get_project_focus "$project_path/.STATUS") + found=1 + else + suggested=$(_flow_suggest_project active) + if [[ -n "$suggested" ]]; then + suggested_project="$suggested" + found=1 fi - done <<< "$projects" - + fi + if (( found )); then suggestion="Start work on" else - # No active project with focus - suggest first active - while IFS= read -r project; do - [[ -z "$project" ]] && continue - local proj_path=$(_dash_find_project_path "$project") - if [[ -n "$proj_path" ]] && [[ -f "$proj_path/.STATUS" ]]; then - local proj_status=$(_dash_get_project_status "$proj_path/.STATUS") - if [[ "$proj_status" == "active" ]]; then - suggested_project="$project" - found=1 - break - fi - fi - done <<< "$projects" - - if (( found )); then - suggestion="Start work on" - else - suggestion="No active projects" - fi + suggestion="No active projects" fi fi @@ -1170,21 +1149,9 @@ _dash_footer() { if [[ -n "$current_project" ]]; then suggestion="💡 Type 'finish' when done • 'dash -i' to switch • 'h' for help" else - # Find first active project to suggest - local projects=$(_flow_list_projects) - local suggested="" - while IFS= read -r project; do - [[ -z "$project" ]] && continue - local proj_path=$(_dash_find_project_path "$project") - if [[ -n "$proj_path" ]] && [[ -f "$proj_path/.STATUS" ]]; then - local proj_status=$(_dash_get_project_status "$proj_path/.STATUS") - if [[ "$proj_status" == "active" ]]; then - suggested="$project" - break - fi - fi - done <<< "$projects" - + # Find first active project to suggest (shared scan, lib/core.zsh) + local suggested=$(_flow_suggest_project active) + if [[ -n "$suggested" ]]; then suggestion="💡 Try: 'work $suggested' to start • 'dash -i' for picker • 'h' for help" else @@ -1280,29 +1247,16 @@ ${_C_DIM}See also:${_C_NC} work help, status help, pick help # HELPERS # ============================================================================ -# Find project path by name +# Find project path by name — delegates to the shared resolver +# (_flow_resolve_project_path, lib/core.zsh) which merges this function's +# original taxonomy with _flow_get_project_fallback's. Kept as a thin +# wrapper so the ~10 internal callers in this file don't need to change. _dash_find_project_path() { local name="$1" - local search_dirs=( - "$FLOW_PROJECTS_ROOT/dev-tools" - "$FLOW_PROJECTS_ROOT/apps" - "$FLOW_PROJECTS_ROOT/r-packages/active" - "$FLOW_PROJECTS_ROOT/r-packages/stable" - "$FLOW_PROJECTS_ROOT/research" - "$FLOW_PROJECTS_ROOT/teaching" - "$FLOW_PROJECTS_ROOT/quarto/manuscripts" - "$FLOW_PROJECTS_ROOT/quarto/presentations" - "$FLOW_PROJECTS_ROOT" - ) - - for dir in "${search_dirs[@]}"; do - if [[ -d "$dir/$name" ]]; then - echo "$dir/$name" - return 0 - fi - done - - return 1 + local resolved project_path + resolved=$(_flow_resolve_project_path "$name") || return 1 + eval "$resolved" + echo "$project_path" } # Detect category from path @@ -1320,45 +1274,15 @@ _dash_detect_category() { esac } -# Parse .STATUS field (supports both "## Field:" and "field:" formats) -# Uses ZSH builtins only - no external commands +# Parse .STATUS field (supports both "## Field:" and "field:" formats). +# Delegates to the shared accessor (_flow_status_field, lib/core.zsh), which +# takes a project ROOT rather than a full file path — this wrapper adapts so +# the ~15 internal callers passing "$proj_path/.STATUS" don't need to change. _dash_get_status_field() { local file="$1" local field="$2" - local value="" line - - [[ ! -f "$file" ]] && return 1 - - # Read file and find matching line - while IFS= read -r line; do - # Try markdown format (## Field:) - if [[ "$line" == "## ${field}:"* ]]; then - value="${line#*: }" # Remove everything up to ": " - value="${value#"${value%%[![:space:]]*}"}" # Trim leading whitespace - break - fi - # Try YAML format (field:) - if [[ "$line" == "${field}:"* ]]; then - value="${line#*: }" - value="${value#"${value%%[![:space:]]*}"}" - break - fi - done < "$file" - - # Normalize status values - if [[ "$field" == "Status" || "$field" == "status" ]]; then - value="${value:l}" # ZSH: lowercase - value="${value// /}" # ZSH: remove spaces - # Map variations - case "$value" in - underreview) value="active" ;; - inprogress) value="active" ;; - wip) value="active" ;; - onhold) value="paused" ;; - esac - fi - - echo "$value" + local root="${file%/.STATUS}" + _flow_status_field "$root" "$field" } # Get project status diff --git a/commands/morning.zsh b/commands/morning.zsh index 0de6e06c5..4ad89d149 100644 --- a/commands/morning.zsh +++ b/commands/morning.zsh @@ -80,13 +80,13 @@ _flow_morning_projects() { if [[ -n "$info" ]]; then eval "$info" - if [[ -n "$path" ]] && [[ -f "$path/.STATUS" ]]; then - focus=$(grep -m1 "^## Focus:" "$path/.STATUS" 2>/dev/null | cut -d: -f2- | sed 's/^ *//') - progress=$(grep -m1 "^## Progress:" "$path/.STATUS" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' | tr -d '%') + if [[ -n "$project_path" ]] && [[ -f "$project_path/.STATUS" ]]; then + focus=$(_flow_status_field "$project_path" "Focus") + progress=$(_flow_status_field "$project_path" "Progress" | tr -d '%') fi fi - - local icon=$(_flow_project_icon "$(_flow_detect_project_type "$path" 2>/dev/null)") + + local icon=$(_flow_project_icon "$(_flow_detect_project_type "$project_path" 2>/dev/null)") printf " %s %-15s" "$icon" "$project" [[ -n "$progress" ]] && printf " [%3d%%]" "$progress" @@ -151,17 +151,8 @@ _flow_morning_suggest() { if _flow_has_atlas; then suggestion=$(_flow_atlas project list --status=active --format=names 2>/dev/null | head -1) else - # Find priority project from filesystem - for status_file in "$FLOW_PROJECTS_ROOT"/**/.STATUS(N); do - local status=$(grep -m1 "^## Status:" "$status_file" | cut -d: -f2 | tr -d ' ' | tr '[:upper:]' '[:lower:]') - local priority=$(grep -m1 "^## Priority:" "$status_file" | cut -d: -f2 | tr -d ' ') - - if [[ "$status" == "active" ]] && [[ "$priority" == "1" || "$priority" == "P1" ]]; then - local dir="${status_file:h}" - suggestion="${dir:t}" - break - fi - done + # Find priority project (shared scan, lib/core.zsh — P1/priority 1) + suggestion=$(_flow_suggest_project priority) fi if [[ -n "$suggestion" ]]; then diff --git a/docs/ATLAS-CONTRACT.md b/docs/ATLAS-CONTRACT.md index 7dd02e334..db58e41a5 100644 --- a/docs/ATLAS-CONTRACT.md +++ b/docs/ATLAS-CONTRACT.md @@ -1,7 +1,7 @@ # Atlas CLI API Contract -**Version:** 1.1.0 -**Parties:** flow-cli (v7.10.x) <-> Atlas CLI `@data-wise/atlas` (v0.9.3) +**Version:** 1.2.0 +**Parties:** flow-cli (v7.14.x) <-> Atlas CLI `@data-wise/atlas` (v0.9.3+, `agenda` proposed) **Status:** Active --- @@ -10,6 +10,7 @@ | flow-cli | Atlas CLI | Notes | |----------|-----------|-------| +| v7.14.x | v0.9.3+ | Contract v1.2 — adds the *proposed* `atlas agenda` opportunistic read source (Track C, dark-ready — see below). No atlas release implements it yet; flow-cli ships this as tested, capability-probed, inert code. | | v7.10.x | v0.9.3 | Contract v1.1 — 5 new integration flags (`session status --format`, `project list --count`, `project list --suggest`, `inbox --count`, `trail --limit`) | | v7.4.x | v0.9.x | Contract v1.0 — original contract | | v7.3.x | v0.8.x | Legacy — no `crumb` command | @@ -63,6 +64,63 @@ the data model and degrades fully without atlas. | Command | Description | Direction | |---------|-------------|-----------| | `atlas schedule push --format=json --data=` | Ingest forward-looking dated items | flow-cli → atlas | +| `atlas agenda --format=json` | Read atlas-tracked deadlines (`Task.dueDate`) for a window | atlas → flow-cli | + +### `atlas agenda` (proposed, dark this cycle — Track C) + +The schedule engine (`lib/schedule.zsh`) merges a third source into +`_schedule_collect` alongside `.STATUS` `## Schedule:` blocks and +`.flow/teach-config.yml`: atlas-tracked deadlines (research tasks, grant +reports, anything atlas's own `Task.dueDate` model tracks) that don't live in +a project's `.STATUS` file at all. + +**Capability probe.** flow-cli runs `atlas agenda --help` once per session and +caches the result (`_FLOW_ATLAS_HAS_AGENDA`, mirroring `_FLOW_ATLAS_HAS_SCHEDULE`). +If atlas is absent, or present but lacks an `agenda` subcommand, +`_schedule_atlas_items` returns nothing and the engine falls back to +`.STATUS` + teach-config only — **no behavior change without atlas**. + +**Call.** `_flow_atlas_json agenda "$window"` — `$window` is the same +look-ahead window (in days) the engine itself uses (`SCHEDULE_DEFAULT_WINDOW`, +0/7/30/3650), passed positionally. `_flow_atlas_json` appends `--format=json`. + +**Response.** A JSON array of records — atlas does NOT include a `source` +field; flow-cli's mapping (`_schedule_atlas_items`) stamps `source=atlas` on +every record it ingests, same idea as `status`/`teach-config` tagging their +own origin: + +```json +[ + {"date":"2026-07-05","label":"Submit grant report","type":"research","project":"grant-writing","recurrence":"none"}, + {"date":"2026-07-12","label":"NIH progress report","type":"research","project":"grant-writing","recurrence":"none"} +] +``` + +| Field | Meaning | +|-------|---------| +| `date` | ISO `YYYY-MM-DD` | +| `label` | Human text | +| `type` | `teaching` · `research` · `general` · `recurring` · `holiday` | +| `project` | Project name (flow-cli maps this to a local path the same way as any other project) | +| `recurrence` | `none` or `weekly:` | + +Mapped into the engine's internal `date\|label\|type\|project\|recurrence\|source` +record shape with `source` hardcoded to `atlas` during mapping (not read from +the JSON). Deduped against `.STATUS`/teach-config records downstream on +`(date, label, project)` like any other source — no special-casing. + +**Out of scope here (atlas's own open question).** How atlas populates +`Task.dueDate` in the first place — whether it ever reads `.STATUS` +`## Schedule:` blocks itself, imports from somewhere else, or is purely +user-entered — is entirely atlas's design space. This contract only pins the +**read interface** flow-cli calls; it does not prescribe atlas's ingestion +mechanism. + +**Deliberately dark this cycle.** No atlas release implements `agenda` yet +(Track B, tracked separately). flow-cli ships this as tested, capability- +probed, inert code — same posture `schedule push` shipped with. Zero user- +visible payoff until atlas's `agenda` command exists; zero rework required +when it does, beyond flipping the capability probe to "yes". ### `atlas schedule push` (proposed) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a0ecc34e9..189bfdadf 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -20,6 +20,10 @@ The format follows [Keep a Changelog](https://keepachangelog.com/), and this pro org installation, and exchanges for a `ghs_` token. Supports `--org`, `--dry-run`, and `--verbose` flags. Interactive `tok mint setup` wizard for one-time credential storage ([#479](https://github.com/Data-Wise/flow-cli/issues/479)). +- **Shared `.STATUS`/project accessors** (`lib/core.zsh`): `_flow_status_field`, `_flow_resolve_project_path`, `_flow_suggest_project` — replace 4 divergent `.STATUS` field readers, 2 project-path resolvers, and 5 project-suggestion scans with one implementation each, guarded by a byte-parity characterization suite. `dash`, `morning`, `next`, `capture`, and `agenda` now share the same accessors instead of reimplementing them. +- **Atlas agenda source, dark-ready** (`lib/schedule.zsh`): `_schedule_atlas_items` — a third, capability-probed schedule source (alongside `.STATUS` `## Schedule:` blocks and teach-config) that will surface atlas-tracked deadlines (`Task.dueDate`) once atlas ships an `agenda` command. Ships as tested, inert code — no atlas release implements it yet, so this is a silent no-op today. See `docs/ATLAS-CONTRACT.md` (`atlas agenda`, proposed). +- **`agenda` routed through the shared schedule pipeline** (`lib/schedule.zsh`, `commands/agenda.zsh`): `_schedule_window_records` gained `category`/`keep_holidays` parameters so `agenda`'s collect→filter→sort→holiday-drop chain is now the same shared pipeline `dash`/`morning`/`today`/`week` already use. Added a `(date, label, project)` dedupe pass — the first duplicate-possible scenario now that a third source exists. +- **`.STATUS` template + warn-only schema checker**: `templates/.STATUS.template` documents the canonical `.STATUS` shape (header fields, `## Schedule:` grammar, `## daily_goal:`, `## Active Worktrees`, session-log convention); `scripts/check-status.zsh` validates it as a warn-only `lint-staged` pre-commit check (prints violations, never blocks — flow-cli-scoped, not an ecosystem standard). ### Fixed @@ -27,6 +31,9 @@ The format follows [Keep a Changelog](https://keepachangelog.com/), and this pro - **Broken link** (`docs/guides/EMAIL-DISPATCHER-GUIDE.md`): removed References entry linking to `internal/EM-V2-ARCHITECTURE.md` which is excluded from the site - **CONTRIBUTING.md deploy instruction**: replaced incorrect `mkdocs gh-deploy --force` step with note that docs auto-deploy via CI (manual deploy is blocked by branch guard) - **DOCUMENTATION-STYLE-GUIDE.md**: updated `--strict` link-check reference to `mkdocs build` (validation block replaces `--strict`); added MkDocs Configuration section documenting breadcrumbs, `not_in_nav`, `exclude` plugin, and the fnmatch footgun +- **`$path` → `$project_path` bug** (`commands/morning.zsh`, `commands/adhd.zsh`): `next`/`morning` read ZSH's PATH-tied `$path` array instead of the `project_path` field the project resolver actually emits — `.STATUS` focus/progress and the project-type icon were silently blank or wrong for every project with nonzero progress. Landed as an isolated pre-step ahead of the shared-accessor refactor above. +- **`_flow_where_fallback` crash** (`lib/atlas-bridge.zsh`): `local status=...` crashed (`status` is a zsh read-only special variable, even as a function-local) — `at where` never actually printed `Status:`/`Focus:`. Fixed as an unavoidable side effect of the shared-accessor migration above. + ## [7.13.0] — 2026-06-19 — flow claude: C7-C11 checks + watch daemon ### Added diff --git a/docs/guides/AGENDA-SCHEDULE-GUIDE.md b/docs/guides/AGENDA-SCHEDULE-GUIDE.md index c833b02b3..e2057472d 100644 --- a/docs/guides/AGENDA-SCHEDULE-GUIDE.md +++ b/docs/guides/AGENDA-SCHEDULE-GUIDE.md @@ -83,6 +83,23 @@ is due: ## Where items come from +Three sources feed the same engine and are merged, deduped, and rendered +identically no matter which surface you're looking at: + +```mermaid +flowchart LR + A["📄 .STATUS
## Schedule: blocks
(no yq needed)"] --> E + B["🎓 .flow/teach-config.yml
teaching dates
(needs yq)"] --> E + C["☁️ atlas agenda
Task.dueDate
(dark-ready — no atlas
release implements it yet)"] -.->|"silent no-op
without atlas"| E + E["_schedule_collect
(lib/schedule.zsh)"] --> F["dedupe on
(date, label, project)"] + F --> G["agenda / dash UPCOMING /
morning / today / week"] +``` + +The dotted line marks the atlas source as **optional and currently inert** — +see [Atlas integration](#atlas-integration-optional-dark-ready) below. The +other two sources work fully without atlas and without `yq` (the teaching +path is the only one that needs `yq`; it's skipped gracefully when absent). + ### 1. `## Schedule:` in a project's `.STATUS` (no `yq` needed) Add a `## Schedule:` section to any project's `.STATUS` file: @@ -127,6 +144,16 @@ agenda still works, the teaching items are just skipped. Holidays are typed `holiday` and hidden unless you pass `--all`. +### 3. Atlas-tracked deadlines (dark-ready, optional) + +A third source, `_schedule_atlas_items` (`lib/schedule.zsh`), reads +atlas-tracked deadlines (`Task.dueDate`) — things you track in atlas rather +than in a project's `.STATUS`. See +[Atlas integration](#atlas-integration-optional-dark-ready) below for details +and a real merged example. **No atlas release implements this yet** — until +it does, this source is a silent no-op and changes nothing about the two +sources above. + --- ## Icons @@ -192,13 +219,64 @@ each command answers. --- -## Atlas integration (optional) +## Atlas integration (optional, dark-ready) + +Atlas integration is two independent, opportunistic paths — a push (flow-cli +→ atlas, shipped) and a read (atlas → flow-cli, dark-ready as of v7.14.0). +Both degrade to a silent no-op without atlas; neither is required for +`agenda` to work. + +### Push: flow-cli → atlas (shipped) When atlas is installed **and** exposes a `schedule` subcommand, `agenda` pushes the collected items opportunistically and asynchronously (`atlas schedule push --format=json`). When atlas is absent — or present but without that subcommand — the push is a silent no-op. flow-cli owns the model; -atlas is just a sync target. See [ATLAS-CONTRACT](../ATLAS-CONTRACT.md). +atlas is just a sync target. + +### Read: atlas → flow-cli (dark-ready, v7.14.0) + +`_schedule_atlas_items` (source #3 above) reads atlas-tracked deadlines back +into the engine via `atlas agenda --format=json`, probed once per +session (`atlas agenda --help`) and cached. **No atlas release implements +`agenda` yet** — real atlas doesn't have the command, so this ships as tested, +capability-probed, inert code. Zero user-visible change until atlas ships it; +zero rework required here when it does. + +Below is a real captured run (not a fabricated transcript) against a stub +`atlas` returning one deadline, merged alongside a `.STATUS` Schedule item and +a teaching week — this is what it looks like once atlas *does* implement +`agenda`: + +```text +$ agenda -m + + 📅 AGENDA (next 30 days) + + THIS WEEK (4) + 🔁 in 2d Grading window (manuscript-x) + 🔬 in 4d Submit JRSS-B revision (manuscript-x) + 🎓 in 5d Week 1 (stat-101) + 🔬 in 7d NIH progress report (manuscript-x) + + LATER (4) + 🔁 in 9d Grading window (manuscript-x) + 🔁 in 16d Grading window (manuscript-x) + 🔁 in 23d Grading window (manuscript-x) + 🔁 in 30d Grading window (manuscript-x) + + 8 items • 'agenda -h' for options +``` + +"NIH progress report" is the only atlas-sourced item (`🔬`, same research +icon as any other research-typed record — the source is invisible to the +rendered output, by design). "Submit JRSS-B revision" and the weekly +"Grading window" come from `.STATUS`; "Week 1" comes from teach-config. If an +atlas record matches a local one exactly on `(date, label, project)`, it's +deduped — the local `.STATUS` record always wins. + +See [ATLAS-CONTRACT](../ATLAS-CONTRACT.md) for the full `atlas agenda` +contract (request/response shape, capability probe). --- diff --git a/docs/help/QUICK-REFERENCE.md b/docs/help/QUICK-REFERENCE.md index 040563516..84ef9b016 100644 --- a/docs/help/QUICK-REFERENCE.md +++ b/docs/help/QUICK-REFERENCE.md @@ -157,6 +157,12 @@ agenda -h Add dated items to any project's `.STATUS` (no `yq` needed). Teaching dates come from `.flow/teach-config.yml` automatically. +!!! tip "Not just for research/teaching" + `agenda` spans every project category — `dev`, `r`, `quarto`, `apps` — not + only `research`/`teach`. A plain `## Schedule:` block with `general` or + `recurring` items works the same way on any project; filter by category + (`agenda dev`, `agenda apps`, …) exactly like `agenda research`/`agenda teach`. + ```markdown ## Schedule: - 2026-06-20 | Submit JRSS-B revision | research diff --git a/docs/reference/MASTER-API-REFERENCE.md b/docs/reference/MASTER-API-REFERENCE.md index 50d721340..2f5e5bce0 100644 --- a/docs/reference/MASTER-API-REFERENCE.md +++ b/docs/reference/MASTER-API-REFERENCE.md @@ -20,7 +20,7 @@ This document provides complete API documentation for all flow-cli library funct ### Coverage Status **Total Functions:** 880 -**Documented:** 178 (20.2%) +**Documented:** 182 (20.7%) **Auto-Generated:** Will be updated by `scripts/generate-api-docs.sh` ### Library Organization @@ -114,6 +114,7 @@ When adding new functions: - [Core Library](#core-library) - Essential utilities - [Atlas Integration](#atlas-integration) - State engine +- [Schedule Engine](#schedule-engine) - Forward-looking schedule layer (`agenda`) - [Project Detection](#project-detection) - Project type detection - [Terminal UI](#terminal-ui) - TUI components - [Tool Inventory](#tool-inventory) - Dependency tracking @@ -526,6 +527,144 @@ echo "Project type: $type" --- +#### `_flow_status_field` + +Read one field from a project's `.STATUS` file (shared accessor — replaces 4 +divergent field readers that used to exist across `dash`/`morning`/`next`/`capture`). + +**Signature:** + +```zsh +_flow_status_field +``` + +**Parameters:** + +- `$1` - Project root directory (NOT the `.STATUS` file path itself — this + function appends `/.STATUS`) +- `$2` - Field name, e.g. `Focus`, `Progress`, `Status` + +**Returns:** + +- 0 - `.STATUS` file found (value may still be empty if the field is absent) +- 1 - `.STATUS` file not found + +**Output:** + +- Field's value with leading whitespace trimmed, or empty + +**Example:** + +```zsh +_flow_status_field "$project_path" "Focus" +_flow_status_field "$project_path" "Progress" | tr -d '%' # site strips % +``` + +**Notes:** + +- Handles both the `## Field:` markdown dialect and the plain `field:` + YAML-ish dialect (case-sensitive field-name match in both) +- Does NOT strip `%` — callers that need a bare number (e.g. Progress) strip + it themselves. A universal strip here would corrupt any other field that + legitimately contains `%` (e.g. `## Focus: hit 80% coverage`) +- `Status`/`status` gets synonym normalization (lowercase, spaces removed; + `underreview`/`inprogress`/`wip` → `active`, `onhold` → `paused`) +- `_dash_get_status_field` (`commands/dash.zsh`) is now a thin delegating + wrapper around this function + +--- + +#### `_flow_resolve_project_path` + +Find a project's absolute path by name (shared resolver — merges +`_dash_find_project_path`'s taxonomy with `_flow_get_project_fallback`'s). + +**Signature:** + +```zsh +_flow_resolve_project_path +``` + +**Parameters:** + +- `$1` - Project name + +**Returns:** + +- 0 - Project found +- 1 - Project not found + +**Output:** + +- A single shell-evaluable line: `project_path="/abs/path"` (never `path=` — + that collides with ZSH's PATH-tied array) + +**Example:** + +```zsh +local out project_path +if out=$(_flow_resolve_project_path "flow-cli"); then + eval "$out" + echo "Found at: $project_path" +fi +``` + +**Notes:** + +- Search order: `dev-tools`, `apps`, `r-packages/active`, `r-packages/stable`, + `research`, `teaching`, `quarto/manuscripts`, `quarto/presentations`, then + an exact match directly under `$FLOW_PROJECTS_ROOT`, then a generic + `quarto/` fallback +- `_dash_find_project_path` and `_flow_get_project_fallback` are now thin + delegating wrappers around this function + +--- + +#### `_flow_suggest_project` + +Suggest a project to work on (shared active/priority/random scan — replaces +5 separate reimplementations in `dash`, `morning`, and `adhd.zsh`). + +**Signature:** + +```zsh +_flow_suggest_project [strategy] +``` + +**Parameters:** + +- `$1` (optional) - Strategy [default: `active`]: + - `active` - first active project + - `active-with-focus` - first active project with a non-empty Focus + - `priority` - first active project with Priority `1`/`P1` + - `random-active` - random pick from the active list (or the first 5 of + all projects if none are active) + +**Returns:** + +- 0 - A project was found +- 1 - No qualifying project found + +**Output:** + +- The suggested project name, or empty + +**Example:** + +```zsh +suggested=$(_flow_suggest_project active-with-focus) +suggested=$(_flow_suggest_project priority) +``` + +**Notes:** + +- Not byte-parity guarded like `_flow_status_field` — the 5 call sites this + replaces (dash "right now"/footer, morning priority scan, adhd `next`/`js`) + genuinely differ in selection logic; each strategy is pinned by its own + unit test instead + +--- + ### Time Utilities #### `_flow_format_duration` @@ -1488,6 +1627,65 @@ at crumb "Debugging auth flow" --- +## Schedule Engine + +**File:** `lib/schedule.zsh` +**Purpose:** Forward-looking schedule layer powering `agenda`, the dash UPCOMING +section, and dated enrichment of `morning`/`today`/`week` +**Note:** This section is not yet exhaustive — only the atlas-source addition +below is documented here so far; `_schedule_collect`, `_schedule_window_records`, +`_schedule_classify`, and the rest of the pipeline are documented inline in +`lib/schedule.zsh` itself and in `docs/guides/AGENDA-SCHEDULE-GUIDE.md`. + +#### `_schedule_atlas_items` + +Read atlas-tracked deadlines (`Task.dueDate`) for a window — a third, +capability-probed schedule source (alongside `.STATUS` `## Schedule:` blocks +and teach-config). **Dark-ready:** no atlas release implements the `agenda` +command yet, so this ships as tested, inert code — a silent no-op today. See +`docs/ATLAS-CONTRACT.md` (`atlas agenda`, proposed). + +**Signature:** + +```zsh +_schedule_atlas_items +``` + +**Parameters:** + +- `$1` - Window in days (the same look-ahead window the engine itself uses) + +**Returns:** + +- 0 - Always (silent no-op on any absence: atlas, `agenda` capability, `jq`) + +**Output:** + +- Record stream in the engine's `date|label|type|project|recurrence|source` + shape, with `source` hardcoded to `atlas` (not read from the JSON) + +**Example:** + +```zsh +_schedule_atlas_items 7 +# (empty today — no atlas release implements `agenda` yet) +``` + +**Notes:** + +- Capability probe (`atlas agenda --help`) is cached per session in + `_FLOW_ATLAS_HAS_AGENDA`, mirroring `_FLOW_ATLAS_HAS_SCHEDULE` +- Requires `jq` to parse the JSON array response; absence of `jq` is just + another silent-no-op path, same as atlas absence — no hard dependency + added to the engine +- Wired into `_schedule_collect` as a third source, called once per collect + (not once per project — atlas returns cross-project results in one call) +- How atlas populates `Task.dueDate` (including whether it ever reads + `.STATUS` itself) is explicitly out of scope here — atlas's own open + design question + +--- + ## Project Detection **File:** `lib/project-detector.zsh` diff --git a/docs/reference/MASTER-DISPATCHER-GUIDE.md b/docs/reference/MASTER-DISPATCHER-GUIDE.md index eafcb2e0c..86b179991 100644 --- a/docs/reference/MASTER-DISPATCHER-GUIDE.md +++ b/docs/reference/MASTER-DISPATCHER-GUIDE.md @@ -66,6 +66,14 @@ The same schedule engine (`lib/schedule.zsh`) also enriches other surfaces: `week` show dated blocks. See the [Agenda & Schedule guide](../guides/AGENDA-SCHEDULE-GUIDE.md). +The engine merges three sources: `.STATUS` `## Schedule:` blocks, teaching +config dates, and — dark-ready as of v7.14.0 — atlas-tracked deadlines +(`Task.dueDate`), once atlas ships an `agenda` command (see +[`docs/ATLAS-CONTRACT.md`](../ATLAS-CONTRACT.md)). Without atlas, or with an +atlas that doesn't implement `agenda` yet, the atlas source is a **silent +no-op** — `agenda`/`dash`/`morning` behave identically to today, driven by +`.STATUS` + teach-config alone. + --- ## How to Use This Guide diff --git a/docs/specs/SPEC-agenda-schedule-2026-06-13.md b/docs/specs/SPEC-agenda-schedule-2026-06-13.md index ae36b54f7..1d3b8d135 100644 --- a/docs/specs/SPEC-agenda-schedule-2026-06-13.md +++ b/docs/specs/SPEC-agenda-schedule-2026-06-13.md @@ -2,8 +2,9 @@ | | | |---|---| -| **Status** | draft | +| **Status** | Implemented | | **Created** | 2026-06-13 | +| **Implemented** | v7.10.0 (`agenda` + dash UPCOMING + schedule engine); atlas agenda source (dark-ready) added in v7.14.0 — see `SPEC-planning-coordination-2026-07-01.md` §3.4 | | **Author** | dt + Claude (brainstorm) | | **From brainstorm** | `/workflow:brainstorm -d -s` session, 2026-06-13 | | **Plan file** | `~/.claude/plans/eventual-bouncing-phoenix.md` | diff --git a/docs/specs/SPEC-planning-coordination-2026-07-01.md b/docs/specs/SPEC-planning-coordination-2026-07-01.md index 4873d445a..8756b27a7 100644 --- a/docs/specs/SPEC-planning-coordination-2026-07-01.md +++ b/docs/specs/SPEC-planning-coordination-2026-07-01.md @@ -1,9 +1,9 @@ # SPEC — flow-cli planning-command refactor + atlas-agenda coordination -**Status:** Draft (awaiting approval) +**Status:** Approved (grilled 2026-07-01 — see `GRILL-planning-coordination-2026-07-01` decisions D1–D16 in `~/.claude/plans/fuzzy-conjuring-milner.md`) **Date:** 2026-07-01 · **Target version:** v7.14.0 · **Focus:** architecture / ops **Companion:** [`BRAINSTORM-planning-coordination-2026-07-01.md`](BRAINSTORM-planning-coordination-2026-07-01.md) -**Scope in this repo:** Track A (flow-cli internal refactor) + Track C (consume atlas agenda). Tracks B (atlas `task`/`agenda` CLI) and D (obs render/seed) are cross-repo dependencies, documented here as contract only. +**Scope in this repo — hard boundary (D7):** Track A (flow-cli internal refactor) + Track C (consume atlas agenda, shipped **dark-ready**, D1) + a new flow-cli-scoped `.STATUS` template/enforcer (D9). Tracks B (atlas `task`/`agenda` CLI) and D (obs render/seed) are cross-repo dependencies, documented here as contract only — **not designed or built in this cycle**; each gets its own spec + grill. --- @@ -14,54 +14,83 @@ flow-cli's planning surface (`agenda`, `dash`, `morning`, `next`, `js`) works bu ## 2. Goals / non-goals **Goals** -- G1 — Single accessor for `.STATUS` fields, project-path resolution, and project suggestion (kill 4×/2×/5× duplication). -- G2 — Fix the `$path` → `project_path` bug in `morning.zsh` + `adhd.zsh`. +- G0 — **(Pre-step, isolated — D2)** Fix the `$path` → `project_path` bug in `morning.zsh` + `adhd.zsh`, red-test-first, landed **before** any refactor so it's independently bisectable/revertable. +- G1 — Single accessor for `.STATUS` fields, project-path resolution, and project suggestion (kill 4×/2×/5× duplication), **guarded by characterization/snapshot tests of every call site's CURRENT output, landed green before consolidation** (Axis 2 — the unification must not silently change what a call site sees). +- G2 — *(superseded by G0 — kept as the design record of the bug; the fix itself ships as the pre-step)*. - G3 — Route `agenda` through the shared `_schedule_window_records` engine (no inlined pipeline). -- G4 — New `_schedule_atlas_items` source: merge `atlas agenda`/`atlas task` records into the schedule engine, capability-probed, graceful-degrading. -- G5 — Pin `atlas task list --format json` + `atlas agenda --format json` in `docs/ATLAS-CONTRACT.md` as proposed contract. +- G4 — New `_schedule_atlas_items` source: merge `atlas agenda`/`atlas task` records into the schedule engine, capability-probed, graceful-degrading, **shipped dark-ready (D1)** — mirrors the existing `schedule push` no-op-until-atlas-ships pattern. +- G5 — Pin `atlas task list --format json` + `atlas agenda --format json` in `docs/ATLAS-CONTRACT.md` as proposed contract. **`.STATUS`-ingestion mechanism is explicitly left as atlas's own open design question (D4) — this SPEC does not prescribe it.** +- G6 — **(D9)** flow-cli-scoped `.STATUS` template (`templates/.STATUS.template`) + a **warn-only** enforcer (`scripts/check-status.zsh`, D11) documenting the dialects `_flow_status_field` supports. **Non-goals** -- Implementing the atlas CLI (Track B) or obs rendering (Track D) — separate repos/specs. +- Implementing the atlas CLI (Track B), its `.STATUS`-ingestion design, or obs rendering (Track D) — separate repos/specs (D4, D7). - Changing the `.STATUS ## Schedule:` grammar or teach-config parsing. - New top-level command (extends existing `agenda`; no count-cascade). +- An ecosystem-wide `.STATUS` standard — flow-cli-scoped only this cycle (deferred per the follow-up to D7). +- Blocking `.STATUS` commits anywhere — the enforcer is warn-only until a follow-up proves the template against real files (D11). ## 3. Design -### 3.1 Shared accessors (`lib/core.zsh`) +### 3.0 Pre-step: `$path` bug fix (G0, D2 — isolated, ahead of everything) +`morning.zsh:83` and `adhd.zsh:103` do `eval "$info"` then read `$path`. The fallback emits `project_path=` (`atlas-bridge.zsh:397` — deliberately, per its comment "Avoid 'path' — conflicts with ZSH's PATH-tied variable"). Result: `$path` = the shell's PATH array, so `.STATUS` focus/progress + `_flow_detect_project_type "$path"` operate on garbage. **This lands as its own commit before Phase 1's refactor**, so it's bisectable independent of the larger change: +1. Red test proving the *current* broken output (focus/progress blank or wrong in `next`/`morning` for a project with nonzero progress). +2. Fix: read `$project_path` directly (not yet through the new resolver — that consolidation is Phase 1 proper). +3. Green — isolated commit. + +### 3.1 Shared accessors (`lib/core.zsh`) — guarded by characterization tests (Axis 2) +**Before writing any of these**, snapshot each call site's CURRENT output against fixtures (four `.STATUS` dialect variants + missing-field cases) and land those characterization tests green on **today's** code. Only then refactor; the same tests must stay green after (parity, not just new-behavior coverage) — this is the guard against silently changing what a call site sees when unifying 4 divergent readers / 2 resolvers / 5 suggesters into one each. - `_flow_status_field ` — replaces `_dash_get_status_field` (`dash.zsh:1325`), the inline `grep`s in `morning.zsh:84` / `adhd.zsh:104` / `capture.zsh:406`, and `_flow_where_fallback` field reads (`atlas-bridge.zsh:836`). Handles both `## Field:` and `field:` dialects (superset of current behavior). - `_flow_resolve_project_path ` — single resolver merging `_dash_find_project_path` (`dash.zsh:1284`, has apps + quarto/manuscripts + presentations) and `_flow_get_project_fallback` (`atlas-bridge.zsh:370`). **Always emits `project_path=` never `path=`.** - `_flow_suggest_project` — single active/priority scan replacing the 5 reimplementations (`dash.zsh:181`, `:1139`, `morning.zsh:144`, `adhd.zsh` `next`, `js`). -### 3.2 Bug fix (G2) -`morning.zsh:83` and `adhd.zsh:103` do `eval "$info"` then read `$path`. The fallback emits `project_path=` (`atlas-bridge.zsh:397` — deliberately, per its comment "Avoid 'path' — conflicts with ZSH's PATH-tied variable"). Result: `$path` = the shell's PATH array, so `.STATUS` focus/progress + `_flow_detect_project_type "$path"` operate on garbage. Fix: read `$project_path` (routed through `_flow_resolve_project_path`). +### 3.2 *(bug fix moved to §3.0 as an isolated pre-step — D2)* ### 3.3 agenda unification (G3) `agenda.zsh:62` runs an inline collect→filter→classify variant. Re-point it at `_schedule_window_records` (`schedule.zsh:549`) so bucketing + holiday-drop logic lives once. `_schedule_classify` stays for OVERDUE/TODAY/THIS-WEEK/LATER labelling. -### 3.4 atlas agenda source (G4) +### 3.4 atlas agenda source (G4 — shipped dark-ready, D1) New `_schedule_atlas_items ` invoked inside `_schedule_collect` (`schedule.zsh:378`, alongside `_schedule_parse_status` + `_schedule_teach_items`): - Probe capability once (session-cached, e.g. `_FLOW_ATLAS_HAS_AGENDA`), mirroring `_FLOW_ATLAS_HAS_SCHEDULE`. - Call `_flow_atlas_json agenda "$window"`; map each record to the engine's `date|label|type|project|recurrence|source` shape (`source=atlas`). - **Absent/older atlas → silent no-op**, engine falls back to `.STATUS` + teach only. No behavior change without atlas. +- **Deliberately dark this cycle:** real atlas has no `agenda` command yet (Track B unbuilt) — this ships as tested, capability-probed, inert code, same posture as `schedule push` today. Zero user-visible payoff until atlas B ships; zero rework required when it does. + +**Test simulation of both atlas states (D15/D16):** +- **Absent:** override the capability flag (`_FLOW_ATLAS_HAS_AGENDA` / `_flow_has_atlas`) directly — `FLOW_ATLAS_ENABLED=no` alone is insufficient (memory `capture-real-agenda-output-for-docs`). +- **Present:** a stub `atlas` shim placed on `PATH` returning `tests/fixtures/atlas-agenda-stub.json` (real atlas can't be used — it lacks the command). The fixture's shape is asserted against what `docs/ATLAS-CONTRACT.md` documents inside `test-atlas-contract.zsh`, so a contract edit without a fixture update fails loudly instead of drifting silently. ### 3.5 Contract (G5) -Add to `docs/ATLAS-CONTRACT.md` (bump contract version): `atlas task list --format json`, `atlas agenda [today|week|--all] --format json`, marked **proposed** until atlas ships them (same status `schedule push` carries today). +Add to `docs/ATLAS-CONTRACT.md` (bump contract version): `atlas task list --format json`, `atlas agenda [today|week|--all] --format json`, marked **proposed** until atlas ships them (same status `schedule push` carries today). **The `.STATUS`-ingestion mechanism is explicitly recorded as atlas's own open question (D4) — not designed here**, to respect the §7/D7 scope boundary. + +### 3.6 `.STATUS` template + enforcer (G6, D9/D11) +flow-cli-scoped only (not an ecosystem standard — that's deferred). Reuses the `scripts/check-math.zsh` pattern: +- **Template** `templates/.STATUS.template` — canonical structure with inline comments: header fields (`## Project/Type/Status/Phase/Focus/Priority/Progress`), the `## Schedule:` grammar, `## daily_goal:`, `## Active Worktrees`, session-log convention. Documents the dual dialect (`## Field:` and `field:`) that `_flow_status_field` (§3.1) supports. +- **Enforcer** `scripts/check-status.zsh` — validates required fields present, `Progress` is int 0–100, `Schedule` lines match grammar, `Status` in the allowed set. **Warn-only (D11): exit 0, prints violations** — does NOT block commits. Flip to blocking only in a follow-up once flow-cli's own `.STATUS` + the template are confirmed compliant. +- **Wiring:** add a `.STATUS`-keyed entry to `lint-staged` (extensionless filename match, not a glob) alongside `check-math.zsh`'s existing entry; new `tests/test-status-schema.zsh` (schema validity + template-parses-clean). ## 4. Files touched - `lib/core.zsh` — new shared accessors. - `lib/schedule.zsh` — `_schedule_atlas_items` + call site in `_schedule_collect`. - `commands/agenda.zsh` — route through shared engine. -- `commands/morning.zsh`, `commands/adhd.zsh` — bug fix + use shared accessors. +- `commands/morning.zsh`, `commands/adhd.zsh` — `$path` pre-step fix + use shared accessors. - `commands/dash.zsh`, `commands/capture.zsh` — migrate to shared accessors. - `lib/atlas-bridge.zsh` — capability probe + `agenda` JSON passthrough for `at`. -- `docs/ATLAS-CONTRACT.md` — proposed contract entries. +- `docs/ATLAS-CONTRACT.md` — proposed contract entries (`.STATUS` ingestion left open). +- `templates/.STATUS.template` (NEW) — canonical `.STATUS` structure. +- `scripts/check-status.zsh` (NEW) — warn-only schema enforcer. +- `tests/fixtures/atlas-agenda-stub.json` (NEW) — atlas-present test fixture, shape-asserted against the contract. --- ## 5. Testing plan *(refined — first-class, red-first, in-tree)* -Run everything in the worktree via `./tests/run-all.sh`; new suites registered there. Judge failures against the `dev` baseline. +Run everything in the worktree via `./tests/run-all.sh`; new suites registered there. Judge failures against the `dev` baseline. **Verification bar (Axis 5): the FULL suite, run independently by the reviewer, at every phase gate and before merge — never a sampled subset, never the agent's self-reported summary.** + +### 5.0 Characterization (parity guard, Axis 2 — precedes 5.1) +| Suite | Asserts | +|---|---| +| `tests/test-status-field-parity.zsh` (new, RED against current code until it captures it, then GREEN pre-refactor) | Snapshots each existing call site's CURRENT output (`_dash_get_status_field`, the 3 inline greps, `_flow_where_fallback`) across 4 `.STATUS` dialect + missing-field fixtures; **must stay green after §3.1's consolidation** — a diff here means silent behavior change, not a passing refactor | ### 5.1 Unit | Suite (new/extended) | Asserts | @@ -70,6 +99,8 @@ Run everything in the worktree via `./tests/run-all.sh`; new suites registered t | `tests/test-project-path-resolver.zsh` (new) | `_flow_resolve_project_path` finds apps, quarto/manuscripts, presentations; emits `project_path=` never `path=`; unknown project → empty | | `tests/test-suggest-project.zsh` (new) | `_flow_suggest_project` prefers active + high-priority; deterministic tie-break; empty registry → graceful | | `tests/test-schedule-atlas-source.zsh` (new) | `_schedule_atlas_items` maps stub JSON → normalized records; malformed JSON → no-op not crash; capability-absent → 0 records | +| `tests/test-path-bug-fix.zsh` (new, pre-step §3.0) | RED first: proves `next`/`morning` focus/progress is blank/wrong today; GREEN after the isolated fix | +| `tests/test-status-schema.zsh` (new, §3.6) | `check-status.zsh` schema validity; `templates/.STATUS.template` parses clean; warn-only (exit 0) on a malformed fixture, prints violations | ### 5.2 Integration (cross-command data flow) | Suite | Asserts | @@ -80,12 +111,12 @@ Run everything in the worktree via `./tests/run-all.sh`; new suites registered t ### 5.3 Dependency / contract | Suite | Asserts | |---|---| -| `tests/test-atlas-contract.zsh` (extend) | `atlas task list --format json` + `atlas agenda --format json` shapes pinned; flow-cli degrades to no-op when atlas lacks them (proven against a stub that omits the commands) | +| `tests/test-atlas-contract.zsh` (extend) | `atlas task list --format json` + `atlas agenda --format json` shapes pinned; flow-cli degrades to no-op when atlas lacks them (proven against a stub that omits the commands); **`tests/fixtures/atlas-agenda-stub.json` shape asserted against the documented contract (D16) — a contract edit without a fixture update fails this test** | ### 5.4 E2E + dogfood | Suite | Asserts | |---|---| -| `tests/e2e-agenda-atlas.zsh` (new) | sandbox + stub `atlas agenda` returning 2 tasks + `_flow_has_atlas` override → merged view shows research deadlines alongside `.STATUS` schedule (capture real output per memory `capture-real-agenda-output-for-docs`) | +| `tests/e2e-agenda-atlas.zsh` (new) | sandbox + **capability-flag override** for atlas-absent (not `FLOW_ATLAS_ENABLED=no` alone, D15) + a **stub `atlas` shim on PATH** for atlas-present returning the shared fixture → merged view shows research deadlines alongside `.STATUS` schedule (capture real output per memory `capture-real-agenda-output-for-docs`) | | dogfood (manual, quoted in PR) | run `agenda` / `dash` / `morning` against the real repo set; confirm the `$path` fix restores focus/progress; confirm no regression when atlas agenda unavailable | ### 5.5 Regression guards @@ -106,7 +137,9 @@ Inventory current state before editing (per doc-currency rule); update only genu | `docs/guides/AGENDA-SCHEDULE-GUIDE.md` | new data source diagram; merged research+dev example with real captured output | | `docs/reference/MASTER-API-REFERENCE.md` | document `_flow_status_field`, `_flow_resolve_project_path`, `_flow_suggest_project`, `_schedule_atlas_items` | | `docs/help/QUICK-REFERENCE.md` | one-line: agenda spans research + dev | -| `docs/ATLAS-CONTRACT.md` | proposed `atlas task`/`atlas agenda` entries + contract version bump | +| `docs/ATLAS-CONTRACT.md` | proposed `atlas task`/`atlas agenda` entries + contract version bump; `.STATUS` ingestion noted as atlas's open question (D4) | +| `templates/.STATUS.template` inline comments (new) | canonical structure + dialect documentation (§3.6) | +| `CONTRIBUTING.md` | note the `.STATUS` template + `check-status.zsh` (warn-only, D11) in the contributor checklist | | `CHANGELOG.md` + `docs/CHANGELOG.md` | `[Unreleased]` — mirrored (both files) | | `CLAUDE.md` | refresh test-file/suite counts after new suites land | | `mkdocs.yml` | no nav change (extends existing pages); `mkdocs build --strict` must pass | @@ -124,15 +157,19 @@ Doc-scorer (`commands/docs/sync.md`, threshold ≥3): guide ✅, refcard ✅, co ## 8. Verification (end-to-end) 1. `source flow.plugin.zsh` — clean load, no errors. -2. `./tests/run-all.sh` — all suites green (1 expected interactive/tmux timeout); new suites pass; counts updated in CLAUDE.md. -3. Dogfood with **no atlas**: `agenda`, `dash`, `morning` identical to pre-change (graceful degradation proven). -4. Dogfood with **stub atlas agenda**: research deadlines appear merged; `$path` fix restores focus/progress in `morning`/`next`. -5. `mkdocs build --strict` — 0 warnings. -6. `/craft:docs:lint` — clean. - -## 9. Rollout - -- Branch: `feature/planning-coordination` (worktree off `dev`). +2. `./tests/run-all.sh` — **run independently by the reviewer** (Axis 5, not the agent's self-report), all suites green (1 expected interactive/tmux timeout); new suites pass; counts updated in CLAUDE.md. +3. Characterization suite (`test-status-field-parity.zsh`) unchanged pre/post-refactor — parity proven, not just new-behavior coverage. +4. `check-status.zsh` — warn-only (exit 0) on a malformed `.STATUS` fixture, prints violations; clean (no violations) on the template and on flow-cli's own `.STATUS`. +5. Dogfood with **no atlas** (capability-flag override, D15): `agenda`, `dash`, `morning` identical to pre-change (graceful degradation proven). +6. Dogfood with **stub atlas shim on PATH** (D15): research deadlines appear merged; `$path` fix restores focus/progress in `morning`/`next`. +7. `mkdocs build --strict` — 0 warnings. +8. `/craft:docs:lint` — clean. + +## 9. Rollout & execution model + +- Branch: `feature/planning-coordination` (worktree off `dev`); `npm install` run in the worktree first so husky/lint-staged (prettier, `check-math.zsh`, the new `check-status.zsh`) fire on every commit (D12 — the worktree has no `node_modules` by default). +- **Execution is phase-gated (D10), not one-shot:** pre-step ($path fix) → Phase 1 (characterization + refactor) → Phase 2 (atlas source + contract) → Phase 3 (`.STATUS` template + enforcer) → Phase 4 (docs). Each phase stops for an independent full-suite review before the next proceeds. +- **Failure recovery (D13/D14):** a phase that fails review gets fixed forward via follow-up instruction to the same agent (context retained), re-verified; capped at 2 attempts. On a 2nd failure, execution stops and the finding is reported directly — no silent retry, no skip-ahead to the next phase. - Ship Track A independently if Track C's atlas dependency isn't ready — the refactor + bug fix stand alone. - Release as v7.14.0 once C's contract is pinned (C degrades safely even before atlas ships). diff --git a/lib/atlas-bridge.zsh b/lib/atlas-bridge.zsh index ce7db201a..3dcb0f6bc 100644 --- a/lib/atlas-bridge.zsh +++ b/lib/atlas-bridge.zsh @@ -367,38 +367,23 @@ _flow_get_project() { # - Search order: root, dev-tools, r-packages/*, research, teaching, quarto # - Uses project_path/proj_status to avoid ZSH reserved names # ============================================================================= +# Delegates to the shared resolver (_flow_resolve_project_path, lib/core.zsh) +# which merges this function's original search dirs with +# _dash_find_project_path's fuller taxonomy (adds apps + quarto/manuscripts + +# quarto/presentations). Kept as a thin wrapper so _flow_get_project's output +# contract (name=/project_path=/proj_status=) is unchanged for callers. _flow_get_project_fallback() { local name="$1" - local path + local resolved project_path - # Try exact match first - if [[ -d "$FLOW_PROJECTS_ROOT/$name" ]]; then - path="$FLOW_PROJECTS_ROOT/$name" - else - # Search in common subdirectories - local search_dirs=( - "$FLOW_PROJECTS_ROOT/dev-tools" - "$FLOW_PROJECTS_ROOT/r-packages/active" - "$FLOW_PROJECTS_ROOT/r-packages/stable" - "$FLOW_PROJECTS_ROOT/research" - "$FLOW_PROJECTS_ROOT/teaching" - "$FLOW_PROJECTS_ROOT/quarto" - ) - for dir in "${search_dirs[@]}"; do - if [[ -d "$dir/$name" ]]; then - path="$dir/$name" - break - fi - done - fi + resolved=$(_flow_resolve_project_path "$name") || return 1 + eval "$resolved" + [[ -z "$project_path" ]] && return 1 - if [[ -n "$path" ]]; then - echo "name=\"$name\"" - echo "project_path=\"$path\"" # Avoid 'path' - conflicts with ZSH's PATH-tied variable - echo "proj_status=\"active\"" # Avoid 'status' - conflicts with ZSH builtin - return 0 - fi - return 1 + echo "name=\"$name\"" + echo "project_path=\"$project_path\"" # Avoid 'path' - conflicts with ZSH's PATH-tied variable + echo "proj_status=\"active\"" # Avoid 'status' - conflicts with ZSH builtin + return 0 } # ============================================================================= @@ -849,20 +834,26 @@ _flow_where_fallback() { echo "📁 Project: $project" - # Show status if available - local status_file + # Show status if available. Reads via the shared accessor + # (_flow_status_field, lib/core.zsh) — NOTE: this also fixes a pre-existing + # crash. The prior inline version declared `local status=...`, but `status` + # is a zsh read-only special variable even as a function-local, so this + # line always errored and Status:/Focus: never actually printed. Found + # while characterizing (tests/test-status-field-parity.zsh) ahead of this + # migration; fixed as an unavoidable side effect of touching this line. + local status_dir for search_dir in "$PWD" "$FLOW_PROJECTS_ROOT/$project"; do if [[ -f "$search_dir/.STATUS" ]]; then - status_file="$search_dir/.STATUS" + status_dir="$search_dir" break fi done - if [[ -f "$status_file" ]]; then - local status=$(command grep -m1 "^## Status:" "$status_file" | command cut -d: -f2 | tr -d ' ') - local focus=$(command grep -m1 "^## Focus:" "$status_file" | command cut -d: -f2-) + if [[ -n "$status_dir" ]]; then + local proj_status=$(_flow_status_field "$status_dir" "Status" | tr -d ' ') + local focus=$(_flow_status_field "$status_dir" "Focus") - [[ -n "$status" ]] && echo " Status: $status" + [[ -n "$proj_status" ]] && echo " Status: $proj_status" [[ -n "$focus" ]] && echo " Focus: $focus" fi } diff --git a/lib/core.zsh b/lib/core.zsh index 38b0db0c7..8cefc0d54 100644 --- a/lib/core.zsh +++ b/lib/core.zsh @@ -839,3 +839,235 @@ _flow_tty_handoff_cleanup() { printf '\e[?1004l\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?2004l' > /dev/tty while read -t 0.05 -k 1 _flow_discard 2>/dev/null; do : ; done < /dev/tty } + +# ============================================================================ +# PLANNING ACCESSORS (shared .STATUS/project helpers — SPEC-planning- +# coordination-2026-07-01 §3.1) +# ============================================================================ +# Consolidates 4 divergent .STATUS field readers, 2 project-path resolvers, +# and 5 project-suggestion scans into one implementation each. Existing +# per-file wrappers (_dash_get_status_field, _dash_find_project_path, +# _flow_get_project_fallback) now delegate here to avoid touching their many +# internal call sites; see tests/test-status-field-parity.zsh for the +# byte-parity guard on the field reader. + +# ============================================================================= +# Function: _flow_status_field +# Purpose: Read one field from a project's .STATUS file (shared accessor) +# ============================================================================= +# Arguments: +# $1 - (required) Project root directory (NOT the .STATUS file path itself — +# this function appends /.STATUS) +# $2 - (required) Field name, e.g. "Focus", "Progress", "Status" +# +# Returns: +# 0 - .STATUS file found (value may still be empty if the field is absent) +# 1 - .STATUS file not found +# +# Output: +# stdout - the field's value (leading whitespace trimmed), or empty +# +# Example: +# _flow_status_field "$project_path" "Focus" +# _flow_status_field "$project_path" "Progress" | tr -d '%' # site strips % +# +# Notes: +# - Handles both the "## Field:" markdown dialect and the plain "field:" +# YAML-ish dialect (matches field name case-sensitively in both). +# - Does NOT strip '%' — callers that need a bare number (e.g. Progress) +# strip it themselves, same as today (_dash_get_project_progress, +# morning.zsh). A universal strip here would corrupt any other field +# that legitimately contains '%' (e.g. "## Focus: hit 80% coverage"). +# - "Status"/"status" gets the same synonym normalization +# _dash_get_status_field has always applied (lowercase, spaces removed, +# underreview/inprogress/wip -> active, onhold -> paused) — required so +# _dash_get_status_field can delegate here without changing behavior. +# ============================================================================= +_flow_status_field() { + local root="$1" + local field="$2" + local file="$root/.STATUS" + local value="" line + + [[ ! -f "$file" ]] && return 1 + + while IFS= read -r line; do + if [[ "$line" == "## ${field}:"* ]]; then + value="${line#*: }" + value="${value#"${value%%[![:space:]]*}"}" + break + fi + if [[ "$line" == "${field}:"* ]]; then + value="${line#*: }" + value="${value#"${value%%[![:space:]]*}"}" + break + fi + done < "$file" + + if [[ "$field" == "Status" || "$field" == "status" ]]; then + value="${value:l}" + value="${value// /}" + case "$value" in + underreview) value="active" ;; + inprogress) value="active" ;; + wip) value="active" ;; + onhold) value="paused" ;; + esac + fi + + echo "$value" + return 0 +} + +# ============================================================================= +# Function: _flow_resolve_project_path +# Purpose: Find a project's absolute path by name (shared resolver) +# ============================================================================= +# Arguments: +# $1 - (required) Project name +# +# Returns: +# 0 - Project found +# 1 - Project not found +# +# Output: +# stdout - a single shell-evaluable line: project_path="/abs/path" +# (never `path=` — that collides with ZSH's PATH-tied array) +# +# Example: +# local out project_path +# if out=$(_flow_resolve_project_path "flow-cli"); then +# eval "$out" +# echo "Found at: $project_path" +# fi +# +# Environment: +# FLOW_PROJECTS_ROOT - Base directory for projects +# +# Notes: +# - Merges _dash_find_project_path's category taxonomy (dev-tools, apps, +# r-packages/active|stable, research, teaching, quarto/manuscripts, +# quarto/presentations) with _flow_get_project_fallback's exact-root-match +# and generic quarto/ fallback. +# - Search order matches _dash_find_project_path's original order (root +# checked last, after the category subdirs) since that is the +# better-tested, more complete taxonomy; the generic "quarto/" fallback +# is appended at the very end as a pure superset for projects that live +# directly under quarto/ rather than manuscripts/ or presentations/. +# ============================================================================= +_flow_resolve_project_path() { + local name="$1" + local dir + + local -a search_dirs=( + "$FLOW_PROJECTS_ROOT/dev-tools" + "$FLOW_PROJECTS_ROOT/apps" + "$FLOW_PROJECTS_ROOT/r-packages/active" + "$FLOW_PROJECTS_ROOT/r-packages/stable" + "$FLOW_PROJECTS_ROOT/research" + "$FLOW_PROJECTS_ROOT/teaching" + "$FLOW_PROJECTS_ROOT/quarto/manuscripts" + "$FLOW_PROJECTS_ROOT/quarto/presentations" + "$FLOW_PROJECTS_ROOT" + "$FLOW_PROJECTS_ROOT/quarto" + ) + + for dir in "${search_dirs[@]}"; do + if [[ -d "$dir/$name" ]]; then + echo "project_path=\"$dir/$name\"" + return 0 + fi + done + + return 1 +} + +# ============================================================================= +# Function: _flow_suggest_project +# Purpose: Suggest a project to work on (shared active/priority/random scan) +# ============================================================================= +# Arguments: +# $1 - (optional) Strategy [default: "active"]: +# active - first active project +# active-with-focus - first active project with a non-empty Focus +# priority - first active project with Priority 1/P1 +# random-active - random pick from the active list (or the +# first 5 of all projects if none are active) +# +# Returns: +# 0 - A project was found +# 1 - No qualifying project found +# +# Output: +# stdout - the suggested project name, or empty +# +# Example: +# suggested=$(_flow_suggest_project active-with-focus) +# suggested=$(_flow_suggest_project priority) +# +# Notes: +# - NOT byte-parity guarded like _flow_status_field — the 5 call sites this +# replaces (dash.zsh:181/:1139, morning.zsh:144, adhd.zsh next/js) +# genuinely differ in selection logic; each strategy is pinned by its own +# unit test in tests/test-suggest-project.zsh instead. +# - Uses _flow_list_projects("active") when available, falling back to the +# unfiltered list (matching existing callers' fallback pattern) since the +# filesystem fallback ignores the status filter param. +# ============================================================================= +_flow_suggest_project() { + local strategy="${1:-active}" + local projects + projects=$(_flow_list_projects "active" 2>/dev/null) + [[ -z "$projects" ]] && projects=$(_flow_list_projects 2>/dev/null) + [[ -z "$projects" ]] && return 1 + + if [[ "$strategy" == "random-active" ]]; then + local pick + pick=$(echo "$projects" | sort -R | head -1) + [[ -z "$pick" ]] && return 1 + echo "$pick" + return 0 + fi + + # NOTE: resolved/project_path/pstatus/focus/priority are declared ONCE + # here, outside the loop — redeclaring `local` on an already-local + # variable on every iteration of a `while ... done <<< ...` loop triggers + # a reproducible zsh quirk where the PREVIOUS iteration's value gets + # echoed to stdout when the variable is redeclared. See + # tests/test-suggest-project.zsh discovery notes / MEMORY zsh-gotchas. + local project resolved project_path pstatus focus priority + while IFS= read -r project; do + [[ -z "$project" ]] && continue + + resolved=$(_flow_resolve_project_path "$project") || continue + eval "$resolved" + [[ -z "$project_path" || ! -f "$project_path/.STATUS" ]] && continue + + pstatus=$(_flow_status_field "$project_path" "Status") + [[ "$pstatus" != "active" ]] && continue + + case "$strategy" in + active) + echo "$project" + return 0 + ;; + active-with-focus) + focus=$(_flow_status_field "$project_path" "Focus") + if [[ -n "$focus" ]]; then + echo "$project" + return 0 + fi + ;; + priority) + priority=$(_flow_status_field "$project_path" "Priority") + priority="${priority// /}" + if [[ "$priority" == "1" || "$priority" == "P1" ]]; then + echo "$project" + return 0 + fi + ;; + esac + done <<< "$projects" + + return 1 +} diff --git a/lib/schedule.zsh b/lib/schedule.zsh index 83227010e..bfc84d29a 100644 --- a/lib/schedule.zsh +++ b/lib/schedule.zsh @@ -51,6 +51,10 @@ typeset -g _SCHEDULE_CACHE_TIME=0 # Atlas `schedule` subcommand capability probe (cached per session). typeset -g _FLOW_ATLAS_HAS_SCHEDULE="" +# Atlas `agenda` subcommand capability probe (cached per session) — dark-ready +# read source, SPEC-planning-coordination-2026-07-01 §3.4. +typeset -g _FLOW_ATLAS_HAS_AGENDA="" + # ============================================================================ # DATE HELPERS # ============================================================================ @@ -443,8 +447,42 @@ _schedule_collect() { fi done < <(_flow_list_projects) + # 3. Atlas agenda source (dark-ready; silent no-op without atlas — SPEC §3.4) + while IFS= read -r rec; do + [[ -z "$rec" ]] && continue + local -a af=("${(@s:|:)rec}") + rtype="${af[3]}" + if [[ -n "$category" ]]; then + local aproj_path aproj_cat="" + aproj_path=$(_dash_find_project_path "${af[4]}") + [[ -n "$aproj_path" ]] && aproj_cat=$(_dash_detect_category "$aproj_path") + _schedule_category_match "$category" "$rtype" "$aproj_cat" || continue + fi + out+=("$rec") + done < <(_schedule_atlas_items "$window") + + # Dedupe on (date, label, project) — fields 1/2/4. With only .STATUS + + # teach-config as sources, duplicates were structurally impossible; the + # atlas source (3.) makes them possible for the first time (the same + # deadline tracked both locally and in atlas), so this is new with Track C. + # First occurrence wins — .STATUS/teach-config are collected before atlas, + # so a local record always takes priority over an atlas duplicate. local records="" - (( ${#out[@]} > 0 )) && records=$(print -rl -- "${out[@]}") + if (( ${#out[@]} > 0 )); then + local -A seen_keys=() + local -a deduped=() + local dkey + local -a df + for rec in "${out[@]}"; do + df=("${(@s:|:)rec}") + dkey="${df[1]}|${df[2]}|${df[4]}" + if (( ! ${+seen_keys[$dkey]} )); then + seen_keys[$dkey]=1 + deduped+=("$rec") + fi + done + records=$(print -rl -- "${deduped[@]}") + fi _SCHEDULE_CACHE_RECORDS="$records" _SCHEDULE_CACHE_KEY="$cache_key" @@ -537,22 +575,37 @@ _schedule_render_line() { # ============================================================================= # Function: _schedule_window_records # Purpose: The shared surface pipeline — collect, window-filter, sort, and drop -# holidays — in one place (dash UPCOMING, morning/today/week, counts). +# holidays — in one place (dash UPCOMING, morning/today/week, counts, +# and `agenda` — SPEC-planning-coordination-2026-07-01 §3.3). # Arguments: # $1 - window in days [default: SCHEDULE_DEFAULT_WINDOW] +# $2 - category filter [default: "" (none)] — forwarded to _schedule_collect +# $3 - keep_holidays (0|1) [default: 0 — drop holidays, existing behavior] # Output: # stdout - the resulting record stream (empty when nothing is due) # Notes: # - Returns 0 with no output when the engine is unavailable or nothing matches, # so callers can `records=$(_schedule_window_records …); [[ -z … ]] && return`. +# - $2/$3 are additive: every pre-existing caller passes only $1, so +# category defaults to none and holidays are still dropped — unchanged +# behavior for dash/morning/today/week/counts. `agenda --all` is the one +# caller that passes keep_holidays=1. # ============================================================================= _schedule_window_records() { local window="${1:-$SCHEDULE_DEFAULT_WINDOW}" + local category="${2:-}" + local keep_holidays="${3:-0}" typeset -f _schedule_collect >/dev/null 2>&1 || return 0 - _schedule_collect "$window" \ - | _schedule_filter_window "$window" \ - | _schedule_sort \ - | _schedule_drop_holidays + if (( keep_holidays )); then + _schedule_collect "$window" "$category" \ + | _schedule_filter_window "$window" \ + | _schedule_sort + else + _schedule_collect "$window" "$category" \ + | _schedule_filter_window "$window" \ + | _schedule_sort \ + | _schedule_drop_holidays + fi } # ============================================================================= @@ -659,3 +712,57 @@ _flow_schedule_to_atlas() { _flow_atlas_async schedule push --format=json --data="$json" return 0 } + +# ============================================================================= +# Function: _schedule_atlas_items +# Purpose: Read atlas-tracked deadlines (Task.dueDate) for a window (dark-ready +# read source, opportunistic + capability-detected). See +# docs/ATLAS-CONTRACT.md's `atlas agenda` section. +# ============================================================================= +# Arguments: +# $1 - window in days (same look-ahead window the engine itself uses) +# +# Returns: +# 0 - Always (silent no-op on any absence: atlas, `agenda` capability, jq) +# +# Output: +# stdout - record stream in the engine's date|label|type|project|recurrence|source +# shape, with `source` hardcoded to `atlas` (not read from the JSON) +# +# Notes: +# - DARK THIS CYCLE: no atlas release implements `agenda` yet (Track B, +# tracked separately). This is tested, inert code — same posture as +# `atlas schedule push` before it shipped. +# - Capability probe (`atlas agenda --help`) is cached in +# _FLOW_ATLAS_HAS_AGENDA, mirroring _FLOW_ATLAS_HAS_SCHEDULE. +# - Requires `jq` to parse the JSON array response; absence of jq is just +# another silent-no-op path, same as atlas absence — no hard dependency +# added to the engine (lib/schedule.zsh stays "pure ZSH, works fully +# without atlas" for everyone who doesn't have jq either). +# - How atlas populates Task.dueDate (including whether it ever reads +# .STATUS itself) is NOT this function's concern — that is atlas's own +# open design question (D4), documented as such in ATLAS-CONTRACT.md. +# ============================================================================= +_schedule_atlas_items() { + local window="$1" + + _flow_has_atlas || return 0 + command -v jq >/dev/null 2>&1 || return 0 + + if [[ -z "$_FLOW_ATLAS_HAS_AGENDA" ]]; then + if atlas agenda --help >/dev/null 2>&1; then + _FLOW_ATLAS_HAS_AGENDA="yes" + else + _FLOW_ATLAS_HAS_AGENDA="no" + fi + fi + [[ "$_FLOW_ATLAS_HAS_AGENDA" == "yes" ]] || return 0 + + local json + json=$(_flow_atlas_json agenda "$window") + [[ -z "$json" ]] && return 0 + + print -r -- "$json" \ + | jq -r '.[]? | "\(.date)|\(.label)|\(.type)|\(.project)|\(.recurrence)|atlas"' 2>/dev/null + return 0 +} diff --git a/package.json b/package.json index bcddcc7f2..954a2de47 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,9 @@ ], "*.qmd": [ "zsh scripts/check-math.zsh" + ], + ".STATUS": [ + "zsh scripts/check-status.zsh" ] } } diff --git a/scripts/check-status.zsh b/scripts/check-status.zsh new file mode 100755 index 000000000..85af628bf --- /dev/null +++ b/scripts/check-status.zsh @@ -0,0 +1,164 @@ +#!/usr/bin/env zsh +# scripts/check-status.zsh — Standalone .STATUS schema validator (WARN-ONLY) +# Used by lint-staged (pre-commit) and callable standalone. Model: +# scripts/check-math.zsh — same shape, different exit-code contract. +# +# SPEC-planning-coordination-2026-07-01 §3.6 (D9/D11): flow-cli-scoped only +# (not an ecosystem-wide standard). Validates required header fields, +# Progress (integer 0-100), Status (allowed set), and ## Schedule: grammar. +# +# D11 — HARD CONSTRAINT: this script ALWAYS exits 0. It prints violations, +# it never blocks a commit. Flip to blocking only in a future follow-up once +# flow-cli's own .STATUS + the template are confirmed compliant across the +# ecosystem — not this cycle. +# +# Usage: zsh scripts/check-status.zsh file1/.STATUS file2/.STATUS ... +# Exit: ALWAYS 0 + +setopt LOCAL_OPTIONS EXTENDED_GLOB + +local script_dir="${0:A:h}" +local project_root="${script_dir:h}" + +# Only need _flow_status_field — lib/core.zsh is self-contained (no module +# guard, no dependency on other lib/*.zsh files), same lightweight-source +# pattern check-math.zsh uses for teach-deploy-enhanced.zsh. +source "${project_root}/lib/core.zsh" 2>/dev/null + +if ! typeset -f _flow_status_field >/dev/null 2>&1; then + print -P "%F{red}ERROR:%f Could not load _flow_status_field from lib/core.zsh" + print -P "%F{yellow}(warn-only: not blocking despite the internal error)%f" + exit 0 +fi + +if (( $# == 0 )); then + print -P "%F{yellow}Usage:%f zsh scripts/check-status.zsh <.STATUS file> ..." + exit 0 +fi + +typeset -a REQUIRED_FIELDS=(Project Type Status Focus Phase Priority Progress) +typeset -a ALLOWED_STATUS=(active paused archived blocked) +typeset -gi TOTAL_ISSUES=0 + +# Normalize a Status value the same way _flow_status_field does internally, +# so a synonym it already accepts ("In Progress", "WIP", ...) is not flagged +# as a violation here. +_check_status_normalize() { + local v="${1:l}" + v="${v// /}" + case "$v" in + underreview|inprogress|wip) v="active" ;; + onhold) v="paused" ;; + esac + print -r -- "$v" +} + +_check_status_file() { + local file="$1" + local root="${file:h}" + local tmp_status_dir="" + + # _flow_status_field expects a ROOT dir and always reads "/.STATUS" + # (lib/core.zsh's established contract — do not special-case it here, + # that would risk the Phase 1 parity guard for every other caller). + # Real .STATUS files satisfy this directly. But this script is also + # asked to validate templates/.STATUS.template — a file that documents + # the shape without being named ".STATUS" itself. For any file NOT + # literally named ".STATUS", stage a throwaway copy under that exact + # name so _flow_status_field's contract is met without duplicating its + # parsing logic here. + if [[ "${file:t}" != ".STATUS" ]]; then + tmp_status_dir=$(mktemp -d) + cp "$file" "$tmp_status_dir/.STATUS" + root="$tmp_status_dir" + fi + + # NOTE: all locals used inside the loops below are declared ONCE here, + # not redeclared per iteration — redeclaring `local` on an already-local + # variable inside a loop is a reproducible zsh quirk that echoes the + # previous iteration's value to stdout (see lib/core.zsh's + # _flow_suggest_project for the first discovery of this). `status` is + # additionally a zsh READ-ONLY special variable (even function-local — + # see lib/atlas-bridge.zsh's _flow_where_fallback fix in Phase 1), hence + # `proj_status` instead. + local field val progress pnum proj_status norm + + # ---- Required fields present ---- + for field in "${REQUIRED_FIELDS[@]}"; do + val=$(_flow_status_field "$root" "$field") + if [[ -z "$val" ]]; then + print -P "%F{yellow}WARN%f $file — missing required field: ## ${field}:" + (( TOTAL_ISSUES++ )) + fi + done + + # ---- Progress: integer 0-100 ---- + progress=$(_flow_status_field "$root" "Progress") + if [[ -n "$progress" ]]; then + pnum="${progress//%/}" + if [[ ! "$pnum" == <-> ]] || (( pnum < 0 || pnum > 100 )); then + print -P "%F{yellow}WARN%f $file — Progress not an integer 0-100: '$progress'" + (( TOTAL_ISSUES++ )) + fi + fi + + # ---- Status: allowed set (synonym-tolerant) ---- + proj_status=$(_flow_status_field "$root" "Status") + if [[ -n "$proj_status" ]]; then + norm=$(_check_status_normalize "$proj_status") + if (( ! ${ALLOWED_STATUS[(Ie)$norm]} )); then + print -P "%F{yellow}WARN%f $file — Status '$proj_status' not in allowed set (active|paused|archived|blocked, or a recognized synonym)" + (( TOTAL_ISSUES++ )) + fi + fi + + # ---- ## Schedule: grammar ---- + # Mirrors _schedule_parse_status's grammar (lib/schedule.zsh): a line the + # real parser can't match is silently skipped there (invisible, not + # fatal) — this is exactly the class of mistake worth flagging here. + local in_section=0 line body when label + local -a parts + local -i lineno=0 + while IFS= read -r line; do + (( lineno++ )) + if [[ "$line" == "## Schedule:"* ]]; then + in_section=1 + continue + fi + if (( in_section )) && [[ "$line" == "## "* ]]; then + in_section=0 + continue + fi + (( in_section )) || continue + + [[ "$line" == "-"* ]] || continue + body="${line#-}" + body="${body#"${body%%[![:space:]]*}"}" + [[ -z "$body" ]] && continue + + parts=("${(@s:|:)body}") + when="${parts[1]//[[:space:]]/}" + label="${parts[2]:-}" + label="${label#"${label%%[![:space:]]*}"}" + + if [[ -z "$when" || -z "$label" ]] \ + || { [[ "$when" != [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]] && [[ "$when" != weekly:* ]] ; }; then + print -P "%F{yellow}WARN%f $file:$lineno — Schedule line doesn't match grammar (expected '- YYYY-MM-DD | label | type' or '- weekly: | label | type'): '$line'" + (( TOTAL_ISSUES++ )) + fi + done < "$file" + + [[ -n "$tmp_status_dir" ]] && rm -rf "$tmp_status_dir" +} + +local file +for file in "$@"; do + [[ -f "$file" ]] || continue + _check_status_file "$file" +done + +if (( TOTAL_ISSUES > 0 )); then + print -P "\n%F{yellow}${TOTAL_ISSUES} .STATUS schema issue(s) found (warn-only — not blocking, D11).%f" +fi + +exit 0 diff --git a/templates/.STATUS.template b/templates/.STATUS.template new file mode 100644 index 000000000..8894f4ef4 --- /dev/null +++ b/templates/.STATUS.template @@ -0,0 +1,70 @@ +# - +# +# This is a flow-cli .STATUS file. It drives `dash`, `morning`, `next`, +# `agenda`, and `flow goal` — keep the header fields below accurate; flow-cli +# reads them directly (no external tools required). +# +# Dual dialect: every field below may be written either as markdown +# ("## Field: value", shown here) or as plain YAML-ish ("field: value", no +# "## " prefix, lowercase). _flow_status_field (lib/core.zsh) reads both — +# pick whichever your project already uses and stay consistent within a file. +# +# flow-cli-scoped convention (SPEC-planning-coordination-2026-07-01 §3.6) — +# this is NOT an ecosystem-wide standard. scripts/check-status.zsh validates +# this shape as a warn-only pre-commit check (D11): it prints violations, it +# never blocks a commit. + +## Project: +## Type: +## Status: active +## Focus: +## Phase: Planning +## Priority: 2 +## Progress: 0 + +# --- Optional fields -------------------------------------------------------- + +# Per-project daily win goal (flow-cli's dopamine features — `win`/`yay`). +# Overrides the global default when present. Must be a bare integer. +## daily_goal: 3 + +# --- Schedule (optional) ----------------------------------------------------- +# Forward-looking dated items surfaced by `agenda` / the dash UPCOMING +# section. One list item per line: +# +# - |