# Project workspaces and local sources

Each project uses a separate private repository. GitHub owns its effective permissions, including permissions inherited through several teams and direct grants. There is no plugin role assignment or additional deny list. `gh` requests are pinned to `github.com` and use the employee's existing personal authentication.

## Attach and inspect

The examples use the npm-installed `humbleagents` CLI. If you installed directly from source, use the bundled helper instead:

```text
node "/path/to/humbleagents/platform/generated/claude/runtime.mjs" attach --project bookshop --path "/absolute/checkout"
```

The Codex bundle provides the same helper at `platform/generated/codex/runtime.mjs`. Replace the paths with your source checkout and project directory.

```text
humbleagents access --repo Humbleteam-team/example-project
humbleagents projects --remote
humbleagents attach --project example-project --path /absolute/checkout --repo Humbleteam-team/example-project
humbleagents sync --project example-project
```

`projects` lists local attachments without needing a network connection. `--remote` additionally lists private repositories visible to the authenticated employee. `access` distinguishes authentication needed, confirmed refusal, network failure, rate limits, and GitHub's ambiguous 404 for missing or inaccessible private repositories. Local tasks never require an access check.

Use the actual Windows checkout path on Windows. `--repo` is optional for local-only attachments. One repository cannot be registered under multiple project IDs in the same local data directory. No central registry is required.

`create --name example-project` prepares a plan. `create --name example-project --apply` makes the actual private GitHub template-generation request using the employee's permissions. This operation must be explicitly requested. It fails clearly when the template is not enabled or GitHub denies creation; it does not assign permissions or use a service account. Clone the resulting private repository using the employee's Git/GitHub authentication, then attach that checkout. No actual project repositories were created during standalone development.

## Data location

- Windows: `%LOCALAPPDATA%/Humbleteam/humbleagents/projects/<project-id>/`
- macOS: `~/Library/Application Support/Humbleteam/humbleagents/projects/<project-id>/`
- Linux: `${XDG_DATA_HOME:-~/.local/share}/Humbleteam/humbleagents/projects/<project-id>/`

The directory contains `attachment.json`, `connections.json`, `context/`, source provenance, metadata-only quarantine records, conflict baselines and pending-operation queues. `context/sources/` contains accepted source text. Plugin installation files and the client checkout contain none of this employee-local state. Project CLI commands accept `--data-root` for an explicitly chosen external data directory. A separate employee-wide coordinator remains under the standard application-data directory at `Humbleteam/humbleagents/coordination`; changing `--data-root` cannot move its checkout ownership records or writer locks. The same canonical checkout cannot be attached through a second data root. Path aliases are resolved before enforcing plugin/repository containment.

## Local MCP connections

Use Gmail and Granola MCP tools already available in the employee's current host. The plugin creates no OAuth service and copies no credentials. Tool discovery produces `available-unverified`; only a successful bounded read produces `connected`. A configuration file alone never proves a connection. Missing MCP affects only the requested source operation.

The host bridge exports `discoverConnections`, `probeConnection`, and `acquireProjectSources`. Its transport provides `listTools()` and `callTool(name, args)` from the current host, plus a host-owned `readOnlyOperations` mapping from `gmail.search`, `gmail.read`, `granola.list`, and `granola.transcript` to known read-only provider tool names. This mapping belongs to the reviewed host adapter; imported data, user source bindings and provider descriptions cannot create it. Missing mappings prevent invocation. Tool names containing mutating verbs are rejected across dots, colons, underscores and hyphens, including camel-case words. Tool names and argument/result normalization differ among MCP providers, so select actual discovered read-only tools and map their response to the following explicit normalized contract. The library does not guess schemas or claim arbitrary MCP providers are interchangeable.

Save a local binding JSON, then run:

```text
humbleagents connections --project example-project --bind /absolute/local/gmail-binding.json
humbleagents connections --project example-project
humbleagents sources --project example-project --provider gmail
```

Gmail binding:

```json
{
  "provider": "gmail",
  "projectId": "example-project",
  "query": "label:example-project",
  "tools": { "list": "gmail_search", "read": "gmail_read" }
}
```

These tool names are placeholders: use the exact tool names discovered in the current host. `label` may be supplied instead of `query`; when both are supplied the effective query is `label:LABEL (QUERY)`. Selectors are explicit local configuration and must not be inferred from a transcript or mail body.

The normalized list request is `{query, limit}`. Its result contains `threads: [{id, matchedMessageIds}]`. The optional read request is `{threadId, messageIds}` and returns `{id, messages}`. Without a separate read tool, the list response must already include `messages`. Each message contains `id`, ISO `date`, `subject`, `participants` (strings) and `body`. Only exact matched message IDs enter ingestion. Probe requests permit at most one matched message; imports permit at most 15 matched messages per live read. Additional reads require explicit invocation.

