driftseal 1.4.0 → 2.1.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
@@ -1,419 +1,353 @@
1
1
  # DriftSeal
2
2
 
3
- > **Seal the intent. Stop the drift.**
3
+ > **Seal the outcome. Stop the drift.**
4
4
 
5
- [简体中文](README.zh-CN.md)
6
-
7
- Agentic coding moves fast. **DriftSeal keeps it honest.**
8
-
9
- Before an agent touches the code, DriftSeal records what this round will accomplish and how completion will be proved. When the work ends, it records what actually happened. The result is a tiny, auditable contract that survives context loss, scope creep, and optimistic “done” claims.
5
+ DriftSeal is a repository-local protocol and toolchain for keeping coding agents
6
+ anchored to a coherent delivery outcome. It records the outcome before durable
7
+ work begins, permits append-only extensions toward that same outcome, binds
8
+ verification to the accumulated contract, and preserves only the decisions that
9
+ need durable rationale.
10
10
 
11
11
  ```text
12
- seal intentdo the workprove the result → close the round
12
+ begin an outcome extend the same outcome verify the cumulative contract → close
13
13
  ```
14
14
 
15
- **One open intent. One declared proof. One durable trail.** No service and no database—just local Node.js tools and plain files that travel with the repo.
15
+ One worktree owns one open outcome. Git records what landed; DriftSeal records
16
+ what the work was meant to achieve, how completion was proved, and why durable
17
+ decisions were made.
18
+
19
+ ## What changed in v2
16
20
 
17
- ## The problem is not speed. It is drift.
21
+ DriftSeal v2 is an outcome log rather than an intent-per-step log.
18
22
 
