Item numbering
Tasks, memories and documentation pages take their numbers from one sequence per project. #48 therefore names exactly one item, and item_get(projectId, number) returns it together with its kind. No typed reader has to be chosen first.
A bare #N only means something together with a project. Unknown and inaccessible numbers return the same message, so a caller learns neither the kind nor the title of an item it may not read.
Qualified references
| Form | Example | Use |
|---|---|---|
| Bare | #48 |
Inside one project, e.g. in task descriptions |
| Project | project:<project-id>#48 |
Outside the project context; the Reference of a project memory |
| Non-project memory | user:memory:<id> |
Personal, organization and system memories (scope first) |
The project form uses the project id, not its slug, so renaming a project breaks no reference. Personal, organization and system memories stay outside the sequence and are never inferred from a bare number. Project instructions have no number; tools address them with ref=instructions.
Allocation
ProjectItemService reserves the next number inside the writing transaction, under a lock on the project row (Projects.NextItemNumber), and registers the item in ProjectItems. The registry key (ProjectId, Number) enforces uniqueness across kinds. ItemNumberConcurrencyTests checks this against SQL Server, because SQLite serialises writers anyway and would hide the problem.
A page proposal gets its number only when it is approved. A rejected proposal never uses one up.
Numbers are never reused
Archived, closed and permanently deleted items keep their registry entry; a deleted one gets the kind deleted. An old #N in a commit message or a task description therefore never resolves to a newer item.
Numbers from before the shared sequence
Tasks and memories were once numbered separately. The migration to one sequence kept every task number, kept memory numbers that did not collide and moved colliding memories to free numbers above the previous maximum. The old memory number is kept as LegacyNumber, and an old link /projects/<project>/memories/<old-number> still opens that memory. Markdown written before the migration is not rewritten: an old #1 could have meant either kind.
Addresses for pages
In the web app a page lives at /projects/{project}/docs/{number}/{path}, where the path is made of the slugs of its ancestors and its own title. Renames and moves change the path, never the number, and outdated paths redirect. See Web routes (#289).