# EasySpecs CLI

Headless **EasySpecs** command-line tool: same orchestration ideas as the **EasySpecs VS Code extension**, without an editor. **`easyspecs-cli help`** is organized around three **factory-level** jobs on **application context**; the rest of the commands and flags exist to tune **where** the work runs (**`--cwd`**, **`--worktree`**, **`--root`**), **how** it behaves (**`--ci`**, **`--json`**, **OpenCode** and **`config.json`** settings), optional **cloud sync**, and **step-level** escapes (partial pipelines, resumes, deterministic checks).

## The three factory-level commands

1. **`easyspecs-cli analysis` — create new context**  
   Runs the full **Generate Context** factory end to end (synthesis until stable, coverage, remediation, reporting, link mapping, index assembly). Use this when you need a fresh or full rebuild of **`.easyspecs/context`** from the repo. **`--force-new-context-analysis`** overrides skip-when-already-analyzed behavior when **`easyspecs.factory.cloudContextAnalyzed`** would otherwise short-circuit the run. **`--no-worktree`** (alias **`--noworktree`**) runs the factory on the repository checkout itself instead of a detached temp git worktree (**SRS-71**). On a **successful** factory exit, the CLI **always** copies **`.easyspecs/context`** from the analysis checkout into **`<repoRoot>`** unless you pass **`--no-promote`** or the checkout is already the repo root (no **`--promote`** needed); stderr **`[pipeline:analysis] promoted …`** or a skip line when in-place. This step does **not** use **`easyspecs.analysis.promoteContextToWorkspace`** — that setting still applies to **`run synthesis`** and **`update context`**. After **`analysis`**, run **`easyspecs-cli upload context`** or **`upload republish`** when you want context on EasySpecs (**`auth login`** + **`config set-project-id`**). If **`git worktree add`** fails because a temp analysis checkout folder was removed but Git still lists it, the CLI runs **`git worktree prune`** in your repository and retries **`git worktree add`** once (same behaviour wherever the CLI creates that temp worktree).

2. **`easyspecs-cli update context` — update context from code churn**  
   Incremental refresh from a **git delta** since the stored baseline (**`easyspecs.factory.updateContext.lastRunAt`**) or, when that is absent, inferred from workspace context mtimes: materialises **`changes-since-date.md`**, runs **scoped** remediation when commits and touched paths warrant it, then optional promotion. **`--no-worktree`** / **`--noworktree`** uses **`<repoRoot>`** as the analysis checkout (**SRS-71**). Use **`upload context`** afterward when you need cloud sync. Use this day to day after **`analysis`** when the codebase moved but specs did not restart from zero.

3. **`easyspecs-cli context drift <referencePath>` — analyze drift against specs**  
   **`referencePath`** is a **file or directory** of truth documents (requirements, specs, PRD, architecture notes — whatever your team treats as ground truth relative to repo root). The CLI compares **repository / analysis checkout** context against that reference (OpenCode-assisted), writes **`drift-<label>-<date>.md`** under **`.easyspecs/context/drift/`**, and can update the reference index markdown. **`--label <slug>`** names the report file; **`--index <path>`** overrides which root markdown anchors the comparison; **`--dry-run`** validates references only (**no worktree, agent, or writes**). **`--no-worktree`** / **`--noworktree`** uses **`<repoRoot>`** as the checkout and cannot be combined with **`--root worktree`** or **`--worktree <path>`** (**SRS-71**).

**Supporting commands (not substitutes for those three goals):** **`auth`** and **`config`** wire API access and **`easyspecs.easyspecsProjectId`**; **`worktree create`** / **`worktree remove`** manage the analysis git checkout snapshot; **`agents materialize`** copies bundled OpenCode agents into a checkout; **`context promote`** and **`context assemble-index`** copy or index worktree context; **`workstation run`** runs one synthesis-tree OpenCode step; **`download context`** / **`upload context`** / **`upload republish`** move artefacts between disk and EasySpecs; **`doctor`** checks readiness and redacted merged settings; **`diagnose`** and **`context link-graph`** are deterministic gates and link maintenance; **`run synthesis`** and **`run synthesis resume-*`** run single stages or catch-up without the full factory; **`ace *`** manages optional learnings under **`.easyspecs/context/learnings`**.

**Implementation-only context (SRS-114):** synthesis agents document **coded capabilities only** — each coordination row and detail markdown Evidence index needs at least one **non-`.md`** implementation cite (`src/…`, tests, manifests). README/SRS may supplement, not replace, code evidence. **`repo-surface-scan.json`** gates whole branches (UI, backend, tests, infrastructure, persistent data); absent surfaces produce **empty** list files and skip list/review agents. See [USER-MANUAL-SRS-114](.easyspecs/docs/srs/USER-MANUAL-SRS-114.md).

