@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 +128 -56
- package/dist/commands/adjudicate.d.ts +16 -0
- package/dist/commands/adjudicate.d.ts.map +1 -0
- package/dist/commands/adjudicate.js +50 -0
- package/dist/commands/adjudicate.js.map +1 -0
- package/dist/commands/check.d.ts +1 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +212 -10
- package/dist/commands/check.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/advisory-verdict.d.ts +80 -0
- package/dist/lib/advisory-verdict.d.ts.map +1 -0
- package/dist/lib/advisory-verdict.js +270 -0
- package/dist/lib/advisory-verdict.js.map +1 -0
- package/dist/lib/check-depth.d.ts +17 -0
- package/dist/lib/check-depth.d.ts.map +1 -0
- package/dist/lib/check-depth.js +16 -0
- package/dist/lib/check-depth.js.map +1 -0
- package/dist/lib/gateway-client.d.ts +48 -1
- package/dist/lib/gateway-client.d.ts.map +1 -1
- package/dist/lib/gateway-client.js +18 -0
- package/dist/lib/gateway-client.js.map +1 -1
- package/dist/lib/local-gateway-client.d.ts +2 -1
- package/dist/lib/local-gateway-client.d.ts.map +1 -1
- package/dist/lib/local-gateway-client.js +7 -1
- package/dist/lib/local-gateway-client.js.map +1 -1
- package/dist/lib/local-llm.d.ts +5 -13
- package/dist/lib/local-llm.d.ts.map +1 -1
- package/dist/lib/local-llm.js +16 -19
- package/dist/lib/local-llm.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -5,11 +5,20 @@
|
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
|
|
8
|
-
**AI agents
|
|
8
|
+
**Your AI agents know the code. They don't know the company.**
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
26
|
-
> rather than failing silently. The first
|
|
27
|
-
> huggingface.co (~23MB) once, and local
|
|
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,
|
|
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
|
-
|
|
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. **
|
|
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
|
|
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.
|
|
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://
|
|
113
|
-
export ALIGN_LLM_API_KEY=sk
|
|
114
|
-
export ALIGN_LLM_MODEL=
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
| `--
|
|
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
|
-
|
|
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 --
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
399
|
-
| `
|
|
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
|
|
443
|
-
align
|
|
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"}
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
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,
|
|
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"}
|