# OpenSpec Admin & Architecture Guide

This guide is for engineers, architects, and tooling maintainers who need to understand OpenSpec deeply — how it is built, how its subsystems fit together, how it stores and validates data, what its security and privacy posture is, and how to extend it (new schemas, new AI-tool adapters, new shells). It is the companion to the [User Guide](Fission-AI__OpenSpec__UserGuide.md); end-user task instructions live there.

OpenSpec is a Node.js/TypeScript command-line application distributed on npm as `@fission-ai/openspec` (current version `1.4.1`, MIT licensed). It is intentionally **infrastructure-light**: no server, no database, no network dependency for its core workflow. State is plain Markdown and small JSON/YAML files on disk. That design choice — "just files" — is the central architectural principle and the reason the tool is portable and reviewable.

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [System Architecture](#2-system-architecture)
3. [Requirements & Runtime](#3-requirements--runtime)
4. [Repository Structure](#4-repository-structure)
5. [Subsystem Deep Dive](#5-subsystem-deep-dive)
6. [The Artifact Graph & Schema Engine](#6-the-artifact-graph--schema-engine)
7. [Data Model & On-Disk State](#7-data-model--on-disk-state)
8. [Configuration Reference](#8-configuration-reference)
9. [Security Analysis](#9-security-analysis)
10. [Privacy & Telemetry](#10-privacy--telemetry)
11. [Extending OpenSpec](#11-extending-openspec)
12. [Build, Test & Release](#12-build-test--release)
13. [Operations & Troubleshooting](#13-operations--troubleshooting)
14. [Code Quality & Conventions](#14-code-quality--conventions)
15. [Potential Improvements](#15-potential-improvements)
16. [Quick Reference](#16-quick-reference)

---

## 1. Executive Summary

OpenSpec implements **spec-driven development** as a thin CLI over a Markdown filesystem. Its job is to give AI coding agents (and humans) a deterministic, reviewable workflow:

```text
proposal  →  specs  →  design  →  tasks  →  implement  →  archive (specs sync)
```

The CLI does five things well:

1. **Scaffolds** change directories and artifact files from schema-defined templates.
2. **Validates** specs and change deltas against a strict structural grammar (Zod-backed).
3. **Tracks** artifact and task completion by reading the filesystem.
4. **Generates** AI-tool integrations — "skills" and `/opsx-*` slash commands — for 30+ assistants via a pluggable adapter registry.
5. **Archives** completed changes by applying their spec deltas back into the canonical specs.

Architecturally it is a layered command application:

- A **commander**-based CLI layer (`src/cli/index.ts`) wires global hooks (telemetry, color) and registers commands.
- A **commands** layer (`src/commands/`) holds command implementations and the artifact-workflow commands (`src/commands/workflow/`).
- A **core** layer (`src/core/`) holds the domain logic: init/update, archive, validation, the artifact-graph/schema engine, command generation, completions, workspaces, context stores, and global config.
- Cross-cutting **utils** (`src/utils/`) and **telemetry** (`src/telemetry/`).

Key non-functional properties: cross-platform (explicit Windows handling for moves and config paths), offline-first, privacy-respecting (opt-out anonymous telemetry only), and forward-compatible (config schemas use `passthrough()` to preserve unknown fields).

---

## 2. System Architecture

### Layered view

```text
┌──────────────────────────────────────────────────────────────┐
│  Entry: bin/openspec.js → dist/cli/index.js (runCli)          │
├──────────────────────────────────────────────────────────────┤
│  CLI LAYER  src/cli/index.ts                                  │
│   • commander Program: name/version/global --no-color         │
│   • preAction hook  → NO_COLOR, telemetry notice, trackCmd    │
│   • postAction hook → telemetry shutdown/flush                │
│   • registers all commands & subcommands                      │
├───────────────┬───────────────────────────┬──────────────────┤
│ COMMANDS      │ WORKFLOW COMMANDS          │ REGISTER-style    │
│ src/commands/ │ src/commands/workflow/     │ commands          │
│  change, spec │  status, instructions,     │ config, schema,   │
│  show,        │  templates, schemas,       │ workspace,        │
│  validate,    │  new-change, set-change,   │ context-store,    │
│  completion,  │  apply-instructions        │ initiative, spec  │
│  feedback     │                            │                   │
├───────────────┴───────────────────────────┴──────────────────┤
│  CORE LAYER  src/core/                                         │
│   init • update • archive • list • view • validation/         │
│   artifact-graph/ (schema engine) • command-generation/       │
│   completions/ • workspace/ • context-store/ • collections/   │
│   global-config • config • config-schema • planning-home      │
│   specs-apply • change-metadata/ • templates/ • shared/        │
├──────────────────────────────────────────────────────────────┤
│  UTILS  src/utils/   • TELEMETRY src/telemetry/ (PostHog)     │
├──────────────────────────────────────────────────────────────┤
│  FILESYSTEM (the only persistence)                            │
│   project: openspec/{specs,changes,changes/archive,config.yaml}│
│   global:  $XDG_CONFIG_HOME/openspec/config.json              │
│   tools:   .claude/ .cursor/ … (generated skills & commands)  │
└──────────────────────────────────────────────────────────────┘
```

### Request lifecycle (every invocation)

1. `bin/openspec.js` loads the compiled `dist/cli/index.js` and calls `runCli(process.argv)`.
2. commander parses argv. The root program's **`preAction` hook** fires first: it applies `--no-color` (setting `NO_COLOR=1`), shows the one-time telemetry notice, and records the command path (e.g. `change:show`) via `trackCommand`.
3. The matched command's `.action()` executes the domain logic, usually delegating to a class or function in `src/core/`.
4. Errors bubble to a per-command `try/catch` that prints via `ora().fail(...)` and sets a non-zero exit code.
5. The **`postAction` hook** flushes and shuts down the PostHog telemetry client.

### Notable design decisions

- **Verb-first command surface** (`list`, `show`, `validate`, `archive`) with **deprecated noun-first aliases** (`change ...`, `spec ...`) kept for backward compatibility and emitting deprecation warnings via their own `preAction` hooks.
- **Lazy imports** of heavy or rarely-used modules (`await import('../core/init.js')`, `@inquirer/prompts`) keep cold-start fast and avoid loading interactive deps in non-interactive paths.
- **Schema-driven everything**: artifact requirements, templates, and the apply phase are data (`schema.yaml`), not code, so workflows are customizable without forking the binary.

---

## 3. Requirements & Runtime

| Item | Value |
|---|---|
| Runtime | Node.js `>= 20.19.0` (enforced via `package.json` `engines`) |
| Language | TypeScript `^5.9.3`, compiled to ESM (`"type": "module"`) |
| Module system | ESM throughout; imports use `.js` extensions on TS source |
| Package manager (dev) | pnpm (build/release scripts assume `pnpm`) |
| Executable | `openspec` → `bin/openspec.js` |
| Published files | `dist/`, `bin/`, `schemas/`, `scripts/postinstall.js` |
| OS support | macOS, Linux, Windows (first-class, with platform-specific code paths) |

### Runtime dependencies

| Dependency | Version | Role |
|---|---|---|
| `commander` | `^14.0.0` | CLI command/option parsing, hooks |
| `@inquirer/core`, `@inquirer/prompts` | `^10`, `^7.8` | Interactive prompts (confirm, select, custom searchable multi-select) |
| `chalk` | `^5.5.0` | Terminal coloring (honors `NO_COLOR`) |
| `ora` | `^8.2.0` | Spinners and success/fail status lines |
| `zod` | `^4.0.17` | Schema validation for config, change/spec data models, workspace state |
| `yaml` | `^2.8.2` | Parse/serialize `schema.yaml`, `config.yaml`, metadata |
| `fast-glob` | `^3.3.3` | Glob resolution for artifact outputs (e.g. `specs/**/*.md`) |
| `cross-spawn` | `7.0.6` | Cross-platform process spawning (editors, agents, openers) |
| `posthog-node` | `^5.20.0` | Anonymous telemetry transport |

Dev tooling: `vitest` (tests + coverage + UI), `eslint` + `typescript-eslint`, `@changesets/cli` (versioning/release), `@types/node`.

> [!NOTE]
> Because the project is pure ESM, `import.meta.url` and `createRequire` are used to locate the package's own `package.json` (for the version string) and to load JSON. There is no CommonJS entry point.

---

## 4. Repository Structure

```text
OpenSpec/
├── bin/openspec.js              # Published CLI shim → dist/cli/index.js
├── build.js                     # Build script (invoked by `pnpm build`)
├── package.json                 # name, bin, engines, scripts, deps
├── src/
│   ├── index.ts                 # Public exports (cli + core)
│   ├── cli/index.ts             # commander program, hooks, command wiring
│   ├── commands/                # Command implementations
│   │   ├── change.ts spec.ts show.ts validate.ts completion.ts feedback.ts
│   │   ├── config.ts schema.ts workspace.ts context-store.ts initiative.ts
│   │   └── workflow/            # Artifact-driven workflow commands
│   │       ├── status.ts instructions.ts templates.ts schemas.ts
│   │       ├── new-change.ts set-change.ts initiative-link.ts shared.ts
│   ├── core/
│   │   ├── init.ts update.ts archive.ts list.ts view.ts
│   │   ├── config.ts config-schema.ts config-prompts.ts global-config.ts
│   │   ├── available-tools.ts legacy-cleanup.ts migration.ts profiles.ts
│   │   ├── planning-home.ts change-status-policy.ts specs-apply.ts
│   │   ├── validation/          # Spec/change validators
│   │   ├── artifact-graph/      # Schema engine (see §6)
│   │   ├── command-generation/  # Tool adapters + generator (see §5)
│   │   │   └── adapters/        # 27 per-tool adapters
│   │   ├── completions/         # Shell completion generators/installers
│   │   ├── workspace/           # Workspace foundation, registry, openers
│   │   ├── context-store/       # Context store foundation/registry/ops
│   │   ├── collections/         # Collections + initiatives runtime
│   │   ├── change-metadata/     # Per-change metadata schema
│   │   ├── schemas/             # Zod models: spec, change, base
│   │   ├── shared/              # Tool detection + skill generation
│   │   ├── templates/           # Skill templates facade
│   │   └── styles/              # Palette / ANSI styling
│   ├── ui/                      # Welcome screen, ascii patterns
│   ├── prompts/                 # Custom searchable multi-select prompt
│   ├── telemetry/               # PostHog client + config
│   └── utils/                   # change-utils, file-system, item-discovery…
├── schemas/                     # Built-in schemas (published)
│   ├── spec-driven/             # Default schema + templates
│   └── workspace-planning/      # Workspace planning schema + templates
├── scripts/                     # postinstall.js, pack-version-check, update-flake
├── openspec/                    # OpenSpec dogfooding its own specs/changes
└── test/                        # vitest suites (mirrors src/ layout)
```

> [!TIP]
> The project **dogfoods itself**: its own `openspec/` directory holds the specs and changes that drove OpenSpec's development. The archived changes under `openspec/changes/archive/` are an excellent source of historical design rationale.

---

## 5. Subsystem Deep Dive

### 5.1 Init (`src/core/init.ts`)

`InitCommand.execute(targetPath)` is the most involved flow:

1. **Validate** the target path and check write permissions (`FileSystemUtils.ensureWritePermissions`); throws `Insufficient permissions to write to <path>` on failure. Detects **extend mode** if `openspec/` already exists.
2. **Legacy cleanup**: `detectLegacyArtifacts` scans for files from older OpenSpec layouts; in interactive mode it prompts ("Upgrade and clean up legacy files?"), in `--force`/non-interactive mode it cleans automatically. Cleanup only removes OpenSpec-managed markers/commands — it never deletes user content.
3. **Tool detection**: `getAvailableTools` (`src/core/available-tools.ts`) scans for each tool's `skillsDir` (or custom `detectionPaths`) to pre-select tools.
4. **Migration**: in extend mode, `migrateIfNeeded` upgrades the project to the profile system.
5. **Tool selection**: `--tools all|none|<ids>` resolves non-interactively; otherwise a searchable multi-select. Validation rejects unknown IDs and the combination of reserved (`all`/`none`) with specific IDs.
6. **Directory structure**: creates `openspec/{specs,changes,changes/archive}`.
7. **Generate skills & commands** per tool (see 5.2).
8. **Create config** (`openspec/config.yaml`) and print a success summary.

The catalog of supported tools is the `AI_TOOLS` array in `src/core/config.ts` — each entry maps a display name, stable `value` ID, and `skillsDir` (e.g. `claude → .claude`, `cursor → .cursor`, `github-copilot → .github` with explicit `detectionPaths`).

### 5.2 Command generation (`src/core/command-generation/`)

A small registry/adapter pattern turns abstract command/skill content into tool-specific files:

- `CommandAdapterRegistry` (`registry.ts`) maps a tool ID to a `ToolCommandAdapter`.
- `generateCommand`/`generateCommands` (`generator.ts`) render `CommandContent[]` through an adapter into `GeneratedCommand[]` ready to write.
- `adapters/` contains **27 adapters** (`claude.ts`, `cursor.ts`, `windsurf.ts`, `codex.ts`, `gemini.ts`, `github-copilot.ts`, `opencode.ts`, `roocode.ts`, …). Each encodes that tool's command file format, directory layout, and slash-command naming (e.g. Claude-style `/opsx:foo` vs hyphen-style `/opsx-foo`).

The set of installed workflows is profile-driven (`src/core/profiles.ts`): `CORE_WORKFLOWS` vs `ALL_WORKFLOWS`. Workflow IDs map to skill directories via `WORKFLOW_TO_SKILL_DIR` in `init.ts`: `explore→openspec-explore`, `new→openspec-new-change`, `continue→openspec-continue-change`, `apply→openspec-apply-change`, `ff→openspec-ff-change`, `sync→openspec-sync-specs`, `archive→openspec-archive-change`, `bulk-archive→openspec-bulk-archive-change`, `verify→openspec-verify-change`, `onboard→openspec-onboard`, `propose→openspec-propose`.

> [!NOTE]
> Several community issues track per-tool command formatting (e.g. Codex generating a deprecated `/opsx:*` form, Cursor/Windsurf body references). The adapter pattern is precisely the seam where these per-tool quirks are fixed.

### 5.3 Archive (`src/core/archive.ts`)

`ArchiveCommand.execute(changeName, options)`:

1. Verifies `openspec/changes/` exists and the named change directory exists (or prompts a `select` picker that shows per-change task progress).
2. Unless `--no-validate`, validates the proposal (informational, non-blocking) and any delta spec files (blocking on errors).
3. Reports task progress; warns on incomplete tasks (confirm unless `--yes`).
4. Unless `--skip-specs`, computes spec updates (`findSpecUpdates`), then in a **prepare-then-write** pattern: builds every updated spec in memory and validates it; only if **all** pass does it write. On any failure it aborts with "No files were changed."
5. Moves the change directory to `changes/archive/<YYYY-MM-DD>-<name>/` via `moveDirectory`, which falls back to **copy-then-remove** on `EPERM`/`EXDEV` (Windows-safe).

This transactional discipline (validate-all-before-write) is the key safety property of archiving.

### 5.4 Spec application (`src/core/specs-apply.ts`)

Implements the delta grammar that turns a change's future-state spec files into edits on canonical specs. It recognizes `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` sections, rebuilds the full spec, and reports counts (`+ added, ~ modified, - removed, → renamed`). RENAMED uses FROM:/TO: header matching; failures raise precise errors (e.g. `<specName> RENAMED failed for header ...`).

### 5.5 Validation (`src/core/validation/`)

`Validator` validates change proposals, change delta specs, and full rebuilt specs. The structural rules enforced (surfaced in templates and errors): requirements as `### Requirement:`, scenarios as exactly `#### Scenario:` (four hashes — three is silently skipped, which validation flags), at least one scenario per requirement, and SHALL/MUST normative phrasing. Bulk validation (`src/commands/validate.ts`) runs items concurrently, bounded by `--concurrency` / `OPENSPEC_CONCURRENCY` (default 6).

### 5.6 Completions (`src/core/completions/`)

A factory (`CompletionFactory`) builds per-shell generators and installers for **bash, zsh, fish, powershell**. `COMMAND_REGISTRY` (`command-registry.ts`) is a declarative description of every command, subcommand, flag, and positional type — the single source for generating static completion scripts. `CompletionProvider` supplies **dynamic** completions (change IDs, spec IDs, schema names) with a 2-second TTL cache to avoid hammering the filesystem during tab completion. The hidden `__complete <type>` command is the runtime hook shells call.

### 5.7 Workspaces, context stores, initiatives (beta)

These three subsystems implement cross-repo coordination and share a Zod-validated, file-backed registry pattern:

- **`workspace/`** — `foundation.ts` (naming/validation), `registry.ts`, `state-io.ts`, `openers.ts` (launch Codex/Claude/Copilot/editor via `cross-spawn`), `open-surface.ts` (inject guidance markers into `AGENTS.md`), `skills.ts`.
- **`context-store/`** — `foundation.ts`, `registry.ts`, `binding.ts`, `operations.ts`, `errors.ts` (typed `ContextStoreError`). Stores can be Git-initialized (`--init-git`).
- **`collections/initiatives/`** — initiative collection, schema, operations, and resolution; `planning-home.ts` resolves whether the current directory is a repo-local or workspace planning home.

`change-status-policy.ts` adapts behavior between `repo-local` and `workspace-planning` modes (allowed edit roots, affected-area requirements, agent constraints).

> [!WARNING]
> The workspace subsystem carries the bulk of the project's defensive validation (path-traversal guards in `collections/runtime.ts` and `workspace/foundation.ts`). It is also the area most actively in flux — see the `workspace-reimplementation-roadmap` change and the `context-store-and-initiatives` initiative in the dogfooded `openspec/`.

---

## 6. The Artifact Graph & Schema Engine

The schema engine (`src/core/artifact-graph/`) is the conceptual core of OpenSpec. A **schema** is a `schema.yaml` describing artifacts and their dependencies; the engine loads it, builds a dependency graph, detects completion state from the filesystem, and generates per-artifact instructions.

### Schema shape (the default `spec-driven`)

```yaml
name: spec-driven
version: 1
description: Default OpenSpec workflow - proposal → specs → design → tasks
artifacts:
  - id: proposal
    generates: proposal.md
    template: proposal.md
    requires: []
  - id: specs
    generates: "specs/**/*.md"     # glob — resolved via fast-glob
    template: spec.md
    requires: [proposal]
  - id: design
    generates: design.md
    template: design.md
    requires: [proposal]
  - id: tasks
    generates: tasks.md
    template: tasks.md
    requires: [specs, design]
apply:
  requires: [tasks]
  tracks: tasks.md
  instruction: |
    Read context files, work through pending tasks, mark complete as you go.
```

### Engine components

| Module | Responsibility |
|---|---|
| `types.ts` | Zod schemas: `ArtifactSchema`, `SchemaYamlSchema`, `Artifact`, `SchemaYaml` |
| `schema.ts` | `loadSchema`/`parseSchema`; raises `SchemaValidationError` on invalid schema, duplicate artifact IDs, or **cyclic dependencies** |
| `graph.ts` | `ArtifactGraph` — build from schema, query artifacts and dependencies |
| `state.ts` | `detectCompleted` — which artifacts exist on disk |
| `outputs.ts` | `artifactOutputExists`, `resolveArtifactOutputs`, `isGlobPattern` (glob vs literal) |
| `resolver.ts` | `resolveSchema`, `listSchemas`, `getSchemaDir`, project/user/package dir resolution; `SchemaLoadError` |
| `instruction-loader.ts` | `loadChangeContext`, `generateInstructions`, `formatChangeStatus` — produce the enriched, XML-tagged instructions the agent consumes |

### Schema resolution precedence

```text
project-local schema   (openspec/… in repo)   ← highest priority
        ▲
user override schema    ($XDG_DATA_HOME/openspec/…)
        ▲
package schema          (schemas/ shipped in the npm package)  ← default
```

`openspec schemas` labels the winning source (`(project)`, `(user override)`, or none for package). A change records its schema in metadata, so downstream commands auto-detect it.

### How commands consume the graph

- **`status`** → `loadChangeContext` + `formatChangeStatus` → per-artifact `done`/`ready`/`blocked` (blocked artifacts list their missing deps).
- **`instructions <artifact>`** → `generateInstructions` emits a structured block (`<task>`, `<project_context>`, `<rules>`, `<dependencies>`, `<output>`, `<template>`, `<unlocks>`) the agent fills in. Blocked artifacts emit a `<warning>` with missing dependencies.
- **`instructions apply`** → reads the schema's `apply` block (`requires`, `tracks`, `instruction`), parses `tasks.md` checkboxes, and reports `blocked` / `ready` / `all_done` with progress counts.
- **`templates`** → resolves each artifact's template path and its source tier.

---

## 7. Data Model & On-Disk State

OpenSpec persists **only** to the filesystem. There are three storage scopes.

### Project scope (committed to the repo)

```text
openspec/
├── config.yaml                       # project config (default schema, context, rules)
├── specs/<capability>/spec.md        # canonical specs (what IS)
└── changes/
    ├── <change-name>/
    │   ├── proposal.md
    │   ├── design.md                 # optional
    │   ├── tasks.md                  # tracked checkbox list
    │   ├── README.md                 # optional (from --description)
    │   ├── .openspec.yaml            # per-change metadata (schema, initiative, areas)
    │   └── specs/<capability>/spec.md# future-state delta spec
    └── archive/<YYYY-MM-DD>-<name>/  # archived changes
```

- **Change metadata** (`.openspec.yaml`, modeled in `src/core/change-metadata/`): records the change's `schema`, optional `initiative` link (`{store, id}`), `affected_areas`, and `goal`. Read/written via `readChangeMetadata`/`writeChangeMetadata` in `src/utils/change-metadata.ts`.
- **Tasks** are parsed from `- [ ]` / `- [x]` checkbox lines; only properly formatted checkboxes are tracked.
- **Specs** follow the requirement/scenario grammar enforced by validation.

### Global scope (user-level)

`global-config.ts` resolves a `config.json` under the XDG config dir:

| Platform | Config dir |
|---|---|
| Any, if `XDG_CONFIG_HOME` set | `$XDG_CONFIG_HOME/openspec/` |
| macOS/Linux | `~/.config/openspec/` |
| Windows | `%APPDATA%\openspec\` (fallback `~/AppData/Roaming/openspec/`) |

A separate **data** dir holds user schema overrides (XDG_DATA_HOME → `~/.local/share/openspec/` → `%LOCALAPPDATA%\openspec\`). Config loading merges with defaults (deep-merging `featureFlags`) and tolerates missing/invalid files by falling back to defaults (logging only on JSON syntax errors).

### Tool scope (generated)

Per selected AI tool, OpenSpec writes skills and slash commands into that tool's directory (`.claude/`, `.cursor/`, `.github/`, etc.). These are OpenSpec-managed and regenerated by `update`.

---

## 8. Configuration Reference

### Global config (`config.json`) — schema in `config-schema.ts`

The Zod `GlobalConfigSchema` uses `.passthrough()` to preserve unknown keys for forward compatibility.

| Key | Type | Default | Meaning |
|---|---|---|---|
| `profile` | `"core" \| "custom"` | `core` | Which workflow set to install |
| `delivery` | `"both" \| "skills" \| "commands"` | `both` | Install skills, slash commands, or both |
| `featureFlags` | `Record<string, boolean>` | `{}` | Boolean feature toggles |
| `workflows` | `string[]` | (unset) | Explicit workflow list (advanced) |

`config set` validates key paths (`validateConfigKeyPath`): unknown top-level keys are rejected unless `--allow-unknown`; `featureFlags.<flag>` is the only nested form allowed (booleans, no deeper nesting). Values auto-coerce (`coerceValue`): `"true"/"false"` → boolean, numeric strings → number, else string; `--string` forces string storage.

### Project config (`openspec/config.yaml`)

Serialized by `config-prompts.ts` with helpful comments. Holds the project's default `schema` (required) plus optional `context` (project background shown to the AI) and per-artifact `rules`.

### Environment variables

| Variable | Read at | Effect |
|---|---|---|
| `OPENSPEC_TELEMETRY` | `src/telemetry/index.ts` | `0` disables telemetry |
| `DO_NOT_TRACK` | `src/telemetry/index.ts` | `1` disables telemetry (standard) |
| `CI` | `src/telemetry/index.ts` | `true` auto-disables telemetry |
| `NO_COLOR` | `src/cli/index.ts`, shared | Disable ANSI color (also `--no-color`) |
| `OPENSPEC_CONCURRENCY` | `src/commands/validate.ts` | Default bulk-validation parallelism (else 6) |
| `XDG_CONFIG_HOME` | `global-config.ts` | Override config dir |
| `XDG_DATA_HOME` | `global-config.ts` | Override data dir (schema overrides) |
| `APPDATA` / `LOCALAPPDATA` | `global-config.ts` | Windows config/data dir resolution |
| `EDITOR` / `VISUAL` | `src/commands/config.ts` | Editor for `config edit` |
| `CODEX_HOME` | `adapters/codex.ts` | Codex adapter target dir |

---

## 9. Security Analysis

OpenSpec's attack surface is small (a local CLI over local files, no server, no auth), but it does spawn processes, write files into project and tool directories, validate untrusted Markdown, and (optionally) make network calls for telemetry. Assessment by concern:

### Filesystem & path traversal

- **Change-name validation** (`validateChangeName`, `src/utils/change-utils.ts`) is applied before any path is built from a change name, explicitly to prevent path traversal. `validateChangeExists` re-validates the name format before touching the filesystem.
- The **collections/workspace runtime** (`collections/runtime.ts`, `workspace/foundation.ts`) enforces a strict path policy: no NUL bytes, no empty/`.`/`..` segments, no path separators in IDs, forward-slashes only, no Windows drive paths, and "path escapes mount" rejection. This is the most defensively coded area of the project.
- **Write permissions** are checked before init writes anything (`Insufficient permissions to write to <path>`).

### Process execution

- Editors and agent openers are launched via **`cross-spawn`** (workspace `openers.ts`, `config edit`). Inputs are tool IDs/paths from local config rather than arbitrary remote strings, limiting injection risk, but operators integrating new openers should keep argument arrays (not shell strings).

### Untrusted input parsing

- All structured inputs (`schema.yaml`, `config.json`, change/spec metadata, workspace state) are parsed through **Zod schemas** with explicit error messages, so malformed input fails loudly rather than corrupting state.
- Spec/change Markdown is parsed by the validator with a strict grammar; malformed deltas are rejected at validate/archive time.

### Transactional safety

- Archive uses **validate-all-then-write** and **copy-then-remove** fallbacks, minimizing the chance of half-applied state even on Windows `EPERM`.

### Secrets

- The embedded PostHog key (`phc_...` in `src/telemetry/index.ts`) is a **public, write-only ingest key** (documented in-code as safe to embed; it can send events, not read data). There are no other credentials in the codebase.

> [!CAUTION]
> The generated skills and `/opsx-*` commands are instructions executed by AI agents with whatever permissions you grant those agents. The security boundary for *actions taken* is your AI tool's permission model, not OpenSpec. Review what your agent is allowed to do (file writes, shell execution) independently.

> [!TIP]
> For locked-down/air-gapped environments, set `OPENSPEC_TELEMETRY=0` (or `DO_NOT_TRACK=1`) globally. With telemetry off, OpenSpec's core workflow makes no outbound network connections.

---

## 10. Privacy & Telemetry

Telemetry lives entirely in `src/telemetry/` and is privacy-first by design:

- **What is sent**: only the command name (e.g. `change:show`), the OpenSpec version, and a `surface: 'cli'` tag — keyed to a random UUID generated on first run and stored in telemetry config. `$ip` is explicitly set to `null`.
- **What is never sent**: command arguments, file paths, file contents, repo names, or your IP.
- **Transport**: `posthog-node` to `https://edge.openspec.dev` (a reverse proxy on the project's own domain to avoid ad-blocker breakage), with hardened settings — `flushAt: 1`, `flushInterval: 0`, `fetchRetryCount: 0`, `requestTimeout: 1000ms`, remote config/surveys disabled.
- **Failure isolation**: a `safeTelemetryFetch` wrapper swallows all network errors and returns a synthetic `204`, so telemetry can never surface noise or break the CLI. `shutdown()` flushes on exit and never throws.
- **Opt-out**: `OPENSPEC_TELEMETRY=0`, `DO_NOT_TRACK=1`, or `CI=true`. A one-time notice is printed on first run.

---

## 11. Extending OpenSpec

### Add or customize a workflow schema

```bash
openspec schema fork spec-driven my-flow     # copy into project to edit
openspec schema init lite --artifacts proposal,tasks --default
openspec schema validate my-flow --verbose   # check structure & templates
```

Edit the resulting `schema.yaml` (artifacts, `requires`, `generates`, `template`, `apply`) and the template files under its `templates/` directory. The engine validates for duplicate IDs and dependency cycles on load. Project-local schemas override user and package schemas.

### Add a new AI-tool adapter

1. Add an entry to `AI_TOOLS` in `src/core/config.ts` (name, `value` ID, `skillsDir`, optional `detectionPaths`).
2. Create `src/core/command-generation/adapters/<tool>.ts` exporting a `ToolCommandAdapter` that encodes the tool's file format and slash-command convention.
3. Register it in `adapters/index.ts` and the `CommandAdapterRegistry`.
4. Add tests under `test/core/command-generation/`. Existing adapters (claude, cursor, codex…) are the templates to copy.

> [!NOTE]
> A tool can be **skills-only** (no command adapter) — Kimi CLI is the reference example. In that case give it a `skillsDir` but no adapter.

### Add a new shell for completions

Implement a generator (`CompletionGenerator`) and installer (`CompletionInstaller`) under `src/core/completions/{generators,installers}/`, then wire both into `CompletionFactory` and add the shell to `SUPPORTED_SHELLS`. The declarative `COMMAND_REGISTRY` already supplies the command metadata.

---

## 12. Build, Test & Release

### Scripts (`package.json`)

| Script | Command | Purpose |
|---|---|---|
| `build` | `node build.js` | Compile TS → `dist/` (ESM) |
| `dev` | `tsc --watch` | Type-check/compile on save |
| `dev:cli` | `pnpm build && node bin/openspec.js` | Build then run the CLI |
| `lint` | `eslint src/` | Lint source |
| `test` | `vitest run` | Run the full test suite |
| `test:watch` / `test:ui` / `test:coverage` | vitest variants | Iterative testing / UI / coverage |
| `prepare` / `prepublishOnly` | `pnpm run build` | Ensure a fresh build before pack/publish |
| `postinstall` | `node scripts/postinstall.js` | Runs after install (setup hints) |
| `check:pack-version` | `node scripts/pack-version-check.mjs` | Verify packaged version consistency |
| `release` / `release:ci` | changesets publish | Version + publish (gated by pack-version check) |

### Testing

Tests use **vitest** and mirror the `src/` layout under `test/` (`test/core/`, `test/commands/`, `test/commands/workflow/`, `test/core/completions/`, `test/telemetry/`, `test/cli-e2e/`). E2E tests build the CLI and invoke `dist/cli/index.js` via `test/helpers/run-cli.ts` (which errors `CLI entry point missing after build. Expected dist/cli/index.js` if you forgot to build). The telemetry suite mocks `posthog-node` and asserts opt-out behavior and the silent-failure fetch wrapper.

### Versioning & release

Versioning is managed by **Changesets** (`@changesets/cli` with `@changesets/changelog-github`). The release flow runs `check:pack-version` first, then `changeset publish`. Tests, build, and publish all assume **pnpm**.

> [!IMPORTANT]
> Because the package ships `schemas/` and `bin/` alongside `dist/`, any change to schema files or the CLI shim must be reflected in the `files` allow-list in `package.json` or it won't be published.

---

## 13. Operations & Troubleshooting

### Health & sanity checks

```bash
openspec --version                 # confirm installed version
openspec schemas                   # confirm schema resolution
openspec schema validate spec-driven --verbose
openspec validate --all --strict   # validate all project artifacts
openspec workspace doctor          # workspace resolvability (beta)
openspec context-store doctor      # context-store registration (beta)
```

### Common operational issues

| Symptom | Likely cause | Resolution |
|---|---|---|
| `No OpenSpec changes directory found` | Not in a project root / no `openspec/` | `cd` to root or `openspec init` |
| `Insufficient permissions to write to <path>` | Init target not writable | Fix perms; run from a writable location |
| Archive aborts: "No files were changed" | Delta spec validation failed | `validate <change> --strict`, fix Markdown, retry |
| `update` insists on `workspace update` | Run inside a workspace | Use `openspec workspace update` |
| `update` crashes with foreign `workspace.yaml` | Filename collision (e.g. Dagster) — issue #1164 | Upgrade; report via `openspec feedback` |
| Scenario ignored during validation | Scenario not using `####` | Use exactly four hashes |
| Slow/hanging bulk validation | Concurrency mis-tuned for machine | Set `--concurrency` / `OPENSPEC_CONCURRENCY` |
| Generated slash command wrong format | Per-tool adapter quirk (issues #1108/#1129) | Upgrade; the adapter is the fix point |

### Cross-platform notes

- **Windows**: directory moves fall back to copy+remove on `EPERM`/`EXDEV`; config/data dirs use `%APPDATA%`/`%LOCALAPPDATA%`. Shell detection covers PowerShell (`PSModulePath`, `COMSPEC`, `WT_SESSION`).
- **Color**: honor `NO_COLOR` in scripts and CI to keep logs clean.

---

## 14. Code Quality & Conventions

- **TypeScript strictness** with ESM and `.js` import specifiers on source files; `import.meta`/`createRequire` for package introspection.
- **Validation-first**: Zod is the gatekeeper for every structured input; errors carry actionable messages.
- **Registry/adapter patterns** for the two most extensible surfaces (AI tools, shells), keeping per-target quirks isolated and testable.
- **Lazy imports** for interactive deps and heavyweight command modules.
- **Defensive filesystem code**: path-policy enforcement, write-permission checks, transactional archive, Windows fallbacks.
- **Privacy/robustness in telemetry**: silent failure, bounded timeouts, explicit IP suppression.
- **Deprecation discipline**: noun-first commands remain but warn; deprecated flags (`--requirements-only`) alias their replacements.
- **Self-hosting**: the project manages its own development with OpenSpec, so specs/changes double as living architecture docs and regression context.

The main complexity hotspots are `init.ts` (multi-phase setup with many branches) and the workspace/context-store/initiative subsystems (large, beta, rapidly evolving). These are the areas to read tests alongside source.

---

## 15. Potential Improvements

Observations from the source, offered as forward-looking suggestions (not defects):

- **Stabilize the workspace/context-store/initiative beta.** It is the largest, most in-flux area, with deferred lifecycle commands (apply/verify/archive) and an explicit reimplementation roadmap. Locking the data model and promoting it out of beta would reduce the per-tool and per-mode special-casing in `change-status-policy.ts`.
- **Consolidate per-tool slash-command formatting.** Multiple community issues (#1108, #1129, #1072, #1007) stem from per-adapter naming/format drift. A shared conformance test matrix across all adapters would catch these before release.
- **Surface `--areas`/workspace constraints earlier.** Several workflow commands validate workspace-only options (`--areas`) only after resolving the planning home; earlier guards with clearer messages would improve agent ergonomics.
- **Numbered change names** (issue #1169) and **custom output directories** (#1147) are requested workflow flexibility that the schema engine could accommodate via additional `generates`/naming policy.
- **Document the schema authoring contract publicly.** The `schema.yaml` shape (artifacts, `requires`, `apply.tracks`) is powerful but currently best learned by reading `spec-driven/schema.yaml`; a reference page would lower the barrier to custom workflows.

---

## 16. Quick Reference

### Architecture at a glance

| Layer | Location | Responsibility |
|---|---|---|
| CLI | `src/cli/index.ts` | commander program, global hooks, command wiring |
| Commands | `src/commands/` (+`workflow/`) | Command implementations |
| Core | `src/core/` | Domain logic, schema engine, generators |
| Utils/Telemetry | `src/utils/`, `src/telemetry/` | Cross-cutting helpers, analytics |
| Persistence | filesystem | `openspec/`, global `config.json`, tool dirs |

### Key files

| File | Why it matters |
|---|---|
| `src/cli/index.ts` | The command map and lifecycle hooks |
| `src/core/init.ts` | Project setup + tool integration |
| `src/core/archive.ts` | Transactional change archival + spec sync |
| `src/core/artifact-graph/` | Schema engine (the conceptual core) |
| `src/core/command-generation/adapters/` | 27 AI-tool adapters |
| `src/core/config.ts` (`AI_TOOLS`) | Supported-tool catalog |
| `schemas/spec-driven/schema.yaml` | Default workflow definition |
| `src/telemetry/index.ts` | Privacy model + opt-out logic |

### Default schema artifacts

```text
proposal → specs → design → tasks → (apply: tracks tasks.md)
```

### Storage locations

| Scope | Path |
|---|---|
| Project | `openspec/{config.yaml,specs/,changes/,changes/archive/}` |
| Global config | `$XDG_CONFIG_HOME/openspec/config.json` (Win: `%APPDATA%`) |
| User schema overrides | `$XDG_DATA_HOME/openspec/` (Win: `%LOCALAPPDATA%`) |
| Tool integrations | `.claude/`, `.cursor/`, `.github/`, … |

### Opt out of telemetry

```bash
export OPENSPEC_TELEMETRY=0   # or DO_NOT_TRACK=1 ; auto-off when CI=true
```

---

_Generated June 2026 from Fission-AI/OpenSpec at 1b06fdd. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