**Vocabulary:** A **Factory** is the full ordered **`analysis`** run. A **Pipeline** is one major stage inside it (synthesis, coverage, remediation, link mapping, upload, download). A **Workstation** is one atomic OpenCode or programmatic step inside a pipeline. Canonical **`config.json`** keys live under **`easyspecs.factory.*`**, **`easyspecs.workstations.*`**, and **`easyspecs.pipelines.upload.*`**; legacy **`easyspecs.orchestration.*`**, **`easyspecs.macro.debug`**, and several **`easyspecs.analysis.*`** tunables still work for one deprecation cycle and may log **`[deprecated-setting]`** on stderr when read. Human stderr may tag lines **`[factory]`**, **`[pipeline:…]`**, **`[workstation:…]`**, **`[pool]`**, **`[ace]`**.

Published as **`@gluecharm-lab/easyspecs-cli`** (npm org **gluecharm-lab**; **EasySpecs** is the product). The executable on your `PATH` is **`easyspecs-cli`**.

## Requirements

- **Node.js** ≥ 18  
- **OpenCode** (`opencode` on `PATH`) for flows that run agents — install OpenCode using its own install instructions 

## Install

```bash
npm install -g @gluecharm-lab/easyspecs-cli
```

Check:

```bash
easyspecs-cli version
easyspecs-cli help
```

Run without a global install:

```bash
npx @gluecharm-lab/easyspecs-cli@latest help
```

**Uninstall:** `npm uninstall -g @gluecharm-lab/easyspecs-cli`

## Quick start

From a git repo (**OpenCode** on **`PATH`** for agent steps). The CLI creates minimal **`.easyspecs/`** directories when needed for **`analysis`**, **`update context`**, **diagnose**, **upload**, and **`download context`**:

```bash
cd /your/repo
easyspecs-cli doctor
easyspecs-cli config init
easyspecs-cli analysis
```

**`config init`** creates **`.easyspecs/config.json`** with defaults (add **`--overwrite`** to replace an existing file). After the first **`analysis`**, typical follow-ups are **`update context`** (incremental sync with git) and **`context drift`** when you hold truth in specs files/folders elsewhere in the repo. Use **`download context`** (**`auth login`** + **`config set-project-id`**) to pull stored context from EasySpecs into **`.easyspecs/context`**. Run **`upload context`** or **`upload republish`** after **`analysis`** / **`update context`** when you want edits on EasySpecs. Use **`--ci`** in automation for non-interactive behaviour and stricter defaults (e.g. Factory outer-iteration cap).

Set **`easyspecs.defaultGitRemoteUrl`** with **`config set-git-remote <url>`** when you want that recorded in config. Tunables belong in **`<repo>/.easyspecs/config.json`** — do not rely on **`EASYSPECS_*`** environment variables for product settings.

Optional checks:

```bash
easyspecs-cli doctor --inspect-config
easyspecs-cli help
easyspecs-cli diagnose coordination-duplicates --cwd /your/repo --ci --json
```

## Download context (pull from EasySpecs)

Use this when you want **application context** already stored for the EasySpecs **project** written into **`<repo>/.easyspecs/context`** (after a clone, environment switch, or to match cloud contents).

**Prerequisites:** same as **`upload context`**: a prior **`auth login`**, and **`easyspecs.easyspecsProjectId`** set (e.g. **`easyspecs-cli config set-project-id <uuid>`**). The CLI creates minimal **`.easyspecs/`** layout when needed, including **`.easyspecs/context`**.

**Behaviour:** the CLI **GET**s the application, reads linked identifiers for persisted context fragments, batches a content **GET**, and writes one UTF‑8 file per row under **`<repo>/.easyspecs/context`**, using each row’s **`name`** as a safe relative path (path traversal is rejected). Rows named **`easyspecs-upload-target.json`** are skipped. When files are written, ids are merged into **`index-application-context.json`** the same way as after upload, if that index file exists.

```bash
cd /your/repo
easyspecs-cli auth login --email you@example.com --password '…'
easyspecs-cli config set-project-id <your-easyspecs-project-uuid>
easyspecs-cli download context
```

| Flag | Meaning |
| ---- | ------- |
| *(none)* | Skip any local file that already exists (no silent overwrite). |
| `--force` | Overwrite existing files that map to cloud rows. |
| `--replace-from-cloud` | Delete local files under **`.easyspecs/context`** first, then write from the cloud. Root **`easyspecs-upload-target.json`** is preserved when present. Implies a full replace of downloaded content; pair with care. |

