driftseal 0.3.0 → 0.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 +21 -2
- package/README.zh-CN.md +18 -2
- package/bin/driftseal-mcp.js +73 -3
- package/bin/driftseal.js +196 -13
- package/package.json +4 -4
- package/skills/use-driftseal/SKILL.md +39 -139
package/README.md
CHANGED
|
@@ -75,6 +75,7 @@ The v1 server provides:
|
|
|
75
75
|
| --- | --- |
|
|
76
76
|
| `driftseal_status`, `driftseal_log` | Read the current intent and intent history. |
|
|
77
77
|
| `driftseal_begin`, `driftseal_end` | Open and honestly close a work round. |
|
|
78
|
+
| `driftseal_reclaim`, `driftseal_unreclaim` | Hide meaningless closed records behind append-only markers, or restore them. |
|
|
78
79
|
| `driftseal_decision_list`, `driftseal_decision_show` | Find and read MADR records. |
|
|
79
80
|
| `driftseal_decision_add`, `driftseal_decision_update` | Add selective decisions and reconcile linked ones. |
|
|
80
81
|
| `driftseal://intent/current` | Read the current intent as a JSON resource. |
|
|
@@ -113,7 +114,9 @@ Single-step commands that only build, check, or record work already done — com
|
|
|
113
114
|
| `driftseal begin "<intent>" [-v "<verify>"] [--decision id] [--force]` | Open a work-round intent and optionally link existing decisions. |
|
|
114
115
|
| `driftseal end [id] [-s status] [-n note] [-r verify-result]` | Close an intent honestly. |
|
|
115
116
|
| `driftseal status` | Show the intent currently in progress. |
|
|
116
|
-
| `driftseal log [-n N]` | Review intent history. |
|
|
117
|
+
| `driftseal log [-n N] [--all]` | Review intent history (`--all` includes reclaimed records). |
|
|
118
|
+
| `driftseal reclaim [id ...] --reason "..." [--older-than days] [--force] [--dry-run]` | Hide meaningless closed records behind append-only markers. |
|
|
119
|
+
| `driftseal unreclaim <id> --reason "..."` | Restore a reclaimed record to the visible log. |
|
|
117
120
|
| `driftseal decision add "<title>" -c "..." -o "..."` | Write a numbered MADR decision. |
|
|
118
121
|
| `driftseal decision update <id> [-s status] -n "..."` | Reconcile a linked decision in the open intent. |
|
|
119
122
|
| `driftseal decision list [-s status] [--last N \| --count]` | List or count decision records, optionally filtered by status. |
|
|
@@ -127,6 +130,22 @@ close as `completed` or `partial`. The update changes the current status when
|
|
|
127
130
|
requested and appends a timestamped history entry tied to the intent. Intents
|
|
128
131
|
without decision links keep the ordinary workflow.
|
|
129
132
|
|
|
133
|
+
## Reclaiming noise records
|
|
134
|
+
|
|
135
|
+
Some closed records stop mattering: a harness or sandbox failure is recorded
|
|
136
|
+
honestly as `failed`, but it says nothing about the project. `driftseal
|
|
137
|
+
reclaim` retires such records without rewriting history — it appends a
|
|
138
|
+
`reclaim` marker (with a mandatory `--reason`) to the same append-only log,
|
|
139
|
+
and reclaimed records disappear from `driftseal log` and `driftseal status`
|
|
140
|
+
output while remaining in `events.jsonl` and visible with `log --all`.
|
|
141
|
+
`driftseal unreclaim <id> --reason "..."` restores a record that turned out to
|
|
142
|
+
matter.
|
|
143
|
+
|
|
144
|
+
Without ids, batch mode reclaims only closed `failed`/`abandoned` records that
|
|
145
|
+
are not linked to decisions and are older than `--older-than` days (default
|
|
146
|
+
7); use `--dry-run` to preview. `completed` and `partial` records, and any
|
|
147
|
+
decision-linked record, can only be reclaimed by explicit id with `--force`.
|
|
148
|
+
|
|
130
149
|
## Consistency and recovery
|
|
131
150
|
|
|
132
151
|
DriftSeal serializes mutating commands with locks on the configured intent and
|
|
@@ -157,7 +176,7 @@ index.
|
|
|
157
176
|
|
|
158
177
|
## Storage
|
|
159
178
|
|
|
160
|
-
- `.intent-log/events.jsonl` is the append-only intent log.
|
|
179
|
+
- `.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.
|
|
161
180
|
- `.decision-log/` contains numbered MADR decision records.
|
|
162
181
|
- Set `DRIFTSEAL_HOME` or `DRIFTSEAL_DECISION_HOME` to store either log outside the current project.
|
|
163
182
|
|
package/README.zh-CN.md
CHANGED
|
@@ -75,6 +75,7 @@ v1 server 提供:
|
|
|
75
75
|
| --- | --- |
|
|
76
76
|
| `driftseal_status`, `driftseal_log` | 读取当前 intent 和 intent 历史。 |
|
|
77
77
|
| `driftseal_begin`, `driftseal_end` | 开启并诚实关闭一轮工作。 |
|
|
78
|
+
| `driftseal_reclaim`, `driftseal_unreclaim` | 用 append-only 标记隐藏已无意义的已关闭记录,或将其恢复。 |
|
|
78
79
|
| `driftseal_decision_list`, `driftseal_decision_show` | 查找并读取 MADR record。 |
|
|
79
80
|
| `driftseal_decision_add`, `driftseal_decision_update` | 克制地增加 decision,并 reconcile 已关联的 decision。 |
|
|
80
81
|
| `driftseal://intent/current` | 以 JSON resource 读取当前 intent。 |
|
|
@@ -113,7 +114,9 @@ driftseal end \
|
|
|
113
114
|
| `driftseal begin "<intent>" [-v "<verify>"] [--decision id] [--force]` | 开启一轮工作,并可关联已有 decision。 |
|
|
114
115
|
| `driftseal end [id] [-s status] [-n note] [-r verify-result]` | 诚实地关闭 intent。 |
|
|
115
116
|
| `driftseal status` | 查看当前进行中的 intent。 |
|
|
116
|
-
| `driftseal log [-n N]` | 查看 intent
|
|
117
|
+
| `driftseal log [-n N] [--all]` | 查看 intent 历史(`--all` 包含已回收的记录)。 |
|
|
118
|
+
| `driftseal reclaim [id ...] --reason "..." [--older-than days] [--force] [--dry-run]` | 用 append-only 标记隐藏已无意义的已关闭记录。 |
|
|
119
|
+
| `driftseal unreclaim <id> --reason "..."` | 把已回收的记录恢复到可见历史中。 |
|
|
117
120
|
| `driftseal decision add "<title>" -c "..." -o "..."` | 写入编号化的 MADR decision。 |
|
|
118
121
|
| `driftseal decision update <id> [-s status] -n "..."` | 在当前 intent 中 reconcile 已关联的 decision。 |
|
|
119
122
|
| `driftseal decision list [-s status] [--last N \| --count]` | 列出或统计 decision records,也可按 status 筛选。 |
|
|
@@ -126,6 +129,19 @@ driftseal end \
|
|
|
126
129
|
每一条关联 decision。update 可以改变当前 status,并会追加一条包含时间和
|
|
127
130
|
intent ID 的 history。没有关联 decision 的 intent 仍沿用普通流程。
|
|
128
131
|
|
|
132
|
+
## 回收已无意义的记录
|
|
133
|
+
|
|
134
|
+
有些已关闭的记录会随着时间失去意义:harness 或 sandbox 导致的失败会被如实记录为
|
|
135
|
+
`failed`,但它与项目本身无关。`driftseal reclaim` 可以在不改写历史的前提下让这类
|
|
136
|
+
记录退场——它只是向同一个 append-only log 追加一条 `reclaim` 标记(必须附带
|
|
137
|
+
`--reason`),被回收的记录会从 `driftseal log` 和 `driftseal status` 的输出中隐藏,
|
|
138
|
+
但仍保留在 `events.jsonl` 中,并可通过 `log --all` 查看。若事后发现某条记录仍然
|
|
139
|
+
重要,用 `driftseal unreclaim <id> --reason "..."` 恢复。
|
|
140
|
+
|
|
141
|
+
不带 id 的批量模式只回收已关闭、未关联 decision、且早于 `--older-than` 天(默认 7
|
|
142
|
+
天)的 `failed`/`abandoned` 记录;可以先用 `--dry-run` 预览。`completed` 和 `partial`
|
|
143
|
+
记录,以及任何关联了 decision 的记录,只能按显式 id 加 `--force` 回收。
|
|
144
|
+
|
|
129
145
|
## 一致性与恢复
|
|
130
146
|
|
|
131
147
|
DriftSeal 会对配置后的 intent log 与 decision log 根目录加锁,并按固定顺序获取这些
|
|
@@ -152,7 +168,7 @@ recovery 只处理当前 intent,因此历史冲突不会阻塞之后的 decisi
|
|
|
152
168
|
|
|
153
169
|
## 数据保存在哪里
|
|
154
170
|
|
|
155
|
-
- `.intent-log/events.jsonl`:append-only intent log
|
|
171
|
+
- `.intent-log/events.jsonl`:append-only intent log。所有读写都必须经过 `driftseal`(CLI 或 MCP)——不要直接读取、修改、移动或删除该文件;需要让无意义的记录退场时使用 `driftseal reclaim`,而不是删除日志行。
|
|
156
172
|
- `.decision-log/`:编号化的 MADR decision records。
|
|
157
173
|
- 设置 `DRIFTSEAL_HOME` 或 `DRIFTSEAL_DECISION_HOME`,即可把对应 log 放到当前项目之外。
|
|
158
174
|
|
package/bin/driftseal-mcp.js
CHANGED
|
@@ -77,6 +77,9 @@ function registerTools(server, api, z) {
|
|
|
77
77
|
verifyResult: z.string().nullable(),
|
|
78
78
|
beganAt: z.string(),
|
|
79
79
|
endedAt: z.string().nullable(),
|
|
80
|
+
reclaimed: z.boolean(),
|
|
81
|
+
reclaimReason: z.string().nullable(),
|
|
82
|
+
reclaimedAt: z.string().nullable(),
|
|
80
83
|
});
|
|
81
84
|
const decisionRecord = z.object({
|
|
82
85
|
id: z.string(),
|
|
@@ -163,18 +166,85 @@ function registerTools(server, api, z) {
|
|
|
163
166
|
{
|
|
164
167
|
title: 'List DriftSeal intent history',
|
|
165
168
|
description:
|
|
166
|
-
'Review recent or complete DriftSeal intent history to re-anchor work and understand prior outcomes.',
|
|
167
|
-
inputSchema: {
|
|
169
|
+
'Review recent or complete DriftSeal intent history to re-anchor work and understand prior outcomes. Reclaimed records are hidden unless includeReclaimed is set.',
|
|
170
|
+
inputSchema: {
|
|
171
|
+
last: z.number().int().positive().max(100).optional(),
|
|
172
|
+
includeReclaimed: z.boolean().default(false),
|
|
173
|
+
},
|
|
168
174
|
outputSchema: { root: z.string(), intents: z.array(intentRecord) },
|
|
169
175
|
annotations: readOnly,
|
|
170
176
|
},
|
|
171
177
|
async (input) =>
|
|
172
178
|
guarded(() => {
|
|
173
|
-
const intents = api.log(input);
|
|
179
|
+
const intents = api.log({ last: input.last, all: input.includeReclaimed });
|
|
174
180
|
return success({ root: api.root, intents }, `Found ${intents.length} DriftSeal intent records.`);
|
|
175
181
|
})
|
|
176
182
|
);
|
|
177
183
|
|
|
184
|
+
server.registerTool(
|
|
185
|
+
'driftseal_reclaim',
|
|
186
|
+
{
|
|
187
|
+
title: 'Reclaim DriftSeal intent records',
|
|
188
|
+
description:
|
|
189
|
+
'Hide meaningless closed intent records (for example harness- or sandbox-caused failures) by appending reclaim markers. Never deletes log lines. Without ids, reclaims failed/abandoned, decision-unlinked records older than olderThan days.',
|
|
190
|
+
inputSchema: {
|
|
191
|
+
ids: z
|
|
192
|
+
.array(z.string())
|
|
193
|
+
.default([])
|
|
194
|
+
.describe('Intent IDs to reclaim; omit for batch mode by age.'),
|
|
195
|
+
reason: nonEmpty.describe('Why these records are meaningless (required, kept in the log).'),
|
|
196
|
+
olderThan: z
|
|
197
|
+
.number()
|
|
198
|
+
.int()
|
|
199
|
+
.positive()
|
|
200
|
+
.optional()
|
|
201
|
+
.describe('Batch mode retention window in days (default 7).'),
|
|
202
|
+
force: z
|
|
203
|
+
.boolean()
|
|
204
|
+
.default(false)
|
|
205
|
+
.describe('Allow reclaiming partial/completed or decision-linked records by explicit id.'),
|
|
206
|
+
dryRun: z.boolean().default(false),
|
|
207
|
+
},
|
|
208
|
+
outputSchema: { root: z.string(), intents: z.array(intentRecord) },
|
|
209
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
210
|
+
},
|
|
211
|
+
async (input) =>
|
|
212
|
+
guarded(() => {
|
|
213
|
+
const intents = api.reclaim({
|
|
214
|
+
ids: input.ids,
|
|
215
|
+
reason: input.reason,
|
|
216
|
+
olderThan: input.olderThan,
|
|
217
|
+
force: input.force,
|
|
218
|
+
dryRun: input.dryRun,
|
|
219
|
+
});
|
|
220
|
+
return success(
|
|
221
|
+
{ root: api.root, intents },
|
|
222
|
+
input.dryRun
|
|
223
|
+
? `${intents.length} DriftSeal intent records match.`
|
|
224
|
+
: `Reclaimed ${intents.length} DriftSeal intent records.`
|
|
225
|
+
);
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
server.registerTool(
|
|
230
|
+
'driftseal_unreclaim',
|
|
231
|
+
{
|
|
232
|
+
title: 'Restore a reclaimed DriftSeal intent record',
|
|
233
|
+
description: 'Restore one reclaimed intent record to the visible log by appending an unreclaim marker.',
|
|
234
|
+
inputSchema: {
|
|
235
|
+
id: z.string(),
|
|
236
|
+
reason: nonEmpty.describe('Why this record is being restored (required, kept in the log).'),
|
|
237
|
+
},
|
|
238
|
+
outputSchema: { root: z.string(), intent: intentRecord },
|
|
239
|
+
annotations: localWrite,
|
|
240
|
+
},
|
|
241
|
+
async (input) =>
|
|
242
|
+
guarded(() => {
|
|
243
|
+
const intent = api.unreclaim(input);
|
|
244
|
+
return success({ root: api.root, intent }, `Restored DriftSeal intent ${intent.id}.`);
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
|
|
178
248
|
server.registerTool(
|
|
179
249
|
'driftseal_decision_list',
|
|
180
250
|
{
|
package/bin/driftseal.js
CHANGED
|
@@ -34,8 +34,8 @@ const DECISION_STATUSES = [
|
|
|
34
34
|
'deprecated',
|
|
35
35
|
'superseded',
|
|
36
36
|
];
|
|
37
|
-
const EVENT_SCHEMA_VERSION =
|
|
38
|
-
const PROTOCOL_VERSION =
|
|
37
|
+
const EVENT_SCHEMA_VERSION = 3;
|
|
38
|
+
const PROTOCOL_VERSION = 7;
|
|
39
39
|
const LOCK_STALE_MS = 30 * 60 * 1000;
|
|
40
40
|
const LOCK_INIT_STALE_MS = 5 * 1000;
|
|
41
41
|
const MAX_DECISION_SLUG_LENGTH = 180;
|
|
@@ -130,6 +130,13 @@ function normalizeEvent(event, line) {
|
|
|
130
130
|
return event;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
if (event.type === 'reclaim' || event.type === 'unreclaim') {
|
|
134
|
+
if (typeof event.reason !== 'string' || event.reason.trim().length === 0) {
|
|
135
|
+
fail(`invalid ${event.type} event on log line ${line}`);
|
|
136
|
+
}
|
|
137
|
+
return event;
|
|
138
|
+
}
|
|
139
|
+
|
|
133
140
|
if (
|
|
134
141
|
event.type === 'decision_reconcile' ||
|
|
135
142
|
event.type === 'decision_reconcile_prepare' ||
|
|
@@ -566,8 +573,28 @@ function fold(events) {
|
|
|
566
573
|
tsEnd: null,
|
|
567
574
|
note: null,
|
|
568
575
|
verifyResult: null,
|
|
576
|
+
reclaimed: false,
|
|
577
|
+
reclaimReason: null,
|
|
578
|
+
reclaimedAt: null,
|
|
569
579
|
});
|
|
570
580
|
order.push(ev.id);
|
|
581
|
+
} else if (ev.type === 'reclaim' || ev.type === 'unreclaim') {
|
|
582
|
+
const rec = records.get(ev.id);
|
|
583
|
+
if (!rec) fail(`${ev.type} event references unknown intent id: ${ev.id}`);
|
|
584
|
+
if (ev.type === 'reclaim') {
|
|
585
|
+
if (rec.status === 'in_progress') {
|
|
586
|
+
fail(`cannot reclaim intent ${ev.id} while it is in_progress`);
|
|
587
|
+
}
|
|
588
|
+
if (rec.reclaimed) fail(`duplicate reclaim event for intent id: ${ev.id}`);
|
|
589
|
+
rec.reclaimed = true;
|
|
590
|
+
rec.reclaimReason = ev.reason;
|
|
591
|
+
rec.reclaimedAt = ev.ts;
|
|
592
|
+
} else {
|
|
593
|
+
if (!rec.reclaimed) fail(`unreclaim event for intent id that is not reclaimed: ${ev.id}`);
|
|
594
|
+
rec.reclaimed = false;
|
|
595
|
+
rec.reclaimReason = null;
|
|
596
|
+
rec.reclaimedAt = null;
|
|
597
|
+
}
|
|
571
598
|
} else if (ev.type === 'end') {
|
|
572
599
|
const rec = records.get(ev.id);
|
|
573
600
|
if (!rec) fail(`end event references unknown intent id: ${ev.id}`);
|
|
@@ -1038,6 +1065,7 @@ function render(rec) {
|
|
|
1038
1065
|
if (rec.verifyResult) lines.push(` verify-result: ${rec.verifyResult}`);
|
|
1039
1066
|
if (rec.note) lines.push(` note: ${rec.note}`);
|
|
1040
1067
|
lines.push(` began: ${rec.tsBegin}` + (rec.tsEnd ? ` ended: ${rec.tsEnd}` : ''));
|
|
1068
|
+
if (rec.reclaimed) lines.push(` reclaimed: ${rec.reclaimReason}`);
|
|
1041
1069
|
return lines.join('\n');
|
|
1042
1070
|
}
|
|
1043
1071
|
|
|
@@ -1053,6 +1081,9 @@ function publicIntent(rec) {
|
|
|
1053
1081
|
verifyResult: rec.verifyResult,
|
|
1054
1082
|
beganAt: rec.tsBegin,
|
|
1055
1083
|
endedAt: rec.tsEnd,
|
|
1084
|
+
reclaimed: rec.reclaimed,
|
|
1085
|
+
reclaimReason: rec.reclaimReason,
|
|
1086
|
+
reclaimedAt: rec.reclaimedAt,
|
|
1056
1087
|
};
|
|
1057
1088
|
}
|
|
1058
1089
|
|
|
@@ -1102,14 +1133,38 @@ This repo uses DriftSeal (\`driftseal\`) to prevent agent drift. Every work roun
|
|
|
1102
1133
|
just-closed log finalizes that round without requiring a new intent. Any content
|
|
1103
1134
|
change made while preparing the commit does require a new intent.
|
|
1104
1135
|
4. **Re-anchor after context loss**: run \`driftseal status\` and \`driftseal log --last 3\` before
|
|
1105
|
-
doing anything else. The open intent is the source of truth
|
|
1136
|
+
doing anything else. The open intent is the source of truth: resume it when its
|
|
1137
|
+
objective still matches the current task; otherwise close it (\`partial\` or
|
|
1138
|
+
\`abandoned\`, with a note) and \`begin\` a new one.
|
|
1139
|
+
|
|
1140
|
+
**Log access goes only through DriftSeal.** Never read, edit, move, or delete
|
|
1141
|
+
\`.intent-log/events.jsonl\` (or anything under \`$DRIFTSEAL_HOME\`) directly; use
|
|
1142
|
+
\`driftseal\` commands or the MCP tools. Retire meaningless closed records with
|
|
1143
|
+
\`driftseal reclaim [id ...] --reason "<why>"\` — it appends a marker, never
|
|
1144
|
+
deletes log lines; \`driftseal unreclaim <id> --reason "<why>"\` restores one.
|
|
1106
1145
|
|
|
1107
1146
|
Log: \`.intent-log/events.jsonl\` (override with \`$DRIFTSEAL_HOME\`); commit it with the code.
|
|
1108
1147
|
${INTENT_PROTOCOL_END}`;
|
|
1109
1148
|
}
|
|
1110
1149
|
|
|
1111
1150
|
function previousIntentProtocolBlock(version) {
|
|
1112
|
-
const
|
|
1151
|
+
const v6 = intentProtocolBlock(version).replace(
|
|
1152
|
+
'doing anything else. The open intent is the source of truth: resume it when its\n' +
|
|
1153
|
+
' objective still matches the current task; otherwise close it (`partial` or\n' +
|
|
1154
|
+
' `abandoned`, with a note) and `begin` a new one.',
|
|
1155
|
+
'doing anything else. The open intent is the source of truth.'
|
|
1156
|
+
);
|
|
1157
|
+
if (version >= 6) return v6;
|
|
1158
|
+
const v5 = v6.replace(
|
|
1159
|
+
'\n**Log access goes only through DriftSeal.** Never read, edit, move, or delete\n' +
|
|
1160
|
+
'`.intent-log/events.jsonl` (or anything under `$DRIFTSEAL_HOME`) directly; use\n' +
|
|
1161
|
+
'`driftseal` commands or the MCP tools. Retire meaningless closed records with\n' +
|
|
1162
|
+
'`driftseal reclaim [id ...] --reason "<why>"` — it appends a marker, never\n' +
|
|
1163
|
+
'deletes log lines; `driftseal unreclaim <id> --reason "<why>"` restores one.\n',
|
|
1164
|
+
''
|
|
1165
|
+
);
|
|
1166
|
+
if (version >= 5) return v5;
|
|
1167
|
+
const v4 = v5.replace(
|
|
1113
1168
|
'1. **Write intent first**, before modifying, creating, or deleting files, or\n' +
|
|
1114
1169
|
' making any other change that may need a rollback:\n' +
|
|
1115
1170
|
' `driftseal begin "<what this round will accomplish>" --verify "<command or check that proves it>"`.\n' +
|
|
@@ -1144,7 +1199,7 @@ revisiting, non-obvious rationale behind a long-lived or costly-to-reverse accep
|
|
|
1144
1199
|
choice, or a deprecated or superseded decision. Do not record routine, local,
|
|
1145
1200
|
readily reversible choices.
|
|
1146
1201
|
|
|
1147
|
-
\`driftseal decision add "<title>" --context "<problem and constraints>" --outcome "<decision and rationale>" --option "<considered option>" --consequence "<result>"\`
|
|
1202
|
+
\`driftseal decision add "<title>" --context "<problem and constraints>" --outcome "<decision and rationale>" --driver "<decision driver>" --option "<considered option>" --consequence "<result>"\`
|
|
1148
1203
|
|
|
1149
1204
|
Add one \`--driver\`, \`--option\`, or \`--consequence\` flag per item. Use
|
|
1150
1205
|
\`--status proposed|accepted|rejected|deferred|deprecated|superseded\` when needed.
|
|
@@ -1209,6 +1264,10 @@ When an intent declares an existing decision with \`--decision <id>\`, use
|
|
|
1209
1264
|
Commit \`.decision-log/\` with the code.`;
|
|
1210
1265
|
}
|
|
1211
1266
|
|
|
1267
|
+
function previousDecisionProtocolBlock(version) {
|
|
1268
|
+
return decisionProtocolBlock(version).replace(' --driver "<decision driver>"', '');
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1212
1271
|
function upgradeManagedBlock({
|
|
1213
1272
|
content,
|
|
1214
1273
|
marker,
|
|
@@ -1408,9 +1467,10 @@ const commands = {
|
|
|
1408
1467
|
},
|
|
1409
1468
|
|
|
1410
1469
|
log(argv) {
|
|
1411
|
-
const { positionals, flags } = parseArgs(argv, { last: '-n' });
|
|
1412
|
-
if (positionals.length > 0) fail('usage: driftseal log [--last N]');
|
|
1470
|
+
const { positionals, flags } = parseArgs(argv, { last: '-n', all: 'boolean' });
|
|
1471
|
+
if (positionals.length > 0) fail('usage: driftseal log [--last N] [--all]');
|
|
1413
1472
|
let records = fold(readEvents({ repairTail: true }));
|
|
1473
|
+
if (!flags.all) records = records.filter((record) => !record.reclaimed);
|
|
1414
1474
|
if (flags.last) {
|
|
1415
1475
|
const n = positiveInteger(flags.last, '--last');
|
|
1416
1476
|
records = records.slice(-n);
|
|
@@ -1423,6 +1483,109 @@ const commands = {
|
|
|
1423
1483
|
return records.map(publicIntent);
|
|
1424
1484
|
},
|
|
1425
1485
|
|
|
1486
|
+
reclaim(argv) {
|
|
1487
|
+
const { positionals, flags } = parseArgs(argv, {
|
|
1488
|
+
reason: '-r',
|
|
1489
|
+
'older-than': 'single',
|
|
1490
|
+
force: 'boolean',
|
|
1491
|
+
'dry-run': 'boolean',
|
|
1492
|
+
});
|
|
1493
|
+
const reason = flags.reason && flags.reason.trim();
|
|
1494
|
+
if (!reason) {
|
|
1495
|
+
fail(
|
|
1496
|
+
'usage: driftseal reclaim [id ...] --reason "<why>" [--older-than <days>] [--force] [--dry-run]'
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
let olderThanDays = 7;
|
|
1500
|
+
if (flags['older-than'] !== undefined) {
|
|
1501
|
+
olderThanDays = positiveInteger(flags['older-than'], '--older-than');
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const records = fold(readEvents({ repairTail: true }));
|
|
1505
|
+
let targets;
|
|
1506
|
+
if (positionals.length > 0) {
|
|
1507
|
+
const ids = [...new Set(positionals)];
|
|
1508
|
+
targets = ids.map((id) => {
|
|
1509
|
+
const record = records.find((candidate) => candidate.id === id);
|
|
1510
|
+
if (!record) fail(`unknown intent id: ${id}`);
|
|
1511
|
+
if (record.status === 'in_progress') {
|
|
1512
|
+
fail(`cannot reclaim intent ${id} while it is in_progress`);
|
|
1513
|
+
}
|
|
1514
|
+
if (record.reclaimed) fail(`intent ${id} is already reclaimed`);
|
|
1515
|
+
const routine = ['failed', 'abandoned'].includes(record.status) &&
|
|
1516
|
+
record.decisions.length === 0;
|
|
1517
|
+
if (!routine && !flags.force) {
|
|
1518
|
+
fail(
|
|
1519
|
+
`intent ${id} is ${record.status}` +
|
|
1520
|
+
(record.decisions.length > 0 ? ' and linked to decisions' : '') +
|
|
1521
|
+
'; re-run with --force to reclaim it anyway'
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
return record;
|
|
1525
|
+
});
|
|
1526
|
+
} else {
|
|
1527
|
+
if (flags.force) fail('--force requires explicit intent ids');
|
|
1528
|
+
const cutoff = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
|
|
1529
|
+
targets = records.filter(
|
|
1530
|
+
(record) =>
|
|
1531
|
+
record.status !== 'in_progress' &&
|
|
1532
|
+
['failed', 'abandoned'].includes(record.status) &&
|
|
1533
|
+
record.decisions.length === 0 &&
|
|
1534
|
+
!record.reclaimed &&
|
|
1535
|
+
Date.parse(record.tsEnd) < cutoff
|
|
1536
|
+
);
|
|
1537
|
+
if (targets.length === 0) {
|
|
1538
|
+
printLine('no reclaimable intents');
|
|
1539
|
+
return [];
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
if (flags['dry-run']) {
|
|
1544
|
+
printLine(targets.map((record) => `${record.id} ${record.status} — ${record.intent}`).join('\n'));
|
|
1545
|
+
return targets.map(publicIntent);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
let events = readEvents({ repairTail: true });
|
|
1549
|
+
for (const record of targets) {
|
|
1550
|
+
events.push(
|
|
1551
|
+
appendEvent({
|
|
1552
|
+
type: 'reclaim',
|
|
1553
|
+
id: record.id,
|
|
1554
|
+
ts: new Date().toISOString(),
|
|
1555
|
+
reason,
|
|
1556
|
+
})
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
const reclaimed = fold(events).filter((record) =>
|
|
1560
|
+
targets.some((target) => target.id === record.id)
|
|
1561
|
+
);
|
|
1562
|
+
printLine(targets.map((record) => `${record.id} reclaimed`).join('\n'));
|
|
1563
|
+
return reclaimed.map(publicIntent);
|
|
1564
|
+
},
|
|
1565
|
+
|
|
1566
|
+
unreclaim(argv) {
|
|
1567
|
+
const { positionals, flags } = parseArgs(argv, { reason: '-r' });
|
|
1568
|
+
const reason = flags.reason && flags.reason.trim();
|
|
1569
|
+
if (positionals.length !== 1 || !reason) {
|
|
1570
|
+
fail('usage: driftseal unreclaim <id> --reason "<why>"');
|
|
1571
|
+
}
|
|
1572
|
+
const events = readEvents({ repairTail: true });
|
|
1573
|
+
const record = fold(events).find((candidate) => candidate.id === positionals[0]);
|
|
1574
|
+
if (!record) fail(`unknown intent id: ${positionals[0]}`);
|
|
1575
|
+
if (!record.reclaimed) fail(`intent ${positionals[0]} is not reclaimed`);
|
|
1576
|
+
events.push(
|
|
1577
|
+
appendEvent({
|
|
1578
|
+
type: 'unreclaim',
|
|
1579
|
+
id: record.id,
|
|
1580
|
+
ts: new Date().toISOString(),
|
|
1581
|
+
reason,
|
|
1582
|
+
})
|
|
1583
|
+
);
|
|
1584
|
+
const restored = fold(events).find((candidate) => candidate.id === record.id);
|
|
1585
|
+
printLine(`${record.id} unreclaimed`);
|
|
1586
|
+
return publicIntent(restored);
|
|
1587
|
+
},
|
|
1588
|
+
|
|
1426
1589
|
decision(argv) {
|
|
1427
1590
|
const [subcommand, ...rest] = argv;
|
|
1428
1591
|
if (subcommand === 'add') {
|
|
@@ -1576,6 +1739,8 @@ const commands = {
|
|
|
1576
1739
|
protocolEol(previousIntentProtocolBlock(2), eol),
|
|
1577
1740
|
protocolEol(previousIntentProtocolBlock(3), eol),
|
|
1578
1741
|
protocolEol(previousIntentProtocolBlock(4), eol),
|
|
1742
|
+
protocolEol(previousIntentProtocolBlock(5), eol),
|
|
1743
|
+
protocolEol(previousIntentProtocolBlock(6), eol),
|
|
1579
1744
|
],
|
|
1580
1745
|
knownLegacyBlocks: [protocolEol(legacyIntentProtocolBlock(), eol)],
|
|
1581
1746
|
});
|
|
@@ -1587,9 +1752,11 @@ const commands = {
|
|
|
1587
1752
|
versionPattern: /^<!-- driftseal-decisions-version: (\d+) -->\r?$/m,
|
|
1588
1753
|
replacement: decisionBlock,
|
|
1589
1754
|
knownManagedBlocks: [
|
|
1590
|
-
protocolEol(
|
|
1591
|
-
protocolEol(
|
|
1592
|
-
protocolEol(
|
|
1755
|
+
protocolEol(previousDecisionProtocolBlock(2), eol),
|
|
1756
|
+
protocolEol(previousDecisionProtocolBlock(3), eol),
|
|
1757
|
+
protocolEol(previousDecisionProtocolBlock(4), eol),
|
|
1758
|
+
protocolEol(previousDecisionProtocolBlock(5), eol),
|
|
1759
|
+
protocolEol(previousDecisionProtocolBlock(6), eol),
|
|
1593
1760
|
],
|
|
1594
1761
|
knownLegacyBlocks: [protocolEol(legacyDecisionProtocolBlock(), eol)],
|
|
1595
1762
|
});
|
|
@@ -1627,7 +1794,11 @@ usage:
|
|
|
1627
1794
|
driftseal begin "<intent>" [--verify "<how to verify>"] [--decision <id>] [--force]
|
|
1628
1795
|
driftseal end [id] [--status completed|partial|failed|abandoned] [--note "..."] [--verify-result "..."]
|
|
1629
1796
|
driftseal status show the intent currently in progress (re-anchor after drift)
|
|
1630
|
-
driftseal log [--last N]
|
|
1797
|
+
driftseal log [--last N] [--all] show intent history (--all includes reclaimed records)
|
|
1798
|
+
driftseal reclaim [id ...] --reason "<why>" [--older-than <days>] [--force] [--dry-run]
|
|
1799
|
+
hide meaningless closed records without deleting them
|
|
1800
|
+
driftseal unreclaim <id> --reason "<why>"
|
|
1801
|
+
restore a reclaimed record to the visible log
|
|
1631
1802
|
driftseal decision add "<title>" --context "..." --outcome "..." [options]
|
|
1632
1803
|
driftseal decision update <id> [--status STATUS] --note "..."
|
|
1633
1804
|
reconcile a linked decision in the open intent
|
|
@@ -1659,6 +1830,7 @@ function requestedEndStatus(argv) {
|
|
|
1659
1830
|
|
|
1660
1831
|
function mutationResources(cmd, argv) {
|
|
1661
1832
|
if (cmd === 'init') return [process.cwd()];
|
|
1833
|
+
if (cmd === 'reclaim' || cmd === 'unreclaim') return [logDir()];
|
|
1662
1834
|
if (cmd === 'begin' && !argv.some((arg) => arg === '--decision' || arg.startsWith('--decision='))) {
|
|
1663
1835
|
return [logDir()];
|
|
1664
1836
|
}
|
|
@@ -1676,7 +1848,7 @@ function dispatch(argv) {
|
|
|
1676
1848
|
const fn = commands[cmd];
|
|
1677
1849
|
if (!fn) fail(`unknown command: ${cmd} (run: driftseal help)`);
|
|
1678
1850
|
const mutates =
|
|
1679
|
-
['begin', 'end', 'init'].includes(cmd) ||
|
|
1851
|
+
['begin', 'end', 'init', 'reclaim', 'unreclaim'].includes(cmd) ||
|
|
1680
1852
|
(cmd === 'decision' && ['add', 'update'].includes(rest[0]));
|
|
1681
1853
|
const readsIntentLog = ['status', 'log'].includes(cmd);
|
|
1682
1854
|
if (mutates || readsIntentLog) {
|
|
@@ -1768,11 +1940,22 @@ function createApi({ root = process.cwd(), isolateStorage = false } = {}) {
|
|
|
1768
1940
|
appendFlag(argv, '--verify-result', verifyResult);
|
|
1769
1941
|
return call(argv);
|
|
1770
1942
|
},
|
|
1771
|
-
log({ last } = {}) {
|
|
1943
|
+
log({ last, all = false } = {}) {
|
|
1772
1944
|
const argv = ['log'];
|
|
1773
1945
|
appendFlag(argv, '--last', last);
|
|
1946
|
+
if (all) argv.push('--all');
|
|
1947
|
+
return call(argv);
|
|
1948
|
+
},
|
|
1949
|
+
reclaim({ ids = [], reason, olderThan, force = false, dryRun = false }) {
|
|
1950
|
+
const argv = ['reclaim', ...ids.map(String), '--reason', reason];
|
|
1951
|
+
appendFlag(argv, '--older-than', olderThan);
|
|
1952
|
+
if (force) argv.push('--force');
|
|
1953
|
+
if (dryRun) argv.push('--dry-run');
|
|
1774
1954
|
return call(argv);
|
|
1775
1955
|
},
|
|
1956
|
+
unreclaim({ id, reason }) {
|
|
1957
|
+
return call(['unreclaim', String(id), '--reason', reason]);
|
|
1958
|
+
},
|
|
1776
1959
|
decisionAdd({ title, context, outcome, status, drivers = [], options = [], consequences = [] }) {
|
|
1777
1960
|
const argv = ['decision', 'add', title, '--context', context, '--outcome', outcome];
|
|
1778
1961
|
appendFlag(argv, '--status', status);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driftseal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Seal intent, verification, and decisions into an auditable workflow for agentic coding",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"driftseal",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"decision-log",
|
|
13
13
|
"madr"
|
|
14
14
|
],
|
|
15
|
-
"homepage": "https://github.com/
|
|
15
|
+
"homepage": "https://github.com/rowan-hiro/DriftSeal#readme",
|
|
16
16
|
"bugs": {
|
|
17
|
-
"url": "https://github.com/
|
|
17
|
+
"url": "https://github.com/rowan-hiro/DriftSeal/issues"
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/
|
|
21
|
+
"url": "git+https://github.com/rowan-hiro/DriftSeal.git"
|
|
22
22
|
},
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"author": "Ruan Boyu <boyuruan@gmail.com>",
|
|
@@ -5,162 +5,62 @@ description: Run repository work through the DriftSeal (`driftseal`) intent, ver
|
|
|
5
5
|
|
|
6
6
|
# Use DriftSeal
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
This skill is the usage guide for DriftSeal: how to find it and which command
|
|
9
|
+
or tool to reach for. The binding protocol — when an intent is required, how
|
|
10
|
+
to close one honestly, when a decision record is warranted, how reclamation
|
|
11
|
+
works — lives in the target repository's `AGENTS.md` (injected by
|
|
12
|
+
`driftseal init`). Follow that file; do not substitute this guide or memory
|
|
13
|
+
for it.
|
|
11
14
|
|
|
12
15
|
## Locate DriftSeal
|
|
13
16
|
|
|
14
17
|
- Prefer the `driftseal_*` MCP tools when the DriftSeal MCP server is available
|
|
15
|
-
for the target repository
|
|
16
|
-
`driftseal_end`,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
## Re-anchor Before Acting
|
|
33
|
-
|
|
34
|
-
1. Run `driftseal status` at the start of work.
|
|
35
|
-
2. Run `driftseal log --last 3` after compaction, a resumed session, or uncertainty.
|
|
36
|
-
3. Continue an open intent when it matches the requested work. Treat it as the
|
|
37
|
-
source of truth; do not open a duplicate intent.
|
|
38
|
-
4. Close a conflicting intent as `partial` or `abandoned` with an honest note,
|
|
39
|
-
then begin the replacement round.
|
|
40
|
-
|
|
41
|
-
Do not use `--force` merely for convenience. If another live actor owns the
|
|
42
|
-
open intent, stop mutating and coordinate instead of abandoning its work.
|
|
43
|
-
|
|
44
|
-
## Begin the Round
|
|
45
|
-
|
|
46
|
-
Before modifying, creating, or deleting files — or making any other change that
|
|
47
|
-
may need a rollback — declare one objective and its proof:
|
|
18
|
+
for the target repository: `driftseal_status`, `driftseal_begin`,
|
|
19
|
+
`driftseal_end`, `driftseal_log`, `driftseal_reclaim`, `driftseal_unreclaim`,
|
|
20
|
+
and the `driftseal_decision_*` tools. Their input schemas come from the MCP
|
|
21
|
+
client, not from `driftseal help`. The server keeps state in its fixed
|
|
22
|
+
repository root and ignores storage-override environment variables.
|
|
23
|
+
- Otherwise prefer `driftseal` from `PATH`, where `DRIFTSEAL_HOME` and
|
|
24
|
+
`DRIFTSEAL_DECISION_HOME` overrides apply. In a DriftSeal source checkout,
|
|
25
|
+
fall back to `node bin/driftseal.js`.
|
|
26
|
+
- Use one interface consistently within a round.
|
|
27
|
+
- If DriftSeal is unavailable, limit activity to read-only discovery and report
|
|
28
|
+
the blocker. Do not mutate the repository without the required log.
|
|
29
|
+
|
|
30
|
+
## Command Map
|
|
31
|
+
|
|
32
|
+
Re-anchor after context loss or uncertainty; when `status` reports an open
|
|
33
|
+
intent, the repository protocol defines whether to resume or replace it:
|
|
48
34
|
|
|
49
35
|
```sh
|
|
50
|
-
driftseal
|
|
51
|
-
|
|
36
|
+
driftseal status
|
|
37
|
+
driftseal log --last 3 # add --all to include reclaimed records
|
|
52
38
|
```
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
only build, check, or record work already done — compiling, running tests,
|
|
58
|
-
`git add`/`git commit` — need no intent of their own.
|
|
59
|
-
|
|
60
|
-
When the round may change or confirm an existing decision, declare each one at
|
|
61
|
-
the boundary with `--decision <id>`. Do not add decision links speculatively.
|
|
62
|
-
|
|
63
|
-
Read-only inspection needed to choose the objective or verifier may happen
|
|
64
|
-
before `begin`. Do not let that inspection turn into unlogged implementation.
|
|
65
|
-
|
|
66
|
-
## Execute Without Drift
|
|
67
|
-
|
|
68
|
-
- Change only what the open intent covers.
|
|
69
|
-
- Preserve unrelated worktree changes and other actors' edits.
|
|
70
|
-
- If the objective expands or changes, close the current intent as `partial`
|
|
71
|
-
or `abandoned`, then start a new round before continuing.
|
|
72
|
-
- If the declared verifier becomes invalid, record that honestly and start a
|
|
73
|
-
new round with the correct verifier instead of silently substituting proof.
|
|
74
|
-
- If the user replaces the active request, reconcile the open intent before
|
|
75
|
-
acting on the replacement.
|
|
76
|
-
|
|
77
|
-
## Record Decisions Selectively
|
|
78
|
-
|
|
79
|
-
Before adding a decision record, ask what useful information would disappear
|
|
80
|
-
if only the intent log and final Git commit remained.
|
|
81
|
-
|
|
82
|
-
Add a MADR record only when it preserves at least one of these:
|
|
83
|
-
|
|
84
|
-
- a rejected path worth preventing future agents from retrying;
|
|
85
|
-
- an unresolved or deliberately deferred path with a concrete revisit trigger;
|
|
86
|
-
- non-obvious rationale or trade-offs behind an accepted choice that is
|
|
87
|
-
long-lived, cross-cutting, or costly to reverse;
|
|
88
|
-
- the reason an earlier decision became deprecated or superseded.
|
|
89
|
-
|
|
90
|
-
Skip routine, local, readily reversible choices. Do not restate an accepted
|
|
91
|
-
change that the intent and commit already explain.
|
|
92
|
-
|
|
93
|
-
Use `proposed` for unresolved choices still under active consideration. Use
|
|
94
|
-
`deferred` for choices that are deliberately postponed, and state the revisit
|
|
95
|
-
trigger in the outcome or consequences. Use `rejected` for an explicitly
|
|
96
|
-
ruled-out choice. Reserve `accepted` for the exceptional accepted decisions
|
|
97
|
-
whose rationale would otherwise be lost.
|
|
98
|
-
|
|
99
|
-
Count postponed choices with `driftseal decision list --status deferred --count`,
|
|
100
|
-
then review them with `driftseal decision list --status deferred` so they do not
|
|
101
|
-
disappear into the chronological log.
|
|
102
|
-
|
|
103
|
-
For every decision explicitly linked by the open intent, reconcile its current
|
|
104
|
-
status and rationale before a successful close:
|
|
40
|
+
Run each work round as the repository's protocol directs (`-v`, `-s`, `-n`,
|
|
41
|
+
and `-r` are the short forms of `--verify`, `--status`, `--note`, and
|
|
42
|
+
`--verify-result`):
|
|
105
43
|
|
|
106
44
|
```sh
|
|
107
|
-
driftseal
|
|
108
|
-
|
|
109
|
-
|
|
45
|
+
driftseal begin "<objective>" --verify "<proof>" [--decision <id>]
|
|
46
|
+
# ... do only what the intent covers ...
|
|
47
|
+
driftseal end --status <status> --note "<what happened>" --verify-result "<proof output>"
|
|
110
48
|
```
|
|
111
49
|
|
|
112
|
-
|
|
113
|
-
unchanged decision still needs an explicit confirmation note. DriftSeal rejects a
|
|
114
|
-
`completed` or `partial` close if any declared decision remains unreconciled;
|
|
115
|
-
`failed` and `abandoned` remain available as escape paths.
|
|
116
|
-
|
|
117
|
-
Do not edit a linked decision after reconciling it. Run `decision update` again
|
|
118
|
-
so the final content hash is recorded. If an update is interrupted, rerun it or
|
|
119
|
-
successfully close the linked intent; DriftSeal recovers only that intent's pending
|
|
120
|
-
transaction. Closing as `failed` or `abandoned` cancels its pending recovery so
|
|
121
|
-
historical conflicts cannot block future decision work.
|
|
50
|
+
Record and reconcile decisions as the repository's decision protocol directs:
|
|
122
51
|
|
|
123
52
|
```sh
|
|
124
|
-
driftseal decision add "<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
--outcome "<current disposition, rationale, and revisit trigger>" \
|
|
128
|
-
--option "<considered option>" \
|
|
129
|
-
--consequence "<result of this disposition>"
|
|
53
|
+
driftseal decision add "<title>" --context "..." --outcome "..."
|
|
54
|
+
driftseal decision update <id> [--status <status>] --note "<what changed or was confirmed>"
|
|
55
|
+
driftseal decision list --status deferred
|
|
130
56
|
```
|
|
131
57
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
Run the declared verification exactly as written. Then close the intent before
|
|
135
|
-
reporting success:
|
|
58
|
+
Retire meaningless closed records as the repository's protocol directs:
|
|
136
59
|
|
|
137
60
|
```sh
|
|
138
|
-
driftseal
|
|
139
|
-
|
|
140
|
-
--note "<what actually happened>" \
|
|
141
|
-
--verify-result "<concise, honest result>"
|
|
61
|
+
driftseal reclaim [id ...] --reason "<why>" [--dry-run]
|
|
62
|
+
driftseal unreclaim <id> --reason "<why>"
|
|
142
63
|
```
|
|
143
64
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
- `completed`: achieve the objective and pass the declared verification.
|
|
147
|
-
- `partial`: leave useful work but do not achieve the whole objective.
|
|
148
|
-
- `failed`: fail to produce a usable result or fail essential verification.
|
|
149
|
-
- `abandoned`: intentionally stop or replace the round.
|
|
150
|
-
|
|
151
|
-
Never leave an intent open merely because the work failed. Never report a
|
|
152
|
-
completed result while the log still says `in_progress`.
|
|
153
|
-
|
|
154
|
-
## Persist the Round in Git
|
|
155
|
-
|
|
156
|
-
Treat a focused Git commit as the third record: the intent says what was
|
|
157
|
-
planned and how it was checked, the decision log preserves otherwise-lost
|
|
158
|
-
context, and the commit shows what actually landed.
|
|
159
|
-
|
|
160
|
-
When the user has authorized a commit, stage and commit only the verified
|
|
161
|
-
changes, the closed intent events, and any relevant decision record. This
|
|
162
|
-
bookkeeping finalizes the just-closed round and does not require a new intent.
|
|
163
|
-
|
|
164
|
-
Keep this exception narrow. Open a new intent before making content changes,
|
|
165
|
-
fixing a hook failure, rewriting history, rebasing, pushing, or including work
|
|
166
|
-
outside the closed round.
|
|
65
|
+
For exact flags, eligibility rules, and recovery behavior, run
|
|
66
|
+
`driftseal help` and read the repository's `AGENTS.md`.
|