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 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
 
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "driftseal",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Seal intent, verification, and decisions into an auditable workflow for agentic coding",
5
5
  "keywords": [
6
6
  "driftseal",