driftseal 3.3.0 → 3.4.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 +34 -7
- package/README.zh-CN.md +26 -5
- package/bin/driftseal.js +61 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
> **Seal the outcome. Stop the drift.**
|
|
4
4
|
|
|
5
|
-
DriftSeal
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
DriftSeal is an engineering protocol and toolchain for coding agents executing
|
|
8
|
+
long tasks independently, with state kept in the repository. It keeps work
|
|
9
|
+
anchored to a coherent delivery outcome: record the outcome before durable work
|
|
10
|
+
begins, append extensions toward that same outcome, bind verification to the
|
|
11
|
+
accumulated contract and workspace, and preserve the decisions that need durable
|
|
12
|
+
rationale.
|
|
10
13
|
|
|
11
14
|
```text
|
|
12
15
|
begin an outcome → extend the same outcome → verify the cumulative contract → close
|
|
@@ -16,6 +19,19 @@ One worktree owns one open outcome. Git records what landed; DriftSeal records
|
|
|
16
19
|
what the work was meant to achieve, how completion was proved, and why durable
|
|
17
20
|
decisions were made.
|
|
18
21
|
|
|
22
|
+
## When to use DriftSeal
|
|
23
|
+
|
|
24
|
+
Use DriftSeal when an agent needs to carry a multi-step engineering task through
|
|
25
|
+
to completion without continuous human guidance. Explicit acceptance criteria,
|
|
26
|
+
cumulative verification, and decision reconciliation provide a structured way to
|
|
27
|
+
track delivery and resume the same contract after context loss or handoff.
|
|
28
|
+
|
|
29
|
+
That rigor adds process overhead. When a person is actively reviewing progress,
|
|
30
|
+
clarifying scope, and guiding the agent, we recommend
|
|
31
|
+
[Inkan](https://github.com/rowan-hiro/inkan) as the lighter option. It records
|
|
32
|
+
delivery intent, changes, and declared results while leaving evaluation to the
|
|
33
|
+
person and the repository's normal tests.
|
|
34
|
+
|
|
19
35
|
## What changed in v2
|
|
20
36
|
|
|
21
37
|
DriftSeal v2 is an outcome log rather than an intent-per-step log.
|
|
@@ -30,8 +46,8 @@ DriftSeal v2 is an outcome log rather than an intent-per-step log.
|
|
|
30
46
|
and MADR reconciliation.
|
|
31
47
|
- Stored events use `logVersion: 2`. Compatible clients accept `schemaVersion`
|
|
32
48
|
`1` or `2`; lane events and non-default `begin.lane` use `schemaVersion: 2`.
|
|
33
|
-
- The generated `AGENTS.md` protocol series is `2.
|
|
34
|
-
recognized `2.0` blocks.
|
|
49
|
+
- The generated `AGENTS.md` protocol series is `2.2`. `driftseal init` upgrades
|
|
50
|
+
recognized `2.0` and `2.1` blocks.
|
|
35
51
|
- Named lanes partition outcome history on the same WAL. The default lane is
|
|
36
52
|
`main`; `driftseal log` follows the current lane.
|
|
37
53
|
- The public CLI, Node API, MCP tools, and MCP resources use outcome terminology.
|
|
@@ -75,6 +91,8 @@ may still write `.seal/outcomes/.gitignore` so derived sidecars stay untracked.
|
|
|
75
91
|
|
|
76
92
|
## Core workflow
|
|
77
93
|
|
|
94
|
+