19
- | Without DriftSeal | With DriftSeal |
20
- | --- | --- |
21
- | Scope quietly expands halfway through a task | One visible intent defines the active round |
22
- | “Done” arrives without meaningful evidence | Verification is declared before implementation |
23
- | Context compaction erases the original goal | `status` and `log` restore the exact intent and history |
24
- | Old architectural debates repeat forever | Selective [MADR](https://adr.github.io/madr/) records preserve the reasoning that matters |
25
- | Concurrent or interrupted writes leave uncertainty | Locks, schema checks, atomic writes, and recovery make failures explicit and recoverable |
23
+ - State lives under one seal root: `.seal/outcomes/events.jsonl` and `.seal/madr/`.
24
+ - `DRIFTSEAL_HOME` overrides the whole v2 `.seal` root. A value inherited from
25
+ v1 still points at an intent-log directory; pass that legacy location to the
26
+ migration command explicitly, then unset or replace the variable.
27
+ - `driftseal extend` appends another step, acceptance criterion, verifier, or
28
+ decision link to the currently open outcome.
29
+ - Every extension changes the contract hash and invalidates earlier verification
30
+ and MADR reconciliation.
31
+ - Stored events use `logVersion: 2`. Compatible clients accept `schemaVersion`
32
+ `1` or `2`; lane events and non-default `begin.lane` use `schemaVersion: 2`.
33
+ - The generated `AGENTS.md` protocol series is `2.1`. `driftseal init` upgrades
34
+ recognized `2.0` blocks.
35
+ - Named lanes partition outcome history on the same WAL. The default lane is
36
+ `main`; `driftseal log` follows the current lane.
37
+ - The public CLI, Node API, MCP tools, and MCP resources use outcome terminology.
38
+ v1 names and storage paths are not runtime aliases.
26
39
 
27
- DriftSeal complements Git instead of competing with it: the intent says what was planned, the decision log preserves why, and the commit shows what landed.
40
+ ## Install
28
41
 
29
- ## Start in 30 seconds
42
+ DriftSeal requires Node.js 18 or newer.
30
43
 
31
44
  ```sh
32
45
  npm install --global driftseal
33
- cd your-project
34
- driftseal init
46
+ driftseal --version
35
47
  ```
36
48
 
37
- `driftseal init` writes the protocol to `AGENTS.md`, including how to `absorb`
38
- colliding worktree logs, and configures the local git merge driver. It can be
39
- run again without duplicating either. Pass `--lang zh-CN` (or another
40
- [BCP 47](https://www.rfc-editor.org/rfc/rfc5646.html) tag) to declare the
41
- language agents should use for intent and decision prose; the default is `en`.
42
- Command names, flags, status tokens, ids, and MADR section headings stay in
43
- English. Re-running `init` without `--lang` preserves the declared language
44
- while upgrading the protocol. DriftSeal requires Node.js 18+.
45
-
46
- For local development from this checkout:
49
+ From a source checkout:
47
50
 
48
51
  ```sh
49
- npm link
52
+ npm install
53
+ node bin/driftseal.js --version
50
54
  ```
51
55
 
52
- ## Recommended agent setup
53
-
54
- Use `AGENTS.md` + the companion skill + the CLI as the default integration:
55
-
56
- - `AGENTS.md`, installed by `driftseal init`, is the authoritative policy.
57
- - `skills/use-driftseal` is a small, agent-agnostic discovery and recovery guide.
58
- - `driftseal` is the default execution surface.
59
-
60
- Install the bundled skill for one platform. Project scope is the default:
56
+ Adopt the protocol in a repository:
61
57
 
62
58
  ```sh
63
- driftseal skill install --target codex
64
- driftseal skill install --target kimi-code --scope global
59
+ driftseal init
65
60
  ```
66
61
 
67
- | Target | Project scope | Global scope |
68
- | --- | --- | --- |
69
- | `codex` | `.agents/skills/use-driftseal` | `~/.agents/skills/use-driftseal` |
70
- | `kimi-code` | `.kimi-code/skills/use-driftseal` | `~/.kimi-code/skills/use-driftseal` |
71
- | `opencode` | `.opencode/skills/use-driftseal` | `~/.config/opencode/skills/use-driftseal` |
72
- | `claude-code` | `.claude/skills/use-driftseal` | `~/.claude/skills/use-driftseal` |
73
- | `cursor` | `.cursor/skills/use-driftseal` | `~/.cursor/skills/use-driftseal` |
62
+ `init` writes or upgrades the managed blocks in `AGENTS.md`, adds the outcome-log
63
+ merge attribute, and configures the local Git merge driver. Run it again in a
64
+ fresh clone because Git config is local to each clone.
74
65
 
75
- Use `--root <repository>` to select a project when running the installer
76
- elsewhere. Repeated installs of identical content are no-ops, and a skill left
77
- by an earlier DriftSeal release is upgraded in place; only a skill this
78
- installer never wrote requires `--force`. MCP and lifecycle hooks are optional
79
- adapters; enable them only for a concrete host constraint or reminder need, not
80
- as additional policy layers.
66
+ Use `driftseal init --lang <BCP-47-tag>` to choose the prose language stored in
67
+ outcome and MADR records. Use `--local-log` only when `.seal/` should remain
68
+ untracked; DriftSeal reports tracked state but does not edit `.gitignore` or the
69
+ Git index.
81
70
 
82
- ## Optional: use DriftSeal through MCP
71
+ ## Core workflow
83
72
 
84
- The same package includes `driftseal-mcp`, a local stdio MCP server. It exposes
85
- structured tools for the complete intent and decision workflow while reusing the
86
- same locking, WAL, atomic-write, schema, and recovery implementation as the CLI.
87
- The server never shells out to `driftseal` and does not parse CLI output.
88
-
89
- Fix the server to one repository when starting it:
73
+ Open the coherent delivery outcome before changing durable project content:
90
74
 
91
75
  ```sh
92
- driftseal-mcp --root /absolute/path/to/repository
76
+ driftseal begin "Ship account recovery" \
77
+ --accept "expired links are rejected" \
78
+ --accept "a valid link resets the password" \
79
+ --verify "npm test"
93
80
  ```
94
81
 
95
- Install the server into the current repository's agent config with one of the
96
- supported targets:
82
+ If another step is still part of that same delivered outcome, append it:
97
83
 
98
84
  ```sh
99
- cd /path/to/repository
100
- driftseal mcp install --target codex
101
- driftseal mcp install --target kimi-code
102
- driftseal mcp install --target opencode
103
- driftseal mcp install --target claude-code
104
- driftseal mcp install --target cursor
85
+ driftseal extend "Document recovery-link expiry" \
86
+ --accept "the expiry behavior is documented" \
87
+ --verify "npm test && npm run docs:check"
105
88
  ```
106
89
 
107
- Project scope is the default because each DriftSeal MCP server belongs to one
108
- repository. Every target pins `--root` to the repository's canonical absolute
109
- path, and repeated installs are idempotent.
110
-
111
- | Target | Project config | Global config |
112
- | --- | --- | --- |
113
- | `codex` | `.codex/config.toml` | `~/.codex/config.toml` |
114
- | `kimi-code` | `.kimi-code/mcp.json` | `~/.kimi-code/mcp.json` or `$KIMI_CODE_HOME/mcp.json` |
115
- | `opencode` | `opencode.json` | `~/.config/opencode/opencode.json` |
116
- | `claude-code` | `.mcp.json` | `~/.claude.json` |
117
- | `cursor` | `.cursor/mcp.json` | `~/.cursor/mcp.json` |
90
+ Adding acceptance requires a replacement verifier that proves the complete
91
+ accumulated contract. An extension without new acceptance may keep the existing
92
+ verifier or replace it. Every extension invalidates previous machine evidence.
93
+ If the delivery outcome itself changes, close the current outcome honestly and
94
+ begin another one.
118
95
 
119
- Use `--root <repository>` when running the installer elsewhere, or choose the
120
- agent's user-level config explicitly:
96
+ Before completion:
121
97
 
122
98
  ```sh
123
- driftseal mcp install --target <target> --scope global --root /absolute/path/to/repository
99
+ driftseal status
100
+ driftseal verify
101
+ driftseal end --status completed --note "Shipped recovery with expiry documentation."
124
102
  ```
125
103
 
126
- Global installs remain pinned to the selected repository. If the chosen config
127
- already contains a different DriftSeal server entry, the installer leaves it
128
- untouched unless `--force` is supplied. Other agent settings and MCP servers are
129
- preserved.
130
-
131
- The root is startup configuration, not a tool input. In MCP mode DriftSeal also
132
- ignores inherited `DRIFTSEAL_HOME` and `DRIFTSEAL_DECISION_HOME` overrides, so a
133
- tool call cannot redirect writes outside the selected repository.
134
-
135
- The v1 server provides:
136
-
137
- | MCP capability | Purpose |
138
- | --- | --- |
139
- | `driftseal_status`, `driftseal_log` | Read the current intent and intent history. |
140
- | `driftseal_begin`, `driftseal_verify`, `driftseal_end` | Open a work round, capture machine verification evidence, and honestly close it. |
141
- | `driftseal_absorb` | Repair merge collisions or absorb another worktree's logs while remapping colliding IDs. |
142
- | `driftseal_reclaim`, `driftseal_unreclaim` | Hide meaningless closed records behind append-only markers, or restore them. |
143
- | `driftseal_decision_list`, `driftseal_decision_show` | Find and read MADR records. |
144
- | `driftseal_decision_add`, `driftseal_decision_update` | Add selective decisions and reconcile linked ones. |
145
- | `driftseal://intent/current` | Read the current intent as a JSON resource. |
146
- | `driftseal://intents/recent` | Read the ten most recent intents as a JSON resource. |
147
- | `driftseal://decisions` | Read the decision catalog as a JSON resource. |
148
-
149
- `driftseal_absorb` accepts optional incoming intent-log and decision-directory
150
- paths, an `ours` or `theirs` abandon strategy, and a dry-run mode. Incoming
151
- paths are read-only sources; all repaired output stays under the repository
152
- fixed at server startup. The Git merge-driver form remains a CLI-only plumbing
153
- command.
154
-
155
- MCP changes only the execution surface. It does not add policy beyond the
156
- repository's `AGENTS.md`, and the companion skill remains limited to discovery
157
- and recovery guidance.
158
-
159
- ## Optional: keep the agent reminded through hooks
160
-
161
- Agents that support lifecycle hooks can inject a short DriftSeal reminder before
162
- the agent starts answering (`UserPromptSubmit`) and surface a warning when it
163
- finishes (`Stop`). The reminders are advisory — they ask whether the round needs
164
- an intent and whether an open intent still needs verification and
165
- `driftseal end`; they never force another model turn, and they stay silent in
166
- repositories without an intent log.
167
-
168
- Install them with:
104
+ An acceptance-bound outcome can be completed only after fresh successful
105
+ verification. Evidence is bound to both the contract hash and the Git-visible
106
+ workspace fingerprint. A verification command that arrived only through tracked
107
+ log data requires inspection and an explicit `--allow-tracked-command` opt-in.
108
+
109
+ After context loss or handoff, re-anchor before changing durable content:
169
110
 
170
111
  ```sh
171
- cd /path/to/repository
172
- driftseal hook install --target kimi-code --scope global
173
- driftseal hook install --target claude-code
174
- driftseal hook install --target codex
112
+ driftseal status
113
+ driftseal log --last 3
175
114
  ```
176
115
 
177
- | Target | Project config | Global config |
178
- | --- | --- | --- |
179
- | `kimi-code` | Not supported | `~/.kimi-code/config.toml` or `$KIMI_CODE_HOME/config.toml` |
180
- | `claude-code` | `.claude/settings.json` | `~/.claude/settings.json` |
181
- | `codex` | `.codex/hooks.json` | `~/.codex/hooks.json` |
116
+ ## What needs an outcome
117
+
118
+ Record an outcome for durable project-content changes: code, configuration,
119
+ documentation, dependencies, and equivalent files. Git operations, checks,
120
+ temporary auxiliary work, and external state changes are exempt when they do not
121
+ write durable content into the project.
182
122
 
183
- Like `mcp install`, the hook installer accepts `--scope global`,
184
- `--root <repository>`, and `--force`, is idempotent, and preserves unrelated
185
- config entries. Kimi Code documents hooks only in its global `config.toml`, so
186
- its target requires `--scope global`. Claude Code receives prompt context through
187
- `hookSpecificOutput.additionalContext`; its `Stop` reminder uses a UI-only
188
- `systemMessage`, avoiding a continuation loop. Codex installs only the prompt
189
- hook because its `Stop` event has no advisory context channel. Hook commands
190
- search the current directory and its ancestors for an intent log. OpenCode and
191
- Cursor have no supported hook surface for this yet.
123
+ The scope belongs to the worktree, not to the agent process. Agents and subagents
124
+ working in the same worktree resume its matching open outcome. Separate worktrees
125
+ hold separate outcomes.
192
126
 
193
- ## A work round
127
+ ## Lanes
194
128
 
195
- Declare the round before making non-Git changes:
129
+ Orthogonal long-lived capabilities can share one append-only log without sharing
130
+ narrative context. Each outcome belongs to one named lane. Untagged history lives
131
+ on `main`. `status` and `log --last 3` follow the current lane, which is local to
132
+ the worktree.
196
133
 
197
134
  ```sh
198
- driftseal begin "add rate limiting to /api/login" \
199
- --accept "the sixth login attempt within one minute receives HTTP 429" \
200
- --verify "npm test test/rate-limit.test.js"
135
+ driftseal lane add index --desc "On-disk inverted index"
136
+ driftseal lane switch index
137
+ driftseal begin "Ship the inverted index" --accept "lookups return stored postings" --verify "npm test"
201
138
  ```
202
139
 
203
- Do the work, reconcile any linked decisions, inspect the declared command with
204
- `driftseal status`, and only then let DriftSeal run it:
140
+ Close the open outcome before switching. Later work on the same capability
141
+ switches back and begins a new outcome on that lane; it does not reopen a closed
142
+ record. `driftseal lane assign <id> <name>` moves a closed outcome. Cross-cutting
143
+ work stays on `main`. An open outcome stays visible in `log` even when it belongs
144
+ to another lane; `status` prints that lane when it differs from the current one.
145
+
146
+ Lanes cannot be renamed or removed. A typo in `lane add` stays in `driftseal lane`
147
+ output; add the intended name and stop using the old one.
148
+
149
+ If a `begin` names a lane whose `lane_add` is missing, DriftSeal infers the lane
150
+ so `status`, `log`, and `lane` still work, and `lane add` can write the missing
151
+ event. Duplicate `lane_add` records last-write the description instead of failing
152
+ the fold. A `lane_assign` that names an unknown lane infers it the same way.
153
+ If the worktree's current-lane pointer names a lane the WAL no longer
154
+ has, `status`, `log`, and `lane` fall back to `main` with a warning; `begin`
155
+ still refuses until you `lane switch main` or add the lane. `log --last N` can
156
+ return more than N records when an open outcome sits on another lane.
157
+
158
+ A derived lane index caches fold state in Git metadata (or beside a custom
159
+ seal). Incremental rebuilds follow `indexedThrough` and `indexedLines`; a full
160
+ rebuild happens when the log identity changes. Per-lane heads, reverse links,
161
+ and WAL byte ranges are stored for a seek path that is not consumed yet. The
162
+ index is reconstructable and is not committed with the log. Custom-home
163
+ sidecars sit next to `events.jsonl`. When that directory is inside a Git
164
+ worktree, they are listed in its `.gitignore`.
165
+
166
+ ## Decisions and MADR
167
+
168
+ Use a MADR only for context that the outcome log and Git cannot recover: a
169
+ rejected or deferred path worth revisiting, non-obvious rationale for a durable
170
+ choice, or a deprecated or superseded decision.
205
171
 
206
172
  ```sh
207
- driftseal verify
173
+ driftseal decision add "Expire recovery links after one hour" \
174
+ --context "Recovery links are security-sensitive bearer tokens." \
175
+ --outcome "Use a one-hour lifetime and reject older links." \
176
+ --driver "Limit token exposure" \
177
+ --option "No expiry" \
178
+ --option "One-hour expiry" \
179
+ --consequence "Users must request another link after expiry."
208
180
  ```
209
181
 
210
- `driftseal verify` passes the exact stored string to the operating-system shell.
211
- The command can therefore read or modify files, access the network, or run any
212
- other program available to the current user. Treat it as executable code, not as
213
- passive log data. DriftSeal records local provenance when an intent is opened:
214
- the default Git workflow parks the intent in Git metadata, while non-Git and
215
- custom `DRIFTSEAL_HOME` workflows keep a small local marker outside the intent
216
- log. Those locally created intents run normally. If an open intent arrives only
217
- through an intent log, without matching local provenance, DriftSeal cannot confirm
218
- who chose its command. It prints the command to stderr and refuses to execute it
219
- until you inspect it and explicitly run
220
- `driftseal verify --allow-tracked-command`. The programmatic API and MCP tool
221
- expose the equivalent `allowTrackedCommand` opt-in. Local provenance state is
222
- removed when the intent closes. Non-Git markers are bound to the local log
223
- file's identity, so copying a marker with the log does not transfer trust. If
224
- local provenance is lost or no longer matches, verification fails safe and
225
- requires the same explicit opt-in.
226
-
227
- The verification event records the command's exit status, duration, output
228
- digest and byte counts, Git HEAD, and a fingerprint of every tracked or
229
- untracked non-ignored workspace file except the intent event log. A successful
230
- result becomes stale if those workspace contents change. DriftSeal therefore
231
- rejects `completed` until the command passes again on the current workspace.
232
- Command output is spooled to temporary files instead of a fixed in-memory
233
- buffer, then replayed after the command exits and removed. Output size therefore
234
- has no DriftSeal-defined limit, though it remains bounded by available disk space.
235
- Ignored files are deliberately outside this fingerprint. Outside a Git
236
- worktree the fingerprint is unavailable, so the gate proves only the command's
237
- recorded exit status and cannot detect later content changes.
238
-
239
- This proves that the declared command passed on recorded contents; it does not
240
- prove that the acceptance criterion or test is adequate. Existing intents
241
- without `--accept` retain the manual verification workflow for compatibility.
242
- Use protected CI, independent review, or human approval when the verifier was
243
- written by the same agent, the outcome is subjective, or the change is high risk.
182
+ Link an existing decision from `begin` or `extend` with `--decision <id>`. Before
183
+ closing the outcome as `completed` or `partial`, reconcile every linked decision:
244
184
 
245
185
  ```sh
246
- driftseal end \
247
- --status completed \
248
- --note "Added the limiter and covered the failure path" \
249
- --verify-result "4 tests pass"
186
+ driftseal decision update 1 --status accepted --note "Confirmed by the final implementation."
250
187
  ```
251
188
 
252
- If the scope changes, close the current intent as `partial` or `abandoned`, then start a new one. After context loss, use `driftseal status` and `driftseal log --last 3` to re-anchor.
253
-
254
- Record an intent for changes intended to persist in the project: edits to code,
255
- configuration, documentation, dependencies, and equivalent project files. The
256
- boundary does not depend on Git: inside a worktree it includes content intended
257
- for commit, while outside Git it includes durable project files. Everything else
258
- is exempt. Git operations are entirely outside the intent log because Git
259
- maintains their history; inspection, branch and worktree management, staging,
260
- commits, merges, rebases, cherry-picks, tags, and pushes never need an intent of
261
- their own, though they still require normal authorization and safety checks.
262
- Single-step builds and checks, such as compiling or running tests, need no
263
- intent. Auxiliary file or shell operations whose results remain outside durable
264
- project content — an `rsync` scratch copy, temp scaffolding — need none either.
265
- State changes to a remote machine or the local environment are also exempt when
266
- they do not write durable project content into this workspace. When an external
267
- operation does bring durable content into the project, record the intent for
268
- that project-content change, not for the external operation itself.
269
-
270
- In multi-agent work the scope belongs to the worktree, not the writer. One
271
- worktree holds one open intent; every agent or subagent changing durable project
272
- content there re-anchors and continues that matching intent. Agents in separate
273
- worktrees hold separate intents. A configured project root outside Git follows
274
- the same single-intent rule. An agent that only receives another agent's changes
275
- through Git or into a shared worktree records no receiving intent and lets
276
- `verify` expose misalignment. Handoff files are exempt while ignored or
277
- otherwise kept outside durable project content and require an intent when
278
- promoted into it. Taking over work in the same root is a re-anchor, not a
279
- boundary: resume the open intent when its objective still matches the task.
280
-
281
- ## Commands
189
+ ## Command reference
282
190
 
283
191
  | Command | Purpose |
284
- | --- | --- |
285
- | `driftseal begin "<intent>" [--accept "<outcome>"] [-v "<command>"] [--decision id] [--force]` | Open a work-round intent. Repeat `--accept` for observable completion criteria; acceptance requires a verification command. |
286
- | `driftseal verify [--allow-tracked-command]` | Execute the acceptance-bound intent's predeclared command and bind machine evidence to the current Git-visible workspace contents. Commands without matching local provenance require the explicit opt-in. |
287
- | `driftseal end [id] [-s status] [-n note] [-r verify-result]` | Close an intent honestly. |
288
- | `driftseal status` | Show the intent currently in progress. |
289
- | `driftseal log [-n N] [--all]` | Review intent history (`--all` includes reclaimed records). |
290
- | `driftseal reclaim [id ...] --reason "..." [--older-than days] [--force] [--dry-run]` | Hide meaningless closed records behind append-only markers. |
291
- | `driftseal unreclaim <id> --reason "..."` | Restore a reclaimed record to the visible log. |
292
- | `driftseal absorb [other-events.jsonl] [--decisions dir] [--abandon-theirs \| --abandon-ours] [--dry-run]` | Merge another worktree's logs, remapping colliding intent and decision ids. |
293
- | `driftseal absorb --git <base> <ours> <theirs>` | Git merge driver for `.intent-log/events.jsonl`. |
294
- | `driftseal decision add "<title>" -c "..." -o "..."` | Write a numbered MADR decision. |
295
- | `driftseal decision update <id> [-s status] -n "..."` | Reconcile a linked decision in the open intent. |
296
- | `driftseal decision list [-s status] [--last N \| --count]` | List or count decision records, optionally filtered by status. |
297
- | `driftseal decision show <id>` | Read one decision record. |
298
- | `driftseal skill install --target TARGET [--scope project\|global] [--root path] [--force]` | Install the bundled skill for Codex, Kimi Code, OpenCode, Claude Code, or Cursor. |
299
- | `driftseal mcp install --target TARGET [--scope project\|global] [--root path] [--force]` | Install the repository-pinned MCP server into Codex, Kimi Code, OpenCode, Claude Code, or Cursor. |
300
- | `driftseal hook install --target TARGET [--scope project\|global] [--root path] [--force]` | Install advisory lifecycle reminders into Kimi Code, Claude Code, or Codex. |
301
- | `driftseal hook prompt\|stop [--format plain\|claude-code]` | Emit the reminder a lifecycle hook injects; never blocks. |
302
- | `driftseal init [--lang <tag>] [--local-log]` | Add the adoption protocol to `AGENTS.md` and configure the git merge driver. `--lang` sets the intent/decision log language (BCP 47, default `en`). `--local-log` keeps the logs local and untracked instead of committing them with the code; if the logs are already tracked, init warns with the remediation steps and leaves the index and `.gitignore` untouched. |
303
- | `driftseal --version` or `driftseal -V` | Print the installed DriftSeal version. |
304
- | `driftseal help` | Print CLI usage. |
305
-
306
- When `begin` declares one or more `--decision <id>` links, every linked
307
- decision must be reconciled with `driftseal decision update` before that intent can
308
- close as `completed` or `partial`. The update changes the current status when
309
- requested and appends a timestamped history entry tied to the intent. Intents
310
- without decision links keep the ordinary workflow. For acceptance-bound linked
311
- intents, perform every decision update before `driftseal verify`, because a decision
312
- update changes the workspace fingerprint: reconcile, verify, then end.
313
-
314
- ## Reclaiming noise records
315
-
316
- Some closed records stop mattering: a harness or sandbox failure is recorded
317
- honestly as `failed`, but it says nothing about the project. `driftseal
318
- reclaim` retires such records without rewriting history — it appends a
319
- `reclaim` marker (with a mandatory `--reason`) to the same append-only log,
320
- and reclaimed records disappear from `driftseal log` and `driftseal status`
321
- output while remaining in `events.jsonl` and visible with `log --all`.
322
- `driftseal unreclaim <id> --reason "..."` restores a record that turned out to
323
- matter.
324
-
325
- Without ids, batch mode reclaims only closed `failed`/`abandoned` records that
326
- are not linked to decisions and are older than `--older-than` days (default
327
- 7); use `--dry-run` to preview. `completed` and `partial` records, and any
328
- decision-linked record, can only be reclaimed by explicit id with `--force`.
329
-
330
- ## Consistency and recovery
331
-
332
- DriftSeal serializes mutating commands with locks on the configured intent and
333
- decision-log roots, acquired in a stable order. Decision reconciliation is
334
- journaled as prepare and commit events around an atomic MADR replacement. If
335
- the process stops between those steps, the next linked `decision update` or
336
- successful `end` recovers the transaction from content hashes. A successful
337
- linked-intent close also verifies that the decision file has not changed since
338
- its latest reconciliation. Unlinked intents do not parse the decision log, and
339
- `failed` or `abandoned` remains an escape path when decision recovery cannot
340
- complete. Those terminal statuses cancel recovery for their pending
341
- transactions, and recovery is scoped to the current intent so historical
342
- conflicts cannot block later decision work.
343
-
344
- New events carry a schema version. DriftSeal rejects newer unsupported schemas and
345
- fails closed if a legacy client closes a linked intent without reconciliation.
346
- `driftseal init` writes versioned managed blocks and upgrades only exact, recognized
347
- older blocks. Current-version blocks that differ only by log language are also
348
- recognized, so `--lang` can change the language without rewriting policy by hand.
349
- It refuses newer protocol versions and any unrecognized or customized block
350
- without changing `AGENTS.md`.
351
-
352
- `--count` prints only the number of records remaining after status filtering.
353
- It cannot be combined with `--last`, whose limiting semantics would make the
354
- count ambiguous. Decision filenames form a lightweight in-memory index: `show`
355
- parses only the requested record, and an unfiltered `--count` reads no MADR
356
- contents. Status-filtered listing and counting parse all records because status
357
- is stored in each MADR document; DriftSeal does not maintain a stale-prone sidecar
358
- index.
359
-
360
- ## Merging worktrees
361
-
362
- Two worktrees allocate intent and decision ids from their local logs, so a
363
- same-day parallel `begin` or `decision add` can collide when the branches
364
- merge. `driftseal absorb` rebuilds a valid log by keeping our ids and remapping
365
- the incoming side, then prints the mapping. A single lineage stays append-only;
366
- absorb is the one cross-lineage rewrite.
192
+ |---|---|
193
+ | `driftseal begin "<outcome>" [--accept "..."] [--verify "..."] [--decision id] [--force]` | Open one coherent outcome. |
194
+ | `driftseal extend "<addition>" [--accept "..."] [--verify "..."] [--decision id]` | Append scope to the same outcome and invalidate earlier verification. |
195
+ | `driftseal verify [--allow-tracked-command]` | Execute the declared cumulative verifier and bind evidence. |
196
+ | `driftseal end [id] [-s status] [-n note] [-r verify-result]` | Close an outcome honestly. |
197
+ | `driftseal status` | Show the outcome in progress and the current lane. |
198
+ | `driftseal log [--last N] [--all] [--all-lanes]` | Review outcome history (current lane unless `--all-lanes`). |
199
+ | `driftseal lane add\|switch\|assign\|show` | Partition history by long-lived capability. |
200
+ | `driftseal reclaim [id ...] --reason "..." [--force]` | Hide meaningless closed records with append-only markers. |
201
+ | `driftseal unreclaim <id> --reason "..."` | Restore a reclaimed record. |
202
+ | `driftseal absorb [other-events.jsonl] [--decisions dir] [--abandon-theirs\|--abandon-ours]` | Merge another lineage and remap colliding outcome or MADR ids. |
203
+ | `driftseal decision add\|update\|list\|show` | Manage MADR records. |
204
+ | `driftseal migrate v1-to-v2 inspect --json [migration paths]` | Normalize v1 state for model-assisted grouping. |
205
+ | `driftseal migrate v1-to-v2 apply --plan <file> [migration paths]` | Validate a grouping plan and stage the v2 seal beside v1. |
206
+ | `driftseal migrate v1-to-v2 check [migration paths]` | Validate the staged result and report the review/deletion gate. |
207
+ | `driftseal init [--lang tag] [--local-log]` | Install or upgrade the repository protocol. |
208
+
209
+ Run `driftseal help` for the complete syntax, including skill, MCP, and hook
210
+ installation targets.
211
+
212
+ ## Migrating from v1
213
+
214
+ Migration is deliberately model-assisted because grouping step-sized intents
215
+ into delivered outcomes is semantic work.
216
+
217
+ When an unmigrated v1 intent log or MADR directory is present, normal v2
218
+ repository commands fail closed instead of silently starting an unrelated
219
+ `.seal` lineage. A MADR-only v1 repository may migrate without creating an
220
+ empty intent log first.
221
+
222
+ 1. Close every v1 intent. A parked v1 intent blocks migration. After upgrading
223
+ the CLI, close it with `driftseal end` (for example `--status abandoned`)
224
+ before `inspect`. Merge or freeze branches that still edit `.intent-log`
225
+ first; `absorb --git` keeps both sides of a v1 log merge instead of dropping
226
+ theirs.
227
+ 2. Inspect the normalized source:
228
+
229
+ ```sh
230
+ driftseal migrate v1-to-v2 inspect --json > /tmp/driftseal-inspection.json
231
+ ```
232
+
233
+ 3. Have the model propose a `driftseal-v1-to-v2-plan` JSON document. Its groups
234
+ must form an ordered, complete partition of all visible v1 records. Only
235
+ records already reclaimed in v1 may be excluded, and every exclusion needs a
236
+ reason. `groups` may be empty when no visible records remain; MADRs are still
237
+ migrated.
238
+ 4. Review the proposed outcomes, then apply the approved plan:
239
+
240
+ ```sh
241
+ driftseal migrate v1-to-v2 apply --plan /tmp/driftseal-plan.json
242
+ driftseal migrate v1-to-v2 check
243
+ ```
244
+
245
+ `apply` fingerprints the source, validates the partition, validates the staged
246
+ v2 log, copies every v1 MADR byte-for-byte, and records a name, size, and hash
247
+ manifest so `check` can still verify them after v1 is removed. Later MADR content
248
+ is accepted only when the latest valid v2 reconciliation attests its current hash. `apply`
249
+ creates `.seal/` beside `.intent-log/` and `.decision-log/`; it never deletes v1
250
+ data. After the user has reviewed and approved the result, remove the old paths
251
+ manually. `check` prints `git rm` when those paths are tracked and `rm -rf` when
252
+ they are local-only. Running `check` afterward reports migration complete.
253
+
254
+ If v1 used custom storage, keep the source and destination explicit for inspect
255
+ and apply. `DRIFTSEAL_DECISION_HOME` is a v1-only default for the MADR source
256
+ and for fail-closed detection; v2 runtime ignores it. The migration marker records repository-local paths as portable
257
+ identities, so later checks recover the source paths from the destination even
258
+ after the repository moves. The destination must not contain, or be contained
259
+ by, the source log file or MADR directory. In particular, an inherited v1
260
+ `DRIFTSEAL_HOME` must not also be used as the v2 destination:
367
261
 
368
262
  ```sh
369
- driftseal absorb ../other-worktree/.intent-log/events.jsonl \
370
- --decisions ../other-worktree/.decision-log
263
+ driftseal migrate v1-to-v2 inspect --json \
264
+ --source-log /path/to/v1-intents/events.jsonl \
265
+ --source-decisions /path/to/v1-decisions \
266
+ --destination /path/to/repository/.seal
267
+ driftseal migrate v1-to-v2 apply --plan /tmp/driftseal-plan.json \
268
+ --source-log /path/to/v1-intents/events.jsonl \
269
+ --source-decisions /path/to/v1-decisions \
270
+ --destination /path/to/repository/.seal
271
+ driftseal migrate v1-to-v2 check \
272
+ --destination /path/to/repository/.seal
371
273
  ```
372
274
 
373
- With no path, `absorb` repairs the current log after a git conflict or a
374
- concatenated duplicate. If both sides still have an open intent, pass
375
- `--abandon-theirs` or `--abandon-ours`. Concurrent edits of a decision that
376
- already existed in the shared base are not auto-merged.
275
+ After applying, unset the v1 `DRIFTSEAL_HOME` or point it at the new seal root.
276
+ The Node API exposes `sourceLog`, `sourceDecisions`, and `destination`. MCP
277
+ migration tools accept custom v1 sources but always stage into the server's
278
+ fixed repository `.seal`, so ordinary MCP workflow tools immediately see the
279
+ migrated state.
377
280
 
378
- `driftseal init` writes that absorb rule into `AGENTS.md`, plus `.gitattributes`
379
- and the local git merge driver so `events.jsonl` merges through `absorb --git`.
380
- When decision ids collide, the driver stops the merge before Git can commit an
381
- ambiguous decision catalog. Run `driftseal absorb`, stage the repaired intent
382
- and decision logs, then continue the merge. Clones need `init` again because
383
- the driver lives in local git config.
281
+ ## Git and merge behavior
384
282
 
385
- In a Git worktree, `begin` parks the open intent in Git metadata instead of
386
- appending to the tracked `events.jsonl`. Git can merge while that intent is
387
- still in progress, so you do not need a log-only commit just to get a clean
388
- tree. `end` moves the parked records into the tracked log and writes the closing
389
- record there — never into Git metadata — so an interrupted `end` leaves the
390
- intent open in the log and can simply be run again. If the parked intent's id
391
- collides with incoming merged events, DriftSeal remaps it the same way `absorb`
392
- remaps colliding worktree ids.
283
+ In a Git worktree, `begin` parks the open outcome in Git metadata so it does not
284
+ dirty the tracked log. `end` flushes the lineage to
285
+ `.seal/outcomes/events.jsonl`. The event log is append-only during normal work.
393
286
 
394
- When a merge brings in a second open intent, `absorb --abandon-ours` closes the
395
- parked one into the tracked log and `absorb --abandon-theirs` closes the
396
- incoming one and leaves yours parked. `end <id>` also works on the incoming
397
- intent directly, and `begin --force` abandons every open intent at once.
287
+ After a merge collision, run:
398
288
 
399
- ## Storage
289
+ ```sh
290
+ driftseal absorb
291
+ ```
400
292
 
401
- - `.intent-log/events.jsonl` is the append-only intent log. All access goes through `driftseal` (CLI or MCP) — never read, edit, move, or delete it directly; use `driftseal reclaim` to retire meaningless records instead of deleting log lines. After a merge collision, use `driftseal absorb` instead of editing the file.
402
- - `.decision-log/` contains numbered MADR decision records.
403
- - Set `DRIFTSEAL_HOME` or `DRIFTSEAL_DECISION_HOME` to store either log outside the current project.
293
+ Do not edit the JSONL manually. `absorb` remaps colliding outcome and decision
294
+ ids, rebinds affected contract hashes, and refuses concurrent edits of a shared
295
+ MADR. If both lineages remain open, choose explicitly with `--abandon-theirs` or
296
+ `--abandon-ours`.
404
297
 
405
- Together, intent events, selective decision records, and Git commits form a layered project log: intent events capture what a work round set out to do and how it would be verified; decision records preserve rationale, rejected paths, or deferred choices that the other layers cannot reconstruct; commits show the coherent change that actually landed. DriftSeal complements Git history rather than duplicating or replacing it.
298
+ ## Node API and MCP
406
299
 
407
- Keeping the DriftSeal logs in version control makes the project’s working agreements and decision trail travel with the code. The npm package uses an explicit file allowlist, so project-local agent logs are not published to npm.
300
+ ```js
301
+ const { createApi } = require('driftseal');
302
+
303
+ const seal = createApi({ root: process.cwd(), isolateStorage: true });
304
+ seal.begin({
305
+ outcome: 'Ship account recovery',
306
+ acceptance: ['the recovery tests pass'],
307
+ verify: 'npm test',
308
+ });
309
+ seal.extend({ extension: 'Document token expiry' });
310
+ ```
311
+
312
+ The API also exposes `status`, `verify`, `end`, `log`, `lane`, `laneAdd`,
313
+ `laneSwitch`, `laneAssign`, `absorb`, reclaim, decision, init, and migration
314
+ methods.
315
+
316
+ The stdio MCP server fixes all operations to one repository root. Its v2 tools
317
+ include `driftseal_status`, `driftseal_begin`, `driftseal_extend`,
318
+ `driftseal_verify`, `driftseal_end`, outcome history, lane, and absorb tools,
319
+ MADR tools, and the three migration tools. Resources are:
320
+
321
+ - `driftseal://outcome/current`
322
+ - `driftseal://outcomes/recent`
323
+ - `driftseal://lanes`
324
+ - `driftseal://madr`
325
+
326
+ ## Storage and trust boundary
327
+
328
+ - `.seal/outcomes/events.jsonl` is the append-only outcome log. Access it through
329
+ DriftSeal; use `reclaim`, `unreclaim`, `lane`, and `absorb` instead of manual edits.
330
+ - `.seal/madr/` stores numbered MADR documents.
331
+ - `$DRIFTSEAL_HOME` replaces the `.seal` root.
332
+ - The current lane and derived lane index live in Git metadata for a default
333
+ repository seal, or beside a custom seal (`outcomes/.current-lane` and
334
+ `outcomes/.lane-index.json`). When the custom seal sits inside a Git
335
+ worktree, those sidecars are gitignored. They are reconstructable and are not
336
+ part of the committed WAL.
337
+ - Advisory hooks remind agents about lifecycle state but never broaden the
338
+ repository's `AGENTS.md` policy.
339
+
340
+ DriftSeal does not decide whether a verification command is safe or whether a
341
+ test is adequate. Inspect commands before execution and apply normal repository
342
+ authorization and security rules.
408
343
 
409
344
  ## Development
410
345
 
411
346
  ```sh
412
347
  npm test
348
+ node --check bin/driftseal.js
349
+ node --check bin/driftseal-mcp.js
350
+ npm pack --dry-run
413
351
  ```
414
352
 
415
- Contributions are welcome. Keep changes focused, add regression coverage for behavior changes, and run the test suite before opening a pull request.
416
-
417
- ## License
418
-
419
- MIT. See [`LICENSE`](LICENSE).
353
+ Licensed under the MIT License.