Skip to content

workstream: to keep plan files out of a code repo, point plans_dir at _Workstreams/.plans/<repo>, never at _Workstreams/plans/

TL;DR.

The workstream CLI's per-repo plans_dir override is the supported way to store plan files in the notes vault instead of the code repo, but $WS_DIR/plans/ is already the sweep archive destination — using it makes sweep copy live plans onto themselves. Use a relative .plans/<repo> instead, which both the Python CLI and the TS harness extension resolve against $WS_DIR.

By default the workstream harness extension writes approved plans to <repo>/.workstream-plans/ (or legacy <repo>/.plans/ when it already exists). In a repo with no matching .gitignore entry that shows up as untracked cruft in git status on every commit.

The fix is a per-repo override in $WS_DIR/config.yaml (the project config inside _Workstreams/, not the ~/.config/workstream/config.yaml global pointer, which only holds workstreams_dir):

repos:
- path: ~/hatnote/montage
  name: montage
  plans_dir: .plans/montage

Two things that are easy to get wrong:

1. Do not use $WS_DIR/plans/. That directory is the sweep archive: _archive_plans() in commands/sweep.py does archive_dir = ws_dir / 'plans' and copies every live plan there under a mangled _compute_archive_name() filename. Pointing a live plans_dir at it means sweep reads and writes the same directory. The live-plan convention is .plans/<repo> (dot-prefixed, per-repo subdir), which is what the one pre-existing override in the config used.

2. name must be the git-toplevel basename. Resolution happens twice, in two independent implementations that must agree: Config.get_plans_dir() (Python, config.py) keys on resolve_repo(repo_name), and resolvePlansDir() (TypeScript, extensions/workstream.ts) re-implements the lookup with a hand-rolled line scanner over the same YAML, keyed on detectRepo(cwd) = basename of git rev-parse --show-toplevel. Both treat a non-absolute plans_dir as relative to $WS_DIR; the TS side only special-cases a leading /, so a ~/... value would resolve wrong there while working fine in Python. Keep it relative.

There is no ws subcommand for this — hand-edit the YAML. Verify both resolvers rather than just the CLI, since the TS one is what actually writes plans at plan-approval time:

python -c "from pathlib import Path; from workstream.config import load_config; c=load_config(); print(c.get_plans_dir('montage', Path.home()/'hatnote/montage'))"

and confirm the harness side by checking that a plan approved after the change lands in the new directory. Existing plan files are usually untracked in the code repo, so mv them into the new dir and rmdir the old one — nothing is lost, and the vault (which already versions the plans/ archive) starts tracking them.

No signals yet