driftseal 2.0.0 → 3.0.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 +73 -11
- package/README.zh-CN.md +63 -9
- package/benchmark/recent-log.js +181 -0
- package/bin/driftseal-mcp.js +117 -4
- package/bin/driftseal.js +1034 -251
- package/lib/outcome-fold.js +377 -0
- package/lib/outcome-index-sqlite.js +802 -0
- package/lib/sqlite-runtime.js +71 -0
- package/package.json +7 -2
- package/skills/use-driftseal/SKILL.md +4 -1
- package/test/package-smoke.js +150 -0
|
@@ -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": "
|
|
3
|
+
"version": "3.0.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": ">=
|
|
51
|
+
"node": ">=22.13.0"
|
|
47
52
|
},
|
|
48
53
|
"dependencies": {
|
|
49
54
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
@@ -32,7 +32,10 @@ driftseal log --last 3
|
|
|
32
32
|
|
|
33
33
|
Then resume, extend, replace, verify, or close the outcome exactly as
|
|
34
34
|
`AGENTS.md` requires. Use `extend` only when the additional work still delivers
|
|
35
|
-
the same coherent outcome.
|
|
35
|
+
the same coherent outcome. `status` and `log --last 3` follow the current lane;
|
|
36
|
+
an open outcome stays visible even if it belongs to another lane. If the
|
|
37
|
+
requested work belongs to a different existing lane, switch first. For
|
|
38
|
+
command syntax, run:
|
|
36
39
|
|
|
37
40
|
```sh
|
|
38
41
|
driftseal help
|
|
@@ -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
|
+
}
|