What happens
The length guard exists only in lib/on.sh:46. past add and past modify have none, so the same name is accepted or rejected depending on which command creates it:
$ focus on <150-character name>
❌ Project name too long (max 100 chars). # exit 2
$ focus past add <the same 150-character name> 2026/06/01-10:00 2026/06/01-11:00
📝 Notes (empty to skip)
✅ Added: ... # stored, length 150
Why it's needed — and the argument against
CONTRACTS/MAIN.md:489 scopes the limit to CMD-ON, so past add is arguably behaving exactly as specified and this is not a contract violation. Against that, MAIN.md:80 describes the limit as a property of project names generally:
Convention customer/project-name, but unenforced beyond MAX_PROJECT_LENGTH.
So the contract says both things depending on where you read it.
The practical impact is small: an over-long name smears the %-22s column in focus past list. The real cost is that a configured limit is only true of one entry point, which is surprising once noticed.
Suggested fix
Pick one and make the contract say it:
- enforce everywhere — move the check next to
sanitize_pipe on each write path, or into the adapter beside _validate_project_name so every writer inherits it; or
- keep it on
on only — as a typo guard for the interactive path, and reword MAIN.md:80 so it does not read as a global guarantee.
Location
lib/on.sh:44-47 (the only enforcement), lib/past.sh (add and modify branches).
What happens
The length guard exists only in
lib/on.sh:46.past addandpast modifyhave none, so the same name is accepted or rejected depending on which command creates it:Why it's needed — and the argument against
CONTRACTS/MAIN.md:489scopes the limit toCMD-ON, sopast addis arguably behaving exactly as specified and this is not a contract violation. Against that,MAIN.md:80describes the limit as a property of project names generally:So the contract says both things depending on where you read it.
The practical impact is small: an over-long name smears the
%-22scolumn infocus past list. The real cost is that a configured limit is only true of one entry point, which is surprising once noticed.Suggested fix
Pick one and make the contract say it:
sanitize_pipeon each write path, or into the adapter beside_validate_project_nameso every writer inherits it; orononly — as a typo guard for the interactive path, and rewordMAIN.md:80so it does not read as a global guarantee.Location
lib/on.sh:44-47(the only enforcement),lib/past.sh(addandmodifybranches).