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/montageTwo 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.