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 +274 -340
- package/README.zh-CN.md +239 -293
- package/bin/driftseal-mcp.js +269 -62
- package/bin/driftseal.js +5420 -3062
- package/index.js +3 -0
- package/package.json +4 -3
- package/skills/use-driftseal/SKILL.md +22 -13
package/bin/driftseal-mcp.js
CHANGED
|
@@ -79,14 +79,26 @@ function registerTools(server, api, z) {
|
|
|
79
79
|
stdoutBytes: z.number().int().nonnegative(),
|
|
80
80
|
stderrBytes: z.number().int().nonnegative(),
|
|
81
81
|
workspace: z.string().regex(/^[a-f0-9]{64}$/).nullable(),
|
|
82
|
+
contractHash: z.string().regex(/^[a-f0-9]{64}$/).nullable(),
|
|
82
83
|
head: z.string().nullable(),
|
|
83
84
|
ranAt: z.string(),
|
|
84
85
|
});
|
|
85
|
-
const
|
|
86
|
+
const extensionRecord = z.object({
|
|
87
|
+
extension: z.string(),
|
|
88
|
+
acceptance: z.array(z.string()),
|
|
89
|
+
verify: z.string().nullable(),
|
|
90
|
+
decisions: z.array(z.string()),
|
|
91
|
+
extendedAt: z.string(),
|
|
92
|
+
head: z.string().nullable(),
|
|
93
|
+
});
|
|
94
|
+
const outcomeRecord = z.object({
|
|
86
95
|
id: z.string(),
|
|
87
|
-
|
|
96
|
+
outcome: z.string(),
|
|
97
|
+
lane: z.string().optional(),
|
|
98
|
+
extensions: z.array(extensionRecord),
|
|
88
99
|
acceptance: z.array(z.string()),
|
|
89
100
|
verify: z.string().nullable(),
|
|
101
|
+
contractHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
90
102
|
verification: verificationRecord.nullable(),
|
|
91
103
|
decisions: z.array(z.string()),
|
|
92
104
|
status: z.enum(END_STATUSES).or(z.literal('in_progress')),
|
|
@@ -99,6 +111,10 @@ function registerTools(server, api, z) {
|
|
|
99
111
|
reclaimed: z.boolean(),
|
|
100
112
|
reclaimReason: z.string().nullable(),
|
|
101
113
|
reclaimedAt: z.string().nullable(),
|
|
114
|
+
imported: z.object({
|
|
115
|
+
sourceIds: z.array(z.string()),
|
|
116
|
+
sourceFingerprint: z.string().regex(/^[a-f0-9]{64}$/),
|
|
117
|
+
}).nullable(),
|
|
102
118
|
readOnly: z.boolean().optional(),
|
|
103
119
|
});
|
|
104
120
|
const decisionRecord = z.object({
|
|
@@ -111,7 +127,7 @@ function registerTools(server, api, z) {
|
|
|
111
127
|
const absorbResult = z.object({
|
|
112
128
|
mappings: z.array(
|
|
113
129
|
z.object({
|
|
114
|
-
kind: z.enum(['
|
|
130
|
+
kind: z.enum(['outcome', 'decision']),
|
|
115
131
|
from: z.string(),
|
|
116
132
|
to: z.string(),
|
|
117
133
|
})
|
|
@@ -131,27 +147,27 @@ function registerTools(server, api, z) {
|
|
|
131
147
|
server.registerTool(
|
|
132
148
|
'driftseal_status',
|
|
133
149
|
{
|
|
134
|
-
title: 'Get current DriftSeal
|
|
150
|
+
title: 'Get current DriftSeal outcome',
|
|
135
151
|
description:
|
|
136
|
-
'Inspect the one
|
|
152
|
+
'Inspect the one outcome currently in progress before repository work or after context loss. Returns null when no outcome is open.',
|
|
137
153
|
inputSchema: {},
|
|
138
154
|
outputSchema: {
|
|
139
155
|
root: z.string(),
|
|
140
|
-
|
|
156
|
+
outcome: outcomeRecord.nullable(),
|
|
141
157
|
readOnly: z.boolean().optional(),
|
|
142
158
|
},
|
|
143
159
|
annotations: readOnly,
|
|
144
160
|
},
|
|
145
161
|
async () =>
|
|
146
162
|
guarded(() => {
|
|
147
|
-
const
|
|
163
|
+
const outcome = api.status();
|
|
148
164
|
const readOnly = api.readOnly;
|
|
149
|
-
const snapshot =
|
|
165
|
+
const snapshot = outcome && readOnly ? { ...outcome, readOnly: true } : outcome;
|
|
150
166
|
const summary = snapshot
|
|
151
|
-
? `
|
|
152
|
-
: 'No DriftSeal
|
|
167
|
+
? `Outcome ${snapshot.id} is ${snapshot.status}.`
|
|
168
|
+
: 'No DriftSeal outcome is in progress.';
|
|
153
169
|
return success(
|
|
154
|
-
{ root: api.root,
|
|
170
|
+
{ root: api.root, outcome: snapshot, ...(readOnly ? { readOnly: true } : {}) },
|
|
155
171
|
readOnly ? `${summary} ${READ_ONLY_SUFFIX}` : summary
|
|
156
172
|
);
|
|
157
173
|
})
|
|
@@ -160,11 +176,11 @@ function registerTools(server, api, z) {
|
|
|
160
176
|
server.registerTool(
|
|
161
177
|
'driftseal_begin',
|
|
162
178
|
{
|
|
163
|
-
title: 'Begin a DriftSeal
|
|
179
|
+
title: 'Begin a DriftSeal outcome',
|
|
164
180
|
description:
|
|
165
|
-
'Open one
|
|
181
|
+
'Open one coherent delivery outcome before making durable project changes. Fails if another outcome is already open.',
|
|
166
182
|
inputSchema: {
|
|
167
|
-
|
|
183
|
+
outcome: nonEmpty.describe('Coherent delivery outcome this work will accomplish.'),
|
|
168
184
|
acceptance: z
|
|
169
185
|
.array(nonEmpty)
|
|
170
186
|
.default([])
|
|
@@ -175,13 +191,35 @@ function registerTools(server, api, z) {
|
|
|
175
191
|
.default([])
|
|
176
192
|
.describe('Existing decision IDs this round may change or explicitly confirm.'),
|
|
177
193
|
},
|
|
178
|
-
outputSchema: { root: z.string(),
|
|
194
|
+
outputSchema: { root: z.string(), outcome: outcomeRecord },
|
|
195
|
+
annotations: localWrite,
|
|
196
|
+
},
|
|
197
|
+
async (input) =>
|
|
198
|
+
guarded(() => {
|
|
199
|
+
const outcome = api.begin(input);
|
|
200
|
+
return success({ root: api.root, outcome }, `Opened DriftSeal outcome ${outcome.id}.`);
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
server.registerTool(
|
|
205
|
+
'driftseal_extend',
|
|
206
|
+
{
|
|
207
|
+
title: 'Extend the current DriftSeal outcome',
|
|
208
|
+
description:
|
|
209
|
+
'Append another scoped step to the same coherent outcome. Added acceptance requires a replacement verifier for the cumulative contract, and every extension invalidates earlier verification.',
|
|
210
|
+
inputSchema: {
|
|
211
|
+
extension: nonEmpty,
|
|
212
|
+
acceptance: z.array(nonEmpty).default([]),
|
|
213
|
+
verify: nonEmpty.optional(),
|
|
214
|
+
decisions: z.array(decisionId).default([]),
|
|
215
|
+
},
|
|
216
|
+
outputSchema: { root: z.string(), outcome: outcomeRecord },
|
|
179
217
|
annotations: localWrite,
|
|
180
218
|
},
|
|
181
219
|
async (input) =>
|
|
182
220
|
guarded(() => {
|
|
183
|
-
const
|
|
184
|
-
return success({ root: api.root,
|
|
221
|
+
const outcome = api.extend(input);
|
|
222
|
+
return success({ root: api.root, outcome }, `Extended DriftSeal outcome ${outcome.id}.`);
|
|
185
223
|
})
|
|
186
224
|
);
|
|
187
225
|
|
|
@@ -190,18 +228,18 @@ function registerTools(server, api, z) {
|
|
|
190
228
|
{
|
|
191
229
|
title: 'Run the declared DriftSeal verification',
|
|
192
230
|
description:
|
|
193
|
-
'Execute the current acceptance-bound
|
|
231
|
+
'Execute the current acceptance-bound outcome\'s cumulative verifier and bind evidence to its contract hash and Git-visible workspace. Inspect it with driftseal_status first; provenance-less commands require allowTrackedCommand.',
|
|
194
232
|
inputSchema: {
|
|
195
233
|
allowTrackedCommand: z
|
|
196
234
|
.boolean()
|
|
197
235
|
.default(false)
|
|
198
236
|
.describe(
|
|
199
|
-
'Explicitly allow a command
|
|
237
|
+
'Explicitly allow a command without matching local outcome provenance after inspection.'
|
|
200
238
|
),
|
|
201
239
|
},
|
|
202
240
|
outputSchema: {
|
|
203
241
|
root: z.string(),
|
|
204
|
-
|
|
242
|
+
outcome: outcomeRecord,
|
|
205
243
|
verification: verificationRecord,
|
|
206
244
|
exitCode: z.number().int().nonnegative(),
|
|
207
245
|
},
|
|
@@ -212,7 +250,7 @@ function registerTools(server, api, z) {
|
|
|
212
250
|
const result = api.verify({ allowTrackedCommand: input.allowTrackedCommand });
|
|
213
251
|
return success(
|
|
214
252
|
{ root: api.root, ...result },
|
|
215
|
-
`Machine verification ${result.verification.passed ? 'passed' : 'failed'} for
|
|
253
|
+
`Machine verification ${result.verification.passed ? 'passed' : 'failed'} for outcome ${result.outcome.id}.`
|
|
216
254
|
);
|
|
217
255
|
})
|
|
218
256
|
);
|
|
@@ -220,60 +258,165 @@ function registerTools(server, api, z) {
|
|
|
220
258
|
server.registerTool(
|
|
221
259
|
'driftseal_end',
|
|
222
260
|
{
|
|
223
|
-
title: 'Close a DriftSeal
|
|
261
|
+
title: 'Close a DriftSeal outcome',
|
|
224
262
|
description:
|
|
225
|
-
'Close the current
|
|
263
|
+
'Close the current outcome honestly. Reconcile linked decisions before final verification; completed acceptance-bound outcomes require fresh contract- and workspace-bound evidence.',
|
|
226
264
|
inputSchema: {
|
|
227
|
-
id: z.string().optional().describe('
|
|
265
|
+
id: z.string().optional().describe('Outcome ID; omit to close the current open outcome.'),
|
|
228
266
|
status: closedStatus.default('completed'),
|
|
229
267
|
note: nonEmpty.optional().describe('What actually happened in the round.'),
|
|
230
268
|
verifyResult: nonEmpty.optional().describe('Concise, honest result of the declared verification.'),
|
|
231
269
|
},
|
|
232
|
-
outputSchema: { root: z.string(),
|
|
270
|
+
outputSchema: { root: z.string(), outcome: outcomeRecord },
|
|
233
271
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
234
272
|
},
|
|
235
273
|
async (input) =>
|
|
236
274
|
guarded(() => {
|
|
237
|
-
const
|
|
238
|
-
return success({ root: api.root,
|
|
275
|
+
const outcome = api.end(input);
|
|
276
|
+
return success({ root: api.root, outcome }, `Closed DriftSeal outcome ${outcome.id} as ${outcome.status}.`);
|
|
239
277
|
})
|
|
240
278
|
);
|
|
241
279
|
|
|
242
280
|
server.registerTool(
|
|
243
281
|
'driftseal_log',
|
|
244
282
|
{
|
|
245
|
-
title: 'List DriftSeal
|
|
283
|
+
title: 'List DriftSeal outcome history',
|
|
246
284
|
description:
|
|
247
|
-
'Review recent or complete DriftSeal
|
|
285
|
+
'Review recent or complete DriftSeal outcome history. Defaults to the current lane. Reclaimed records are hidden unless includeReclaimed is set.',
|
|
248
286
|
inputSchema: {
|
|
249
287
|
last: z.number().int().positive().max(100).optional(),
|
|
250
288
|
includeReclaimed: z.boolean().default(false),
|
|
289
|
+
allLanes: z
|
|
290
|
+
.boolean()
|
|
291
|
+
.default(false)
|
|
292
|
+
.describe('Show outcomes from every lane instead of the current lane.'),
|
|
251
293
|
},
|
|
252
294
|
outputSchema: {
|
|
253
295
|
root: z.string(),
|
|
254
|
-
|
|
296
|
+
outcomes: z.array(outcomeRecord),
|
|
255
297
|
readOnly: z.boolean().optional(),
|
|
256
298
|
},
|
|
257
299
|
annotations: readOnly,
|
|
258
300
|
},
|
|
259
301
|
async (input) =>
|
|
260
302
|
guarded(() => {
|
|
261
|
-
const
|
|
303
|
+
const outcomes = api.log({
|
|
304
|
+
last: input.last,
|
|
305
|
+
all: input.includeReclaimed,
|
|
306
|
+
allLanes: input.allLanes,
|
|
307
|
+
});
|
|
262
308
|
const readOnly = api.readOnly;
|
|
263
|
-
const summary = `Found ${
|
|
309
|
+
const summary = `Found ${outcomes.length} DriftSeal outcome records.`;
|
|
264
310
|
return success(
|
|
265
|
-
{ root: api.root,
|
|
311
|
+
{ root: api.root, outcomes, ...(readOnly ? { readOnly: true } : {}) },
|
|
266
312
|
readOnly ? `${summary} ${READ_ONLY_SUFFIX}` : summary
|
|
267
313
|
);
|
|
268
314
|
})
|
|
269
315
|
);
|
|
270
316
|
|
|
317
|
+
const laneRecord = z.object({
|
|
318
|
+
name: z.string(),
|
|
319
|
+
description: z.string().nullable(),
|
|
320
|
+
addedAt: z.string().nullable(),
|
|
321
|
+
inferred: z.boolean().optional(),
|
|
322
|
+
visible: z.number().int().nonnegative(),
|
|
323
|
+
count: z.number().int().nonnegative(),
|
|
324
|
+
current: z.boolean().optional(),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
server.registerTool(
|
|
328
|
+
'driftseal_lane',
|
|
329
|
+
{
|
|
330
|
+
title: 'Show DriftSeal lanes',
|
|
331
|
+
description:
|
|
332
|
+
'List named outcome lanes and the current lane. Re-anchoring and log history follow the current lane.',
|
|
333
|
+
inputSchema: {},
|
|
334
|
+
outputSchema: {
|
|
335
|
+
root: z.string(),
|
|
336
|
+
current: z.string(),
|
|
337
|
+
missingCurrentLane: z.string().nullable().optional(),
|
|
338
|
+
lanes: z.array(laneRecord),
|
|
339
|
+
total: z.number().int().nonnegative(),
|
|
340
|
+
readOnly: z.boolean().optional(),
|
|
341
|
+
},
|
|
342
|
+
annotations: readOnly,
|
|
343
|
+
},
|
|
344
|
+
async () =>
|
|
345
|
+
guarded(() => {
|
|
346
|
+
const snapshot = api.lane();
|
|
347
|
+
const readOnly = api.readOnly;
|
|
348
|
+
return success(
|
|
349
|
+
{ root: api.root, ...snapshot, ...(readOnly ? { readOnly: true } : {}) },
|
|
350
|
+
`Current DriftSeal lane is ${snapshot.current}.`
|
|
351
|
+
);
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
server.registerTool(
|
|
356
|
+
'driftseal_lane_add',
|
|
357
|
+
{
|
|
358
|
+
title: 'Add a DriftSeal lane',
|
|
359
|
+
description:
|
|
360
|
+
'Create a named lane for a long-lived capability. The default lane main always exists.',
|
|
361
|
+
inputSchema: {
|
|
362
|
+
name: nonEmpty.describe('Lane name: a lowercase letter, then letters, digits, or hyphens.'),
|
|
363
|
+
description: nonEmpty.optional(),
|
|
364
|
+
},
|
|
365
|
+
outputSchema: { root: z.string(), name: z.string(), description: z.string().nullable() },
|
|
366
|
+
annotations: localWrite,
|
|
367
|
+
},
|
|
368
|
+
async (input) =>
|
|
369
|
+
guarded(() => {
|
|
370
|
+
const lane = api.laneAdd({ name: input.name, description: input.description });
|
|
371
|
+
return success({ root: api.root, ...lane }, `Added DriftSeal lane ${lane.name}.`);
|
|
372
|
+
})
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
server.registerTool(
|
|
376
|
+
'driftseal_lane_switch',
|
|
377
|
+
{
|
|
378
|
+
title: 'Switch the current DriftSeal lane',
|
|
379
|
+
description:
|
|
380
|
+
'Move this worktree onto an existing lane. Refused while an outcome is open.',
|
|
381
|
+
inputSchema: { name: nonEmpty },
|
|
382
|
+
outputSchema: { root: z.string(), current: z.string() },
|
|
383
|
+
annotations: localWrite,
|
|
384
|
+
},
|
|
385
|
+
async (input) =>
|
|
386
|
+
guarded(() => {
|
|
387
|
+
const result = api.laneSwitch({ name: input.name });
|
|
388
|
+
return success({ root: api.root, ...result }, `Switched to DriftSeal lane ${result.current}.`);
|
|
389
|
+
})
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
server.registerTool(
|
|
393
|
+
'driftseal_lane_assign',
|
|
394
|
+
{
|
|
395
|
+
title: 'Assign a closed outcome to a lane',
|
|
396
|
+
description: 'Move a closed outcome onto an existing lane with an append-only assign event.',
|
|
397
|
+
inputSchema: {
|
|
398
|
+
id: nonEmpty.describe('Closed outcome id.'),
|
|
399
|
+
lane: nonEmpty,
|
|
400
|
+
},
|
|
401
|
+
outputSchema: { root: z.string(), outcome: outcomeRecord },
|
|
402
|
+
annotations: localWrite,
|
|
403
|
+
},
|
|
404
|
+
async (input) =>
|
|
405
|
+
guarded(() => {
|
|
406
|
+
const outcome = api.laneAssign({ id: input.id, lane: input.lane });
|
|
407
|
+
return success(
|
|
408
|
+
{ root: api.root, outcome },
|
|
409
|
+
`Assigned outcome ${outcome.id} to lane ${outcome.lane}.`
|
|
410
|
+
);
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
|
|
271
414
|
server.registerTool(
|
|
272
415
|
'driftseal_absorb',
|
|
273
416
|
{
|
|
274
417
|
title: 'Absorb another DriftSeal lineage',
|
|
275
418
|
description:
|
|
276
|
-
'Repair the fixed repository after a merge collision or absorb another worktree\'s
|
|
419
|
+
'Repair the fixed repository after a merge collision or absorb another worktree\'s outcome and MADR logs, remapping colliding IDs.',
|
|
277
420
|
inputSchema: {
|
|
278
421
|
otherLog: z
|
|
279
422
|
.string()
|
|
@@ -286,7 +429,7 @@ function registerTools(server, api, z) {
|
|
|
286
429
|
abandon: z
|
|
287
430
|
.enum(['ours', 'theirs'])
|
|
288
431
|
.optional()
|
|
289
|
-
.describe('Side whose open
|
|
432
|
+
.describe('Side whose open outcome to abandon when both lineages have one in progress.'),
|
|
290
433
|
dryRun: z.boolean().default(false),
|
|
291
434
|
},
|
|
292
435
|
outputSchema: { root: z.string(), result: absorbResult },
|
|
@@ -306,14 +449,14 @@ function registerTools(server, api, z) {
|
|
|
306
449
|
server.registerTool(
|
|
307
450
|
'driftseal_reclaim',
|
|
308
451
|
{
|
|
309
|
-
title: 'Reclaim DriftSeal
|
|
452
|
+
title: 'Reclaim DriftSeal outcome records',
|
|
310
453
|
description:
|
|
311
|
-
'Hide meaningless closed
|
|
454
|
+
'Hide meaningless closed outcome records behind append-only markers. Never deletes log lines.',
|
|
312
455
|
inputSchema: {
|
|
313
456
|
ids: z
|
|
314
457
|
.array(z.string())
|
|
315
458
|
.default([])
|
|
316
|
-
.describe('
|
|
459
|
+
.describe('Outcome IDs to reclaim; omit for batch mode by age.'),
|
|
317
460
|
reason: nonEmpty.describe('Why these records are meaningless (required, kept in the log).'),
|
|
318
461
|
olderThan: z
|
|
319
462
|
.number()
|
|
@@ -327,12 +470,12 @@ function registerTools(server, api, z) {
|
|
|
327
470
|
.describe('Allow reclaiming partial/completed or decision-linked records by explicit id.'),
|
|
328
471
|
dryRun: z.boolean().default(false),
|
|
329
472
|
},
|
|
330
|
-
outputSchema: { root: z.string(),
|
|
473
|
+
outputSchema: { root: z.string(), outcomes: z.array(outcomeRecord) },
|
|
331
474
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
332
475
|
},
|
|
333
476
|
async (input) =>
|
|
334
477
|
guarded(() => {
|
|
335
|
-
const
|
|
478
|
+
const outcomes = api.reclaim({
|
|
336
479
|
ids: input.ids,
|
|
337
480
|
reason: input.reason,
|
|
338
481
|
olderThan: input.olderThan,
|
|
@@ -340,10 +483,10 @@ function registerTools(server, api, z) {
|
|
|
340
483
|
dryRun: input.dryRun,
|
|
341
484
|
});
|
|
342
485
|
return success(
|
|
343
|
-
{ root: api.root,
|
|
486
|
+
{ root: api.root, outcomes },
|
|
344
487
|
input.dryRun
|
|
345
|
-
? `${
|
|
346
|
-
: `Reclaimed ${
|
|
488
|
+
? `${outcomes.length} DriftSeal outcome records match.`
|
|
489
|
+
: `Reclaimed ${outcomes.length} DriftSeal outcome records.`
|
|
347
490
|
);
|
|
348
491
|
})
|
|
349
492
|
);
|
|
@@ -351,19 +494,19 @@ function registerTools(server, api, z) {
|
|
|
351
494
|
server.registerTool(
|
|
352
495
|
'driftseal_unreclaim',
|
|
353
496
|
{
|
|
354
|
-
title: 'Restore a reclaimed DriftSeal
|
|
355
|
-
description: 'Restore one reclaimed
|
|
497
|
+
title: 'Restore a reclaimed DriftSeal outcome record',
|
|
498
|
+
description: 'Restore one reclaimed outcome record to the visible log by appending an unreclaim marker.',
|
|
356
499
|
inputSchema: {
|
|
357
500
|
id: z.string(),
|
|
358
501
|
reason: nonEmpty.describe('Why this record is being restored (required, kept in the log).'),
|
|
359
502
|
},
|
|
360
|
-
outputSchema: { root: z.string(),
|
|
503
|
+
outputSchema: { root: z.string(), outcome: outcomeRecord },
|
|
361
504
|
annotations: localWrite,
|
|
362
505
|
},
|
|
363
506
|
async (input) =>
|
|
364
507
|
guarded(() => {
|
|
365
|
-
const
|
|
366
|
-
return success({ root: api.root,
|
|
508
|
+
const outcome = api.unreclaim(input);
|
|
509
|
+
return success({ root: api.root, outcome }, `Restored DriftSeal outcome ${outcome.id}.`);
|
|
367
510
|
})
|
|
368
511
|
);
|
|
369
512
|
|
|
@@ -408,7 +551,7 @@ function registerTools(server, api, z) {
|
|
|
408
551
|
{
|
|
409
552
|
title: 'Add a DriftSeal decision',
|
|
410
553
|
description:
|
|
411
|
-
'Create a MADR record only for durable rationale, rejected paths, deferred choices, or costly-to-reverse decisions that Git and the
|
|
554
|
+
'Create a MADR record only for durable rationale, rejected paths, deferred choices, or costly-to-reverse decisions that Git and the outcome log cannot recover.',
|
|
412
555
|
inputSchema: {
|
|
413
556
|
title: nonEmpty,
|
|
414
557
|
context: nonEmpty,
|
|
@@ -433,7 +576,7 @@ function registerTools(server, api, z) {
|
|
|
433
576
|
{
|
|
434
577
|
title: 'Reconcile a DriftSeal decision',
|
|
435
578
|
description:
|
|
436
|
-
'Reconcile one decision linked to the current open
|
|
579
|
+
'Reconcile one decision linked to the current open outcome, updating or explicitly confirming its status with a history note.',
|
|
437
580
|
inputSchema: {
|
|
438
581
|
id: decisionId,
|
|
439
582
|
status: decisionStatus.optional(),
|
|
@@ -448,6 +591,63 @@ function registerTools(server, api, z) {
|
|
|
448
591
|
return success({ root: api.root, decision }, `Reconciled DriftSeal decision ${decision.id}.`);
|
|
449
592
|
})
|
|
450
593
|
);
|
|
594
|
+
|
|
595
|
+
const migrationPlan = z.object({
|
|
596
|
+
format: z.literal('driftseal-v1-to-v2-plan'),
|
|
597
|
+
sourceFingerprint: z.string().regex(/^[a-f0-9]{64}$/),
|
|
598
|
+
groups: z.array(z.object({
|
|
599
|
+
outcome: nonEmpty,
|
|
600
|
+
summary: nonEmpty,
|
|
601
|
+
sourceIds: z.array(z.string()).min(1),
|
|
602
|
+
})),
|
|
603
|
+
excluded: z.array(z.object({ sourceId: z.string(), reason: nonEmpty })).default([]),
|
|
604
|
+
});
|
|
605
|
+
const migrationSources = {
|
|
606
|
+
sourceLog: nonEmpty.optional().describe('Path to the v1 events.jsonl source.'),
|
|
607
|
+
sourceDecisions: nonEmpty.optional().describe('Path to the v1 MADR directory.'),
|
|
608
|
+
};
|
|
609
|
+
server.registerTool(
|
|
610
|
+
'driftseal_migration_inspect',
|
|
611
|
+
{
|
|
612
|
+
title: 'Inspect a DriftSeal v1 repository for v2 migration',
|
|
613
|
+
description: 'Read and normalize custom or repository-default v1 logs for model-assisted grouping. The v2 destination is the fixed repository .seal root. Makes no changes.',
|
|
614
|
+
inputSchema: migrationSources,
|
|
615
|
+
outputSchema: { root: z.string(), inspection: z.unknown() },
|
|
616
|
+
annotations: readOnly,
|
|
617
|
+
},
|
|
618
|
+
async (locations) => guarded(() => {
|
|
619
|
+
const inspection = api.migrationInspect(locations);
|
|
620
|
+
return success({ root: api.root, inspection }, `Found ${inspection.records.length} closed v1 intent records.`);
|
|
621
|
+
})
|
|
622
|
+
);
|
|
623
|
+
server.registerTool(
|
|
624
|
+
'driftseal_migration_apply',
|
|
625
|
+
{
|
|
626
|
+
title: 'Stage a validated DriftSeal v1-to-v2 migration',
|
|
627
|
+
description: 'Validate a model-generated ordered grouping plan, create .seal side-by-side, copy MADRs byte-for-byte, and never delete v1 data.',
|
|
628
|
+
inputSchema: { plan: migrationPlan, ...migrationSources },
|
|
629
|
+
outputSchema: { root: z.string(), result: z.unknown() },
|
|
630
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
631
|
+
},
|
|
632
|
+
async ({ plan, ...locations }) => guarded(() => {
|
|
633
|
+
const result = api.migrationApply({ plan, ...locations });
|
|
634
|
+
return success({ root: api.root, result }, 'Staged DriftSeal v2 without deleting v1 data.');
|
|
635
|
+
})
|
|
636
|
+
);
|
|
637
|
+
server.registerTool(
|
|
638
|
+
'driftseal_migration_check',
|
|
639
|
+
{
|
|
640
|
+
title: 'Check a staged DriftSeal v1-to-v2 migration',
|
|
641
|
+
description: 'Validate the staged outcome log and manifest-backed MADRs, then report whether v1 still awaits manual removal.',
|
|
642
|
+
inputSchema: migrationSources,
|
|
643
|
+
outputSchema: { root: z.string(), result: z.unknown() },
|
|
644
|
+
annotations: readOnly,
|
|
645
|
+
},
|
|
646
|
+
async (locations) => guarded(() => {
|
|
647
|
+
const result = api.migrationCheck(locations);
|
|
648
|
+
return success({ root: api.root, result }, result.complete ? 'Migration complete.' : 'Migration valid; v1 remains for user review.');
|
|
649
|
+
})
|
|
650
|
+
);
|
|
451
651
|
}
|
|
452
652
|
|
|
453
653
|
function registerResources(server, api) {
|
|
@@ -464,22 +664,29 @@ function registerResources(server, api) {
|
|
|
464
664
|
};
|
|
465
665
|
|
|
466
666
|
registerJson(
|
|
467
|
-
'current-
|
|
468
|
-
'driftseal://
|
|
469
|
-
'Current DriftSeal
|
|
470
|
-
'The
|
|
471
|
-
() => ({ root: api.root,
|
|
667
|
+
'current-outcome',
|
|
668
|
+
'driftseal://outcome/current',
|
|
669
|
+
'Current DriftSeal outcome',
|
|
670
|
+
'The delivery outcome currently in progress for the fixed repository.',
|
|
671
|
+
() => ({ root: api.root, outcome: api.status() })
|
|
672
|
+
);
|
|
673
|
+
registerJson(
|
|
674
|
+
'recent-outcomes',
|
|
675
|
+
'driftseal://outcomes/recent',
|
|
676
|
+
'Recent DriftSeal outcomes',
|
|
677
|
+
'The ten most recent outcome records on the current lane.',
|
|
678
|
+
() => ({ root: api.root, outcomes: api.log({ last: 10 }) })
|
|
472
679
|
);
|
|
473
680
|
registerJson(
|
|
474
|
-
'
|
|
475
|
-
'driftseal://
|
|
476
|
-
'
|
|
477
|
-
'
|
|
478
|
-
() => ({ root: api.root,
|
|
681
|
+
'outcome-lanes',
|
|
682
|
+
'driftseal://lanes',
|
|
683
|
+
'DriftSeal outcome lanes',
|
|
684
|
+
'Named lanes that partition outcome history, including the current lane.',
|
|
685
|
+
() => ({ root: api.root, ...api.lane() })
|
|
479
686
|
);
|
|
480
687
|
registerJson(
|
|
481
688
|
'decision-catalog',
|
|
482
|
-
'driftseal://
|
|
689
|
+
'driftseal://madr',
|
|
483
690
|
'DriftSeal decision catalog',
|
|
484
691
|
'All MADR decision summaries for the fixed repository.',
|
|
485
692
|
() => ({ root: api.root, decisions: api.decisionList() })
|
|
@@ -498,7 +705,7 @@ async function createServer({ root }) {
|
|
|
498
705
|
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
499
706
|
{
|
|
500
707
|
instructions:
|
|
501
|
-
'Use driftseal_status before
|
|
708
|
+
'Use driftseal_status before durable project changes or after context loss. Open one coherent outcome with driftseal_begin and append same-outcome steps with driftseal_extend. Reconcile linked decisions after the final extension, inspect the cumulative verifier, use driftseal_verify for fresh contract-bound evidence, and close honestly with driftseal_end. Named lanes isolate orthogonal capability history; driftseal_log follows the current lane.',
|
|
502
709
|
}
|
|
503
710
|
);
|
|
504
711
|
registerTools(server, api, z);
|