> For the complete documentation index, see [llms.txt](https://docs.khotan.com/llms.txt). A markdown version of any page is available at its `index.md` URL.

# OpenSpec

Two commands write a project from the repository. Neither appears in [commands](/cli/commands/), because both read files off disk and send a document rather than a flat set of flags — the same reason `files upload` sits apart. Both are listed by `khotan help`.

See [Projects](/basics/projects/) for what a project and a change are.

## Push a change

`khotan change push` reads a change directory under `openspec/changes/` and files it as an issue under the project this repository delivers.

```sh
khotan change push                          # when the repo has exactly one active change
khotan change push add-invoice-export       # name the change
khotan change push --dry-run                # report what would be filed, write nothing
```

Usage:

```txt
khotan change push [changeId] [--project <slug>] [--ref <sha>] [--dir <path>] [--dry-run] [--json]
```

Options:

- `changeId` — directory name under `openspec/changes/`. Inferred when the repo has exactly one active change; with several, the CLI names them and stops rather than guess.
- `--project` — project slug. Defaults to the `khotan:` block in `openspec/config.yaml`.
- `--ref` — commit sha recorded as the issue's provenance.
- `--dir` — where to start looking for `openspec/`. Defaults to the current directory.
- `--dry-run` — read the repo and print the result without authenticating or calling the API.

### Name the project

The change id names the change, not the contract it belongs to, so the repository states its project once:

```yaml
# openspec/config.yaml
schema: spec-driven

khotan:
  project: acme-billing
```

Without that block, and without `--project`, the push stops and asks for one.

### What gets filed

`proposal.md` becomes the issue description. `design.md` and the spec deltas become attached documents. `tasks.md` becomes a checklist. Each directory under `specs/` contributes its name as a capability label, since the capability appears nowhere inside the file itself.

The push is idempotent on `changeId`: pushing the same change again updates the issue it already filed. A change with no proposal or design content is refused — there would be nothing to read.

If the repository has an `origin` remote on a host whose URL layout is known, the issue also gets a link back to the change directory. A checkout with no remote still pushes; it just gets no link.

## Commit spec edits

`khotan projects spec commit` applies edits to a project's spec tree and commits them as one revision.

```sh
khotan projects spec commit <projectId> --file edits.json
cat edits.json | khotan projects spec commit <projectId> --file -
```

Usage:

```txt
khotan projects spec commit <projectId> --file <path> [--expected-revision <n>] [--change-summary <text>] [--json]
```

Options:

- `--file` — path to a JSON document, or `-` to read stdin.
- `--expected-revision` — the revision you edited against. Overrides the value in the document.
- `--change-summary` — why this revision exists. Overrides the value in the document.

The document is a non-empty `edits` array plus the revision it was written against:

```json
{
  "expectedRevision": 12,
  "changeSummary": "Split delivery into two phases",
  "edits": [
    { "nodeId": "…", "title": "Phase 1", "body": "…" }
  ]
}
```

`expectedRevision` is required — in the document or on the flag. It makes the commit a compare-and-swap: if the spec moved since you read it, the commit is refused with `revision_conflict` and tells you the current revision. Re-read with `khotan projects spec`, reapply, and commit again.

For a single node, `khotan projects node update` is simpler. Use `spec commit` when several nodes change together and should land as one revision.

## Next

See [projects commands](/cli/commands/projects/) for the rest of the family, and [safety](/cli/safety/) for how destructive commands are gated.