**`--json`:** prints one summary line on stdout with **`ok`**, **`downloaded`**, **`skipped`**, **`failed`** (count), and **`localRemoved`** (files deleted when **`--replace-from-cloud`** was used). With **`--verbose`**, if **`failed` > 0`**, the line may include a **`failures`** array. A non-zero exit is used when **`failed` > 0`**.

**Examples:**

```bash
easyspecs-cli download context
easyspecs-cli download context --force
easyspecs-cli download context --replace-from-cloud
easyspecs-cli --json download context
```

## Configuration

Primary configuration file: **`<repo>/.easyspecs/config.json`** (auto-created when needed, except for **`help`**, **`version`**, and **`doctor`** which do not create the file on first run). **`auth login --email … --password …`** stores tokens for **upload** and **download context** commands. You may append **`--session-path <file>`** at the **end** of the **`auth login`** argument list to update **`easyspecs.cliSessionPath`** in **`config.json`** for that login flow.

After **`auth login`**, session data is stored at **`easyspecs.cliSessionPath`** in **`config.json`** when that path is set (otherwise **`~/.easyspecs/cli-session.json`**). Tools such as the VS Code extension may spawn **`easyspecs-cli`** with global **`--session-path <file>`** so this process uses a temporary session file without editing **`config.json`**.

### Default **`config.json`** (from **`easyspecs-cli config init`**)

Run **`easyspecs-cli config init`** in your repo (add **`--overwrite`** to replace an existing file). A fresh install writes **`.easyspecs/config.json`** with a shape similar to:

```json
{
  "schemaVersion": 2,
  "easyspecs": {
    "deploymentEnvironment": "production",
    "apiBaseUrl": "https://api.easyspecs.ai",
    "easyspecsProjectId": "",
    "defaultGitRemoteUrl": "",
    "cliSessionPath": "",
    "agentHarness": {
      "kind": "opencode",
      "executable": "opencode",
      "skipCredentialsCheck": true
    },
    "workstations": {
      "maxConcurrentAi": 30,
      "enableAdaptivePoolPressure": false,
      "harnessTimeout": 900000
    },
    "analysis": {
      "promoteContextToWorkspace": true
    },
    "orchestration": {},
    "openCodeRuntime": {
      "executable": "opencode",
      "skipCredentialsCheck": true,
      "credentialsPath": ".opencode/auth.json",
      "providers": {},
      "run": {
        "argvTemplate": [
          "run",
          "--agent",
          "{agentId}",
          "{taskUserInstruction}",
          "-f",
          "{promptFile}"
        ]
      },
      "coordinationRepairs": {
        "listJsonSchemaRepairAttempts": 1,
        "markdownEvidenceRepairAttempts": 2,
        "markdownOpenQuestionIterations": 5
      },
      "pool": {
        "maxConcurrentAgents": 30
      },
      "projectConfigOverlay": {}
    },
    "diagnose": {
      "zeroReference": {
        "maxPercentNonReferenced": null
      },
      "coordinationDuplicates": {
        "strict": true
      }
    },
    "upload": {
      "contextDirectory": ""
    },
    "macro": {
      "debug": false
    },
    "cli": {
      "bundledResourcesRoot": ""
    },
    "auth": {
      "ciLogin": {}
    },
    "remoteHarness": {
      "useRemoteOpenCodeExecution": false,
      "baseUrl": "https://api.easyspecs.ai",
      "requesterId": "",
      "completionLedgerDir": ".easyspecs/harness-remote-run",
      "submitTimeoutMs": 30000,
      "completionPollIntervalMs": 2000,
      "completionLedgerLogIntervalMs": 30000
    }
  }
}
```

**`remoteHarness.baseUrl`** defaults to the same value as **`apiBaseUrl`**. Before enabling remote execution (`useRemoteOpenCodeExecution: true` or **`--remote-harness`**), set **`requesterId`**. Each agent run uses the **active analysis checkout** as API **`work_dir`** when paths are the same on CLI and worker (shared disk). POST **`harness`** follows **`agentHarness.kind`**. Only if the worker sees a **different absolute path** for the same tree, set **`localRootPrefix`** + **`defaultRemoteWorkDir`**, **`workDirByLocalRoot`**, or **`remoteOpenCodeWorkDir`** (see **`commands.md`** § Remote agent harness). Set **`SERVICE_API_KEY`** and **`AI_MODEL_PRESET_ID`** in the environment, or in **`<workspace>/.env`** (shell env takes precedence), or use **`--set`** on **`analysis`** / **`config init`** for this process only (**SRS-102**).