|
|
95
|
+
|
|
78
96
|
Open the coherent delivery outcome before changing durable project content:
|
|
79
97
|
|
|
80
98
|
```sh
|
|
@@ -98,6 +116,13 @@ verifier or replace it. Every extension invalidates previous machine evidence.
|
|
|
98
116
|
If the delivery outcome itself changes, close the current outcome honestly and
|
|
99
117
|
begin another one.
|
|
100
118
|
|
|
119
|
+
When the agent is in plan mode, write every `driftseal` command this work will
|
|
120
|
+
run into the plan file as an explicit command. Closing commands may leave
|
|
121
|
+
status and note as placeholders. As soon as plan mode ends, re-anchor before
|
|
122
|
+
any other action. End an unrelated open outcome before any lane switch. Switch
|
|
123
|
+
lanes when this work belongs to another lane. Then `begin` or `extend` to
|
|
124
|
+
match that re-anchored state.
|
|
125
|
+
|
|
101
126
|
Before completion:
|
|
102
127
|
|
|
103
128
|
```sh
|
|
@@ -118,6 +143,8 @@ driftseal status
|
|
|
118
143
|
driftseal log --last 3
|
|
119
144
|
```
|
|
120
145
|
|
|
146
|
+

|
|
147
|
+
|
|
121
148
|
## What needs an outcome
|
|
122
149
|
|
|
123
150
|
Record an outcome for durable project-content changes: code, configuration,
|
package/README.zh-CN.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
> **Seal the outcome. Stop the drift.**
|
|
4
4
|
|
|
5
|
-
DriftSeal
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
DriftSeal 是一套面向 Agent 独立执行长任务的工程化协议与工具链,状态随 repo 保存。
|
|
8
|
+
它让 coding agent 始终围绕一个完整的交付 outcome 工作:在持久改动开始前记录
|
|
9
|
+
outcome,以 append-only 方式补充同一 outcome 的后续步骤,把验证结果绑定到累计
|
|
10
|
+
contract 和工作区,并只为确实需要长期保留理由的选择建立 MADR。
|
|
9
11
|
|
|
10
12
|
```text
|
|
11
13
|
开启 outcome → 扩展同一 outcome → 验证累计 contract → 关闭
|
|
@@ -14,6 +16,16 @@ DriftSeal 是一套跟随 repo 保存的协议与工具,用来让 coding agent
|
|
|
14
16
|
一个 worktree 只持有一个 open outcome。Git 记录最终落地了什么;DriftSeal 记录这轮
|
|
15
17
|
工作想交付什么、如何证明完成,以及长期 decision 背后的理由。
|
|
16
18
|
|
|
19
|
+
## 什么时候使用 DriftSeal
|
|
20
|
+
|
|
21
|
+
当 Agent 需要在缺少人持续引导的情况下,独立推进并完成多步骤工程任务时,适合使用
|
|
22
|
+
DriftSeal。明确的验收标准、累计验证和 decision reconciliation 为交付提供结构化
|
|
23
|
+
约束,也让 Agent 在上下文丢失或交接后,可以回到同一份交付约定继续执行。
|
|
24
|
+
|
|
25
|
+
这些工程化约束也会增加流程成本。如果有人持续审阅进展、澄清范围并引导 Agent,
|
|
26
|
+
推荐使用更轻量的 [Inkan](https://github.com/rowan-hiro/inkan)。它记录交付意图、
|
|
27
|
+
过程中的变更和结束时声明的结果,把结果判断留给人和仓库原有的测试流程。
|
|
28
|
+
|
|
17
29
|
## v2 的变化
|
|
18
30
|
|
|
19
31
|
DriftSeal v2 从“按步骤记录 intent”改为“按交付记录 outcome”。
|
|
@@ -25,7 +37,7 @@ DriftSeal v2 从“按步骤记录 intent”改为“按交付记录 outcome”
|
|
|
25
37
|
- 每次 extend 都会改变 contract hash,并让之前的 verification 与 MADR reconciliation 失效。
|
|
26
38
|
- event 使用 `logVersion: 2`。兼容客户端接受 `schemaVersion` `1` 或 `2`;lane
|
|
27
39
|
事件以及非默认的 `begin.lane` 使用 `schemaVersion: 2`。
|
|
28
|
-
- `AGENTS.md` 的新协议版本是 `2.
|
|
40
|
+
- `AGENTS.md` 的新协议版本是 `2.2`。`driftseal init` 会升级可识别的 `2.0` 和 `2.1` block。
|
|
29
41
|
- 具名 lane 在同一条 WAL 上切分 outcome 历史。默认 lane 是 `main`;`driftseal log`
|
|
30
42
|
跟随当前 lane。
|
|
31
43
|
- CLI、Node API、MCP tool 与 resource 全部使用 outcome 命名;v1 名称和路径不会作为
|
|
@@ -68,6 +80,8 @@ attribute,并配置本地 Git merge driver。Git config 不会随 clone 传播
|
|
|
68
80
|
|
|
69
81
|
## 基本工作流
|
|
70
82
|
|
|
83
|
+

|
|
84
|
+
|
|
71
85
|
在修改持久项目内容前,先开启完整的交付 outcome:
|
|
72
86
|
|
|
73
87
|
```sh
|
|
@@ -89,6 +103,11 @@ driftseal extend "Document recovery-link expiry" \
|
|
|
89
103
|
acceptance 的 extend 可以沿用原 verifier,也可以替换它。任何 extend 都会让之前的
|
|
90
104
|
machine evidence 失效。如果交付目标本身变了,应诚实关闭当前 outcome,再开启新的。
|
|
91
105
|
|
|
106
|
+
Agent 处于 plan 模式时,要把这次工作将要运行的每条 `driftseal` 命令作为显式命令
|
|
107
|
+
写进计划文件。结束命令的 status 和 note 可以写成占位符。plan 模式一结束,先重新
|
|
108
|
+
锚定,再做其他动作。若已有无关的 open outcome,在切换 lane 之前结束它。工作属于
|
|
109
|
+
另一条 lane 时再切换。然后按重新锚定后的状态执行 `begin` 或 `extend`。
|
|
110
|
+
|
|
92
111
|
完成前依次执行:
|
|
93
112
|
|
|
94
113
|
```sh
|
|
@@ -109,6 +128,8 @@ driftseal status
|
|
|
109
128
|
driftseal log --last 3
|
|
110
129
|
```
|
|
111
130
|
|
|
131
|
+

|
|
132
|
+
|
|
112
133
|
## 哪些工作需要 outcome
|
|
113
134
|
|
|
114
135
|
准备长期留在项目中的代码、配置、文档、依赖及同类文件改动需要 outcome。Git 操作、
|
package/bin/driftseal.js
CHANGED
|
@@ -56,7 +56,7 @@ const LOG_VERSION = 2;
|
|
|
56
56
|
const EVENT_SCHEMA_VERSION = 2;
|
|
57
57
|
const DEFAULT_WRITE_SCHEMA_VERSION = 1;
|
|
58
58
|
const LEGACY_EVENT_SCHEMA_VERSION = 4;
|
|
59
|
-
const PROTOCOL_VERSION = '2.
|
|
59
|
+
const PROTOCOL_VERSION = '2.2';
|
|
60
60
|
const DEFAULT_LOG_LANGUAGE = 'en';
|
|
61
61
|
const DEFAULT_LANE = 'main';
|
|
62
62
|
const LANE_NAME_RE = /^[a-z][a-z0-9-]{0,62}$/;
|
|
@@ -2698,17 +2698,40 @@ Seal root: \`.seal/\` (override with \`$DRIFTSEAL_HOME\`); outcome log:
|
|
|
2698
2698
|
${INTENT_PROTOCOL_END}`;
|
|
2699
2699
|
}
|
|
2700
2700
|
|
|
2701
|
-
function intentProtocolBlockV21(version =
|
|
2702
|
-
|
|
2701
|
+
function intentProtocolBlockV21(version = '2.1', language = DEFAULT_LOG_LANGUAGE, localLog = false) {
|
|
2702
|
+
if (String(version) !== '2.1') fail(`protocol block 2.1 cannot be stamped as version ${version}`);
|
|
2703
|
+
return intentProtocolBlockV21Text('2.1', language, localLog, { historyRepair: true });
|
|
2703
2704
|
}
|
|
2704
2705
|
|
|
2705
|
-
function intentProtocolBlockV21AbsorbCollisions(version =
|
|
2706
|
-
|
|
2706
|
+
function intentProtocolBlockV21AbsorbCollisions(version = '2.1', language = DEFAULT_LOG_LANGUAGE, localLog = false) {
|
|
2707
|
+
if (String(version) !== '2.1') fail(`protocol block 2.1 cannot be stamped as version ${version}`);
|
|
2708
|
+
return intentProtocolBlockV21Text('2.1', language, localLog, { historyRepair: false });
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
const PLAN_MODE_PROTOCOL_STEP = `5. **Plan mode writes the commands, then runs them first.** When the agent is
|
|
2712
|
+
in plan mode, write every \`driftseal\` command this work will run into the
|
|
2713
|
+
plan file as an explicit command. Closing commands may leave status and note
|
|
2714
|
+
as placeholders. As soon as plan mode ends, re-anchor before any other
|
|
2715
|
+
action. End an unrelated open outcome before any lane switch. Switch lanes
|
|
2716
|
+
when this work belongs to another lane. Then \`begin\` or \`extend\` to match
|
|
2717
|
+
that re-anchored state.`;
|
|
2718
|
+
|
|
2719
|
+
function intentProtocolBlockV22(version = PROTOCOL_VERSION, language = DEFAULT_LOG_LANGUAGE, localLog = false) {
|
|
2720
|
+
if (String(version) !== String(PROTOCOL_VERSION)) {
|
|
2721
|
+
fail(`protocol block ${PROTOCOL_VERSION} cannot be stamped as version ${version}`);
|
|
2722
|
+
}
|
|
2723
|
+
return intentProtocolBlockV21Text(PROTOCOL_VERSION, language, localLog, { historyRepair: true }).replace(
|
|
2724
|
+
'\n\n**Log access goes only through DriftSeal.**',
|
|
2725
|
+
`\n${PLAN_MODE_PROTOCOL_STEP}\n\n**Log access goes only through DriftSeal.**`
|
|
2726
|
+
);
|
|
2707
2727
|
}
|
|
2708
2728
|
|
|
2709
2729
|
function intentProtocolBlock(version = PROTOCOL_VERSION, language = DEFAULT_LOG_LANGUAGE, localLog = false) {
|
|
2710
|
-
|
|
2711
|
-
return
|
|
2730
|
+
const requested = String(version);
|
|
2731
|
+
if (requested === '2.0') return intentProtocolBlockV20(language, localLog);
|
|
2732
|
+
if (requested === '2.1') return intentProtocolBlockV21('2.1', language, localLog);
|
|
2733
|
+
if (requested === String(PROTOCOL_VERSION)) return intentProtocolBlockV22(PROTOCOL_VERSION, language, localLog);
|
|
2734
|
+
fail(`unsupported outcome protocol version "${version}"`);
|
|
2712
2735
|
}
|
|
2713
2736
|
|
|
2714
2737
|
function v1IntentProtocolBlock(version = 14, language = DEFAULT_LOG_LANGUAGE, localLog = false) {
|
|
@@ -3002,13 +3025,22 @@ function protocolEol(content, eol) {
|
|
|
3002
3025
|
return eol === '\n' ? content : content.replace(/\n/g, eol);
|
|
3003
3026
|
}
|
|
3004
3027
|
|
|
3005
|
-
function decisionProtocolBlockAbsorbCollisions(version =
|
|
3006
|
-
|
|
3028
|
+
function decisionProtocolBlockAbsorbCollisions(version = '2.1', language = DEFAULT_LOG_LANGUAGE, localLog = false) {
|
|
3029
|
+
const requested = String(version);
|
|
3030
|
+
if (requested !== '2.0' && requested !== '2.1') {
|
|
3031
|
+
fail(`absorb-collisions decision protocol cannot be stamped as version ${version}`);
|
|
3032
|
+
}
|
|
3033
|
+
return decisionProtocolBlockText(requested, language, localLog, { historyRepair: false });
|
|
3007
3034
|
}
|
|
3008
3035
|
|
|
3009
3036
|
function decisionProtocolBlock(version = PROTOCOL_VERSION, language = DEFAULT_LOG_LANGUAGE, localLog = false) {
|
|
3010
|
-
|
|
3011
|
-
return
|
|
3037
|
+
const requested = String(version);
|
|
3038
|
+
if (requested === '2.0') return decisionProtocolBlockAbsorbCollisions('2.0', language, localLog);
|
|
3039
|
+
if (requested === '2.1') return decisionProtocolBlockText('2.1', language, localLog, { historyRepair: true });
|
|
3040
|
+
if (requested === String(PROTOCOL_VERSION)) {
|
|
3041
|
+
return decisionProtocolBlockText(PROTOCOL_VERSION, language, localLog, { historyRepair: true });
|
|
3042
|
+
}
|
|
3043
|
+
fail(`unsupported decision protocol version "${version}"`);
|
|
3012
3044
|
}
|
|
3013
3045
|
|
|
3014
3046
|
function decisionProtocolBlockText(version = PROTOCOL_VERSION, language = DEFAULT_LOG_LANGUAGE, localLog = false, { historyRepair = true } = {}) {
|
|
@@ -3132,6 +3164,14 @@ function previousDecisionProtocolBlock(version, language = DEFAULT_LOG_LANGUAGE,
|
|
|
3132
3164
|
return v8.replace(' --driver "<decision driver>"', '');
|
|
3133
3165
|
}
|
|
3134
3166
|
|
|
3167
|
+
function customizedProtocolBlockError(marker) {
|
|
3168
|
+
return (
|
|
3169
|
+
`cannot safely upgrade customized protocol block beginning with ${marker}; ` +
|
|
3170
|
+
'the block may come from a newer DriftSeal release even when its protocol version matches ' +
|
|
3171
|
+
`(current client: ${PACKAGE_VERSION}); upgrade DriftSeal and retry`
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3135
3175
|
function upgradeManagedBlock({
|
|
3136
3176
|
content,
|
|
3137
3177
|
marker,
|
|
@@ -3182,7 +3222,7 @@ function upgradeManagedBlock({
|
|
|
3182
3222
|
key === protocolBlockKey(replacement) ||
|
|
3183
3223
|
knownManagedBlocks.some((known) => key === protocolBlockKey(known));
|
|
3184
3224
|
if (!recognized) {
|
|
3185
|
-
fail(
|
|
3225
|
+
fail(customizedProtocolBlockError(marker));
|
|
3186
3226
|
}
|
|
3187
3227
|
return {
|
|
3188
3228
|
content: content.slice(0, start) + replacement + content.slice(after),
|
|
@@ -3197,7 +3237,7 @@ function upgradeManagedBlock({
|
|
|
3197
3237
|
found: true,
|
|
3198
3238
|
};
|
|
3199
3239
|
}
|
|
3200
|
-
fail(
|
|
3240
|
+
fail(customizedProtocolBlockError(marker));
|
|
3201
3241
|
}
|
|
3202
3242
|
|
|
3203
3243
|
const MCP_TARGETS = ['codex', 'kimi-code', 'opencode', 'claude-code', 'cursor'];
|
|
@@ -6845,8 +6885,10 @@ const commands = {
|
|
|
6845
6885
|
...sourceLanguages.flatMap((source) => [
|
|
6846
6886
|
protocolEol(intentProtocolBlock(PROTOCOL_VERSION, source), eol),
|
|
6847
6887
|
protocolEol(intentProtocolBlock(PROTOCOL_VERSION, source, true), eol),
|
|
6848
|
-
protocolEol(
|
|
6849
|
-
protocolEol(
|
|
6888
|
+
protocolEol(intentProtocolBlock('2.1', source), eol),
|
|
6889
|
+
protocolEol(intentProtocolBlock('2.1', source, true), eol),
|
|
6890
|
+
protocolEol(intentProtocolBlockV21AbsorbCollisions('2.1', source), eol),
|
|
6891
|
+
protocolEol(intentProtocolBlockV21AbsorbCollisions('2.1', source, true), eol),
|
|
6850
6892
|
protocolEol(intentProtocolBlockV20(source), eol),
|
|
6851
6893
|
protocolEol(intentProtocolBlockV20(source, true), eol),
|
|
6852
6894
|
protocolEol(v1IntentProtocolBlock(14, source), eol),
|
|
@@ -6881,8 +6923,10 @@ const commands = {
|
|
|
6881
6923
|
...sourceLanguages.flatMap((source) => [
|
|
6882
6924
|
protocolEol(decisionProtocolBlock(PROTOCOL_VERSION, source), eol),
|
|
6883
6925
|
protocolEol(decisionProtocolBlock(PROTOCOL_VERSION, source, true), eol),
|
|
6884
|
-
protocolEol(
|
|
6885
|
-
protocolEol(
|
|
6926
|
+
protocolEol(decisionProtocolBlock('2.1', source), eol),
|
|
6927
|
+
protocolEol(decisionProtocolBlock('2.1', source, true), eol),
|
|
6928
|
+
protocolEol(decisionProtocolBlockAbsorbCollisions('2.1', source), eol),
|
|
6929
|
+
protocolEol(decisionProtocolBlockAbsorbCollisions('2.1', source, true), eol),
|
|
6886
6930
|
protocolEol(decisionProtocolBlock('2.0', source), eol),
|
|
6887
6931
|
protocolEol(decisionProtocolBlock('2.0', source, true), eol),
|
|
6888
6932
|
protocolEol(v1DecisionProtocolBlock(14, source), eol),
|