driftseal 1.1.2 → 1.1.3
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 +7 -0
- package/README.zh-CN.md +5 -0
- package/bin/driftseal-mcp.js +48 -0
- package/bin/driftseal.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,6 +132,7 @@ The v1 server provides:
|
|
|
132
132
|
| --- | --- |
|
|
133
133
|
| `driftseal_status`, `driftseal_log` | Read the current intent and intent history. |
|
|
134
134
|
| `driftseal_begin`, `driftseal_end` | Open and honestly close a work round. |
|
|
135
|
+
| `driftseal_absorb` | Repair merge collisions or absorb another worktree's logs while remapping colliding IDs. |
|
|
135
136
|
| `driftseal_reclaim`, `driftseal_unreclaim` | Hide meaningless closed records behind append-only markers, or restore them. |
|
|
136
137
|
| `driftseal_decision_list`, `driftseal_decision_show` | Find and read MADR records. |
|
|
137
138
|
| `driftseal_decision_add`, `driftseal_decision_update` | Add selective decisions and reconcile linked ones. |
|
|
@@ -139,6 +140,12 @@ The v1 server provides:
|
|
|
139
140
|
| `driftseal://intents/recent` | Read the ten most recent intents as a JSON resource. |
|
|
140
141
|
| `driftseal://decisions` | Read the decision catalog as a JSON resource. |
|
|
141
142
|
|
|
143
|
+
`driftseal_absorb` accepts optional incoming intent-log and decision-directory
|
|
144
|
+
paths, an `ours` or `theirs` abandon strategy, and a dry-run mode. Incoming
|
|
145
|
+
paths are read-only sources; all repaired output stays under the repository
|
|
146
|
+
fixed at server startup. The Git merge-driver form remains a CLI-only plumbing
|
|
147
|
+
command.
|
|
148
|
+
|
|
142
149
|
MCP changes only the execution surface. It does not add policy beyond the
|
|
143
150
|
repository's `AGENTS.md`, and the companion skill remains limited to discovery
|
|
144
151
|
and recovery guidance.
|
package/README.zh-CN.md
CHANGED
|
@@ -128,6 +128,7 @@ v1 server 提供:
|
|
|
128
128
|
| --- | --- |
|
|
129
129
|
| `driftseal_status`, `driftseal_log` | 读取当前 intent 和 intent 历史。 |
|
|
130
130
|
| `driftseal_begin`, `driftseal_end` | 开启并诚实关闭一轮工作。 |
|
|
131
|
+
| `driftseal_absorb` | 修复 merge 撞号,或吸收另一条 worktree 日志并重编号冲突 ID。 |
|
|
131
132
|
| `driftseal_reclaim`, `driftseal_unreclaim` | 用 append-only 标记隐藏已无意义的已关闭记录,或将其恢复。 |
|
|
132
133
|
| `driftseal_decision_list`, `driftseal_decision_show` | 查找并读取 MADR record。 |
|
|
133
134
|
| `driftseal_decision_add`, `driftseal_decision_update` | 克制地增加 decision,并 reconcile 已关联的 decision。 |
|
|
@@ -135,6 +136,10 @@ v1 server 提供:
|
|
|
135
136
|
| `driftseal://intents/recent` | 以 JSON resource 读取最近十条 intent。 |
|
|
136
137
|
| `driftseal://decisions` | 以 JSON resource 读取 decision catalog。 |
|
|
137
138
|
|
|
139
|
+
`driftseal_absorb` 可以接收另一份 intent log、decision 目录、`ours` 或 `theirs`
|
|
140
|
+
放弃策略,以及 dry-run 模式。传入的路径只作为只读来源;修复后的内容仍只会写入
|
|
141
|
+
server 启动时固定的 repository。Git merge driver 形式仍是 CLI 专用的底层命令。
|
|
142
|
+
|
|
138
143
|
MCP 只替换执行入口,不会在 repository 的 `AGENTS.md` 之外增加 policy;
|
|
139
144
|
配套 skill 也仍只负责发现与恢复工作流。
|
|
140
145
|
|
package/bin/driftseal-mcp.js
CHANGED
|
@@ -88,6 +88,19 @@ function registerTools(server, api, z) {
|
|
|
88
88
|
file: z.string(),
|
|
89
89
|
});
|
|
90
90
|
const decisionWithContent = decisionRecord.extend({ content: z.string() });
|
|
91
|
+
const absorbResult = z.object({
|
|
92
|
+
mappings: z.array(
|
|
93
|
+
z.object({
|
|
94
|
+
kind: z.enum(['intent', 'decision']),
|
|
95
|
+
from: z.string(),
|
|
96
|
+
to: z.string(),
|
|
97
|
+
})
|
|
98
|
+
),
|
|
99
|
+
abandoned: z.string().nullable(),
|
|
100
|
+
copies: z.array(z.string()),
|
|
101
|
+
outputFile: z.string(),
|
|
102
|
+
exitCode: z.number().int(),
|
|
103
|
+
});
|
|
91
104
|
const closedStatus = z.enum(END_STATUSES);
|
|
92
105
|
const decisionStatus = z.enum(DECISION_STATUSES);
|
|
93
106
|
const decisionId = z.string().regex(/^\d+$/, 'decision id must contain only digits');
|
|
@@ -181,6 +194,41 @@ function registerTools(server, api, z) {
|
|
|
181
194
|
})
|
|
182
195
|
);
|
|
183
196
|
|
|
197
|
+
server.registerTool(
|
|
198
|
+
'driftseal_absorb',
|
|
199
|
+
{
|
|
200
|
+
title: 'Absorb another DriftSeal lineage',
|
|
201
|
+
description:
|
|
202
|
+
'Repair the fixed repository after a merge collision or absorb another worktree\'s intent and decision logs, remapping colliding IDs. Omit otherLog to repair the current repository. This rewrites only the fixed repository; incoming paths are read-only sources.',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
otherLog: z
|
|
205
|
+
.string()
|
|
206
|
+
.optional()
|
|
207
|
+
.describe('Incoming events.jsonl path. Relative paths resolve from the fixed repository.'),
|
|
208
|
+
otherDecisions: z
|
|
209
|
+
.string()
|
|
210
|
+
.optional()
|
|
211
|
+
.describe('Incoming decision directory. Relative paths resolve from the fixed repository.'),
|
|
212
|
+
abandon: z
|
|
213
|
+
.enum(['ours', 'theirs'])
|
|
214
|
+
.optional()
|
|
215
|
+
.describe('Side whose open intent to abandon when both lineages have one in progress.'),
|
|
216
|
+
dryRun: z.boolean().default(false),
|
|
217
|
+
},
|
|
218
|
+
outputSchema: { root: z.string(), result: absorbResult },
|
|
219
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
220
|
+
},
|
|
221
|
+
async (input) =>
|
|
222
|
+
guarded(() => {
|
|
223
|
+
const result = api.absorb(input);
|
|
224
|
+
const action = input.dryRun ? 'Absorb dry run' : 'Absorb';
|
|
225
|
+
return success(
|
|
226
|
+
{ root: api.root, result },
|
|
227
|
+
`${action} completed with ${result.mappings.length} ID remapping(s).`
|
|
228
|
+
);
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
|
|
184
232
|
server.registerTool(
|
|
185
233
|
'driftseal_reclaim',
|
|
186
234
|
{
|
package/bin/driftseal.js
CHANGED
|
@@ -1709,6 +1709,19 @@ function directoryDigest(directory) {
|
|
|
1709
1709
|
return digest.digest('hex');
|
|
1710
1710
|
}
|
|
1711
1711
|
|
|
1712
|
+
function preserveRegularFileModes(source, destination) {
|
|
1713
|
+
for (const name of fs.readdirSync(source)) {
|
|
1714
|
+
const sourcePath = path.join(source, name);
|
|
1715
|
+
const destinationPath = path.join(destination, name);
|
|
1716
|
+
const stat = fs.lstatSync(sourcePath);
|
|
1717
|
+
if (stat.isDirectory()) {
|
|
1718
|
+
preserveRegularFileModes(sourcePath, destinationPath);
|
|
1719
|
+
} else if (stat.isFile()) {
|
|
1720
|
+
fs.chmodSync(destinationPath, stat.mode & 0o777);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1712
1725
|
function installSkill(request) {
|
|
1713
1726
|
const { force, root, scope, skillDir, skillsDir, target, targetLabel } = request;
|
|
1714
1727
|
const sourceDir = path.join(__dirname, '..', 'skills', SKILL_NAME);
|
|
@@ -1736,6 +1749,7 @@ function installSkill(request) {
|
|
|
1736
1749
|
let movedExisting = false;
|
|
1737
1750
|
try {
|
|
1738
1751
|
fs.cpSync(sourceDir, temporary, { recursive: true, errorOnExist: true });
|
|
1752
|
+
preserveRegularFileModes(sourceDir, temporary);
|
|
1739
1753
|
if (existingDigest !== null) {
|
|
1740
1754
|
fs.renameSync(skillDir, backup);
|
|
1741
1755
|
movedExisting = true;
|
|
@@ -3588,6 +3602,17 @@ function createApi({ root = process.cwd(), isolateStorage = false } = {}) {
|
|
|
3588
3602
|
if (all) argv.push('--all');
|
|
3589
3603
|
return call(argv);
|
|
3590
3604
|
},
|
|
3605
|
+
absorb({ otherLog, otherDecisions, abandon, dryRun = false } = {}) {
|
|
3606
|
+
if (abandon && !['ours', 'theirs'].includes(abandon)) {
|
|
3607
|
+
fail('absorb abandon must be "ours" or "theirs"');
|
|
3608
|
+
}
|
|
3609
|
+
const argv = ['absorb'];
|
|
3610
|
+
if (otherLog) argv.push(String(otherLog));
|
|
3611
|
+
appendFlag(argv, '--decisions', otherDecisions);
|
|
3612
|
+
if (abandon) argv.push(`--abandon-${abandon}`);
|
|
3613
|
+
if (dryRun) argv.push('--dry-run');
|
|
3614
|
+
return call(argv);
|
|
3615
|
+
},
|
|
3591
3616
|
reclaim({ ids = [], reason, olderThan, force = false, dryRun = false }) {
|
|
3592
3617
|
const argv = ['reclaim', ...ids.map(String), '--reason', reason];
|
|
3593
3618
|
appendFlag(argv, '--older-than', olderThan);
|