Your file may gain additional keys over time (for example cloud-analysis cache fields under **`easyspecs.analysis`**); validate against the schema your package version documents.

### Config keys (reference)

Merge order for **`easyspecs-cli`**: **`.easyspecs/config.json`** plus global CLI flags (for example **`--api-base-url`**, **`--environment`**). The CLI does **not** read **`EASYSPECS_*`** or **`VITE_*`** for product settings.

| Path | Purpose |
| ---- | ------- |
| `schemaVersion` | Integer (e.g. `2`) for future migrations. |
| `easyspecs.deploymentEnvironment` | `production` or `staging` (default `production`). Used with built-in System Manager URLs when `apiBaseUrl` is empty. |
| `easyspecs.apiBaseUrl` | Explicit System Manager origin; wins after `--api-base-url`. Default non-empty value uses the production API host. Use **`""`** if you want the built-in URL from **`deploymentEnvironment`** / **`--environment`** only. |
| `easyspecs.easyspecsProjectId` | EasySpecs **project** id (Content application UUID) for **`upload context`**, **`upload republish`**, and **`download context`**. Set with **`config set-project-id`** or the EasySpecs app; legacy **`applicationId`** in older files may be migrated into this field. |
| `easyspecs.defaultGitRemoteUrl` | Optional primary **`git remote`** URL (HTTPS or SSH) for reference; analysis still uses the live repo. |
| `easyspecs.cliSessionPath` | Repo-relative or absolute path to **`cli-session.json`**. Empty **`""`** → **`~/.easyspecs/cli-session.json`**. Precedence: global **`--session-path`** → **`cliSessionPath`** → home default. |
| `easyspecs.openCode` | `executable`, `skipCredentialsCheck` (shortcut; see **`openCodeRuntime`**). |
| `easyspecs.analysis.*` | **`promoteContextToWorkspace`**, ACE toggles, and other analysis-only flags. Cloud “already analyzed” cache is canonical under **`easyspecs.factory.cloudContextAnalyzed`** / **`At`** (legacy **`easyspecs.analysis.cloudContext*`** still read once with deprecation stderr). |
| `easyspecs.factory.*` | Factory debug, backoff, outer-iteration caps, ping-pong cap, cloud cache. Replaces **`easyspecs.orchestration.*`** and **`easyspecs.macro.debug`** (aliases still read). |
| `easyspecs.workstations.*` | OpenCode argv, timeouts, repair attempts, pool width, coordination lock timeout (canonical; legacy **`easyspecs.analysis.openCodeTest*`** / **`markdown*`** / **`listJsonSchemaRepairAttempts`** / **`maxConcurrentOpenCodeAgents`** aliases). |
| `easyspecs.pipelines.upload.useBatch` | Upload batching (legacy **`easyspecs.analysis.uploadUseBatch`**). |
| `easyspecs.orchestration.*` | **Deprecated** — prefer **`easyspecs.factory.*`**. |
| `easyspecs.openCodeRuntime` | Providers (`apiKey`, `defaultModel` per provider), `run`, `coordinationRepairs`, `pool`, `projectConfigOverlay`. |
| `easyspecs.diagnose.zeroReference.maxPercentNonReferenced` | Number or **`null`**. **`null`** means **`diagnose reference-coverage`** does not fail on percentage alone. |
| `easyspecs.diagnose.coordinationDuplicates.strict` | Boolean (default **`true`**). |
| `easyspecs.upload.contextDirectory` | Repo-relative or absolute override for **`upload republish`**; empty **`""`** → automatic resolution from the analysis snapshot. |
| `easyspecs.upload.fetchContextAnalyzedInCloud` | Boolean, default **`true`**. When **`true`**, after a successful **`upload context`** / **`upload republish`**, the CLI may **`GET`** application status and emit **`contextAnalyzedInCloud`** / **`contextAnalyzedInCloudAt`** on **`--json`**, and update **`easyspecs.analysis.cloudContext*`** cache. When **`false`**, skips that fetch. |
| `easyspecs.upload.contextAnalyzedStatusTimeoutMs` | Optional positive integer (ms) for that GET; default **15000** if omitted. |
| `easyspecs.factory.debug` | When **`true`**, Factory phase transitions log extra lines to stderr. Legacy **`easyspecs.macro.debug`**. |
| `easyspecs.cli.bundledResourcesRoot` | Optional absolute or repo-relative path to the bundled **`resources/`** directory. Empty → resolve next to the installed CLI package. |
| `easyspecs.auth.ciLogin.email` / `password` | Used with **`--ci`** when **`--email` / `--password`** are omitted on **`auth login`**. Prefer CI-generated **`config.json`**; avoid committing secrets. |

