driftseal 2.1.0 → 3.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.
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ const MINIMUM_NODE = Object.freeze({ major: 22, minor: 13, display: '22.13.0' });
4
+
5
+ class SqliteUnavailableError extends Error {
6
+ constructor(message, cause) {
7
+ super(message, cause ? { cause } : undefined);
8
+ this.name = 'SqliteUnavailableError';
9
+ }
10
+ }
11
+
12
+ function assertSupportedNode(version = process.versions.node) {
13
+ const [major, minor] = String(version)
14
+ .split('.')
15
+ .map((part) => Number.parseInt(part, 10));
16
+ if (major > MINIMUM_NODE.major || (major === MINIMUM_NODE.major && minor >= MINIMUM_NODE.minor)) {
17
+ return;
18
+ }
19
+ throw new Error(`DriftSeal requires Node.js ${MINIMUM_NODE.display} or newer; found ${version}`);
20
+ }
21
+
22
+ function warningType(args) {
23
+ const first = args[0];
24
+ if (typeof first === 'string') return first;
25
+ if (first && typeof first === 'object') return first.type || first.name || null;
26
+ return null;
27
+ }
28
+
29
+ function warningMessage(warning) {
30
+ if (warning instanceof Error) return warning.message;
31
+ return String(warning);
32
+ }
33
+
34
+ function loadNodeSqlite() {
35
+ assertSupportedNode();
36
+ if (process.env._DRIFTSEAL_TEST_DISABLE_SQLITE === '1') {
37
+ throw new SqliteUnavailableError('node:sqlite is unavailable in this runtime');
38
+ }
39
+ const originalEmitWarning = process.emitWarning;
40
+ process.emitWarning = function filteredEmitWarning(warning, ...args) {
41
+ if (
42
+ warningType(args) === 'ExperimentalWarning' &&
43
+ /\bSQLite\b/i.test(warningMessage(warning))
44
+ ) {
45
+ return;
46
+ }
47
+ return Reflect.apply(originalEmitWarning, this, [warning, ...args]);
48
+ };
49
+ try {
50
+ return require('node:sqlite');
51
+ } catch (error) {
52
+ if (error instanceof SqliteUnavailableError) throw error;
53
+ throw new SqliteUnavailableError('node:sqlite is unavailable in this runtime', error);
54
+ } finally {
55
+ process.emitWarning = originalEmitWarning;
56
+ }
57
+ }
58
+
59
+ let databaseSync;
60
+
61
+ function getDatabaseSync() {
62
+ if (!databaseSync) ({ DatabaseSync: databaseSync } = loadNodeSqlite());
63
+ return databaseSync;
64
+ }
65
+
66
+ module.exports = {
67
+ MINIMUM_NODE,
68
+ SqliteUnavailableError,
69
+ assertSupportedNode,
70
+ getDatabaseSync,
71
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "driftseal",
3
- "version": "2.1.0",
3
+ "version": "3.1.0",
4
4
  "description": "Seal outcomes, verification, and decisions into an auditable workflow for agentic coding",
5
5
  "keywords": [
6
6
  "driftseal",
@@ -34,16 +34,21 @@
34
34
  "files": [
35
35
  "index.js",
36
36
  "bin",
37
+ "lib",
38
+ "benchmark",
37
39
  "skills",
40
+ "test/package-smoke.js",
38
41
  "README.md",
39
42
  "README.zh-CN.md",
40
43
  "LICENSE"
41
44
  ],
42
45
  "scripts": {
46
+ "benchmark:recent-log": "node benchmark/recent-log.js",
47
+ "test:package": "node test/package-smoke.js",
43
48
  "test": "node --test test/*.test.js"
44
49
  },
45
50
  "engines": {
46
- "node": ">=18"
51
+ "node": ">=22.13.0"
47
52
  },
48
53
  "dependencies": {
49
54
  "@modelcontextprotocol/sdk": "^1.30.0",
@@ -47,13 +47,14 @@ If `status` or `log` fails with a duplicate id or with `multiple outcomes in
47
47
  progress`, or the outcome log has conflict markers, run `driftseal absorb`
48
48
  instead of editing `.seal/outcomes/events.jsonl`. When both sides still have an
49
49
  open outcome, add `--abandon-theirs` or `--abandon-ours`; this works whether your
50
- open outcome sits in the log or is parked in Git metadata. `driftseal init` also
50
+ open outcome sits in the log or is parked beside the WAL. `driftseal init` also
51
51
  configures the local git merge driver; clones need `init` again for that driver.
52
52
 
53
- In a Git worktree, `begin` does not dirty the tracked outcome log, so `git merge`
54
- can run with an outcome still in progress. `end` writes the closed record to
55
- `.seal/outcomes/events.jsonl`; if it is interrupted, the outcome stays open
56
- there and `end` can be run again.
53
+ In a default Git-repository seal, `begin` parks beside the WAL and does not dirty
54
+ the tracked outcome log, so `git merge` can run with an outcome still in
55
+ progress. Custom `$DRIFTSEAL_HOME` seals write that open outcome directly to
56
+ `events.jsonl`. `end` writes the closed record to `.seal/outcomes/events.jsonl`;
57
+ if it is interrupted, the outcome stays open there and `end` can be run again.
57
58
 
58
59
  For a v1 repository, do not reinterpret or delete the old logs directly. Use
59
60
  `driftseal migrate v1-to-v2 inspect`, prepare a reviewed grouping plan, apply
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+ const { execFileSync, spawnSync } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const root = path.resolve(__dirname, '..');
10
+ const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'driftseal-package-smoke-'));
11
+ const packDirectory = path.join(temporary, 'pack');
12
+ const consumer = path.join(temporary, 'consumer');
13
+
14
+ function npmCli() {
15
+ const npmJs = process.env.npm_execpath;
16
+ assert.equal(typeof npmJs, 'string', 'package smoke expects to run under npm (npm_execpath)');
17
+ assert.notEqual(npmJs, '', 'package smoke expects to run under npm (npm_execpath)');
18
+ assert.match(path.basename(npmJs), /\.js$/i, 'npm_execpath must be the JavaScript CLI');
19
+ assert.equal(fs.existsSync(npmJs), true, `npm_execpath exists: ${npmJs}`);
20
+ return npmJs;
21
+ }
22
+
23
+ function run(command, args, options = {}) {
24
+ return execFileSync(command, args, {
25
+ cwd: root,
26
+ encoding: 'utf8',
27
+ stdio: ['ignore', 'pipe', 'pipe'],
28
+ ...options,
29
+ });
30
+ }
31
+
32
+ function runNpm(args, options = {}) {
33
+ return run(process.execPath, [npmCli(), ...args], options);
34
+ }
35
+
36
+ function leftoverRebuildFiles(outcomeDir) {
37
+ return fs
38
+ .readdirSync(outcomeDir)
39
+ .filter(
40
+ (name) =>
41
+ name.startsWith('..outcome-index.sqlite.') && name.endsWith('.tmp')
42
+ );
43
+ }
44
+
45
+ function assertValidRebuiltIndex(indexFile, outcomeDir, runtime) {
46
+ assert.equal(fs.existsSync(indexFile), true, 'rebuilt SQLite index exists');
47
+ const raw = fs.readFileSync(indexFile);
48
+ assert.notEqual(raw.toString('utf8'), 'corrupt package smoke index');
49
+ assert.equal(raw.subarray(0, 16).toString(), 'SQLite format 3\0');
50
+
51
+ const leftovers = leftoverRebuildFiles(outcomeDir);
52
+ assert.deepEqual(leftovers, [], `temporary rebuild files remain: ${leftovers.join(', ')}`);
53
+
54
+ const DatabaseSync = runtime.getDatabaseSync();
55
+ const db = new DatabaseSync(indexFile, { readOnly: true });
56
+ try {
57
+ assert.equal(
58
+ Number(db.prepare('PRAGMA user_version').get().user_version),
59
+ runtime.INDEX_SCHEMA_VERSION
60
+ );
61
+ assert.equal(db.prepare('PRAGMA quick_check').get().quick_check, 'ok');
62
+ const outcomes = db
63
+ .prepare('SELECT id, ordinal, lane, status FROM outcomes ORDER BY ordinal')
64
+ .all();
65
+ assert.equal(outcomes.length, 1);
66
+ assert.equal(typeof outcomes[0].id, 'string');
67
+ assert.notEqual(outcomes[0].id, '');
68
+ assert.equal(Number(outcomes[0].ordinal), 0);
69
+ assert.equal(outcomes[0].lane, 'main');
70
+ assert.equal(outcomes[0].status, 'abandoned');
71
+ } finally {
72
+ db.close();
73
+ }
74
+ }
75
+
76
+ try {
77
+ fs.mkdirSync(packDirectory);
78
+ fs.mkdirSync(consumer);
79
+ fs.writeFileSync(
80
+ path.join(consumer, 'package.json'),
81
+ `${JSON.stringify({ name: 'driftseal-package-smoke', private: true }, null, 2)}\n`
82
+ );
83
+ const packed = JSON.parse(
84
+ runNpm(['pack', '--json', '--pack-destination', packDirectory])
85
+ );
86
+ assert.equal(packed.length, 1);
87
+ const tarball = path.join(packDirectory, packed[0].filename);
88
+ runNpm(['install', '--ignore-scripts', '--no-audit', '--no-fund', tarball], {
89
+ cwd: consumer,
90
+ });
91
+
92
+ const installed = path.join(consumer, 'node_modules', 'driftseal');
93
+ for (const file of [
94
+ 'bin/driftseal.js',
95
+ 'bin/driftseal-mcp.js',
96
+ 'lib/outcome-fold.js',
97
+ 'lib/outcome-index-sqlite.js',
98
+ 'lib/sqlite-runtime.js',
99
+ 'benchmark/recent-log.js',
100
+ 'test/package-smoke.js',
101
+ ]) {
102
+ assert.equal(fs.existsSync(path.join(installed, file)), true, `${file} is packaged`);
103
+ }
104
+
105
+ const sandbox = path.join(temporary, 'sandbox');
106
+ const home = path.join(sandbox, '.seal');
107
+ const outcomeDir = path.join(home, 'outcomes');
108
+ const indexFile = path.join(outcomeDir, '.outcome-index.sqlite');
109
+ fs.mkdirSync(sandbox);
110
+ const env = {
111
+ ...process.env,
112
+ DRIFTSEAL_HOME: home,
113
+ DRIFTSEAL_DECISION_HOME: path.join(home, 'madr'),
114
+ };
115
+ const cli = path.join(installed, 'bin', 'driftseal.js');
116
+ run(process.execPath, [cli, 'begin', 'packaged sqlite smoke'], {
117
+ cwd: sandbox,
118
+ env,
119
+ });
120
+ run(
121
+ process.execPath,
122
+ [cli, 'end', '--status', 'abandoned', '--note', 'packaged smoke'],
123
+ { cwd: sandbox, env }
124
+ );
125
+ const logged = spawnSync(process.execPath, [cli, 'log', '--last', '1'], {
126
+ cwd: sandbox,
127
+ env,
128
+ encoding: 'utf8',
129
+ stdio: ['ignore', 'pipe', 'pipe'],
130
+ });
131
+ assert.equal(logged.status, 0, logged.stderr);
132
+ assert.match(logged.stdout, /packaged sqlite smoke/);
133
+ assert.doesNotMatch(logged.stderr, /SQLite is an experimental feature/);
134
+ assert.equal(fs.existsSync(indexFile), true);
135
+ fs.writeFileSync(indexFile, 'corrupt package smoke index');
136
+ const rebuilt = run(process.execPath, [cli, 'log', '--last', '1'], {
137
+ cwd: sandbox,
138
+ env,
139
+ });
140
+ assert.match(rebuilt, /packaged sqlite smoke/);
141
+ assertValidRebuiltIndex(indexFile, outcomeDir, {
142
+ INDEX_SCHEMA_VERSION: require(path.join(installed, 'lib', 'outcome-index-sqlite.js'))
143
+ .INDEX_SCHEMA_VERSION,
144
+ getDatabaseSync: require(path.join(installed, 'lib', 'sqlite-runtime.js'))
145
+ .getDatabaseSync,
146
+ });
147
+ process.stdout.write(`package smoke passed: ${packed[0].filename}\n`);
148
+ } finally {
149
+ fs.rmSync(temporary, { recursive: true, force: true });
150
+ }