Repository bindings
Tie a git checkout to a Lific project so agents working in it find their project without being told, locally or against a remote server.
A repository binding records that a git repository belongs to a Lific project. An agent that starts lific mcp inside a bound checkout then works in that project by default, so you do not have to name the project in every prompt.
A binding stores two identities derived from the repository, never your checkout's path, so every clone of the same repository resolves to the same project without being bound again. The server never runs git on your machine; the lific command computes the identities locally and sends them.
Closing issues from commit messages is a separate feature. See Git hooks.
Bind a checkout
From anywhere inside the repository:
lific bind APPThat binds the repository to the existing project APP. To create the project at the same time, add --create. The new project is named after its identifier; rename it later in the web UI or with lific project update.
lific bind APP --createRun lific bind with no project to see what the checkout resolves to. It changes nothing, which makes it the quickest way to ask "which project is this?":
This repository is bound to APP (App).
remote v1:github.com/example/app (matches this binding)
root v1:a96d132749268ec285739f51445b73b0c6268c8b (matches this binding)bind uses the instance your configuration selects, like every other command. Pass --config or --db to pick another one. Against a local database it acts with the full authority of whoever holds the database file.
Use a remote server
When the tracker runs on another machine, sign in to it once, then bind through the HTTP backend. No local database is needed.
lific login --url https://tracker.example.com
lific --backend http --url https://tracker.example.com bind APPBinding a repository, and removing or merging bindings, needs the project's lead role or an instance administrator. The account your agent uses also needs to be able to read the project, or the binding is invisible to it.
An MCP client connected straight to the server's /mcp URL cannot use a binding, because the server cannot see the client's working directory. Have the client launch a local stdio proxy from the checkout instead:
lific mcp --remote --url https://tracker.example.comGive the server's base URL, without /mcp; the proxy adds it. The proxy computes the checkout's identity locally and forwards every call to the server, authenticated with the same credentials the CLI uses: LIFIC_API_KEY if it is set, otherwise the token lific login stored. It refuses to send a credential over plain http:// to any host but your own machine. If the server rejects the credential or cannot be reached, the call fails with an error; the proxy never falls back to a local database.
lific connect writes client configurations from a local instance and does not set up this remote form for you. Add the command above to your client's MCP configuration by hand; Connect agents shows where each client keeps it. To reach several servers through one stdio connection, use multi-instance MCP.
How a repository is identified
lific bind asks git for up to two identities, called aliases:
| Alias | Value |
|---|---|
remote | The origin remote's URL, normalized and prefixed with v1:. |
root | The root commit of HEAD's first-parent history, prefixed with v1:. |
Normalizing the remote removes the transport, any credentials and a trailing .git, and lowercases the host name. The path's case and an explicit port are kept. So git@github.com:Example/App.git and https://github.com/Example/App give the same alias, v1:github.com/Example/App. A local path or a file:// remote gives no remote alias.
A shallow clone gives no root alias, because its oldest visible commit is not the real root. A repository with no commits has none either. A checkout with neither alias cannot be bound: add an origin remote that names a host, or make a commit in a full clone.
Moving a checkout changes neither alias. Forks usually share a root commit with the original, so an alias is a lookup key, not proof that a repository belongs to anyone, and a binding grants no access. Resolution only ever reports projects the caller is allowed to see.
Resolving never records new aliases. If you change origin and the root commit still matches, the repository keeps resolving but the new remote is not stored. Run lific bind APP again to add it. Bindings do not travel in project archives.
Which tools use the binding
lific mcp and lific mcp --remote work out the binding once, when they start, from the directory they were started in. Start them from the checkout, and restart the MCP client after you change a binding. When a project is bound, the initialization instructions the agent receives name it. When the checkout matches two different bindings, no project is assumed and the session starts unbound; run lific bind in the checkout to see the conflict.
With a bound project, an omitted project argument means the bound project on these tools:
| Tool | When the binding fills in project |
|---|---|
list_issues, create_issue, get_board, create_plan, get_briefing | Always. |
list_resources | For resource_type issue, plan, module, label and folder. |
An explicit project always wins. Everything else keeps its unbound meaning: search still searches every project, create_page without a project still creates a workspace page, and list_resources for pages still lists every project's pages. Tools that take an identifier such as APP-42 still need it, and bulk updates and deletes never pick up an implicit project. A binding never raises anyone's permissions.
Diagnose or remove a binding
lific bind --json reports what the checkout resolves to:
{
"resolution": "one",
"project": { "id": 1, "identifier": "APP", "name": "App" },
"projects": [],
"aliases": [
{ "kind": "remote", "value": "v1:github.com/example/app", "matched": true },
{ "kind": "root", "value": "v1:a96d132749268ec285739f51445b73b0c6268c8b", "matched": true }
],
"created": false
}resolution is none, one or conflict. project is set only for one, and projects lists the candidates only for conflict. Each alias says whether it matched. An unmatched alias next to a resolved project is normal when only the other alias was stored.
If the agent reports no binding, check that it started in the checkout, that it uses the instance you bound, that its account can read the project, and that git is installed. An account that cannot see a project gets the same answer as if no binding existed.
When the two aliases point at different bindings, lific bind refuses to move them silently. Look at the records over the REST API, then delete the wrong binding or merge the two:
| Route | Purpose |
|---|---|
POST /api/repos/resolve | Resolve an aliases array of { "kind", "value" } objects. Matches you can see include their binding IDs. |
POST /api/repos/bind | Bind a project identifier to an aliases array. Aliases that belong to another binding are refused. Needs lead or administrator. |
GET /api/projects/{id}/bindings | List a project's bindings and their aliases. Needs read access to the project. |
DELETE /api/repos/bindings/{id} | Remove a binding. Needs lead or administrator on its project. |
POST /api/repos/merge | Move the aliases of binding from into binding into. Needs lead or administrator on both projects. |
from and into are numeric binding IDs, not project IDs. Merging moves aliases between bindings; it does not merge the projects. The into binding survives and the from binding is removed. Afterwards, run lific bind to confirm the result and restart the agent.