**Resolved API base URL:** `--api-base-url` → non-empty **`easyspecs.apiBaseUrl`** → built-in URL from **`--environment`** or **`easyspecs.deploymentEnvironment`** or **`production`**.

**OpenCode providers:** put API keys under **`easyspecs.openCodeRuntime.providers`** in **`config.json`**. They are passed to the **`opencode`** child process and are redacted in **`doctor --inspect-config`** output.

## Global flags

These appear **before** the subcommand (everything after the first non-flag token is the command and its arguments).

| Flag | Effect |
| ---- | ------ |
| `--cwd <dir>` | Repository root for git resolution and paths (default: current working directory). |
| `--ci` | Non-interactive mode; affects merged settings (e.g. Factory outer-iteration default). **`EASYSPECS_CI` is not read** — use this flag. |
| `--json` | On supported exits, one JSON summary line on stdout. On **non-zero** exits, the line includes **`exitCode`** (number) and **`exitMeaning`** (one-line classification) alongside any command-specific fields. Factory / pipeline failures include **`factoryFailures`** (per failed phase), **`failurePhase`**, and **`validationExitId`** (legacy alias of **`failureExitId`**). See [error-code.md](./error-code.md) — OS exit **`5`** is **not** used (**SRS-70**). |
| `--verbose` | Extra stderr logging where implemented. |
| `--api-base-url <url>` | System Manager API origin for this process (overrides `easyspecs.apiBaseUrl`). |
| `--session-path <file>` | Session JSON path for **this process only** (overrides **`easyspecs.cliSessionPath`**); does not rewrite **`config.json`** unless you use **`auth login`** tail **`--session-path`** as documented under **Auth**. |
| `--environment production` \| `staging` | Alias **`--env`**. Overrides `easyspecs.deploymentEnvironment` for built-in URL selection when no explicit URL is set. |
| `--promote` | After **`run synthesis`**: force copying **`.easyspecs/context`** into the repo when **`easyspecs.analysis.promoteContextToWorkspace`** would disable it. **`analysis`** does not need this flag — promotion is automatic unless **`--no-promote`**. |
| `--no-promote` | After **`run synthesis`** or **`analysis`**: skip copying **`.easyspecs/context`** into the repo for this run. |
| `--help`, `-h` | Built-in help. Does **not** create **`.easyspecs/config.json`**. |
| `--version`, `-V` | CLI package version. Does **not** create config. |
| `--config <path>` | Parsed but **unused** in current releases; read **`<repo>/.easyspecs/config.json`** instead. |

### Run logs (persistent stderr transcript)

Every command that resolves a repository root (all commands except **`help`** and **`version`**) writes a **plain-text** run log under **`<repo>/.easyspecs/logs/`**:

- **Filename:** `run-log-YYYY-MM-DDTHHmmssZ.txt` (UTC start time). If that name already exists, the CLI appends **`-<pid>`** before **`.txt`**.
- **Contents:** A short header (CLI version, repo root, redacted argv, log path), then **every human stderr diagnostic line** emitted during the run (tags such as **`[pool]`**, **`[AgentCode]`**, **`[factory]`**), with **no ANSI color codes**. Each line is flushed to disk as it is printed.
- **Discoverability:** The first stderr line after the header is **`[log] run log: <absolute path>`** (also recorded in the file).
- **Stdout is not mirrored** (including **`--json`** summary lines). With **`--json`**, stderr diagnostics are suppressed unless you also pass **`--verbose`**; the run log then contains only the header, the **`[log]`** line, and the footer.
- **Secrets:** Passwords on argv are redacted in the header; session tokens are never written.
- **Git:** Add **`.easyspecs/logs/`** to **`.gitignore`** — run logs are local forensics, not source artefacts.

### Performance reports (live artefact table, SRS-118)

Alongside each run log, the CLI writes **`perf-report-YYYY-MM-DDTHHmmssZ.md`** in the same **`.easyspecs/logs/`** directory. This is a **separate report type** from the append-only run log:

