Skip to main content
A project is the spine. Milestones group work inside it, tasks are the units, and both address their parent by slug or by display name.

Creating a project

Why it works. --path is what connects the project to a folder. Since 0.16.0 that folder is how base knows which project a file belongs to: touching a file inside it loads the project’s own rules, with no trigger needed. A project without a path still exists, it just has no way to notice you are working on it. base project list shows each project’s path, its parent and whether it is nested, and its next step with how old that step is. The gotcha. If you omit --path and the protocol configuration is not enabled, the project has no folder at all. base project list --unscoped finds these later, and they are usually a mistake rather than a choice.

Milestones and tasks

Why it works. A task carries a project edge and, optionally, a milestone edge. They are separate edges, which is why reassigning one leaves the other alone. Next rung. base task tag <slug> --add urgent attaches free-form labels, and base task list --label urgent --label backend narrows to tasks carrying all of them. Repeating --label intersects rather than unions.

Editing instead of recreating

Why it works. Every update is addressed by slug or display name and rewrites only the fields you named. Moving a task to a different milestone rewrites the milestone edge and leaves the project edge untouched. The gotcha worth acting on. --next-action is the field that decides whether a project is resumable. A project with a status and no next action tells you it is alive and nothing else. Since 0.16.0 a next step carries its age, and base doctor lists the ones older than [doctor] stale_next_days (14), with the command that rewrites each. Steps written before 0.16.0 carry no date and are listed as undated.

A project inside another

--parent links a project to the one it sits inside. --nested true says work in the inner project also carries the outer project’s rules; --nested false, the default, says it does not. Each project carries its own setting, and base project add takes both flags too. A parent must be a registered project, a link that would close a loop is refused, and --parent none removes the link. base never guesses the link from folders. With --nested true, a file in the inner project brings the inner project’s rules first, then the outer project’s, labelled [FILE MATCH: portal (parent of portal-api)]. A tight budget drops the parent’s rules first.

When a folder moves

Both arguments are positional: slug, then the new path. Why it works. This updates the graph and the domain trigger together. A path trigger pointing at a folder that no longer exists is a domain that silently stops firing, and nothing will tell you. base project update billing-service --path ~/services/billing does the same since 0.16.0: the path is stored absolute and the project’s domain trigger moves with it. The gotcha. Do this whenever a folder moves, even if the project still “works”. The failure is silent and looks like base having stopped caring about that project.

Finding each project’s real folder

--suggest lists every project whose folder is missing, not a folder, too broad, or contradicted by its own docs, with a suggested folder and the evidence; a project with two candidates gets both. It writes nothing. --apply sets the folders in a list you reviewed, and any bad line refuses the whole file.

Renaming a project

The first prints the plan and writes nothing. With --yes it renames the project and its same-named domain together, in the global tier and in the workspace you stand in: the records, the decisions, tasks, milestones and rules keyed by the old name, a handoff’s project field, and the domain’s name in domains.toml. It backs up every file it writes first. The old name keeps working: --domain billing-servce, base project get billing-servce and the rest reach the renamed project and print billing-servce is now billing-service. Prompt keywords are never changed, so the old spelling stays a keyword if it was one.

One project, two workspaces

Why it works. This adds a link rather than copying anything. The project lives in one place, and an edit from either workspace edits the same project. To move it outright:
It takes the project’s tasks, its domain, and its decisions, rules and notes with it, and regenerates the code map at the destination rather than carrying stale paths across. It prints the plan and changes nothing without --yes.

Finding a project you cannot find

The gotcha. base project list shows the current workspace only. A project missing from it is usually a project that now lives in another workspace, not a project that is gone. Check --all before concluding anything.

Deleting, and what goes with it

All three preview by default and change nothing until --yes. The gotcha. project delete --force takes the decisions and rules as well, and those are usually the part you would actually miss. The tasks are replaceable; the reasoning behind a choice made eight months ago is not.

Bringing state back in line

This reads each project’s folder for when it was last touched and updates its active or deferred state to match. Since 0.16.0 the dry run also previews the deferral pass for handoffs, forks, tasks and milestones, and run from outside any workspace it says it read nothing instead of printing a plan of zeros. The gotcha. Run --dry-run first, always. It decides from filesystem activity, so a project you have been working on outside its registered folder looks dormant when it is not.

Deferred work

Work nobody has touched in a while can stop crowding session start without being archived. It is off until you turn it on:
With [defer] enabled = true, a handoff, fork, task or milestone untouched for a set number of days (10 by default, per kind under [defer.days], or [defer] global_days for all) stops showing at session start and stays listed. Each deferred list gives every row the command that brings it back; base handoff show and base fork show bring one back too, and so does touching it. Projects already deferred under [protocol]; base project deferred lists them with the date each was deferred. Before you turn it on. Records from before 0.16.0 never had their activity clock kept, so a lot of live work would look cold at once. base defer migrate previews which records it would reset and writes nothing; base defer migrate --apply resets them, --limit <N> and --older-than <D> stage it, and --rollback restores every tier it wrote from the snapshot it took first.

Next

Multiple sessions

Coordinating when more than one session is working the same project.

Projects reference

Every flag on every command here.