@aligndottech/cli 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,11 +5,20 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
6
  [![Node](https://img.shields.io/node/v/@aligndottech/cli.svg)](https://nodejs.org)
7
7
 
8
- **AI agents are shipping code from decisions they can't see.**
8
+ **Your AI agents know the code. They don't know the company.**
9
9
 
10
- [Align](https://align.tech) captures the reasoning behind every engineering choice - across Git, GitHub, GitLab, Jira, Confluence, Linear, Slack, Microsoft Teams, Zoom, and Notion - links them into a cross-tool decision graph, and surfaces that context to every agent and engineer on your team.
10
+ The decisions behind the code live in commits, tickets, chat and meetings - and months later
11
+ nobody can tell what still stands, what conflicts, or why. [Align](https://align.tech) pulls
12
+ those decisions into one graph your agents and your team check before they build.
11
13
 
12
- The CLI lets you import your decision history, query it in plain English, and run Align as a local [MCP](https://modelcontextprotocol.io) server so your AI assistants have authoritative context inline - and check their changes against it automatically.
14
+ This CLI is the free, open-source way in. It builds a decision graph from the tools you
15
+ already use - git history, GitHub, GitLab, Jira, Confluence, Linear, Slack, Teams, Zoom,
16
+ Notion - and serves it to any MCP agent, with an edit hook that surfaces prior decisions
17
+ before the agent writes. No account required. Beta, pre-1.0.
18
+
19
+ In a published benchmark, giving a coding agent recorded product decisions took decision
20
+ compliance from 46% to 95% ([Dillon & Varanasi, arXiv:2605.08112](https://arxiv.org/abs/2605.08112) -
21
+ a small vendor study, 8 tasks and 41 decision points, and it isn't our data).
13
22
 
14
23
  ```
15
24
  npm install -g @aligndottech/cli
@@ -17,15 +26,15 @@ npm install -g @aligndottech/cli
17
26
 
18
27
  Node 20+ required. MIT licensed.
19
28
 
20
- > **Install notes.** Cloud mode needs no native build. `--local` mode additionally
29
+ > **Install notes.** Cloud mode needs no native build. Local-only mode additionally
21
30
  > uses an on-device embedding model (`@huggingface/transformers`, an optional dependency)
22
31
  > that ships native binaries for macOS, glibc Linux, and Windows (x64/arm64) - on
23
32
  > those platforms `npm i -g` just works. On Alpine/musl, uncommon architectures, or
24
33
  > behind a strict proxy the optional model may not install; the global install still
25
- > succeeds and cloud mode works, and `--local` will tell you the model is unavailable
26
- > rather than failing silently. The first `--local` import downloads the model from
27
- > huggingface.co (~23MB) once, and local mode cannot start until that succeeds - so on a
28
- > restricted network, check that host is reachable before you begin.
34
+ > succeeds and cloud mode works, and local-only mode will tell you the model is unavailable
35
+ > rather than failing silently. The first import downloads the model from
36
+ > huggingface.co (~23MB) once, and nothing local can be embedded or searched until that
37
+ > succeeds - so on a restricted network, check that host is reachable before you begin.
29
38
 
30
39
  ## Quick start
31
40
 
@@ -59,16 +68,21 @@ Want a hand setting this up? I do free 30 minute setup calls: https://calendly.c
59
68
 
60
69
  1. **Import** pulls the decisions out of the tools you already use - read-only, nothing is modified.
61
70
  2. Align links them into a **cross-tool decision graph**: what was decided, why, who decided it, and how decisions relate (supersedes, conflicts with, depends on).
62
- 3. Your agents and you **query and check against** that graph - over MCP, a deterministic edit hook in your agent, CI, or plain `align ask`.
71
+ 3. Your agents and you **query and check against** that graph - over MCP, an edit hook in your agent, CI, or plain `align ask`.
63
72
 
64
- The CLI and MCP server are open source (this repo). The hosted graph + relationship detection is a separate commercial gateway; you can also run fully local with `--local`.
73
+ Wiring context into an agent is the easy part, and this repo is the open-source version of it.
74
+ The hard part is the record underneath: what your team actually decided, across every tool,
75
+ kept current. The CLI and MCP server are open source (this repo,
76
+ plus the [connector SDK](https://github.com/aligndottech/align-connector-sdk)). The hosted
77
+ graph and the heavier cross-tool relationship detection are a separate commercial gateway; you
78
+ can also run fully local with `--local`.
65
79
 
66
80
  ## Auto-alignment for AI agents
67
81
 
68
82
  When you run `align setup`, Align makes itself available to your AI agents four ways, so the context fires whether or not the model thinks to ask for it:
69
83
 
70
84
  1. **MCP server** - your assistant (Claude Code, Cursor, Claude Desktop, Windsurf) can query the decision graph inline. The server ships with instructions telling the agent to check alignment *before* making non-trivial changes.
71
- 2. **Deterministic edit hooks** - setup registers `align check --advisory` with every host that exposes a hook API, so prior decisions related to the change reach the model whether or not it thought to ask. **Claude Code** (`.claude/settings.json`), **pi** (`.pi/extensions/align.ts`), **Gemini CLI** (`.gemini/settings.json`) and **OpenCode** (`.opencode/plugins/align.js`) all check the *proposed* change before it is written. It is **non-blocking and fail-open**: it never denies an edit by default, and if Align is missing, slow or unreachable the edit proceeds untouched.
85
+ 2. **Edit hooks** - setup registers `align check --advisory` with every host that exposes a hook API, so prior decisions related to the change reach the model whether or not it thought to ask. **Claude Code** (`.claude/settings.json`), **pi** (`.pi/extensions/align.ts`), **Gemini CLI** (`.gemini/settings.json`) and **OpenCode** (`.opencode/plugins/align.js`) all check the *proposed* change before it is written. It is **non-blocking and fail-open**: it never denies an edit by default, and if Align is missing, slow or unreachable the edit proceeds untouched. As `align setup` writes it, the hook is retrieval only, so it needs no AI provider key and makes no provider call. (Adding `--block-on-critical` yourself changes both halves of that - see the table below.)
72
86
 
73
87
  **Cursor and Codex CLI cannot do this**, and that is a limit of those hosts, not a gap in setup: Cursor has no `beforeFileEdit` and its `afterFileEdit` hook has no output fields, and Codex's `PreToolUse` intercepts Bash only. They get layers 1, 3 and 4. The full per-host matrix, and why, is in [docs/agent-hooks.md](docs/agent-hooks.md).
74
88
  3. **Editor rules** - a managed, marker-delimited block in your `CLAUDE.md` and `AGENTS.md`, plus a `.cursor/rules/align.md` file (Cursor doesn't honor Claude Code hooks), nudge agents to consult the graph.
@@ -78,7 +92,7 @@ The hook, rule and `.mcp.json` files are committed to the repo, so the whole tea
78
92
 
79
93
  > **Heads up:** the first time Claude Code loads a project with a committed hook, it shows a one-time "approve hooks" prompt. Accept it to enable automatic alignment.
80
94
 
81
- You can also run the advisory check yourself. It always exits 0, and when it finds related prior decisions (or could not check at all) prints the hook output in whichever host's shape you ask for - `--format text` is plain prose for a host with no JSON contract. It reports the decisions as related, not as conflicts: retrieval finds decisions on the same subject and does not adjudicate opposition.
95
+ You can also run the advisory check yourself. It always exits 0, and when it finds related prior decisions (or could not check at all) prints the hook output in whichever host's shape you ask for - `--format text` is plain prose for a host with no JSON contract. It reports the decisions as related, not as conflicts: retrieval finds decisions on the same subject and does not adjudicate opposition. A decision the hook surfaced moments ago in the same directory is not repeated, so the pre and post hooks don't say everything twice.
82
96
 
83
97
  ```bash
84
98
  align check --advisory # Claude Code shape (default)
@@ -96,7 +110,7 @@ align ask "what was decided about caching"
96
110
  align ask "do we use redis"
97
111
  ```
98
112
 
99
- Ask in plain English - the graph picks keyword or semantic search automatically based on your phrasing, so full questions ("why do we use postgres") work as well as short terms ("postgres"). Pass a file path instead of a question to find decisions related to that file:
113
+ Ask in plain English. In cloud mode the gateway picks keyword or semantic search based on your phrasing; in local-only mode every query is semantic. Either way, full questions ("why do we use postgres") work as well as short terms ("postgres"). Pass a file path instead of a question to find decisions related to that file:
100
114
 
101
115
  ```bash
102
116
  align ask src/auth/session.ts
@@ -106,16 +120,16 @@ align ask src/auth/session.ts
106
120
 
107
121
  Align is **provider-agnostic** - `align ask` (and local relationship typing) uses **your own AI provider**. It resolves one, in order:
108
122
 
109
- 1. A named provider via env key: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` (or `GOOGLE_API_KEY`), `GROQ_API_KEY`, `MISTRAL_API_KEY`, or `GROK_API_KEY` (or `XAI_API_KEY`). Each has an optional model override (`ALIGN_ANTHROPIC_MODEL`, `ALIGN_OPENAI_MODEL`, `ALIGN_GEMINI_MODEL`, `ALIGN_GROQ_MODEL`, `ALIGN_MISTRAL_MODEL`, `ALIGN_GROK_MODEL`).
110
- 2. **Any OpenAI-compatible endpoint** via `ALIGN_LLM_BASE_URL` (+ `ALIGN_LLM_API_KEY`, `ALIGN_LLM_MODEL`) - covers OpenRouter, Together, DeepSeek, LM Studio, vLLM, or any self-hosted OpenAI-compatible server. Example:
123
+ 1. **Any OpenAI-compatible endpoint** via `ALIGN_LLM_BASE_URL` (+ `ALIGN_LLM_API_KEY`, `ALIGN_LLM_MODEL`) - covers OpenRouter, Together, DeepSeek, LM Studio, vLLM, or any self-hosted OpenAI-compatible server. This outranks the named keys below, so it wins even when `ANTHROPIC_API_KEY` is also set. Example:
111
124
  ```bash
112
- export ALIGN_LLM_BASE_URL=https://openrouter.ai/api/v1
113
- export ALIGN_LLM_API_KEY=sk-or-...
114
- export ALIGN_LLM_MODEL=anthropic/claude-3.5-sonnet
125
+ export ALIGN_LLM_BASE_URL=https://api.deepseek.com
126
+ export ALIGN_LLM_API_KEY=sk-...
127
+ export ALIGN_LLM_MODEL=deepseek-chat
115
128
  ```
116
- 3. [Ollama](https://ollama.com) running locally (auto-detected on `localhost:11434`, override `OLLAMA_HOST`), with a general-purpose model from a recognised family installed: `llama`, `mistral`, `gemma`, `phi`, `qwen` or `deepseek-r`. The installed version is read from Ollama itself, so a new release of any of those works the day it ships, and the newest one you have is the one used.
129
+ 2. A named provider via env key: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` (or `GOOGLE_API_KEY`), `GROQ_API_KEY`, `MISTRAL_API_KEY`, or `GROK_API_KEY` (or `XAI_API_KEY`). Each has an optional model override (`ALIGN_ANTHROPIC_MODEL`, `ALIGN_OPENAI_MODEL`, `ALIGN_GEMINI_MODEL`, `ALIGN_GROQ_MODEL`, `ALIGN_MISTRAL_MODEL`, `ALIGN_GROK_MODEL`).
130
+ 3. [Ollama](https://ollama.com) running locally (auto-detected on `localhost:11434`, override `OLLAMA_HOST`), with a general-purpose model from a recognised family installed: `llama`, `mistral`, `gemma`, `phi`, `qwen` or `deepseek-r`. The installed version is read from Ollama itself, so a new release of any of those works the day it ships; families are tried in the order above, and within the first one you have installed, the newest wins.
117
131
 
118
- Ollama will not answer from a model outside those families, or from one tuned for a different job (a `-coder`, `-math`, `-embed`, `-vision` or `-uncensored` variant). Such a model will still write fluent prose about your decisions, including relationships between them that do not exist, and it is not obvious from the output that anything went wrong. To use any model regardless, name it and it is used as-is:
132
+ Ollama will not answer from a model outside those families, or from one tuned for a different job (any tag containing `coder`, `code`, `math`, `embed`, `guard`, `vision`, `uncensored` or `dolphin`). Such a model will still write fluent prose about your decisions, including relationships between them that do not exist, and it is not obvious from the output that anything went wrong. To use any model regardless, name it and it is used as-is:
119
133
 
120
134
  ```bash
121
135
  export ALIGN_OLLAMA_MODEL=my-fine-tune:latest
@@ -123,27 +137,43 @@ Align is **provider-agnostic** - `align ask` (and local relationship typing) use
123
137
 
124
138
  If none is available, `align ask` still works - it prints a ranked list of the matching decisions instead of a synthesised paragraph. No key is ever required.
125
139
 
126
- **Note:** A Claude.ai or ChatGPT subscription is not the same as an API key - you need a separate API account. [Groq](https://console.groq.com/keys) offers a free tier with no credit card and is the fastest option.
140
+ **Note:** A Claude.ai or ChatGPT subscription is not the same as an API key - you need a separate API account. [Groq](https://console.groq.com/keys) offers a free tier with no credit card.
127
141
 
128
- The retrieval itself (search over your graph) always runs against Align - the API key is only used locally to phrase the answer.
142
+ In cloud mode, retrieval (the search over your graph) runs on Align's gateway and the provider key is only used locally to phrase the answer. In local-only mode, retrieval is on-device too.
129
143
 
130
144
  ## Authentication
131
145
 
132
146
  ```bash
133
- align login # opens browser, paste token when prompted
147
+ align login # opens your browser; the token arrives via a localhost callback
134
148
  align login --token algt_... # non-interactive, good for CI / self-hosted
135
149
  align whoami # verify current session
136
150
  align logout # clear stored credentials
137
151
  ```
138
152
 
139
- Tokens are stored locally in your OS config directory. To create one manually, go to **Settings > API Tokens** in the Align web app.
153
+ `align login` starts a small localhost listener, opens the Align sign-in page, and receives the token on the callback - nothing to paste. It prints the sign-in URL too, in case the browser does not open; if the callback cannot complete (a locked-down machine, no free port), use `align login --token` instead. Tokens are stored locally in your OS config directory. To create one manually, go to **Settings > API Tokens** in the Align web app.
140
154
 
141
- ## Cloud vs local mode
155
+ ## Cloud or local-only
142
156
 
143
157
  `align setup` offers two modes:
144
158
 
145
159
  - **Personal cloud** (default) - your decision graph is hosted at Align: synced across machines, backed up, and upgradeable to a shared team workspace. Connectors connect via **read-only browser OAuth** (no tokens to paste), and `align ask` retrieval runs server-side. Nothing you connect can be modified by the CLI - it only reads.
146
- - **Local-only** (`align setup --local`) - **no account, and no Align servers**: the graph, embeddings, and search all live in a SQLite database on your machine, and the CLI never sends your decisions to us. Three things do use the network, all worth knowing before you point it at work content. The embedding model downloads once from huggingface.co. Then, **only if a provider key is set in your environment** (see below), two commands call **your own AI provider**: `align ask` sends your question plus the titles and summaries of the decisions it retrieved (up to `--limit`, default 8), and `align check` sends up to 2,000 characters of the proposed change paired with one retrieved decision at a time. With no key set, nothing at all leaves the machine after that first download - retrieval is on-device, so the editor hook still surfaces related decisions. Seeds from your git history out of the box; other sources connect by pasting a **read-only personal token** (OAuth needs the hosted callback, so it isn't available offline). Related decisions are surfaced on-device by semantic similarity; typed relationships (supersedes / conflicts with / depends on) and conflict detection on a change are typed at query time using **your own AI provider key** (Anthropic, OpenAI, or a local Ollama) - without one, related decisions still surface, just not typed. The heavier cross-tool relationship detection runs in the hosted gateway. Run `align local status` to inspect it, `align local reset` to wipe it.
160
+ - **Local-only** (`align setup --local`) - **no account, and no Align servers**: the graph, embeddings, and search all live in a SQLite database on your machine, and the CLI never sends your decisions to us.
161
+
162
+ What uses the network in local-only mode, all worth knowing before you point it at work content:
163
+
164
+ - The embedding model downloads once from huggingface.co (~23MB), on the first import.
165
+ - `align import <tool>` calls that tool's API, read-only, with the token you pasted - that is what an import is. The data goes from your tool to your machine; none of it goes to Align.
166
+ - **Only when an AI provider is available** - an API key in your environment, or a running Ollama, which needs no key - three surfaces call **your own provider**: `align ask` sends your question plus the titles and summaries of the decisions it retrieved (up to `--limit`, default 8), and `align check` and the MCP tool `align_check_alignment` send up to 2,000 characters of the proposed change paired with one retrieved decision at a time. The editor hook never does by default - it is retrieval only, provider or no provider. The one exception is explicit: adding `--block-on-critical` to your hook line opts that hook into background adjudication on the same terms as `align check`, roughly once per edit where retrieval found something.
167
+ - Ollama runs on your own machine by default, so those calls stay local - unless you have pointed `OLLAMA_HOST` at another box, in which case they go there.
168
+ - With no provider available at all, nothing goes to any AI provider - retrieval is on-device, so the editor hook still surfaces related decisions - and nothing ever goes to Align. The network surface is then just the one-time model download and whatever imports you run.
169
+
170
+ How the local graph behaves:
171
+
172
+ - Seeds from your git history out of the box; other sources connect by pasting a **read-only personal token** (OAuth needs the hosted callback, so it isn't available offline). Add `--env local` to any `align import <tool>` run.
173
+ - **Re-importing is safe**: a decision is identified by its source URL and title, so running the same import twice updates what changed rather than duplicating the graph.
174
+ - Related decisions are surfaced on-device by semantic similarity; typed relationships (supersedes / conflicts with / depends on) are typed at query time using **your own AI provider key** (Anthropic, OpenAI, or a local Ollama) - without one, related decisions still surface, just not typed. The heavier cross-tool relationship detection runs in the hosted gateway.
175
+ - Run `align local status` to inspect the graph, `align local reset` to wipe it.
176
+ - Works in local-only mode today: `setup`, `import <tool>`, `capture`, `ask`, `search`, `check`, `status`, `context sync`, `mcp`, and the `local` commands. Not yet routed to the local graph (they address the cloud gateway): `decisions`, `export`, `drift`, `links`, `spaces`, `check --resolve`, and the connector-scan commands under `align import`.
147
177
 
148
178
  Pick cloud for sync + team upgrade, local for maximum privacy. You can always start local and move to cloud later.
149
179
 
@@ -153,7 +183,7 @@ Pull your existing work into the decision graph. The more sources you add, the r
153
183
 
154
184
  **Easiest way: `align setup`.** It connects each source via a **read-only browser OAuth** consent - no tokens to create or paste. The CLI only ever *reads*; it can't modify your tools (write access lives only in the team/org bot apps). GitHub, Jira, Confluence, Slack, Microsoft Teams, Zoom, Linear, GitLab (gitlab.com), and Notion all use OAuth. Self-managed GitLab (a custom domain) uses a read-only token you paste.
155
185
 
156
- The same OAuth flow also works per source: `align import <source> --personal` opens the browser consent (or reuses the token a previous `align setup` cached) - no PAT to create. The `align import <source> --token ...` forms below are the manual / CI alternative (and how to connect self-managed hosts). Every import previews what will be imported and asks for confirmation before sending anything (use `--approve` to skip the prompt).
186
+ The same OAuth flow also works per source: `align import <source> --personal` opens the browser consent (or reuses the token a previous `align setup` cached) - no PAT to create. The `align import <source> --token ...` forms below are the manual / CI alternative (and how to connect self-managed hosts). Every import previews what will be imported and asks for confirmation before sending anything (use `--approve` to skip the prompt). In local-only mode, add `--env local` - a machine that has also logged in to cloud otherwise imports to the cloud graph.
157
187
 
158
188
  ### Git
159
189
 
@@ -231,7 +261,18 @@ align import notion --token <your-notion-integration-token>
231
261
 
232
262
  ### Microsoft Teams / Zoom
233
263
 
234
- Connect these through `align setup` (OAuth) - they have no read-only personal token to paste, so they are cloud-only.
264
+ Prefer `align setup` (OAuth) - neither has a personal token a human can create in-app, so they are not offered in local-only setup. `align import teams --token <Graph token>` and `align import zoom --token <OAuth token>` exist for tokens you obtained elsewhere.
265
+
266
+ ### Connector scans (cloud)
267
+
268
+ With a cloud account, the gateway can also run connector-side scans and hold the results as suggestions for review:
269
+
270
+ ```bash
271
+ align import --all # start a scan across every enabled connector
272
+ align import list # scan jobs and their status
273
+ align import suggestions # review what a scan found
274
+ align import scan-runs # scan history
275
+ ```
235
276
 
236
277
  ## Capturing decisions
237
278
 
@@ -242,32 +283,39 @@ align capture https://yourco.atlassian.net/browse/ENG-123
242
283
  align capture https://yourco.slack.com/archives/C123/p1700000000000000
243
284
  ```
244
285
 
286
+ `align capture` takes a URL; raw text capture is not supported from the CLI yet. Over MCP, `align_capture` accepts text too, in local-only mode.
287
+
245
288
  ## Searching and browsing
246
289
 
247
290
  ```bash
248
291
  align ask "any question in plain English" # natural language answer
249
292
  align search "authentication strategy" # keyword/semantic search - returns a list
250
- align decisions list # browse the graph
293
+ align decisions list # browse the graph (cloud)
251
294
  align decisions list --space backend # filter by space
252
295
  align decisions list --platform jira # filter by source platform
253
- align decisions show <id> # full detail for one decision
254
- align links list # cross-tool decision relationships
255
- align drift # decisions that may be out of date
256
- align export # export decisions as a structured brief
296
+ align decisions show <id> # full detail for one decision (cloud)
297
+ align links list # cross-tool decision relationships (cloud)
298
+ align drift # decisions that may be out of date (cloud)
299
+ align export # export decisions as a structured brief (cloud)
257
300
  ```
258
301
 
259
- `align ask` synthesises an answer. `align search` returns a ranked list - useful when you want to browse.
302
+ `align ask` synthesises an answer. `align search` returns a ranked list - useful when you want to browse. The commands marked cloud address your hosted graph; in local-only mode use `align search`, `align ask` and `align local status` instead.
260
303
 
261
304
  ## Alignment check
262
305
 
263
- Check your current changes against the decision graph. Exit code `1` means a conflict was
264
- found; `2` means the check could not run and nothing was verified, which is not a pass.
306
+ Check your current changes against the decision graph.
265
307
 
266
308
  ```bash
267
- align check # check the staged diff
309
+ align check # check the staged diff (falls back to the HEAD diff when nothing is staged)
268
310
  align check --all # check the full working-tree diff vs HEAD
269
311
  ```
270
312
 
313
+ Four outcomes: aligned or nothing-related exits `0`; a conflict exits `1`; and `2` means the
314
+ check retrieved decisions it could not adjudicate, or could not run at all - not a pass, and
315
+ distinguishable from a conflict on purpose. That clean 0/1/2 contract is guaranteed under
316
+ `--ci`; in default interactive mode a transport error or a missing git repository also exits
317
+ `1`.
318
+
271
319
  Modes:
272
320
 
273
321
  | Mode | Behavior |
@@ -275,18 +323,28 @@ Modes:
275
323
  | (default) | Human-readable output; exits `1` on any conflict. |
276
324
  | `--hook` | Pre-commit mode: silent when there's no context, only fails on **critical** conflicts. |
277
325
  | `--advisory` | Agent hook mode (detects pre vs post from the hook payload on stdin): **always exits 0**, emits related, unadjudicated decisions - or an explicit "could not check" notice - in the host's hook shape (`--format claude\|gemini\|pi\|opencode\|text`). Fail-open. |
278
- | `--ci` | Emits JSON to stdout for CI; exits `1` on conflict. |
326
+ | `--advisory --block-on-critical` | Opt-in deferred adjudication: when retrieval finds related decisions, the hook additionally spawns a background full check, and a **retry of a change already judged a critical conflict is denied** (Claude Code `permissionDecision: "deny"`), with the verdict expiring after 15 minutes. The verdict is keyed on the tool, the target file and the text together, so a different file or an adjusted approach proceeds untouched - it catches a re-presentation, never a first proposal. Runs per edit, up to 3 at a time per project. In local mode it calls **your own AI provider**, which the default hook never does. |
327
+ | `--ci` | Emits JSON to stdout; the 0/1/2 exit contract above. **Pass `--base`** or there is nothing to diff. |
279
328
 
280
- In CI:
329
+ Useful flags: `--title "what this change decides"` improves adjudication on a bare diff;
330
+ `--base <ref>` diffs `base...HEAD` instead of the staged diff.
331
+
332
+ In CI, always pass `--base` - a clean checkout has no staged diff, and a check with nothing to
333
+ diff would pass without looking:
281
334
 
282
335
  ```yaml
283
336
  - name: Check alignment
284
- run: align check --all --ci
337
+ run: align check --base origin/${{ github.base_ref }} --ci
285
338
  env:
286
339
  ALIGN_TOKEN: ${{ secrets.ALIGN_TOKEN }}
287
340
  ```
288
341
 
289
- Resolve a flagged conflict (records the resolution so it stops surfacing):
342
+ Or use the published GitHub Action, which always passes `--base`, writes the verdict to the
343
+ job summary and annotates the changed files:
344
+ [`aligndottech/decision-check`](https://github.com/aligndottech/decision-check).
345
+
346
+ When a check flags a conflict, resolve it so it stops surfacing (only meaningful while the
347
+ current diff is conflicting):
290
348
 
291
349
  ```bash
292
350
  align check --resolve <decision_id>:honored # or overridden | context_changed
@@ -295,8 +353,9 @@ align check --resolve <decision_id>:honored # or overridden | context_chang
295
353
  ## Write decisions into your agent's context files
296
354
 
297
355
  Agents read local files before they reach for any tool. `align context sync` writes your
298
- current decisions to `.align/decisions.md` and adds one import line to CLAUDE.md, so an
299
- agent knows what your team decided without a single tool call.
356
+ active decisions to `.align/decisions.md` and adds one import line to CLAUDE.md, so an
357
+ agent knows what your team decided without a single tool call. Superseded and archived
358
+ decisions are history, and stay out of it.
300
359
 
301
360
  ```bash
302
361
  align context sync # write .align/decisions.md, import it from CLAUDE.md
@@ -363,7 +422,7 @@ Once configured, your assistant can call these tools to query and update your de
363
422
  |------|---------|
364
423
  | `align_ask` | Natural-language question about past decisions |
365
424
  | `align_search` | Search the decision graph |
366
- | `align_capture` | Capture a decision from a URL or text |
425
+ | `align_capture` | Capture a decision from a URL (raw text too, in local-only mode) |
367
426
  | `align_check_alignment` | Check a proposed change for conflicts with prior decisions |
368
427
  | `align_check_drift` | Check whether code/config has drifted from a decision |
369
428
  | `align_get_related_decisions` | Decisions related to a file or module |
@@ -372,14 +431,18 @@ Once configured, your assistant can call these tools to query and update your de
372
431
 
373
432
  ## Environments
374
433
 
375
- By default the CLI targets `prod` (`api.align.tech`). Use `--env` or set a sticky default:
434
+ By default the CLI targets `prod` (`api.align.tech`). Use a sticky default, or override per command - `--env` belongs after the command name:
376
435
 
377
436
  ```bash
378
437
  align env set preview # stick to preview for this machine
379
438
  align env get # show current default
380
- align --env local <command> # one-off override
439
+ align search "auth" --env local # one-off override on any command
381
440
  ```
382
441
 
442
+ One naming trap: `--env local` means your embedded SQLite graph only after `align setup --local`
443
+ has configured it. On a machine that never ran that, `local` is a developer convenience that
444
+ addresses a gateway on `localhost:8080`.
445
+
383
446
  ## Environment variables
384
447
 
385
448
  | Variable | Description |
@@ -388,16 +451,21 @@ align --env local <command> # one-off override
388
451
  | `ALIGN_ENV` | Default environment (`prod`, `preview`, `local`) |
389
452
  | `ALIGN_GATEWAY_URL` | Override gateway URL (self-hosted) |
390
453
  | `ALIGN_TENANT_ID` | Override tenant ID (self-hosted / CI). Against `preview` or `prod` it needs `ALIGN_TOKEN` set too: a tenant on its own authenticates nothing, and the CLI refuses rather than sending it |
391
- | `ALIGN_TELEMETRY` | Set it to anything other than `1`/`true`/`yes`/`on` to send no usage events at all. Cloud mode reports one `cli.command` event per invocation - the command name, nothing else, over the connection it was already using. Local mode sends nothing either way |
454
+ | `ALIGN_TELEMETRY` | Set it to anything other than `1`/`true`/`yes`/`on` to send no usage events at all (empty counts as unset, so leaves them on). Cloud mode reports one `cli.command` event per invocation to the same gateway - the command name, nothing else. Local mode sends nothing either way |
455
+ | `ALIGN_DEBUG` | Set to any value to print the full stack trace when the CLI crashes with an unexpected error |
392
456
  | `ANTHROPIC_API_KEY` | Anthropic API key for `align ask` synthesis |
393
457
  | `OPENAI_API_KEY` | OpenAI API key for `align ask` synthesis |
394
- | `GEMINI_API_KEY` | Google Gemini API key for `align ask` synthesis |
458
+ | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | Google Gemini API key for `align ask` synthesis |
395
459
  | `GROQ_API_KEY` | Groq API key for `align ask` synthesis |
396
460
  | `MISTRAL_API_KEY` | Mistral API key for `align ask` synthesis |
397
461
  | `GROK_API_KEY` / `XAI_API_KEY` | xAI Grok API key for `align ask` synthesis |
398
- | `ALIGN_LLM_BASE_URL` | Any OpenAI-compatible endpoint (with `ALIGN_LLM_API_KEY`, `ALIGN_LLM_MODEL`) |
399
- | `OLLAMA_HOST` | Ollama host (default: `http://localhost:11434`). Your own machine by default; point it at a shared box and local mode's relationship typing goes there instead |
462
+ | `ALIGN_LLM_BASE_URL` | Any OpenAI-compatible endpoint. Outranks the named keys above |
463
+ | `ALIGN_LLM_API_KEY` | Bearer token for `ALIGN_LLM_BASE_URL` |
464
+ | `ALIGN_LLM_MODEL` | Model name for `ALIGN_LLM_BASE_URL` (default `gpt-4o-mini`) |
465
+ | `OLLAMA_HOST` | Ollama host (default: `http://localhost:11434`). Your own machine by default; point it at a shared box and local relationship typing goes there instead |
400
466
  | `ALIGN_OLLAMA_MODEL` | Use this Ollama model, whatever family it is from |
467
+ | `ALIGN_INGEST_CONCURRENCY` | Concurrent ingest batch requests during imports (default `6`) |
468
+ | `PI_CODING_AGENT_DIR` | Where `align mcp --setup` writes pi's `mcp.json`, if pi keeps its config somewhere non-standard |
401
469
 
402
470
  Advanced: override the model per provider with `ALIGN_ANTHROPIC_MODEL`, `ALIGN_OPENAI_MODEL`, `ALIGN_GEMINI_MODEL`, `ALIGN_GROQ_MODEL`, `ALIGN_MISTRAL_MODEL`, `ALIGN_GROK_MODEL`, or `ALIGN_OLLAMA_MODEL`.
403
471
 
@@ -439,14 +507,18 @@ align import slack Import from Slack (experimental)
439
507
  align import teams Import from Microsoft Teams
440
508
  align import zoom Import from Zoom recording transcripts
441
509
  align import notion Import from Notion
442
- align decisions list List decisions in your graph
443
- align decisions show <id> Show full detail for a decision
510
+ align import --all Start a connector scan across enabled connectors (cloud)
511
+ align import list List scan jobs (cloud)
512
+ align import suggestions Review scan suggestions (cloud)
513
+ align import scan-runs Scan history (cloud)
514
+ align decisions list List decisions in your graph (cloud)
515
+ align decisions show <id> Show full detail for a decision (cloud)
444
516
  align status Value readout: what your graph has done for you
445
517
  align context sync Write decisions to .align/decisions.md + CLAUDE.md import
446
- align export Export decisions as a structured brief
447
- align drift Show decisions that may be out of date
448
- align links list Show cross-tool decision relationships
449
- align spaces list List spaces (project scopes)
518
+ align export Export decisions as a structured brief (cloud)
519
+ align drift Show decisions that may be out of date (cloud)
520
+ align links list Show cross-tool decision relationships (cloud)
521
+ align spaces list List spaces (project scopes) (cloud)
450
522
  align env set <name> Set default environment
451
523
  align env get Show current environment
452
524
  align mcp Start local MCP server
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `align adjudicate <event-id>` - answer a check that ran and declined to rule (ALI-710).
3
+ *
4
+ * The other half of a strict CI gate. `fail-on: conflict-or-unknown` fails an incomplete
5
+ * result, which is right, but a NON-VERDICT never becomes complete on its own: the judge
6
+ * reached the change, found a relationship it cannot turn into a pass or a conflict, and
7
+ * every re-run returns that same answer. Without this the only ways past it are a repo-admin
8
+ * bypass, which leaves no record, or weakening the policy for everyone.
9
+ *
10
+ * The gateway matches the answer to a digest of the content it was sent, so re-running the
11
+ * check on the same change finds it, and answering something you were never shown is not
12
+ * available.
13
+ */
14
+ import type { Command } from 'commander';
15
+ export declare function registerAdjudicateCommand(program: Command): void;
16
+ //# sourceMappingURL=adjudicate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adjudicate.d.ts","sourceRoot":"","sources":["../../src/commands/adjudicate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAczC,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqDhE"}
@@ -0,0 +1,50 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { createConfigStore } from '../lib/config.js';
4
+ import { createGatewayClient } from '../lib/gateway-client.js';
5
+ import { resolveEnv } from '../lib/resolve-env.js';
6
+ const VERDICTS = ['accepted', 'conflicting'];
7
+ function isVerdict(v) {
8
+ return VERDICTS.includes(v);
9
+ }
10
+ export function registerAdjudicateCommand(program) {
11
+ program
12
+ .command('adjudicate <event-id>')
13
+ .description('Answer an alignment check that reached the judge and declined to rule. The event id is printed by the failing check.')
14
+ .requiredOption('--verdict <verdict>', `Your answer: accepted (the change may proceed) or conflicting (it really does conflict)`)
15
+ .option('--note <note>', 'Why - recorded alongside your answer')
16
+ .option('--env <env>', 'Environment')
17
+ .action(async (eventId, opts) => {
18
+ if (!isVerdict(opts.verdict)) {
19
+ console.error(chalk.red(`\n --verdict must be one of: ${VERDICTS.join(', ')}\n`));
20
+ process.exit(1);
21
+ }
22
+ const client = createGatewayClient(createConfigStore().getEnvironment(resolveEnv(opts.env)));
23
+ const spinner = ora('Recording your answer...').start();
24
+ try {
25
+ const res = await client.adjudicateCheck(eventId, opts.verdict, opts.note);
26
+ spinner.stop();
27
+ if (res.alreadyAdjudicated) {
28
+ // Not an error: the first answer stands by design, so someone got here first and
29
+ // the caller's intent is already served. Saying whose answer it is matters more
30
+ // than saying the write did not happen.
31
+ console.log(chalk.yellow(`\n Already answered by ${res.adjudicatedBy} as "${res.verdict}". The first answer stands.\n`));
32
+ return;
33
+ }
34
+ console.log(chalk.green(`\n Recorded: ${res.verdict}.\n`));
35
+ if (res.verdict === 'accepted') {
36
+ console.log(chalk.dim(' Re-run the check on the same change and it will pass on this answer.\n'));
37
+ }
38
+ else {
39
+ // A 'conflicting' answer is a real act with a real consequence, so it does not get
40
+ // a message implying anything is now unblocked.
41
+ console.log(chalk.dim(' The check stays red on this change, which is the point.\n'));
42
+ }
43
+ }
44
+ catch (err) {
45
+ spinner.fail(chalk.red(err.message));
46
+ process.exit(1);
47
+ }
48
+ });
49
+ }
50
+ //# sourceMappingURL=adjudicate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adjudicate.js","sourceRoot":"","sources":["../../src/commands/adjudicate.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAgB,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,MAAM,QAAQ,GAAG,CAAC,UAAU,EAAE,aAAa,CAAU,CAAC;AAGtD,SAAS,SAAS,CAAC,CAAS;IAC1B,OAAQ,QAA8B,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAgB;IACxD,OAAO;SACJ,OAAO,CAAC,uBAAuB,CAAC;SAChC,WAAW,CACV,sHAAsH,CACvH;SACA,cAAc,CACb,qBAAqB,EACrB,yFAAyF,CAC1F;SACA,MAAM,CAAC,eAAe,EAAE,sCAAsC,CAAC;SAC/D,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC;SACpC,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,IAAuD,EAAE,EAAE;QACzF,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,iCAAiC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CACpE,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,mBAAmB,CAAC,iBAAiB,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7F,MAAM,OAAO,GAAG,GAAG,CAAC,0BAA0B,CAAC,CAAC,KAAK,EAAE,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3E,OAAO,CAAC,IAAI,EAAE,CAAC;YAEf,IAAI,GAAG,CAAC,kBAAkB,EAAE,CAAC;gBAC3B,iFAAiF;gBACjF,gFAAgF;gBAChF,wCAAwC;gBACxC,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CACV,2BAA2B,GAAG,CAAC,aAAa,QAAQ,GAAG,CAAC,OAAO,+BAA+B,CAC/F,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,iBAAiB,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC;YAC5D,IAAI,GAAG,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;gBAC/B,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,GAAG,CAAC,0EAA0E,CAAC,CACtF,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,mFAAmF;gBACnF,gDAAgD;gBAChD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC,CAAC;YACxF,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { Command } from 'commander';
1
+ import { type Command } from 'commander';
2
2
  import type { AlignmentResult } from '../lib/gateway-client.js';
3
3
  export declare function registerCheckCommand(program: Command): void;
4
4
  type AdvisoryConflict = NonNullable<AlignmentResult['conflicts']>[number];
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAgBhE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmM3D;AAmFD,KAAK,gBAAgB,GAAG,WAAW,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,KAAK,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAKvE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,GAAG,IAAI,CAQzG;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,GAAG,IAAI,CAM3E;AA4BD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,GAAG,UAAU,GAAG,MAAM,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAwBD,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,GACvB,OAAO,GAAG,IAAI,CAQhB"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,WAAW,CAAC;AASjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AA+BhE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsO3D;AAkID,KAAK,gBAAgB,GAAG,WAAW,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAkH1E,KAAK,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAKvE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,GAAG,IAAI,CAQzG;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,GAAG,IAAI,CAM3E;AAiED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,GAAG,UAAU,GAAG,MAAM,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAqBD,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,GACvB,OAAO,GAAG,IAAI,CAQhB"}