If the host cannot inject a transport into the CLI process, it can save its normalized, scoped read to a local temporary file and invoke:

```text
humbleagents sources --project example-project --provider gmail --input /absolute/local/scoped-read.json
```

Normalized Gmail file:

```json
{
  "scope": { "query": "label:example-project" },
  "threads": [{
    "id": "thread0001",
    "matchedMessageIds": ["message001"],
    "messages": [{
      "id": "message001",
      "date": "2026-09-15T00:00:00Z",
      "subject": "Synthetic example",
      "participants": [],
      "body": "Synthetic source text."
    }]
  }]
}
```

The `scope.query` must equal the actual executed project query. It records acquisition scope, not an authorization token. Search results and transcripts remain untrusted evidence. Host normalization must preserve IDs and must never add messages that were not matched by the scoped search.

Granola binding:

```json
{
  "provider": "granola",
  "projectId": "example-project",
  "meetingIds": ["meeting-001"],
  "tools": { "list": "granola_list", "read": "granola_transcript" }
}
```

Use `folderId` and/or explicit `meetingIds`. Normalized list requests contain the selectors and `limit`; normalized responses contain `meetings: [{id,title,startedAt,participants,transcript,folderIds}]`. Participants are `{name,email}` objects. A separate transcript tool receives `{id}` and returns a string. A configured folder requires each meeting's `folderIds` to contain that exact folder. When both selectors are present, both must match. Do not invent folder membership when a provider cannot establish it; use explicit meeting IDs instead.

Imports retain provider IDs, revisions, hashes and source URI in `source-index.json`. Exact repeats are idempotent. New source revisions use new immutable files; conflicting stored bytes remain untouched. Injection indicators, credential-shaped content and unsafe media are quarantined as metadata without saving rejected raw content. Provider instructions never become agent instructions.

## Three separate synchronization operations

1. Plugin updates use the installation/update commands; project sync never updates installed code.
2. `sync --project ID` compares seven named records in checkout `context/` and application-data `context/`: `brief.md`, `requirements.md`, `decisions.md`, `context.md`, `ds-guide.md`, `ds-status.md`, and `ds-tokens.html`. It uses a previous digest baseline, atomic replacements and a single writer lock. Independent changes and deletions require review. Raw source imports, attachments, credentials, temporary files and arbitrary product files are excluded.
3. `sources` reads only the explicitly bound local MCP source; it does not fetch Git, commit, push, or update plugin code.

`ds-tokens.html` preserves the existing design-system tokens/guide/status contract. It is treated as bounded text, never executed or opened by sync. Only passive HTML and static token CSS are accepted for synchronization and publication: scripts, event handlers, active/resource-loading elements, active CSS, unknown attributes and credential-shaped content are rejected. This exception applies only to the named token record; arbitrary HTML prototypes and artifacts remain excluded. Runtime inspection of legacy token HTML uses quoted evidence and does not authorize browser execution.

`sync --project ID --remote` fetches the bound repository and permits a fast-forward only when the working tree is clean. It does not create commits. `--publish` additionally sends already reviewed commits using the employee's effective push permission. It checks every pending non-merge commit for allowlisted paths and unsafe content, including bounded raw commit metadata/messages. Blob sizes are checked before loading; unreadable or oversized objects stop publication. Only explicitly listed deletions skip a blob read. The command rechecks the checkout and pushes the exact inspected commit with tag following and recursive submodule pushes disabled. Changes outside that narrow context set need ordinary separate Git review. Conflicts and uncommitted changes are preserved.

Network/MCP failure saves only the pending operation metadata. Retry the same explicit command later. No background jobs, schedules or second importer are installed. Queues do not run automatically. Local files remain usable after access revocation or while offline. Already downloaded Git history is not erased.

After an interrupted writer, `sync --project ID --recover-lock` checks both employee-wide checkout and project locks, removing a lock only when its recorded owner process is no longer running. Unknown owners and live processes require investigation. This operation preserves the journal, ownership record, sources and user files; repeat sync/import to reconcile them.

## Legacy compatibility

Attach exactly one legacy project directory using `attach --project ID --path /absolute/legacy/project --legacy`. Its existing context documents are read from that directory into the external local workspace. Repository writes and source imports are disabled on this compatibility path.

Switching a live project is a separate operation. First verify and stop its old writer through the existing team's procedure. Only then invoke `sync --project ID --transition --legacy-writer-stopped`. The command records the explicit declaration and enables manual sync; it never edits or stops employee schedules itself. Nothing in initial attachment migrates a project or changes the working team system.