- **Shape:** Markdown summary + **Artefacts** table — **one row per workstation-pool work item**, updated in place (full file rewrite + `fsync`).
- **Live view:** Open the `.md` in your editor’s Markdown preview during an **`analysis`** run; refresh to see **Elapsed** and retry counters change on **running** rows.
- **Discoverability:** stderr prints **`[log] perf report: <absolute path>`** once at startup.
- **Example:** [`.easyspecs/docs/examples/perf-report-example.md`](./.easyspecs/docs/examples/perf-report-example.md)

### Pool pressure reports (adaptive cap timeline, SRS-119)

Alongside each run log, the CLI also writes **`pool-pressure-YYYY-MM-DDTHHmmssZ.md`** in **`.easyspecs/logs/`**:

- **Shape:** Markdown **Events** table — one row per **step-down** or **step-up** of `adaptiveMaxConcurrentAi`, with **Document**, **Workstation**, and **Step** naming the trigger.
- **Use case:** Diagnose why the pool throttled concurrency without grepping the full run log.
- **Discoverability:** stderr prints **`[log] pool pressure report: <absolute path>`** once at startup.
- **Example:** [`.easyspecs/docs/examples/pool-pressure-example.md`](./.easyspecs/docs/examples/pool-pressure-example.md)

## Diagnose / context flags

Used on the tail of **`diagnose <subcommand>`** or **`context link-graph`**:

| Flag | Values | Meaning |
| ---- | ------ | ------- |
| `--root` | `workspace` or `worktree` | Resolve paths against the workspace repo root or an analysis checkout (**required** for **`reference-coverage`**, **`coordination-duplicates`**, **`coverage-report`**, **`missing-artefacts`**, **`context link-graph`**). |
| `--worktree` | path | Analysis git checkout (use with **`--root worktree`**, or alone where **`diagnose zero-reference`** documents it). |

## Commands and flags (complete)

Every command the CLI accepts, with command-specific tokens. Global flags above apply to all of these unless noted.

