@boyingliu01/xp-gate 0.35.21 → 0.35.23

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.
Files changed (36) hide show
  1. package/adapters/kotlin.sh +53 -8
  2. package/bin/xp-gate.js +12 -0
  3. package/gate-13-compiler-strictness.sh +15 -1
  4. package/hooks/gate-13-compiler-strictness.sh +15 -1
  5. package/hooks/pre-commit +137 -9
  6. package/lib/__tests__/agent-runtime-journal.test.js +302 -0
  7. package/lib/__tests__/mirror-sync.test.js +277 -0
  8. package/lib/__tests__/runtime-journal.test.js +164 -0
  9. package/lib/agent-runtime-journal.js +347 -0
  10. package/lib/mattpocock-license-verdict.json +1 -1
  11. package/lib/mirror-sync.js +335 -0
  12. package/lib/runtime-journal.js +156 -0
  13. package/mock-policy/AGENTS.md +2 -2
  14. package/mutation/AGENTS.md +2 -2
  15. package/package.json +1 -1
  16. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  17. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  18. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  19. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  20. package/plugins/dsh/package.json +1 -1
  21. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  22. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  23. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  24. package/plugins/opencode/package.json +1 -1
  25. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  26. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  27. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  28. package/plugins/qoder/plugin.json +1 -1
  29. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  30. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  31. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  32. package/principles/AGENTS.md +2 -2
  33. package/scripts/delphi-external-review.cjs +68 -1
  34. package/skills/delphi-review/AGENTS.md +2 -2
  35. package/skills/sprint-flow/AGENTS.md +2 -2
  36. package/skills/test-specification-alignment/AGENTS.md +2 -2