| Command | Command-specific flags / tokens | Behaviour summary |
| ------- | ------------------------------- | ------------------ |
| `help` or first token `--help` | — | Prints usage (same as **`--help`** before the command). |
| `version` | — | Prints the CLI package version string. |
| `doctor` | **`--readiness`**, **`--inspect-config`** (optional; default if none → readiness-style check) | Does **not** create **`config.json`**. **`--readiness`**: **`cliVersion=`** (same as **`version`**), repo root, API URL, OpenCode, agents dir; **`--json`** adds **`cliVersion`** on the envelope. **`--inspect-config`**: redacted merged settings + config JSON. Both flags together runs both. |
| `config init` | **`--overwrite`** (optional) | Writes **`.easyspecs/config.json`** with defaults; may import legacy **`.easyspecs/cli.json`** once if present. Existing file unchanged unless **`--overwrite`**. |
| `config set-project-id <easyspecsProjectId>` | — | Sets **`easyspecs.easyspecsProjectId`** (creates config with defaults if missing). |
| `config set-git-remote <url>` | — | Sets **`easyspecs.defaultGitRemoteUrl`** (same bootstrap-if-missing behaviour). |
| `config dump` | — | **Deprecated.** Prints the same redacted payload as **`doctor --inspect-config`** and stderr warns to use **`doctor --inspect-config`**. |
| `auth login` | **`--email <email> --password <password>`**, optional tail **`--session-path <path>`** | Login to resolved API; writes session file. With **`--ci`**, credentials may come from **`easyspecs.auth.ciLogin`** in **`config.json`** if argv omits email/password. Tail **`--session-path`** updates **`easyspecs.cliSessionPath`** in **`config.json`**. Global **`--session-path`** overrides effective session file without editing **`config.json`**. |
| `auth logout` | — | Deletes the effective session file. |
| `auth status` | — | Reports whether a session file exists / looks populated. |
| `run synthesis` | — | Context artefact pipeline pass; **`requireOpenCode`** applies. **`--promote` / `--no-promote`** affect promotion after success. |
| `run synthesis resume-missing` | **`--worktree <path>`** (optional); repeatable **`--keys <missingKey>`** (filters rows) | Parallel missing-artefact remediation pool on an existing checkout (from **`--worktree`** or last snapshot). Omit **`--keys`** to process all missing rows. **`requireOpenCode`**. |
| `run synthesis resume-synthesis` | **`--worktree <path>`** (optional) | Same pool implementation as **`resume-missing`** when **`--keys`** is omitted (alias command path). |
| `worktree create` | — | Creates a detached analysis git worktree at **HEAD** and writes the file-backed workspace snapshot under **`.easyspecs/`**. **`--json`**: **`worktreePath`**, **`fullPath`**, **`repoRoot`**, **`folderName`**. |
| `worktree remove` | — | Removes the saved analysis worktree from disk and clears the snapshot. No-op cleanup if the path is already gone. |
| `agents materialize` | **`--worktree <path>`** (optional) | Copies bundled **`resources/harness-agents`** into the **active harness** worktree agents directory (e.g. **`<checkout>/.opencode/agents`** or **`.claude/agents`**) and records materialization in the workspace snapshot. **`requireOpenCode`** is not required. |
| `context promote` | **`--worktree <path>`** (optional) | Copies **`.easyspecs/context`** from the analysis checkout into **`<repoRoot>/.easyspecs/context`**. Fails when source and destination are the same path. |
| `context assemble-index` | **`--worktree <path>`** (optional) | Runs link-mapping on the checkout context dir, then writes **`index-application-context.json`**. |
| `workstation run` | **`--worktree <path>`** (required); **`--step <ContextSynthesisTestStep>`** or markdown **`--detail-agent <stem>`** + **`--output-basename <file>`**; optional scope **`--feature`**, **`--use-case`**, **`--entity`**, **`--infrastructure`**, **`--qa`** | One OpenCode workstation (synthesis tree **Run** / repo-surface preflight). **`requireOpenCode`**. |
| `analysis` | **`--force-new-context-analysis`** (optional; tail only). **`--no-worktree`** / **`--noworktree`** (**SRS-71**). **SRS-126:** zero-reference remediation **off by default** (`easyspecs.factory.skipRemediationPipeline: true` from **`config init`**). **`--enable-remediation-pipeline`** — opt in for this run. **`--skip-remediation-pipeline`** — explicit skip (same as default). **`--zero-reference-convergence=strict|best_effort`** (ignored when remediation is skipped). **`--upload`** / **`--skip-upload`** → usage exit; **`--synthesis-only`** → ignored + stderr deprecation (**SRS-60**); cannot combine **`--enable-remediation-pipeline`** with **`--skip-remediation-pipeline`**. | Full **Generate Context** factory — **create new context** trajectory. CLI **does not** run backend upload inside **`analysis`**; use **`upload context`** / **`upload republish`** afterward. **Promotion** after success is **automatic** unless **`--no-promote`** or checkout is already **`<repoRoot>`** (independent of **`easyspecs.analysis.promoteContextToWorkspace`**). If **`easyspecs.factory.cloudContextAnalyzed`** is **`true`** and **`--force-new-context-analysis`** is absent, exits **0** early with **`analysisSkipped`**. Stale temp-worktree registration: **`git worktree prune`** then one retry of **`git worktree add`**. |
| `update context` | **`--no-worktree`** / **`--noworktree`** (**SRS-71**). | **Update context** trajectory: git window after baseline → analysis checkout → **`changes-since-date.md`** → optional remediation → **`--promote` / `--no-promote`** → **`easyspecs.factory.updateContext.lastRunAt`**. Run **`upload context`** separately for cloud sync. Seed context should exist on **HEAD** in the checkout (the factory may copy from workspace when the detached worktree lacks context; skipped when **`--no-worktree`**). |
| `diagnose reference-coverage` | **`--root workspace`** \| **`--root worktree`**, optional **`--worktree <path>`** | Reference coverage gate; optional percent limit via **`easyspecs.diagnose.zeroReference.maxPercentNonReferenced`**. |
| `diagnose coordination-duplicates` | same | Duplicate/orphan reporting; **`easyspecs.diagnose.coordinationDuplicates.strict`**. |
| `diagnose coverage-report` | same | Writes coverage execution report path. |
| `diagnose missing-artefacts` | same | Lists missing artefacts from workspace state (JSON on stdout in human mode). |
| `diagnose zero-reference` | **`--worktree <path>`** (optional); **`--in-place`** / **`--no-worktree`** / **`--noworktree`** (**SRS-71**, **SRS-124**) — use repository root as analysis checkout; repeatable **`--paths <repo-relative>`** (subset only) | Zero-reference remediation pool (**OpenCode**). Omit **`--paths`** to remediate all zero-reference paths from coverage JSON. After a successful pool run, applies context markdown navigation links under the analysis checkout context directory (fails the command if that step reports broken links). |
| `context link-graph` | **`--root workspace`** \| **`--root worktree`**, optional **`--worktree <path>`** | Refreshes EasySpecs navigation marker blocks in **`.easyspecs/context/**/*.md`** from coordination JSON only (no agents). **`--json`** may include **`contextDir`**, **`error`**, **`brokenLinks`**. |
| `context drift <referencePath>` | **`--label <slug>`**, **`--index <path>`**, **`--dry-run`**, **`--no-worktree`** / **`--noworktree`** (**SRS-71**; not with **`--root worktree`** / **`--worktree`**). Optional **`--root` / `--worktree`** for other routing. | **Analyze drift against specs** trajectory: **`referencePath`** = file or directory of reference docs. Uses analysis checkout + OpenCode drift agent → **`.easyspecs/context/drift/drift-<slug>-<date>.md`** and reference index maintenance; global **`--promote` / `--no-promote`** apply when not **`--dry-run`**. Success stdout: drift report path. |
| `download context` | **`--force`**, **`--replace-from-cloud`** (optional; any order after **`download context`**) | Requires **`auth login`** session + **`easyspecs.easyspecsProjectId`**. Resolves linked context documents from **`GET /api/content/application/:id`**, batch-fetches content, writes UTF-8 files under **`<repo>/.easyspecs/context`**. Default skips existing paths; **`--force`** overwrites; **`--replace-from-cloud`** wipes local context files first (preserves root **`easyspecs-upload-target.json`**). **`--json`**: **`downloaded`**, **`skipped`**, **`failed`**, **`localRemoved`**. Non-zero exit if **`failed` > 0`. Full narrative under **Download context (pull from EasySpecs)** above. |
| `upload context` | — | Requires auth session + **`easyspecs.easyspecsProjectId`**. Context dir: **`<repo>/.easyspecs/context`**. Optional cloud status GET per **`easyspecs.upload.fetchContextAnalyzedInCloud`**. |
| `upload republish` | — | Same auth + project id; context dir from **`easyspecs.upload.contextDirectory`** or analysis worktree snapshot. Same optional cloud status behaviour as **`upload context`**. |
| `ace clear` | — | Deletes learnings under **`.easyspecs/context/learnings`**. |
| `ace learn` | **`--worktree <path>`** (optional) | Offline ACE learn from traces; **`requireOpenCode`**. Worktree = **`--worktree`** if it contains **`.opencode/schemas/ace`**, else workspace root. |
| `ace auto-learn` | **`--worktree <path>`** (optional) | ACE auto-learn pool; **`requireOpenCode`**. Worktree = **`--worktree`** if valid git dir, else workspace root. Concurrency capped by **`merged.pipelineOpenCode.maxConcurrentAgents`** (schema field name; mirrors **`easyspecs.workstations.maxConcurrentAi`** / legacy **`maxConcurrentOpenCodeAgents`**). |

**OpenCode:** commands that spawn agents require **`opencode`** (or configured executable) on **`PATH`** and credentials unless **`skipCredentialsCheck`** or provider keys in **`config.json`** apply (**`requireOpenCode`**).

**EasySpecs API session:** **`download context`**, **`upload context`**, and **`upload republish`** need a prior **`auth login`** (or **`--ci`** with **`easyspecs.auth.ciLogin`** in **`config.json`** when argv omits email/password on login), plus **`easyspecs.easyspecsProjectId`** for those commands.

**Unknown command:** prints help and exits with a usage error.

## Development (this repository)

Canonical source for **`@gluecharm-lab/easyspecs-cli`** lives in **`easyspecs.ai-cli`** (GitLab: `glue-charm-extended/gluecharm-extended/easyspecs.ai-cli`). The VS Code extension monorepo still contains a copy under **`packages/cli/`** until SRS-97 Step 2.

```bash
git clone git@gitlab.com:glue-charm-extended/gluecharm-extended/easyspecs.ai-cli.git
cd easyspecs.ai-cli
npm ci
npm run build
npm test
node dist/main.cjs version
```

Release: push tag **`vX.Y.Z`** matching **`package.json` `version`**; GitLab CI runs **`publish-npm`** on that tag and **fails the pipeline** if **`NPM_TOKEN`** is missing, version mismatches, or **`npm publish`** errors. Command reference for contributors: **`commands.md`**, **`error-code.md`**.

Integration scripts (require env credentials): **`scripts/testing/easyspecs-cli-*.sh`**.

## Product information

**EasySpecs** is a product for agentic documentation and requirements. The public site and app live at **easyspecs.ai** (sign-in, projects, browser workspace). This npm package is the **headless CLI** only; it does not bundle the web app.

## License

Copyright © **Spaii Edutainment SL** (Barcelona, Spain).

This package is licensed under the **Elastic License 2.0** (**ELv2**, SPDX **`Elastic-2.0`**). The complete license text ships in the **`LICENSE`** file inside the published **`@gluecharm-lab/easyspecs-cli`** package on npm.