@@ -0,0 +1,277 @@
1
+ /**
2
+ * TDD: written RED first (per AGENTS.md TDD-GATE spec).
3
+ * @test REQ-713 Mirror checks must ignore untracked build artifacts, and a
4
+ * one-shot sync must bring every mirror back to byte parity
5
+ * @intent The hook-lib mirror check failed on a machine where `githooks/lib`
6
+ * held an untracked `__pycache__/` — Python byte-compiled the helper during a
7
+ * test run. The canonical file set was otherwise byte-identical to the mirror.
8
+ * A false failure here costs a real fix cycle at commit time, which is exactly
9
+ * the rework this task set out to remove. The check must compare what git
10
+ * tracks, and a single command must repair drift instead of hand-copying.
11
+ * @covers AC-713-01
12
+ * @covers AC-713-02
13
+ * @covers AC-713-03
14
+ * @covers AC-713-04
15
+ */
16
+ 'use strict';
17
+
18
+ const fs = require('fs');
19
+ const os = require('os');
20
+ const path = require('path');
21
+ const { execFileSync } = require('child_process');
22
+
23
+ const {
24
+ collectMirrorPairs,
25
+ diffTrees,
26
+ syncMirrors,
27
+ handleSyncMirrors,
28
+ SYNC_EXCLUDES,
29
+ } = require('../mirror-sync');
30
+
31
+ let TMP_REPO;
32
+
33
+ beforeEach(() => {
34
+ TMP_REPO = fs.mkdtempSync(path.join(os.tmpdir(), 'mirror-sync-'));
35
+ execFileSync('git', ['init', '-q'], { cwd: TMP_REPO });
36
+ execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: TMP_REPO });
37
+ execFileSync('git', ['config', 'user.name', 'Test'], { cwd: TMP_REPO });
38
+ });
39
+
40
+ afterEach(() => {
41
+ fs.rmSync(TMP_REPO, { recursive: true, force: true });
42
+ });
43
+
44
+ /**
45
+ * Write a file, creating parent directories.
46
+ * @param {string} root
47
+ * @param {string} rel
48
+ * @param {string} content
49
+ */
50
+ function write(root, rel, content) {
51
+ const full = path.join(root, rel);
52
+ fs.mkdirSync(path.dirname(full), { recursive: true });
53
+ fs.writeFileSync(full, content);
54
+ }
55
+
56
+ /**
57
+ * Stage everything so the files count as tracked.
58
+ * @param {string} root
59
+ */
60
+ function trackAll(root) {
61
+ execFileSync('git', ['add', '-A', '-f'], { cwd: root });
62
+ }
63
+
64
+ describe('diffTrees — AC-713-01 untracked artifacts are not drift', () => {
65
+ it('ignores an untracked build artifact in the canonical tree', () => {
66
+ write(TMP_REPO, 'canonical/a.sh', 'echo a\n');
67
+ write(TMP_REPO, 'mirror/a.sh', 'echo a\n');
68
+ // The artifact that broke the real check: untracked, gitignored.
69
+ write(TMP_REPO, 'canonical/__pycache__/helper.pyc', 'binary');
70
+ write(TMP_REPO, '.gitignore', '__pycache__/\n');
71
+ trackAll(TMP_REPO);
72
+
73
+ const diff = diffTrees(TMP_REPO, 'canonical', 'mirror');
74
+ expect(diff.missing).toEqual([]);
75
+ expect(diff.differing).toEqual([]);
76
+ expect(diff.inSync).toBe(true);
77
+ });
78
+
79
+ it('still reports a genuinely missing mirrored file', () => {
80
+ write(TMP_REPO, 'canonical/a.sh', 'echo a\n');
81
+ write(TMP_REPO, 'canonical/b.sh', 'echo b\n');
82
+ write(TMP_REPO, 'mirror/a.sh', 'echo a\n');
83
+ trackAll(TMP_REPO);
84
+
85
+ const diff = diffTrees(TMP_REPO, 'canonical', 'mirror');
86
+ expect(diff.inSync).toBe(false);
87
+ expect(diff.missing).toContain('b.sh');
88
+ });
89
+
90
+ it('reports byte differences, not just missing files', () => {
91
+ write(TMP_REPO, 'canonical/a.sh', 'echo a\n');
92
+ write(TMP_REPO, 'mirror/a.sh', 'echo CHANGED\n');
93
+ trackAll(TMP_REPO);
94
+
95
+ const diff = diffTrees(TMP_REPO, 'canonical', 'mirror');
96
+ expect(diff.inSync).toBe(false);
97
+ expect(diff.differing).toContain('a.sh');
98
+ });
99
+
100
+ it('excludes declared noise directories even when they are tracked', () => {
101
+ write(TMP_REPO, 'canonical/a.sh', 'echo a\n');
102
+ write(TMP_REPO, 'mirror/a.sh', 'echo a\n');
103
+ write(TMP_REPO, 'canonical/node_modules/x.js', 'x');
104
+ write(TMP_REPO, '.gitignore', 'node_modules/\n');
105
+ trackAll(TMP_REPO);
106
+
107
+ const diff = diffTrees(TMP_REPO, 'canonical', 'mirror');
108
+ expect(diff.inSync).toBe(true);
109
+ expect(SYNC_EXCLUDES).toContain('node_modules');
110
+ });
111
+ });
112
+
113
+ describe('diffTrees — per-pair excludes', () => {
114
+ it('honours a pair-specific exclude, matching the guard script it mirrors', () => {
115
+ // check-hook-mirror.sh:98 compares src/principles with `-x __tests__`.
116
+ // A sync that copied tests into the package would ship test code and make
117
+ // the guard fail on the very next run.
118
+ write(TMP_REPO, 'canonical/lib.ts', 'export const a = 1;\n');
119
+ write(TMP_REPO, 'canonical/__tests__/lib.test.ts', 'test\n');
120
+ write(TMP_REPO, 'mirror/lib.ts', 'export const a = 1;\n');
121
+ trackAll(TMP_REPO);
122
+
123
+ const withoutExclude = diffTrees(TMP_REPO, 'canonical', 'mirror');
124
+ expect(withoutExclude.inSync).toBe(false);
125
+
126
+ const withExclude = diffTrees(TMP_REPO, 'canonical', 'mirror', { exclude: ['__tests__'] });
127
+ expect(withExclude.inSync).toBe(true);
128
+ expect(withExclude.missing).toEqual([]);
129
+ });
130
+ });
131
+
132
+ describe('collectMirrorPairs — AC-713-02 pair discovery', () => {
133
+ it('discovers a pair only when both sides exist', () => {
134
+ write(TMP_REPO, 'githooks/lib/a.sh', 'echo a\n');
135
+ write(TMP_REPO, 'src/npm-package/hooks/lib/a.sh', 'echo a\n');
136
+ trackAll(TMP_REPO);
137
+
138
+ const pairs = collectMirrorPairs(TMP_REPO);
139
+ const found = pairs.find((p) => p.canonical === 'githooks/lib' && p.mirror === 'src/npm-package/hooks/lib');
140
+ expect(found).toBeDefined();
141
+ });
142
+
143
+ it('does not invent a pair for a canonical tree with no mirror', () => {
144
+ write(TMP_REPO, 'githooks/lib/a.sh', 'echo a\n');
145
+ trackAll(TMP_REPO);
146
+
147
+ const pairs = collectMirrorPairs(TMP_REPO);
148
+ const found = pairs.find((p) => p.canonical === 'githooks/lib');
149
+ expect(found).toBeUndefined();
150
+ });
151
+ });
152
+
153
+ describe('syncMirrors — AC-713-03 repair instead of hand-copying', () => {
154
+ it('copies a missing file and produces parity', () => {
155
+ write(TMP_REPO, 'canonical/a.sh', 'echo a\n');
156
+ write(TMP_REPO, 'canonical/b.sh', 'echo b\n');
157
+ write(TMP_REPO, 'mirror/a.sh', 'echo a\n');
158
+ write(TMP_REPO, 'mirror/b.sh', 'echo b\n');
159
+ trackAll(TMP_REPO);
160
+
161
+ // Remove the mirror copy to simulate drift.
162
+ fs.rmSync(path.join(TMP_REPO, 'mirror/b.sh'));
163
+
164
+ const result = syncMirrors(TMP_REPO, [{ canonical: 'canonical', mirror: 'mirror' }]);
165
+ expect(result.copied).toContain('b.sh');
166
+ expect(diffTrees(TMP_REPO, 'canonical', 'mirror').inSync).toBe(true);
167
+ });
168
+
169
+ it('rewrites a differing file to match the canonical bytes', () => {
170
+ write(TMP_REPO, 'canonical/a.sh', 'echo canonical\n');
171
+ write(TMP_REPO, 'mirror/a.sh', 'echo stale\n');
172
+ trackAll(TMP_REPO);
173
+
174
+ syncMirrors(TMP_REPO, [{ canonical: 'canonical', mirror: 'mirror' }]);
175
+ expect(fs.readFileSync(path.join(TMP_REPO, 'mirror/a.sh'), 'utf8')).toBe('echo canonical\n');
176
+ });
177
+
178
+ it('is idempotent — a second run changes nothing', () => {
179
+ write(TMP_REPO, 'canonical/a.sh', 'echo a\n');
180
+ write(TMP_REPO, 'mirror/a.sh', 'echo a\n');
181
+ trackAll(TMP_REPO);
182
+
183
+ syncMirrors(TMP_REPO, [{ canonical: 'canonical', mirror: 'mirror' }]);
184
+ const second = syncMirrors(TMP_REPO, [{ canonical: 'canonical', mirror: 'mirror' }]);
185
+ expect(second.copied).toEqual([]);
186
+ expect(second.cleaned).toEqual([]);
187
+ });
188
+
189
+ it('supports --dry-run so the caller can preview before writing', () => {
190
+ write(TMP_REPO, 'canonical/a.sh', 'echo fresh\n');
191
+ write(TMP_REPO, 'mirror/a.sh', 'echo stale\n');
192
+ trackAll(TMP_REPO);
193
+
194
+ const result = syncMirrors(TMP_REPO, [{ canonical: 'canonical', mirror: 'mirror' }], { dryRun: true });
195
+ expect(result.copied).toContain('a.sh');
196
+ // Nothing was written.
197
+ expect(fs.readFileSync(path.join(TMP_REPO, 'mirror/a.sh'), 'utf8')).toBe('echo stale\n');
198
+ });
199
+
200
+ it('prunes a mirror file whose canonical source is gone', () => {
201
+ write(TMP_REPO, 'canonical/a.sh', 'echo a\n');
202
+ write(TMP_REPO, 'mirror/a.sh', 'echo a\n');
203
+ write(TMP_REPO, 'mirror/orphan.sh', 'echo orphan\n');
204
+ trackAll(TMP_REPO);
205
+
206
+ const result = syncMirrors(TMP_REPO, [{ canonical: 'canonical', mirror: 'mirror' }]);
207
+ expect(result.cleaned).toContain('orphan.sh');
208
+ expect(fs.existsSync(path.join(TMP_REPO, 'mirror/orphan.sh'))).toBe(false);
209
+ });
210
+ });
211
+
212
+ describe('handleSyncMirrors — CLI contract', () => {
213
+ let captured;
214
+ let outSpy;
215
+
216
+ beforeEach(() => {
217
+ captured = [];
218
+ outSpy = vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
219
+ captured.push(String(chunk));
220
+ return true;
221
+ });
222
+ });
223
+
224
+ afterEach(() => {
225
+ outSpy.mockRestore();
226
+ });
227
+
228
+ it('reports NOT MEASURABLE and exits 1 outside a repo with no pairs', () => {
229
+ expect(handleSyncMirrors([], { root: TMP_REPO })).toBe(1);
230
+ expect(captured.join('')).toMatch(/NOT MEASURABLE/i);
231
+ });
232
+
233
+ it('defaults to a dry run and does not modify files', () => {
234
+ write(TMP_REPO, 'githooks/lib/a.sh', 'echo canonical\n');
235
+ write(TMP_REPO, 'src/npm-package/hooks/lib/a.sh', 'echo stale\n');
236
+ trackAll(TMP_REPO);
237
+
238
+ const code = handleSyncMirrors([], { root: TMP_REPO });
239
+ // Dry run with pending work is a non-zero exit so CI can act on it.
240
+ expect(code).toBe(1);
241
+ expect(captured.join('')).toMatch(/Re-run with --write/);
242
+ expect(fs.readFileSync(path.join(TMP_REPO, 'src/npm-package/hooks/lib/a.sh'), 'utf8')).toBe('echo stale\n');
243
+ });
244
+
245
+ it('applies the repair with --write and reports it', () => {
246
+ write(TMP_REPO, 'githooks/lib/a.sh', 'echo canonical\n');
247
+ write(TMP_REPO, 'src/npm-package/hooks/lib/a.sh', 'echo stale\n');
248
+ trackAll(TMP_REPO);
249
+
250
+ expect(handleSyncMirrors(['--write'], { root: TMP_REPO })).toBe(0);
251
+ expect(fs.readFileSync(path.join(TMP_REPO, 'src/npm-package/hooks/lib/a.sh'), 'utf8')).toBe('echo canonical\n');
252
+ });
253
+ });
254
+
255
+ describe('syncMirrors — AC-713-04 safety', () => {
256
+ it('refuses a pair whose canonical directory does not exist', () => {
257
+ write(TMP_REPO, 'mirror/a.sh', 'echo a\n');
258
+ trackAll(TMP_REPO);
259
+
260
+ // Guessing "canonical is empty, so wipe the mirror" would delete real files.
261
+ expect(() => syncMirrors(TMP_REPO, [{ canonical: 'canonical', mirror: 'mirror' }])).toThrow(/canonical/i);
262
+ });
263
+
264
+ it('preserves the executable bit so hooks stay runnable', () => {
265
+ write(TMP_REPO, 'canonical/hook.sh', '#!/bin/sh\necho a\n');
266
+ fs.chmodSync(path.join(TMP_REPO, 'canonical/hook.sh'), 0o755);
267
+ write(TMP_REPO, 'mirror/hook.sh', '#!/bin/sh\necho a\n');
268
+ trackAll(TMP_REPO);
269
+
270
+ syncMirrors(TMP_REPO, [{ canonical: 'canonical', mirror: 'mirror' }]);
271
+ const mode = fs.statSync(path.join(TMP_REPO, 'mirror/hook.sh')).mode;
272
+ // On Windows the exec bit is not meaningful; only assert where it applies.
273
+ if (process.platform !== 'win32') {
274
+ expect(mode & 0o111).toBeTruthy();
275
+ }
276
+ });
277
+ });
@@ -0,0 +1,164 @@
1
+ /**
2
+ * TDD: written RED first (per AGENTS.md TDD-GATE spec).
3
+ * @test REQ-712 Agent runtime efficiency must be measurable from session journals
4
+ * @intent The runtime-journal CLI must resolve a session store, honour --days,
5
+ * and report NOT MEASURABLE instead of a silent all-zero report.
6
+ * @covers AC-712-07
7
+ * @covers AC-712-08
8
+ */
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+
15
+ const { handleRuntimeJournal, sessionRootCandidates, pickSessionRoot, parseDays } = require('../runtime-journal');
16
+
17
+ let TMP_DIR;
18
+ let outSpy;
19
+ let captured;
20
+
21
+ beforeEach(() => {
22
+ TMP_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'runtime-cli-'));
23
+ captured = [];
24
+ outSpy = vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
25
+ captured.push(String(chunk));
26
+ return true;
27
+ });
28
+ });
29
+
30
+ afterEach(() => {
31
+ outSpy.mockRestore();
32
+ fs.rmSync(TMP_DIR, { recursive: true, force: true });
33
+ });
34
+
35
+ /**
36
+ * Write a session dir containing an uncompressed journal.
37
+ * @param {string} root
38
+ * @param {string} name
39
+ * @param {Array<object>} events
40
+ */
41
+ function writeSession(root, name, events) {
42
+ const dir = path.join(root, name);
43
+ fs.mkdirSync(dir, { recursive: true });
44
+ fs.writeFileSync(path.join(dir, 'session.v4.jsonl'), events.map((e) => JSON.stringify(e)).join('\n') + '\n');
45
+ }
46
+
47
+ /**
48
+ * Emit a tool/call + tool/result pair.
49
+ * @param {string} callId
50
+ * @param {string} name
51
+ * @param {object} args
52
+ * @param {number} startT
53
+ * @param {number} endT
54
+ * @returns {Array<object>}
55
+ */
56
+ function pair(callId, name, args, startT, endT) {
57
+ return [
58
+ { type: 'tool/call', time: startT, data: { turn: 1, step: 1, callId, name, arguments: JSON.stringify(args) } },
59
+ { type: 'tool/result', time: endT, data: { message: { toolCallId: callId, content: [{ type: 'text', text: 'ok' }] } } },
60
+ ];
61
+ }
62
+
63
+ describe('parseDays', () => {
64
+ it('defaults when the flag is absent', () => {
65
+ expect(parseDays(['--json'], 3)).toBe(3);
66
+ });
67
+
68
+ it('reads --days N', () => {
69
+ expect(parseDays(['--days', '7'], 3)).toBe(7);
70
+ });
71
+
72
+ it('rejects a non-numeric value rather than treating it as 0', () => {
73
+ expect(parseDays(['--days', 'abc'], 3)).toBe(3);
74
+ });
75
+ });
76
+
77
+ describe('sessionRootCandidates', () => {
78
+ it('honours DSH_SESSIONS_DIR as the highest-priority override', () => {
79
+ const prev = process.env.DSH_SESSIONS_DIR;
80
+ process.env.DSH_SESSIONS_DIR = 'C:/custom/store';
81
+ try {
82
+ expect(sessionRootCandidates('xp-gate')[0]).toBe('C:/custom/store');
83
+ } finally {
84
+ if (prev === undefined) delete process.env.DSH_SESSIONS_DIR;
85
+ else process.env.DSH_SESSIONS_DIR = prev;
86
+ }
87
+ });
88
+
89
+ it('derives the store slug from the full path, not the basename', () => {
90
+ const prev = process.env.DSH_SESSIONS_DIR;
91
+ delete process.env.DSH_SESSIONS_DIR;
92
+ try {
93
+ // DSH slugs `D:\projects\xp-gate` to `--D-projects-xp-gate--`. A
94
+ // basename-only guess would produce `--xp-gate--`, which never exists.
95
+ const candidates = sessionRootCandidates('D:\\projects\\xp-gate');
96
+ expect(candidates.some((c) => c.endsWith('--D-projects-xp-gate--'))).toBe(true);
97
+ } finally {
98
+ if (prev !== undefined) process.env.DSH_SESSIONS_DIR = prev;
99
+ }
100
+ });
101
+ });
102
+
103
+ describe('pickSessionRoot', () => {
104
+ it('picks the first candidate that actually holds a journal', () => {
105
+ const store = path.join(TMP_DIR, 'store');
106
+ writeSession(store, 's1', pair('a', 'read', {}, 1000, 1010));
107
+ const missing = path.join(TMP_DIR, 'nope');
108
+ expect(pickSessionRoot([missing, store])).toBe(store);
109
+ });
110
+
111
+ it('returns null when no candidate holds a journal', () => {
112
+ expect(pickSessionRoot([path.join(TMP_DIR, 'nope')])).toBeNull();
113
+ });
114
+ });
115
+
116
+ describe('handleRuntimeJournal — AC-712-07 NOT MEASURABLE', () => {
117
+ it('reports NOT MEASURABLE and exits 0 when no journal is found', () => {
118
+ const code = handleRuntimeJournal(['--sessions-dir', path.join(TMP_DIR, 'absent')]);
119
+ expect(code).toBe(0);
120
+ expect(captured.join('')).toMatch(/NOT MEASURABLE/i);
121
+ });
122
+
123
+ it('reports measurable:false in JSON form so a machine reader sees the gap', () => {
124
+ handleRuntimeJournal(['--json', '--sessions-dir', path.join(TMP_DIR, 'absent')]);
125
+ const payload = JSON.parse(captured.join(''));
126
+ expect(payload.measurable).toBe(false);
127
+ expect(payload.reason).toMatch(/no session journals/i);
128
+ });
129
+ });
130
+
131
+ describe('handleRuntimeJournal — AC-712-08 report content', () => {
132
+ it('measures a real journal and emits the anti-pattern numbers', () => {
133
+ const store = path.join(TMP_DIR, 'store');
134
+ const now = Date.now();
135
+ writeSession(store, 's1', [
136
+ ...pair('j1', 'job_output', { job_id: 'b', wait: true }, now - 600000, now - 60000),
137
+ ...pair('s1', 'bash', { command: 'sleep 120' }, now - 50000, now - 1000),
138
+ ]);
139
+
140
+ const code = handleRuntimeJournal(['--json', '--sessions-dir', store, '--days', '1']);
141
+ expect(code).toBe(0);
142
+ const payload = JSON.parse(captured.join(''));
143
+ expect(payload.measurable).toBe(true);
144
+ expect(payload.blockingJobOutput.explicitWaitTrue).toBe(1);
145
+ expect(payload.idleWait.sleep.count).toBe(1);
146
+ });
147
+
148
+ it('excludes calls outside the --days window', () => {
149
+ const store = path.join(TMP_DIR, 'store');
150
+ const now = Date.now();
151
+ const oldT = now - 30 * 24 * 60 * 60 * 1000;
152
+ writeSession(store, 's1', pair('j1', 'job_output', { wait: true }, oldT, oldT + 600000));
153
+
154
+ handleRuntimeJournal(['--json', '--sessions-dir', store, '--days', '1']);
155
+ const payload = JSON.parse(captured.join(''));
156
+ expect(payload.blockingJobOutput.explicitWaitTrue).toBe(0);
157
+ });
158
+
159
+ it('always exits 0 — this is a diagnostic, not a gate', () => {
160
+ const store = path.join(TMP_DIR, 'store');
161
+ writeSession(store, 's1', pair('a', 'read', {}, 1000, 1010));
162
+ expect(handleRuntimeJournal(['--sessions-dir', store])).toBe(0);
163
+ });
164
+ });